Java编程思想 第十章:内部类

可以将一个类的定义放在另一个类的定义内部,这就是内部类

内部类远不止如此,它就像是一个外围类;并能与之通信;而且你用内部类写出来的代码更加优雅清晰。

1. 创建内部类

将一个类定义在另一个类的内部,这就是内部类。内部类与组合是不同的概念。

如上我们创建了一个内部类,内部类与其它类的区别在于将类隐藏在了另一个类的内部,同时如contents方法所示,外部类的方法还可以返回一个指向内部类的引用,这也是很常见的一种用法。此外我们看到main()方法中创建的内部类对象是使用外部类的引用关联创建的,这一点在下一节中会说到。

public class Parcell {
	class Contents{
		private int i = 11;
		public int value(){
			return i;
		}
	}
	class Destination{
		private String label;
		public Destination(String whereto) {
			// TODO Auto-generated constructor stub
			label = whereto;
		}
		String readLabel(){
			return label;
		}
	}
	public Contents contents(){
		return new Contents();
	}
	public Destination destination(String s){
		return new Destination(s);
	}
	public void ship(String dest){
		Contents c = new Contents();
		Destination d = new Destination(dest);
		System.out.println(d.readLabel());
	}
	public static void main(String[] args) {
		Parcell p1 = new Parcell();
		p1.ship("Inner Class");
		Parcell p2 = new Parcell();
		Contents c = p2.contents();
		Parcell.Destination d = p2.destination("Class Inner");
	}
}

2. 链接到外部类

上边的代码似乎只展示了内部类与其它类名字和组织结构的区别,内部类还有其它的用途。当我们创建了一个内部类对象,此对象就与制造它的外围对象之间有了一种关联,所以它能访问其外围对象的所有成员,而不需要任何特殊条件。此外,内部类还拥有其外围类的所有元素的访问权。

所以内部类自动拥有对其外围类所有成员的访问权限,那么这是如何做到的呢?

当某个外围类的对象创建一个内部类的对象的时候,这个内部类对象必然会秘密捕获一个外围类对象的引用,也就是这个引用来选择外围类的成员。这里所有的细节都交给了编译器来处理。内部类的对象只能在其与外部类的对象相关联的时候才能被创建(在static方法中),构建内部类对象时,需要一个指向其外围类对象的引用,如果编译器访问不到这个引用就会报错,不过绝大多数情况下这种都不需要我们操心。这里也就解释了上一节关于内部类初始化方式不同的原因。意思就是在static方法中,不能通过直接的new 构造函数的形式创建内部类,因为这种形式没有将内部类与外部类对象做关联,要先创建一个外部类的对象,然后使用该对象的引用创建内部类对象。当然如果内部类是静态的,那么就没有这种要求了。

3.使用.this与.new

如果我们需要在内部类生成外部类对象的引用,那么可以使用外部类名.this的形式,这里如果只使用this,则返回的是内部类对象的引用。

public class DoThis {
    void f(){
        System.out.println("This is outClass's method");
    }
    public class Inner{
        public DoThis outer(){
            return DoThis.this;
            //return this;
        }
    }
    public Inner inner(){
        return new Inner();
    }
    public static void main(String[] args) {
        DoThis dt = new DoThis();
        Inner in = dt.inner();
        in.outer().f();
        
        Inner out=dt.new Inner();
        out.outer().f();
    }
}

如前文说到,我们不能使用new直接创建内部类对象,我们需要使用外部类对象的引用创建,这里可以使用外部类对象的引用.new语法进行创建。

4. 内部类与向上转型

当将内部类向上转型为基类时,尤其是转型为接口时,内部类就有了用武之地。这是因为我们可以使内部类也就是接口的实现完全不可见也不可用,得到的只是基类或者接口的引用,从而更好的隐藏了实现的细节。我们先创建两个接口:

public interface Destination{
	String readLabel();
}
public interface Contents {
	int value();
}
class Parcell4{
	private class PContents implements Contents{
		private int i = 11;
		@Override
		public int value() {
			// TODO Auto-generated method stub
			return i;
		}
	}
	protected class PDestination implements Destination{
		private String label;
		private PDestination(String whereto) {
			// TODO Auto-generated constructor stub
			label = whereto;
		}
		@Override
		public String readLabel() {
			// TODO Auto-generated method stub
			return label;
		}
	}
	public Destination destination(String s){
			return new PDestination(s);
		}
	public Contents contents(){
			return new PContents();
		}
}
public class TestParcell {
	public static void main(String[] args) {
		Parcell4 p = new Parcell4();
		Contents c = p.contents();
		Destination d = p.destination("Inner Class");
		System.out.println(d.readLabel());
		System.out.println(c.value());
		//因为PContents是private 所以不能被访问
		//Parcell4.PContents pc = p.new PContents();
	}
}

Parcell4中增加了一些新的东西,首先内部类PContents是private,除了Parcell4没有人能访问它,所以main函数最后一行编译不能通过。其次PDestination是protected的,所以除了该类本身和其子类还有同一个包中的类,其它类不能访问。因此客户端如果想访问这些实现,就受到了限制。不过我们可以看到,main函数的第二、第三行都实现了转型,也就是虽然不可见,但是不影响使用接口的实现。因此private的内部类提供了一种设计思路,通过这种方式完全阻止了依赖任何类型的编码,并且完全隐藏了实现的细节,并且由于不能访问任何新增加的、原本不属于公共接口的方法,因此接口的扩展就是没有价值的了。

5. 在方法和作用域内的内部类

有些时候我们可以将内部类创建在方法的作用域里或者是其它任何地方的作用域中,这么做有两个理由:

  1. 如前所示,实现了某个类型的接口,可以创建并返回接口的引用。
  2. 要解决一个复杂的问题,需要一个类来辅助解决,但是又不希望这个类是公开的。

在方法的作用域内部创建的内部类称为局部内部类:

6. 匿名内部类

public class Parcel7 {
    public Contents contents(){
        return new Contents(){
            private int i = 11;
            @Override
            public int value() {
                return i;
            }
        };
    }
    public static void main(String[] args) {
        Parcel7 p7 = new Parcel7();
        Contents c = p7.contents();
    }
}

在此处,contents方法内部要返回一个Contents对象的时候,我们突然加了一个类的定义,这个类没有名字,它实现了Contents接口,也就是我们实际上创建了一个继承自Contents类的匿名类对象,于是这个return对象的引用就变成了一个来自向上转型的Contents引用。上述这个匿名内部类是下面这种形式的一种简化。

上述代码使用了默认的无参构造器,下边的示例展示当基类的构造器为有参数的构造器时,匿名内部类应当如何创建:

基类:

public class Wrapping {
	private int i;
	public Wrapping(int x){
		i=x;
	}
	public int value(){
		return i;
	}
}

匿名内部类:

public class Parcel8 {
	public Wrapping wrapping(int x){
		return new Wrapping(x){
			public int value(){
				return super.value() * 11;
			}
		};
	}
}

7. 嵌套类(静态内部类)

前边我们说的内部类,都是必须要有外部类关联的,也就是这些内部类有个隐式的引用,指向外部类。如果我们不需要这种关联,那么就可以将内部类显示的声明为static的,这种内部类称为嵌套类。嵌套类意味着:

  1. 要创建嵌套类的对象,并不依赖外部对象
  2. 不能从嵌套类的对象中访问非静态的外围类对象

嵌套类与普通的内部类还有一个区别,普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有static数据和static字段,也不能包含嵌套类,而嵌套类可以包含所有这些东西。

8. 为什么需要内部类

一般来说,内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外围类的对象,所以可以认为内部类提供了一种进入它外围类的窗口。如果我们只是需要一个对接口的引用,那么为何不使用外围类去实现那个接口呢?答案是如果这样能满足需求,那就需要这样做。内部类实现接口与外部类实现接口的区别在于后者不是总能享用到接口带来的方便,有时需要用到接口的实现。所以使用内部类最吸引人的原因:

每个内部类都能够独立的继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的实现),对于内部类都没有影响

