JAVA核心技术卷I(接口、lambda表达式与内部类)
一、接口
1.1 接口的特点
接口中的方法自动都是public的,因此不必声明关键字public。
接口绝对不会有实例字段,在Java8之前,接口中绝对不会实现方法。(现在已经可以在接口中提供简单的方法了。当然们这些方法不能引用实例字段——接口没有实例)
提供实例字段和方法实现的任务应该由实现接口的那个类完成。(可以理解成没有实例字段的抽象类)
虽然不能有实例字段,但是可以包含常量。
在Java8中允许接口中增加静态方法(只是有违将接口作为抽象规范的初衷)。
1.2 默认方法
1.2.1 介绍
在前面加一个 default
关键字。
可以这么想,原先有一个接口,且有类实现了这个接口。后来为接口添加新的方法,如果这时候这个类没有实现这个新的方法就会报错,又或者没有报错,但是调用了这个方法依旧报错。因此添加一个默认方法,就可以解决这个问题。
1.2.2 默认方法冲突
超类优先
如果一个接口A和超类B都有一个getName的方法,那么派生类C同时继承了B,实现了A。这时候用的是超类B的getName方法。
接口冲突
如果接口A,B都有一个getName方法,而类C同时实现了A和B。这时,只要在类C中添加一个getName方法即可,在这个方法中可以选择两个冲突方法中的一个。如:
java// 这里假设A,B有默认实现
class C implements A, B {
public String getName() {return A.super.getName();}
}或者自己重新实现类C的getName方法。
1.3 接口与回调
P233
1.4 Comparable 和 Comparator 接口
Comparable :给自己创建的类添加可比较方法(实现compareTo方法,在对象上调用)
Comparator :对于已经存在的类(别人写的类),我们想要自定义它的比较方法。(实现compare方法,在比较器对象上调用,他不是一个静态方法,所以要new)
1.5 对象克隆
P239(基本不用这个)
默认的克隆操作是浅拷贝,这样对基本数据类型当然没有问题。但是,如果对象包含子对象的引用那么两个对象引用将会指向同一个堆内存,有时候这不是我们想要的(如果对象是只读的那么没问题,如果是可变的就有问题了)。
Cloneable接口是一个标记接口:即仅仅只是定义了一个接口,里面什么都没有。
不管clone的默认(浅拷贝)实现能否满足需求,还是需要实现Coneable接口,将clone重新定义为public,再调用super.clone()。
Java1.4 之前,clone方法返回类型总是Object,而现在可以为你的clone方法指定返回正确的返回类型。这就是协变返回类型:即子类覆盖(即重写)基类方法时,返回的类型可以是基类方法返回类型的子类。协变返回类型允许返回更为具体的类型。
二、lambda 表达式
2.1 lambda 表达式语法
即使lambda表达式没有参数,也要提供(),如果表达式只有一条语句不用写return,如果有多条要用{}而且要显式return。
如果可以推导出一个lambda表达式的参数类型,则可以忽略其类型。
如果只有一个参数,而且这个参数类型可以推导出来,那么可以省略()
2.2 函数式接口
对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式。
2.3 方法引用
P247-248
要用::运算符分割方法名与对象或类名:
object::instanceMethod:方法引用等价于向方法传递参数的lambda表达式
javaSystem.out::println
// 等价于
x -> System.out.println(x)Class::instanceMethod:第一个参数会成为方法的隐式参数
javaString::compareToIgnoreCase
// 等价于
(x, y) -> x.compareToIgnoreCase(y)Class::staticMethod:所有参数都传递到静态方法
javaMath::pow
// 等价于
(x, y) -> Math.pow(x, y)
注意:只有当lambda表达式的体只调用一个方法而不做其他操作时,才能把lambda表达式重写为方法引用。
方法引用不能独立存在,总是会转成函数式接口的实例。
2.4 构造器引用
Person::new
2.5 变量作用域
lambda表达式可以捕获外围作用域中的变量的值。闭包
lambda表达式中,只能引用值不会改变的变量(即final)。
lambda表达式中声明一个与局部变量同名的参数或局部变量是不合法的。
lambda表达式中this关键字,是指创建这个lambda表达式的方法的this参数。
2.6 处理 lambda 表达式
使用lambda表达式的重点是延迟执行。
如果要设计自己的接口,其中只有一个方法可以使用注解 @FunctionalInterface。这样有助于编译检查。
2.7 再谈 Compartor
P255 挺有用的,代码看起来简洁,明了。
三、内部类
3.1 内部类特点
- 内部类可以对同一个包中的其他类隐藏
- 内部类方法可以访问定义这个类的作用域中的数据,包括原本私有的数据
- 内部类不能有static方法
- 内部类中声明的所有静态方法必须是final
3.2 局部内部类
声明局部内部类时不能有访问说明符(即 public 或 private)。局部类的作用域被限定在声明这个个局部类的块中。
局部类对外部世界完全隐藏,除start方法之外,没有任何方法知道A类的存在:
public void start(boolean beep) { |
当start方法退出时,beep参数将不复存在。为了保证内部类A能够继续使用这个beep,将beep字段复制为start方法的局部变量。final boolean val$beep
3.3 匿名内部类
一般语法:
new SuperType(参数) { |
其中SuperType可以是接口(那么内部类就要实现这个接口)也可以是类(内部类就要扩展这个类)。
匿名内部类没有构造器,但是可以提供一个对象初始化块。
双括号初始化
invite(new ArrayList<String>() {{add("Harry"); add("Tony");}}); |
这里外层{}建立了ArrayList的一个匿名子类。内层{}则是一个对象初始化块。
但是这个技巧很少使用。invite方法可以直接传入:
List.of("Harry", "Tony"); |
3.4 静态内部类
只要内部类不需要访问外围对象,就应该使用静态内部类。
静态内部类可以用静态字段和方法。
在接口中声明的内部类自动是static和public。
不能访问外部非静态方法和字段。
四、代理
代理类包含一下方法:
- 指定接口所需要的全部方法
- Object类中的全部方法
不过,不能再运行时为这些方法定义新代码。
4.1 创建代理对象
需要使用Proxy类的newProxyInstance方法。这个方法有三个参数:
一个类加载器
一个Class对象数组,每个元素对应需要实现的各个接口
一个调用处理器
调用处理器是实现了InvocationHandler接口的类的对象。这个接口只有一个方法:
Object invoke(Object proxy, Method method, Object[] args)
使用代理的目的
- 将方法调用路由到远程服务器
- 再运行的程序中将用户界面时间与动作关联起来
- 为了调试,跟踪方法调用
动态代理
需要依赖接口!
package cn.wangbowen.java; |
cglib
https://www.cnblogs.com/jie-y/p/10732347.html
public class CglibProxy { |