【Java 基础】内部类 类型、功能概述

一、概述

  • 将一个类定义在另一个类 / 方法里面,这样的类称为内部类。
  • 内部类可以分为四种:成员内部类局部内部类匿名内部类静态内部类

二、分类

1、成员内部类

  • 成员内部类的定义方式,类似于类的成员变量,通常的定义方式如下:
public class OuterClass {
    public class InnerClass{
        //成员内部类
    }
}

① 成员内部类无条件访问外部类的属性和方法

  • 成员内部类访问外部类的属性、方法时,由于成员内部类在形式上处于外部类以内,所以我们无需手动创建外部类对象,可直接访问。
public class OuterClass {
	private int o = 5;                  //定义外部类成员变量(属性)
	private void move(){}               //定义外部类方法move

	public class InnerClass{            //成员内部类
		int b = o;                      //将外部类成员变量(属性)的值赋予内部类的成员变量(属性)
		//定义内部类自己的方法 moveFast
        public void moveFast() {
			System.out.println(o);      //直接打印外部类成员变量(属性)的值 
			move(); 					//直接调用外部类的 move方法
		}
	}
}

② 外部类访问成员内部类属性和方法

  • 而外部类需访问内部类属性、方法时,则需创建内部类对象,方可访问。
public class OuterClass {
    InnerClass innerClass = new InnerClass();//创建内部类对象
    
    //定义外部类的方法innerLove
    public void innerLove(){           
        innerClass.outerLove(); //
        System.out.println(innerClass.b);
    }

    public class InnerClass{            //成员内部类
        int b = 3;                      //定义成员内部类的成员变量
        //定义内部类自己的方法 outerLove
        public void outerLove() {}
    }
}

③ 外部类属性或方法隐藏

  • 如果成员内部类的属性或者方法与外部类的同名,将导致外部类的这些属性与方法在内部类被隐藏。
  • 可按照该格式调用(外部类.this.属性 / 方法):
package com.outerinnerdemo;

public class OuterClass {		//外部类
	int b = 4; 					//定义外部类的成员变量
	//定义外部类的方法 love
	public void love(){System.out.println("外部类");}

	public class InnerClass{		//成员内部类
		int b = 3; 					//定义成员内部类的成员变量
		//定义内部类自己的方法 love,方法重名(注意观察是否注释该方法,运行结果的差别)
		public void love() {
			System.out.println("内部类");
		}
		//协助方法ll,用以区分
		public void ll(){
			OuterClass.this.love();	//调用外部类方法(只要有同名方法存在,就必须通过这种方式 --> 调用外部类的对应方法)
			love();					//调用内部类方法
		} 
	}
}
package com.outerinnerdemo;

public class Demo {
	OuterClass outerClass = new OuterClass();
	OuterClass.InnerClass innerClass = outerClass.new InnerClass();

	public static void main(String[] args) {
		Demo demo = new Demo();
		demo.loveInner();
	}
	public void loveInner(){
		innerClass.ll();//调用协同方法
	}
}

运行结果如下:
内外部类同名方法

④ 创建成员内部类对象

public class OuterClass {
	//创建内部类对象
	InnerClass innerClass = new InnerClass();
    public class InnerClass{
        //成员内部类
    }
}
  • 成员内部类可被 权限(public、protected、default、private)修饰符修饰,这意味着它可以被外部的其他类访问。
  • 不仅可以在外部类中创建对应内部类对象,也可以在外部类外创建内部类对象,一般的访问方式如下:
    成员内部类、外部类
  • 我们注意到,由于InnerClass不是静态内部类,说明其依托于自身外部类,必须先创建外部类对象,再通过这个外部类对象创建内部类对象。
    访问方式

2、局部内部类

  • 位于方法体中的类,我们成为局部内部类,局部内部类不可被 权限(public、protected、private)修饰符修饰,这意味着它不可与外界交互,完全依附于方法体。
  • 即 局部内部类的访问权限仅限于方法或作用域内。
  • 局部内部类不可被static修饰
