Java核心技术 卷I 第六章
第六章 接口、lambda表达式与内部类
文章目录
思维导图
6.1 接口
Comparable接口,带泛型
public interface Comparable<T> {
public int compareTo(T o);
}
关于Arrays.sort(Object[] a)方法
public static void sort(Object[] a) {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a);
else
ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
}
对一个数组排序,要求数组元素必须实现Comparable接口的
接口特性
1、接口不是类,可以用接口声明对象(多态)
2、可用instanceof检查对象是否实现了特定接口
3、接口可extends接口
4、接口不能有实例域
5、接口可以有静态方法带方法体(java8允许)
6、接口可有常量 public static final
7、接口中的方法自动带修饰符public,域自动带public static final(常量)
8、接口可什么也不声明,如Cloneable
public interface Cloneable {
}
9、接口的default方法可选择实现(带方法体)
接口标准格式:
public interface InterfaceName{
//域-常量
public static int finalVar = 1;
//方法-静态(有方法体)
public static void staticMethod(){
System.out.println("我是接口的静态方法");
}
//方法-实例(要实现的方法,无方法体)
public void instanceMethod();
//方法-默认(可选择实现的方法,有方法体)
public default void defaultMethod(){
System.out.println("我是接口的默认方法");
//默认方法可调用实例方法
instanceMethod();
}
//内部类-静态
public static class StaticInnerClass{
}
}
解决默认方法冲突
什么是默认方法冲突?
SubClass extends SuperClass implements Interface,SuperInterface{
}
子类继承超类又实现接口,接口中的默认方法会和超类的同名方法冲突
编译规则:
1)超类优先
超类中的public方法最大,protected和default方法均和接口中的默认方法冲突
解决超类和接口方法的冲突:选择一个实现即可
注意:
如果子类不在父类同包下,子类继承不了父类的同包方法
选择重写实现的方法修饰符都只能是public
父类中受保护的方法
@Override
public void protectMethod() {
super.protectMethod();//父类实现
Interface.super.protectMethod();//接口实现
}
父类中的同包方法
@Override
public void defaultMethod() {
super.defaultMethod();//父类实现
Interface.super.defaultMethod();//接口实现
}
2)接口冲突
1.类同时实现子接口和父接口,子接口继承父接口,两同名默认方法不会发生冲突
默认是得到了子接口的默认方法,且不允许改用父接口的默认
2.类同时实现接口1和接口2,两同名方法(其中一个为默认)会发生冲突
解决:选择一个实现
3.类同时实现接口1和接口2,两同名非默认方法,不会发生冲突
@Override
public void mMethod() {
Interface1.super.mMethod();//接口1实现
Interface2.super.mMethod();//接口2实现
}
Comparator接口
Arrays.sort方法的第二个版本,参数(数组,比较器)
比较器是实现了Comparator的实例
Comparator接口
public interface Comparator<T> {
int compare(T o1, T o2);
}
//LengthComparator实现了Comparator<T>接口,指定了T为String
//实现了compare方法
//用字符串长度作为比较依据
String[] friends = {"Peter","Paul","Tom"};
Arrays.sort(friends,new LengthComparator());
6.2 对象克隆
clone 方法是 Object 的一个 protected 方法
这说明你的代码不能直接调用这个方法
如a.clone();只能在a的内部调用
1)浅拷贝(默认)
如果原对象和浅克隆对象共享的子对象是不可变的,那么这种共享就是安全的
例子:域全是基本数据类型和不可变数据类型String
2)深拷贝
Cloneable接口没有指定clone方法,这个方法是从Object类继承的,这个接口什么都没有,只是作为一个标记,指示类的设计者。
对象对克隆很‘偏执’,如果一个对象请求克隆,但没有实现这个接口,就会产生一个受查异常
标记接口
没有方法,唯一用途就是允许在类型查询中使用instanceof
if(obj instanceof Cloneable){
...
}
实现接口重写clone
所有的克隆需求均需要实现Cloneable接口,将clone重定义为public,再调用super.clone();例:
class Employee implements Cloneable{
@Override
//重写Object的克隆方法,将其声明为public,并修改返回值类型
public Employee clone() throws CloneNotSupportedException{
return (Employee)super.clone();
}
}
如果在一个对象上调用clone,但这个对象的类并没有实现Cloneable接口,Object类的clone方法就会抛出一个CloneNotSupportedException,尽管是类和实例实现了Cloneable接口,但编译器不了解,仍然需要声明抛出异常
深拷贝案例
数组克隆
所有数组类型都有一个public的clone方法,可以用这个方法建立一个新数组。
int[] a = {1,2,3,4,5,6,7,8};
int[] cloned = a.clone();
6.3 lambda
lambda表达式即为一个函数式接口
语法格式一(单句表达式无 return):
(参数列表)->返回值表达式
语法格式二(多句显示return):
(参数列表)->{
语句1;
语句2;
return 返回值;
}
//单句表达式的lambda
Comparator<String> stringComparator = (String first, String second)
-> first.length() - second.length();
Consumer<String> stringConsumer = (String a) -> System.out.println(a);
6.3.3 函数式接口
java中已经有很多封装代码块的接口,如ActionListener或Comparator。
lambda表达式与这些接口是兼容的。
对于只有一个抽象方法的接口,需要这种接口的对象就可以提供一个lambda表达式
不能把lambda表达式赋值给Object,Object并不是函数式接口
6.3.4 方法引用
object::instanceMethod
Class::staticMethod
Class::instanceMethod
this::instanceMethod
super::instanceMethod
6.3.5 构造器引用
Person::new
int[]::new
6.3.6 lambda表达式内的变量
lambda的内部变量可捕获外围作用域的值,但必须是不会改变的量(final)
int b = 1;
(String a) -> System.out.println(b)
规则:
1)lambda表达式中捕获的变量必须实际上是最终变量(final)
2)lambda表达式声明的局部变量不能和之前的同名
3)lambda 表达式中使用this关键字,this指代创建这个表达式的方法的所属对象
6.4 内部类
内部类可访问定义其类所在作用域中的数据(包括私有)
内部类在外部同包中无法被访问(对同包隐藏)
6.4.2 内部类特殊语法规则
1.在内部类中调用外围类引用:OuterClass.this.域
2.反之,外部类new内部类对象:this.new 内部类名();
3.在外围类的作用之外,可以这样引用内部类:OuterClass.InnerClass
4.内部类不能有static方法
5.内部类静态域必须是final
6.4.3 内部类编译的class文件
Tes3t类中的内部类A和内部类B 还有Test3类本身
6.4.4 局部内部类
void m(){
class AAAA{
int i;
static final int b = 0;
public void m2(){
System.out.println("我是局部内部类");
}
}
new AAAA().m2();
}
局部内部类生成.class文件规则: 主类$编号局部内部类名.class
规则:
局部内部类不能用public 和private声明,作用域是声明局部类的块中
局部内部类的绝对优势:对外部完全隐藏,即使是最外部的本类中也无法访问。
6.4.6 匿名内部类
语法:
接口/超类 名字 = new 接口/超类(参数列表)
{
接口要实现的代码;
}
public static void main(String[] args) {
TestInterface testInterface = new TestInterface() {
@Override
public void m3() {
System.out.println("我被匿名内部类实现了");
}
};
testInterface.m3();
}
6.4.7 静态内部类
public class A{
public static class 类名{
}
}
静态方法中只能用静态内部类,否则抛异常
与常规内部类不同,静态内部类可以有静态域和方法
接口中也可以有内部类,自动修饰为public static class 类名{} 为静态内部类
6.5 代理
有接口class对象,如何构造这个实现类呢?
正常是使用反射的class.newInstance方法来找出这个类的构造器,但是不能实例化一个接口
代理:创建一个全新的类,该类proxy可代替我们想要扩充功能进行表演的类A,就是用proxy类代替A,扩充功能或者直接替代
1)代理类需要和被代理类实现同一个接口
提供调用处理器(invocation handler)
调用处理器是实现了InvocationHandler接口的类对象,实现的方法:
//proxy 是返回值,method是要增强的方法,args是方法的参数列表
Object invok(Object proxy,Method method,Object[] args)
6.5.2 创建代理对象
需要使用Proxy类的newProxyInstance方法,三个参数
1)一个类加载器,null为默认的类加载器
2)一个Class对象数组,每个元素都是需要实现的接口
3)一个调用处理器
定义一个处理器:
public class TraceHandler implements InvocationHandler {
private Object target;
public TraceHandler(Object target) {
this.target = target;
}
//等同于target.method(args);
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(target,args);
}
}
利用这个处理器来构建代理对象
不想构建处理器类,可以使用匿名内部类,第三个参数就是处理器,用匿名内部类+lambda表达式方式实现
public class DynamicTest {
public static void main(String[] args) {
Customer customer = new Customer();
//获得代理对象,用接口多态声明,对Proxy.newProxyInstance(...)强转
OrderInterface deliver = (OrderInterface)Proxy.newProxyInstance(
//参数1,一个类加载器
Customer.class.getClassLoader(),
//参数2,接口class对象
new Class[]{OrderInterface.class},
//参数3,处理器----对某些方法进行增强
(Object proxy, Method method, Object[] arg) -> {
//对部分原方法进行增强,也可以不增强
if(method.getName().equals("order")) {
System.out.println("增强下单方法");
return method.invoke(customer, arg);
}
else{
return method.invoke(customer, arg);
}
}
);
deliver.m1();//未增强方法
deliver.order("麻婆豆腐");//增强了的方法
System.out.println(deliver.getClass());//class com.sun.proxy.$Proxy0
}
}
那么这个生成的代理类对象究竟是什么class?
是com.sun.proxy包下的以$proxy开头的类名
对同一个类进行代理多次,都是$proxy0名字的类
代理第二个类(接口不变),则类名为:仍然是$proxy0
代理第二个不同接口的类,则类名为:$proxy1
依次按照接口不同编号递增