《On Java 8》笔记

第一章 对象的概念

复用

组合和聚合

  • 组合(Composition)经常用来表示“拥有”关系(has-a relationship)。例如,“汽车拥有引擎”
  • 聚合(Aggregation)动态的组合

实心三角形指向“ Car ”表示 组合 的关系;如果是 聚合 关系,可以使用空心三角形

组合关系中

  1. 整件拥有部件的生命周期,所以整件删除时,部件一定会跟着删除
  2. 多个整件不可以同时共享同一个部件

两个类生命周期不同步,则是聚合关系,生命周期同步就是组合关系

人和手,脚是组合关系,因为当人死亡后人的手也就不复存在了。人和他的电脑是聚合关系

第三章 万物皆对象

数据存储

  1. 寄存器 CPU
  2. 栈内存
  3. 堆内存
  4. 常量存储 ROM
  5. 非RAM存储

基本数据类型

byte 8 bit
short char 16
int 32
long 64

float 32bit
double 64
boolean
Java规范中,没有明确指出boolean的大小。在《Java虚拟机规范》给出了4个字节,和boolean数组1个字节的定义,具体还要看虚拟机实现是否按照规范来,所以1个字节、4个字节都是有可能的

高精度数值

BigInteger BigDecimal

基本类型默认值

如果类的成员变量(字段)是基本类型,那么在类初始化时,这些类型将会被赋予一个初始值。局部变量则会提示:“编译时错误,该变量可能尚未被初始化”
boolean:false
char:\u0000 (null)
其他都是对应类型的0 比如0.0d 0L

返回类型

方法名和参数列表统称为方法签名(signature of the method)。签名作为方法的唯一标识

static

  1. 只为特定字段(注:也称为属性、域)分配一个共享存储空间
  2. 创建一个与此类的任何对象无关的方法

小试牛刀

System 类中有几个字段,如果你选择了 out,你会发现它是一个静态的 PrintStream 对象
System.out.println("A String of things");

第四章 运算符

关系运算符

Integer 内部维护着一个 IntegerCache 的缓存,默认缓存范围是 [-128, 127],所以 [-128, 127] 之间的值用 == 和 != 比较也能能到正确的结果,但是不推荐用关系运算符比较

字面值常量

        int i1 = 0x2f; // 16进制 (小写)
        System.out.println(
        "i1: " + Integer.toBinaryString(i1)); // i1: 101111

        int i3 = 0177; // 8进制 (前导0)
        System.out.println(
        "i3: " + Integer.toBinaryString(i3)); // i3: 1111111

        int bin = 0b0010_1111_1010_1111_1010_1111_1010_1111; // 2进制(前导 0b 或 0B)
        System.out.println(Integer.toBinaryString(bin)); // bin:101111101011111010111110101111

Long 型数值,结尾使用大写 L 或小写 l 皆可(不推荐使用 l,因为容易与阿拉伯数值 1 混淆)
大写 F 或小写 f 表示 float 浮点数
大写 D 或小写 d 表示 double 双精度
Java 7 中有一个深思熟虑的补充:我们可以在数字字面量中包含下划线 _,以使结果更清晰

字符串运算符 +

只要当中有一条数据是字符串类型,其他非字符串数据都将被转换为字符串形式并连接

截断和舍入

从 float 和 double 转换为整数值时,小数位将被截断。四舍五入,可以使用 java.lang.Math 的 round() 方法

类型提升

char、byte 或 short 执行任何算术或按位操作,这些值会在执行操作之前类型提升为 int,并且结果值的类型为 int
若想重新使用较小的类型,必须使用强制转换(由于重新分配回一个较小的类型,结果可能会丢失精度)
通常,表达式中最大的数据类型是决定表达式结果的数据类型
两个大的 int 型整数相乘时,结果有可能超出 int 型的范围,发生溢出

第五章 控制流

第六章 初始化和清理

重载方法

以根据参数列表中的参数顺序来区分不同的方法
传入的参数类型大于方法期望接收的参数类型,你必须首先做下转换,如果你不做的话,编译器就会报错
不能通过方法名和返回值区分方法:因为调用一个方法且忽略返回值,编译器就不知道你要调用哪个方法

构造器

一旦你显式地定义了构造器(无论有参还是无参),编译器就不会自动为你创建无参构造器

this

this 关键字只能在非静态方法内部使用
在构造器中调用构造器:其中只能通过 this 调用一次构造器。另外,必须首先调用构造器,否则编译器会报错

static

