内部类自救指南

内部类是Java的一个重要组成,参考《Java编程思想》一书后,对内部类进行了一些整理…

初识内部类

内部类是指在类内部定义的类.

内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部类的可视性。

内部类了解外围类,并能与之通信,而且你用内部类写出的代码更加优雅而清晰,尽管并不总是这样。
——摘自《Java编程思想》

内部类与外围类的通信

开始并创建一个内部类

//代码1.0
public class Outer {
	private class Inner {
		private String desc="inner class";
		public void printDesc() {
			system.out.println(desc);
		}
}
public Inner getInner() {
		return new Inner();
	}
	public static void main(String[] args) {
		Outer outer=new Outer();
		Inner inner=outer.getInner();
		inner.printDesc();
	}
}

代码1.0创建了一个简单的内部类Inner,并对它进行了简单的测试,至少可以得出以下几点:

  • 可以直接为内部类中的成员变量进行初始化
  • 通过外部类可以创建内部类的对象
  • 可以直接使用new 创建内部类(尽管不总是这样)

内部类与外围类的通信

普通的内部类拥有外围类的所有元素的访问权限(嵌套类除外,嵌套类后面会单独讨论);

下图通过代码提示框清除的展示了这一点:
内部类对外围类的成员权限

那么内部类如何能访问到外部类的成员,编译器内部帮我们完成了这项艰巨的任务:外围类的对象创建了一个内部类的对象时,内部类对象秘密的包含一个指向外围类对象的引用(编译器帮你做好了这一步);

使用.this与.new

通过.this可以在内部类中获得外部类的引用;
通过.new可以直接创建内部类对象,而之前我们是通过调用方法返回内部类的对象。

代码1.1很好的展示它们的用法与关系(请注意打*的两条语句)

//代码1.1
public class Outer {
	private class Inner {
		public Outer getOuter() {
			return Outer.this;  //(*)
		}
	}
	
	public void doSomething() {
		System.out.println("hello outer class");
	}
	public static void main(String[] args) {
		Outer outer=new Outer();
		Inner inner=outer.new Inner();//(*)
		inner.getOuter().doSomething();
		//输出:hello outer class
	}
}

注意 内部类的创建的前提是必须有一个外部类,因为内部类需要自动创建一个外部类的引用,所有无法简单的使用Inner inner=new Outer.Inner();来创建一个内部类的对象

还有一点要说明:请注意下面的写法1和写法2是有区别的:

public class A{
	class B{ }
	public static void main(String[] args){
		//写法1:
		A a=new A();
		B b1=a.new B();
		
		//写法2:
		B b2=new A().new B();
	}
}

内部类的继承与转型

内部类继承其他类

内部类可以实现接口或者继承父类,也可以向上转型为基类
代码1.2展示这一概念:
(以下代码来自不同的java文件,如果要放入一个java文件,请保证只有一个public类)

//代码1.2
//父接口
public interface BaseOuter {
	void print();
}

public class Outer {
	private class Inner implements BaseOuter {
		@Override
		public void print() {
			System.out.println("implements BaseOuter...");
		}
	}
	public void doSomething() {
		System.out.println("hello outer class");
	}
	public static void main(String[] args) {
		Outer outer = new Outer();
		BaseOuter base=outer.new Inner();
		base.print();
		//输出:implements BaseOuter...
	}
}

继承自内部类

内部类之间的继承关系并不简单,原因在于内部类中存在一个指向外部类引用,这个引用需要在继承关系中被初始化。

要对该引用进行初始化,不能使用默认构造函数,必须传入一个外部类的引用然后调用它的构造方法,这种语法再内部类继承时是唯一的,也是必须的。(代码1.3)

//代码1.3
class WithInner{
	class Inner{}
}
public class Outer extends WithInner.Inner{
	public Outer(WithInner withInner) {
		withInner.super();
	}
}

内部类的分类

普通内部类

以上所讨论的都是普通的内部类。

方法内部类

方法内部类是指定义在方法之中的内部类,作用范围只在方法中。(代码1.4)

//代码1.4
interface FunctionClassBase{
	void doSomething();
}

public class Outer {
	public FunctionClassBase includeInnerClass() {
		class FunctionClass implements FunctionClassBase{
			public void doSomething() {
				System.out.println("function inner class...");
			}
		}
		return new FunctionClass();
	}
	public static void main(String[] args) {
		Outer outer=new Outer();
		outer.includeInnerClass().doSomething();
	}
	//输出:function inner class...
}

如果FunctionClass不继承自类/实现接口,那么将无法得到它的实例,因为它只能在includeInnerClass方法中使用,所以includeInnerClass方法的返回值无法定义为FunctionClass.

作用域内部类

作用域内部类和方法内部类很相似,它的作用域更有局限,只能在某个作用范围中,比如if语句中。

注意:方法内部类和作用域内部类可以理解为局部内部类,它们不能使用访问修饰符,就像方法中的变量无法使用访问修饰符一样。

匿名内部类

匿名内部类值得引起注意,它是使用最多的内部类,至少我遇到的最多。

代码1.5展示了一个复杂且冗余的例子,如果实际开发中都是这样写,我想程序员会崩溃的。