public class OuterClass {
	public void move(){
		class myclass{
			//局部内部类
		}
	} 
}
  • 我们可以明显看到,其与成员内部类的差异:
    局部内部类

3、匿名内部类

  • 创建一个匿名类,实现一个特定的接口(继承某个类),完成某种特定的功能。
  • 由于匿名内部类没有特定名称,所以匿名内部类不可被外部访问。
  • 一般作为参数使用,可以使原先类在不实现该接口的情况下,完成接口提供的某种方法。

  我们来看下面的例子,定义了一个接口Car。

  分析代码知道静态方法driveCar需要一个Car对象,我们通过实现接口创建一个匿名类对象传递过去。

public class carTest {
	public static void main(String[] args) {
		//调用 driveCar 方法
		driveCar(new Car(){		//继承Car接口的匿名内部类
			//重写实现run方法
			@Override
			public void run() {
				System.out.println("汽车跑起来!");
			}
		});
	}
	//定义方法
	public static void driveCar(Car car) {
		car.run();
	}
}
//定义接口Car
interface Car {
	//定义抽象方法run
	void run();
}

注意事项:匿名内部类没有构造方法。也是唯一没有构造方法的内部类。匿名内部类和局部内部类只能访问外部类的final变量

① 匿名内部类访问外部类的属性和方法

  • 当匿名内部类需要访问外部类的属性和方法,但名称冲突时,可以以外部类名.this.方法名 / 属性名的形式对其进行访问。

匿名内部类大多数作为参数传入,方便了我们的使用,但也可以以其他形式出现,例如:

public class Demo {     //测试类
    public static void main(String[] args) {
        A a = new A() {     //实例化一个实现A接口的 匿名内部类对象,并创建接口引用指向子类对象
            @Override
            public void run() {	//实现接口A规定的 run 方法
				System.out.println("running......");
            }
        };
        a.run();        //调用这个内部类对象的方法
    }
}
//定义接口A
interface A {
    //定义抽象方法run
    void run();
}

② lambda表达式

  • 匿名内部类看起来十分的不美观,尤其是当接口中方法较少时,我们很容易直到这些方法的参数。
  • 为简化匿名内部类,我们可以使用 lambda表达式 对其进行简化,简化上一小节的代码,转换为如下形式:
public class Demo {     //测试类
    public static void main(String[] args) {
		//实例化一个匿名的、实现A接口的内部类对象,并创建接口引用指向子类对象
		A a1 = () -> {System.out.println("running......");};
		a1.run();        //调用这个内部类对象的方法
        
		//当实现的方法体中仅有一句语句时,可省略花括号,直接写出语句
		A a2 = () -> System.out.println("running......");
		a2.run();        //调用这个内部类对象的方法
    }
}
//定义接口A
@FunctionalInterface
interface A {
    //定义抽象方法run
    void run();
}

