《Java核心卷 I》第10版阅读笔记

眉间皱痕 提交于 2020-10-17 07:53:10


文章代码链接:https://horstmann.com/corejava/

1.java程序设计概述

在这里插入图片描述

2. java的基本程序设计结构

1、区分大小写
2、注释: //;/* /;/* */(可自动生成文档)
3、数据类型
整型:byte,short,int,long(1,2,4,8个字节),都是有符号的
浮点型:float,double(4,8个字节),浮点数值不适用于无法接受舍入误差的计算,( 2.0-1.1 ) 结果为 0.8999999999999999, 而不是 0.9。因为浮点数值采用二进制系统表示, 而在二进制系统中无法精确地表示分数 1/10。如果在数值计算中不允许有任何舍入误差, 就应该使用 BigDecimal类
char类型:2个字节,描述了 UTF-16 编码中的一个代码单元。(参考-https://www.zhihu.com/question/27562173)
boolean类型:整型值和布尔值之间 不能进行相互转换,即0不代表false,1不代表true;java规范中,没有明确指出boolean的大小。在《Java虚拟机规范》给出了4个字节,和boolean数组1个字节的定义,具体还要看虚拟机实现是否按照规范来(参考-https://blog.csdn.net/gaoyong_stone/article/details/79538195)
4、变量
以字母开头并由字母或数字构成的序列
Java 中“ 字母” 和“ 数字” 的范围更大。字母包括’A’ ~ ’Z’、 ’a’ ~ ’z’、’_’、’$’ 或在某种语言中表示字母的任何 Unicode 字符.
声明一个变量之后,必须用赋值语句对变量进行显式初始化
常量:利用关键字 final指示常量,只能赋值一次,不能修改,习惯上, 常量名使用全大写。
关键字strictfp: 浮点运算不会因为不同的硬件平台导致结果不一致
5、运算符
数值类型之间的合法转换,实心箭头为无信息丢失的转换;虚箭头, 表示可能有精度损失的转换
在这里插入图片描述
数值类型之间的强制转换:当存在信息丢失的可能性,需显式的强制类型转换
位运算符:处理整型类型时,可以直接对组成整型数值的各个位完成操作。这意味着可以使用掩码 技术得到整数中的各个位。位运算符包括: & (“and”); | (“or”) ; ^ (“xor”); ~(“not”) ,按位模式处理。
<< 和>>运算符将位模式左移或右移。>>> 运算符会用 0 填充高位,而>>会用符号位填充高位。不存在<<< 运算符。 int类型的左操作数,移位运算符的右操作数要完成模 32 的运算,long型左操作数右操作数模 64,例如,1<<35 的值等同于 1<<3 .
6、字符串
子串,拼接(+,join),不可变(不能修改字符串,可修改字符串变量 ,让它引用另一个字符串 )
不可变字符串的优点:字符串共享。各种字符串存放在公共的存储池中。字符串变量 指向存储池中相应的位置。若复制一个字符串变量, 原始字符串与复制的字符串共享相同的字符。 实际上只有字符串常量是共享的,而+ 或 substring 等操作产生的结果并不是共享的。
码点与代码单元 :常用 Unicode 字符使用一个代码单元表示,而辅助字符需要一对代码单元表示。
构建字符串:StringBuilder ; StringBuffer(支持多线程)
7、输入输出
标准输出流:System.out.println
标准输人流:System.in;Scanner in = new Scanner(System.in); 由于输入是可见的, Scanner 类不适用于从控制台读取密码,可使用Console 类实现
格式化输出:f 表示浮点数,s 表示字符串,d 表示十进制整数等System.out.printf("%8.2f", x); 用 8 个字符的宽度和小数点后两个字符的精度打印 x。格式说明符语法 如下图:
在这里插入图片描述
文件输入与输出
读文件:Scanner in = new Scanner(Paths.get(“niyflle.txt”), “UTF-8”);
写文件:PrintWriter out = new PrintWriter (“myfile.txt”, “UTF-8”);
相对路径问题:文件位于 Java 虚拟机启动路径(是命令解释器的当前路径)的相对位置 。如果使用集成开发环境, 那么启动路径将由 IDE 控制。 可以使用下面的调用方式找到路径的位置: System.getProperty(“user.dir”);
8、控制流
条件(if/else,switch)、循环(while,for)
块作用域:不能在嵌套的两个块中声明同名的变量
switch:fallthrough(避免忘加break导致错误);case标签:char、byte、short 或、int 的常量表达式;枚举常量;字符串字面量(java7+)
中断控制流程:
break,continue
break带标签:标签必须放在希望跳出的最外 层循环之前,并且必须紧跟一个冒号
9、大数值
整数和浮点数精度不能够满足需求
Biglnteger 和 BigDecimal
10、数组
声明、初始化(new、直接赋值)、
java.util.Arrays 工具类:数组拷贝、排序、二分查找、填充(一维)等
for each循环
Java 实际上没有多维数组,只有一维数组。多维数组被解释为“ 数组的数组”
构造不规则数组:先规定行数int [ ][ ] odds = new int[n][ ]; 再初始化每一行















































3 对象与类

类–>对象实例
在类之间, 最常见的关系有
•依赖( “ uses-a”):应该尽可能地将相互依赖的类减至最少;低耦合
•聚合( “ has-a”):聚合关系意味着类 A 的对象包含类 B 的对象。
•继承( “ is-a”)
任何对象变量的值都是对存储在另外一个地方的一个对象引用
访问器方法 vs 修改器方法:





访问器方法只访问对象,返回新的值;不要编写返回引用可变对象的访问器方法,如果需要返回一个可变对象的引用, 应该首先对它进行克隆(clone)详见p127
修改器方法直接修改对象

预定义类 vs 用户自定义类

预定义类:使用LocalDate而不是Date
用户自定义类: 构造器(不要在构造器中定义与实例域重名的局部变量);隐式参数(即调用这个方法的对象,用this指代)与显式参数;封装(数据域private,get,set)

基于类的访问权限

一个方法可以访问所属类的所有对象的私有数据

私有方法

由于公有数据非常危险,所以应该将所有的数据域都设置为私有的。绝大多数方法都被设计为公有的,但在某些特殊情况下,也可能将它们设计为私有的。例如将一个计算代码划分成若干个独立的辅助方法。只要方法是私有的,它不会被外部的其他类操作调用

静态域和静态方法

静态域:静态常量
静态方法;工厂方法(用于构造对象);main方法

方法参数(p135)

Java方法是按值调用,方法得到的是参数值的一个拷贝,方法不能修改传递给它的任何参数变量的内容。
方法参数共有两种类型:

基本数据类型(数值型、布尔值),无法修改其值
对象引用,方法得到的是对象引用的拷贝,对象引用及其拷贝同时引用同一个对象,可以改变对象的参数状态,但不能改变对象引用的值。

对象构造

1.初始化块:static{…}
2.构造器:Java 提供了多种编写构造器的机制,多个构造器可相互调用

重载overload:类有多个构造器,如果多个方法有相同的名字、不同的参数,便产生了重载。Java 允许重载任何方法, 而不只是构造器方法。要完整地描述一个方法, 需要指出方法名以及参数类型,这叫做方法的签名。

默认域初始化:必须明确地初始化方法中的局部变量。 但是, 如果没有初始化类中的域, 将会被自动初始化为默认值(差的编程习惯)
无参构造器:仅当类没有提供任何构造器的时候, 系统才会提供一个默认的构造器

包: 使用包(package) 将类组织起来,使用包的主要原因是确保类名的唯一性

  • 一个类可以使用所属包中的所有类, 以及其他包中的公有类(public class)
  • 编译器在编译源文件的时候不检查目录结构(包结构),但如果包与目录不匹配, 虚拟机就找不到类
  • 包作用域:标记为public 的部分可以被任意的类使 用;标记为private 的部分只能被定义它们的类使用。如果没有指定 public 或 private, 这个部分(类、方法或变量)可以被同一个包中的所有方法访问。

类路径

类存储在文件系统的子目录中, 类的路径必须与包名匹配。(告诉编译器,去哪儿查找类文件)

文档注释(javadoc)

  • 类注释: 类注释必须放在 import 语句之后,类定义之前
  • 方法注释:每一个方法注释必须放在所描述的方法之前:@param @return ©throws (表明方法可能抛异常)
  • 域注释:要对公有域(通常指的是静态常量)建立文档。
  • 通用注释

类的设计技巧

1 一定要保证数据私有
2一定要对数据初始化
3不要在类中使用过多的基本类型
4不是所有的域都需要独立的域访问器和域更改器
5将职责过多的类进行分解
6类名和方法名要能够体现它们的职责
7优先使用不可变的类





4.继承

is-a” 规则, 子类的每个对象也是父类的对象。
4.1类、超(父)类、子类
extends(继承):子类继承了父类的非私有的属性和方法,还可以扩展自己的属性和方法。将通用的方法放在超类中, 而将具有特殊用途的方法放在子类中。
override(覆盖):通过定义一个定义了一个与超类签名相同的方法,子类中的方法覆盖父类中的方法。不过,返回类型不是签名的一部分, 因此,在覆盖方法时, 一定要保证返回类型的兼容性, 允许子类将覆盖方法的返回类型定义为原返回类型的子类型。在覆盖一个方法的时候,子类方法不能低于超类方法的可见性
super: 在子类中访问父类的方法和构造器
子类的构造器:子类构造器的第一条语句必须是对超类构造器的调用。如果子类的构造器没有显式地调用超类的构造器, 则将自动地调用超类默认(没有参数) 的构造器。
**继承层次:**Java不支持多继承(最多只有一个父类)
多态: is-a” 规则的另一种表述法是置换法则。它表明程序中出现超类对象的任何地方都可以用子类对象置换






即将一个子类的对象赋给超类变量。 在 Java程序设计语言中,对象变量是多态的,父类对象变量既可以指向父类对象,也可以指向其子类的对象(此时编译器仍将其看作父类对象,即不能访问子类的对象中特有的方法),但不能将一个超类的对象赋给子类变量。
● 子类数组的引用可以转换成超类数组的引用, 而不需要采用强制类型转换。使用时需要注意,可能编译通过,但运行出bug。在编译阶段,只是检查参数的引用类型, 然而在运行时,Java 虚拟机指定对象的类型并且运行该对象的方法
(详见https://blog.csdn.net/zhenyucheung/article/details/2439194)

理解方法调用

如何在对象上应用方法调用

1.编译器査看对象的声明类型和方法名。假设调用 x.f(param),有可能存在多个名字为 f, 但参数类型不一样的方法。编译器将会一一列举所有 C 类中名为 f 的方法和其超类中访问属性为 public 且名为 f 的方法。至此,编译器已获得所有可能被调用的候选方法。
2.编译器将査看调用方法时提供的参数类型,如果在所有名为 f 的方法中存在 一个与提供的参数类型完全匹配,就选择这个方法。这个过程被称为重载解析(overloading resolution)。由于允许类型转换(int 可以转换成double, Manager 可以转换成 Employee, 等等), 所以这个过程可能很复杂。如果编译器没有找到与参数类型匹配的方法, 或者发现经过类型转换后有多个方法与之匹配, 就会报错。 至此, 编译器已获得需要调用的方法名字和参数类型。
3. 如果是 private 方法、 static 方法、final 方法或者构造器, 那么编译器将可以准确地知道应该调用哪个方法,这种调用方式称为静态绑定(static binding)。
4. 当程序运行,并且采用动态绑定调用方法时, 虚拟机调用与 x 所引用对象的实际类型最合适的那个类的方法。假设 x 的实际类型是 D,它是 C类的子类。如果 D类定义了 方法 f(String),就直接调用它;否则,将在 D类的超类中寻找 f(String), 以此类推。每次调用方法都要进行搜索,时间开销相当大。因此,虚拟机预先为每个类创建了一个 方法表, 其中列出了所有方法的签名和实际调用的方法。动态绑定有一个非常重要的特性: 无需对现存的代码进行修改,就可以对程序进行扩展。


阻止继承:final类和方法

●不允许扩展的类被称为 final 类。
●类中的特定方法也可以被声明为 final,这样子类就不能覆盖这个方法。final 类中的所有方法自动地成为 final 方法,但不包括域。
●将方法或类声明为final 主要目的是: 确保它们不会在子类中改变语义。
●如果一个方法没有被覆盖并且很短, 编译器就能够对它进行优化处理, 这个过程为称为内联 ( inlining)。例如,内联调用 e.getName( ) 将被替换为访问 e.name 域。然而,如果 getName 在另外一个类中被覆盖, 那么编译器就无法知道覆盖的代码将会做什么操作,因此也就不能对它进行内联处理了。 幸运的是, 虚拟机中的即时编译器比传统编译器的处理能力强得多。这种编译器可以准 确地知道类之间的继承关系, 并能够检测出类中是否真正地存在覆盖给定的方法。如果方法很简短、 被频繁调用且没有真正地被覆盖, 那么即时编译器就会将这个方法进行内联处理。如果虚拟机加载了另外一个子类,而在这个子类中包含了对内联方法的覆盖, 优化器将取消对覆盖方法的内联。


强制类型转换

●普通类型间的强制转换
●将某个类的对象引用转换成另外一个类的对象引用。
将子类的引用赋给一个超类变量(上转型), 编译器是允许的。但将一个超类的引用赋给一个子类变量, 必须进行类型转换(下转型)。
基本原则:
●只能在继承层次内进行类型转换。
●在将超类转换成子类之前,应该使用 instanceof进行检查。
●上下转型的意义:https://blog.csdn.net/xyh269/article/details/52231944





抽象类( abstract)
抽象类是用来捕捉子类的通用特性的,是子类的模板

类如果包含抽象方法,一定是抽象类,但抽象类可以不包含抽象方法,即可提供方法的默认实现。
抽象类不能被实例化,可以定义一个抽象类的对象变量, 但是它只能引用非抽象子类的对象。
抽象类可以有构造器,除了不能实例化抽象类之外,和普通Java类没有任何区别
一个子类实现了父类(抽象类)的所有抽象方法,且子类中未新定义抽象方法,那么该子类可以不必是抽象类,否则就是抽象类


受保护访问(protected)

1.父类的protected成员包内可见且对其子类可见;
2.对于子类父类不同包情形,在子类所在的包中,子类能访问自身从基类继承而来protected成员,而不能访问基类实例本身的protected成员,要访问基类实例本身的protected成员需使用super。

Java 用于控制可见性的 4 个访问修饰符:
1 ) 仅对本类可见 private。
2 ) 对本包可见default,不需要修饰符
3 ) 对本包和所有子类可见 protected
4 ) 对所有类可见 public