static 方法中不会存在 this。你不能在静态方法中调用非静态方法(反之可以)
静态方法是为类而创建的,不需要任何对象

垃圾回收

当垃圾回收器准备回收对象的内存时,首先会调用其 finalize() 方法,并在下一轮的垃圾回收动作发生时,才会真正回收对象占用的内存
finalize() 是一个潜在的编程陷阱:

  1. 对象可能不被垃圾回收
  2. 垃圾回收不等同于 析构(清空并释放对象先前创建或是占用的存储器资源)
  3. 垃圾回收只与内存有关

垃圾回收器的存在并不能完全替代析构函数(而且绝对不能直接调用 finalize(),所以这也不是一种解决方案),还是得明确调用某个恰当的 Java 方法
无论是"垃圾回收"还是"终结",都不保证一定会发生。如果 Java 虚拟机(JVM)并未面临内存耗尽的情形,它可能不会浪费时间执行垃圾回收以恢复内存

垃圾回收器如何工作

对于任意"活"的对象,一定能最终追溯到其存活在栈或静态存储区中的引用。这个引用链条可能会穿过数个对象层次,由此,如果从栈或静态存储区出发,遍历所有的引用,你将会发现所有"活"的对象。对于发现的每个引用,必须追踪它所引用的对象,然后是该对象包含的所有引用,如此反复进行,直到访问完"根源于栈或静态存储区的引用"所形成的整个网络。你所访问过的对象一定是"活"的。注意,这解决了对象间循环引用的问题,这些对象不会被发现,因此也就被自动回收了

如何处理找到的存活对象,取决于不同的 Java 虚拟机实现
其中有一种做法叫做停止-复制(stop-and-copy)(效率低下):
需要先暂停程序的运行(不属于后台回收模式),然后将所有存活的对象从当前堆复制到另一个堆,没有复制的就是需要被垃圾回收的。另外,当对象被复制到新堆时,它们是一个挨着一个紧凑排列,然后就可以按照前面描述的那样简单、直接地分配新空间了。当对象从一处复制到另一处,所有指向它的引用都必须修正

一旦程序进入稳定状态之后,可能只会产生少量垃圾,甚至没有垃圾,来回复制导致浪费
一些 Java 虚拟机会进行检查:要是没有新垃圾产生,就会转换到另一种模式(即"自适应")。这种模式称为标记-清扫(mark-and-sweep)

Java 虚拟机中有许多附加技术用来提升速度。尤其是与加载器操作有关的,被称为"即时"(Just-In-Time, JIT)编译器的技术,可以把程序全部或部分翻译成本地机器码,所以不需要 JVM 来进行翻译,因此运行得更快

成员初始化

局部变量必须初始化
类的成员变量为基本类型会被赋予初值(char 值为 0,所以显示为空白),如果是为引用类型就会赋值:null

初始化的顺序

即使类成员初始化的代码散布在类的各处,他们也都会在调用构造器或其他方法之前得到初始化

静态数据的初始化

static 关键字不能应用于局部变量,所以只能作用于属性(字段、域),会有默认值

加载顺序:

  • 初始化静态成员变量
  • 构造器:先初始化它的静态成员变量、实例成员变量、指定的构造器

静态初始化只有在必要时刻才会进行,如果不创建指定对象,那么这个类就不会被初始化
只有在某个对象第一次被创建(或被访问)时,它们才会被初始化

静态对象不会再次被初始化,初始化的顺序先是静态对象(如果它们之前没有被初始化的话),然后是非静态对象

判断执行顺序:

  1. 找到main 先看看有没有静态对象或者静态代码块,先进行静态初始化 (只初始化一次)
    • 如果其中包含对象的实例化,就要追溯到这个类里
  2. 实例化对象,先看是否有静态初始化,没有就看成员变量初始化包括代码块 (每次实例化对象都会调用),然后是构造器

(如果main方法所在的类有静态初始化,要在main之前先静态初始化,如果是成员初始化或者代码块,那就要等待main或者其他实例化该类触发初始化)

静态块

这段代码仅执行一次:当首次创建这个类的对象或首次访问这个类的静态成员(甚至不需要创建该类的对象)时

可变参数

编译器都会使用自动装箱来匹配重载的方法,然后调用最明确匹配的方法,不当使用报错:ambiguous
你应该总是在重载方法的一个版本上使用可变参数列表,或者压根不用它

枚举

