第六章 接口、lambda表达式与内部类

  • 6. 接口、lambda 表达式与内部类
    • 6.1 接口
      • 6.1.1 接口概念
      • 6.1.2 接口的特性
      • 6.1.3 接口与抽象类
      • 6.1.4 静态方法
      • 6.1.5 默认方法
      • 6.1.6 解决默认方法冲突
    • 6.2 接口示例
      • 6.2.1 接口与回调
      • 6.2.2 Comparator接口
      • 6.2.3 对象克隆
    • 6.3 lambda 表达式
      • 6.3.1 为什么引入lambda 表达式
      • 6.3.2 lambda 表达式的语法
      • 6.3.3 函数式接口
      • 6.3.4 方法引用
      • 6 .3.5 构造器引用
      • 6.3.6 变量作用域
      • 6.3.7 处理lambda表达式
      • 6.3.8 再谈Comparator
    • 6.4 内部类
      • 6.4.1 使用内部类访问对象状态
      • 6.4.2 内部类的特殊语法规则
      • 6.4.3 内部类是否有用、必要和安全
      • 6.4.4 局部内部类
      • 6.4.5 由外部方法访问变量
      • 6.4.6 匿名内部类
      • 6.4.7 静态内部类

6. 接口、lambda 表达式与内部类

6.1 接口

接口主要用来描述类具有什么功能,而并不给出每个功能的具体实现。

public interface Comparable
{int compareTo(Object other) ;
}

6.1.1 接口概念

一个类只能有一个超类,但可以实现一个或多个接口。

接口绝不能含有实例域, 在JavaSE 8 之前, 也不能在接口中实现方法。现在已经可以在接口中提供简单方法了。当然, 这些方法不能引用实例域。

提供实例域和方法实现的任务应该由实现接口的那个类来完成。因此, 可以将接口看成是没有实例域的抽象类,但是这两个概念还是有一定区别的。

为了让类实现一个接口, 通常需要下面两个步骤:

  1. 将类声明为实现给定的接口。
  2. 对接口中的所有方法进行定义。
class Employee implements Cloneable, Comparable;//将类声明为实现某个接口

6.1.2 接口的特性

接口不是类,不能用new实例化一个接口。

尽管不能构造接口的对象,却能声明接口的变量,接口变量必须引用实现了接口的类对象:

Comparable x; // OK
x = new Employee(. . .); // OK

检查一个对象是否实现了某个特定的接口

if (anObject instanceof Comparable) { . . . }

接口的扩展

public interface Moveable
{void move(double x, double y) ;
}public interface Powered extends Moveable
{double milesPerCallon();
}

虽然在接口中不能包含实例域或静态方法,但却可以包含常量。

与接口中的方法都自动地被设置为public—样,接口中的域将被自动设为public static final。

6.1.3 接口与抽象类

为什么要用接口而不用抽象类呢?使用抽象类表示通用属性存在这样一个问题: 每个类只能扩展于一个类。但每个类可以实现多个接口。

6.1.4 静态方法

在Java SE 8 中,允许在接口中增加静态方法。只是这有违于将接口作为抽象规范的初衷。

目前为止, 通常的做法都是将静态方法放在伴随类中。在标准库中, 你会看到成对出现的接口和实用工具类, 如Collection/Collections 或Path/Paths。

6.1.5 默认方法

default

默认方法可以调用任何其他方法。

接口演化

6.1.6 解决默认方法冲突

如果先在一个接口中将一个方法定义为默认方法, 然后又在超类或另一个接口中定义了同样的方法,采取规则:

  1. 超类优先
  2. 接口冲突

6.2 接口示例

6.2.1 接口与回调

指出某个特定事件发生时应该采取的动作

定时器和动作监听器的使用

6.2.2 Comparator接口

按长度递增的顺序对字符串进行排序,而不是按字典顺序进行排序。要处理这种情况,ArrayS.Sort 方法还有第二个版本, 有一个数组和一个比较器( comparator )作为参数, 比较器是实现了Comparator 接口的类的实例。

