六、接口、lambda 表达式与内部类
接口(interface)技术主要用来描述类具有什么功能,而并不给出每个功能的具体实现使用 lambda 表达式, 可以用一种精巧而简洁的方式表示使用回调或变量行为的代码。内部类技术主要用于设计具有相互协作关系的类集合。代理(proxy)是一种实现任意接口的对象代理是一种非常专业的构造工具,它可以用来构建系统级的工具。
6.1 接口
实现:
- 将类声明为实现给定的接口。
- 对接口中的所有方法进行定义。
- 要将类声明为实现某个接口, 需要使用关键字implements
特征:
- 允许在接口中增加静态方法
- 可以为接口方法提供一个默认实现使用关键词(default)
解决默认方法冲突
- 超类优先。如果超类提供了一个具体方法, 同名而且有相同参数类型的默认方法会被忽略。
- 接口冲突。如果一个超接口提供了一个默认方法, 另一个接口提供了一个同名而且参数类型(不论是否是默认参数)相同的方法, 必须覆盖这个方法来解决冲突
6.2 接口示例
-
接口与抽象类
- 接口不是类,尤其不能使用new 运算符实例化一个接口,但能声明接口的变量
- 每个类只能继承一个抽象类,但是每个类可以像下面这样实现多个接
-
Comparator 接口
-
对象克隆
克隆条件:
- 默认的clone 方法是否满足要求(实现Cloneable 接口)
- 是否可以在可变的子对象上调用clone 来修补默认的clone 方法(重新定义clone 方)
- 是否不该使用clone
注意:
Cloneable 接口是Java 提供的一组标记接口之一。(有些程序员称之为记号接口( mark interface))。Comparable 等接口的通常用途是确保一个类实现一个或一组特定的方法。标记接口不包含任何方法; 它唯一的作用就是允许在类型查询中使用 instanceof:
if (obj instanceof Cloneable) ...
建议你自己的程序中不要使用标记接口。
6.3 lambda表达式
-
表达式形 (均无需指定lambda 表达式的返回类型)
-
参数, 箭头(->) 以及一个表达式。如果代码要完成的计算无法放在一个表达式中,就可以像写方法一样,把这些代码放在{丨中,并包含显式的return 语句。
(String first, String second) ->{ if (first.length() < second.length()) return -1; else if (first.length() > second.length()) return 1; else return 0; }
-
lambda 表达式没有参数, 仍然要提供空括号
() -> { for (int i = 100; i >= 0;i--) System.out.println(i); }
-
若可以推导出一个lambda 表达式的参数类型,则可以忽略其类型
Comparator<String> comp = (first, second) -> first.length() - second.length();
-
方法只有一参数, 而且这个参数的类型可以推导得出,那么甚至还可以省略小括号
ActionListener listener = event -> System.out.println(new Date());
注意:
-
lambda 表达式可以传递到函数式接口
-
如果一个lambda 表达式只在某些分支返回一个值, 而在另外一些分支不返回值,这是不合法的。例如,
(int x) -> { if (x >= 0) return 1; }
就不合法。
-
-
方法引用
-
若有现成的方法可以完成你想要传递到其他代码的某个动作,则一下俩个代码等价
Timer t = new Timer(1000, event -> System.out.println(event)); // 另一个 Timer t = new Timer(1000, System.out::println);
-
用 :: 操作符分隔方法名与对象或类名
-
object::instanceMethod (列如:super::instanceMethod, super为超类)
-
Class::staticMethod
-
Class::instanceMethod
-
前2 种情况中, 方法引用等价于提供方法参数的lambda 表达式,第1 个参数会成为方法的目标
-
-
构造器引用
-
构造器引用与方法引用很类似,只不过方法名为new
-
可以用数组类型建立构造器引用。例如,
int[]::new
是一个构造器引用, 它有一个参数:即数组的长度。这等价于lambda 表达式x-> new int[x]
-
假设我们需要一个Person 对象数组。Stream 接口有一个toArray 方法可以返回Object 数组:
ArrayList<String> names = ...; Stream<Person> stream = names.stream().map(Person::new); Object[] people = stream.toArray();
-
用户希望得到一个Person 引用数组,而不是Object 引用数组。流库利用构造器引用解决了这个问题。可以把
Person[]::new
传入 toArray 方法:ArrayList<String> names = ...; Stream<Person> stream = names.stream().map(Person::new); Person[] people = stream.toArray(Person::new);
lambda 表达式可以捕获外围作用域中变量的值。在Java 中, 要确保所捕获的值是明确定义的,这里有一个重要的限制。在lambda 表达式中, 只能引用值不会改变的变量。
public static void countDown(int start, int delay){ ActionListener listener = event -> { start; // Error: Can't mutate captured variable System.out.println(start); }; new Timer(delay, listener).start(); }
lambda 表达式中捕获的变量必须实际上是最终变量( effectively final )。实际上的最终变量是指, 这个变量初始化之后就不会再为它赋新值。在这里,text 总是指示同一个 String对象,所以捕获这个变量是合法的。不过,i 的值会改变,因此不能捕获
public static void repeat(String text , int count){ for (int i = 1; i <= count; i ++){ ActionListener listener = event -> { System.out.println(i + ": " + text) ; }; new Timer(1000, listener) .start(); } }
-
-
lambda 表达式使用
使用lambda 表达式的重点是延迟执行
-
在一个单独的线程中运行代码;
-
多次运行代码;
-
在算法的适当位置运行代码(例如, 排序中的比较操作);
-
发生某种情况时执行代码(如, 点击了一个按钮, 数据到达, 等等);
-
只在必要时才运行代码。
-
-
lambda 表达式常用函数式接口
-
lambda 表达式基本类型函数式接口
大多数标准函数式接口都提供了非抽象方法来生成或合并函数。例如, Predicate.isEqual(a) 等同于
a::equals
, 不过如果a 为null 也能正常工作。已经提供了默认方法and、or 和negate 来合并谓词。例如Predicate.isEqual(a).or(Predicate.isEqual(b))
就等同于x -> a.equals(x) || b.equals(x)
。 如果设计你自己的接口,其中只有一个抽象方法, 可以用@FunctionalInterface 注解来标记这个接口,但并不是必须使用注解。这样做有两个优点。如果你无意中增加了另一个非抽象方法, 编译器会产生一个错误消息。另外javadoc 页里会指出你的接口是一个函数式接口。
-
Comparator 接口包含很多方便的静态方法来创建比较器。
-
静态comparing 方法取一个“ 键提取器” 函数, 它将类型T 映射为一个可比较的类型(如String )。对要比较的对象应用这个函数, 然后对返回的键完成比较。例如, 假设有一个Person 对象数组,可以如下按名字对这些对象排序:
Arrays.sort(people, Comparator.comparing(Person::getName));
还可以把比较器与thenComparing 方法串起来,如果两个人的姓相同, 就会使用第二个比较器。
Arrays.sort(people, Comparator.comparing(Person::getlastName).thenComparing(Pe rson::getFirstName));
这些方法有很多变体形式。可以为comparing 和thenComparing 方法提取的键指定一个比较器。例如,可以如下根据人名长度完成排序:
Arrays.sort(people, Comparator.comparing(Person::getName, (s, t) -> Integer.compare(s.1ength() , t.length())))
如果键函数可以返回null, 可能就要用到nullsFirst 和nullsLast 适配器。
Comparator.comparing(Person::getMiddleName, Comparator.nullsFirst(naturalOrder())) // naturalOrder() 升序 // reverseOrder() 降序 // naturalOrder().reversed() 降序
6.4 内部类
-
内部类
原因
- 内部类方法可以访问该类定义所在的作用域中的数据, 包括私有的数据。
- 内部类可以对同一个包中的其他类隐藏起来。
- 当想要定义一个回调函数且不想编写大量代码时,使用匿名(anonymous) 内部类比较便捷。
内部类
- 内部类中声明的所有静态域都必须是final。原因很简单,我们希望一个静态域只有一个实例, 不过对于每个外部对象, 会分别有一个单独的内部类实例。如果这个域不是final , 它可能就不是唯一的。
- 内部类不能有static 方法。Java 语言规范对这个限制没有做任何解释。也可以允许有静态方法, 但只能访问外围类的静态域和方法。显然,Java 设计者认为相对于这种复杂性来说, 它带来的好处有些得不偿失。
-
匿名类
建立一个与超类大体类似(但不完全相同)的匿名子类通常会很方便。不过, 对于equals 方法要特别当心。第5 章中, 我们曾建议equals 方法最好使用以下测试,但是对匿名子类做这个测试时会失败。
if (getClass() != other.getClass()) return false;
-
静态类
使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类对象。为此,可以将内部类声明为static, 以便取消产生的引用。
-
只有内部类可以声明为static。静态内部类的对象除了没有对生成它的外围类对象的引用特权外, 与其他所冇内部类完全一样。
-
在内部类不需要访问外围类对象的时候, 应该使用静态内部类。有些程序员用嵌套类(nested class ) 表示静态内部类。
-
与常规内部类不同, 静态内部类可以有静态域和方法。
-
声明在接口中的内部类自动成为static 和public 类。
-
6.5 代理
-
代理对象(代理类是在程序运行过程中创建的)
- 代理类可以在运行时创建全新的类。这样的代理类能够实现指定的接口。
- 代理类具有指定接口所需要的全部方法、Object 类中的全部方法。
java.Iang.reflect.InvocationHandler
-
Object invoke(Object proxy, Method method, 0bject[] args)
定义了代理对象调用方法时希望执行的动作。
java.Iang.reflect.Proxy
-
static Class getProxyClass(Cl assLoader loader, Class… interfaces)
返回实现指定接口的代理类。 -
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)
构造实现指定接口的代理类的一个新实例。所有方法会调用给定处理器对象的invoke 方法。
-
static boolean isProxyClass(Class<?> cl)
如果cl 是一个代理类则返回true。
API注释
6 . 1.1 接口概念
java.lang.Comparable
int compareTo(T other)
// 用这个对象与other 进行比较。如果这个对象小于other 则返回负值; 如果相等则返回0; 否则返回正值。
java.util.Arrays 1.2
static void sort(Object[] a )
// 使用mergesort 算法对数组a 中的元素进行排序。要求数组中的元素必须属于实现了Comparable 接口的类, 并且元素之间必须是可比较的。
java.lang.lnteger 1.0
static int compare(int x , int y) 7
// 如果x < y 返回一个负整数;如果x 和y 相等,则返回0; 否则返回一个负整数。
java.lang.Double 1.0
static int compare(double x , double y) 1.4
// 如果x < y 返回一个负数;如果x 和y 相等则返回0; 否则返回一个负数
6.5.3 代理类的特性
java.Iang.reflect.InvocationHandler 1.3
Object invoke(Object proxy, Method method, Object[] args)
// 定义了代理对象调用方法时希望执行的动作。
java.Iang.reflect.Proxy 1.3
static Class< ?> getProxyClass(ClassLoader loader, Class< ?>... interfaces)
// 返回实现指定接口的代理类。
static Object newProxyInstance(ClassLoader loader, Class< ?>[] interfaces, InvocationHandler handler)
// 构造实现指定接口的代理类的一个新实例。所有方法会调用给定处理器对象的invoke 方法。
static boolean isProxyClass(Class< ?> cl)
// 如果cl 是一个代理类则返回true。
评论区