第6章 接口、lambda表达式与内部类
接口¶
如果实现了某个接口,就能使用这个接口的方法和变量。
接口的属性¶
- 接口中的方法都是
public
,但定义时不需要特别说明。 - 基于上一点,如果父类实现了某个接口,则子类也相当于实现这个接口。如果子类希望改变接口方法的行为,可以选择覆盖这些方法。
- 接口不能定义变量,但可以定义常量。某个类只要实现了接口,就自动获得接口中的常量。在类中调用接口的常量不需要指定接口名字。
- 接口的常量都是
public static final
,同样不需要在定义时特别说明。 - 接口可以扩展成更高级的接口。
默认方法¶
接口可以定义默认方法,例如:
public interface Collection {
int size();
default boolean empty() {return size() == 0;}
}
默认方法可以不实现。
默认方法的作用:提供兼容性。假设 Bag
类实现了 Collection
接口,Collection
接口新增了一个方法,可以将这个方法设置为默认方法,这样原有的代码可以正常运行。
解决默认方法冲突¶
超类与接口冲突,超类优先。接口与接口冲突,必须覆盖解决冲突。
- 超类优先规则:如果子类继承了超类的方法,同时实现了接口,接口中有方法(无论这个方法是否为默认方法)与超类中的方法冲突,则超类的方法优先。例如:
interface myInterface { default void method() { System.out.println("Interface - method"); } } class baseClass { public void method() { System.out.println("baseClass - method"); } } class childClass extends baseClass implements myInterface{} public class Main { public static void main(String[] args) { var a = new childClass(); a.method(); // 会输出 baseClass - method } }
- 接口冲突:如果一个接口提供了一个默认方法,另一个接口提供了一个同名而且参数类型相同的方法(无论这个方法是否为默认方法),必须覆盖这个方法来解决冲突。
标记接口¶
某些接口不包含任何方法,唯一的作用是允许在类型查询的语句中使用 instanceof
。例如 Cloneable
接口,实现这个接口不需要实现任何方法,但如果没有作出标记,请求克隆时就会生成一个检查型异常。
Object
类的 clone
方法是 protected
,因此,即使默认的浅拷贝能够满足要求,也要将 clone
定义为 public
,再调用 super.clone()
。
lambda 表达式¶
lambda
表达式定义了一个代码块,类似于匿名函数(但 java
中没有函数类型 ),方便在以后使用。
函数式接口¶
有些接口只有一个抽象方法,这种接口称为函数式接口。
可以将 lambda
表达式作为某个实现了函数式接口的对象传入参数中,这样可以避免创建对象等繁琐的流程。
在传入 lambda
表达式时,参数的类型以及返回类型都会自动推导,例如:
- 将字符串数组按字符串长度排序,
Arrays.sort()
的第二个参数接收一个实现了Comparable<T>
接口的对象。var planets = new String[] { "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune" }; Arrays.sort(planets, (x, y) -> x.length() - y.length());
- 创建定时任务,
Timer()
的第二个参数接收一个实现了ActionListener
接口的对象。var timer = new Timer(1000, event -> System.out.println("The time is " + new Date()));
ArrayList
中的removeIf()
方法,能够根据条件删除元素,其参数接收一个实现了Predicate<T>
接口的对象。list.removeIf(e -> e == null);
Supplier<T>
接口提供了懒计算功能。// 只有在 null 时才执行第二个参数 LocalDate hireDay = Objects.requireNonNullOrElseGet(day, () -> new LocalDate(1970, 1, 1));
方法引用¶
lambda
表达式可以进一步简化成一个方法引用,有以下三种用法:
object::instanceMethod
,例如在创建定时任务的参数中使用System.out::println
:var timer = new Timer(1000, System.out::println);
System.out::println
有 10 个重载,系统会自动匹配最接近的重载,在这个例子中,最接近的是println(Object event)
的方法,相当于:var timer = new Timer(1000, event -> System.out.println(event));
Class::instanceMethod
,例如在对字符串数组排序时,想忽略字母的大小写:这种情况下,第 1 个参数会成为方法的隐式参数,相当于:var planets = new String[] { "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune" }; Arrays.sort(planets, String::compareToIgnoreCase);
Arrays.sort(planets, (x, y) -> x.compareToIgnoreCase(y));
Class::staticMethod
,例如Math::pow
,这种情况下所有的参数都会传递到静态方法当中,相当于(x, y) -> Math.pow(x, y)
Info
注意,只有当 lambda
表达式的结构时只调用一个方法而不做其他操作时,才能把 lambda 表达式重写为方法引用。例如:
s -> s.length() == 0
变量作用域¶
lambda
表达式可以自动捕获外围作用域的变量(不需要手动添加捕获)。lambda
表达式只能引用不会改变的变量,被捕获的变量既不能在内部改变,也不能在外部改变,否则报错。- 在
lambda
表达式中声明与一个局部变量同名的参数或局部变量是不合法的。 - 在
lambda
表达式中使用this
关键字时,是指创建这个lambda
表达式的方法的this
参数。例如:
public class Application {
public void init()
{
ActionListener liStener = event -> {
System.out.println(this.toString());
......
};
......
}
}
表达式 this .toString()
会调用 Application
对象的 toString
方法,而不是 ActionListener
实例的方法。
最后更新:
2023-12-10
创建日期: 2023-12-03
创建日期: 2023-12-03