内部类学习笔记

本文详细解析了Java内部类的概念,包括内部类与外部类之间的通信机制、匿名内部类的使用方式及其限制条件,以及如何利用内部类实现工厂模式等高级特性。此外,还介绍了内部类的优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

将一个类的定义放在另一个类的内部,就是内部类。内部类与组合是完全不同的概念,最初看来内部类仿佛是一种代码隐藏机制,但内部类不仅了解外围类,并能与之通信,借助内部类可以写出更加优雅的代码。


1.内部类与外部类的通信

public class OuterClass {
	
	private  Object[] objects;

	public OuterClass(int size){
		objects=new Object[size];
	}

	public InnerClass getInnerClass(){
		return new InnerClass();
	}

	public static InnerClass innerClass(){
		OuterClass.InnerClass innerClass= new OuterClass(10).new InnerClass();
		return innerClass;
	}

	class InnerClass{
		
		public OuterClass getOuterClass(){
			return OuterClass.this;
		}
		public Object getObject(int i){
			if(i>objects.length){
				return null;
			}
			return objects[i];
		}
	}
}


1.1在内部类的外围类的静态方法或者其他类中创建内部类的实例对象,必须具体的指明这个对象的类型:OuterClass.InnerClass;
1.2内部类自动拥有对其外围类所有成员的访问权:当某个外围类的对象创建了一个内部类对象时,此时内部类对象必定会秘密的捕获一个指向那个外围类对象的引用,用以访问外围类的成员。
1.3需要在其他类创建内部类对象的时候(此内部类是非static修饰),必须通过该内部类的外围类对象使用.new的方式来创建该内部类对象。


2.匿名内部类

2.1匿名内部类竟可以扩展类(实现抽象类),也可以实现接口,但是不能两者兼备,而且如果是实现接口,只能实现一个接口,不能实现多个;

interface InnerClass{
	String getStr();
}

public class OuterClass {

	public InnerClass getInnerClass(final String str){
		return new InnerClass(){
			private String string=str;
			@Override
			public String getStr() {
				return string;
			}
		};
	}

	public static void main(String[] args){
		OuterClass oc=new OuterClass();
		InnerClass ic=oc.getInnerClass("test");
		System.out.println(ic.getStr());
	}
}

2.2如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数是final修饰的;

   其实不仅仅匿名内部类,方法和作用域内的内部类内部使用的外部变量也必须是final修饰的;后面匿名内部类与方法和作用域内的内部类统称局部内部类但是从java8开始,可以不加final修饰符,由系统默认添加。java将这个功能称为:Effectively final 功能

interface InnerClass{
	Integer addXYZ();
}

public class OuterClass {

	public InnerClass getInnerClass(final Integer x){
		final Integer y=100;
		return new InnerClass(){
			private Integer z=100;
			@Override
			public Integer addXYZ() {
				return x+y+z;
			}
			//public void changeY(){y+=1;}
		};
	}
	
	public static void main(String[] args){
		OuterClass oc=new OuterClass();
		InnerClass ic=oc.getInnerClass(100);
		System.out.println(ic.addXYZ());
	}
}

为什么必须用final修饰呢?

问题的关键在于局部变量(方法参数)的生命周期与局部内部类的对象的生命周期的不一致性,而用final修饰就是为了保证局部变量与匿名内部类中取到的变量值的一致性;

从内存角度简单分析:程序运行在JVM中时,方法相当于线程栈的栈帧,方法的局部变量位于该栈帧上,而内部类对象实例位于堆上。当方法结束时,栈帧被弹出,该变量消失。但是,定义在这个类中的内部类对象仍然存活在堆上,所以两者生命周期不同导致了这种不一致性。


那么局部内部类又是怎样实现访问局部变量的呢?

   实际上java是将局部变量作为参数传给了局部内部类的构造函数,而将其作为内部类的成员属性封装在了类中。相当于是做了一份拷贝,所以我们看到的内部类访问局部变量实际上只是访问了自己的成员属性而已。

因此当栈帧中的真正的局部变量死亡时,局部内部类对象仍可以访问局部变量(其实访问的是拷贝之后的成员属性),给人的感觉像是局部变量的"生命期"延长了。

同时为了避免外部方法修改局部变量而导致内部类得到的拷贝与之不一致  或者内部类修改拷贝而导致外部方法的参数值与之不一致。于是就用 final 来让该形参不可改变。保证原始值与拷贝值的一致性(基本数据类型值一致,引用类型引用一致)。

3.匿名内部类实现工场模式


interface Service{
	void method();
}

interface ServiceFactory{
	Service getService();
}

class Implementation1 implements Service{
	@Override
	public void method() {
		System.out.println("Implementation1");
	}
	public static ServiceFactory factory=new ServiceFactory(){
		@Override
		public Service getService() {
			return new Implementation1();
		}
	};
}

class Implementation2 implements Service{
	@Override
	public void method() {
		System.out.println("Implementation2");
	}
	public static ServiceFactory factory=new ServiceFactory(){
		@Override
		public Service getService() {
			return new Implementation2();
		}
	};
}

public class Factories {
	public static void main(String[] args){
		Implementation1.factory.getService().method();
		Implementation2.factory.getService().method();
	}
}

4.嵌套类

简单来说,嵌套类就是static修饰的内部类。普通的内部类对象隐式的保存了一个引用指向创建它的外围类对象,而嵌套类不是这样。

4.1创建嵌套类对象并不需要通过其外围类对象来创建;

4.2嵌套类的对象中不能访问非静态的外围类对象;

4.3可以包含static方法和字段,不能使用外围类的this引用;


5.内部类class文件命名规则

interface InnerClass {
	Integer addXYZ();
}


public class OuterClass {

	public InnerClass getInnerClass(final Integer x) {
		final Integer y = 100;
		return new InnerClass() {
			private Integer z = 100;
			@Override
			public Integer addXYZ() {
				return x + y + z;
			}
		};
	}

	public InnerClass getInnerClass2(final Integer x) {
		final Integer y = 100;
		return new InnerClass() {
			private Integer z = 100;
			@Override
			public Integer addXYZ() {
				return x + y + z;
			}
		};
	}

	class InnerClassTest {
	}

	public void test() {
		class MethodInner {}
	}

	public void test2() {
		class MethodInner {}
	}

	{
		class MethodInner {}
	}

	public static class staticClass{}

	/*public static void main(String[] args) {
		OuterClass oc = new OuterClass();
		InnerClass ic = oc.getInnerClass(100);
		System.out.println(ic.addXYZ());
	}*/
}


5.1嵌套类/内部类的class文件命名是:外围类名+$+内部类名

5.2匿名内部类的class文件命名是:外围类名+$+(1、2、3...)

5.3方法和作用域内的内部类命名:外围类名+$+(1、2、3...)+内部类名


6.内部类优缺点

优点:可以封装隐藏操作代码,精简代码;匿名内部类使用方便;多个内部类实现不同接口,间接实现多继承;

缺点:代码结构重用率低,会生成多个class文件;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值