枚举类型的实例是常量,因此按照常量命名
你创建 enum 时,编译器会自动添加一些有用的特性:

  • toString() 显示某个 enum 实例的名称
  • ordinal() 表示某个特定 enum 常量的声明顺序
  • static values() 按照 enum 常量的声明顺序,生成这些常量值构成的数组

第七章 封装

所有优秀的作者——包括那些编写软件的人——都知道一件好的作品都是经过反复打磨才变得优秀的。如果你把一段代码置于某个位置一段时间,过一会重新来看,你可能发现更好的实现方式。这是重构(refactoring)的原动力之一,重构就是重写可工作的代码,使之更加可读,易懂,因而更易维护。

但是,在修改和完善代码的愿望下,也存在巨大的压力。通常,一些用户(客户端程序员(client programmers))希望你的代码在某些方面保持不变。所以你想修改代码,但他们希望代码保持不变。由此引出了面向对象设计中的一个基本问题:“如何区分变动的事物和不变的事物”。

代码组织

package 语句,它必须是文件中除了注释之外的第一行代码

package hiding;

创建独一无二的包名

CLASSPATH 包含一个或多个目录,用作查找 .class 文件的根目录。从根目录开始,Java 解释器获取包名并将每个句点替换成反斜杠,生成一个基于根目录的路径名(取决于你的操作系统,包名 foo.bar.baz 变成 foo\bar\baz 或 foo/bar/baz 或其它)

例如:当编译器遇到导入 simple的库的 import 语句时,它首先会在 CLASSPATH 指定的目录中查找子目录 com/mindviewinc/simple,然后从已编译的文件中找出名称相符者(对 Vector 而言是 Vector.class,对 List 而言是 List.class)。注意,这两个类和其中要访问的方法都必须是 public 修饰的

访问权限修饰符

public,protected 和 private 位于定义的类名,属性名和方法名之前。每个访问权限修饰符只能控制它所修饰的对象
class:只能用 pulbic 和 包访问权限
类成员:四个都可以使用

接口和实现

为了清晰起见,你可以采用一种创建类的风格:public 成员放在类的开头,接着是 protected 成员,包访问权限成员,最后是 private 成员

总结

控制成员访问权限有两个原因:

  1. 使用户不要接触他们不该接触的部分,这部分对于类内部来说是必要的,但是不属于客户端程序员所需接口的一部分
  2. 让类库设计者更改类内部的工作方式,而不用担心会影响到客户端程序员

类的 public 接口是用户真正看到的,所以在分析和设计阶段决定这部分接口是最重要的部分。尽管如此,你仍然有改变的空间。如果最初没有创建出正确的接口,可以添加更多的方法,只要你不删除那些客户端程序员已经在他们的代码中使用的东西

第八章 复用

方式

  1. 在新类中创建现有类的对象。这种方式叫做“组合”(Composition)
  2. 创建现有类类型的新类,这种方式就叫做“继承”(Inheritance)

初始化基类(父类)

通过调用父类构造函数在构造函数中执行初始化,该构造函数具有执行父类初始化所需的所有适当信息和特权。Java 自动在派生类(子类)构造函数中插入对父类构造函数的调用

即使不为子类创建构造函数,编译器也会为你生成一个无参数构造函数,调用父类构造函数

委托

Java不直接支持的第三种重用关系称为委托。这介于继承和组合之间
子类集成父类,父类的所有方法会暴露在子类中,用委托解决:将子类方法方法被转发到父类对象,因此接口与继承的接口是相同的。但是,你对委托有更多的控制,因为你可以选择只在成员对象中提供方法的子集

组合与继承的选择

组合和继承都允许在新类中放置子对象(组合是显式的,而继承是隐式的)

  • 组合:当你想在新类中包含一个已有类的功能时。在新类中嵌入一个对象(通常是私有的),以实现其功能。新类的使用者看到的是你所定义的新类的接口,而非嵌入对象的接口
  • 继承:使用一个现有类并开发出它的新版本。通常这意味着使用一个通用类,并为了某个特殊需求将其特殊化

这种“是一个”的关系是用继承来表达的,而“有一个“的关系则用组合来表达

protected

通过 protected 控制类的继承者的访问权限,但是最好的方式是将属性声明为 private 以一直保留更改底层实现的权利

向上转型

子类和父类的一种关系:“子类是已有类的一种类型”
因为继承保证了父类的所有方法在子类中也是可用的,所以任意发送给该父类的消息也能发送给子类
因为是从一个更具体的类转化为一个更一般的类,所以向上转型永远是安全的,这就是为什么编译器在没有任何明确转型或其他特殊标记的情况下,仍然允许向上转型的原因

