文章目录

  • 第十三章 面向对象高级-抽象
    • 13.1 抽象类
      • 13.1.1 抽象类
      • 13.1.2 注意
    • 13.2 接口
      • 13.2.1 接口
      • 13.2.2 注意
      • 13.2.3 默认方法
      • 13.2.4 接口与抽象类的区别
      • 13.2.5 实现接口与继承类的区别
      • 13.2.6 什么时候使用接口
      • 13.2.7 类与类之间的关系
    • 13.3 内部类
      • 13.3.1 实例内部类
      • 13.3.2 静态内部类
      • 13.3.3 局部内部类
      • 13.3.4 匿名内部类
      • 13.3.5 内部类如何访问到外部类的私有字段
      • 13.3.6 如何继承实例内部类
    • 13.4 lambda 表达式
    • 13.5 方法引用
      • 13.5.1 方法引用
      • 13.5.2 构造器引用
    • 13.x 总结回顾
    • 13.y 脑海练习
  • 参考答案
    • 第十三章答案

第十三章 面向对象高级-抽象

内容导视:

  • 抽象类
  • 接口
  • 内部类
  • lambda 表达式
  • 方法引用

13.1 抽象类

内容导视:

  • 抽象类
  • 注意

13.1.1 抽象类

父类型的引用指向子类型的对象,使得不同子类型的对象调用重写后的方法后得到不同的结果。为此父类必须声明一个实例方法,让子类重写,但本身不确定如何实现该方法时,可以将其声明为抽象方法。设计好后,让子类继承,并实现抽象方法的具体步骤。

在继承这节中,Animal 中的 play 方法就是如此。

class Animal {...public void play() {System.out.println("...");}
}

抽象方法声明:访问权限修饰符 abstract 返回值类型 方法名(形参列表);

这是一个没有方法体(没有实现)的方法,以分号结尾。

当一个类存在抽象方法时,需要将该类声明为抽象类。抽象类可以被继承,由子类实现抽象方法。(重写此方法)

访问权限修饰符 abstract class 类名 {...}
abstract class Animal {public abstract void play();
}
class Cat extends Animal {@Overridepublic void play() {System.out.println("飞天遁地,无所不能!");}
}

13.1.2 注意

  • 只有抽象类中才能存在抽象方法
  • 抽象类中可以没有抽象方法
  • 抽象类不能被实例化
  • 非抽象类如果继承抽象类,必须实现抽象类的所有抽象方法(抽象类可以不用)
  • 抽象方法没有方法体,不能使用 private、final、static 修饰,否则无法被子类实现
  • 抽象方法的访问权限最好不要设置为默认级别,否则当其它包下的类继承抽象类时,无法被访问到的父类方法无法被实现。

13.2 接口

内容导视:

  • 接口
  • 注意
  • 默认方法
  • 接口与抽象类的区别
  • 实现接口与继承类的区别
  • 什么时候使用接口
  • 类与类之间的关系

13.2.1 接口

接口是抽象方法的集合,作用是让其他类实现,用于扩展功能。接口编译后生成的是字节码文件。

语法:接口中只有静态常量和抽象方法。

访问权限修饰符 interface 接口名 {// 声明常量public static final 数据类型 常量名 = 值;// 声明抽象方法public abstract 返回值类型 方法名(形参列表);
}

声明常量时的 public static final 可以省略不写;

声明方法时的 public 修饰符可以省略不写;

声明抽象方法时的 abstract 可以省略不写。

最终简化为:

访问权限修饰符 interface 接口名 {// 声明常量数据类型 常量名 = 值;// 声明抽象方法返回值类型 方法名(形参列表);
}

接口中没有代码块、构造器,不能被实例化;接口里的常量、方法的访问权限默认是公开的,不可更改。

这些抽象方法需要被类实现,使用 implements 关键字,语法:访问权限修饰符 class 类名 implements 接口名 {...}

接口中的方法访问权限是公开的:

interface I1 {int COUNT = 5;void some();
}
// 子类必须实现接口中的所有抽象方法
class A implements I1 {/*com.cqh.arr2.A中的some()无法实现com.cqh.arr2.I1中的some()正在尝试分配更低的访问权限; 以前为public*/// protected void some() {}@Override// JDK6 以前不可用于标注实现方法public void some() {}
}

在 JDK8 及以后,允许接口包含默认方法和静态方法,访问权限也默认为 public。

interface I2 {// 默认方法default void some() {System.out.println("接口的 some 方法执行了");}// 静态方法static int add() {return 0;}
}

区分接口、父类中的同名方法:

class B implements I2 {@Overridepublic void some() {System.out.println("子类重写了 some 方法");// 访问接口的 some 方法,父类直接使用 super.some()I2.super.some();}
}

this.some()是访问本类的 some 方法,I2.super.some()是访问本类从 I2 继承的 some 方法。

区分接口、父类中的同名字段:

interface I1 {int x = 0;
}
class A {int x = 2;
}class B extends A implements I1 {public void s1() {// 对x的引用不明确,A 中的变量 x 和 I1 中的变量 x 都匹配//System.out.println(x);}// 区分接口与父类的同名变量public void s2() {System.out.println(I1.x + "=" + super.x);}
}

13.2.2 注意

  • 接口之间支持多继承,实现类也要实现接口继承的其它接口的抽象方法。

    interface I1 {void some1();
    }
    interface I2 extends I1 {void some2();
    }
    class A implements I2 {// 需要实现 some2、some1 方法,当然如果继承的父类已有此方法,就不用实现
    }
    
  • 一个类可以实现多个接口,class A implements I1, I2, I3

  • 接口类型的引用可以指向实现类型的对象,也算多态。

    interface I3 {default void some() {System.out.println("默认");}void other();
    }
    class C implements I3 {@Overridepublic void some() {System.out.println("重写");}public void other() {System.out.println("实现");}
    }
    class Test {public static void main(String[] args) {invoke(new C());}public static void invoke(I3 i) {i.some();i.other();}
    }
    
  • extends 与 implements 可以同时存在,class A extends Object implements I1

  • 实现类与接口之间满足 like a 的逻辑关系,例:鱼实现了翅膀这个接口,拥有了飞翔的功能,鱼变得可以像鸟一样飞翔。

    当子类继承了父类,就自动拥有了父类的功能。如果子类需要扩展功能,可以通过实现接口的方式扩展。

  • 如果继承的父类与接口存在同一个方法,调用此方法时父类优先。

13.2.3 默认方法

当实现接口时,只需其中一个方法,总是强制重写所有抽象方法未免有些烦人;可以使用抽象类先实现几个抽象方法,以后所有需要实现此接口的类,直接继承抽象方法即可。

interface I1 {void some1();void some2();void some3();void some4();
}
abstract class A1 implements I1 {@Overridepublic void some1() {}@Overridepublic void some2() {}@Overridepublic void some3() {}
}
class C1 extends A1 {@Overridepublic void some4() {}
}

default 作用就在于此,将不太重要的方法声明为 default,而不必让子类全部实现。

interface I1 {default void some1() {}default void some2() {}default void some3() {}void some4();
}
class C1 implements I1 {@Overridepublic void some4() {}
}

另一种用法,当使用者实现了一个接口,后来接口又扩充了一个方法,则之前所有实现它的类,则需要实现新增的方法,否则就会报错;如果扩充的方法声明为 default,就不必担心对使用者造成影响。

13.2.4 接口与抽象类的区别

比较 接口 抽象类
组合 可以实现多个接口 只能继承一个类
状态 只有静态常量 任意
构造器
代码块
访问权限 public 任意

13.2.5 实现接口与继承类的区别

接口和继承解决的问题不同

继承的价值主要在于:解决代码的复用性和可维护性。

接口的价值主要在于:设计好各种规范(方法),让其它类去实现这些方法。

接口比继承更加灵活

接口比继承更加灵活,继承是满足 is-a 的关系,天生决定。而接口只需满足 like-a 的关系。

13.2.6 什么时候使用接口

  1. 类只支持单继承,当已经继承了其它类时,只有实现接口才能扩展功能。

  2. 制定规范,让其它人去实现;消费者直接调用,而不必关心提供者。

  3. 方法形参使用接口类型,用于解耦合,因为接口类型的引用可以指向实现接口的类的对象

解耦合:降低程序的耦合度,提高程序的扩展力

例1:解耦合

修改之前:

class Person {public void play(Swim swim) {swim.play();}public void play(Run run) {run.play();}// 新增其它运动需要新增方法
}// 如果 Swim 类修改了,Person 中有关 Swim 的部分可能会受到影响
class Swim {public void play() {System.out.println("游泳中...");}
}
// 同理
class Run {public void play() {System.out.println("跑步中...");}
}class Test {public static void main(String[] args) {Person zs = new Person();zs.play(new Run());}
}

修改方案 1,使用关联,使用接口类型的变量作为实例变量,直接调用它的方法。

interface Sport {void play();
}
class Swim implements Sport {public void play() {System.out.println("游泳中...");}
}
class Run implements Sport {public void play() {System.out.println("跑步中...");}
}
class Person {Sport sport;public void setSport(Sport sport) {this.sport = sport;}public void play() {sport.play();}
}
class Test {public static void main(String[] args) {Person zs = new Person();zs.setSport(new Run());zs.play();}
}

修改方案 2,作为形参,根据传进的实参不同,调用的 play 方法也不同。

class Person {public void play(Sport sport) {sport.play();}
}
class Test {public static void main(String[] args) {Person zs = new Person();// 如果需要其它的运动,可以编写一个类继承 Sport 接口// 不用修改 Person 类,只用传入实参即可zs.play(new Swim());}
}

例2:制定规范

设计一系列公开的接口:

interface Driver {Connection connect(String name);
}
interface Connection {void open();void close();
}class Manager {static Driver driver;public static void register(Driver driver) {Manager.driver = driver;}// 面向接口调用,让制定规则的人、使用者不用关心实现类是谁public static Connection getConnection(String name) {return driver.connect(name);}
}

这时来个张三实现接口:

class ZsDriver implements Driver {public Connection connect(String name) {System.out.println("使用张三写的代码连接数据库成功!");return new ZsConnection(name);}
}
class ZsConnection implements Connection {private String name;public ZsConnection(String name) {this.name = name;}public void open() {System.out.println(name + "打开了通道");}public void close() {System.out.println(name + "关闭了连接");}
}

使用者编写程序,选择一个实现者,按接口声明的规范调用方法。

class Test{public static void main(String[] args) {Manager.register(new ZsDriver());Connection conn = Manager.getConnection("路人甲");conn.open();conn.close();}
}

如果又来个李四实现接口,只需要改动测试类的一个地方,不需要改 Manager 类和规范。

Manager.register(new LsDriver());

以后通过读取配置文件,自动创建对象并传入 register 方法,连代码都不需要改。

例3:扩展功能

class Animal {int age;int type;
}
interface Wing {void fly();
}// 如果猫已经继承了 Animal 类,想要飞,只能实现接口,而不能再继承鸟类
class Cat extends Animal implements Wing {public void fly() {System.out.println("本猫也会飞了...");}
}

13.2.7 类与类之间的关系

多用组合,少用继承

依赖:A 类的方法中使用 B 类型的对象,如上学时借助公交车。

class A {public void some() {B b = new B(数据1, 数据2);b.invoke();}
}

关联(组合):B 类作为 A 类的成员变量。

class A {B b;
}

聚合:特殊的关系,B 类组成的集合作为 A 类的成员变量,是部分与整体的关系;每个 b 的生命周期不由 a 决定;如同我与我拥有的书的关系:我没来时,书在,我不在了,书还在。

class Person {List<Book> bookList = new ArrayList<>(3);// 人的脑海static int count;// 人的数量String name;// 人的姓名public Person(String name) {this.name = name;}public Book end() {return new Book(name + (++count));}public void selection(Book[] books) {for (int i = 0; i < books.length; i++) {bookList.add(books[i]);}}
}
class World {public static void main(String[] args) {Book b1 = new Book("月亮是如何练成的");Book b2 = new Book("太阳的燃烧");Book b3 = new Book("语法快速入门");Book b4 = new Book("恶魔法则");Book b5 = beginToEnd("我", b1);// 人不在了,书还在Book b6 = beginToEnd("你", b2, b5);Book b7 = beginToEnd("他", b1, b2, b3, b4, b6);}public static Book beginToEnd(String name, Book... books) {Person p = new Person(name);p.selection(books);// 人收集书籍return p.end();}
}

合成:特殊的聚合,B 类组成的集合作为 A 类的成员变量,部分与整体的关系;每个 b 的生命周期由 a 决定;如同我与我的四肢的关系。

实现:实现类与接口的关系。

class A implements B {}

继承(泛化):父子类关系,A

class A extends B {}
关系 UML 连接符
依赖 人----->车
关联 人——>狗
聚合 车◊——>轮胎
合成 人♦——>四肢
实现 实现类-----▻接口
继承 子类——▻父类

双向关联可以双箭头,也可以无箭头。

class A {B b;
}
class B {A a;
}

13.3 内部类

内容导视:

  • 实例内部类
  • 静态内部类
  • 局部内部类
  • 匿名内部类
  • 内部类如何访问到外部类的私有字段
  • 如何继承实例内部类

定义在类中的类称为内部类,外面的类称为外部类。如同正常类使用即可,编译也会生成 .class 文件。

class OutClass {// 外部类static interface InnerInterface2 {// 内部接口(static 可以省略,接口默认是静态成员)}
}

内部类可以访问外部类的私有变量,对同一个包下的类隐藏。

13.3.1 实例内部类

实例内部类定义在外部类的成员位置,地位等同于实例变量,由于静态方法不能直接访问实例相关的,需要通过外部类的实例.new InnerClass()访问。

访问权限修饰符 class 外部类类名 {访问权限修饰符 class 内部类类名 {...}
}

实例内部类可以直接访问外部类的所有成员,包括私有的;作用域为整个类体。

创建内部类对象:

// 外部类中
class OutClass {class InnerClass {}// 实例方法中public void some() {InnerClass inner = new InnerClass();}// 静态方法中public static void some2() {OutClass out = new OutClass();InnerClass inner = out.new InnerClass();// new OutClass().new InnerClass();}
}
// 外部其它类中
class B {public static void main(String[] args) {OutClass out = new OutClass();OutClass.InnerClass inner = out.new InnerClass();}
}

实例内部类不支持静态相关的声明,除了静态常量(以字面量的方式赋值)。编译后生成 OutClass$InnerClass.class 文件。

最好不要使用 $ 作为标识符,$ 是用于编译器自定义类名、变量名,以免冲突。

实例内部类不能被继承外部类的类重写。

class Out {class Inner {public void some() {System.out.println("最开始的内部类");}}
}
class Out2 extends Out {class Inner {public void some() {System.out.println("重写后的内部类");}}
}
class Test {public static void main(String[] args) {Out out = new Out2();Out.Inner inner = out.new Inner();inner.some();// 最开始的内部类}
}

13.3.2 静态内部类

静态内部类定义在外部类的成员位置,有 static 修饰,地位等同于静态变量,使用 new 外部类类名.内部类类名()访问。

当不需要直接访问外部类的实例变量或方法时可以定义为静态内部类。

访问权限修饰符 class 外部类类名 {访问权限修饰符 static class 内部类类名 {...}
}

静态内部类可以直接访问外部类所有的静态成员,包括私有的;作用域为整个类体。

创建静态内部类对象:

// 外部类中
class OutClass {static class InnerClass {public static int i = 3;}// 方法中public static void some() {InnerClass inner = new InnerClass();}
}
// 外部其它类中
class B {public static void main(String[] args) {OutClass.InnerClass inner = new OutClass.InnerClass();int i = OutClass.InnerClass.i;}
}

编译后生成 OutClass$InnerClass.class 文件。

13.3.3 局部内部类

局部内部类定义在外部类的局部位置,地位等同于局部变量,可以使用 final 修饰。

访问权限修饰符 class 外部类类名 {方法中、代码块中 {class 内部类类名 {...}}
}

在实例方法、实例代码块、构造器内声明的内部类可以直接访问外部类的所有成员,包括私有的;在静态方法、静态代码块内声明的内部类只能访问静态成员;

局部内部类只在定义内部类时的方法或代码块内有效。

创建内部类对象:

class OutClass {public void some() {// 仅在此方法内有效class InnerClass {}InnerClass inner = new InnerClass();}
}

局部内部类不支持静态相关的声明,除了静态常量(以字面量的方式赋值)。

所在方法内,局部内部类只能使用值不会改变的局部变量。(使用的变量本质是 final 变量,使得局部变量与在局部类内建立的拷贝保持一致)

public static void main(String[] args) {int i = 3;abstract class InnerClass {// 闭包public void some() {// i++;从内部类引用的本地变量必须是最终变量或实际上的最终变量System.out.println(i);}}
}

通过反编译可以看出,引用的本地变量 i 存储在内部类的成员变量 final int val$i;,且在构造器中赋的值。

abstract class com.cqh.arr1.Out$1InnerClass {final int val$i;com.cqh.arr1.Out$1InnerClass();Code:0: aload_01: iload_12: putfield      #1                  // Field val$i:I5: aload_06: invokespecial #2                  // Method java/lang/Object."<init>":()V9: return

在计算机科学中,闭包(Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。

简单来说就是当一个方法引用了方法局部变量外的变量时,它就是一个闭包。闭包是由方法和与其相关的引用环境(方法外变量)组合而成的实体。

13.3.4 匿名内部类

匿名内部类是一种局部内部类,类名是隐匿的,所以没有构造器,作为子类或实现类,定义匿名内部类的同时也会创建该类的实例,一次性用品。

作为子类:

父类类名 引用名 = new 父类类名(实参列表) {子类类体...
};

作为实现类:

接口名 引用名 = new 接口名() {实现类类体...
};

例:

interface I1 {void some();
}
class Test {public static void main(String[] args) {// 生成了一个实现 I1 接口的匿名类,创建了此匿名类的对象后调用 some 方法new I1() {@Overridepublic void some() {System.out.println("实现 some 方法...");}}.some();// 局部内部类class A implements I1 {@Overridepublic void some() {System.out.println("实现 some 方法 2...");}};new A().some();}
}

编译后生成 OutClass$1.class 文件,从 1 开始,每遇见匿名内部类就加一。

当不想编写大量代码时,可以使用匿名内部类更加方便快捷:

// 之前
class ArrayList2<String> extends ArrayList {{add("zs");add("ls");}
}
System.out.println(new ArrayList2());// 之后
System.out.println(new ArrayList<String>(){{add("zs"); add("ls");}});

13.3.5 内部类如何访问到外部类的私有字段

class Out {private String name;class Inner {public void some() {name = "g";System.out.println(name);}private Inner() {}}public static void main(String[] args) {new Out().new Inner().some();}
}

编译后生成 Out.class,Out$Inner.class,反编译后如下:

class Out {private java.lang.String name;Out();public static void main(java.lang.String[]);static java.lang.String access$002(Out, java.lang.String);static java.lang.String access$000(Out);
}
class Out$Inner {final Out this$0;public void some();private Out$Inner(Out);Out$Inner(Out, Out$1);
}

还原真实的代码:

class Out {private String name;public static void main(String[] args) {new Inner(new Out(), null).some();}// 此方法用于赋值static String access$002(Out out, String str) {out.name = str;return str;}// 此方法用于访问私有字段static String access$000(Out out) {return out.name;}
}
class Inner {// 需要外部类的引用final Out this$0;public void some() {// 给外部类引用的字段赋值Out.access$002(this$0, "g");System.out.println(Out.access$000(this$0));}// 外部类引用通过构造器赋值private Inner(Out out) {this$0 = out;}Inner(Out out, Out out2) {this(out);}
}

可以看见外部类生成了默认访问权限的 access$xxx 方法供内部类访问。如果想要修改本类的 private 字段,代码需要与 Out 类放置在同一个包中。

假如我现在已有 Out 类源码,正好此类的内部类访问了外部类的私有变量,我可以将复制此类,将源码中的内部类去掉,手动添加 access$xxx 方法。

package com.cqh;
// 想要访问 Out.class 中的私有字段
class Out {private boolean flag = true;class Inner {public void other() {if (flag) {System.out.println("hello");flag = false;}}}
}

在任意地方新建 com/cqh,复制此类到 cqh 目录下,去掉内部类后编译生成 class 文件。

package com.cqh;// 与原 Out.class 在同一个包下
// 另一个地方的 Out.java
class Out {private boolean flag = true;static boolean access$000(Out out) {return out.flag;}static boolean access$002(Out out, boolean flag) {out.flag = flag;return out.flag;}
}
// 攻击代码
class Invade {public static void main(String[] args) {Out out = new Out();out.access$002(out, false);System.out.println(out.access$000(out));}
}

将编译后生成的 Invade.class 放置在 Out.class 所在目录下(实际目录);就可以访问、修改 Out 类的私有字段。

有人说,那我岂不是把类放在 java.xxx 包下,就可以修改 JDK 源码的私有字段吗?

不可以,因为有安全机制,包名不允许以 java. 开头:

Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.awt
安全异常,禁止的包名称

13.3.6 如何继承实例内部类

通过上节的还原真实的代码,可以看出实例内部类必须传入外部类的引用,在构造器中完成赋值。

所以继承实例内部类时,需要使用特殊的语法,将外部类引用传递进去。

class Out {class Inner {// 调用的是此构造public Inner() {}}
}
class Other extends Out.Inner {public Other(Out out) {// 调用 Out$Inner 类的无参构造,将外部类引用作为参数传递out.super();}
}

13.4 lambda 表达式

JDK8 推出的新特性,目的是简化创建函数式接口的实现类对象的代码,一般作为实参使用。可以看作是简化的匿名内部类。

函数式接口(functional interface):只包含一个抽象方法的接口(可以有默认方法和静态方法)

函数式接口可以加 @FunctionalInterface 注解,如果无意增加了另一个抽象方法会报错。

@FunctionalInterface
interface I1 {void some();
}

lambda 语法:接口名 引用名 = (形参列表) -> {方法体}

规则

  • 可以省略形参的数据类型

  • 当形参只有一个时,可以省略小括号

  • 当方法体只有一条语句时,可以省略花括号

    • 当这条语句为 return xxx 时,可以省略 return
    interface Math {int add(int n1, int n2);
    }Math math = (n1, n2) -> n1 + n2;
    Math math2 = (n1, n2) -> {return n1 + n2;}
    Math math3 = new Math() {@Overridepublic int add(int n1, int n2) {return n1 + n2;}
    };
    

例:

interface I1 {void some();
}
class Test {public static void main(String[] args) {// lambda 表达式I1 i1 = () -> {System.out.println("实现 some 方法");};// 匿名内部类I1 i2 = new I1() {@Overridepublic void some() {System.out.println("实现 some 方法 2...");}};// 作为实参invoke(() -> System.out.println("实现 some 方法 3..."));}public static void invoke(I1 i) {i.some();}
}

13.5 方法引用

内容导视:

  • 方法引用
  • 构造器引用

JDK 8 新特性,当 lambda 表达式中只有一条语句时,可以使用此特性,例:

interface I1 {void some(int num);
}
class Test {public static void main(String[] args) {I1 i = System.out::println;I1 i2 = num -> System.out.println(num);}
}

13.5.1 方法引用

只有一条调用方法的语句时。

有三种形式:

  • 引用::实例方法名
  • 类名::静态方法名
  • 类名::实例方法名

前两种方式,形参继续传递给调用的方法:

interface I1 {int add(int n1, int n2);
}
class A {public static int add(int n1, int n2) {return n1 + n2;}
}
class Test {public static void main(String[] args) {I1 i1 = (n1, n2) -> A.add(n1, n2);// 等价于下面的语句I1 i2 = A::add;}
}

后一种方式,第一个形参作为对象引用,其它参数传递给调用的方法:

interface I1 {void init(A a, int age);
}
class A {int age;public void setAge(int age) {this.age = age;}
}
class Test {public static void main(String[] args) {I1 i1 = (a, age) -> a.setAge(age);I1 i2 = A::setAge;}
}

13.5.2 构造器引用

只有一条返回对象的语句时。

语法:类型::new

传进来的实参作为构造器的实参。

interface I1 {A init(int age);
}
class A {int age;public A(int age) {this.age = age;}
}
class Test {public static void main(String[] args) {I1 i1 = age -> new A(age);I1 i2 = A::new;}
}

13.x 总结回顾

某实例方法只是为了让子类重写,自身不确定如何实现,可以声明为抽象方法。

接口里的常量、方法都是 public 修饰的;当不想强迫子类实现某方法时,可以使用 default。

接口作为规范让子类实现,用于扩展功能。

匿名内部类很常见,用于简化创建对象,Lambda 表达式对此简化更近一步,用于函数式接口。

13.y 脑海练习

13.1 控制台上输出?

1)

class Student {final String name;Vehicle vehicle;public Student(String name) {this.name = name;}public void goToSchool() {if (!(vehicle instanceof Bike)) {this.vehicle = VehiclesFactory.getBike();}System.out.print(this.name);vehicle.work("学校");}public void flying() {this.vehicle = VehiclesFactory.getBambooDragonfly();System.out.print(this.name);        vehicle.work("飞天");}
}interface Vehicle {void work(String goal);
}
class Bike implements Vehicle {@Overridepublic void work(String goal) {System.out.println("骑着老旧的小自行车嘎吱嘎吱缓缓地驶向了" + goal);}
}
class VehiclesFactory {private static final Bike bike = new Bike();public static Vehicle getBike() {return bike;}public static Vehicle getBambooDragonfly() {return goal -> System.out.println("转着竹蜻蜓" + goal + "~~~");}
}
class Test {public static void main(String[] args) {Student zs = new Student("张三");zs.goToSchool();zs.flying();}
}

2)

abstract class World {public final void life() {goToSchool();goToWork();toGetMarried();toHaveChildren();die();}protected abstract void goToSchool();protected abstract void goToWork();protected abstract void toGetMarried();protected abstract void toHaveChildren();protected abstract void die();
}
class Loser extends World {String name;public Loser(String name) {this.name = name;}@Overridepublic void goToSchool() {System.out.println(name + "满怀疲惫地上学了,成绩倒数...");}@Overridepublic void goToWork() {System.out.print("末尾淘汰制,因业绩垫底," + name +"被辞退了;");System.out.println("但" + name + "丝毫不慌,因为穷不过三代。");}@Overridepublic void toGetMarried() {System.out.println("父母催着结婚,于是" + name + "急着结婚了" +",尽管不知道如何抚养孩子,想着给钱就行了...");}@Overridepublic void toHaveChildren() {System.out.print(name + "在外地辛苦工作,妻子半辈子守在农村;似乎孩子不怎么认" + name + "了...");System.out.println("哦,原来半生只是一场梦...");}@Overridepublic void die() {System.out.print(name + "在阳台上安详地晒着太阳,突然就没了生息...");System.out.print("可惜高楼大厦,每层人家门户紧闭,没有人注意到" + name + "的逝去,");System.out.println("而" + name + "家里除他以外再没有人了...");}
}
class Test {public static void main(String[] args) {new Loser("三代目").life();}
}

3)

class Arr {int[] arr;public void foreach(I2 i2) {Objects.requireNonNull(i2);for (int i = 0; i < arr.length; i++) {i2.some(arr[i]);}}public Arr(int[] arr) {Objects.requireNonNull(arr);this.arr = arr;}
}
interface I2 {void some(int element);
}
class Test {public static void main(String[] args) {Arr a = new Arr(new int[]{4, 6, 8});a.foreach((element) -> {System.out.println("遍历的元素为:" + element);});}
}

4)

interface Math {int invoke(int n1, int n2);
}static Math math = (n1, n2) -> {Math math1 = (a1, b1) -> a1 + b1;Math math2 = (x1, y1) -> x1 * y1 - x1;return math1.invoke(n1, n2) - math2.invoke(n1, n2);
};public static void main(String[] args) {System.out.println(math.invoke(5, 6));
}

5)

interface IntConsumer {void run(int value);
}
public static void repeat(int n, IntConsumer action) {for (int i = 0; i < n; i++) {action.run(i);}
}
public static void main(String[] args) {repeat(10, i -> System.out.println("down:" + (9 - i)));
}

6)

interface TypeC {void work();
}
interface MicroUSB {void work2();
}class Phone implements MicroUSB {@Overridepublic void work2() {System.out.println("充电中...");}
}
class Adapter implements TypeC {Phone phone;public Adapter(Phone phone) {Objects.requireNonNull(phone);this.phone = phone;}@Overridepublic void work() {phone.work2();}
}
class Test {public static void main(String[] args) {Phone phone = new Phone();charge(new Adapter(phone));}public static void charge(TypeC typeC) {typeC.work();}
}

参考答案

第十三章答案

13.1 控制台上输出?

1)

class Student {final String name;Vehicle vehicle;public Student(String name) {this.name = name;}public void goToSchool() {if (!(vehicle instanceof Bike)) {this.vehicle = VehiclesFactory.getBike();}System.out.print(this.name);vehicle.work("学校");}public void flying() {this.vehicle = VehiclesFactory.getBambooDragonfly();System.out.print(this.name);        vehicle.work("飞天");}
}interface Vehicle {void work(String goal);
}
class Bike implements Vehicle {@Overridepublic void work(String goal) {System.out.println("骑着老旧的小自行车嘎吱嘎吱缓缓地驶向了" + goal);}
}
class VehiclesFactory {private static final Bike bike = new Bike();public static Vehicle getBike() {return bike;}public static Vehicle getBambooDragonfly() {return goal -> System.out.println("转着竹蜻蜓" + goal + "~~~");}
}
class Test {public static void main(String[] args) {Student zs = new Student("张三");zs.goToSchool();zs.flying();}
}

zs 调用 goToSchool 方法,vehicle 默认为 null,if 条件为 true,进入;调用 getBike 方法,返回 Bike 类型的对象,赋给了 zs.vehicle;

vehicle.work(),vehicle 实际类型为 Bike,调用的是 Bike 类中的 work 方法,输出 张三骑着老旧的小自行车嘎吱嘎吱缓缓地驶向了学校

zs 调用 flying 方法,getBambooDragonfly 返回的是实现 Vehicle 接口的匿名内部类的对象,赋给了 zs.vehicle;

调用 vehicle.work 方法,输出 张三转着竹蜻蜓飞天~~~


2)在接口或抽象类中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

主要解决:一些方法通用,却在每一个子类都重新写了这一方法。

关键代码:在抽象类实现,其他步骤在子类实现。

优点:1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。

缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

使用场景:1、有多个子类共有的方法,且逻辑相同。 2、重要的、复杂的方法,可以考虑作为模板方法。

注意事项:为防止恶意操作,一般模板方法都加上 final 关键词。

如果子类重写的方法,逻辑大致相同,那没必要每个类都写一遍,可以提取公共部分,在父类中定义一个骨架,某个步骤延迟到子类实现。

abstract class World {public final void life() {goToSchool();goToWork();toGetMarried();toHaveChildren();die();}protected abstract void goToSchool();protected abstract void goToWork();protected abstract void toGetMarried();protected abstract void toHaveChildren();protected abstract void die();
}
class Loser extends World {String name;public Loser(String name) {this.name = name;}@Overridepublic void goToSchool() {System.out.println(name + "满怀疲惫地上学了,成绩倒数...");}@Overridepublic void goToWork() {System.out.print("末尾淘汰制,因业绩垫底," + name +"被辞退了;");System.out.println("但" + name + "丝毫不慌,因为穷不过三代。");}@Overridepublic void toGetMarried() {System.out.println("父母催着结婚,于是" + name + "急着结婚了" +",尽管不知道如何抚养孩子,想着给钱就行了...");}@Overridepublic void toHaveChildren() {System.out.print(name + "在外地辛苦工作,妻子半辈子守在农村;似乎孩子不怎么认" + name + "了...");System.out.println("哦,原来半生只是一场梦...");}@Overridepublic void die() {System.out.print(name + "在阳台上安详地晒着太阳,突然就没了生息...");System.out.print("可惜高楼大厦,每层人家门户紧闭,没有人注意到" + name + "的逝去,");System.out.println("而" + name + "家里除他以外再没有人了...");}
}
class Test {public static void main(String[] args) {new Loser("三代目").life();}
}

Loser 类中没有 life 方法,所以调用的是 World 接口中的 life 方法,内部的这些方法已被 Loser 重写,所以依次调用 Loser 类中的 goToSchool、goToWork、toGetMarried … 等方法。


3)

class Arr {int[] arr;public void foreach(I2 i2) {Objects.requireNonNull(i2);for (int i = 0; i < arr.length; i++) {i2.some(arr[i]);}}public Arr(int[] arr) {Objects.requireNonNull(arr);this.arr = arr;}
}
interface I2 {void some(int element);
}
class Test {public static void main(String[] args) {Arr a = new Arr(new int[]{4, 6, 8});a.foreach((element) -> {System.out.println("遍历的元素为:" + element);});}
}

有参构造赋值 a.arr = {4,6,8}

调用 foreach,遍历数组,每次遍历调用 i2.some 方法,输出 遍历的元素为:4遍历的元素为:6遍历的元素为:8


4)

interface Math {int invoke(int n1, int n2);
}static Math math = (n1, n2) -> {Math math1 = (a1, b1) -> a1 + b1;Math math2 = (x1, y1) -> x1 * y1 - x1;return math1.invoke(n1, n2) - math2.invoke(n1, n2);
};public static void main(String[] args) {System.out.println(math.invoke(5, 6));
}
math.invoke(5, 6) 返回值为 math1.invoke(5, 6) - math2.invoke(5, 6);math1.invoke(5, 6) 返回值为 5 + 6 = 11;
math2.invoke(5, 6) 返回值为 5 * 6 - 5 = 25;所以返回值为 11 - 25 = -14

5)

interface IntConsumer {void run(int value);
}
public static void repeat(int n, IntConsumer action) {for (int i = 0; i < n; i++) {action.run(i);}
}
public static void main(String[] args) {repeat(10, i -> System.out.println("down:" + (9 - i)));
}

repeat 方法中每次遍历调用 action 的 run 方法,传入 0、1、2、…、9

输出 down:9down:8、… down:0


6)

interface TypeC {void work();
}
interface MicroUSB {void work2();
}class Phone implements MicroUSB {@Overridepublic void work2() {System.out.println("充电中...");}
}
// 优先使用组合,而不是继承
class Adapter implements TypeC {Phone phone;public Adapter(Phone phone) {Objects.requireNonNull(phone);this.phone = phone;}@Overridepublic void work() {phone.work2();}
}/*class Adapter extends Phone implements TypeC {@Overridepublic void work() {super.work2();}
}*/
class Test {public static void main(String[] args) {Phone phone = new Phone();charge(new Adapter(phone));// 充电中...}public static void charge(TypeC typeC) {typeC.work();}
}

由于 Phone 不是 TypeC 类型,无法作为 charge 方法的实参;要想充电,必须借助实现 Typec 接口的适配器 Adapter,通过适配器调用 Phone 的方法。

JavaSE 接口与内部类相关推荐

  1. Java查漏补缺(08)关键字:static、单例设计模式、理解main方法、类的成员之四:代码块、final关键字、抽象类、接口、内部类、枚举类、注解、包装类

    Java查漏补缺(08)关键字:static.单例设计模式.理解main方法.类的成员之四:代码块.final关键字.抽象类.接口.内部类.枚举类.注解.包装类 本章专题与脉络 1. 关键字:stat ...

  2. java接口匿名内部类_JAVA技术分享:接口,内部类,匿名内部类

    原标题:JAVA技术分享:接口,内部类,匿名内部类 接口: 接口中只有抽象方法,而没有非抽象方法 特点: 1:接口不能被实例化,只能通过多态的形式,让子类实例化 2:接口的子类 A:可以是具体类,但是 ...

  3. Java03接口与内部类

    6 接口与内部类 接口 interface 对象克隆 内部类 inner class 代理 proxy 6.1 接口 public interface Comparable<T> {int ...

  4. java - 抽象类、接口、内部类

    2019独角兽企业重金招聘Python工程师标准>>> 作者:egg 微博:http://weibo.com/xtfggef 出处:http://blog.csdn.net/zhan ...

  5. Java自学笔记——Java面向对象——04.抽象类、接口、内部类

    Java 面向对象各节 Java自学笔记--Java面向对象--01.回顾方法 Java自学笔记--Java面向对象--02.构造器.类和对象 Java自学笔记--Java面向对象--03.封装.继承 ...

  6. day15笔记:抽象类和抽象类方法、接口、内部类

    抽象类和抽象类方法.接口.内部类 1.抽象类和抽象方法(abstract关键字) 2.接口 3.内部类 1.抽象类和抽象方法(abstract关键字) 1.abstract 抽象的 2.abstrac ...

  7. Java笔记018-抽象类、抽象类最佳实践-模板设计模式、接口、内部类

    目录 抽象类 先看一个问题 小结: 抽象类快速入门 抽象类的介绍 抽象类使用的注意事项和细节讨论 抽象类练习题 抽象类最佳实践-模板设计模式 基本介绍 模板设计模式能解决的问题 最佳实践 最佳实践 接 ...

  8. 黑马程序员 - 接口、内部类和异常

    ------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------- 抽象类 特点: 1.方法只有声明没有实现时,该方法就是抽象类,需要用 abstract 修饰 2 ...

  9. idea 查看一个类的子类_Java-05-多态、抽象类、接口和内部类

    什么是多态 多态是指实例方法根据调用者会有不同的表现,就像多种形态一样.这里要注意的是:静态方法没有多态的说法. 可以看到,虽然都是 Person 类,但是 p1 是真正的 Person,而 p2 是 ...

最新文章

  1. 当区块链遇到零知识证明
  2. 中传计算机应用研究生分数线,2018中国传媒大学考研复试分数线(含2016-2018年)...
  3. Vim - 编辑多个文件
  4. Android入门(十二)SQLite事务、升级数据库
  5. 《挖财编程题》求数列的和
  6. 【渝粤教育】电大中专电子商务网站建设与维护 (22)作业 题库
  7. java工厂模式静态工厂_Java设计模式之静态工厂模式详解
  8. Android studio吧,Android Studio 连接真机
  9. mysql增加超级用户_Mysql添加远程超级用户
  10. C++和MFC的常用总结
  11. GitGithub 视频教程(Java版)
  12. 从定制 Ghost 镜像聊聊优化 Dockerfile
  13. Tribler for Mac(BT资源搜索下载器)
  14. python数据分析与挖掘(二十七)--- Pandas量化--股票基础知识
  15. 动态代理及JDK动态代理源码分析
  16. LeetCode 字符串(简单题)
  17. 通用流量录制回放工具 jvm-sandbox-repeater 尝鲜 (二)——repeater-console 使用
  18. dota是java中的_用java开发dota英雄最华丽的技能
  19. 群联USB3.0 PS2251-02\PS2251-03主控量产工具
  20. 如何把表格识别成电子版?这篇文章告诉你

热门文章

  1. ICV:超声波雷达迎来数字化变革,2026年全球市场规模将达145亿美元
  2. mysql查询表的内容_mysql查询表内容
  3. 旗舰版ndows7bios设置,戴尔成铭 3988台式机装win7系统的方法(intel 9代BIOS设置方法和USB驱动)...
  4. 现代计算机理论模型与工作原理
  5. cml sml区别_资本资产定价模型中cml与sml有什么区别?
  6. 产品版本、软件版本、文档版本定义
  7. 阿里云EasyExcel读写excel表数据
  8. 在ubuntu16.04下测试ffplay程序
  9. iOS 数据库-SQLite3 CoreData FMDB
  10. 塔科玛校区的计算机教育,华盛顿大学塔科马校区申请难度