4、静态内部类(静态嵌套类)

  • 静态内部类的核心关键字是static,关于它的介绍,可以参考博主的上一篇文章(static关键字介绍

Nested classes are divided into two categories: static and non-static. Nested classes that are declared static are called static nested classes. Non-static nested classes are called inner classes.
(——转自Oracle官方)

根据官方说法可以看到,我们俗称的 “静态内部类” 的官方名称为 静态嵌套类

  • 字面上也可以看出来,静态内部类可以方便我们更好的使用形式上存在于外部类(对应“嵌套类”的概念,我们可以称之为“外壳类”)的静态变量,其本身与其他static修饰的静态域一样,不依附于外部类。

  • 用static修饰的内部类,称为“静态内部类”。

① 静态内部类无条件访问外部类的的静态变量、方法

  • 静态内部类可直接使用外部类的静态变量、方法,无论相隔多少层,可直接使用名称调用、使用。
    static
  • 将外部类的变量赋予内部类成员时,其自身必须为static修饰的静态变量。
    静态变量

② 在静态内部类使用main方法

  • 只有静态内部类可以调用main方法,而普通的内部类则不可以。
    内部类 - main方法
    静态内部类 - mian方法

③ 外部类访问静态内部类属性和方法

  • 外部类访问静态内部类属性,内部类的属性必须为静态属性。
    外部类访问静态内部类属性

④ 创建静态内部类对象

  • 创建静态内部类对象与使用静态变量、静态方法相类似,不同于成员内部类,我们无需提前创建一个外部类对象,直接创建内部类对象即可:
    创建静态内部类对象

三、分析

  • 为了进一步揭示内部类与外部类的关系,我们将逐一对各种内部类进行编译与反编译:

1、成员内部类

public class OuterClass {
	int b = 4;
	public void love(){
		System.out.println("OuterClass ");
	}

	public class InnerClass{
		int b = 3;                      
		public void love() {
			System.out.println("InnerClass");
		}
		public void ll(){
			OuterClass.this.love();
			love();
		}
	}
}

使用 javac 编译:

javac OuterClass.java

编译后得到的结果为:
编译
此时我们注意到,经过编译后,内部类与外部类分离,并且内部类的命名为外部类名$内部类名.class

使用 javap 对内部类 class文件 进行反编译:

javap OuterClass$InnerClass.class

反编译

  • 从反编译结果看,第一行说明了该类的外部类为OuterClass。
  • 非静态内部类有一个 所在外部类 类型的属性 this$0 ,还有一个参数为外部类类型的构造方法。
  • this$0 是内部类所自动保留的一个指向所在外部类的引用,数字代表当前类外部类的层数。
//OuterClass.java
public class OuterClass {//this$0
	public class FirstInnerClass {//this$1
		public class SecondInnerClass {//this$2
			public class ThirdInnerClass {
			}
		}
	}
}

编译后得到如下结果:
编译
我们对第三层内部类 class 文件进行反编译:
反编译
这说明了,内部类确实是逐层向外指向,即自动保留的一个指向所在外部类的引用,而数字代表当前类所在外部类的层数。

2、局部内部类

为探寻 局部内部类,我们定义如下的demo类:

public class Demo{
	public void run(){
		class A{}
	}
	public void fly(){
		class B{}
	}
}

使用 javac 编译:
编译

  • 它以数字编号区分 不同方法 同名的类,从而生成不同的class文件。

为进一步探究,我们更改 fly 方法中 A类的名称,再次编译:

public class Demo{
	public void run(){
		class A{}
	}
	public void fly(){
		class B{}
	}
}

编译

  • 这验证了我们上面的结论,体现了编号的作用。

同样我们对其中一个内部类 class 文件反编译,得到这样的结果:
反编译

  • 与成员内部类相类似,唯一的不同是,它的构造方法是没有访问权限修饰符的(default),所以它的访问权限仅限于方法或作用域内。

3、匿名内部类

为探寻 匿名内部类,我们定义car接口 与 carTest类:

public interface Car {
	void run();
}
public class CarTest {
	public static void main(String[] args) {
		driveCar(new Car(){
			@Override
			public void run() {
				System.out.println("running......");
			}
		});
	}
	public static void driveCar(Car car) {
		car.run();
	}
}

使用 javac 编译:
编译

  • 我们看到了与局部内部类类似的命名方法,由于它是匿名的,所以它只拥有编号。

使用 javap 对内部类 class 文件反编译:
反编译

  • 反编译的结果证实了它是一个实现接口的匿名类,并且拥有一个没有参数的默认构造方法。
  • 与成员内部类、局部内部类不同的是它并没有 形如 this$x 的final 引用,这也说明了其 “匿名性质”,即外部类无法直接访问到它。

4、静态内部类

  • 同样,我们定义这样的类:
public class Demo {
	public static class StaticDemo{
		int a = 0;
	}
}

使用 javac 编译:
编译
使用 javap 对内部类 class 文件反编译:
反编译

  • 可以看到,静态内部类中仅有一个无参的默认构造方法以及其拥有的属性,并没有保留指向外部类的引用。

四、总结

内部类的优点:

  • 完善了Java多继承机制,由于每一个内部类都可以独立的实现接口、继承类。
  • 无论外部类 是否 继承某个类 或 实现某个接口,对内部类没有影响。
  • 方便写事件驱动程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值