JavaSE 进阶 (3) 内部类

内部类概述

内部类:就是在一个类中定义一个类。举例:在一个 类 A 的内部定义一个 类 B类 B 就被称为内部类

意义: 在同一个包中,同名的类无法创建,内部类可以避免

/images/java/JavaSE 进阶 (3) 内部类/1.png
(图1)

内部类的定义格式

  • 格式
1
2
3
4
5
public class 类名 {
    修饰符 class 类名 {

    }
}
  • 范例
1
2
3
4
5
public class Outer {
    public class Inner {

    }
}

内部类的访问特点

  • 内部类可以直接访问外部类的成员,包括私有
  • 外部类想访问内部类的成员,必须先创建内部类对象,才能去使用
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class Outer {
	private int num = 10;

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

	public void method() {
		//show();  报错,外部类不能直接访问内部类的成员
		
		Inner i = new Inner();
		i.show();
	}
}

内部类的概念

  一般情况,我们把类定义成独立的单元。有些情况下,我们把一个类放在另一个类的内部定义,称为内部类 (innerclasses)

  内部类可以使用 publicdefaultprotectedprivate 以及 static 修饰。而外部顶级类(我们以前接的类)只能便用 publicdefault 修饰。

注意:内部类只是一个编译时概念,一旦我们编译成功,就会成为完全不同的两个类。对于一个名为 Outer 的外部类和其内部定义的名为 Inner 的内部类。编译完成后会出现 Outer.classOuter$Inner.class 两个类的字节码文件。所以内部类是相对独立的一种存在,其 成员变量/方法名 可以 和外部类的相同

内部类的作用

  1. 内部类提供了更好的封装。只能让外部类直接访问,不允许同一个包中的其他类直接访问
  2. 内部类可以直接访问外部类的私有属性,内部类被当成其外部类的成员。但外部类不能访问内部类的内部属性

成员内部类

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

  • 在类的成员位置:成员内部类
  • 在类的局部位置:局部内部类 (见下面的 局部内部类)

