6.8.3 方法引用与构造器引用

如果Lambda表达式的代码块只有一条代码,程序就可以省略Lambda表达式中代码块的花括号。
如果L ambda表达式的代码块只有一条代码,还可以在代码块中使用方法引用构造器引用

方法引用构造器引用可以让Lambda表达式的代码块更加简洁。方法引用和构造器引用都需要使用两个英文冒号

Lambda表达式的几种引用方式

种类 示例 说明 对应的Lambda表达式
引用类方法 类名::类方法 调用该类的类方法,并把函数式接口中被实现的抽象方法的全部参数传给该类方法作为参数 (a,b,...)->类名.类方法(a,b,…)
引用特定对象的实例方法 特定对象::实例方法 调用该对象的方法,并把函数式接口中被实现的抽象方法的全部参数传给该实例方法作为参数 (ab,…)->特定对象.实例方法(a,b,…)
引用某类对象的实例方法 类名::实例方法 函数式接口中被实现的抽象方法的第一个参数作为调用者,剩下的参数全部传给该实例方法作为参数 (ab,…)->a.实例方法(b,…)
引用构造器 类名::new 函数式接口中被实现的抽象方法的全部参数传给该构造器作为参数 (a,b,...)->new 类名(a,b,…)

1. 引用类方法

1
2
3
4
5
6
7
8
9
10
// 下面代码使用Lambda表达式创建Converter对象
Converter converter1 = from -> Integer.valueOf(from);
Integer value1 = converter1.convert("99");
System.out.println(value1); // 输出整数99

// 引用Integer类的valueOf类方法,
// 并将函数式接口Converter中的convert方法的参数from传递给valueOf方法作为参数
Converter converter2 = Integer::valueOf;
Integer value2 = converter2.convert("99");
System.out.println(value2); // 输出整数99

2. 引用特定对象的实例方法

1
2
3
4
5
6
7
8
9
10
11
String testStr = "HelloWorld";
String subStr = "World";
// 下面代码使用Lambda表达式创建Converter对象
Converter converter1 = from -> testStr.indexOf(from);
Integer value1 = converter1.convert(subStr);
System.out.println(value1);

// 引用Stringd对象的indexOf方法,indexOf方法的参数和Converter的convert方法的参数一样。
Converter converter2 = testStr::indexOf;
Integer value2 = converter2.convert(subStr);
System.out.println(value2);

3. 引用某类对象的实例方法

1
2
3
4
5
6
7
8
9
10
11
12
String testStr = "HelloWorld";
// 下面代码使用Lambda表达式创建MyTest对象
MyTest mt1 = (a, b, c) -> a.substring(b, c);
String str1 = mt1.test(testStr, 5, testStr.length());
System.out.println(str1);

// 引用String类的substring实例方法,
// 通过test方法的第一个参数来调用substring,
// test方法的剩余参数,传给substring方法
MyTest mt2 = String::substring;
String str2 = mt2.test(testStr, 5, testStr.length());
System.out.println(str2);

4. 引用构造器

1
2
3
4
5
6
7
YourTest yt1 = (String a) -> new JFrame(a);
JFrame jf1 = yt1.win("我的窗口");
System.out.println(jf1);
// 引用JFrame的构造器,以win方法的参数作为构造器的参数
YourTest yt2 = JFrame::new;
JFrame jf2 = yt2.win("我的窗口");
System.out.println(jf2);

6.8.2 Lambda表达式与函数式接口

Lambda表达式的类型,也被称为”目标类型”(target type)”,** Lambda表达式的目标类型必须是”函数式接口(functional interface)"**。

什么是函数式接口

函数式接口就是只包含一个抽象方法的接口。函数式接口可以包含多个默认方法、类方法,但只能声明一个抽象方法。

通过匿名内部类来创建函数式接口实例

如果采用匿名内部类语法来创建函数式接口的实例,则只需要实现一个抽象方法即可。

通过Lambda表达式来创建函数式接口实例

采用Lambda表达式创建出来的对象的目标类型就是这个函数式接口的类型。

