内部类 & Lambda 表达式简明笔记

内部类

内部类的基本使用

内部类概念:在一个类中定义一个类

比如,在一个类 A 的内部定义一个类 B,类 B 就被称为内部类。内部类定义格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
class 外部类名{
修饰符 class 内部类名{

}
}
*/

class Outer {
//此处的Inner就是所说的内部类
public class Inner {

}
}

内部类的访问特点

  • 内部类可以直接访问外部类的成员,包括私有
  • 外部类要访问内部类的成员,必须创建对象

1、示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test1Inner {
public static void main(String[] args) {
//此处编译器报错
Inner i = new Inner();
}
}

class Outer {
private int a = 10;
//内部类
class Inner {
int num = 10;

public void show(){
System.out.println("Inner..show");
}
}
}

以上代码中,为什么这里编译器会报错呢:

1
Inner i = new Inner();

因为类名没有写全 –> 为什么呢?举例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Test1Inner {
public static void main(String[] args) {
Inner i = new Inner();
}
}

class Outer {
private int a = 10;
//内部类
class Inner {

}
}

class Outer2{
class Inner{

}
}

此时创建 Inner 对象时,编译器都懵掉了,你到底是想要创建那个类中的 Inner 类 –> 需要我们正确书写格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test1Inner {
public static void main(String[] args) {
//创建内部类的格式
//外部类名.内部类类名 对象名 = new 外部类对象().new 内部类对象;
Outer.Inner i = new Outer().new Inner();
}
}

class Outer {
private int a = 10;
//内部类
class Inner {

}
}

emm,创建内部类为什么格式那么复杂:-)。如下是演示内部类变量、内部类方法的访问方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Test1Inner {  
public static void main(String[] args) {
//创建内部类的格式
//外部类名.内部类类名 对象名 = new 外部类对象().new 内部类对象;
Outer.Inner i = new Outer().new Inner();
//访问内部类变量
System.out.println(i.num);
//访问内部类方法
i.show();
}

}

class Outer {
private int a = 10;
//内部类
class Inner {
int num = 10;
public void show(){
System.out.println("Inner...show");
}
}
}

内部类是可以直接使用外部类中的成员变量的,代码如下:

可以看到,调用内部类方法时,可以访问到外部类中的 a 变量(包括私有的成员变量)

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
public class Test1Inner {  
public static void main(String[] args) {
//创建内部类的格式
//外部类名.内部类类名 对象名 = new 外部类对象().new 内部类对象;
Outer.Inner i = new Outer().new Inner();
//访问内部类变量
System.out.println(i.num);
//访问内部类方法
i.show();
}

}

class Outer {
//外部类中私有的成员变量
private int a = 10;
//内部类
class Inner {
int num = 10;
public void show(){
System.out.println("Inner...show");
//内部类可以直接访问外部类的成员变量(包括私有的成员变量)
System.out.println("外部类中的成员变量" + a);
}
}
}

程序输出

1
2
3
10
Inner...show
外部类中的成员变量10

成员内部类

按照内部类在类中定义的位置不同,可以分为如下两种形式

  • 在类的成员位置:成员内部类
  • 在类的局部位置:局部内部类

外界创建成员内部类格式

1
2
//外部类名.内部类名 对象名 = 外部类对象.内部类对象;
Outer.Inner oi = new Outer().new Inner();

成员内部类,也属于(成员),既然是成员就可以被一些修饰符所修饰,比如 private 和 static

私有成员内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test2Innerclass {  
public static void main(String[] args) {
//此处编译错误
Outer.Inner oi = new Outer().new Inner();
}
}

class Outer {
//私有成员内部类
private class Inner {
public void show(){
System.out.println("inner..show");
}
}
}

此处,创建成员内部类对象时编译错误
因为 private 是同一类中可见 –> 也就是说只在 Outer 类中是可见的。那么外界如何创建这个内部类对象呢:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Test2Innerclass {  
public static void main(String[] args) {
Outer outer = new Outer();
//调用外部类中的方法,利用类中的方法来间接访问内部类
outer.method();
}
}