Object: 所有类的超类
Object 类是 Java 中所有类的始祖, 在 Java 中每个类都是由它扩展而来的。
可以使用 Object 类型的变量引用任何类型的对象(上转型),要想对其中的内容进行具体的操作,还需要清楚对象的原始类型, 并进行下转型
1.equals方法 : 在 Object 类中,这 个方法将判断两个对象是否具有相同的引用


相等测试与继承: Java语言规范要求 equals 方法具有下面的特性

①自反性②对称性③传递性④一致性⑤对于任意非空引用 x, x.equals(null) 应该返回 false。
然而, 就对称性来说, 当参数不属于同一个类的时候需要仔细地思考一下。
child instanceof Father:判断其左边对象是否为其右边类的实例,也可以用来判断继承中的子类的实例是否为父类的实现,在Father.equals 中用 instanceof进行检测, 则返回 true,根据equals的对称性,若father.equals(child)为true,则child.equals(father)也应该为true,这就使得 Child类受到了束缚,子类的 equals方法必须能够用自己与任何一 个Father对象进行比较, 而不考虑子类拥有的那部分特有信息。
•如果子类能够拥有自己的相等概念, 则对称性需求将强制采用 getClass 进行检测
•如果由超类决定相等的概念,那么就可以使用 instanceof 进行检测, 这样可以在不同子类的对象之间进行相等的比较