内部类有效的实现了“多重继承”,如果在一个类中要使用两个接口,那么使用单一类和内部类看起来没有什么区别(因为单一类可以直接实现多个接口,此处不写例子了),而如果这两个接口换成是抽象类或者是具体的类,那么由于Java不支持多重继承的原因,这里使用单一类显然不能解决问题了,而内部类恰好可以有效的解决这个问题,看似是个“多重继承”。

如果不是要解决类似上边的“多重继承”问题,那么可以不实用内部类,但是使用内部类还可以获得一些其它的特性。

  1. 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外部类的对象信息相互独立。
  2. 在单个外围类中,可以让多个内部类继承或实现多个基类。
  3. 创建内部类的时刻并不依赖于外围对象的创建。(个人不太理解这里,因为前边说了非嵌套类在static方法中使用时,需要关联一个外部对象的引用,不知道这里具体指的是什么。)
  4. 内部类并没有令人迷惑的is-a关系,它是一个独立的实体。

9. 内部类继承

class withInner{
	class Inner{		
	}
}
public class InheritInner extends withInner.Inner{
	InheritInner(withInner wi){
		wi.super();
	}
	public static void main(String[] args) {
		withInner wi = new withInner();
		InheritInner ii = new InheritInner(wi);
	}
}

10 内部类可以被覆盖吗

如果一个类继承了另一个外部类,那么基类中的内部类会发生覆盖吗?

但是实际结果显然不是这样的,它还是走了正常的逻辑流程,说明子类并没有覆盖基类中内部类,这两个内部类彼此独立,在自己的命名空间中。当我们想进行类似“覆盖”内部类的功能时,可以明确的继承内部类,然后覆盖其方法。

11. 局部内部类

定义在方法体中内部类称为局部内部类

12. 内部类标识符

由于每个类都会产生一个.class文件,其中包含如何创建该类型的全部信息。(此信息产生一个“meta-class”,叫做class对象)所以内部类也一定会有个.class文件,它们有规范的命名规则,外围类加上“$”加上内部类的名字。

如果是匿名内部类,编译器会简单的产生一个数字作为标识符,如果内部类是嵌套在别的内部类里,那么就继续使用"$"符号。

Counter.class
LocalInnerclass$1.class
LocalInnerclass$LocalCounter.class
LocalInnerClass.class

13 总结

  1. 内部类是定义在一个类内部的类,这个类可以在方法中,也可以在方法外。内部类可以访问到其外部类的所有域。
  2. 内部类与外部类之间的关联关系是使用一个隐式的外部类引用,所以在创建内部类时,需要先创建一个外部类引用进行关联。这种形式出现在当内部类不是static修饰并且在static方法域中创建内部类对象引用的时候。
  3. 在内部类中,要使用外部类.this才可以返回外部类的对象引用,如果使用this只是返回了内部类的对象引用。当我们创建了一个外部类对象引用时,可以使用引用.new 内部类()的形式创建内部类对象。
  4. 内部类可以用来向上转型实现接口,这种方式有效的建立了接口与实现的隔离,可以使实现完全不可见,不可修改。
  5. 在方法和作用域里的类,有两个作用,一个是如前所示用来实现接口并返回。另一个原因是我想创建一个类辅助我解决问题,但是不想这个类可见。
  6. 使用return new xx(){} 在{}内部定义类的一些域可以创建一个实现或继承xx的匿名类,这个匿名类没有名字,也就没有构造函数。匿名类使用的外部方法引用需要被修饰为final。
  7. 如前所示,内部类的创建需要与外部类进行关联。如果我们不需要进行关联,那么可以将内部类修饰为static,这种称为嵌套类。嵌套类与外部类彼此独立。
  8. 内部类可以实现类似“多重继承”。
  9. 内部类与外部类的引用有关联,所以在继承内部类的时候需要显示的在构造函数中引用外部类的引用,以说明这种关联。
  10. 外部类被继承之后,内部类没有发生特别的变化,也就是它不会被覆盖,如果在子类中重新定义同名的内部类,这会被认为是第二个类,与之前的内部类彼此在不同的命名空间,没有关联。
  11. 局部内部类是定义在方法中的,作用与匿名类相同,但是有构造函数,可以进行构造函数重载。
  12. 所有的类都有标识符,内部类的标识符为外部类名字$内部类名字。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值