接口 、Lamda表达式
-
接口不是类,而是对类的一组需求描述
-
接口的所有方法自动属于public,不过在实现接口时,必须将实现方法声明为public,否则,编译器认为试图给出更严格的访问权限的警告,接口可以定义常量,但接口不能有实例变量
-
类实现一个接口,需要步骤为:
- 将类声明为实现给定的接口
- 对接口内的所有方法进行定义
-
compare的比较,常规的相减操作时明确比较的值是非负整数或者其值在(Integer.MAX_Value-1)/2的范围内,否则用Integer.compare来替换常规的相减操作,相减技巧也不适用与浮点数,可用Double.compare来替换。
-
语言标准规定,对于任意X和Y,实现必须能够保证sgn(x.compareTo(y))=-sign(y.compareTo(x)),这里的sign是一个符号,如果n是负值时,sign(n)=-1,如果n是正值时,sign(n)=1,如果n为0时,sign(n)=0
-
接口的特性:
- 接口不是一个类,尤其是不能使用new运算符实例化一个接口
- 尽管不能实例化一个接口,但是能声明接口变量,接口变量的引用必须是实现了接口的类对象
- 可以使用instanceof检查一个对象是否实现了某个特定接口
- 接口可以被扩展
- 接口不能包含静态域和实例域,但能有常量,接口的方法自动标记为public,域自动标记为public static final
- jdk1.8,允许在接口中增加静态方法,虽然可以,但是不建议,因为有违将接口作为抽象规范的初衷,通常做法是将静态方法放到伴随类中。
-
可以为接口提供一个默认实现方法(实现类中可重写),必须用default修饰符标记这样的一个方法。
-
默认方法一个重要用法是接口演变。例如定义了一个接口很久之后,再为其新增一个方法。但是由于这方法是新增的,所以事先改接口的类没有实现该方法,会导致实现类不能通过编译,也即“代码不兼容”的问题,即使用原来实现类的jar文件,但如果调用新增方法,则会报错。
-
默认方法冲突:即一个接口定义了默认方法,但在超类或另一个接口中也同样定义了一相同的方法:
- 超类优先,如果超类提供了一个具体的方法,默认方法会被忽略。类优先的规则确保了与jdk7代码兼容。(注意,不要在接口中的默认方法冲新定义Object的某个方法,因为由于类优先规则,导致没有意义)
- 接口冲突,两个接口有完全相同的默认方法(或者有相同方法,但至少有一个接口提供了对方法的实现),java编译器会让程序员自己来选择,即在实现类中冲写该方法,然后在方法内用 接口名.super.方法名
-
默认的clone是浅拷贝,即拷贝了基本数据类型,但引用对象还是同一个引用。
-
对于每一个类的拷贝,需要确定一下条件:
- 默认的clone方法是否满足需求
- 是否可以在可变的子对象上调用clone方法修补默认的clone方法
- 是否不该使用clone方法。
- 实际上第3个选项是默认选项,如果选择第一项或者第2箱,类必须
- 实现Cloneable接口
- 重新定义clone方法,并制定public访问修饰符
-
需要理解的是Object类中的clone为什么定义为protected?既然clone是object类中的方法,为什么还要实现Cloneable接口呢?
-
上述第一个问题:定义为protected是为了让保证对象用用一定重写了clone,设置为protected,子类只能调用clone来克隆自己的对象,若要克隆其他对象,则必须要其他对象自己调用clone,这样克隆引用对象就必须宝恒该对象重写了clone方法,确保该对象已经深拷贝过了。且必须重新定义clone为public才能允许所有方法克隆该对象(这个理解可能为只有设置为public,对象在其他类中也才能调用克隆,否则,会因为包的局限性问题?)
-
上述第二个问题:Cloneable接口是java提供的一组标记接口之一,其通常用途是一个类实现一个或一组特定方法。注意标记接口不包含任何方法。除此外的作用允许在类型查询中使用instanceof。
-
深拷贝是为每个引用对象通过clone克隆,浅拷贝是直接调用super.clone方法。
-
lamda表达式是一个可传递的代码块,可以在以后执行一次或多次。因为java是面向对象的,如果要传递代码块就要构建一个对象来传递。
-
一个lamda表达式,如果只在某些分支返回一个值,而在另一个分支不返回值,这是不合法的。
(int x) -> {if(x>0) return 1;}这是不合法的
-
函数式接口(lamda表达式和函数式接口的关系:应该是要有函数式解耦,才能使用lamda表达式):
- 对于只有一个抽象方法的接口,需要这种接口对象时,就可以提供lamda表达式,注意是 只有一个抽象方法 而default和静态方法有实现,故不算抽象方法。
- lamda表达式不是Object对象的子类,其是一个函数式接口,可以赋给一个函数式接口变量
-
方法引用是有现成的方法来完成 传递代码 的动作
-
方法引用的情况:
- object::instanceMethod :等价于提供参数的lamda表达式,如System.out::println等价于(x)->{System.out.println(x)}
- Class::staticMethod :等价于提供参数的lamda表达式
- Class::instanceMethod :这种情况与上两种不同,第一个参数会成为方法的目标,
-
构造器引用和方法引用类似,只不过方法名为new,即Person::new
-
lamda 表达式可以捕获外围作用域中变量引用不会改变的值,类似final修饰,但不被修饰也行,只要全局中没被改变(外部改变也不行),且lamda表达式中只能读取不能改变引用就行(即lamda表达式中不改变引用,但能改引用对象中的值),因为如果在lamda表达式中改变,并发执行多个动作会不安全?因为lamda表达式是替换匿名内部类的,而匿名内部类 注意解决该问题
-
lamda表达式捕获的变量是最终值,也就是变量初始化后,变量引用不能再更改。
-
在一个lamda表达式中使用了this关键字,是指创建lamda表达式的方法所对应的的this。
-
使用lamda表达式的重点是延迟执行,lamda表达式是替换匿名内部类的,而匿名内部类是调用再执行,避免可性能的消耗
public class LoggerDemo { private static void log(int level, MessageBuilder builder) { if (level == 1) { System.out.println(builder.buildMessage()); } } public static void main(String[] args) { String msgA = "Hello"; String msgB = "World"; String msgC = "Java"; long start = System.currentTimeMillis(); //条件不通过的情况,就不会执行lamda表达式 log(2, () -> { System.out.println("Lambda执行!"); return msgA + msgB + msgC; }); long end = System.currentTimeMillis(); System.out.println("未延迟执行花费时间:"+(end-start)); } } /== * 下面这个是函数式接口,方便lambda表达式的使用。 */ @FunctionalInterface interface MessageBuilder { String buildMessage(); }
-
lamda表达式执行的场景:
- 在一个单独的线程中运行代码
- 多次运行代码
- 在算法适当位置运行代码
- 发生某种情况时执行代码
- 只在必要时才运行代码
-
常用的函数是接口,即java提供了函数式接口,可以直接用,无需再重新定义函数式捷库
- Runnable :参数为空,返回值为空
- Supplier:参数为空,返回值为T(泛型,一般可以任意)
- Consumer:参数为T,返回值为空
- BiConsumer<T,U> :参数为T,U,返回值为空
- Function<T,R>:参数为T,返回值为R
- BiFunction<T,U,R>:参数为T,U,返回值为R
- UnaryOperator:参数为T,返回值为T
- BinaryOperator,参数为T,T,返回值为T
- Predicate:参数为T,返回值为boolean
- BiPredicate<T,U>:参数为T,U,返回值为boolean
-
如果有基本类型,为了避免装箱和拆箱的操作,可以使用下列函数式接口(pq为int,long,double.PQ为Integer,Long,Double)
- BooleanSupplier 参数为空,返回为boolean
- PSupplier 参数为空,返回p(int ,double,long)
- PConsumer 参数为p,返回值为void
- ObjPConsumer 参数类型为T,p,返回类型为空
- PFunction ,参数类型为p,返回类型为T
- PToQFunction 参数类型为p,返回类型为q
- ToPFunction,参数类型为T,返回类型为p
- ToBiFunction<T,U> 参数类型为T,U,返回类型为p
- PUnaryOperator 参数类型为p,返回类型为p
- PBinaryOperator 参数类型为p,p,返回类型为p
- PPredicate 参数类型为p,返回类型为boolean
-
如果自己设计函数式接口时,可以使用@FunctionalInterface来标注明这是一个函数式接口
-
使用内部类的原因为:
- 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据
- 内部类可以对同一个包中的其他类隐藏起来
- 当想要定义一个回调函数且不想编写大量代码时,可以使用匿名内部类
-
内部类既可以访问自身的数据域,也可以访问创建他的外围类对象 的数据域
-
内部类对象总有一个隐式引用,他指向创建他的外围类对象,编译器修改了所有的内部类构造器,添加了一个外围引用参数
-
只有内部类可以是私有类(但虚拟机中不存在私有类),而常规类只可以具有包可见性或公有可见性
-
内部类可以用OuterClass.this表示外围类引用,如TalkingClock.this.name,也可以使用outerClass.new InnerClass()表示内部类的构造器引用。外围类作用域外,可以OuterClass.InnerClass来引用内部类
-
内部类中声明的所有静态域都必须是final,首先用static是表示所有对象共有的,但是对于每个外部对象都会分别有一个自己的单独内部类实例空间,如果不加final修饰,会导致不唯一,
-
java规范表明内部类不能有static 方法,但实际上也可以有static方法,不过只可以访问外围静态域和静态方法,为什么不呢能有呢,因为静态方法加载会在类加载时加载,但是内部类如果不是静态的,加载外部类就不会加载,其类似于外部类的一个实例变量,也就不能只能实例化内部类才会有,这就很矛盾。
-
内部类是一种编译器现象,与虚拟机无关,编译器会将内部类翻译成用$来分格外部类类名和内部类类名的常规类文件
-
局部内部类不能使用public或private访问复进行声明,作用域被限制在声明这个局部类的代码块中
-
局部类可以对外部世界完全隐藏起来,即外部世界不可感知到这类,即使在代码块的类中。
-
局部类不仅可以访问包含他的外部类,也可以访问局部变量,不过局部变量必须事实上为final,即引用不会更改,外部类实例可以不为final,
-
局部内部类由于声明周期的不一致性,所以可能会对外部类域通过局部变量进行备份。(这个理解稍弱,以后自己查下资料)
-
编译器必须检测对局部的访问,为每一个变量建立相应的数据域,并将局部变量拷贝到构造器中,以便将这些数据域初始化为局部变量的副本。和前面提到的局部变量事实上为final照应,只有为final,初始化会引用不改变,这样局部变量与在局部类中建立的拷贝保持一致
-
匿名内部类不能有构造器,取而代之的是将构造器参数传给超类构造器(不太懂,以后自己去查下资料)
-
匿名内部类可以使用双括号初始化。如;
new ArrayList(){{add("hary");add("liko")}};
-
匿名子类对于冲写equals方法的if(getClass() !=otherClass.getCLass()) return false; 会失效
-
如果要使用getCLass,可以用new Object(){}.getClass().getEnclosingClass()获得其外围类
-
静态内部类没有生成对他的外围类对象引用,在不需要方位外围类对象时,应该将内部类声明为静态内部类,静态内部类可以有静态域和静态方法
-
声明在街口的内部类自动转换成为static 和public 修饰的类
-
代理(理解不清晰,要反复查资料)
-
动态代理是在编译时无法确定类型,代理类可以在运行时创建全新的类,这样代理类能够实现指定接口,一把需要提供一个调用该处理器,一个代理类只有一个实例域,即调用处理器,代理类一定是static 和final
-
创建代理对象,需要使用Proxy类的newProxyInstance方法,参数为:一个类加载器 、2,一个Class对象数组,每个元素都是要实现的接口、 3, 一个调用处理器
-
所有的代理类都覆盖(重写)了object的toString、equals和hashcode,而其他方法美哦与被重写