(一)本章概述:
1. 接口的概念:接口不是类,而是对类的一组需求描述,描述类具体有什么功能,而并不给出每个功能的具体实现。 一个类可以实现一个或多个接口,并在需要接口的地方,随时使用实现了相应接口的对象。
2. lambda表达式的概念:这是一种表示可以在将来某个时间点执行的代码块的简介方法。 使用lambda表达式可以用一种精巧而简洁的方式表示毁掉或变量行为的代码 。
3. 内部类机制:内部类主要定义在另外一个类的内部,其中的方法可以访问包含他们(内部类)的外部类的域。 这种技术主要用于设计具有相互协作关系的类集合 。
(二) 谈谈接口:
接口不是类,而是对类的一组需求描述
举个例子: 我们经常听见服务提供商这样说:“如果类遵从某个特定接口,那么就履行这项服务”,比如,Arrays类中的sort方法承诺可以对对象数组进行排序,但这是有一个前提的:对象所属的类必须实现了Comparable接口。
下面是Comparable接口的代码:
public interface Comparable{
int compareTo(Object other);
}
这就是说,任何实现了Comparable接口的类,都需要包含compareTo方法,并且这个方法的参数是一个Object对象,返回一个整型数值。
Tip:从java5开始,Comparable接口已经改进为了泛型类型:
public interface Comparable<T>{
int compareTo(T other); //泛型,ok了吧
}
接口中的所有方法都自动的属于public。所以,在接口中声明方法时,不必提供关键字public。 在这个例子中还有一个问题没说到,就是上面的接口有一个没有说明的附加要求,即,在调用x.compareTo(y)的时候,这个compareTo方法必须确实比较两个对象的内容,并返回比较的结果,当x小于y的时候返回个负数;等于,返回0;大于,返回正数。
在接口中可以定义常量(final那种!!!注意是常量啊!!!),接口绝对不能含有实例域,在java8之前,也不能在接口中实现方法。提供实例域和具体的实现方法应该有实现接口的那个类完成。
现在,假设希望Arrays类的sort方法对Employee对象数组进行排序,Employee类就必须实现Comparable接口。
要将类声明为实现了一个接口,需要使用关键字impements:
class Employee imlements Comparable<Employee>
当然,这里的Employee类需要提供compareTo方法。假设希望根据雇员的薪水进行比较,以下是compareTo方法的实现:
public int compareTo<Employee other>{
return Double.compare(salary,other.salary);
}
现在我们已经看到,让一个类实现排序服务时,必须让他实现compareTo方法。这是由于要想sort方法提供比较的方式。
(三)接口的特性:
接口不是类,尤其不能使用new运算符实例化一个接口:
x = new Comparable(...)//错误
有意思的是,接口不能实例化,但是可以声明接口变量:Comparable x; x = new Employee();
这是可以的。这样需要提及的内容是,我们可以使用instanceof,检查一个对象是否属于某个特定类一样,也可以使用intanceof方法来检查一个对象是否实现了某个特定接口
例如:if(anObject instanceof Comparable){...}
还需要说的就是,接口也可以被扩展 嗯…这里再举个例子:
public interface Moveable{
void move(double x,double y);
}
//可以吧 Moveable 作为基础 扩展一个叫做powered的接口:
public interface Powered extends Moveable{
double milesPerGallon();//新加的
}
接口中可以包含常量 , 例如 double SPEED = 95; 这些常量将会自动设置为public static final。
尽管每个类只能有一个超类,但是可以有多个接口,像这样:
class Employee implements Cloneable,Comparable
(四)静态方法与默认方法:
Fist,在java SE 8中,java才允许向接口中增加静态方法。理论上讲,没有理由认为这不合法,只是有违于将接口作为抽象规范的初衷。
现在假设,你的jdk不是java8,那该如何实现上面的功能呢?目前为止,通常的做法都是将静态方法放在伴随类中。比如在java库中,你会看见成对出现的接口和实用工具类,例如:Collection/Collections 或者 Path/Paths 。
我们还可以为接口方法提供一个默认实现。必须用default修饰标记这样的一个方法
public interface CompareTo<T>
{
default int compareTo(T other){return 0;}
}
当然,在上面的那个例子中,默认方法并没有太大用处,因为Comparable的每一个实际实现都要覆盖这个方法。不过有些情况下默认方法可能很有用!(回忆下以前学过设计模式的选择模式) 。 在选择模式中,我们可以只为我们真正要用的东西覆盖相应的方法。默认方法可以调用任何方法。
对于默认方法,用户有三种选择:
①完全无视默认方法
直接继承了上级接口的默认方法
②重新声明默认方法
重新把默认方法声明为抽象方法(无实现,具体子类必需再次实现该方法)
③重新实现默认方法
重写了默认方法的实现。
现在假设一种冲突:即如果先在一个接口中将一个方法定义为默认方法,然后又在超类中或另外一个接口中定义了同样的方法,会发生什么?
Java对这种情况的规则如下:
1)超类优先。如果一个超类提供了一个具体方法,同名而且有相同参数类型的默认方法会被忽略。
一个类扩展了一个超类,同时实现了一个接口,并从超类和接口中继承了相同的方法。即,class Student extends Person implements Named{...};
。这种情况下,只会考虑超类方法,接口的所有默认方法都被忽略。
2)接口冲突。如果一个超接口提供了一饿默认方法,另一个接口提供了一个同名且参数类型相同的方法,必须覆盖这个方法解决冲突。(编译器会报错,程序员来解决。)
(五)接口示例:
①定时器接口示例:
介绍一种设计模式:回调。 在这种设计模式中,可以指出某个特定时间发生时,应该采取的动作。
在java.swing包中有一个Timer类,可以使用它在到达给定的时间间隔发出通告。例如,假如程序有一个时钟,就可以请求每秒钟获得一个通告,以便更新适中的表盘。 构造定时器时候,需要设置一个时间间隔,并告诉定时器,到达时间间隔需要做什么操作。
在Java中如何向定时器传递这个操作呢?Java是通过把某个类的对象传递给定时器,然后定时器调用这个对象的方法。当然,定时器需要知道调用哪一个方法,并要求传递的对象所属的类实现了java.awt.event包中的ActionLister接口。下面就是这个接口:
public interface ActionListener{
void actionPerformed(ActionEvent event);
}
加入希望每间隔十秒就打印一条消息“At the tone,the time is … ”,然后响一声,就应该定义一个ActionListener接口的类,然后将需要执行的语句放在actionPerformed方法中。如下:
class TimePrinter implements ActionListener{
public void actionPerformed(ActionEvent event){
System.out.println("At the tone,the time is " + new Date());
Toolkit.getDefaultToolkit().beep();
}
}
接下来,构造这个类的一个对象,然后吧它传递给Timer构造器:
ActionListener listener = new TimePrinter();
Timer t = new Timer(10000,listener);
//最后,启动定时器
t.start();
②Comparator接口示例:
在之前,我们了解了如何对一个对象数组排序,前提是这些对象是实现了Comparable接口的类的实例。
现在我们举一个例子:例如现在有两个字符串,字符串本身是有compareTo方法地,这个方法是安扎字典顺序比较字符串地,但是!假设我们现在希望按长度递增的顺序对字符串进行排序,而不是按照字典顺序对字符串进行排序。肯定不能让String类用两种不同的方式实现compareTo方法—–更何况String类也不应由我们来修改。
对付这种情况,Arrays.sort方法还有第二个版本,有一个数组和一个比较器(comparator)u哦为参数,比较器是实现了Comparator接口的类的实例。
public interface Compartor<T>{
int compare(T first,T second);
}
要按长度比较字符串,可以如下定义一个实现Compartor的类:
class LengthComparator implements Compartor<String>{
pulic int compare(String first,String second){
return first.length() - second.length();
}
}
具体完成比较时,需要建立一个实例:
Comparator<String>comp = new LengthComparator();
if(comp.compare(wrods[i],words[j])>0). . .
把这个调用和之前写的 words[i].compareTo(words[j])的调用作比较,这个还覆盖了interface,而且这个实在比较器对象上调用,而不是在字符串本身上调用,而且这个调用的是compare,那个是compareTo!
③对象克隆:
介绍下,Cloneable接口,这个接口只是了一个类体哦概念股了一个安全的clone方法。
为了解释克隆,下面我们举一个例子:
先来回忆一下,一个包含对象引用的变量建立副本时会发生什么。原变量和副本都是同一个对象的引用。这说明任何一个变量改变,都会影响另一个变量。
如果i希望copy的是一个新的对象,它的初始状态和 原本的对象相同,但是之后他们会有各自不同的状态,这种情况下就可以使用clone方法。
但是!clone方法是Object的一个protected方法,这说明你的代码不能直接调用这个方法。只有Employee类可以克隆Employee对象。这个限制是有原因地,想一下,Object类如何实现克隆,它对这个对象一无所知,所以智能逐个域的进行拷贝。如果对象中的所有数据域都是数值或其他基本类型,拷贝这些域没有任何问题。但是如果对象中包含对子对象的引用呢???这样拷贝域就会得到相同子对象的一个引用,这样一来,原对象和子对象仍然会共享一些信息!
默认的克隆操作是“浅拷贝”,并没有克隆对象中引用的其他对象。
完全克隆就是深拷贝。
对于每一个类,需要确定:
1)默认的clone方法是否满足需求;
2)是否可以在可变的子对象上调用clone来修补默认的clone方法;
如果选择1)或者2 ),那么那个类必须:
1. 实现了Cloneable接口;
2. 重新定义了clone方法,并指定Public修饰符。
对象对于克隆很“偏执”,如果一个对象请求克隆,但没有实现这个接口,就生成一个受查异常。
所以,即使clone的默认(浅拷贝)实现能够满足需求,我们还是要实现Cloneable接口,将clone重新定义为Public,再调用super.clone()。下面给出一个例子:
class Employee implements Cloneable{
public Employee clone() throws CloneNotSupportExcption
{
return (Employee)super.clone // clone是Object下的方法
}
}
和Object.clone提供的浅拷贝相比,深拷贝还需要做更多的动作:克隆对象中可变的实例域。
class Employee implements Clonable{
...
public Employee clone() throws CloneNotSupportException
{
Employee cloned = (Employee)super.clone();
cloned.可变实例域(可能是对另一个对象的引用) = 可变实例域.clone();
return cloned;
}
}
如果在一个对象上调用clone()方法,但是没有实现Cloneable接口,Object类的clone方法就会抛出一个CloneNotSupportedException的异常。