再论组合和继承

尽量少使用继承,除非确实使用继承是有帮助的
一种判断使用组合还是继承的最清晰的方法是:是否需要把子类向上转型为父类。如果必须向上转型,那么继承就是必要的,但如果不需要,则要进一步考虑是否该采用继承

final

常指的是“这是不能被改变的”。防止改变有两个原因:设计或效率(现在不用考虑通过final提高效率)
final常量:

  1. 必须是基本类型
  2. 用关键字 final 修饰
  3. 必须在定义常量的时候进行赋值

一个被 static 和 final 同时修饰的属性只会占用一段不能改变的存储空间
final 修饰对象(数组也是对象)引用:使引用恒定不变,引用被初始化指向了某个对象,它就不能改为指向其他对象,但是对象本身是可以修改

空白 final

没有初始化值的 final 属性,必须在定义时或在每个构造器中执行 final 变量的赋值操作,保证 final 属性在使用前已经被初始化过

final 参数

在方法中不能改变参数指向的对象或基本变量,只能读取而不能修改参数。这个特性主要用于传递数据给匿名内部类

final 和 private

类中所有的 private 方法都隐式地指定为 final。因为不能访问 private 方法,所以不能重写它
可以给 private 方法添加 final 修饰,但是并不能给方法带来额外的含义

final 忠告

使用final修饰,类被复用是很困难的,特别是通用类
例子:Vector 类,它的所有方法出于"效率"考虑(然而并没有提升效率,只是幻觉)全被指定为 final

类初始化和加载

每个类的编译代码都存在于它自己独立的文件中。该文件只有在使用程序代码时才会被加载
一般可以说“类的代码在首次使用时加载”。这通常是指创建类的第一个对象,或者是访问了类的 static 属性或方法
构造器也是一个 static 方法,尽管它的 static 关键字是隐式的
因此,准确地说,一个类当它任意一个 static 成员被访问时,就会被加载

继承和初始化

通俗理解:运行一个main(静态方法),加载器启动并找出这个类(main方法对应的类)的编译代码,如果这个类有父类会继续加载父类,不论是否创建了父类的对象,父类都会被加载,先初始化这个类的父类,再初始化这个类(这里初始化的都是static域 和 static代码块,只初始化一次),存在多个父类,就从根基类的static的初始化开始执行,因为子类static初始化可能依赖于父类的成员的正确初始化
然后回到mian方法,子类构造器默认会访问父类构造器,

其他

  1. Java 1.4 开始已将 Vector 类大多数方法的 final 去掉
  2. Java 5.0中放宽了“重写方法的返回值类型被要求必须与被重写方法一致”这一限制,添加了对协变返回类型的支持,在重写的时候,重写方法的返回值类型可以是被重写方法返回值类型的子类

第九章 多态

向上转型

把一个对象引用当作它的基类引用的做法称为向上转型,因为继承图中基类一般都位于最上方

方法调用绑定

将一个方法调用和一个方法主体关联起来称作绑定,绑定发生在程序运行前(如果有的话,由编译器和链接器实现),叫做前期绑定
后期绑定也称为动态绑定或运行时绑定:在运行时根据对象的类型进行绑定
Java 中除了 static 和 final 方法(private 方法也是隐式的 final)外,其他所有方法都是后期绑定。这意味着通常情况下,我们不需要判断后期绑定是否会发生——它自动发生

多态是一项“将改变的事物与不变的事物分离”的重要技术

陷阱:“重写”私有方法

private 方法可以当作是 final 的,对于子类类来说是隐蔽的,子类并不是重写了该方法,而是一个新方法,因为父类版本的该方法屏蔽了子类的该方法 ,因此它都不算是重写方法
用多态的方式子类对象赋值给父类,调用这个方法实际上还是调用父类的方法

非 private方法 才能重写,使用@Override 注解检测

陷阱:属性与静态方法

子类上转型,子类调用的是父类的属性(父类属性非private),子类方法调用父类属性显式地用super关键字
静态的方法只与类关联,与单个的对象无关(静态方法不能被重写,直接用类调用)

构造器调用顺序

  1. 子类构造器被调用。这个步骤被递归地重复,这样一来类层次的顶级父类会被最先构造,然后是它的子类,以此类推,直到最底层的子类
  2. 按声明顺序初始化成员
  3. 调用子类构造器的方法体

构造器内部多态方法的行为