public interface Comparators
{int compare(T first, T second);
}
class LengthComparator implements Comparator<String>
{public int compare(String first, String second) {return first.length() - second.length();}
}Comparator<String> comp = new LengthComparator();
if (comp.compare(words[i], words[j]) > 0) . . .

6.2.3 对象克隆

Cloneable 接口是Java 提供的一组标记接口( tagging interface ) 之一。标记接口不包含任何方法; 它唯一的作用就是允许在类型查询中使用instanceof。

浅拷贝深拷贝

所有数组类型都有一个public 的clone 方法, 而不是protected: 可以用这个方法建立一个新数组, 包含原数组所有元素的副本:

int[] luckyNumbers = { 2, 3, 5, 7, 11, 13 };
int[] cloned = luckyNumbers.clone();
cloned[5] = 12; // doesn't change luckyNumbers[5]

6.3 lambda 表达式

6.3.1 为什么引入lambda 表达式

lambda 表达式是一个可传递的代码块, 可以在以后执行一次或多次。

6.3.2 lambda 表达式的语法

有三个部分:

  1. 一个代码块
  2. 参数
  3. 自由变量的值,这里指非参数而且不在代码中定义的变量
(String first, String second)
-> first.length() - second.length()

如果代码要完成的计算无法放在一个表达式中,就可以像写方法一样,把这些代码放在{}中,并包含显式的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) // Same as (String first, String second)-> first.length() - second.length();

如果方法只有一参数, 而且这个参数的类型可以推导得出,那么甚至还可以省略小括号