class Outer {
//私有成员内部类
private class Inner {
public void show(){
System.out.println("inner..show");
}
}

public void method(){
//因为和Inner类都属于Outer类,所以可以直接这样创建
Inner i = new Inner();
//访问类中的方法
i.show();
}
}

小结

私有成员内部类访问:在自己所在的外部类中创建对象访问

静态成员内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test3Innerclass {  
public static void main(String[] args) {
// 外部类名.内部类名 对象名 = new 外部类名.内部类名();
Outer.Inner oi = new Outer.Inner();
oi.show();
}
}

class Outer {
//静态成员内部类
static class Inner {
public void show(){
System.out.println("inner..show");
}
}
}
1
2
Outer.Inner oi = new Outer().new Inner(); //错误,这是没有加static修饰符时的方式
Outer.Inner oi = new Outer.Inner(); //正确
  • 因为 Inner 不是 private 修饰的,所以外部可以访问
  • 因为 Inner 是 static 修饰的,可以不用创建类的对象『new Outer ()』来访问,通过类『Outer』直接就可以访问了

对于 Staic 修饰的其实很好调用,比如静态内部类中还有一个静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test3Innerclass {  
public static void main(String[] args) {
// 外部类名.内部类名 对象名 = new 外部类名.内部类名();
Outer.Inner oi = new Outer.Inner();
//类名一路调用即可
Outer.Inner.method();
}
}

class Outer {
//静态成员内部类
static class Inner {
public static void method(){
System.out.println("inner..method");
}
}
}
1
2
//类名一路调用即可,都是Static的,无需创建对象,直接类名调用
Outer.Inner.method();

小结

静态成员内部类访问:

1
外部类名.内部类名 对象名 = new 外部类名.内部类名(); 

静态成员内部类中的静态方法:

1
外部类名.内部类名.方法名();

局部内部类

局部内部类定义位置:局部内部类是在方法中定义的类,所以外界是无法直接访问的,需要在方法内部创建对象并使用

该类可以直接访问外部类的成员,也可以访问方法内地局部变量

1、代码展示:调用局部内部类中的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Test4Innerclass {  
public static void main(String[] args) {
Outer o = new Outer();
o.method();
}
}

class Outer {

public void method(){

//写在method当中的局部内部类
class Inner {
public void show(){
System.out.println("show...");
}
}

//方法内部可以访问Inner类
Inner inner = new Inner();
inner.show();
}
}

因为局部内部类的访问范围仅仅是方法体中的范围,范围如下图所示

01-局部内部类的访问范围.png

2、代码展示:局部内部类访问不同变量

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
public class Test4Innerclass {
public static void main(String[] args) {
Outer o = new Outer();
o.method();
}
}

class Outer {
// 类中的成员变量
int a = 10;

public void method(){
// 方法中的局部变量
int b = 20;

//写在method当中的局部内部类
class Inner {
public void show(){
System.out.println("show...");
// 可以访问(嵌在里边的可以访问外边的)
System.out.println(a);
// 可以访问方法中的局部变量
System.out.println(b);
}
}

//方法内部可以访问Inner类
Inner inner = new Inner();
inner.show();
}
}

小结

  • 局部内部类,外界是无法直接使用,需要在方法内部创建对象并使用
  • 该类可以直接访问外部类的成员,也可以访问方法内的局部变量
  • 局部内部类我们平时是很少编写的,因为局部内部类实在是太受限了;在看源码的过程中也很少会见到局部内部类,讲他的作用是为后面的匿名内部类打基础。因为匿名内部类属于一种特殊的局部内部类

匿名内部类

匿名内部类本质上说一个特殊的局部内部类(定义在方法内部)
前提:需要存在一个接口或类

格式如下:

1
2
3
4
5
6
7
8
9
10
/*
new 类名或接口名(){
重写方法;
}
*/

new Inter(){
@Override
public void method(){}
}

1、代码演示:正常使用接口中的方法需要几步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test5Innerclass {
public static void main(String[] args) {
InterImpl ii = new InterImpl();
ii.show();
}
}

interface Inner{
void show();
}

class InterImpl implements Inner{
@Override
public void show() {
System.out.println("InterImpl 重写的show方法");
}
}

1)创建实现类,通过 implements 关键字去实现接口
2)重写方法
3)创建实现类对象
4)调用重写后的方法