父类构造器中调用可被重写的方法,在多态中,子类重写了这个方法,初始化子类构造器会先初始化父类构造器,这里会调用子类重写的方法,如果这个方法中用到了一些子类中定义的成员变量,那么这些变量这个时候还是默认值

因此,编写构造器有一条良好规范:做尽量少的事让对象进入良好状态
尽量不要调用类中的任何方法。在子类的构造器中能安全调用的只有子类的 final 方法(这也适用于可被看作是 final 的 private 方法)。这些方法不能被重写,因此不会产生意想不到的结果

协变返回类型

Java 5 中引入了协变返回类型,子类的被重写方法可以返回父类方法返回类型的子类型

替代 vs 扩展

因为类的接口已经确定了它是什么。继承可以确保任何子类都拥有子类的接口,绝对不会少
纯粹的替代意味着子类可以完美地替代子类,当使用它们时,完全不需要知道这些子类的信息,也就是说,子类可以接收任意发送给子类的消息,因为它们具有完全相同的接口。只需将子类向上转型,不要关注对象的具体类型。所有一切都可以通过多态处理

向下转型与运行时类型信息

由于向上转型(在继承层次中向上移动)会丢失具体的类型信息,那么为了重新获取类型信息,就需要在继承层次中向下移动,使用向下转型
向上转型永远是安全的,因为基类不会具有比派生类更多的接口
下转型失败: ClassCastException 异常

第十章 接口

接口和抽象类提供了一种将接口与实现分离的更加结构化的方法

抽象类和方法

在那些例子中,创建这个通用接口的唯一理由是,不同的子类可以用不同的方式表示此接口
Java 提供了一个叫做抽象方法的机制,这个方法是不完整的:它只有声明没有方法体
包含抽象方法的类叫做抽象类。如果一个类包含一个或多个抽象方法,那么类本身也必须限定为抽象的

abstract class Basic {
    abstract void unimplemented();
}   

如果创建一个继承抽象类的新类并为之创建对象,那么就必须为基类的所有抽象方法提供方法定义,否则新类必须是一个抽象类
不包含任何抽象方法的类指明为 abstract ,可以阻止其创建对象
接口只允许 public 方法
抽象类同时也是一种有用的重构工具,使用它们使得我们很容易地将沿着继承层级结构上移公共方法

接口创建

Java 8 之前的接口更加容易,因为它们只允许抽象方法,interface 关键字产生一个完全抽象的类。只能决定方法名、参数列表和返回类型,但是无法确定方法体

public interface PureInterface {
    int m1(); 
    void m2();
    double m3();
}

Java 8 允许接口包含默认方法静态方法

接口与抽象类最明显的区别:

  • 接口的典型使用是代表一个类的类型或一个形容词,如 Runnable 或 Serializable
  • 抽象类通常是类层次结构的一部分或一件事物的类型,如 String 或 ActionHero

接口

默认包访问权限,需要用public修饰
接口属性被隐式指明为 static 和 final

默认方法

任何实现接口却没有定义方法的时候可以使用 default 创建的方法体。默认方法比抽象类中的方法受到更多的限制

增加默认方法的极具说服力的理由是它允许在不破坏已使用接口的代码的情况下,在接口中增加新的方法。默认方法有时也被称为守卫方法虚拟扩展方法

多继承

Java 通过默认方法具有了某种多继承的特性
结合带有默认方法的接口意味着结合了多个基类中的行为,因为接口中仍然不允许存在属性(只有静态属性),所以属性仍然只会来自单个基类或抽象类,也就是说,不会存在状态的多继承

集成两个接口中包含签名相同的方法,实现类必须重写(权限为public)

interface Jim1{
    default void jim(){
        System.out.println("Jim1::jim");
    }
}
interface Jim2{
    default void jim(){
        System.out.println("Jim2::jim");
    }
}
public class Jim implements Jim1,Jim2 {
    @Override
    public void jim(){
        // 使用 super 关键字选择基类实现
        Jim2.super.jim();
    }

    public static void main(String[] args) {
        new Jim().jim();
    }
}

接口中的静态方法

Java 8 允许在接口中添加静态方法,这么做能恰当地把工具功能置于接口中,从而操作接口,或者成为通用的工具

模板方法设计模式:

public interface Operations {
    void execute();

    static void runOps(Operations... ops) {
        for (Operations op : ops) {
            op.execute();
        }
    }
    
    static void show(String msg){
        System.out.println(msg);
    }
}

class Bing implements Operations {

    @Override
    public void execute() {
        Operations.show("Bing");
    }
}