下面给出编写一个完美的 equals方法的建议:
1 ) 显式参数命名为 otherObject
2 ) 检测 this 与 otherObject 是否引用同一个对象: if (this == otherObject) return true; (优化,如果引用对象相等,具体的域肯定也相等)。
3 ) if (otherObject == null) return false;
4 ) 比较 this 与 otherObject 是否属于同一个类。
如果 equals 的语义在每个子类中有所改 变,就使用 getClass 检测: if (getClass() != otherObject.getCIassO) return false;
如果所有的子类都拥有统一的语义,就使用 instanceof检测: if (!(otherObject instanceof ClassName)) return false;
5 ) 将 otherObject 转换为相应的类类型变量: ClassName other = (ClassName) otherObject
6 ) 现在开始对所有需要比较的域进行比较了。使用 = 比较基本类型域,使用 equals 比 较对象域。如果所有的域都匹配, 就返回 true; 否 则 返 回 false。
如果在子类中重新定义 equals, 就要在其中包含调用super.equals(other)








2.hashCode方法:

散列码是由对象导出的一个整型值,每个对象都有默认散列码,值由对象存储地址得出。字符串散列码由内容导出,值可能相同
为了保证在集合类中的一致性,一般需要同时重写 equals 和 hashCode,二者 的定义必须一致(比较的依据),要求 equals 相同 hashCode 必须相同,即两个相等的对象要返回相等的散列码,以便用户可以将对象插人到散列表中,hashCode 相同 equals 未必相同。