java API中的函数式接口

查询Java8API文档,可以发现大量的函数式接口,例如:RunnableActionListener等接口都是函数式接口。

函数式接口注解

Java8专门为函数式接口提供了@FunctionalInterface注解,该注解通常放在接口定义前面,该注解对程序功能没有任何作用,它用于告诉编译器执行更严格检查—检查该接口必须是函数式接口,否则编译器就会报错。

使用Lambda表示式复制

由于Lambda表达式的结果就是被当成对象,因此程序中完全可以使用Lambda表达式进行赋值。

使用Lambda表达式创建线程执行体

RunnableJava本身提供的一个函数式接口,所以可以直接使用Lambda表达式来创建Runnable实例。

1
2
3
4
5
Runnable r = () -> {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
};

Lambda表达式的限制

Lambda表达式实现的是匿名方法—因此它只能实现特定函数式接口中的唯一方法。这意味着Lambda表达式有如下两个限制:

  1. Lambda表达式的目标类型必须是明确的函数式接口。
  2. Lambda表达式只能为函数式接口创建对象。 Lambda表达式只能实现一个方法,因此它只能为只有一个抽象方法的接口创建对象。

如何保证Lambda表达式的目标类型一定是函数式接口

为了保证Lambda表达式的目标类型是一个明确的函数式接口,可以有如下三种常见方式。

  • Lambda表达式赋值给 函数式接口类型的引用变量
  • Lambda表达式 作为函数式接口类型的参数 传给某个方法。
  • 使用函数式接口对Lambda表达式进行强制类型转换

相同的Lambda表达式的目标类型可变 只要形参列表相同即可

同样的Lambda表达式可以被当成不同的目标类型,唯一的要求是Lambda表达式的形参列表与函数式接口中唯一的抽象方法的形参列表相同

Java8预定义的函数式接口及其作用

Java8java.util.function包下预定义了大量函数式接口,典型地包含如下4类接口。

函数式接口 描述
XxxFunction 这类接口中通常包含一个apply()抽象方法,该方法对参数进行处理、转换,然后返回一个处理后的新值。该函数式接口通常用于对指定数据进行转换处理。
XxxConsumer 这类接口中通常包含一个accept()抽象方法,该方法与XxxFunction接口中的apply()方法基本相似,也负责对参数进行处理,只是该方法不会返回处理结果
XxxPredicate 这类接口中通常包含一个test()抽象方法,该方法通常用来对参数进行某种判断,然后返回一个boolean值。该接口通常用于判断参数是否满足特定条件,经常用于进行筛滤数据
XxxSupplier 这类接口中通常包含一个getAsXxx()抽象方法,该方法不需要输入参数,该方法会按某种逻辑算法回一个数据。

Lambda表达式可以方便的创建函数式接口的实例

Lambda表达式的本质很简单,就是使用简洁的语法来创建函数式接口的实例,这种语法可以避免创建匿名内部类的烦琐操作。

6.8 Java8新增的Lambda表达式

  • Lambda表达式是Java8的重要更新,
  • Lambda表达式支持将代码块作为方法参数,
  • Lambda表达式允许使用更简洁的代码来创建只有一个抽象方法的接口的实例。

6.8.1 Lambda表达式入门

Lambda表达式替代匿名内部类

接口

1
2
3
4
5
public interface Command
{
// 接口里定义的process()方法用于封装“处理行为”
void process(int[] target);
}

匿名内部类方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class CommandTest {
public static void main(String[] args) {
ProcessArray pa = new ProcessArray();
int[] array = { 3, -4, 6, 4 };
// 处理数组,具体处理行为取决于匿名内部类
pa.process(array, new Command() {
public void process(int[] target) {
int sum = 0;
for (int tmp : target) {
sum += tmp;
}
System.out.println("数组元素的总和是:" + sum);
}
});
}
}

Lambda表达式方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

public class CommandTest2 {
public static void main(String[] args) {
ProcessArray pa = new ProcessArray();
int[] array = { 3, -4, 6, 4 };
// 处理数组,具体处理行为取决于匿名内部类
pa.process(array, (int[] target) -> {
int sum = 0;
for (int tmp : target) {
sum += tmp;
}
System.out.println("数组元素的总和是:" + sum);
});
}
}

