Java基础(四)—— 内部类

本文最后更新于:2021年9月28日 晚上

概览:Java内部类:静态内部类、局部内部类和匿名内部类。

2021/07/25

内部类就是类的定义位于外部类之内,它与外部类的属性方法并列。

内部类分别有:

  • 成员内部类
  • 静态内部类
  • 匿名内部类
  • 局部内部类

成员内部类

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

// 外部类定义的属性和方法
private int id = 1;
private static int sex = 0;

public MemberInnerClass(int id) {
this.id = id;
}

public void printInfo() {
System.out.println("Outer class info: " + id + ", " + sex);
}

public static void printSex() {
System.out.println("Outer class static info: " + sex);
}

// 内部类
class InnerClass1 {
private int one = 1;
public int two = 2;
// public static int three = 3; // 成员内部类不支持static属性或者方法

public InnerClass1(int one, int two) {
this.one = one;
this.two = two;
}

public void publicFunc() {
System.out.println("Inner class public func");
System.out.println("one,two " + one + two);
System.out.println("Outer member: " + id);
System.out.println("Outer static member: " + sex);
printSex(); // 等价于 MemberInnerClass.printSex();
printInfo(); // 等价于 MemberInnerClass.this.printInfo();
}
}

// 外部类访问内部类中的数据
public void testInnerClass() {
InnerClass1 inner = new InnerClass1(1, 2);
inner.publicFunc();
}
}

public class MemberInnerClassTest {

public static void main(String[] args) {
// 定义一个成员内部类的对象
MemberInnerClass outer = new MemberInnerClass(1);
MemberInnerClass.InnerClass1 inner1 = outer.new InnerClass1(1, 2);

// 通过对象尝试访问私有或者公有的方法
inner1.publicFunc();
}
}

特点:

  1. 成员内部类中不能够书写静态的属性和方法!
  2. 成员内部类可以访问外部类的属性和方法,包括私有的静态的
    • 访问静态的直接通过变量名或者外部类.变量名的方式调用。
    • 访问普通的成员直接通过变量名或者外部类.this.变量名的方式调用。
  3. 关于在类外创建内部类对象,需要注意特殊写法,外部类对象.new 内部类()
  4. 内部类是一个编译时的概念,一旦编译成功,就会称为两个不同的类,例如上述的例子中,最终生成了MemberInnerClass.classMemberInnerClass$InnerClass1.class

内部类能够访问外部类的原因在于,在通过外部类对象来访问内部类对象时,外部类对象会将自己的引用传递给内部类,内部类可以通过Outer.this的方式来调用外部类的属性和方法。

作用:

  1. 用成员内部类定义在外部类中不可访问的属性。这样就在外部类中实现了比外部类的private还要小的访问权限。
  2. 数据安全。如果我们的内部类不想轻易被任何人访问,可以选择使用private修饰内部类,这样我们就无法通过创建对象的途径来访问,想要访问只需要在外部类中定义一个public修饰的方法,间接调用。

静态内部类

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

private int id = 1;
private static int sex = 0;

public StaticInnerClass(int id) {
this.id = id;
}

public void printInfo() {
System.out.println("Outer class info: " + id + ", " + sex);
}

public static void printSex() {
System.out.println("Outer class static info: " + sex);
}

static class InnerClass{
private int id = 100;
private static int sex = 1;

public InnerClass(int id) {
this.id = id;
}

public static void printSexAndOuter(){
// System.out.println("Inner class info : " + id); // 错误,不能访问
System.out.println("Inner class static info: " + sex);
System.out.println("Outer class static info: " + StaticInnerClass.sex);
}
// 静态内部类的普通方法
public void printInfo(){
System.out.println("Inner class: " + id);
// System.out.println("Outer class: " + StaticInnerClass.this.id); // 错误,不能访问
System.out.println("Inner class: " + sex);
System.out.println("Outer class: " + StaticInnerClass.sex);
}
}
}

public class StaticInnerClassTest {
public static void main(String[] args) {
StaticInnerClass outer = new StaticInnerClass(10);
StaticInnerClass.InnerClass inner = new StaticInnerClass.InnerClass(100);

// 通过对象实例访问普通方法
inner.printInfo();
// 通过类名访问static方法
StaticInnerClass.InnerClass.printSexAndOuter();
}
}

特点:

  1. 静态内部类可以声明静态的成员和方法。而成员内部类则不可以。
  2. 和常规相同,static中没有this指针,static方法不能访问普通的成员变量。
  3. 注意内部类对象声明的写法,和成员内部类不同。
  4. 编译生成的文件和成员内部类相同。

作用:

  1. 提供调试作用。我将main方法写在静态内部类中,生成.class文件后,调试代码在静态内部类当中,当我删除静态内部类后,其他人仍然可以使用我的外部类。👍

