b. 面向对象-抽象
内容导视:
接口内部类兄弟们,真心要学的话,一个月内把语法随便过一遍,那玩意不重要,别像我一样关注于别人定义的规则无法自拔,重复造轮子。会语法的人太多了,随便就被替代,要研究别人不会的、难的、一般人做不来的,才能脱颖而出,不被取代,不然很可悲的。
他们说是要算法、数据结构、 等底层知识,可惜我现在么有精力去弄,先把 JavaSE 笔记搞完吧。
另外,求职:软件测试实习生或其他缺人岗位,个人详细
b.1 接口
内容导视:
抽象类接口默认 接口与抽象类的区别实现接口与继承类的区别什么时候使用接口类与类之间的关系b.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 { @Override public void play() { System.out.println("飞天遁地,无所不能!"); } }
其它:
只有抽象类中才能存在抽象 抽象类中可以没有抽象 抽象类不能被实例化非抽象类如果继承抽象类,必须实现抽象类的所有抽象 (抽象类可以不用)抽象 没有 体,不能使用 private、final、static 修饰,否则无法被子类实现抽象 的访问权限更好不要设置为默认级别,否则当其它包下的类继承抽象类时,无法被访问到的父类 无法被实现。b.1.2 接口
接口是抽象 的***,作用是让其他类实现,用于扩展功能。
语法:接口中只有静态常量和抽象 。
访问权限修饰符 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 { @Override public 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); } }
注意:
接口之间支持多继承,实现类也要实现接口继承的其它接口的抽象 。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 { @Override public 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 的逻辑关系,例:鱼实现了翅膀这个接口,拥有了飞翔的功能,鱼变得可以像鸟一样飞翔。当子类继承了父类,就自动拥有了父类的功能。如果子类需要扩展功能,可以通过实现接口的方式扩展。如果继承的父类与接口存在同一个 ,调用此 时父类优先。
b.1.3 默认
当实现接口时,只需其中一个 ,总是强制重写所有抽象 未免有些烦人;可以使用抽象类先实现几个抽象 ,以后所有需要实现此接口的类,直接继承抽象 即可。
interface I1 { void some1(); void some2(); void some3(); void some4(); }
abstract class A1 implements I1 { @Override public void some1() {} @Override public void some2() {} @Override public void some3() {} }
// 这样实现类就不用实现所有 了 class C1 extends A1 { @Override public void some4() {} }
default 作用就在于此,将不太重要的 声明为 default,而不必让子类全部实现。
interface I1 { default void some1() {} default void some2() {} default void some3() {} void some4(); } class C1 implements I1 { @Override public void some4() {} }
另一种用法,当使用者实现了一个接口,后来接口又扩充了一个抽象 ,则之前所有实现它的类,则需要实现新增的 ,否则就会报错;但扩充的 声明为 default,就不会对使用者造成影响。
b.1.4 接口与抽象类的区别
比较
接口
抽象类
组合
可以实现多个接口
只能继承一个类
状态
只有静态常量
任意
构造器
无
有
代码块
无
有
访问权限
public
任意
b.1.5 实现接口与继承类的区别
接口和继承解决的问题不同
继承的价值主要在于:解决代码的复用性和可维护性。
接口的价值主要在于:设计好各种规范( ),让其它类去实现这些 。
接口比继承更加灵活
接口比继承更加灵活,继承是满足 is-a 的关系,天生决定。而接口只需满足 like-a 的关系。
b.1.6 什么时候使用接口
类只支持单继承,当已经继承了其它类时,只有实现接口才能扩展功能。制定规范,让其它人去实现;消费者直接使用,而不必关心提供者。 形参使用接口类型,用于解耦合,因为接口类型的引用可以指向实现类的对象。解耦合:降低程序的耦合度,提高程序的扩展力。
例 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("本猫也会飞了..."); } }
b.1.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 {}
继承(泛化):父子类关系。
class A extends B {}
关系
UML 连接符
依赖
人----->车
关联
人——>狗
聚合
车◊——>轮胎
合成
人♦——>四肢
实现
实现类-----▻接口
继承
子类——▻父类
双向关联可以双箭头,也可以无箭头。
class A { B b; } class B { A a; }
b.1.8 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 的实现类,实现了 some ,some 中调用了 lambda$main$0 // lambda$main$0 语句就是 -> {} 中的语句 I1 i1 = () -> { System.out.println("实现 some "); }; // 匿名内部类 I1 i2 = new I1() { @Override public void some() { System.out.println("实现 some 2..."); } }; // 作为实参 invoke(() -> System.out.println("实现 some 3...")); } public static void invoke(I1 i) { i.some(); } }
b.1.9 引用
JDK 8 新特性,当 lambda 表达式中只有一条语句时,可以使用此特性,例:
interface I1 { void some(int num); } class Test { public static void main(String[] args) { // 底层创建 I1 的实现类,some 内部调用 PrintStream.println:(int) I1 i = System.out::println; // 底层创建 I1 的实现类,some 内部调用 Test.lambda$main$0:(int) // lambda$main$0(int) 内部调用 System.out.println(int) I1 i2 = num -> System.out.println(num); } }
大概类似于:
// lambda 表达式 final class Test$$Lambda$16/0x0000000800c03000 implements I1 { @Override public void some(int num) { Test.lambda$main$0(num); } } // Test 类中新增 private static void lambda$main$0(int num) { System.out.println(num); }
// 引用 final class Test$$Lambda$15/0x0000000800c01bf8 implements I1 { private final PrintStream stream; // 需要传入 PrintStream 类型的实参 private Test$$Lambda$15/0x0000000800c01bf8(PrintStream stream) { this.stream = stream; } @Override public void some(int num) { stream.println(num); } }
怎么得来的?看字节码,当然如果你们有好办法直接查看运行时生成的类的源码,非常欢迎一起分享。
// 引用对应的动态调用点,可以看出它需要传入一个 PrintStream 类型的实参,才能得到 I1 类型的对象 8: invokedynamic #19, 0 // InvokeDynamic #0:some:(Ljava/io/PrintStream;)Lcom/cqh/arr1/I1; // lambda 表达式对应的动态调用点 14: invokedynamic #23, 0 // InvokeDynamic #1:some:()Lcom/cqh/arr1/I1;
// 通过 引用实现的 some 内部实际调用 println(int) #60 REF_invokeVirtual java/io/PrintStream.println:(I)V // 通过 lambda 表达式实现的 some 内部实际调用 Test.lambda$main$0:(int) #61 REF_invokeStatic com/cqh/arr1/Test.lambda$main$0:(I)V
只有一条调用 的语句时使用:
有三种形式:
引用::实例 名类名::静态 名类名::实例 名前两种方式,形参继续传递给调用的 :
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 的实现类,add 内部调用 lambda$main$0 // lambda$main$0 内部调用 A.add(int, int); I1 i1 = (n1, n2) -> A.add(n1, n2); // 底层创建了 I1 的实现类,add 内部调用 A.add(int, int); 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; } }
构造器引用
只有一条返回对象的语句时。
语法:类型::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; } }
b.2 内部类
内容导视:
实例内部类静态内部类局部内部类匿名内部类内部类如何访问到外部类的私有字段如何继承实例内部类定义在类中的类称为内部类,外面的类称为外部类。如同正常类使用即可,编译也会生成 .class 文件。
class OutClass {// 外部类 static interface InnerInterface2 {// 内部接口(static 可以省略,接口默认是静态成员) } }
内部类可以访问外部类的私有变量,对同一个包下的类隐藏。
b.2.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();// 最开始的内部类 } }
b.2.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 文件。
b.2.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_0 1: iload_1 2: putfield #1 // Field val$i:I 5: aload_0 6: invokespecial #2 // Method java/lang/Object."<init>":()V 9: return
在计算机科学中,闭包(Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。
简单来说就是当一个 引用了 局部变量外的变量时,它就是一个闭包。闭包是由 和与其相关的引用环境( 外变量)组合而成的实体。
其实我也在思考,为什么 Lambda 表达式、内部类中捕获的局部变量不可修改。
【1】访问静态变量,直接通过 getstatic 指令(类名.字段名)获取值,不需要捕获,所以不纳入考虑范围。
【2】访问实例变量,内部类需要定义在外部类的实例 中,在内部类的实例 中访问外部类的实例变量,得先有一个外部类实例。
下面是伪代码:
class OutClass$1InnerClass { // 被捕获的外部类实例 final OutClass this$0; // 构造器需要一个外部类实例当作实参 OutClass$1InnerClass(OutClass arg$1) { this$0 = arg$1; } public void capture() { System.out.println(this$0.name); } }
外部类的实例 有隐藏参数 this,创建内部类实例时,将 this 传进构造器。(从源码中看不出来,可以看字节码)
capture 访问外部类的实例变量 name,是通过访问 this$0.name 实现的。
class OutClass { String name; public OutClass(String name) { this.name = name; } // this 此时是 zs public void closure() { class InnerClass { public void capture() { name = "ls"; } } // zs 传进构造器,给内部类的 this$0 赋值 InnerClass inner = new InnerClass(); // capture 中,通过 this$0 引用修改 zs 的 name 为 “ls” inner.capture(); } public static void main(String[] args) { OutClass zs = new OutClass("zs"); // zs 调用 closure ,则此时的 this 就是 zs zs.closure(); System.out.println(zs.name);// "ls" } }
【3】内部类如何访问外部类的局部变量?毫无关联的不同的两个类之间怎么数据互通的?
假如现有一局部变量 i,需要在内部类的 capture 内部访问 i。可以在内部类中定义一个变量 val$i 接收此局部变量,在创建内部类实例时,在构造器中完成赋值。
以后需要访问到 i 的地方,使用 val$i 代替。
class OutClass$1InnerClass { final int val$i; OutClass$1InnerClass() { // 第 1 个变量槽存的值***到栈顶 {this} 0: aload_0 // 第 2 个变量槽存的值***到栈顶 {this, 33} 1: iload_1 // 给 this.val$i 赋值,val$i = 33; 2: putfield #1 // Field val$i:I 5: aload_0 6: invokespecial #7 // Method java/lang/Object."<init>":()V 9: return } public void capture() { // {this} 0: aload_0 // 访问 this.val$i,即 33 1: getfield #1 // Field val$i:I // 33 赋给 j 4: istore_1 5: return } }
class OutClass { public static void closure() throws Exception { int i = 333; // 此类中定义了 val$i,在构造器中完成赋值,val$i = 333 class InnerClass { public void capture() { // 取出 val$i 的值赋给 j,此时 j = 333 int j = i; System.out.println(j); } } new InnerClass().capture(); } public static void main(String[] args) throws Exception { OutClass.closure(); } }
我大概能猜到被捕获的局部变量不可修改的原因了。在内部类 中修改实例变量,本质是修改被捕获的对象的状态,对象在堆中,所有能访问到此对象的地方,都会发现它的状态已变,从【2】中可以看出来,对象的 name 在内部类中修改为 “ls”,在 main 中可察觉。
例子:
【2】中的 p1,通过内部类的 p2 引用保存它的值。
// 外部类中的引用 p1 Person p1 = new Person(); // 假设内部类通过 p2 拷贝了此对象引用 Person p2 = p1; // 修改实例变量 // 可以通过修改 p2.name,来修改 p1.name p2.name = "ls";
而 【3】中的 i,通过 val$i 变量保存它的值。
// 外部类中的局部变量 i int i = 44; // 假设内部类通过 val$i 拷贝了此局部变量 i int val$i = i; // 修改局部变量 // 可以通过修改 val$i 的值,来修改局部变量 i???不可能 val$i = 333;
意思是说,无法在内部类中修改外部类的局部变量保存的值,至少通过值传递的方式做不到。也无法在拷贝后,通过修改外部类的局部变量保存的值,来影响内部类的 val$i。
既然做不到,假如下例能够成立,数据就不同步了。
int i = 333; class InnerClass { public void capture() { // 实际上修改不了 i 的值 i = 2; } }
但是下例可以成立,因为并没有修改 p1 保存的值。
Person p1 = new Person(); class InnerClass { public void capture() { p1.name = "ls"; } }
b.2.4 匿名内部类
匿名内部类是一种局部内部类,类名是隐匿的,所以没有构造器,作为子类或实现类,定义匿名内部类的同时也会创建该类的实例,一次性用品。
作为子类:
父类类名 引用名 = new 父类类名(实参列表) { 子类类体... };
作为实现类:
接口名 引用名 = new 接口名() { 实现类类体... };
例:
interface I1 { void some(); } class Test { public static void main(String[] args) { // 生成了一个实现 I1 接口的匿名类,创建了此匿名类的对象后调用 some new I1() { @Override public void some() { System.out.println("实现 some ..."); } }.some(); // 局部内部类 class A implements I1 { @Override public 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");}});
b.2.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 安全异常,禁止的包名称
b.2.6 如何继承实例内部类
通过上节的还原真实的代码,可以看出实例内部类必须传入外部类的引用,在构造器中完成赋值。
所以继承实例内部类时,需要使用特殊的语法,将外部类引用传递进去。
我个人感觉这里设计得不太好。
class Out { class Inner { // 调用的是此构造 public Inner() {} } }
class Other extends Out.Inner { public Other(Out out) { // 调用 Out$Inner 类的无参构造,将外部类引用作为参数传递 out.super(); } }
b.x 总结回顾
某实例 只是为了让子类重写,自身不确定如何实现,可以声明为抽象 。
接口里的常量、 都是 public 修饰的;当不想强迫子类实现某 时,可以使用 default。
接口作为规范让子类实现,用于扩展功能。
匿名内部类很常见,用于简化创建对象,Lambda 表达式对此简化更近一步,用于函数式接口。
b.y 课后习题
下面不太是正经题目,算是设计模式?我也不太确定。
b.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 { @Override public 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; } @Override public void goToSchool() { System.out.println(name + "满怀疲惫地上学了,成绩倒数..."); } @Override public void goToWork() { System.out.print("末尾淘汰制,因业绩垫底," + name +"被辞退了;"); System.out.println("但" + name + "丝毫不慌,因为穷不过三代。"); } @Override public void toGetMarried() { System.out.println("父母催着结婚,于是" + name + "急着结婚了" + ",尽管不知道如何抚养孩子,想着给钱就行了..."); } @Override public void toHaveChildren() { System.out.print(name + "在外地辛苦工作,妻子半辈子守在农村;似乎孩子不怎么认" + name + "了..."); System.out.println("哦,原来半生只是一场梦..."); } @Override public 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 { @Override public void work2() { System.out.println("充电中..."); } }
class Test { public static void main(String[] args) { Phone phone = new Phone(); // 要求:调用 charge 给手机充电 } public static void charge(TypeC typeC) { typeC.work(); } }
b.z 习题答案
b.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 { @Override public 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; } @Override public void goToSchool() { System.out.println(name + "满怀疲惫地上学了,成绩倒数..."); } @Override public void goToWork() { System.out.print("末尾淘汰制,因业绩垫底," + name +"被辞退了;"); System.out.println("但" + name + "丝毫不慌,因为穷不过三代。"); } @Override public void toGetMarried() { System.out.println("父母催着结婚,于是" + name + "急着结婚了" + ",尽管不知道如何抚养孩子,想着给钱就行了..."); } @Override public void toHaveChildren() { System.out.print(name + "在外地辛苦工作,妻子半辈子守在农村;似乎孩子不怎么认" + name + "了..."); System.out.println("哦,原来半生只是一场梦..."); } @Override public 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:9、down:8、... down:0
6)
interface TypeC { void work(); } interface MicroUSB { void work2(); } class Phone implements MicroUSB { @Override public void work2() { System.out.println("充电中..."); } } // 优先使用组合,而不是继承 class Adapter implements TypeC { Phone phone; public Adapter(Phone phone) { Objects.requireNonNull(phone); this.phone = phone; } @Override public void work() { phone.work2(); } } /*class Adapter extends Phone implements TypeC { @Override public 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 的 。