- 接口(interface):这种技术主要用来描述类具有什么功能,而并不给出每个功能的具体实现。
- lambda表达式:是一种表示可以在将来某个时间点执行的代码块的简介方法。
- 内部类(inner class):捏不累定义在另外一个类的内部,其中的方法可以访问包含它们的外部类的域,主要用于设计具有相互协作关系的类集合。
接口
接口概念
接口不是类,而是对类的一组需求描述,比如:Arrays类中的sort方法可以对对象数组进行排序,但要满足一个前提:对象所属的类必须实现了Comparable接口。
接口代码:
public interface Comparable<T>
{
int compareTo(T other);
}
任何实现Comparable接口的类都需要包含compareTo方法。
- 接口中的所有方法都自动的属于public,不必提供关键字
- 接口可以包含多个方法
- 接口中可以定义常量,变量
- 接口中绝不能含有实例域,不能构造接口对象
- 可以在接口中提供简单方法,但这些方法不能引用实例域——接口中没有实例
- 提供实例域和方法实现的任务应该由实现接口的那个类完成
为了让类实现一个接口,需要以下步骤
- 将类声明为实现给定的接口
class Employee implements Comparable
- 对接口中的所有方法进行定义
这里的Employee类需要提供compareTo方法,假设根据雇员薪水进行比较
在Employee类中:
public int compareTo(Object otherObject)
{
Employee other = (Employee) otherObject;
return Double.compare(salary, other.salary);
}
这里使用了静态方法Double.compare,如果第一个参数小于第二个参数,返回一个负值,如果二者相等返回0, 否则返回一个正值。
注意:声明接口时,CompareTo方法没有声明public,是因为所有方法默认时public,但实现接口时,必须把方法声明public,否则编译器将认为这个方法的访问属性是包可见(默认访问属性),编译器会给出提供更严格访问权限的警告信息
接口和抽象类
Java不支持多继承,每个类只能扩展一个类,假设Employee类已经扩展了person类,就不能再扩展第二个类了,但每个类可以实现多个接口,接口可以提供多重继承的大多数好处,同时可以避免多种继承的复杂性和低效性。
默认方法
可以为接口方法提供一个默认实现,必须用default修饰符标记这样一个方法。在有些情况下有用。
解决默认方法冲突
如果在一个接口中将一个方法定义为默认方法,然后又在超类或另一个接口定义了同样的方法:
- 超类优先,如果超类提供了一个具体方法,同名而且有相同参数类型的默认方法会被忽略
- 接口冲突,如果一个超接口提供了一个默认方法,另一个接口提供了一个同名而且参数类型相同的方法,必须覆盖这个方法来解决冲突。
对象克隆
Cloneable接口,这个接口提供了安全的clone方法,如果希望copy是一个新对象,它的初始状态与original相同,但之后各自会有自己的不同的状态,这种情况就可以使用clone方法。
clone方法是Object的一个protected方法,不能直接调用这个方法,原因:Object类对于要拷贝的对象一无所知,只能逐个区域进行拷贝,如果对象中的数据域都是数值或其他基本数据类型, 拷贝没有问题,但是如果对象包含子对象的引用,拷贝域就会得到相同子对象的另一个引用, 这样原对象和克隆对象会共享一些信息。
浅拷贝
默认的克隆操作没有克隆对象引用的其他对象,如果原对象和浅克隆对象共享的子对象是不可变的,这种共享就是安全的;如果子对象属于一个不可变类,如String,或者在生命周期中,子对象一直包含不变的常量,没有更改器去更改它,这种情况也是安全的。
但是,子对象通常是可变的,必须重新定义clone方法来建立一个深拷贝,同时克隆所有子对象,对于每个类,需要确定
- 默认的clone方法是否满足要求
- 是否可以在可变的子对象上调用clone来修补默认的clone方法
- 是否不该使用clone
- 实现Cloneable接口
- 重新定义clone方法,并指定public修饰符
子类只能调用受保护的clone方法来克隆它的对象,必须重新定义clone为public才能允许所有方法克隆对象。Cloneable接口的出现与接口的使用没有关系,它是Java提供的一组标记接口之一,只是作为一个标记,Cloneable接口的用途就是确保一个类实现一个或一组特定的方法,标记接口不包含任何方法,唯一的作用就是允许在类型查询中使用instanceof;
即使clone的默认实现(浅拷贝)能满足要求,还是需要实现Cloneable接口,将clone重新定义为public,在调用super.clone()
class Employee implements Cloneable
{
//提高访问权限到public
public Employee clone() throw CloneNotSupportException
{
//改变返回值类型
return (Employee)super.clone();
}
}
深拷贝
只将方法变为public还不够,要建立深拷贝,需要拷贝可变的实例域。
如果在一个对象上调用clone,但这个对象没有实现Cloneable接口,Object类的clone方法就会抛出一个CloneNotSupportedException。
lambda表达式
传入代码来检查一个字符串是否比另一个字符串短
first.length() - second.length()
first和second都是字符串,还需指定它们的类型
(String first, String secong)
-> first.length() - second.length()
这就是lambda表达式。
参数、箭头(->)、表达式,如果代码要完成的计算无法放在表达式中,就可以像写方法一样放在{}中,并包含return语句
(String first, String second) ->
{
if(first.length() < second.length()) return -1;
else if (first.length() > second.length()) return 1;
else return 0;
}
- 即使表达式没有参数,也要提供括号,就像无参构造方法一样
() -> {for (int i = 100; i>=0; i--) System.out.println(i); }
- 如果可以推导出表达式的参数类型,就可以忽略其类型
- 如果方法只有一个参数,而且这个参数的类型可以推导出,就可省略小括号
- 无需指定表达式的返回类型,lambda表达式的返回值会根据上下文推导得出
函数式接口
对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式,这种接口称为函数式接口。
为什么函数接口必须有一个抽象方法,不是接口中的所有方法都是抽象的吗?
接口完全可能重新声明Object类的方法,如toString、clone,这些声明可能会使方法变的不再是抽象的,Java SE8中,接口可以声明非抽象方法。
方法引用
有时可能已经有现成的方法完成你想要传递给其他代码的某个动作,如:加入希望只要出现一个定时器事件就打印这个事件对象
Timer t = new Timer(1000, event -> System.out.println(event))
如果直接将println方法传递到Timer构造器就更好了
Timer t = new Timer(1000, System.out::println)
System.out::println 是一个方法引用,等价于表达式x -> System.out.println(x)
例二:加入想要对字符串排序而不考虑大小写,可有传递以下方法表达式
Arrays.sort(strings, String::compareToIgnoreCase)
用::操作符分隔方法名与对象或类名,主要有三种情况
- object::instanceMethod
- Class::staticMethod
- Class::instanceMethod
前两种情况,方法引用等价于提供方法参数的lambda表达式,
例如:System.out::println 等价于 x -> System.out.println(x)
Math::pow 等价于 (x,y) ->Math.pow(x,y)
第三种情况, 第一个参数会成为方法的目标,
例如:String::compareToIgnoreCase 等价于 (x, y) -> x.compareToIgnoreCase(y)
可以在方法引用中使用this参数, this::quals 等同于 x -> this.equals(x), 使用super也是合法的,
super::instanceMethod
使用this作为目标,会调用给定方法的超类版本。
具体双冒号的理解:双冒号的理解
构造器引用
构造器引用和方法引用很相似, 只不过方法名为new, 例如:Person:: new是Person构造器的一个引用, 具体引用哪个构造器取决于上下文。
可以用数组类型建立一个构造器引用, 例如:int[]::new是一个构造器引用,他有一个参数,即数组的长度,等价与lambda表达式 x -> new int[x]
处理lambda表达式
lambda表达式的重点就是延迟执行, 希望以后再执行的原因很多,如
- 在一个单独额线程中执行代码
- 多次运行代码
- 在算法的合适位置运行代码
- 某种情况时运行代码
- 只有必要时才运行代码
内部类
定义在另一个内部的类,使用内部类的原因
- 内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据。
- 内部类可以对同一个包中的其他类隐藏起来。
- 想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。
编译器会把内部类翻译成用$分隔外部类名与内部类名的常规文件, 如TalkingClock类内部的TimdePrinter类会被翻译成文件TalkingClock $ TimdePrinter
局部内部类
如果一个内部类只在一个方法中创建这个类型的对象时使用了一次,可以在方法中定义一个局部类,局部类不能用public或private声明,它的作用域被限定在生命这个局部类的块中。局部内部类有一个优势:对外部完全隐藏,即使外围类中的其他代码也不能访问。除了创建的方法以外没有任何方法知道这个类的存在。
局部内部类还有一个优点:它不仅能访问包含它们的外部类,还可以访问局部变量,不过这些局部变量必须是final,一旦被赋值就不会改变。
匿名内部类
将局部内部类的使用在深入一步,假如只创建这个类的一个对象,就不必命名了,称为匿名内部类。
静态内部类
有时使用一个内部类只为了把类隐藏在另外一个类的内部,并不需要内部类引用外围类对象,因此可以将内部类声明为static,以便取消产生的引用。