class Crack implements Operations {
    @Override
    public void execute() {
        Operations.show("Crack");
    }
}

class Twist implements Operations {
    @Override
    public void execute() {
        Operations.show("Twist");
    }
}


public class Machine {
    public static void main(String[] args) {
        Operations.runOps(
                new Bing(), new Crack(), new Twist()
        );
    }
}

抽象类和接口

特性 接口 抽象类
组合 新类可以组合多个接口 只能继承单一抽象类
状态 不能包含属性(除了静态属性,不支持对象状态) 可以包含属性,非抽象方法可能引用这些属性
默认方法 和 抽象方法 不需要在子类中实现默认方法。默认方法可以引用其他接口的方法 必须在子类中实现抽象方法
构造器 没有构造器 可以有构造器
可见性 隐式 public 可以是 protected 或 "friendly"

在合理的范围内尽可能地抽象。因此,更倾向使用接口而不是抽象类。只有当必要时才使用抽象类。除非必须使用,否则不要用接口和抽象类。大多数时候,普通类已经做得很好,如果不行的话,再移动到接口或抽象类中

完全解耦

策略设计模式:方法包含算法中不变的部分,策略包含变化的部分。策略就是传入的对象,它包含要执行的代码

class Processor {
    public String name() {
        return getClass().getSimpleName();
    }

    public Object process(Object input) {
        return input;
    }
}

class Upcase extends Processor {
    @Override
    // 子类重写父类方法时,返回值若为类类型,则必须与父类返回值类型相同或为其子类
    public String process(Object input) {
        return ((String) input).toUpperCase();
    }
}

class Downcase extends Processor {
    @Override
    public String process(Object input) {
        return ((String) input).toLowerCase();
    }
}

class Splitter extends Processor {
    @Override
    public String process(Object input) {
        return Arrays.toString(((String) input).split(" "));
    }
}

public class Applicator {
    public static void apply(Processor p, Object s){
        System.out.println("Using Processor "+p.name());
        System.out.println(p.process(s));
    }
    
    public static void main(String[] args) {
        String s= "We are such stuff as dreams are made on";
        apply(new Upcase(), s);
        apply(new Downcase(), s);
        apply(new Splitter(), s);
        
    }
}

接口的方式

public interface Processor {
    // 默认方法
    default String name() {
        return getClass().getSimpleName();
    }

    Object process(Object input);
}

public class Applicator {
    public static void apply(Processor p, Object s) {
        System.out.println("Using Processor " + p.name());
        System.out.println(p.process(s));
    }
}

// 接口继承接口
interface StringProcessor extends Processor {
    @Override
    String process(Object input);
    
    String S = "If she weighs the same as a duck, she's made of wood";
    // 接口里可以写main函数
    static void main(String[] args) {
        Applicator.apply(new Upcase(), S);
        Applicator.apply(new Downcase(), S);
        Applicator.apply(new Splitter(), S);
    }
}

class Upcase implements StringProcessor {
    @Override
    public String process(Object input) {
        return ((String) input).toUpperCase();
    }
}

class Downcase implements StringProcessor {
    @Override
    public String process(Object input) {
        return ((String) input).toLowerCase();
    }
}

class Splitter implements StringProcessor {
    @Override
    public String process(Object input) {
        return Arrays.toString(((String) input).split(" "));
    }
}

可以在接口中定义 main() 方法
多接口结合 继承拓展接口

接口适配

接口的一种常见用法是前面提到的策略设计模式。编写一个方法执行某些操作并接受一个指定的接口作为参数。可以说:“只要对象遵循接口,就可以调用方法” ,这使得方法更加灵活,通用,并更具可复用性

接口字段

都自动是 static 和 final 的

初始化接口中的字段

接口中定义的字段不能是“空 final",但是可以用非常量表达式初始化
因为字段是 static 的,所以它们在类第一次被加载时初始化,这发生在任何字段首次被访问时
这些字段不是接口的一部分,它们的值被存储在接口的静态存储区域中

接口嵌套

类中嵌套接口可以是private public 包级私有,接口嵌套接口只能public
实现 private 接口是一种可以强制该接口中的方法定义不会添加任何类型信息(即不可以向上转型)的方式,private 接口不能在定义它的类之外被实现

参考资料

《On Java 8》
组合和聚合的区别
[Java基础]Java中boolean类型到底占用多少个字节?

posted @ 2021-02-25 09:13  AaronLin  阅读(217)  评论(0编辑  收藏  举报