当使用Lambda表达式代替匿名内部类创建对象时, Lambda表达式的代码块将会代替实现抽象方法的方法体, Lambda表达式就相当一个匿名方法

lambda表达式的组成

Lambda表达式的主要作用就是代替匿名内部类的烦琐语法。它由三部分组成。

  1. 形参列表。形参列表允许省略形参类型。如果形参列表中只有一个参数,甚至连形参列表的圆括号也可以省略。
  2. **箭头(->)**。必须通过英文中画线和大于符号组成
  3. 代码块。如果代码块只包含一条语句,
    • Lambda表达式允许省略代码块的花括号,那么这条语句就不要用花括号表示语句结束。
    • Lambda代码块只有一条return语句,甚至可以省略return关键字。
    • Lambda表达式需要返回值,而它的代码块中仅有一条省略了return的语句, Lambda表达式会自动返回这条语句的值。

Lambda表达式简写

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

interface Eatable {
void taste();
}

interface Flyable {
void fly(String weather);
}

interface Addable {
int add(int a, int b);
}

public class LambdaQs {
// 调用该方法需要Eatable对象
public void eat(Eatable e) {
System.out.println(e);
e.taste();
}

// 调用该方法需要Flyable对象
public void drive(Flyable f) {
System.out.println("我正在驾驶:" + f);
f.fly("【碧空如洗的晴日】");
}

// 调用该方法需要Addable对象
public void test(Addable add) {
System.out.println("5与3的和为:" + add.add(5, 3));
}

public static void main(String[] args) {
LambdaQs lq = new LambdaQs();
System.out.println("------------------------------------");
// 完整形式
lq.eat(() -> {
System.out.println("苹果的味道不错!");
});
// 简写形式
// Lambda表达式的代码块只有一条语句,可以省略花括号。
lq.eat(() -> System.out.println("苹果的味道不错!"));
System.out.println("------------------------------------");
// 完整形式
lq.drive((weather) -> {
System.out.println("今天天气是:" + weather);
System.out.println("直升机飞行平稳");
});
// 简写形式
// Lambda表达式的形参列表只有一个形参,省略圆括号
lq.drive(weather -> {
System.out.println("今天天气是:" + weather);
System.out.println("直升机飞行平稳");
});
System.out.println("------------------------------------");
// 完整形式
lq.test((a, b) -> {
return a + b;
});
// 简写形式
// Lambda表达式的代码块只有一条语句,省略花括号
// 代码块中只有一条语句,即使该表达式需要返回值,也可以省略return关键字。
lq.test((a, b) -> a + b);
}
}

  • Lambda表达式的代码块只有一行代码时,可以省略代码块的花括号;
  • Lambda表达式的形参列表只有一个形参时,可以省略了形参列表的圆括号;
  • Lambda表达式的代码块中只有一个返回语句时,可以省略花括号和return,直接以返回语句的表达式的计算结果作为返回值。

Lambda表达式实际上将会被当成一个”任意类型”的对象,到底需要当成何种类型的对象,这取决于运行环境的需要。

6.7.5 Java8改进的匿名内部类

匿名内部类适合创建那种只需要一次使用的类,匿名内部类不能重复使用。

定义匿名内部类的格式

创建接口的匿名内部类

1
2
3
4
new 实现接口()
{
//匿名内部类类体部分
}

实现接口方式接口名后面的括号里不能传入参数

通过实现接口来创建匿名内部类时,由于接口中不能定义构造器,因此匿名内部类只有一个隐式的无参数构造器,故new 接口名后的括号里不能传入参数值

创建抽象类的匿名内部类

1
2
3
4
new 父类构造器(实参列表)
{
//匿名内部类类体部分
}

抽象类的匿名内部类可以调用带参构造器