//代码1.5
interface Contents{
	void doSomething();
}
public class Outer {
	class Inner{
		public void setContents(Contents c) {
			c.doSomething();
		}
	}
	class SubContents implements Contents{
		public void doSomething() {
			System.out.println("no name class ...");
		}
	}
	public static void main(String[] args) {
		Outer outer=new Outer();
		Inner inner=outer.new Inner();
		Contents content=outer.new SubContents();
		inner.setContents(content);
		//输出:no name class ...
	}

为了调用setContents方法,我们写了很多冗余的代码,如果使用匿名内部类,将会使上面的例子变得清爽简洁(代码1.6改用匿名内部类,省略了接口定义和内部类定义)

//代码1.6
public class Outer {
	public static void main(String[] args) {
		Outer outer=new Outer();
		Inner inner=outer.new Inner();
		inner.setContents(new Contents() {  //*
			@Override
			public void doSomething() {
				System.out.println("no name class...");
			}
		});
		//输出:no name class ...
	}
}

带*号的位置开始的一段代码被当作一个Contents对象传入方法,这就是匿名内部类,由于它没有名字,所以使用接口类型代替(由于存在向上转型),接口必须实现方法,因此后面的大括号内重写了doSomething方法,这是匿名内部类的基本定义方法。如果不太理解请仔细对比代码1.5和代码1.6.

匿名内部类的应用非常广泛,比如Android中大量使用了匿名内部类,代码1.7展示了Android中的单击事件如何使用匿名内部类:

无需关系代码的含义,只需要注意setOnClickListener方法中参数是如何使用匿名内部类的。

//代码1.7
confirmChangePsw=findViewById(R.id.confirm_change_psw);
confirmChangePsw.setOnClickListener(new View.OnClickListener() {
    @Override
public void onClick(View v) {             
if(!newPsw.getText().toString().equals(checkNewPsw.getText().toString())){
            Toast.makeText(ChangePswActivity.this,"修改失败",Toast.LENGTH_SHORT).show();
            return;
        }
        String sql="update user set password=? where u_id="+uid;
        noteDatabaseHelper.getReadableDatabase().execSQL(sql,new String[]{newPsw.getText().toString()});
        Toast.makeText(ChangePswActivity.this,"修改成功",Toast.LENGTH_SHORT).show();
    }
});

关于匿名内部类还有两点需要说明:

如果你有一个匿名内部类,它要使用一个宰他外部定义的对象,编译器会要求其参数引用是final的;如果你忘记了,会得到一个编辑期错误信息。

匿名内部类与正规的继承相比有些受限,因为匿名内部类既可以扩展类也可以实现接口,但是不能两者兼备。而且如果是实现接口,也只能实现一个接口。
——摘自《Java编程思想》

嵌套类

嵌套类是指static修饰的类,它不会与外围类建立联系,所以也不能访问外围类的非静态成员,创建嵌套类也不需要依赖于外围类,这是与之前的普通内部类最大的区别。

嵌套类的另一个特点在于:在嵌套类内部允许包含static成员属性/方法,并且可以再包含嵌套类。嵌套类甚至可以定义在接口中。普通内部类无法做到。
之前提到,普通内部类无法使用new OuterClassName.InnerClassName()创建,而对于嵌套类这样是可以的,可以理解为一个静态方法的调用。(代码1.8)

//代码1.8
public class Outer {
	static class Inner{
		private static String desc="static inner class";
		public static void showDesc() {
			System.out.println(desc);
		}
	}
	public static void main(String[] args) {
		Outer.Inner inner=new Outer.Inner();
		inner.showDesc();
	}
}

代码1.8还可以再简化一点,由于是静态方法与嵌套类,不需要任何对象也可以直接调用。(代码1.9)

//代码1.9
public class Outer {
	static class Inner{
		private static String desc="static inner class";
		public static void showDesc() {
			System.out.println(desc);
		}
	}
	public static void main(String[] args) {
		Outer.Inner.showDesc();
	}
}

内部类标识符

Java中,每一个类都会产生一个class文件,内部类也不例外,普通类的class文件名即为类名,内部类略有不同,它的命名为OuterClassName$InnerClassName.class

例如代码1.10的简单结构编译后会产生如下两个文件:

  • Outer.class
  • Outer$Inner.class
//代码1.10
public class Outer{
	
	class Inner{

	}

	public static void main(String[] args) {
	}
}

匿名内部类则会以一个数字代替内部类的名字。

为什么选择内部类

使用内部类实现多重继承

使用内部类可以实现多重继承,如果是实现多个接口,你可以选择直接使用外围类实现多个接口;如果面对多个非接口(类/抽象类)要继承,那么一个外围类是无法做到的(Java只允许单继承),这时可以使用内部类完成,使用匿名内部类是常见的。

代码1.11定义了两个抽象类,如果要同时再一个类中重写这两个抽象类的方法,使用匿名内部类(见代码1.12)

//代码1.11
abstract class A{
	public void doA() {}
}
abstract class B{
	public void doB() {}
}
//代码1.12
public class Outer extends A{
	@Override
	public void doA() {
		super.doA();
		System.out.println("extends A");
	}
	public void doB(B b) {
		b.doB();
	}
	public static void main(String[] args) {
		Outer outer=new Outer();
		outer.doA();
		outer.doB(new B() {
			@Override
			public void doB() {
				super.doB();
				System.out.println("extends B");
			}
		});
	}
}

《Java编程思想》中对使用内部类的好处做了如下说明:

  1. 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立
  2. 单个外围类中,你可以让多个内部类以不同的方式实现同一个接口/继承同一个类
  3. 创建内部类对象的时刻并不依赖于外部类对象的创建
  4. 内部类没有令人迷惑的“is-a”关系:它就是一个独立的实体
  5. 内部类可以更加完美的实现回调机制

个人博客网站:http://smartpig612.club
微信公众号:SmartPig

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值