ActionListener listener = event ->System.out.println("The time is " + new Date()");// Instead of (event) -> . . . or (ActionEvent event) -> . . .

无需指定lambda 表达式的返回类型。lambda 表达式的返回类型总是会由上下文推导得出。

如果一个lambda 表达式只在某些分支返回一个值, 而在另外一些分支不返回值,这是不合法的

(int x)-> { if (x >= 0) return 1; }//不合法

6.3.3 函数式接口

对于只有一个抽象方法的接口, 需要这种接口的对象时, 就可以提供一个lambda 表达式。这种接口称为函数式接口( functional interface )。

Arrays.sort 方法的第二个参数需要一个Comparator 实例, Comparator 就是只有一个方法的接口, 所以可以提供一个lambda 表达式:

Arrays.sort (words ,
(first , second) -> first.length() - second.length()) ;

lambda 表达式可以转换为接口

实际上,在Java 中, 对lambda 表达式所能做的也只是能转换为函数式接口。甚至不能把丨ambda 表达式赋给类型为Object 的变量,Object 不是一个函数式接口。

  • Predicate接口
public interface Predicate<T>
{boolean test(T t);// Additional default and static methods
}

ArrayList 类有一个removelf 方法, 它的参数就是一个Predicate。这个接口专门用来传递lambda 表达式。例如,下面的语句将从一个数组列表删除所有null 值:

list.removelf(e -> e == null);
  • Supplier < T >接口

6.3.4 方法引用

Timer t = new Timer(1000, event -> System.out.println(event)):

直接把println 方法传递到Timer 构造器

Timer t = new Timer(1000, System.out::println);

表达式System.out::println 是一个方法引用( method reference ), 它等价于lambda 表达式x 一> System.out.println(x)

类似的,Math::pow 等价于(x,y) ->Math.pow(x, y)

String::compareToIgnoreCase等同于(x, y) -> x.compareToIgnoreCase(y)

Arrays.sort(strings,String::compareToIgnoreCase);//对字符串排序而不考虑字母的大小写

::操作符分隔方法名与对象或类,主要有3种情况:

  • object::instanceMethod
  • Class::staticMethod
  • Class::instanceMethod

注意,只有当lambda表达式的体只调用一个方法而不做其他操作时,才能把lambda表达式重写为方法引用。

s -> s.length() == 0这里有一个方法调用,但是还有一个比较,所以不能用方法引用。

可以在方法引用中使用thissuperthis::equals 等同于x -> this.equals(x)

6 .3.5 构造器引用

构造器引用与方法引用很类似,只不过方法名为new。例如,Person::newPerson构造器的一个引用。

可以用数组类型建立构造器引用,例如int[]::new等价于于lambda 表达式x-> new int[x].

Java 有一个限制,无法构造泛型类型T 的数组。数组构造器引用对于克服这个限制很有用。

6.3.6 变量作用域

在Java 中, lambda 表达式就是闭包。

lambda 表达式可以捕获外围作用域中变量的值。在Java 中, 要确保所捕获的值是明确定义的,这里有一个重要的限制。在lambda 表达式中, 只能引用值不会改变的变量。因为如果在lambda 表达式中改变变量, 并发执行多个动作时就会不安全。如果在lambda 表达式中引用变量, 而这个变量可能在外部改变,这也是不合法的。

也就是:lambda 表达式中捕获的变量必须实际上是最终变量( effectivelyfinal),这个变量初始化之后就不会再为它赋新的值。

表达式的体与嵌套块有相同的作用域。lambda 表达式中声明与一个局部变量同名的参数或局部变量是不合法的。

在一个lambda 表达式中使用this 关键字时, 是指创建这个lambda 表达式的方法的this参数。

public class ApplicationO
{public void init (){ActionListener listener * event ->{System.out.print n(thi s.toStringO) ;}...}
}

表达式this.toStringO 会调用Application 对象的toString 方法,而不是ActionListener 实例的方法。

6.3.7 处理lambda表达式

使用lambda 表达式的重点是延迟执行(deferred execution )

例子:重复一个动作n次

repeat(10, 0 -> System.out.println("Hello, World!"));public static void repeat(int n, Runnable action)
{for (int i = 0; i < n; i++) action.run() ;
}

改进:告诉这个动作它出现在哪一次迭代中

public interface IntConsumer
{void accept(int value) ;
}
public static void repeat(int n, IntConsumer action)
{for (int i = 0; i < n; i++) action.accept(i);
}
repeat(10, i -> System.out.println("Countdown: " + (9 - i)));

6.3.8 再谈Comparator

Comparator 接口包含很多方便的静态方法来创建比较器。这些方法可以用于lambda 表达式或方法引用。

静态comparing 方法取一个“ 键提取器” 函数, 它将类型T 映射为一个可比较的类型( 如String )。对要比较的对象应用这个函数, 然后对返回的键完成比较。

Arrays.sort(people, Comparator.comparing(Person::getName)) ;//按名字排序Arrays.sort(people,Comparator.comparing(Person::getlastName.thenConipari ng(Pe rson::getFi rstName)) ;//如果姓相同,就用第二个比较器Arrays.sort(people, Comparator.companng(Person::getName,(s, t) -> Integer.compare(s.1ength() , t.length())));//根据名字的长度排序

6.4 内部类

内部类( inner class ) 是定义在另一个类中的类。

用内部类的原因:

  1. 内部类方法可以访问该类定义所在的作用域中的数据, 包括私有的数据。
  2. 内部类可以对同一个包中的其他类隐藏起来。
  3. 当想要定义一个回调函数且不想编写大量代码时,使用匿名(anonymous) 内部类比较便捷。不过如今lambda表达式在这方面可以做的更好。

6.4.1 使用内部类访问对象状态

内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域。为此,内部类的对象总有一个隐式引用, 它指向了创建它的外部类对象。

只有内部类可以是私有类,而常规类只可以具有包可见性,或公有可见性。

6.4.2 内部类的特殊语法规则

使用外围类引用的正规语法:

OuterClass.this

在外围类的作用域之外,可以这样引用内部类:

OuterClass.InnerClass

内部类中声明的所有静态域都必须是final。

内部类不能有static 方法。

6.4.3 内部类是否有用、必要和安全

内部类是一种编译器现象, 与虚拟机无关。

6.4.4 局部内部类

局部类不能用public 或private 访问说明符进行声明。它的作用域被限定在声明这个局部类的块中。

局部类有一个优势, 即对外部世界可以完全地隐藏起来。

6.4.5 由外部方法访问变量

与其他内部类相比较, 局部类还有一个优点。它们不仅能够访问包含它们的外部类, 还可以访问局部变量。不过, 那些局部变量必须事实上为final。这说明, 它们一旦赋值就绝不会改变。

在内部类被首次提出时, 原型编译器对内部类中修改的局部变量自动地进行转换。不过, 后来这种做法被废弃。毕竟, 这里存在一个危险。同时在多个线程中执行内部类中的代码时, 这种并发更新会导致竞态条件(第14章)

6.4.6 匿名内部类

假如只创建这个类的一个对象,甚至不需要为类指定名字,这样的类称为匿名内部类(anonymous inner class)。

new SuperType(construction parameters)
{inner class methods and data
}// 例如:
public void start(int interval, boolean beep)
{ActionListener listener = new ActionListener(){public void actionPerformed(ActionEvent event){System.out.println("At the tone, the time is " + new Date());if (beep) Toolkit.getDefaultToolkit().beep();}};Timer t = new Timer(interval, listener);t.start();
}

其中, SuperType 可以是ActionListener 这样的接口, 于是内部类就要实现这个接口。SuperType 也可以是一个类,于是内部类就要扩展它。

由于构造器的名字必须与类名相同, 而匿名类没有类名, 所以, 匿名类不能有构造器。取而代之的是,将构造器参数传递给超类( superclass) 构造器。尤其是在内部类实现接口的时候, 不能有任何构造参数。

  • 构造一个类的新对象与构造一个扩展了那个类的匿名内部类的对象之间有什么差别?

    如果构造参数的闭小括号后面跟一个开大括号, 正在定义的就是匿名内部类。

多年来,Java 程序员习惯的做法是用匿名内部类实现事件监听器和其他回调。如今最好还是使用lambda 表达式

双括号初始化:外层括号建立了ArrayList 的一个匿名子类。内层括号则是一个对象构造块:

invite(new ArrayList<String>0 {{ add("Harry") ; add("Tony") ; }}) ;

生成曰志或调试消息时,用匿名类:

System.err.println("Something awful happened in " + getClass());
//但是对静态方法不奏效,因为调用getClass 时调用的是this.getClass(), 而静态方法没有this
//这时候可以用下面的表达式
new Object0{}.getCIass0-getEndosingClass() // gets class of static method

在这里,newObject(){} 会建立Object 的一个匿名子类的一个匿名对象,getEnclosingClass则得到其外围类, 也就是包含这个静态方法的类。

6.4.7 静态内部类

有时候, 使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类对象。为此,可以将内部类声明为static, 以便取消产生的引用。

静态内部类的对象除了没有对生成它的外围类对象的引用特权外, 与其他所冇内部类完全一样。

与常规内部类不同, 静态内部类可以有静态域和方法。

声明在接口中的内部类自动成为static 和public 类。

【Java】 第六章 接口、lambda 表达式与内部类 Java核心技术卷1基础知识原书第10版 读书笔记相关推荐

  1. Java核心卷Ⅱ(原书第10版)笔记(上)

    Java核心卷Ⅱ(原书第10版)笔记(上) 写在最前面,个人认为,卷Ⅱ更适合当手册使用,更多的是讲API的使用,前两章内容比较实际,要是合并到卷一就好了. 文章目录 第1章 Java SE 8 的流库 ...

  2. Java语言程序设计 基础篇 原书第10版 ,梁勇著,百度云链接及密码

    一本好书,对java小白来说是一本非常不错的书.第一次分享,请大家多多指教, Java语言程序设计 基础篇 原书第10版 ,梁勇著 链接:https://pan.baidu.com/s/1-MN_AS ...

  3. java第十版基础篇答案第九章_《Java语言程序设计》(基础篇原书第10版)第九章复习题答案...

    第九章 9.1:类为对象定义属性和行为,对象从类创建. 9.2:public class ClassName { } 9.3:ClassName v; 9.4:new ClassName(); 9.5 ...

  4. Java核心技术 卷II 高级特性 原书第9版pdf

    下载地址:网盘下载 内容简介  · · · · · · Java领域最有影响力和价值的著作之一,由拥有20多年教学与研究经验的资深Java技术专家撰写(获Jolt大奖),与<Java编程思想&g ...

  5. 《Java语言程序设计》(基础篇原书第10版)第八章复习题答案

    第八章 8.1:int[] array = new int[4][5]; 8.2: 二维数组的行可以有不同的长度. 8.3:输出结果为:array[0][1] is 2 8.4: int[][] r ...

  6. 《Java核心技术 卷Ⅱ 高级特性(原书第10版)》一导读

    前 言 致读者 本书是按照Java SE 8完全更新后的<Java核心技术 卷Ⅱ 高级特性(原书第10版)>.卷Ⅰ主要介绍了Java语言的一些关键特性:而本卷主要介绍编程人员进行专业软件开 ...

  7. Java核心技术 卷1 基础知识

    网站 更多书籍点击进入>> CiCi岛 下载 电子版仅供预览及学习交流使用,下载后请24小时内删除,支持正版,喜欢的请购买正版书籍 电子书下载(皮皮云盘-点击"普通下载" ...

  8. 《Java核心技术:卷I 基础知识》第1章 Java 程序设计概述 阅读与重点标记

    第 1 章 Java 程序设计概述 1996年 Java 第一次发布就引起了人们的极大兴趣.关注 Java 的人士不仅限于计算机出版界,还有诸如<纽约时报><华盛顿邮报>< ...

  9. 《JAVA语言程序设计-YDL-李娜-机械工业出版社-原书第八版》笔记

    2019独角兽企业重金招聘Python工程师标准>>> 目录:硬件-程序-选择-循环-方法-数组-对象和类-继承多态-多继承接口-IO-异常-递归 计算机是通过程序设计语言编写的软件 ...

最新文章

  1. PyTorch常用代码段合集
  2. C#学习笔记(四):数组
  3. Nginx源码分析:3张图看懂启动及进程工作原理
  4. Windows 10 系统版本更新历史
  5. 理解值和对象-快照图
  6. learnpython有中文版吗_简介 | Learn Python the Hard Way 中文版
  7. 全球及中国PMN-PT单晶行业发展模式及未来产销前景预测报告2022-2028年版
  8. mac上的Android虚拟机,android虚拟机能在retina MacBook pro上跑吗?
  9. SpringDataJpa 概述
  10. oracle库导出,oracle整库导出
  11. 学校计算机考试交卷过程中关机了,计算机基础考试注意事项
  12. Rocket-chip-Cache
  13. Word 2016 撰写论文(6): 取消/撤销 自动编号
  14. 基于STM32F103单片机的车牌识别图像处理识别系统 原理图PCB程序设计
  15. CorelDRAW 2019中文版安装使用教程
  16. 本次技术博客平台的选择
  17. 车牌识别,车辆检测,车牌检测和识别,与车相关的点点滴滴
  18. 推荐一款看书学习必备的读书笔记app
  19. 学无止境啊,身体是革命本钱
  20. “资产证券化支持实体经济万里行”启幕 探索实体经济发展新态势

热门文章

  1. C++探索之旅 | 第一部分第二课:C++编程的必要软件
  2. win7原版映像中添加usb3.0驱动
  3. 计算机没有usb视频教程,Win7已安装但没有USB3.0驱动如何安装教程
  4. (转)卷积网络中的通道(Channel)和特征图
  5. oracle 误删 log文件,Redo log文件被删除恢复
  6. 数字集成电路设计-12-状态机的四种写法
  7. scala io实现 获取目下的所有子文件和子目录
  8. 大数据----------------R语言下依赖库与依赖包的安装
  9. Greenplum实战--standby master的模拟故障与修复
  10. 关于《一种鱼眼图象到透视投影图象的变换模型》