可以看到,想要使用接口中的方法还是比较复杂的。而如果是通过匿名内部类,就可以化简为 1 步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Test5Innerclass {  
public static void main(String[] args) {
- InterImpl ii = new InterImpl();
- ii.show();
//匿名内部类
+ new Inner(){
+ @Override
+ public void show() {
+ System.out.println("匿名内部类中的show方法");
+ }
+ }.show();
}
}

interface Inner{
void show();
}

-class InterImpl implements Inner{
- @Override
- public void show() {
- System.out.println("InterImpl 重写的show方法");
- }
-}

我们重点分析这几行代码

1
2
3
new Inner(){
//『实现接口的类』中需要重写的方法
}.show();

可以认为

02-正常方式访问接口中的方法VS匿名内部类方式.png

匿名内部类的理解:将继承(或实现)、方法重写、创建对象这三步放在了一步都当完成

1
2
new Inner(){}; --> 相当于创建了一个实现了接口的实现类对象
new Inner(){}.show(); --> 实现类对象调用方法

2、代码演示:如果接口中有多个方法,匿名内部类最多只能调用其中的一个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Test5Innerclass {
public static void main(String[] args) {

//匿名内部类
new Inner(){
@Override
public void show1() {
System.out.println("匿名内部类中的show1方法");
}

@Override
public void show2() {
System.out.println("匿名内部类中的show2方法");
}
//此处只能调用一次方法,就相当于"对象.方法",而不能"对象.方法1.方法2"
}.show1();
}
}

interface Inner{
void show1();
void show2();
}

此时,我就想调用其中的多个方法呢,怎么办

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
public class Test5Innerclass {  
public static void main(String[] args) {

//Inner i 接口的引用;new Inner(){} 实现类对象 --> 父类的引用指向了一个子类的对象(多态)
Inner i = new Inner(){
@Override
public void show1() {
System.out.println("匿名内部类中的show1方法");
}

@Override
public void show2() {
System.out.println("匿名内部类中的show2方法");
}
};

i.show1();
i.show2();
}
}

interface Inner{
void show1();
void show2();
}

案例小结:如果说一个接口当中有多个方法,如果使用匿名内部类的方式将其中的多个方法都进行调用,可以在匿名内部类前面通过一个父类或父接口的引用去接收一下,这样就能以多态的形式将匿名内部类接受过来。通过引用就可以调其中的方法了

匿名内部类在开发中的使用

当方法的形式参数是接口或者抽象类时,可以将匿名内部类作为实际参数进行传递

1、代码演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class TestSwimming {  
public static void main(String[] args) {

//调用方法(参数要求为实现类对象,这里我们使用匿名内部类的方式)
goSwimming(new Swimming(){
@Override
public void swim() {
System.out.println("GoGoGo!");
}
});
}

//实际参数为接口的方法
public static void goSwimming(Swimming swimming){
swimming.swim();
}
}

interface Swimming {
void swim();
}

此处蓝色标记的就是创建了一个匿名内部类,将其作为 goSwimming () 方法的参数

03-匿名内部类作为方法参数.png

既然匿名内部类作为方法参数,其格式比较固定,编译器也为我们提供了代码提示:只要输入 new 父接口名,根据提示回车即可自动生成代码段

04-匿名内部类作为方法参数代码快速生成.gif

Lambda 表达式

Lambda 表达式可以认为是对匿名内部类的优化,在了解这一块知识前,请先熟悉匿名内部类

匿名内部类和 Lambda 表达式

1、代码演示:匿名内部类和 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
public class TestSwimming {
public static void main(String[] args) {

//1、使用匿名内部类的方式
goSwimming(new Swimming() {
@Override
public void swim() {
System.out.println("铁汁, 我们去游泳吧");
}
});

//2、使用Lambda表达式,此处可以理解为是对匿名内部类的优化
goSwimming(() -> System.out.println("铁汁, 我们去游泳吧"));
}

//使用接口的方法
public static void goSwimming(Swimming swimming) {
swimming.swim();
}
}

interface Swimming {
void swim();
}

小结:Lambda 表达式可以使关注点更加明确,为什么这么说呢,请看一下分析:

1、对于匿名内部类的方式(面向对象思想,以什么形式去做)
1)方法要一个接口,我得给个接口的实现类对象
2)创建匿名内部类对象,重新方法
3)方法要干嘛呢,其实就是打印一句话

而其实,我们想要做的仅仅是打印一句话,也就是想要完成第 3 步,而第 1 步和第 2 步都是附加的操作,是 “不得不” 才这样写的

2、对于 Lambda 表达式(函数式编程思想,更多关注做什么)

Lambda 表达式的标准格式

05-匿名内部类与Lambda表达式.png

组成 Lambda 表达式的三要素:形式参数、箭头、代码块

1
(形式参数) -> {代码块}
  • 形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
  • ->:表示将小括号中的形式参数传到大括号的代码块中进行处理
  • 代码块:是我们具体要做到事情,也就是方法体中的内容

Lambda 表达式的使用前提:

  • 有一个接口(也就表明 Lambda 只能操作接口,不能操作类)
  • 接口中有且仅有一个抽象方法

Lambda 带参数无返回值

1、代码示例:

  • 接口中有且只有一个方法,方法有一个参数,无返回值
  • 采用匿名内部类和 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
public class StringHandlerDemo {  

public static void main(String[] args) {
// 匿名内部类的实现方式
useStringHandler(new StringHandler() {
@Override
public void printMessage(String msg) {
System.out.println("我是匿名内部类" + msg);
}
});

// Lambda实现方式
useStringHandler((String msg)->{System.out.println("我是Lambda表达式" + msg);});
useStringHandler( (msg) -> System.out.println("我是Lambda表达式" + msg));
useStringHandler( msg -> System.out.println("我是Lambda表达式" + msg));
}

public static void useStringHandler(StringHandler stringHandler){
stringHandler.printMessage("coffeelize");
}
}

interface StringHandler {
//带参数无返回值
void printMessage(String msg);
}

Lambda 无参数有返回值

如果 Lambda 所操作的接口中的方法,有返回值,一定要通过 return 语句,否则会出现编译错误

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
public class RandomNumHandlerDemo {  

public static void main(String[] args) {
//匿名内部类实现方式
useRandomNumHandler(new RandomNumHandler() {
@Override
public int getNumber() {
Random r = new Random();
//产生一个1-10的随机数
int num = r.nextInt(10) + 1;
return num;
}
});

//Lambda表达式实现方式
useRandomNumHandler( () -> {
Random r = new Random();
int num = r.nextInt(10) + 1;
// 注意: 如果lambda所操作的接口中的方法, 有返回值, 一定要通过return语句,否则会出现编译错误
return num;
} );
}

public static void useRandomNumHandler(RandomNumHandler randomNumHandler){
int result = randomNumHandler.getNumber();
System.out.println(result);
}
}

interface RandomNumHandler {
//无参数有返回值
int getNumber();
}

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
public class CalculatorDemo {  

public static void main(String[] args) {
//匿名内部类的实现方式
useCalculator(new Calculator() {
@Override
public int calc(int a, int b) {
return a + b;
}
});

//Lambda表达式实现方式
useCalculator((int a, int b)->{
return a + b;
});

useCalculator( (a,b) ->
a + b
);
}

public static void useCalculator(Calculator calculator){
int result = calculator.calc(10,20);
System.out.println(result);
}
}

interface Calculator {
//带参数,带返回值
int calc(int a, int b);
}

我们可以看到,以上代码中,第二种 Lambda 的书写更加简练,关于省略规则请往下看

1
2
3
4
5
6
7
8
//Lambda表达式实现方式  
useCalculator((int a, int b)->{
return a + b;
});

useCalculator( (a,b) ->
a + b
);

省略规则

  • 参数类型可以省略,但是有多个参数的情况下,不能只省略一个
    • 为什么可以省略呢?因为在接口中的方法中已经定义了参数类型,可以推导出来参数类型,所以可以省略
  • 如果参数有且仅有一个,那么小括号可以省略
  • 如果代码块的语句只有一条,可以省略大括号和分号,甚至是 return
    • 若有返回参数,要省略,大括号、分号和 return 一起省略