6.1_接口
6.1.1_接口示例
- 接口不是类,而是对类的一组需求描述。
Arrays.sort()只可对已实现Comparable接口的类的对象进行排序。- 接口中的方法默认为
public。 - 接口可定义常量,但不能定义实例域。在 Java1.8 之前,不能在接口中实现方法。
class Employee implements Comparable<Employee> {
public int compareTo(Employee other) {
return Double.compare(salary, other.salary);
}
}
为什么不能在 Employee 类中直接提供 compaTo() 方法,而必须实现 Comparable 接口呢?
这是因为Java是一种强类型语言。在调用方法时,编译器将会检查这个方法是否存在。
Integer、Double 的静态方法
static int compare(int x, int y)static int compare(double x, double y)
compareTo 方法的反对称规则
先看一段代码
class Manager extends Employee {
public int compareTo(Employee other) {
Manager otherManager = (Manager) other; // NO
}
}
- 这不符合反对称规则,如果 x 是一个
对象,y 是一个 Manager 对象,调用
不会抛出异常,因为 x 实现的是泛型为
的
接口。可将
看做雇员。
故,在 compareTo 方法逻辑之前都应该进行以下检测:
if(getClass() != other.getClass()) throw new ClassCastException();
6.1.2_接口的特性
- 不能构造接口对象,但可声明接口变量。
Comparable c; // Ok
- 可使用
instanceOf检查一个对象是否实现了某个接口。if(anObject instanceOf Comparable) {...}
- 接口可被扩展。
public interface i1 {...} public interface i2 extends i1 {...} - 接口的域(非实例域)被自动设为
public static final - 每个类可实现多个接口,但只能继承自一个超类。
6.1.3_接口与抽象类
为什么不将 Comparable 直接设计成抽象类。
假设 Employee 扩展了 Person 类,那就不能再扩展 Comparable 类了。该类的对象也就失去了可比较性。
6.1.4_静态方法
Java8中后,允许在接口中定义静态方法。
public interface Path {
public static Path get(String first, String... more) {
return Fi1eSystems.getDefault().getPath(first, more);
}
}
6.1.5_默认方法
- 可用
default修饰一个接口方法的默认实现。 - 默认方法可调用任何其他方法。
public interface Collection { int size(); // An abstract method default boolean isEmpty() { return size() = 0; } .. }- 好处:实现默认方法就不用再写 isEmpty 方法的逻辑。
6.1.6_解决默认方法冲突
在Java中,如果现在一个接口中将一个方法 定义为默认方法,然后又在超类中/另一个接口中定义了同样(同名、相同参数类型)的方法,则以以下规则处理:
- 超类优先。
- 接口冲突。
interface Named { default String getName() { return getClass().getName() + "_" + hashCodeO; } } 一、如果有一个类同时实现了这两个接口会怎么样呢? class Student implements Person, Named {...} 答:Java 编译器会报告一个错误,程序员必须在这个方法中,选择两个冲突方法中的一个 class Student implements Person, Named { public String getName() { return Person.super.getName(); } } 二、假设 Named 接口没有为 getName 提供默认实现: interface Named { String getNameO; } Student 类会从 Person 接口继承默认方法吗? 答:同一
另一种情况(类优先原则)
一个类扩展了一个超类,同时实现了一个接口,并从超类和接口继承了相同的方法。例如,假设 Person 是一个类, Student 定义为:
class Student extends Person implements Named {. . . }
在这种情况下, 只会考虑超类方法,接口的所有默认方法都会被忽略。
6.2_接口示例
6.2.1_接口与回调
- 回调(
)是一种指出某个特定事件发生时应该采取的动作的程序设计模式。
6.2.2_Comparator 接口
String 类实现了 Comparable 接口,故 方法可按字典序比较字符串。
但现在要让字符串的以字符串长度排序呢?
Array.sort()有另一个版本,它的参数是一个数组和一个比较器 。
public interface Comparator<T> {
int compare(T first, T second);
}
满足上述需求,可这样实现比较器
class LengthComparator implements Comparator<String> {
public int compare(String first, String second) {
return first.length() - second.length();
}
}
使用方法
String[] friends = { "Peter", "Paul", "Mary" };
Arrays.sort(friends, new LengthComparator()):
6.2.3_对象克隆
- Object 类的 clone 方法被声明为 protected。且拷贝方式为浅拷贝。
- 浅拷贝:拷贝一个对象的子对象的引用,得到的拷贝对象会共享这个子引用指向的实例信息。
- 深拷贝:则反之。
class Employee implements Cloneable { public Employee cloneO throws CloneNotSupportedException { // call Object,clone() Employee clonedEmp = (Employee) super.clone(); // clone mutable fields clonedEmp.hireDay = (Date) hireDay.clone(); return clonedEmp; } - 如果在一个没有实现 Cloneable 接口的对象上调用 clone()方法 , Object 类 的 clone 方法就会拋出一个
注意的问题: Employee 类定义了 clone 方法,任何人都可以用它 来克隆 Manager 对象,能成功吗?这取决于 Manager 类的域。
- 所有数组类型都有一个
的
方法
int[] luckyNumbers = { 2, 3, 5, 7,11, 13 };
int[] cloned = luckyNumbers.clone();
cloned[5] = 12; // doesn't change luckyNumbers[5]
6.3_lambda 表达式
有关lambda表达式的学习可移步segmentfault.com/a/119000001…,这里讲得比较全面。
6.3.1_为什么引入 lambda 表达式
lambda表达式是一个可传递的代码块。
6.3.2_lambda 表达式的语法
- 即使 lambda 表达式没有参数, 仍然要提供空括号,就像无参数方法一样:
0 -> { for (inti = 100;i >= 0;i ) System.out.println(i); } - 如果可以推导出一个 lambda 表达式的参数类型,则可以忽略其类型。例如:
Comparator<String> comp
= (first, second) // Same as (String first, String second)
-> first.length() - second.length();
- 无需指定 lambda 表达式的返回类型。
6.3.3_函数式接口
- 只含有一个被显式声明的抽象方法的接口。 Comparator 就是只有一个方法的接口,故可进行以下操作。
Arrays.sort(words, (first, second) -> first.length() - second.length());
6.3.4_方法引用
有现成的方法可以完成你想要传递到其他代码的某个动作则可使用方法引用来完成。
Timer t = new Timer(1000, event -> System.out.println(event));
Timer t = new Timer(1000, Systei.out::println);
- 表达式
System.out::println是一个方法引用(method reference), 它等价于 lambda 表达式x -> System.out.println(x)
Arrays.sort(strings,String::conpareToIgnoreCase)
规律
-
object::instanceMethod -
Class::staticMethod -
Class::instanceMethod- 在前 2 种情况中,方法引用等价于提供方法参数的 lambda 表达式。比如
System.out::println等价于x -> System.out.println(x)。类似地,Math::pow等价于(x,y) -> Math.pow(x, y) - 对于第 3 种情况, 第 1 个参数会成为方法的目标。例如,
String::compareToIgnoreCase等同于(x,y)-> x.compareToIgnoreCase(y)
- 在前 2 种情况中,方法引用等价于提供方法参数的 lambda 表达式。比如
6.3.5_构造器引用
- 构造器引用与方法引用很类似,只不过方法名为
new。
6.3.6_变霣作用域
lambda 表达式的体与嵌套块有相同的作用域。
Path first = Paths.get(7usr/Mn");
Couparator<String> comp =
(first, second) -> first.length() - second.length();
// Error: Variable first already defined
在一个 lambda 表达式中使用 this 关键字时, 是指创建这个 lambda 表达式的方法的 this 参数。例如,考虑下面的代码
public class Application() {
public void init() {
ActionListener listener = event -> {
System.out.println(this.toString());
...
}
...
}
- 表达式
this.toString()会调用Application对象的toString方法, 而不是ActionListener实例的方法。