由于抽象类可以定义构造器,并且抽象类的构造器是提供给子类调用的,所以,创建抽象类的匿名内部类时:
new 抽象类名后面的括号中可以传入参数,也可以不传入参数

  • 传入参数时,将调用对应的带参构造器。
  • 不传入参数时,将调用无参构造器

匿名内部类规则

关于匿名内部类还有如下两条规则。

  • 匿名内部类不能是抽象类,因为系统在创建匿名内部类时,会立即创建匿名内部类的对象。因此不允许将匿名内部类定义成抽象类
  • 匿名内部类不能定义构造器。由于匿名内部类没有类名,所以无法定义构造器,但匿名内部类可以定义初始化块,可以通过实例初始化块来完成构造器需要完成的事情

匿名内部类必须实现所有抽象方法

由于匿名内部类不能是抽象类,所以匿名内部类必须实现它的抽象父类或者接口里包含的所有抽象方法。如果有需要,也可以重写抽象父类中的普通方法

匿名内部类不可以修改局部变量的值

java8之前被匿名内部类访问的局部变量必须 手动 用final修饰

Java8之前,Java要求被局部内部类匿名内部类访问的局部变量必须使用final修饰。

java8后被匿名内部类访问的局部变量会 自动 加上final修饰

Java8开始,被局部内部类匿名内部类访问的局部变量可以不显示用final修饰。如果局部变量被匿名内部类访问,那么该局部变量相当于自动使用了final修饰
Java8将这个功能称为"effectively final",它的意思是对于被匿名内部类访问的局部变量,可以用final修饰,也可以不用final修饰,但必须按照有final修饰的方式来用。
也就是被匿名内部类访问的局部变量一旦被赋值一次后,以后不能再重新赋值

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
interface A {
void test();
}

public class ATest {
public static void main(String[] args) {
int age = 8; // ①
// 下面代码将会导致编译错误
// 由于age局部变量被匿名内部类访问了,因此age相当于被final修饰了
// age = 2;
A a = new A() {
public void test() {
// 在Java 8以前下面语句将提示错误:age必须使用final修饰
// 从Java 8开始,匿名内部类、局部内部类允许访问非final的局部变量
System.out.println(age);
}
};
a.test();
}
}

6.7.4 局部内部类

什么是局部内部类

在方法里定义内部类就是一个局部内部类,局部内部类仅在该方法里有效。

由于局部内部类不能在外部类的方法以外的地方使用,因此局部内部类不能使用访问控制符和static修饰符修饰

为什么 局部成员 不能使用 static 修饰

对于局部成员而言,不管是局部变量还是局部内部类,它们的上一级程序单元都是方法,而不是类,使用static修饰它们没有任何意义。因此,所有的局部成员都不能使用static修饰。

为什么 局部成员 不能使用 访问控制符 修饰

因为局部成员的作用域是所在方法,在方法之外的地方局部成员不存在,其他程序单元永远也不可能访问到一个方法中的局部成员,所以所有的局部成员都不能使用访问控制符修饰

局部内部类只能在方法中使用

如果需要用局部内部类定义变量、创建实例或派生子类,那么都只能在局部内部类所在的方法内进行。

实际开发中很少使用局部内部类

大部分时候,定义一个类之后,都是希望多次复用这个类,局部内部类只能在当前方法中使用,在其他地方就用不到了,可复用性差,因此在实际开发中很少使用局部内部类

6.7.3 使用内部类

1. 在外部类内部使用内部类

在外部类内部使用内部类时,与平常使用普通类没有太大的区别。一样可以直接

  • 通过内部类类名来定义变量,
  • 通过new调用内部类构造器来创建实例。

唯一存在的一个区别是:**不要在外部类的静态成员(包括静态方法和静态初始化块)中使用非静态内部类,因为静态成员不能访问非静态成员**。

2. 在外部类以外使用非静态内部类

如果希望在外部类以外的地方访问内部类(包括静态和非静态两种),则内部类不能使用private访问控制权限, private修饰的内部类只能在外部类内部使用。对于使用其他访问控制符修饰的内部类,则能在访问控制符对应的访问权限内使用。也就是:

  • 如果省略访问控制符的内部类,只能被与外部类处于同一个包中的其他类所访问。
  • 如果使用protected修饰的内部类,可被与外部类处于同一个包中的其他类和外部类的子类所访问
  • 如果使用public修饰的内部类,可以在任何地方被访问。