3.toString 方法
它用于返回表示对象值的字符串

只要对象与一个字符串通过操作符“ +” 连接起 来,Java 编译就会自动地调用 toString方法,以便获得这个对象的字符串描述。

泛型数组列表ArrayList

数组:允许在运行时确定数组的大小
ArrayList :采用类型参数(type parameter) 的泛型类(generic class),解决运行时动态更改数组大小
ArrayList<Employee//> staff = new ArrayList<Employee>0;
进一步的可以写成,ArrayList<Employee> staff = new ArrayList<>(); 这被称为“ 菱形” 语法
管理着对象引用的一个内部数组,如果调用 add且内部数组已经满了,数组列表就将自动地创建 一个更大的数组,并将所有的对象从较小的数组中拷贝到较大的数组中。 在初始化ArrayList的时候可以设定内部数组大小的初值n,初始化完容量和长度都是零,插入一个元素后,容量就会扩容至n,长度为1
操作ArrayList:add(),get(),set(),remove()
数组列表转数组:
①Object[] toArray():是将list直接转为Object[] 数组,并不是数组的原类型
②<T> T[] toArray(T[] a):将list转化为特定类型的数组
如果数组存储的元素数比较多, 又经常需要在中间位置插入、删除元素, 就应该考虑使用链表了
类型化与原始数组列表的兼容性:
●将一个原始 ArrayList 赋给一个类型化 ArrayList 会得到一个警告,使用类型转换并不能避免出现警告,可以添加@SuppressWamings(“unchecked”) 。反之是没有问题的。










