Java编程思想(十、内部类)
简单来说,/将一个类的定义放在另一个类的定义内部,这就是内部类。内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可视性。
1、创建内部类。把类的定义置于外部类的里面:
public class Parcel{ class Contents{ private int i = 11; public int value(){ return i;} }
public Contents contents{
return new Contents();
}
public static void main(String[] args){
Pracel p = new Pracel();
Pracel.Contents c = p.contents();
}
}
如果想从外部类的非静态方法之外的任意位置创建某个内部类的对象,那么必须像在main()方法中那样,具体地指明这个对象的类型:OuterClassName.InnerClassName。
2、链接到外部类。
当生成一个内部类的对象时,此对象与制造它的外围对象之间就有了以一种联系。所以它能访问其外围对象的所有成员,而不需要任何特殊条件。此外,内部类还拥有其外围类的所有元素的访问权。
这是如何做到的呢?当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用。然后,在你访问此外围类的成员时,就是那个引用来选择外围类的成员。
3、使用 .this 和 .new
如果你需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟原点和this。这样产生的引用自动具有正确的类型,这一点在编译期就被知晓并受到检查,因此没有任何运行时开销。
public class DotThis{ void f(){ System.out.println("DotThis.f()"); } public class Inner{ public DotThis outer{ return DotThis.this; } } public Inner inner(){ return new Inner(); } public static void main(String[] args){ DotThis dt = new DotThis(); DontThis.Inner dti = dt.inner(); dti.outer().f(); } }
但有时你可能想要告知某些其他对象,去创建其某个内部类的对象。要实现此目的,你必须在new表达式中提供对其他外部类对象的引用,这是需要 .new语法。
public class DotNew{ public class Inner{} public static void main(String[] args){ DotNew dn = new DotNew(); DotNew.Inner dni = dn.new Inner(); } }
要想直接创建内部类的对象,你不能按照你想象的方式,去引用外部类的名字DotNew,而是必须使用外部类的对象来创建该内部类对象。而是必须使用外部类的对象来创建该内部类的对象,就像在上面的程序中所看到的那样。这也解决了内部类名字作用域的问题,因此你不必声明(实际上你不能声明) dn.new DotNew.Inner();
在拥有外部类对象之前是不可能创建内部类对象的。这是因为内部类对象会暗暗地连接到创建它的外部类对象上。但是,如果你创建的是嵌套类(静态内部类),那么它就不需要对外部类对象的引用。
4、内部类与向上转型。当将内部类向上转型为其基类,尤其是转型为一个接口的时候,内部类就有了用武之地。(从实现了某个接口的对象,得到对此接口的引用,与向上转型为这个对象的基类,实质上效果是一样的)这是因为此内部类---某个接口的实现---能够完全不可见,并且不可用。所得到的只是指向基类或接口的引用,所以能够很方便地隐藏实现细节。
我们可以创建前一个示例的接口:
public interface Destination{ String readLabel(); } public interface Contents{ int value(); }
现在Contents 和Destination表示客户端程序员可用的接口。当取得了一个指向基类或接口的引用时,甚至可能无法找出它确切的类型。
class Parcel4{ private class PContents implements Contents{ private int i =11; public int value(){ return i; } } protected class PDestination implements Destination{ private String label; private PDestination(String whereTo){ label = whereTo; } public String readLabel(){ return label;} }
public Destination destination(String s){
return new PDestination(s);
}
public Contents contents(){
return new PContents();
}
}
public class TestParcel{
public static void main(String[] args){
Parcel4 p = new Parcel4();
Contents c = p.contents();
Destination d = p.destination("Tamsmania");
}
}
Parcel4中增加了一些新东西:内部类PContents是private,所以除了Parcel4,没人能访问它。PDestination是protected,所以只有Parcel4及其子类、还有与Parcel4同一个包中的类(因为protected也给予了包访问权)能访问PDestination。这意味着,如果客户端程序员想了解或者访问这些成员,那是要受到限制的。实际上,甚至不能向下转型成private内部类(或protected内部类,除非是继承自它的子类),因为不能访问其名字。于是,private内部类给类的设计者提供了一种途径,通过这种方式可以完全阻止任何依赖于类型的编码,并且完全隐藏了实现的细节。此外,从客户端程序员的角度来说,由于不能访问任何新增加的,原本不属于公共接口的方法,所以扩展接口是没有价值的。
5、在方法和作用域内的内部类。可以在一个方法里面或在任意的作用域内定义内部类。这么做有两个理由:1).如前所示,你实现了某类型的接口,于是可以创建并返回对其的引用。2).你要解决一个复杂的问题,想创建一个类来辅助你的解决方案,但是又不希望这个类是公共可用的。
6、匿名内部类。
public class Parcel7{ public Contents contents(){ return new Contents(){ private int i = 11; public int value(){return i;} } } public static void main(String[] args){ Parcel7 p =new Parcel7(); Contents c = p.contents(); } }
contents()方法将返回值的生成与表示这个返回值的类的定义结合在一起。另外,这个类是匿名的,它没有名字。更糟的是,看起来似乎是你正要创建一个Contents对象。但是然后(在到达语句结束的分号之前)你却说:“等一等,我想在这里插入一个类的定义”。这种奇怪的语法指的是:“创建一个继承自Contents的匿名类的对象。”通过new表达式返回的引用被自动向上转型为对Contents的引用。上述匿名内部类的语法是下述形式的简化形式:
public class Parcel7b{ class Mycontents implements Contents{ private int i = 11; public int value() { return i;} } public Contents contents(){ return new MyContents(); } public static void main(String[] args){ Parcel7b p = new Parcel7b(); Contents c = p.contents(); } }
如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数是final的。否则,编译器将会报错。如果只是传递给匿名类的基类的构造器,那么不需要将传入的形参定为final。
改进工厂方法:
interface Service{ void method1(); void method2(); } interface ServiceFactory{ Service getService(); } class Implementation1 implements Service{ private Implementation1(){} public void method1() { System.out.print("Implementation1 method1");} public void method2() { System.out.print("Implementation1 method2");} public static void ServiceFactory factory = new ServiceFactory(){ public Service getService(){ return new Implementation1(); } } } class Implementation2 implements Service{ private Implementation2(){} public void method1() { System.out.print("Implementation2 method1");} public void method2() { System.out.print("Implementation2 method2");} public static void ServiceFactory factory = new ServiceFactory(){ public Service getService(){ return new Implementation2(); } } } public class Factories{ public static void serviceConsumer(ServiceFactory fact){ Service s = fact.getService(); s.method1(); s.method2(); } public static void main(String[] args){ serviceConsumer(Implementation1.factory); serviceConsumer(Implementation2.factory); } }
现在用于Implementation1和Implementation2的构造器都可以private的。并且没有任何必要去创建作为工厂的具名类。另外,你经常只需要单一的工厂对象。因此在本例中它被创建为Service实现中的一个static域。
7、嵌套类。如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为static。这通常被称为嵌套类。想要理解static应用于内部类时的含义,就必须记住,普通的内部类对象隐式地保存了一个引用,指向创建它的外围类对象。然而,当内部类是static的时,就不是这样了。嵌套类意味着:
1)要创建嵌套类的对象,并不需要其外围类的对象。
2)不能从嵌套类的对象中访问非静态的外围类对象。
嵌套类与普通的内部类还有一个区别,普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有static数据和static字段,也不能包含嵌套类。但是嵌套类可以包含所有这些东西。
接口内部的类:正常情况下,不能在接口内部放置任何代码。但嵌套类可以作为接口的一部分。你放到接口中的任何类都自动是public和static的。因为类是static的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。
public interface ClassInInterface{ void howdy(); class Test implements ClassInInterface{ public void howdy(){ System.out.print("Howdy!"); } public static void main(String[] args){ new Test().howdy(); } } }
从多层嵌套类中访问外部类的成员:一个内部类被嵌套多少层并不重要--它能透明地访问所以它所嵌入的外围类的所有成员。
class MNA{ private void f(){} class A{ private void g() {} public class B{ void h(){ g(); f(); } } } } public class MultiNestingAccess{ public static void main(String[] args){ MNA mna = new MNA(); MNA.A mnaa = mna.new A(); MNA.A.B mnaab = mnaa.new B(); mnaab.h(); } }
8、为什么需要内部类。一般来说,内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外围类的对象。所以可以认为内部类提供了某种进入其外围类的窗口。每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
如果没有内部类提供的、可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。从这个角度看,内部类使得多重继承的解决方案变得完整。接口解决了部分问题。而内部类有效地实现了“多重继承”。也就是说,内部类允许继承多个非接口类型(类或抽象类)。
当必须在一个类中以某种方式实现两个接口。由于接口的灵活性,你有两种选择:使用单一类,或者使用内部类。
如果拥有的是抽象的类或具体的类,而不是接口,那就只能使用内部类才能实现多重继承。
如果不需要解决“多重继承”的问题,那么自然可以用别的方式编码,而不需要使用内部类。但如果使用内部类,还可以获得一些其他的特性:
1)、内部类可以有多个实例,每个实例都有自己的状态信息。并且与其外围类对象的信息相互独立。
2)、在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。
3)、创建内部类对象的时刻并不依赖于外围类对象的创建。
4)、内部类并没有令人迷惑的“is-a”关系;它就是一个独立的实体。
闭包与回调:闭包是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。通过这个定义,可以看出内部类是面向对象的闭包,因为它不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥有一个指向此外围类对象的引用,在此作用域内,内部类有权操作所有的成员,包括private成员。
9、内部类的继承。因为内部类的构造器必须连接到指向其外围类对象的引用,所以在继承内部类的时候,事情会变得有点复杂。问题在于,那个指向外围类的“秘密的”引用必须被初始化,而在导出类中不再存在可连接的默认对象。要解决这个问题,必须使用特殊的语法来明确说清它们之间的关联:
class WithInner{ class Inner {} } public classs InheritInner extends WithInner.Inner{ InheritInner(WithInner wi){ wi.super(); } public static void main(String[] args){ WithInner wi = new WithInner(); InheritInner ii = new InheritInner(wi); } }
可以看到,InheritInner只继承自内部类,而不是外围类。但是当要生成一个构造器时,默认的构造器并不算好,而且不能只是传递一个指向外围类对象的引用。此外,必须在构造器内使用如下语法:enclosingClassReference.super();这样才提供了必要的引用,然后程序才能编译通过。
10、内部类可以被覆盖吗?
class Egg{ private Yolk y; protected class Yolk{ public Yolk(){ Systrm.out.print("Egg.Yolk()");} } public Egg(){ System.out.print("New Egg()"); y = new Yolk(); } } public class BigEgg extends Egg{ public class Yolk{ public Yolk(){ Systrm.out.print("BigEgg.Yolk()"); } } public static void main(String[] args){ new Egg(); } }/Output: New Egg() Egg.Yolk()
默认的构造器是编译器自动生成的,这里是调用基类的默认构造器。你可能认为既然创建了BigEgg的对象,那么所使用的应该是“覆盖后”的Yolk版本。但从输出中可以看到实际情况并不是这样。这个例子说明,当继承了某个外围类的时候,内部类并没有发生什么特别神奇的变化。这两个内部类完全是独立的两个实体。各自在自己的命名空间内。当然,明确地继承某个内部类也是可以的:
class Egg2{ protected class Yolk{ public Yolk() { System.out.print("Egg2.Yolk()");} public void f() { System.out.print("Egg2.Yolk.f()");} } private Yolk y = new Yolk(); public Egg2() { System.out.print("New Egg2()"); } public void insertYolk(Yolk yy ){ y=yy;} public void g() { y.f(); } } public class BigEgg2 extends Egg2{ public class Yolk extends Egg2.Yolk{ public Yolk() { System.out.print("BigEgg2.Yolk()");} public void f() { System.out.print("BigEgg2.Yolk.f()");} } public BigEgg2() { insertYolk(new Yolk()); } public static void main(Stringp[] args){ Egg2 e2 = new BigEgg2(); e2.g(); } }/Output: Egg2.Yolk() New Egg2() Egg2.Yolk() BigEgg2.Yolk() BigEgg2.Yolk.f()
11、局部内部类。可以在代码块中创建内部类。典型的方式是在一个方法体的里面创建。局部内部类不能有访问说明符,因为它不是外围类的一部分。但是它可以访问当前代码块内的常量以及此外围类的所有成员。
12、内部类标识符。由于每个类都会产生一个.class文件,其中包含了如何创建该类型的对象的全部信息。(此信息产生一个“meta-class”,叫做Class对象)。内部类也必须生成一个.class文件以包含它们的Class对象信息。这些类文件的命名有严格的规则:外围类的名字,加上"$",再加上内部类的名字。如果内部类是匿名的,编译器会简单地产生一个数字作为标识符。如果内部类是嵌套在别的内部类之中,只需直接将它们的名字加载其外围类标识符与“$”的后面。

浙公网安备 33010602011771号