非静态内部类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class Outer {
	private int num = 10;

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

/*
  测试类
*/
public class InnerDemo {
	public static void main(String[] args) {
		//创建内部类对象,并调用方法
		//Inner i = new Inner();
		//报错,这是因为Inner是在Outer内部定义的,你要想使用Outer中的成员,需要通过Outer限定才可以使用,直接使用肯定是不行的
		
	}
}

成员内部类,外界如何创建对象使用呢?

因为内部类完全依附于外部类,所以也可以用以下形式创建内部类

  • 格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象;
  • 范例:Outer.Inner oi = new Outer().new Inner();

另一种方式

1
2
Outer o = new Outer();  
Inner i = o.new Inner(); 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class Outer {
	private int num = 10;

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

/*
  测试类
*/
public class InnerDemo {
	public static void main(String[] args) {
		//创建内部类对象,并调用方法
		Outer.Inner oi = new Outer().new Inner();
		oi.show();
	}
}

我们定义内部类,就是为了不让外部类访问,所以不用 public 修饰,一般用 private 修饰

使用 private 修饰,Outer.Inner oi = new Outer().new Inner(); 报错,这是因为 Outer 中的私有内容只能在 Outer 中访问,不能在外部访问

 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 Outer {
	private int num = 10;

	//public class Inner {
	//	public void show() {
	//		System.out.println(num);
	//	}
	//}
	
	private class Inner {
		public void show() {
			System.out.println(num);
		}
	}
}

/*
  测试类
*/
public class InnerDemo {
	public static void main(String[] args) {
		//创建内部类对象,并调用方法
		//Outer.Inner oi = new Outer().new Inner();  报错
		//oi.show();
	}
}

此时如果要使用内部类的方法该怎么办呢?

虽然外部无法使用,但是内部是可以访问的

外界通过创建 Outer 对象,来调用 method() 方法,间接的创建 Inner 对象来调 show() 方法

 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 Outer {
	private int num = 10;

	private class Inner {
		public void show() {
			System.out.println(num);
		}
	}
	
	//通过创建方法调用内部类对象
	public void method() {
		Inner i = new Inner();
		i.show();
	}
}

/*
  测试类
*/
public class InnerDemo {
	public static void main(String[] args) {
		Outer o = new Outer();
		o.method();
	}
}

当外部类属性和内部类属性发生重名时,可以通过:Outer.this.成员名 来调用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class Outer {
	private int num = 10;

	private class Inner {
		private int num = 20;
		
		public void show() {
			int num = 30;
			System.out.println(num);  //30
			System.out.println(this.num);  //20
			System.out.println(Outer.this.num); //10
		}
	}
	
}

总结

  1. 非静态内部类对象必须寄存在一个外部类对象里 Face.Nose nose = new Face().Nose()。因此,如果有一个非静态内部类对象那么一定存在对应的外部类对
  2. 非静态内部类可以直接访问外部类的成员,但是外部类不能直接访问非静态内部类成员
  3. 非静态内部类不能有静态方法,静态属性和静态初始化块
  4. 非静态内部类成员变量访问要点:
    • 访问内部类里方法的局部变量:变量名
    • 访问内部类属性:this.变量名
    • 访问外部类属性:外部类名.this.变量名

静态内部类

定义方式

1
2
3
static class ClassName {
  // 类体
}

使用要点:

  1. 静态内部类可以访问外部类的静态成员,不能访问外部类的普通成员
  2. 静态内部类看作外部类的一个静态成员

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Outer2 {
	private int a = 10;
	private static int b = 20;
	
	static class Inner2 {
		public void test() {
			//System.out.println(a);  静态内部类不能访问外部类的普通属性
			System.out.println(b);  //静态内部类可以访问外部类的静态属性
		}
	}
}

public class TeststaticInnerclass{
	public static void main(String[] args){
		//通过 new 外部类名.内部类名() 来创建内部类对象
		Outer2.Inner2 inner =new Outer2.Inner2();
		inner.test();
	}
}

局部内部类

局部内部类(在类的局部位置定义的类)

 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 Outer {
	private int num = 10;

	public void method() {
		int num2 = 20;
		class Inner {
			public void show() {
				System.out.printin(num);
				System.out.printin(num2);
			}
		}

		Inner i = new Inner();
		i.show();
	}
}

/*
  测试类
*/
public class OuterDemo {
	public static void main(String[] args) {
		Outer o = new Outer();
		o.method();
	}
}
  1. 局部内部类是在方法中定义的类,所以外界是无法直接使用,需要在方法内部创建对象并使用
  2. 该类可以直接访问外部类的成员,也可以访问方法内的局部变量

意义: 节省内存,随着方法的结束,释放局部内部类的内存,写在外部会存放在方法区,得等到程序结束才会释放内存

注意: 局部内部类访问的 外部类的局部变量 需要添加 final 关键字,这是因为内部类的生命周期与外部类局部变量的生命周期不一致所导致的

  1. 内部类的生命周期和外部类的生命周期是相等的,并不会因为外部类的代码执行完成后就销毁。
  2. 当内部类使用外部类的局部变量,这个变量在外部类执行后就会被 gc,内部类就会丢失该变量的引用。
  3. 所以使用 final 关键字修饰一个引用变量,其指向的内存区域就不会变了。内部类就可以使用该变量的引用

匿名内部类

匿名内部类是局部内部类的一种特殊

前提: 存在一个类或者接口,这里的类可以是具体类也可以是抽象类

1
2
3
new 类名或者接口名() {
  重写方法
}
1
2
3
4
5
new Inter() {
  public void show() {

  }
}

没有名字,整体是一个匿名对象

本质: 是一个继承了该类的子类匿名对象,或者是一个实现了该接口的实现类匿名对象

 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 interface Inter {
	void show();
}

public class Outer {
	public void method() {
		/*
		new Inter() {
			@Override
			public void show() {
				System.oUt.println("匿名内部类"):
			}
		};
		*/
		
		//show();
		
		new Inter() {  //返回的是一个对象
			@Override
			public void show() {
				System.oUt.println("匿名内部类");
			}
		}.show();
	}
}

public class OuterDemo {
	public static void main(String[] args) {
		Outer o = new Outer();
		o.method();
	}
}

若要多次调用,可以通过以下方式:

因为 new Inter() 是实现了接口的匿名实现类对象,所以可以通过 Inter I = new Inter(); 来进行赋值,赋值给一个接口,用接口对象来调用 show() 方法,这也可以叫做多态(接口多态),因为 右边是左边接口的匿名实现类对象,编译看左边,运行看右边,所以运行的是匿名内部类中的方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public interface Inter {
	void show();
}

public class Outer {
	public void method() {
		Inter i = new Inter() {  //返回的是一个对象
			@Override
			public void show() {
				System.oUt.println("匿名内部类");
			}
		};
		
		i.show();
		i.show();
	}
}

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

/images/java/JavaSE 进阶 (3) 内部类/2.png
(图2)
1
2
ActionListener ac = new MyActionListener();
jButton.addActionListener(ac);

通过上面 2 行代码可知,JButton.addActionListener(ac); 需要传入 ActionListener 接口的实现类对象

我们通过匿名内部类的方式进行优化

1
2
3
4
5
6
jButton.addActionListener(new ActionListener() {
	@Override
	public void actionPerformed(ActionEvent e) {
		System.out.printun("你再点试试"):
	}
});	

体验Lambda表达式

lambda 是函数式思想的体现

函数式编程思想概述

/images/java/JavaSE 进阶 (3) 内部类/3.png
(图3)
  • 在数学中,函数就是有输入量、输出量的一套计算方案,也就是 拿数据做操作
  • 面向对象思想强调 必须通过对象的形式来做事情
  • 函数式思想则尽量忽略面向对象的复杂语法:强调做什么,而不是以什么形式去做
  • Lambda 表达式就是函数式思想的体现

体验Lambda表达式

  1. 需求:按照如下代码要求,使用接口,进行测试
/images/java/JavaSE 进阶 (3) 内部类/4.png
(图4)
  1. 面向对象思想:匿名内部类实现
  2. 函数式思想: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
public interface Inter {
	void show();
}

/*
  测试类
*/
public class InterDemo {

	public static void useInter(Inter i) {
		i.show();
	}

	public static void main(string[] args){
		//匿名内部类实现
		useInter(new Inter() {
				@Override
				public void show() {
					System.out.println("匿名内部类实现");
				}
		});
		
		//Lambda表达式实现
		useInter(() -> {
			System.out.println("Lambda表达式实现");
		});
	}
}

Lambda 表达式是匿名内部类的简化,基于: 能推断就省略

Lambda表达式标准格式

匿名内部类中重写show()方法的代码分析

1
2
3
4
5
6
7
//匿名内部类实现
useInter(new Inter() {
		@Override
		public void show() {
			System.out.println("匿名内部类实现");
		}
});
  1. 方法形式参数为空,说明调用方法时不需要传递参数
  2. 方法返回值类型为 void,说明方法执行没有结果返回
  3. 方法体中的内容,是我们具体要做的事情

Lambda表达式的代码分析

1
2
3
4
//Lambda表达式实现
useInter(() -> {
	System.out.println("Lambda表达式实现");
});
  1. (): 里面没有内容,可以看成是方法形式参数为空
  2. ->: 用箭头指向后面要做的事情
  3. {}: 包含一段代码,我们称之为代码块,可以看成是方法体中的内容

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


Lambda表达式的标准格式

  1. 格式: (形式参数) -> {代码块}
  2. 形式参数: 如果有多个参数,参数之间用逗号隔开,如果没有参数,留空即可
  3. ->: 由英文中划线和大于符号组成,固定写法,代表指向动作
  4. 代码块: 是我们具体要做的事情,也就是以前我们写的方法体内容

拿着形式参数去做代码块中的事情

练习1(无参无返回值)

Lambda 表达式的使用前提

  • 有一个接口
  • 接口中 只能有一个抽象方法

练习1:

(1) 定义一个接口(Eatable),里面定义一个抽象方法:void eat();

(2) 定义一个测试类(EatableDemo),在测试类中提供两个方法

  • 一个方法是:useEatable(Eatable e)
  • 一个方法是主方法,在主方法中调用 useEatable 方法
 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 interface Eatable {
	void eat();
}

public class EatableDemo {

	public static void useEatable(Eatable e){
		e.eat();
	}

	public static void main(String[] args) {
		//在主方法中调useEatable方法
		//匿名内部类
		useEatable(new Eatable(){
			@Override
			public void eat(){
				System.out.println("一天一苹果,医生远离我")
			}
		});
		
		//Lambda表达式
		useEatable(() -> {
			System.out.println("一天一苹果,医生远离我");
		});
	}
}

练习2(带参无返回值)

(1) 定义一个接口(Flyable),里面定义一个抽象方法:void fly(String s);

(2) 定义一个测试类(FlyableDemo),在测试类中提供两个方法

  • 一个方法是:useFlyable(Flyable f)
  • 一个方法是主方法,在主方法中调用 useFlyable 方法
 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
public interface Flyable {
	void fly(string s);
}

public class FlyableDemo {

	public static void useFlyable(Flyable f) {
		f.fly("风和日丽,晴空万里");
	}

	public static void main(String[] args) {
		//在主方法中调useFlyable方法
		//匿名内部类
		useFlyable(new Flyable(){
			@Override
			public void fly(String s){
				System.out.println(s);
				System.out.println("飞机自驾游");
			}
		});
		
		//lambda
		useFlyable((String s) -> {
			System.out.println(s);
			System.out.println("飞机自驾游");
		});
	
	}
}

练习3(带参带返回值)

(1) 定义一个接口(Addable),里面定义一个抽象方法:int add(int x, int y);

(2) 定义一个测试类(AddableDemo),在测试类中提供两个方法

  • 一个方法是:useAddable(Addable a)
  • 一个方法是主方法,在主方法中调用 useAddable 方法
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public interface Addable {
	int add(int x,int y);
}

public class AddableDemo {
	public static void main(String[] args) {
		//在主方法中调useAddable方法
		useAddable((int x,int y) -> {
			return x + y;
		});
	}
	
	public static void useAddable(Addable a) {
		int sum = a.add(10, 20);
		System.out.println(sum);
	}
}

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
public interface Addable {
	int add(int x,int y);
}

public interface Flyable {
	void fly(String s);
}

/*
   Lambda表达式的省略模式
*/
public class LambdaDemo {
	public static void main(String[] args) {
		//lambda格式
		useAddable((int x,int y) -> {
			return x + y;
		});
		
		//参数的类型可以省略
		useAddable((x, y) -> {
			return x + y;
		});
		
		//但是有多个参数的情况下,不能只省略一个
		//useAddable((x, int y) -> {
		//	return x + y;
		//});
		
		//------------------------------
		
		//lambda格式
		useFlyable((String s) -> {
			System.out.println(s);
		});
		
		//参数的类型可以省略
		useFlyable((s) -> {
			System.out.println(s);
		});
		
		//如果参数有且仅有一个,那么小括号也是可以省略的
		useFlyable(s -> {
			System.out.println(s);
		});
		
		//如果代码块的语句只有一条,可以省略大括号和分号
		useFlyable(s -> System.out.println(s));
		
		//如果代码块的语句只有一条,可以省略大括号和分号,如果有return, return也要省略
		useAddable((x, y) -> x + y);
	}

	private static void useFlyable(FLyable f) {
		f.fly("风和日丽,晴空万里");
	}

	private static void useAddable(Addable a) {
		int sum = a.add(10, 20)
		System.out.println(sum);
	}
}
  1. 参数的类型可以省略,但是有多个参数的情况下,不能只省略一个
  2. 如果参数有且仅有一个,那么小括号也是可以省略的
  3. 如果代码块的语句只有一条,可以省略大括号和分号,甚至是 return

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
66
67
68
69
70
71
//接口
public interface Inter {
	void show();
}

//抽象类
public abstract class Animal {
	public abstract void method();
}

//普通类
public class Student {
	public void study() {
		System.out.println("爱生活,爱Java");
	}
}

/*
	Lambda表达式和匿名内部类的区别
*/
public class LambdaDemo{
	public static void main(String[] args) {
		//匿名内部类
		useInter(new Inter(){
			@Override
			public void show(){
				System.out.println("接口");
			}
		});
		
		useAnimal(new Animal(){
			@Override
			public void method(){
				System.out.println("抽象类");
			}
		})
		
		//具体类也可以重写
		useStudent(new Student(){
			@Override
			public void study(){
				System.out.println("具体类");
			}
		});

		//---------------------------------------
		
		//lambda
		useInter(() -> System.out.println("接口"));

		/*
			useAnimal(() -> System.out.println("抽象类")); 报错
			useStudent(() -> System.out.println("具体类")); 报错
		*/
		
	}

	private static void useStudent(Student s){
		s.study();
	}
	

	private static void useAnimal(Animal a){
		a.method();

	}

	private static void useInter(Inter i){
		i.show();
	}
}

所需类型不同

  • 匿名内部类: 可以是接口,也可以是抽象类,还可以是具体类
  • 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
//接口
public interface Inter {
	void show();
	void show2();
}

/*
	Lambda表达式和匿名内部类的区别
*/
public class LambdaDemo
{
	public static void main(String[] args) {
		
		useInter(new Inter(){
			@Override
			public void show(){
				System.out.println("show");
			}

			@Override
			public void show2(){
				System.out.println("show2")
			}
		});
		
	}

	private static void useStudent(Student s) {
		s.study();
	}
	

	private static void useAnimal(Animal a) {
		a.method();

	}

	private static void useInter(Inter i) {
		i.show();
	}
}

使用限制不同

  • 如果接口中仅有一个抽象方法,可以使用 Lambda 表达式,也可以使用匿名内部类
  • 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用 Lambda 表达式

情况一:测试类中什么也没有

/images/java/JavaSE 进阶 (3) 内部类/5.png
(图5)
 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
//接口
public interface Inter {
	void show();
}

//抽象类
public abstract class Animal {
	public abstract void method();
}

//普通类
public class Student {
	public void study() {
		System.out.println("爱生活,爱Java");
	}
}

/*
	Lambda表达式和匿名内部类的区别
*/
public class LambdaDemo {
	public static void main(String[] args) {
		
	}

	private static void useStudent(Student s) {
		s.study();
	}
	

	private static void useAnimal(Animal a) {
		a.method();

	}

	private static void useInter(Inter i) {
		i.show();
	}
}

情况二:匿名内部类

/images/java/JavaSE 进阶 (3) 内部类/6.png
(图6)
 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
//接口
public interface Inter {
	void show();
}

//抽象类
public abstract class Animal {
	public abstract void method();
}

//普通类
public class Student {
	public void study() {
		System.out.println("爱生活,爱Java");
	}
}

/*
	Lambda表达式和匿名内部类的区别
*/
public class LambdaDemo {
	public static void main(String[] args) {
		
		useInter(new Inter(){
			@Override
			public void show() {
				System.out.println("接口");
			}
		});
		
	}

	private static void useStudent(Student s) {
		s.study();
	}
	

	private static void useAnimal(Animal a) {
		a.method();

	}

	private static void useInter(Inter i) {
		i.show();
	}
}

情况三:lambda表达式

/images/java/JavaSE 进阶 (3) 内部类/7.png
(图7)
 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
//接口
public interface Inter {
	void show();
}

//抽象类
public abstract class Animal {
	public abstract void method();
}

//普通类
public class Student {
	public void study() {
		System.out.println("爱生活,爱Java");
	}
}

/*
	Lambda表达式和匿名内部类的区别
*/
public class LambdaDemo {
	public static void main(String[] args) {
		useInter(() -> System.out.println("接口"));
	}

	private static void useStudent(Student s) {
		s.study();
	}
	

	private static void useAnimal(Animal a) {
		a.method();

	}

	private static void useInter(Inter i) {
		i.show();
	}
}

实现原理不同

  1. 匿名内部类: 编译之后,产生一个单独的 .class 字节码文件
  2. Lambda 表达式: 编译之后,没有看到一个单独的 .class 字节码文件,因为它对应的字节码文件会在运行的时候动态生成

0%