对象包装器与自动装箱

所有的基本类型都有一个与之对应的类。 这些类称为包装器 ( wrapper),对象包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值。同时, 对象包装器类还是 final, 因此不能定义它们的子类。 有些人认为包装器类可以用来实现修改数值参数的方法, 然而这是错误的,因为Integer 对象是不可变的
自动装箱;自动地拆箱
在两个包装器对象比较时调用 equals方法,会进行值的比较,用 == 运算符会比较对象是否指向同一个存储区域
装箱和拆箱是编译器认可的,而不是虚拟机。编译器在生成类的字节码 时, 插人必要的方法调用。虚拟机只是执行这些字节码。
常见方法:intValue(); toString(); [parseInt();valueOf()]



参数数量可变的方法

参数定义:double… values
可以将已经存在且最后一个参数是数组的方法重新定义为可变参数的方法, 而不会破坏任何已经存在的代码

枚举类: enum

常用方法:
•static Enum valueOf(Cl ass enumClass ,String name) 返回指定名字、给定类的枚举常量。
•String toString( ) 返回枚举常量名。
•int ordinal ( ) 返回枚举常量在 enum 声明中的位置,位置从 0开始计数。
•int compareTo( E other ) 按枚举常量出现的先后顺序比较



反射
在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
与反射相关的类:
Class类 代表类的实体,在运行的Java应用程序中表示类和接口
Field类 代表类的成员变量(成员变量也称为类的属性)
Method类 代表类的方法
Constructor类 代表类的构造方法
详见:https://www.jianshu.com/p/9be58ee20dee**(写代码实验一下)**
建议 Java 开发者不要使用 Method 对象的 回调功能。使用接口进行回调会使得代码的执行速度更快, 更易于维护







继承的设计技巧
1、将公共操作和域放在超类
2、不要使用受保护的域
3、使用继承实现“ is-a” 关系
4、 除非所有继承的方法都有意义, 否则不要使用继承
5、在覆盖方法时, 不要改变预期的行为 :应该是由于超类的方法不适宜子类的应用,或者有缺陷的时候,才覆盖该方法
6、使用多态, 而非类型信息
7、不要过多地使用反射






5 接口、lambda 表达式与内部类

5.1 接口

