①回调:可以指出某个特定事件发生时应该采取的动作。
例:java.swing.Timer可以使用它在到达给定的时间间隔时发出通告。
在构造定时器时,需要设置一个时间间隔,并告之定时器,当到达时间间隔时需要做些什么操作。要达到这样效果,在Java中,需将某个类的对象传递给定时器,然后定时器调用这个对象的方法。定时器需要知道调用哪一个方法,并要求传递的对象所属的类实现了java.awt.event中的ActionListener接口。
public interface ActionListener{
void actionPerformed(ActionEvent event);
}
class TimerPrinter implements ActionListener(){
public void actionPerformed(ActionEvent event(){
System.out.println("At the tone the time is "+new Date());
Toolkit.getDefaultToolkit().beep();
}
}
ActionListener listener=new TimerPrinter();
Timer t=new Timer(1000,listner)
t.start();
假设我们希望按长度递增的顺序对字符串进行排序,而不是字典顺序进行排序。肯定不能让String类用两种不同的方式实现compareTo方法。
要处理这种情况,Arrays.sort还有第二个版本,有一个数组和一个比较器作为参数。比较器是实现了Comparator接口的类的实例。
public interface Comparator{
int compare(T first,T second);
}
class LengthComparator implements Comparator{
public int compare(String first,String second){
return first.lengt()-second.length();
}
}
Comparator comp=new LengthComparator();
if(comp.compare(words[i],words[j]){
....
}
// 这个compare方法是在比较器对象上调用,而不是在字符串本身上调用
String[] friends={"Peter","Paul","Marry"};
Arrays.sorts(friends,new LengthComparator());
本节我们会讨论Cloneable接口,这个接口指示了一个类提供了一个安全的clone方法。
在展开对clone方法的讲解时,我们先回忆下为了一个包含对象引用的变量创建副本会发生什么。
原变量和副本都是同一个对象引用。(说明:任何一个变量改变都会影响另一个变量)
Employee orignal=new Employee("John Public",50000);
Employee copy=original;
如果希望copy是一个新对象,它的初始状态与orignal相同,但是之后它们各自会有自己 的不同的状态。这种情况下,只能使用clone()方法。
不过并没有那么简单。clone方法是Object类的一个protected方法,说明代码不能直接调用这个方法。只有Employee对象才可以克隆Employee对象。
a)如果对象中所有数据域都是数值或其他基本类型,拷贝这些域没有任何问题。
b)如果对象包含子对象的引用,拷贝域就会得到相同子对象的另一个引用。这样原对象与克隆的对象仍然会共享一些信息。默认的克隆操作是“浅拷贝”,并没有克隆对象中引用的其他对象。因此,我们通常需要重新定义clone方法来建立一个深拷贝,同时克隆所有的子对象。
对于每个类需要确定:
注意:Cloneable接口的出现就接口的正常使用没有什么关系。它没有指定clone方法。这个接口只是作为一个标记,指示类设计者了解克隆过程。但是,如果对象请求克隆,但没有实现这个接口,就会生成一个受检查异常。
class Employee implements Cloneable{
....
public Employee clone() throws CloneNotSupportedException{
return (Employee)super.clone();
}
}
要建立深拷贝的clone方法的一个例子:克隆对象中可变的实例域
class Employee implements Cloneable{
....
public Employee clone throws CloneNotSupportedException{
Employee cloned=(Employee)super.clone();
cloned.hireDay=(Date)hireDay.clone();
return cloned;
}
}
lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。
到目前为止,在Java传递一个代码块并不容易,不能直接传递代码块。Java是一种面向对象语言,所以必须构造一个对象,这个对象的类需要有一个方法包含所需的代码。
因此就引入了lambda表达式来解决这个问题
无需指定lambda表达式的返回类型。因为它总能由上下文推导得出。
a)lambda表达式形式1:参数,箭头(->),以及一个表达式。如果代码要完成的计算无法放在一个表达式中,就可以像写方法一样,把这些代码放在{}中。即使lambda没有参数,也需要提供空括号
①first.length()-second.length();
(String first,String second)->first.length()-second.length();
②()->{for(int i=100;i>=0;i--) System.out.println(i);}
b)lambda表达式形式2:如果可以推导一个lambda表达式的参数形式,可以忽略其参数
Comparatorcomp=
(first,second)->first.length()-second.length();
c)lambda表达式形式3:如果方法只有一个参数,而且这个参数的类型可以推导出,还可以省略小括号
ActionListener listener=event->System.out.println("The time is "+new Date());
lambda表达式与很多封装代码块的接口是兼容的。
函数式接口:对于只有一个抽象方法的接口。需要这种接口的对象时,就可以提供一个lambda表达式。
最好把lambda表达式看做是一个函数,而不是一个对象。
a)lambda可以传递到函数式接口
Arrays.sorts(friends,(first,second)->first.length()-second.length());
b)lambda表达式可以转换为接口。
Timer t=new Timer(1000,event->{
System.out.println("The time is "+new Date());
Toolkit.getDefaultToolkit().beep();
}
);
Timer t=new Timer(1000,event->System.out.println(event));
// 用方法引用来表示
Timer t=new Timer(1000,System.out::println);
// System.out::println等价于 x->System.out.println(x)
要用::操作符分隔方法名与对象或类名,主要有三种情况:
可在方法引用中使用this参数,this::equals相当于x->this.equals(x)
使用super也是合法的。super::instanceMethod
构造器引用于方法引用很类似,只不过方法名为new。
可以用数组类型建立构造器引用。int[]::new是一个构造器引用,它有一个参数是数组的长度,相当于x->new int[n];
lambda表达式有三部分:一个代码块、参数和自由变量的值。
表示lambda表达式的数据结构必须存储自由变量的值。
lambda表达式可以捕获外围作用域中变量的值,在这里有个重要的限制:lambda表达式中,只能引用值不会改变的变量(effectively final)。
lambda表达式的体与嵌套块有相同的作用域。这里同样适用于命名冲突和遮蔽的有关规定。
处理lambda表达式的重点就是 延迟执行。
Comparator接口包含很多方便的静态方法来创建比较器。这些方法也适用于lambda表达式或方法引用
例如:
Arrays.sort(people,Comparator.comparing(Person::getName);
Arrays.sort(people,Comparator.comparing(Person::getLastName).
thenComparing(Person::getFirstName));