目录
前五章为Java的全部基本知识。本章讲开始介绍几种常用的高级技术。
接口(interface)技术,主要用来描述类具有什么功能,而并不给出每个功能的具体实现。一个类可以实现(implement)一个或多个接口,并在需要的地方,可以随时使用实现了相应接口的对象。
lambda表达式是一种表示可以在将来某个时间点执行的代码块的简洁方法(一个表示可执行代码块的简洁方法)。通过使用lambda表达式,可以用一种精巧而简洁的方式表示使用回调或变量行为的代码(用一种精巧的方式表示代码)。
内部类(inner class)机制,内部类包含在另一个类中,该内部类中的方法可以访问其外部类的域。内部类技术主要用于设计具有相互协作关系的类集合。
代理(proxy)是一种实现任何接口的对象。它可以用来构建系统级别的工具。
1、接口
接口概念
接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。
任何实现某一接口的类都需要包含该接口中的方法。
接口中的所有方法自动地属于public。因此,在接口中声明方法时,不必提供关键字public,但在接口的调用类中需要public。除此之外,接口的所有域被自动设为public static final。
接口中可以包含多个方法,还可以定义常量。然而,接口绝不能包含实例域。接口没有实例。提供实例域和实现方法的应该由实现该接口的那个类来完成。因此,接口可以近似看成是没有实例域的抽象类。
为了让类实现一个接口,需要以下步骤:
- 将类声明为实现给定的接口。通过关键字implements
- 对接口中的所有方法进行定义
示例:若想使用Arrays类中的sort方法进行排序,需要实现接口Comparable,其中只有一个方法compareTo();
package com.company;
import java.util.Arrays;
/**
* This program demonstrates the use of the Comparable interface
*/
public class Main {
public static void main(String[] args){
Employee[] staff = new Employee[3];
staff[0] = new Employee("Harry",35000);
staff[1] = new Employee("Carl",45000);
staff[2] = new Employee("Tony",38000);
Arrays.sort(staff);
//print out all employees
for(Employee e : staff){
System.out.println("name:"+e.getName()+" salary:"+e.getSalary());
}
}
}
class Employee implements Comparable<Employee> {
private String name;
private double salary;
public Employee(String name,double salary){
this.name = name;
this.salary = salary;
}
public String getName(){
return this.name;
}
public double getSalary(){
return this.salary;
}
public void raiseSalary(double byPercent){
double raise = salary * byPercent / 100;
salary += raise;
}
/**
* Compares employees by salary
* @param other another Employee object
*/
@Override
public int compareTo(Employee o) {
return Double.compare(salary,o.salary); /*if x<y, return -1; x=y,return 0; x>y, return 1;*/
}
}
//result:
name:Harry salary:35000.0
name:Tony salary:38000.0
name:Carl salary:45000.0
Process finished with exit code 0
compareTo方法为sort方法提供对象的比较方式。但为什么不可以直接在Employee中定义一个compareTo方法,而要实现Comparable接口呢?主要原因是Java是一种强类型(strongly typed)语言。
强类型(strongly typed)语言也称为强类型定义语言。是一种总是强制类型定义的语言,要求变量的使用要严格符合定义,所有变量都必须先定义后使用。
sort方法种可能包含以下代码:
...
if a[I].compareTo(a[j]) > 0)
{
// rearrange a[I] and a[j];
...
}
...
这就传入sort方法的a[i]一定要有compareTo方法。若a为Comparable对象的数组,就可以确保拥有compareTo方法
接口的特性
接口不是类,不能使用new实例化一个接口
但接口可以声明接口的变量:Comparable c;
接口变量只能引用实现了接口的类对象:c = new Employee(. . .);
可以像检查一个对象是否属于某个类一样,使用instanceof检查一个对象是否实现了一个接口:
if (anObject instanceof Conparable)
{
...
}
类似类的继承,接口也可以被扩展。
public interface Moveable{
void move(double x, double y);
}
public interface Powered extends Moveable{
double milesPerGallon();
double SPEED_LIMIT = 95;
}
注意:在接口中不能有实例域,但可以有常量。
一个类还能有一个超类,但可以实现多个接口。
class Employee implements Cloneable,Comparable
接口与抽象类
前文讲到,接口可以近似看作没有实例域的抽象类,因为不需要定义方法的实现过程
虽然接口可以以抽象类的过程近似实现,但是存在以下的问题:一个类只能有一个超类。
在C++中允许继承多个类,此特性称为:多重继承(multiple inheritance),而Java并不支持,因为多继承会导致语言的复杂性和降低运行效率。而接口可以提供多重继承的大多数好处,并避免了复杂性和低效性。
静态方法
在SE 8中允许在接口中增加静态方法。这种做法合法但是有违将接口作为抽象规范的初衷。
默认方法
为接口定义默认方法:必须使用default修饰符
public interface Comparable<T>{
default int compareTo(T other){
return 0;
}
}
接口中的默认方法不需要被实现它的类定义,其他所有方法必须在实现它的类中定义。
默认方法可以让程序员只关注他们所需,而不用考虑将所有方法定义,在他们只需要接口中的部分方法时。
除此之外,默认方法的另一个用法是接口演化(interface evolution)。若在Java更新时,向某些接口中添加了新方法,则原先实现这些接口的类将编译不通过,因为都没有定义添加的新方法,除非新方法在添加时为默认方法,这样,对于旧版本的代码也可以正常编译,增加了代码版本的兼容性。
解决默认方法冲突
1、超类优先。如果超类提供了一个具体方法,同名而且有相同参数类型的默认方法会被忽略。
2、接口冲突。如果一个超接口提供了一个默认方法,另一个接口提供了一个同名而且参数类型相同的方法,必须覆盖这个方法来解决冲突。
若一个类实现了两个包含同名同参数方法的接口,需要程序员在代码中提供一个同名同参数方法,在这个方法中,可以选择冲突的方法中的一个。
class Main implements interface1,interface2
{
public String functionInCommonName(){
return interface1.super.functionInCommonName();
}
}
3、“类优先”规则。一个类继承的超类和实现的接口包含相同方法时,只会考虑超类的方法。
2、接口示例
接口与回调
回调(callback)是一种常见的程序设计模式。在这种模式中,可以指出某个特定事件发生时应该采取的动作。
在java.swing包中有一个Timer类,可以使用它在到达给定的时间间隔时发出通告。如何告知定时器Timer要执行什么呢?在Java中,它将一个对西那个传递给定时器,然后,定时器调用这个对象的方法。当然,定时器需要知道具体执行那一个方法,并要求传递的对象所属的类实现了ActionListener接口(java.awt.event包中)
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;
//消除了javax.swing.Timer与javax.util.TImer之间的二义性。
public class TimerTest{
public static void main(String[] args){
ActionListener listener = new TimePrinter();
//construct a timer that calls the listener
//once every 5 seconds
Timer t = new Timer(5000, listener); //Timer构造器有两个参数,第一个是时间间隔,第二个是监听器对象
t.start(); //启动定时器
JOptionPane.showMessageDialog(null, "Quit program?");
System.exit();
}
}
class TimePrinter implements ActionListener{
@Override
public void actionPerformed(ActionEvent event){
System.out.println("The time is: " + new Date());
Toolkit.getDefaultToolkit().beep();
}
}
Comparator接口
在前文中,我们已经知道通过实现Comparable接口进行自定义类对象的数组排序,一些类已经实现了该接口,可以直接使用sort进行数组排序,例如String类、Integer类等。
现在假设我们不想按字典顺序给数组排序,我们希望按字符串长度进行排序,要处理这种情况,就需要使用Arrays.sort()的第二种形式,有两个参数:对象数组和比较器(comparator)对象
package com.company;
import java.util.Arrays;
import java.util.Comparator;
/**
* This program demonstrates the use of the ActionListener interface
*/
public class Main {
public static void main(String[] args) {
String[] friends = {"Amy","Peter","John","Tifany","Jonathan"};
Arrays.sort(friends, new LengthComparator());
for(String s : friends){
System.out.println(s);
}
}
}
class LengthComparator implements Comparator<String>{
@Override
public int compare(String first,String second){
return first.length() - second.length();
}
}
执行结果:
Amy
John
Peter
Tifany
Jonathan
Process finished with exit code 0
若将first.length() - second.length()更改为:second.length() - first.length(),执行结果如下:
Jonathan
Tifany
Peter
John
Amy
Process finished with exit code 0
在进行数组排序时,Arrays.sort()有两种形式:
1、Arrays.sort (byte []):若该对象数组中的对象类实现了Comparable<TYPE>接口,则可以直接使用该方法进行排序,若为自定义类实例化的对象,需要自行实现Comparable<ClassName>接口,并定义compareTo方法。该方法会根据字典顺序进行排序。
2、Arrays.sort (byte [], Comparator):第二个参数为比较器实例,该实例的类需要实现了Comparator接口,然后在具体完成比较时,需要建立一个实例
Comparable和Comparator实现的异同
1、两者都是用于数组比较的接口
2、方法调用对象不同。Comparable接口的compareTo方法是通过该类的对象实例进行调用的,但Comparator接口的compare方法要在比较器对象上调用,而不是在类对象本身上调用。
Comparator<String> cmp = new LengthComparator();
if(cmp.compare(words[I], words[j] > 0). . .
//而Comparable的方法调用是:
words[I].compareTo(words[j]);
对象克隆
在建立一个对象的副本的时候,会发生什么?
Employee original = new Employee(. . .);
Employee copy = original;
copy.raiseSalary(10); //oops, also change original
两者为同一个引用,改变任意一个会相互影响。
若想要只拷贝初始状态,后面的改变互不影响,则需要使用到克隆。
Employee copy = original.clone();
copy.raiseSalary() //OK! original unchanged
clone()方法是Object类的一个protected方法,虽然按道理其子类可以访问该方法,但受保护的规则比较微妙,子类只能调用受保护的clone方法克隆它自己的对象。必须重新定义clone为public才能允许所有方法克隆对象。
在普通克隆的过程中,若克隆的对象中还包含子对象,则拷贝域就会得到相同子对象的同一个引用,这样,原对象和克隆对象还是有部分信息共享,这是浅克隆。通常子对象是可变的,所以必须重新定义clone方法来建立一个深克隆,同时克隆所有子对象。
对于一个类,需要确定:
1、默认的clone方法是否满足要求,有无子对象,子对象是否可变,子对象是否包含不变的常量活没有更改器方法会改变它
2、是否可以在可变的子对象上调用clone来修补默认的clone方法
3、是否不该使用clone。
第三项为默认选项。若选择第一/第二项,类必须:
1、实现Cloneable接口
2、重新定义clone方法,并制定public访问修饰符
此处的Cloneable接口域接口的正常使用没有关系。具体来说,它没有clone方法,该方法是从Object继承来的。这个接口只是作为一个标记,只是类设计者了解克隆的过程,并且坚定的需要进行类克隆。若没有实现该接口,就会生成一个受查异常。
Cloneable接口是Java提供的一组标记接口(tagging interface)的一个。这些接口的通常用途是确保一个类实现了一个或一组特定的方法。标记接口不包含任何方法;他唯一的作用就是允许在类型查询中使用instanceof
if (obj instanceof Cloneable)
clone方法返回的Object,现在可以显示地强制转换为合适的类类型
以下为代码示例:
package com.company;
import java.util.Date;
import java.util.GregorianCalendar;
public class CloneTest {
public static void main(String[] args){
//在一个对象上调用clone方法,因为不确定其类是否有实现Cloneable接口,所以需要声明/捕获异常。
//声明如下:throws CloneNotSupportedException;
//或捕获如下:
try{
Employee original = new Employee("Henry",35000);
original.setHireDay(2000,1,1);
Employee copy = original.clone();
copy.setHireDay(2002,12,31);
copy.raiseSalary(10);
System.out.println("original:" + original);
System.out.println("copy:" + copy);
}
catch(CloneNotSupportedException e){
e.printStackTrace();
}
}
}
class Employee implements Cloneable {
private String name;
private double salary;
private Date hireDay;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
this.hireDay = new Date();
}
/**
* 即使默认clone方法可以满足要求,还是需要实现Cloneable接口,将clone重新定义为public,再调用super.clone()
*/
@Override
public Employee clone() throws CloneNotSupportedException {
//call Object.clone()
Employee cloned = (Employee) super.clone(); //返回的是Object,需要显式得声明对象类型。
//clone mutable fields
cloned.hireDay = (Date) hireDay.clone(); //克隆对象的可变实例域
return cloned;
}
public void setHireDay(int year, int month, int day) {
Date newHireDay = new GregorianCalendar(year, month - 1, day).getTime();
//Example of instance field mutation
hireDay.setTime(newHireDay.getTime());
}
public void raiseSalary(double byPercent) {
double raise = salary * byPercent / 100;
salary += raise;
}
@Override
public String toString() {
return "Employee[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay + "]";
}
}