●一个类可以实现(implement)一个或多个接口,接口不是类,而是对类的一组需求描述
●接口中的所有方法自动地属于 public,域是public static final
●接口绝不能含有实例域(变量), 在 JavaSE 8之前, 也不能在接口中实现方法,在 Java SE 8中,允许在接口中增加静态方法,可以为接口方法提供一个默认实现(default修饰符)。
使用 Arrays 类的 sort 方法对 Employee 对象数组进行排序, Employee类就必须实现 Comparable 接口,Employee 类需要提供 compareTo方法的实现。
●与 equals 方法一样, 在继承过程中compareTo方法有可能会出现问题



1、如果子类之间的比较含义不一样, 那就属于不同类对象的非法比较,在compareTo一开始就要检查if (getClass()!= other.getClass()) throw new ClassCastException()
2、如果存在这样一种通用算法, 它能够对两个不同的子类对象进行比较, 则应该在超 类中提供一个 compareTo方法,并将这个方法声明为 final

●接口的特性:无法实例化;能声明接口变量,并引用实现了接口的类对象;可用 instanceof 检查一个对象是否实现了某个特定的接口;接口也可以被扩展(接口之间的extends),允许存在多条从具有较高通用性的接口到较高专用性的接口的链
●重要接口 Cloneable 接口,某个类实现了这个几口,Object 类中的 clone方法就可以创建类对象的一个拷贝
●默认方法冲突:①超类优先②接口冲突:如果一个超接口提供了一个默认方法,另一个接口提供了一个同名而且参数类型(不论是否是默认方法)相同的方法, 必须覆盖这个方法来解决冲突。

5.2 接口示例

5.2.1接口与回调(callback):当某个事件发生时,回调
5.2.2Comparator 接口:定义一个比较器类,实现Comparator 接口,实现compare方法,这个 compare方法要在比较器对象上调用, 而不是在对象本身上调用。这样可以定义多个比较器,实现按不同规则的排序,而前面的实现Comparable接口,因为是在对象本身上调用的,一次只能定义一种比较规则。
应用到Arrays.sort(),需要传入一个比较器对象。
5.2.3 对象克隆
Cloneable 接口:希望 copy 是一个新对象,它的初始状态与 original 相同, 但是之后它们各自会有自 己不同的状态。
①直接使用Object类的clone方法(protected),默认的克隆操作是“ 浅拷贝”,并没有克隆对象中引用的其他对象;实现Cloneable 接口可以进行“深拷贝”
②如果在一个对象上调用clone, 但这个对象的类并没有实现 Cloneable 接口, Object 类 的 clone 方法就会拋出一个 CloneNotSupportedException
③所有数组类型都有一个 public 的 clone 方法






5.3 lambda 表达式

lambda 表达式就是一个代码块
lambda表达式的形式:(参数), 箭头(->) 以及一个表达式

● 如果代码要完成的计算无法放在一个表达式中,可以把这些代码放在 { }中, 并包含显式的 return语句
● 即使 lambda 表达式没有参数, 仍然要提供空括号
● 如果可以推导出一个 lambda 表达式的参数类型,则可以忽略其类型
● 如果方法只有一个参数, 而且这个参数的类型可以推导得出,那么甚至还可以省略小括号
● 无需指定 lambda 表达式的返回类型。lambda 表达式的返回类型总是会由上下文推导得出



函数式接口:@FunctionalInterface,一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。是适用于函数式编程场景的接口,而java中的函数式编程体现就是lambda,lambda 表达式可以转换为函数式接口。
java8提供的一些内置函数式接口:supplier接口、Consumer接口、Predicate接口、Function接口等
在这里插入图片描述
方法引用:用于传递方法,要用::操作符分隔方法名与对象或类名,方法引用不能独立存在,总是会转换为函数式接口的实例
主要有 3 种情况:
•object::instanceMethod
•Class::staticMethod
•Class::instanceMethod
在前 2 种情况中,方法引用等价于提供方法参数的 lambda 表达式
对于第 3 种情况, 第 1 个参数会成为方法的目标。例如,String::compareToIgnoreCase 等 同于(x,y)-> x.compareToIgnoreCase(y)
构造器引用:构与方法引用很类似,只不过方法名为 new。例如,Person::new 是 Person构造器的一个引用
函数式接口、方法引用、构造器引用:https://www.jianshu.com/p/baa614e8c16c
变量作用域