在外部类之外定义内部类的引用变量

在外部类以外的地方定义内部类(包括静态和非静态两种)变量的语法格式如下:
OuterClass.InnerClass varName;

完整的内部类类名

从上面语法格式可以看出,在外部类以外的地方使用内部类时,内部类完整的类名应该是OuterClass.InnerClass。如果外部类有包名,则还应该增加包名前缀。

在外部类之外创建非静态内部类对象

由于非静态内部类的对象必须寄生在外部类的对象里,因此创建非静态内部类对象之前,必须先创建其外部类对象。在外部类以外的地方创建非静态内部类实例的语法如下:
outerInstance.new InnerConstructor();

从上面语法格式可以看出,在外部类以外的地方创建非静态内部类实例必须使用外部类实例new来调用非静态内部类的构造器

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
class Out
{
// 定义一个内部类,不使用访问控制符,
// 即只有同一个包中其他类可访问该内部类
class In
{
public In(String msg)
{
System.out.println(msg);
}
}
}
public class CreateInnerInstance
{
public static void main(String[] args)
{
Out.In in = new Out().new In("测试信息");
/*
上面代码可改为如下三行代码:
使用OutterClass.InnerClass的形式定义内部类变量
Out.In in;
创建外部类实例,非静态内部类实例将寄存在该实例中
Out out = new Out();
通过外部类实例和new来调用内部类构造器创建非静态内部类实例
in = out.new In("测试信息");
*/
}
}

非静态内部类的构造器必须使用外部类对象来调用

继承非静态内部类

当创建一个子类时,子类构造器总会调用父类的构造器,因此在创建非静态内部类的子类时,必须保证让子类构造器可以调用非静态内部类的构造器,调用非静态内部类的构造器时,必须存在一个外部类对象。

非静态内部类In对象和器子类SubClass对象都必须持有指向Outer对象的引用,区别是创建两种对象时传入Out对象的方式不同:

  • 当创建非静态内部类In类的对象时,必须通过Outer对象来调用new关键字;
  • 当创建创建非静态内部类In类的子类的对象时,必须使用Outer对象作为调用者来调用ln类的构造器,
    • 这就要求在SubClass的构造器中传如一个外部Outer对象,然后

非静态内部类的子类不一定是内部类,它可以是一个外部类。
非静态内部类的子类实例一样需要保留一个引用,该引用指向其父类所在外部类的对象。也就是说,如果有一个内部类子类的对象存在,则一定存在与之对应的外部类对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Out {
// 定义一个内部类,不使用访问控制符,
// 即只有同一个包中其他类可访问该内部类
class In {
String msg;

public In(String msg) {
System.out.println(msg);
}
}
}

public class SubClass extends Out.In {
// 显示定义SubClass的构造器
public SubClass(Out out, String msg) {
// 通过传入的外部类对象out来显式调用内部类In的构造器
// super相当于In
out.super(msg);
}
}

3. 在外部类以外使用静态内部类

创建静态内部类对象时无须创建外部类对象

创建静态内部类实例

在外部类以外的地方创建静态内部类实例的语法如下:
new OuterClass.InnerConstructor();
不管是静态内部类还是非静态内部类,它们声明变量的语法完全一样。
区别只是在创建内部类对象时,

  • 静态内部类只需使用外部类即可调用构造器,
  • 而非静态内部类必须使用外部类对象来调用构造器。

继承静态内部类

因为调用静态内部类的构造器时无须使用外部类对象,所以创建静态内部类的子类也比较简单,下面代码就为静态内部类StaticIn类定义了一个空的子类。

1
2
3
public class StaticSubClass extends StaticOut.StaticIn{

}

优先使用静态内部类

相比之下,使用静态内部类比使用非静态内部类要简单很多,只要把外部类当成静态内部类的包空间即可。因此当程序需要使用内部类时,应该优先考虑使用静态内部类

