目录
一. 内部类
1.1 静态内部类
1.2 实例内部类
1.3匿名内部类
二. 接口的使用实例
2.1 Comparable接口
2.2 Comparator接口 ---比较器
2.3 Cloneable接口
深拷贝浅拷贝
一. 内部类
public class OutClass {class InnerClass {}}// OutClass 是外部类// InnerClass 是内部类
注:内部类和外部类共用同一个java源文件,但是经过编译之后,内部类会形成单独的字节码文件 ,命名格式:外部类名字$内部类名字.class
内部类可以分为四类:
1. 静态内部类
2. 实例内部类
3. 匿名内部类(最常用)
4. 局部内部类 (不常用,不做讲解)
1.1 静态内部类
被static修饰的内部成员类称为静态内部类。
上述代码, 我们可以看到,
1. 在静态内部类中定义的成员变量(方法), 可以是static修饰的,
2. 在静态内部类中的成员方法, 可以直接访问在静态内部类中定义的成员变量(方法)和在外部类中定义的static修饰的变量(方法), 但不能直接访问外部类中没有被static修饰的变量
3. 想要在静态内部类的方法中访问外部类的成员变量, 需要创建一个外部类型的对象, 通过对象去访问.
4. 在外部类中创建一个方法, 在这个方法里面, 可以直接创建静态内部类的对象
5. 在外部类之外, 想要创建静态内部类对象, 不需要先创建外部类对象, 必须通过外部类. 去使用
1.2 实例内部类
由上述代码, 我们可以得到:
1. 实例内部类中不能定义static修饰的变量(方法) , 如果想定义, 需要加final
2 . 在外部类中创建一个方法, 在这个方法里面, 可以直接创建实例内部类的对象
3. 在外部类之外, 想要创建实例内部类对象, 需要先创建外部类对象, 通过外部类对象的引用外部对象. 去创建实例内部类对象
或
4. 在实例内部类的方法中, 都可直接访问外部类和内部类中的任意访问限定符修饰的成员变量(方法)
5. 如果外部类和实例内部类中具有相同名称成员时,优先访问的是内部类自己的, 加this. 还访问自己的.
如果要访问外部类同名成员:外部类名称.this.同名成员名字
或像静态内部类一样, 创建一个外部类对象, 对对象的引用
1.3匿名内部类
我们先看一下什么是匿名对象
如果以后的场景是, 只是用一次对象, 那么可以使用匿名对象.
使用匿名内部类, 首先我们先创建一个接口
正常情况下我们是这样使用的
而匿名内部类, 可以不用创建类TestA, 直接匿名实现接口, 并对其重写
或更高阶的写法:
当然, 也可以匿名new类.
二. 接口的使用实例
2.1 Comparable接口
定义两个字符串
如何比较两个字符串的大小呢?
显然, 是错误的,java当中, 不可以直接比较引用类型的大小
那么, 我们就可以借助String类型里面的compareTo()方法进行比较
按住Ctrl我们点进compareTo发现 :
这个方法是通过重写接口中的方法得来的, 那么我们点进去黄框, 就会发现:
原来String类型, 可以进行字符串的比较, 是因为引用了Comparable这个接口, 然后在类中重写compareTo这个方法实现的
那么同理, 当我们自定义一个类, 创建两个对象时, 想要比较两个对象的大小, 我们就可以通过implements Comparable<>, 并在类中重写compareTo方法, 根据自己的需求, 自定义要比较的标准.
举例:
我们也可以以姓名为标准进行比较:
再举例:
定义一个数组, 通过Arrays.sort()对数组进行排序:
运行时我们发现报错:
原因是, Arrays.sort() 不知道以什么标准进行比较
所以Student类, 需要实现Comparable接口, 并重写compareTo方法
结果:
2.2 Comparator接口 ---比较器
比较什么, 就定义一个类, 实现Comparator接口, 重写compare方法, 将比较的对象当做参数传递过去, 非常灵活
再如上述对数组排序时, 可以使用Arrays.sort(数组名, 比较器), 这样在比较时, 就会使用比较器的方法进行排序.
2.3 Cloneable接口
现在, 我们定义一个对象, 我们想把第一个对象克隆到第二个对象
我们看到, Object类中有clone()方法, 我们想拿过来直接用, 发现:
不允许我们直接用, 我们点进去clone()方法可以看到:
它是被protected修饰的, 我们学过, 在不同包中, 想要访问protected修饰的方法, 必须是在子类中访问, 需要通过super进行访问, 所以我们要对clone方法进行重写.
利用快捷方法:Alt + Insert
这个方法就被创建好了.
我们看到:
返回类型是Object, 所以我们要调用这个方法时, 要进行向下转型
后面跟着一串throws CloneNotSupportedException, 这个我们先不管后面讲, 先照抄在main方法后面.
此时运行我们发现报错:
解决方法:
要实现一个Cloneable接口, 表示此类可以被克隆.
到此为止, 才算克隆成功.
我们点进去Cloneable接口可以看到:
里面什么也没有, 我们管这种接口叫做空接口/标记接口
一个类实现了空接口/标记接口, 那么表示当前这个类, 是可以被怎么样的, 如实现Cloneable, 表示此类可以被克隆.
深拷贝浅拷贝
什么是深拷贝浅拷贝呢?
我们看一个例子:
在对象里创建一个对象:
上述代码我们修改了person2中的m的money的值, 理论上来说输出的结果应该为:
person1还是6.6 ,person2被改成9.9 ,但是当我们运行起来后发现:
person1的值也被改了.
这是为什么呢? 我们画图理解一下:
所以person1, person2修改之前都是6.6. 此时, 两个m都指向同一块空间
所以, 修改money的值, person1和person2都变.
这就是浅拷贝, 只拷贝了原来的对象, 但对象里的对象没有被拷贝.
那么同理:深拷贝是指完全克隆出一个独立于原来的对象的对象
那么, 上述代码, 如果想要达到深拷贝效果, Money这个类必须也是一个可以拷贝的类, 所以Money类要实现Cloneable接口, 并重写父类中的clone方法.
这样我们就做好了准备工作, 但是在哪里怎么调用这个方法呢?
在Person的clone()里:
此时person1的m 和person2的m 是两个独立的对象, 相互不耽误. 所以, 修改person2的m的money的值, person1不变, person2变.
完整代码如下:
class Money implements Cloneable{
public double money = 6.6;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Person implements Cloneable{
public String name;
public Money m;
public Person(String name) {
this.name = name;
m=new Money();
}
@Override
protected Object clone() throws CloneNotSupportedException {
Person tmp = (Person) super.clone();
tmp.m = (Money) this.m.clone();
return tmp;
}
}
public class Test2 {
public static void main(String[] args)throws CloneNotSupportedException{
Person person1 = new Person("zhangsan");
Person person2 =(Person) person1.clone();
System.out.println("person1修改之前:"+person1.m.money);
System.out.println("person2修改之前:"+person2.m.money);
person2.m.money = 9.9;
System.out.println("person1修改之后:"+person1.m.money);
System.out.println("person2修改之后:"+person2.m.money);
}
}
可见, 是不是深拷贝, 是要看程序猿实现的怎么样.