lamada表达式有三个部分:
* 1)一个代码块
* 2)参数
* 3)自由变量的值,这是指非参数而且不在代码中定义的变量
含有自由变量的代码块才被称之为“闭包(closure)”。在Java中,lambda表达式就是闭包。事实上,内部类一直都是闭包。lambda 表达式可以捕获外围作用域中变量的值。在 Java 中, 要确保所捕获的值是明确定义的
1,在lamada表达式中,只能引用值不会改变的变量(内外部都不能改变),lambda 表达式中捕获的变量必须实际上是最终变量
2, 在lamada表达式中声明与一个局部变量同名的参数或局部变量是不合法的
3, 在一个 lambda 表达式中使用 this 关键字时, 是指创建这个 lambda 表达式的方法的 this 参数






处理lambda表达式

使用 lambda 表达式的重点是延迟执行。要接受一个 lambda 表达式, 需要选择(偶尔可能需要提供)一个函数式接口(见上面的表)

再谈 Comparator

Comparator 接口包含很多方便的静态方法来创建比较器
● 如:comparing、thenComparing、naturalOrder、reverseOrder。这些方法可以用于 lambda 表达式或方法引用。
静态 comparing 方法取一个“ 键提取器” 函数, 它将类型 T 映射为一个可比较的类型 (如 String)。对要比较的对象应用这个函数, 然后对返回的键完成比较。
naturalOrder 方法可以为任何实现了 Comparable 的类建立一个自然顺序比较器。naturalOrder().reversed() 等同于 reverseOrder()。
还有nullsFirst和nullsLast适配器,会修改现有的比较器,从而在键函数返回 null 值时不会抛出异常,而是将这个值标记为小于或大于正常值。
代码示例如下:




//示例
Arrays.sort(people, Comparator.comparing(Person::getName));
Arrays.sort(people, Comparator.comparing(Person::getLastName).thenComparing(Person::getFirstName));
Arrays.sort(people, Comparator.comparing(Person::getName, (s, t) -> Integer.compare(s.length(), t.length())));
Arrays.sort(people, Comparator.comparingInt(p -> p.getName().length()));
Arrays.sort(people, comparing(Person::getMiddleName, nullsFirst(naturalOrder())));

以上代码都未实践

5.4 内部类

内部类是定义在另一个类中的类,设计内部类的原因:
•内部类方法可以访问该类定义所在的作用域中的数据, 包括私有的数据。 即:内部类既可以访问自身的数据域,也 可以访问创建它的外围类对象的数据域
•内部类可以对同一个包中的其他类隐藏起来。
•当想要定义一个回调函数且不想编写大量代码时,使用匿名(anonymous) 内部类比较便捷。
内部类的对象有一个隐式引用, 它引用了实例化该内部对象的外围类对象。通过这个指针, 可以访问外围类对象的全部状态。static 内部类没有这种附加指针。



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

编译器修改了所有的内部类的构造器, 添加一个外围类引用的参数(传入this),通过这个引用, 可以访问外围类对象的全部状态
内部类声明为私有的。这样一来, 只有他所在的类的方法才能够构造内部类对象。只有内部类可以是私有类,而常规类只可以具有包可见性,或公有可见性

5.4.2 内部类的特殊语法规则

内部类有一个外围类的引用 outer,表达式 OuterClass.this 表示外围类引用
可以采用下列语法格式更加明确地编写内部对象的构造器:
outerObject.new InnerClass(construction parameters)
例如:ActionListener listener = this.new TimePrinter();
在这里,最新构造的 TimePrinter 对象的外围类引用被设置为创建内部类对象的方法中的 this 引用。这是一种最常见的情况。通常,this 限定词是多余的。不过,可以通过显式地命名将外围类引用设置为其他的对象。
例如, 如果 TimePrinter 是一个公有内部类,对于任意的语音时钟都可以构造一个 TimePrinter
TalkingClock jabberer = new TalkingClock (1000, true);
TalkingClock.TimePrinter listener = jabberer.new TimePrinter();
需要注意, 在外围类的作用域之外,可以这样引用内部类: OuterClass.InnerClass
内部类中声明的所有静态域都必须是 final.原因很简单。我们希望一个静态域只 有一个实例, 不过对于每个外部对象, 会分别有一个单独的内部类实例。如果这个域不 是 final, 它可能就不是唯一的。
内部类不能有 static 方法。Java 语言规范对这个限制没有做任何解释。也可以允许有静态方法,但只能访问外围类的静态域和方法 p265









5.5 代理


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!