6.7.2 静态内部类

如果使用static来修饰一个内部类,则这个内部类就属于外部类本身,而不属于外部类的某个对象。因此使用static修饰的内部类被称为类内部类,有的地方也称为静态内部类

为什么static不能修饰外部类

static关键字的作用是把类的成员变成类相关,也就是static修饰的类成员属于类,既然要属于类,则表示上一级程序单元必须是一个类.
由于外部类的上一级程序单元是包,所以不可使用static修饰;

为什么static可以修饰内部类

内部类的上一级程序单元是外部类,使用static修饰可以将内部类变成外部类相关。

总结:static关键字不可修饰外部类,但可修饰内部类。

静态内部类可以包含静态成员,也可以包含非静态成员

静态内部类只能访问外部类的静态成员

根据静态成员不能访问非静态成员的规则:

  • 静态内部类不能访问外部类的实例成员,只能访问外部类的类成员
  • 即使是静态内部类的实例方法也不能访问外部类的实例成员,只能访问外部类的静态成员

为什么静态内部类的实例方法不能访问外部类的实例成员

因为静态内部类对象是寄生在外部类的类本身中,而不是寄生在外部类的实例中。当静态内部类对象存在时,并不存在一个被它寄生的外部类对象,静态内部类对象只持有外部类的类引用,没有持有外部类对象的引用。如果允许静态内部类的实例方法访间外部类的实例成员,但找不到被寄生的外部类对象,这将引起错误。

外部类的所有地方都可也使用静态内部类

静态内部类是外部类的一个静态成员,因此外部类的所有方法、所有初始化块中可以使用静态内部类来定义变量、创建对象等。

外部类不可以直接访问静态内部类的成员

外部类依然不能直接访问静态内部类的成员,但可以使用静态内部类的类名作为调用者来访问静态内部类的类成员,也**可以使用静态内部类对象作为调用者来访问静态内部类的实例成员**。

接口内部类只能是静态内部类

Java还允许在接口里定义内部类,接口里定义的内部类默认使用public static修饰,也就是说,接口内部类只能是静态内部类。

接口内部类默认public static修饰

  • 如果为接口内部类指定访问控制符,则只能指定public访问控制符;
  • 如果定义接口内部类时省略访问控制符,则该内部类默认是public访问控制权限。

接口里是否可以定义内部接口

答:可以但没必要

  • 接口里的内部接口是接口的成员,因此系统默认添加public static两个修饰符。
  • 定义接口里的内部接口的意义不大,因为接口的作用是定义一个公共规范来供大家使用,如果把这个接口定义成一个内部接口,在接口里定义内部接口没有意义。

6.7.1 非静态内部类

如何定义内部类

定义内部类非常简单,只要把一个类放在另一个类的内部定义即可。注意内部类一定是放在另一个类的类体部分定义的,也就是要在类名后的花括号里面定义。

内部类的定义位置

内部类可以在类中的任意位置定义
内部类定义语法格式如下:

1
2
3
4
public class OuterClass
{
//此处可以定义内部类
}

什么是局部内部类

方法里定义的内部类被称为局部内部类

什么是成员内部类

成员内部类是一种与成员变量、方法、构造器和初始化块相似的类成员,而局部内部类匿名内部类则不是类成员
大部分时候,内部类都被作为成员内部类定义,而不是作为局部内部类

成员内部类分类

成员内部类分为两种:静态内部类非静态内部类,

  • 使用static修饰的成员内部类是静态内部类,
  • 没有使用static修饰的成员内部类是非静态内部类。

内部类能使用的修饰符

因为内部类作为其外部类的类成员,所以可以使用任意访问控制符privateprotectedpublic等修饰。

为什么外部类只有两种访问控制权限

外部类的上一级程序单元是,所以它只有2个作用域:同一个包内任何位置
因此外部类只需2种访问权限:包访问权限公开访问权限,正好对应省略访问控制符public访问控制符。
省略访问控制符是包访问权限,即同一包中的其他类可以访问省略访问控制符的成员。因此,如果一个外部类不使用任何访问控制符修饰,则只能被同一个包中其他类访问。

