内部类:
内部类的概述:把类定义在其他类的内部,这个类就被称为内部类。
内部类的访问特点:
内部类可以直接访问外部类的成员,包括私有。
外部类要访问内部类的成员,必须要创建内部类对象。
按照内部类在类中定义的位置不同,可以分为如下两种格式:
成员位置:成员内部类
局部位置:局部内部类
成员内部类:
1、定义在类的成员位置上
2、内部类可以访问外部类的成员,包括私有的。
如果想要在测试类中访问到成员内部类中的方法,就必须要创建对象。
正确创建成员内部类对象的格式:
成员内部类常见的修饰符:
private:其他类不能直接创建内部类的对象,要想使用被private修饰的内部类成员,必须在本类中间接的创建对象调用。
static:内部类如果被static修饰时,只能访问外部类中的静态成员。同时这里出现了另外一种创建内部类的方式:
在内部类中通过不同的访问方式访问不同的成员变量:
class Outer2{
public int num=10;
class Inner2{
public int num=20;
public void fun(){
int num=30;
System.out.println(num);
// 输出30
System.out.println(this.num);
// 访问本类中的num。输出20
// 想要访问Outer中的成员num,两种方式:
System.out.println(new Outer2().num);
// 通过创建对象访问Outer2中的num。输出10
// 这里不可以用super,因为Outer2与Inner2不是继承关系
System.out.println(Outer2.this.num);
// 通过访问Outer中的this.num,同样输出10
}
}
}
public class Test2Demo {
public static void main(String[] args) {
Outer2.Inner2 inner=new Outer2().new Inner2();
inner.fun();
}
}
局部内部类:
1、定义在方法中的类
2、局部内部类可以直接访问外部类中的所有成员
3、要想使用局部内部类的方法,在定义局部内部类的成员方法中,创建局部内部类对象调用方法。
局部类案例:
class Out{
private int num=10;
public void fun(){
int num2=20;
class Inner{
int num=30;
public void show(){
System.out.println(num);
System.out.println(num2);
// num2=34;
/*
在局部内部类中引用的本地的变量必须是最终变量或者实际上的最终变量
因为存在于局部内部类中的方法中定义的变量自动加上final关键字
即int num2=30;实际上完整代码应该是:final int num2=30;因此不可更改值
在JDK1.8之后会自动加上final关键字
*/
num=44;
System.out.println(num);
}
}
Inner i=new Inner();
i.show();
}
输出结果:
匿名内部类:
就是局部内部类的简写,匿名内部类存在的前提:
需要存在一个类或者接口,该类可以是抽象类也可以是具体的类。
使用匿名内部类创建对象案例:
interface Inter {
public abstract void show();
public abstract void show2();
}
/*
一开始的做法是需要定义一个类去实现接口,然后使用多态创建对象调用方法
class B implements Inter{
@Override
public void show() {
System.out.println("这是show方法");
}
@Override
public void show2() {
System.out.println("这是show2方法");
}
}
Inter inter=new B();
*/
class Outer3{
public void fun(){
// 使用匿名内部类的方式创建对象实现接口inter中的方法
new Inter(){
@Override
public void show() {
System.out.println("这是show方法");
}
@Override
public void show2() {
System.out.println("这是show2方法");
}
}.show();
// 使用匿名内部类的方式创建对象调用show方法
new Inter(){
@Override
public void show() {
System.out.println("这是show方法");
}
@Override
public void show2() {
System.out.println("这是show2方法");
}
}.show2();
// 使用匿名内部类的方式创建对象调用show2方法
/* 当将来需要使用匿名内部类去调用方法时,
若是方法很多则需要创建很多空间,很麻烦,因此给予匿名内部类一个名字
使用接口多态的形式去命名
*/
Inter inter = new Inter() {
@Override
public void show() {
System.out.println("这是show方法");
}
@Override
public void show2() {
System.out.println("这是show2方法");
}
};
inter.show();
inter.show2();
}
}
public class Test4Demo {
public static void main(String[] args) {
Outer3 outer3=new Outer3();
outer3.fun();
}
}
输出结果:
匿名内部类补齐代码例题:
分析:
1、根据main方法中调用的代码推出:method方法为静态,可以直接通过类名调用。
2、根据接口中的show方法以及mian方法中调用完method方法后还可以继续调用show可以推出:method方法有返回值,且返回值正好是接口Inter类型。
3、method方法的形参为空,推出:method方法没有参数
于是得到完整代码如下:这里直接使用匿名内部类创建对象并实现接口并返回
interface Inter2{
void show();
}
class Outer8{
//---------补齐代码部分---------------------------
public static Inter2 method(){
return new Inter2() {
@Override
public void show() {
System.out.println("HelloWorld");
}
};
}
//=========补齐代码部分===========================
}
public class InnerClassDemo9 {
public static void main(String[] args) {
Outer8.method().show();
}
}
常用类:
相对路径:将当前项目作为根目录(test.Test.src.com.changyonglei)
绝对路径/完整路径:带上盘符:(D:\IdeaProjects\src\test\Test\src\com\changyonglei\Student.java)
Object类:
Java帮助文档中的解释:
Object:Class Object是类Object结构的根。 每个class都有Object作为超类。 所有对象(包括数组)都实现了这个类的方法。 java中每个类都直接或者间接的继承了Object类
Object类中的方法:
public int hashCode():返回对象的哈希码值。 支持这种方法是为了散列表,如HashMap提供的那样。
注意:这里的哈希码值是根据哈希算法计算出来的一个值。这个值和地址有关系,但是这里返回的地址值并不是实际的地址值 现在就简单理解为地址值的另外一种表现形式。
public final Class getClass():返回的是该对象的类对象
返回此Object的运行时类。 返回的类对象是被表示类的static synchronized方法锁定的对象。
public class StduentTest {
public static void main(String[] args) {
Student s1 = new Student();
System.out.println(s1.hashCode()); // 1163157884
Student s2 = new Student();
System.out.println(s2.hashCode()); // 1956725890
Student s3 = s1;
System.out.println(s3.hashCode()); // 1163157884
System.out.println("=========================================");
Student s4 = new Student();
System.out.println(s4.getClass());
// class test.Test.src.com.changyonglei.Student
Class studentClass = s4.getClass();
// 返回由类对象表示的实体的名称(类,接口,数组类,原始类型或void),作为String 。
System.out.println(studentClass.getName());
// test.Test.src.com.changyonglei.Student
//链式编程
System.out.println(s4.getClass().getName());
// test.Test.src.com.changyonglei.Student
}
}
public String toString()返回对象的字符串表示形式。
一般来说, toString方法返回一个“textually代表”这个对象的字符串。
结果应该是一个简明扼要的表达,容易让人阅读。 建议所有子类覆盖此方法。
该toString类方法Object返回一个由其中的对象是一个实例,该符号字符`的类的名称的字符串@ ”和对象的哈希码的无符号的十六进制表示。
换句话说,这个方法返回一个等于下列值的字符串:
getClass().getName() + '@' + Integer.toHexString(hashCode())
简单理解:未重写前toString的实际意思就是:
该符号字符的类的名称的字符串+字符'@'+该对象的地址值
Integer: public static String toHexString(int i) :返回整数参数的字符串表示形式,作为16位中的无符号整数。 将哈希值转化一个地址值。
我们虽然掌握了toString()的方法使用,但是呢打印的一个结果是一个我们看不懂的地址值,换句话我们拿到这个结果没有意义 返回对象的字符串表示形式,实际上我们更想去看的是该对象中各个成员变量的值。
恰好toString()方法是被public修饰的,也恰好它的返回值是String类型的,所以我们可以在其他类中对它做重写
简单理解就是toString在重写之后表示的就是打印某个类中的各个成员变量的值
今后无特殊情况不需要自己手写,自动生成即可。
重写之后的例子:
public class Student2 extends Object {
private String name;
private int age;
public Student2() {
}
public Student2(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// 重写之后的toString方法
@Override
public String toString() {
return "Student2{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
// 测试类
public class StudentTest2 {
public static void main(String[] args) {
Student2 s = new Student2();
System.out.println(s.toString());
// 输出未进行赋值前的对象s中的成员变量的默认值
System.out.println("=======================================");
System.out.println(s.getClass().getName());
// 输出对象s所在文件的相对路径
System.out.println("=======================================");
// toString()等价于getClass().getName() + '@' + Integer.toHexString(hashCode())
// s.getClass().getName()+'@'+ Integer.toHexString(s.hashCode())
// this.getClass().getName()+'@'+ Integer.toHexString(this.hashCode())
System.out.println(s.toString());
System.out.println(s.getClass().getName()+"@"+Integer.toHexString(s.hashCode()));
// 输出未进行赋值前的对象s中的成员变量的默认值
// 输出相对路径+当前对象的地址值
System.out.println("========================================");
Student2 s2 = new Student2("小王", 18);
System.out.println(s2.toString());
// 输出赋值后的s中的成员变量的值
输出结果:
public boolean equals(Object obj):指示一些其他对象是否等于此。
今后我们想要弄清楚一个方法的实现的时候,想要弄明白结果是为什么的时候,看源码
将鼠标光标放置要看的方法上,按下ctrl+鼠标左键查看源码
而==比较引用数据类型的时候,比较是地址值,当地址值不一样的时候,返回的是false
==:
基本数据类型的时候:比较的是两个值是否一样
引用数据类型的时候:比较的是两个对象的地址值是否一样
equals:
只能比较的引用数据类型
实际开发的时候,调用equals方法更希望它比较的是成员变量的值是否一样
所以我们应该在子类中进行重写 不需要我们自己动手,自动生成即可
总结: 子类若是没有重写equals方法,使用的是父类Object类中的方法,比较的是地址值。 子类要是重写了equals方法,比较的是成员变量值是否相同。
测试equals:
public class StudentTest3 {
public static void main(String[] args) {
Student3 s1 = new Student3("小刘", 18);
Student3 s2 = new Student3("小刘", 18);
System.out.println(s1==s2); // false
Student3 s3 = s1;
System.out.println(s1==s3); // true
System.out.println("==========================");
System.out.println(s1.equals(s2)); // false //true
System.out.println(s1.equals(s3)); // true
System.out.println(s1.equals(s1)); // true
// 虽然我们搞清楚了equals的比较方式,但是我们观察现实生活中,姓名一样,年龄一样,就说明是同一
// 个人,应该返回的是true
Student3 s4 = new Student3("小王", 19);
System.out.println(s1.equals(s4));
}
}
输出结果:
protected void finalize(): 当垃圾收集确定不再有对该对象的引用时,垃圾收集器在对象上调用该对象。 一个子类覆盖了处理系统资源或执行其他清理的finalize方法。 简单理解这个方法为用于垃圾回收的,什么时候回收,不确定 GC机制,标记法。
protected Object clone() 创建并返回此对象的副本。 执行特定的克隆工作。
其他包中的子类要想使用被protected修饰的方法,使用super关键字调用。 clone的方法Object执行特定的克隆操作。
首先,如果此对象的类不实现接口Cloneable ,则抛出CloneNotSupportedException 。 一个类要想使用clone(),就必须实现Cloneable接口 通过观察API发现,Cloneable接口中没有常量,也没有抽象方法 今后看到类似于Cloneable一样,里面什么都没有的接口,我们称之为标记接口。
拷贝在IT行业中常见两种:
浅拷贝: 浅拷贝是指我们拷贝出来的对象的内部引用类型变量和原来的对象内部引用类型变量的地址值是一样的(指向的是同一个对象) 但是整个拷贝出来的对象和新对象不是同一个地址值。
深拷贝: 全部拷贝对象的内容,包括内存的引用类型也进行拷贝,拷贝的时候,重新创建一个对象,成员变量值和原来被拷贝的一样。 但是后续再对拷贝后的引用数据类型变量做修改,不会影响到原来被拷贝的。
拷贝案例:
import java.util.Objects;
//Student4实现了标记接口Cloneable,表示这个允许被克隆
public class Student4 extends Object implements Cloneable{
private String name;
private int age;
private Demo demo;
public Student4() {
}
public Student4(String name, int age,Demo demo) {
this.name = name;
this.age = age;
this.demo = demo;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Demo getDemo() {
return demo;
}
public void setDemo(Demo demo) {
this.demo = demo;
}
// @Override
// public String toString() {
//
//
@Override
public String toString() {
return "Student4{" +
"name='" + name + '\'' +
", age=" + age +
", demo=" + demo +
'}';
}
// return "姓名:" + name + ",年龄:" + age;
// }
//s1.equals(s2)
@Override
public boolean equals(Object o) {
//this -- s1
//o -- s2
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student4 student3 = (Student4) o;
return age == student3.age && Objects.equals(name, student3.name);
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
这里是测试类:
public class StudentTest4 {
public static void main(String[] args) throws CloneNotSupportedException {
Demo demo = new Demo();
Student4 s1 = new Student4("小刘", 18,demo);
System.out.println(s1.toString());
//这里其实隐含了一个多态
Object obj = s1.clone();
System.out.println(obj.toString());//输出拷贝后的成员变量值
System.out.println(s1.toString());//输出拷贝前的成员变量值
//拷贝前的demo与拷贝后demo的地址值做比较
//发现demo地址值是一样的
System.out.println(s1.getDemo().hashCode());//拷贝前demo方法的地址值
Student4 s4 = (Student4)obj;
System.out.println(s4.getDemo().hashCode());//拷贝后demo方法的地址值
//拷贝前整体对象的地址值与拷贝后整体对象的地址值
//发现拷贝后的地址值与原来对象的地址值不一样
System.out.println(s1.hashCode()); //1956725890 //拷贝前整体对象的地址值
System.out.println(obj.hashCode()); //356573597 //拷贝后整体对象的地址值
System.out.println("=================================");
//clone()的意义到底在哪里?
//要想搞明白这个意义,就得知道一个知识点(浅拷贝/深拷贝)
//clone()到底是浅拷贝还是深拷贝
//clone()是属于浅拷贝的
输出结果: