[java复习] 接口和内部类

概要

这一章的复习主要涵盖 抽象类接口作用域内部类 尤其是 匿名内部类的性质。这里整理的都是个人觉得比较重要重新认真分析的东西,不一定全部为基础,如果有不准确的地方,还望大佬从评论区指出,感激不尽。

文章中的所有的自定义的测试都在自己的 java-review 仓库中,本文对应的链接:

抽象类和抽象方法

抽象类中可以没有抽象方法,但有抽象方法的类必须是抽象类
  1. 抽象类和普通的类,在继类的承和接口的实现上的规范是基本相同的,都采用 单继承
  2. abstract 关键字可以指定一个类或方法为抽象的,表示类或方法不完整,使得必须
      • 子类继承抽象类
      • 重写方法 (因为必须重写,所以方法必须暴露给子类,所以不能使用 private 修饰,私有化方法就会必须重写暴露给子类矛盾)
  3. 抽象类无法直接创建对象实例

接口

接口的性质

接口中的方法

  • 接口中的方法的权限都是 public
  • 在实现接口的时候,根据方法重写的规则,必须将方法的权限设置为 public

接口中的成员变量

  • interface 产生一个 完全抽象的类,它内部的所有的方法必须都是抽象的,所有成员变量都是 public static

default

jdk 8 的新特性中,default 关键字可以在接口中中添加默认方法,default 关键字 只能用于接口中,该方法为 非静态方法,只有通过实例对象才能访问

接口的 default 方法的性质测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// test

public class DefaultMethod {

/* Inter # f */
interface Inter1 {

default void f() {
log("Inter1 # f");
}

}

/* only extends Inter1 */
interface Inter2 extends Inter1 {}

/* extends Inter1, and override Inter1 # f */
interface Inter3 extends Inter1 {
@Override
default void f() {
log("Inter3 # f");
}
}

/* extends Inter1, and add abstract method Inter4 # f */
interface Inter4 extends Inter1 {
void f();
}

private static void defaultMethodTest() {
log("new Inter1() {}.f(): ", false);
new Inter1() {}.f();

log("new Inter2() {}.f(): ", false, -1);
new Inter2() {}.f();

log("new Inter3() {}.f(): ", false, -1);
new Inter3() {}.f();

//! new Inter4() {}.f(); // Inter4 # f 被覆盖成了抽象方法,必须被重写才能访问

/* Output:

new Inter1() {}.f(): Inter1 # f

new Inter2() {}.f(): Inter1 # f

new Inter3() {}.f(): Inter3 # f
*///:~

}
}
  1. default 和普通类中的非静态方法基本一致
  2. default 方法可以被接口的抽象方法所覆盖

接口嵌套

  • 接口的默认修饰为 static,注意是是接口的修饰,而不是接口内部的属性
  • 又因为接口内部的成员变量都是 public static,所以接口的嵌套就很理所当然
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// test

public class InterfaceTest {

/*static*/ interface InterfaceDecorate {} // Modifier 'static' is redundant for inner interfaces

/* Nesting interfaces. */
interface Nesting1 {
interface Nesting2 {
interface Nesting3 {
// ...
}
}
}

}

接口继承和实现

  • 接口使用 extends 来继承其他接口,完成接口的组合,接口支持多继承
  • 一个 类/抽象类 可以 implements 实现多个接口

测试接口的继承和实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// test

public class InterfaceConflict {

/* 测试接口的多继承 */
interface Inter1 { void f(); }
interface Inter2 { void f(); }
interface Inter3 extends Inter1 { void f(); }
interface Inter4 extends Inter1, Inter2 {}
interface Inter5 extends Inter1, Inter2 { void f(); }

interface Inter6 { default void f() {} }
interface Inter7 { default void f() {} }
interface Inter8 extends Inter6 { void f(); }
//! interface Inter9 extends Inter6, Inter7 {}
interface Inter10 extends Inter6, Inter7 { void f(); }
interface Inter11 extends Inter6, Inter7 { default void f() {} }

/* 测试接口的实现 */
class class1 implements Inter1 {
@Override
public void f() {}
}

class class2 implements Inter1, Inter2 {
@Override
public void f() {}
}

class class3 implements Inter1, Inter6 {
@Override
public void f() {}
}

class class4 implements Inter6, Inter7 {
@Override
public void f() {}
}

}

测试的情况

  • 接口多继承中有重复的方法
    1. 抽象方法之间重复
    2. default 默认方法之间重复
    3. 抽象方法 和 default 默认方法之间的重复
  • 类/抽象类实现多个接口 ,接口中存在重复方法的情况
    1. 抽象方法之间重复
    2. default 默认方法之间的重复
    3. 抽象方法 和 defautl 方法之间重复

结论

但凡是存在 default 方法冲突的时候,必须重写该方法

  • 接口继承的冲突中,实现继承的接口中,重写为 抽象default 方法 均可
  • 类/抽象类实现多个接口出现冲突的时候,类必须重写为类的非静态方法抽象类 还可以重写为 抽象方法

作用域

作用域级别 区域的位置
类级 类的 static 变量
对象实例级 成员变量
方法级 方法内部
块级 块内部的声明周期,比如 if 等大括号包围的区域
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// test

public class Domain {

private static int classParam = 1; // 1. 类级 作用域

private int instance = 2; // 2. 对象实例级 作用域


{ /* 3. 块级 的作用域,初始化块和静态块都属于这个级别,
以及常见的 if for 等块状结构 */

}

private void test() { // 方法级的作用域
int i = 3;
}

}

其实我感觉应该都是块级的作用域…
无非就是括号的范围造成的影响除了成员变量级之外,作用域以外都不能使用该作用域内部的变量

内部类

下面所有的所有的类中,除了匿名内部类意外,其他的所有的内部类,都和普通的类一样,可以寄继承其他的类/抽象类,实现接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// test

public class InnerClass {

private int value = 0;

private InnerClass(int value) {
this.value = value;
}


/* 成员内部类 */
class RememberInnerClass {}

/* 初始化块中的内部类 */
{
class InitializerBlockInnerClass {}
}

/* 静态块中的内部类 */
static {
class StaticBlockInnerClass {}
}

/* 局部内部类 */
private void method() {

class MethodInnerClass {} // 方法内部类

if (true) {
class ScopeInnerClass {} // 作用域内部类
}

InnerClass innerClass = new InnerClass(10) { // 匿名内部类,带参构造器初始化
@Override
public String toString() {
return super.toString();
}
};
}

}

作用域内部类

每个 { } 中间的区域其实都是一个域,只不过通常将除了类的域以外的,都称为 局部

成员内部类

作为 类成员变量 中
  • 一个类或者抽象类声明在了另一个类的内部,此时他就相当于一个类的内部成员变量
  • 使用 static 关键字可以使它和外部类断开联系独立出来,通过 new OutClass.InnerClass() 就可以访问

局部内部类

方法内部 或者 作用域(包括 方法内部,初始化块,静态代码块) 中

匿名内部类

  • 匿名内部类同样可以使用 带有参数的构造器 进行初始化
  • 匿名内部类中就不能自定义构造器了,但是可以直接使用所在的外部域的属性进行初始化

匿名内部类测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
public class Anonymous {

static abstract class InnerClass1 {
int value;
abstract void method();

}
static abstract class InnerClass2 {
int value;
}

private static void superRef() {
int initVal = 2;
InnerClass1 innerClass1 = new InnerClass1() {

int value = initVal;

@Override
void method() {
log("method()", '-', -1);
log("value: " + value);
log("this.value: " + this.value);
log("super.value: " + super.value);
}

private void newMethod() {
log("newMethod()", '-', -1);
log("value: " + value);
log("this.value: " + this.value);
log("super.value: " + super.value);
}

};

log("remember", '-');
log("innerClass1.value: " + innerClass1.value);
log("((InnerClass1) innerClass).value: " + ((InnerClass1) innerClass1).value);

innerClass1.method();
//! ((InnerClass1) innerClass).newMethod(); // 相当于是父引用不能嗲用子类新增的方法

/* Output:

---------- remember ----------
innerClass.value: 0
((InnerClass1) innerClass).value: 0

---------- method() ----------
value: 2
this.value: 2
super.value: 0
*///:~
}

private static void directRef() {
int value = new InnerClass2() {
int value = 2;
}.value;
log("remember", '-');
log("new InnerClass2() {}.value: " + value);
log("((InnerClass2)instance).value: " + ((InnerClass2)(new InnerClass2() {
int value = 2;
})).value);

new InnerClass2() {
void newMethod() {
log("newMethod()", '-', -1);
log("value: " + value);
log("this.value: " + this.value);
log("super.value: " + super.value);
}
}.newMethod();


/* Output:

---------- remember ----------
new InnerClass2() {}.value: 2
((InnerClass2)instance).value: 0

---------- newMethod() ----------
value: 0
this.value: 0
super.value: 0
*///:~
}

}

创建匿名内部类相当于实现、继承的作用,访问成员变量和方法的时候跟前面 继承中访问成员变量和方法 的情况是完全一致的,区别只是创建出的这个类,没有一个具体名字的而已

  • 直接使用 匿名内部类 访问,相当于

    1
    2
    3
    SubClass subClass = new SubClass();
    subClass.xxx;
    subClass.xxx();
  • 使用了 被实现的类型 作为引用,相当于

    1
    2
    3
    4
    /* 仅能访问父类中已有的成员变量和方法 */
    SuperClass superClass = new SubClass();
    superClass.xxx;
    superClass.xxx();

默认访问对象

这里使用 父类引用 和 子类引用 来描述,因为匿名内部类的话我不知道该怎么描述
  1. 重写的方法。 不管是使用父类引用还是子类引用,都默认调用子类中重写的方法
  2. 成员变量。 父类引用和子类引用两者不冲突不会覆盖,通过对应的类型调用对应的成员变量