局部内部类

  • 局部内部类是一个在方法的内部声明的类。
  • 局部内部类可以访问外部类的成员变量以及方法。
  • 局部内部类中如果要访问该内部类所在方法中的局部变量,那么这个局部变量就必须是final修饰的
    • 不过从Java8开始,只要局部变量没有发生过改变,也是不会报错的~
    • final修饰变量:变为常量,会在常量池中放着,逆向思维想这个问题,如果不使用final修饰,当局部内部类被实例化后,方法弹栈,局部变量随着跟着消失,这个时候局部内部类对象再想去调用该局部变量,就会报错,因为该局部变量已经没了,当局部变量用final修饰后,就会将其加入常量池中,即使方法弹栈了,该局部变量还在常量池中呆着,局部内部类也就是够调用。所以局部内部类想要调用局部变量时,需要使用final修饰,不使用,编译通不过。
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
public class LocalInnerClass {

private int id = 1;
private static int sex = 0;

public LocalInnerClass(int id) {
this.id = id;
}

public void printInfo() {
System.out.println("Outer class info: " + id + ", " + sex);
}

public static void printSex() {
System.out.println("Outer class static info: " + sex);
}

public void localFunc() {
int id = 10;
// 局部内部类
class InnerClass {
public void printInfo() {
System.out.println("local inner id: " + id);
System.out.println("outer: " + LocalInnerClass.this.id + "," + LocalInnerClass.sex);
}
}

InnerClass inner = new InnerClass();
inner.printInfo();
}
}

public class LocalInnerClassTest {
public static void main(String[] args) {
LocalInnerClass outer = new LocalInnerClass(1);
outer.printInfo();

outer.localFunc();
}
}

///////执行结果
Outer class info: 1, 0
local inner id:10
outer: 10

特点:

  1. 局部内部类不需要也不能通过外部类对象直接实例化,它只能在其所在方法中实例化自己。
  2. 局部内部类只能用于那个方法之中,相当于一份局部变量,但是!局部内部类可以访问所在类的属性和方法。

作用:

  1. 局部内部类只能在所在的方法体作用域中进行实例化,如果有想要所在方法返回这个类,就要通过接口的向上转型操作!在某些情况下,某些业务逻辑需要临时处理,这些业务逻辑只在这里使用又可以封装成一个类的话,而又没必要重新建个文件,所以可以这写一个局部内部类来处理。java代理模式中可能有用到局部内部类,在方法中直接实现接口,返回代理对象,简单而又方便。
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
// 专用做像向上转型的父接口
interface Innerface{
public void inner();
}

public class ReturnLocalInnerClass {
public Innerface work(){

class InnerClass implements Innerface{

@Override
public void inner() {
System.out.println("Inner implement innerface");
}
}
return new InnerClass();
}

}

public class ReturnLocalInnerClassTest {
public static void main(String[] args) {
ReturnLocalInnerClass outer = new ReturnLocalInnerClass();
Innerface inner = outer.work();
inner.inner();
}
}

匿名内部类

  • 类似于匿名对象,仅当需要时使用一次,作为临时实现的内部类。
  • 本质上是一个继承了类或者实现了接口的子类匿名对象。
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
class OnceClass{
public void printInfo(String info){
System.out.println("Only once info: " + info);
}
private void printInfoPrv(String info){
System.out.println("Only once info with private: " + info);
}
}

public class AnonInnerClass {
private String name;

public AnonInnerClass(String name) {
this.name = name;
}

public void work(){
System.out.println("AnonInnerClass work");
// 创建匿名对象之后立即调用
new OnceClass().printInfo(this.name);
// new OnceClass().printInfoPrv(this.name); // 不能调用,子类不能直接调用父类中私有的属性和方法
}
}

public class AnonInnerClassTest {
public static void main(String[] args) {
AnonInnerClass outer = new AnonInnerClass("Bob");
outer.work();
}
}

特点:

  1. 匿名内部类需要依托其他类或者接口来创建。如果依托于类,那么就是这个类的子类,如果依托于接口,那就是这个接口的实现类。
  2. 匿名内部类除了依托的类或接口之外,不能指定继承或者实现其他类或接口,同时也不能被其他类所继承,因为没有名字。
  3. 匿名内部类的创建只能是使用new关键字,可以new 类名/接口名字(){ 重写func();}.func()这样的形式。

作用:

  1. 如果某些方法仅仅会被调用一次,那么就可以使用匿名内部类进行格式简化。某些方法参数提示是抽象类或者接口作为参数,此时就可以提供一个对象的子类对象。例如Thread的构造方法中有一种需要传入Runnable接口参数的方法。

为什么使用内部类

1 实现封装性

通过内部类来进一步封装一个类内部的属性,如果不想要内部类轻易被访问,那么可以通过加private修饰符来达到效果,这样就不能通过实例化内部类对象的方式来访问内容,只能通过外部类提供的接口来访问。

2 实现回调功能

可以编写一个接口,然后实现那个接口,再将接口按照一个对象当作参数的形式传递给另一个方法中,然后通过这个接口实例化的对象来调用方法

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

public class InnerClassCallBack {
public void func(CallBack call){
System.out.println("Callback is running");
call.callBackFunc(); // 触发回调
}

public static void main(String[] args) {
InnerClassCallBack iccb = new InnerClassCallBack();
iccb.func(new CallBack() {
@Override
public void callBackFunc() {
System.out.println("callbackfunc is running");
}
});
}
}

To Be Continue

参考链接:https://blog.csdn.net/weixin_45039616/article/details/105289452

https://blog.csdn.net/Hacker_ZhiDian/article/details/82193100


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!