为什么内部类能使用4中访问控制权限

由于内部类的上一级程序单元是外部类,它就具有4个作用域:同一个类父子类同一个包、和任何位置,因此可以使用4种访问控制权限。

在外部类里使用非静态内部类时,与平时使用普通类并没有太大的区别。

成员内部类的class文件名

成员内部类(包括静态内部类、非静态内部类)的class文件的文件名总是这种形式: OuterClass$InnerClass.class

为什么非静态内部类可以直接访问外部类的private成员

因为在非静态内部类对象里,保存了一个它所寄生的外部类对象的引用,所以在非静态内部类里可以直接访问外部类的private成员。

非静态内部类变量查找过程

当在非静态内部类的方法内访问某个变量时,系统

  • 优先在该方法内查找是否存在该名字的局部变量,如果存在就使用该变量;
  • 如果不存在,则到该方法所在的内部类中查找是否存在该名字的成员变量,如果存在则使用该成员变量;
  • 如果不存在,则到该内部类所在的外部类中查找是否存在该名字的成员变量,如果存在则使用该成员变量;
  • 如果依然不存在,系统将出现编译错误:提示找不到该变量。

变量同名的情况

因此,如果外部类成员变量内部类成员变量内部类里方法的局部变量同名,则可通过使用this外部类类名.this作为限定来区分。例如

  • 通过OutterClass.this.propName的形式访问外部类的实例变量,
  • 通过this.propName的形式访问非静态内部类的实例变量。

外部类不能直接访问非静态内部类的私有成员

如果外部类需要访问非静态内部类的成员,则必须显式创建非静态内部类对象来调用访问其实例成员。

非静态内部类和外部类的关系

如果存在一个非静态内部类对象,则一定存在一个被它寄生的外部类对象。
但外部类对象存在时,外部类对象里不一定寄生了非静态内部类对象。

外部类访问非静态内部类时要小心

外部类对象访问非静态内部类成员时,可能非静态普通内部类对象根本不存在,从而无法访问,

非静态内部类可以访问静态内部类成员

非静态内部类对象访问外部类成员时,外部类对象一定存在,所以非静态内部类对象可以访问外部类的成员。

外部类的静态成员中不能直接使用非静态内部类

不允许在外部类的静态成员中直接使用非静态内部类。

非静态内部类中不能定义静态成员

Java不允许在非静态内部类里定义静态成员,也就是非静态内部类里不能有静态方法、静态成员变量、静态初始化块

非静态内部可可以包含普通初始化块

非静态内部类可以包含普通初始化块。非静态内部类普通初始化块的作用与外部类初始化块的作用完全相同。

6.7 内部类

什么是内部类

定义在其他类内部的类就被称为内部类,

什么是外部类

包含内部类的类也被称为外部类

内部类的作用

JavaJDK1.1开始引入内部类,内部类主要有如下作用。

  • 内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类。
  • 内部类成员可以直接访问外部类的私有数据,
    • 因为内部类被当成其外部类的一个成员,同一个类的成员之间可以互相访问。
    • 但外部类不能访问内部类的实现细节,例如内部类的成员变量。
  • 匿名内部类适合用于创建那些仅需要一次使用的类。

内部类和外部类的区别

从语法角度来看,定义内部类与定义外部类的语法大致相同,内部类除需要定义在其他类里面之外,还存在如下两点区别。

  1. 内部类比外部类可以多使用三个修饰符:privateprotectedstatic,外部类不可以使用这三个修饰符。
  2. 只有静态内部类才能拥有静态成员。外部类不能使用static修饰,所以不存在静态外部类.

6.6.6 面向接口编程

接口体现的是一种规范和实现分离的设计哲学,充分利用接口可以极好地降低程序各模块之间的耦合,从而提高系统的可扩展性和可维护性。

1. 简单工厂模式

2. 命令模式

这本书上讲的设计模式都不深入,想要弄懂设计模式还得看其他书籍。