第十章 内部类

2013年6月23日 星期日 16时50分56秒

第十章 内部类
可以将一个类的定义放在另一个类的定义内部,这就是内部类。
内部类允许你把一些逻辑相关的类组织在一起,并控制位于内部类的可见性。 内部类与组合不同。
10.1 创建内部类
创建内部类的方式就如你想的一样----把类的定义置于外围类的里面。
package chapter10;
/*@name RandomDoubles.java
* @describe 10.1 创建内部类
* @since 2013-06-23 16:59
* @author 张彪
*/
public class Parcel1 {
class Contents{
private int i=11;
public int value(){return i;}
}
class Desination{
private String label;
Desination(String label){this.label=label;}
String readLabel(){return label;}
}
public void ship(String dest){
Contents c=new Contents();
Desination d=new Desination(dest);
System.out.println(d.readLabel());
}
public static void main(String[] args){
Parcel1 p=new Parcel1();
p.ship("Tasmania");
}
}
//当我们在ship()方法里面使用内部类的时候,与使用不同类没什么区别。在这里,实际的区别只是内部类的名字嵌套在Parel1里面的。更典型的情况是,外部类将有一个方法,方法返回一个指向内部类的引用,就像在to和contents()方法中看到的那样。
package chapter10;
/*@name RandomDoubles.java
* @describe 10.1 创建内部类
* @since 2013-06-23 16:59
* @author 张彪
*/
public class Parcel2 {
class Contents{
private int i=11;
public int value(){return i;}
}
class Desination{
private String label;
Desination(String label){
this.label=label;
}
String readLabel(){
return label;
}
}
public Desination to(String s){
return new Desination(s);
}
public Contents contents(){
return new Contents();
}
public void ship(String dest){
Contents c=new Contents();
Desination d=new Desination(dest);
System.out.println(d.readLabel());
}
public static void main(String[] args){
Parcel2 p=new Parcel2();
p.ship("Tasmania");
Parcel2.Contents c=p.contents();
Parcel2.Desination d= p.to("Boring");
}
}
//如果想在外部类的非静态方法中之外的任何位置创建某个内部类的对象。那么必须在main()方法中那样,具体地指明这个对象的类型。OuterClassName.InnerClassName.

10.2 链接到外部类
到目前为止,内部类似乎还只是一种名字隐藏和组织代码的模式。这些是很有用,但还不是最引人注目的。当生成一个内部类的对象时,此对象与制造它的外围对象之间就有了一种联 系,所以它能访问其外围对象的所有成员,而不需要任何特殊条件。此外,内部类还拥有其他外围类的所有元素的访问权限。(这与C++嵌套类的设计非常不同,在C++中只是单纯的名字隐藏 机制,与外围对象没有联系)
package chapter10;
/*@name Sequence.java
* @describe 10.2 链接到外部类
* @since 2013-06-23 17:48
* @author 张彪
*/
interface Selector{
boolean end();
Object current();
void next();
}
public class Sequence {
private Object[] items;
private int next=0;
public Sequence(int size){
items=new Object[size];
}
public void add(Object x){
if(next <items.length){
items[next++]=x;
}
}
private class SequenceSelector implements Selector{
private int i=0;
public boolean end(){return i==items.length;}
public Object current(){return items[i];}
public void next(){if(i<items.length){i++;}}
}
public Selector selector(){
return new SequenceSelector();
}
public static void main(String[] args){
Sequence s=new Sequence(10);
for(int i=0;i<10;i++){
s.add(Integer.toString(i));
}
Selector selector=s.selector();
while(!selector.end()){
System.out.println(selector.current());
selector.next();
}
}
}

注意:内部类自动拥有对外围类所有成员的访问权限。这是如何做到的呢?当某个外围类的对象创建了一个内部类对象是,此内部类对象必定会秘密地捕获一个指向那个外围类的引用。

10.3 使用.this和.new
10.3.1 .this的用法
如果你需要生成对外边对象的引用,可以使用外边类的名字后面紧跟圆点和this。这样产生的引用自动地具有正确的类型。
package chapter10;
/*@name DotThis.java
* @describe 10.3 使用.this和.new
* @since 2013-06-23 17:48
* @author 张彪
*/
public class DotThis {
void f(){System.out.println("DotThis.f()");}
public class Inner{
public DotThis outer(){
return DotThis.this; //注意:this的使用
}
}
public Inner inner(){
return new Inner();
}
public static void main(String[] args){
DotThis d=new DotThis();
DotThis.Inner n= d.inner();
n.outer().f();
}
}

10.3.2 .new的用法
有时你可能要告知某些其他对象,去创建其某个内部类的对象。要实现此目的,你必须在new表达式中提供对其他外部对象的引用,这是需要使用.new语法。例如:
package chapter10;
/*@name DotNew.java
* @describe 10.3 使用.this和.new
* @since 2013-06-23 18:48
* @author 张彪
*/
public class DotNew {
public class Inner{}
public static void main(String[] args){
DotNew dn=new DotNew();
DotNew.Inner n=dn.new Inner(); //注意.new的用法
//dn.new DotNew.Inner(); 不能这样声明
//Inner r=new Inner(); 也不能这样声明
}
}

要想直接创建内部类的对象,必须使用外部类的对象来创建内部类对象。这样解决了内部类名字作用域的问题。因此你不必声明(实际上你不能声明)dn.new DotNew.Inner()

10.3.4 嵌套类(静态内部类)
在拥有外部类对象之前是不可能创建内部类对象的。这是因为内部类对象会暗暗地链接到创建它的外部类对象上。但是如果你创建的是嵌套类(静态内部类),那么它就不需 要对外部类对象的引用。
package chapter10;
/*@name DotNew.java
* @describe 10.3 嵌套类(静态内部类)
* @since 2013-06-23 19:11
* @author 张彪
*/
public class Pracel3 {
class Contents{
private int i=11;
public int value(){return i;}
}
class Destination{
private String label;
Destination(String whereTo){this.label=whereTo;}
String readLabel(){return label;}
}
public static void main(String[] args){
//Must use instance of outer class to create an instance of the inner class
Pracel3.Contents c=(new Pracel3()).new Contents();
Pracel3.Destination d=(new Pracel3()).new Destination("Tasmania");
}
}

10.4 内部类和向上转型
当将内部类向上转型为其基类,尤其是转型为一个接口的时候,内部类就有了用武之地。(从实现了某个接口的对象,得到对此接口的引用,与向上转型为这个对象的基类,实质上效 果是一样的。)这是因为此内部类--某个接口的实现--能够完全不可见,并且不可用。所得到的只是指向基类或接口的引用,所以能够很方便地隐藏实现的细节。
我们可以创建前一个示例的接口:
package chapter10;
import chapter10.Contents;
import chapter10.Destination;
public class Pracel4 {
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 label){this.label=label;}
public String readLabel(){return label;}
}
public Destination destination(String s){
return new PDestination(s);
}
public Contents contents(){
return new PContents();
}
public static void main(String[] args){
Pracel4 p=new Pracel4();
Destination pd= p.destination("Tasmania");
Contents c=p.contents();
Pracel4.PContents pc= p.new PContents(); //在同一个类就可以这样访问;若在不同类中,则不能访问私有类。
}
}

10.5 在方法和作用域内的内部类
到目前为止,所见的都是“平凡的”内部类,简单而且容易理解。然而,内部类的语法覆盖了大量的其他的更加难以理解的技术。例如。可以在一个方法里面或者在任意作用域内定义 内部类。这么做有两个理由:
1)如前所示,你实现了某类型的接口,于是可以创建并返回对其引用。
2)你要解决一个复杂的问题,想创建一个类用来辅助你的解决方案,但又不希望这个类是公共可用的。
下面的例子,先前的代码将被修改,以用于实现:
1)一个定义在方法中的类
2)一个定义在作用域中的类,此作用域在方法的内部
3)一个实现了接口的匿名类
4)一个匿名类,它扩展了有非默认构造器的类
5)一个匿名类,它执行字段初始化
6)一个匿名类,它通过实例初始化实现构造(匿名类不可能有构造器)

下面这个例子展示了在方法的作用域内(而不是在其他类的作用域内)创建一个完整的类。这称为局部内部类。
package chapter10;
import chapter10.Pracel4.PDestination;
/*@name DotNew.java
* @describe 10.5 在方法的作用域内(而不是在其他类的作用域内)创建一个完整的类。这称为局部内部类。
* @since 2013-06-23 22:49
* @author 张彪
*/
public class Parcel5 {
public Destination destination(String s){
class PDestination implements Destination{
private String label;
private PDestination(String whereTo){this.label=whereTo;}
public String readLabel(){return label;}
}
return new PDestination(s);
}
public static void main(String[] args){
Parcel5 p=new Parcel5();
Destination d= p.destination("Tasmania");
System.out.println(d.readLabel());
}
}

10.5.1 在任意的作用域内嵌入一个内部类
package chapter10;
/*@name DotNew.java
* @describe 10.5 如何在任意的作用域内嵌入一个内部类
* @since 2013-06-23 22:49
* @author 张彪
*/
public class Parcel6 {
private void internalTracking(boolean b){
if(b){
class TrackingSlip{
private String id;
TrackingSlip(String id){this.id=id;}
String getSlip(){return id;}
}
TrackingSlip ts=new TrackingSlip("slip");
String s=ts.getSlip();
}
}
public void track(){internalTracking(true);}
public static void main(String[] args){
Parcel6 p=new Parcel6();
p.track();
}
}

TrackingSlip类被嵌入在if语句的作用域内,这并不是说该类的创建是有条件的,它其实与别的类一起编译过了。

10.6 匿名内部类
看下面的这个例子看起来有点奇怪哦:
package chapter10;
/*@name DotNew.java
* @describe 10.7 匿名内部类
* @since 2013-06-24 9:00
* @author 张彪
*/
public class Parcel7 {
public Contents contents(){
return new Contents(){ //insert a class definition
private int i=11;
public int value(){return i;}
};
}
public static void main(String[] args){
Parcel7 p=new Parcel7();
Contents s =p.contents();
System.out.print(s.value());
}
}

contents()方法将返回值的生成与表示这个返回值的类的定义结合在一起!另外这个类是匿名的,它没有名字,更糟的是,看起来似乎是你正要创建一个Contents对象。但是然后你却说:"等一等,我想在这里插入一个类的定义。"


这种奇怪的语法指的是:“创建一个继承自Contents的匿名类的对象。”通过new表达式返回的引用被自动向上转型为对Contents的引用。
上述匿名内部类的语法是下述形式的简化形式:
package chapter10;
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 s =p.contents();
System.out.print(s.value());
}
}

在匿名内部类中定义字段并执行初始化操作:
package chapter10;
/*@name DotNew.java
* @describe 10.6 匿名内部类中定义字段并执行初始化操作
* @since 2013-06-24 9:00
* @author 张彪
*/
public class Parcel9 {
public Destination destination(final String dest){
return new Destination(){
private String label=dest;
public String readLabel(){return dest;}
};
}
public static void main(String[] args){
Parcel9 p=new Parcel9();
Destination d=p.destination("Timanis");
System.out.println(d.readLabel());
}
}

注意:如果定义了一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译会要求其参数引用的是final的。如上例destination()方法,否则编译器会报错。
package chapter10;
public class Parcel10 {
public Destination destination(final String dest, final float price){
return new Destination(){
private int cost;
{
cost=Math.round(price);
if(cost>100){System.out.println("Over bugget!");}
}
private String label=dest;
public String readLabel(){return label;}
};
}
public static void main(String[] args){
Parcel10 p=new Parcel10();
Destination d=p.destination("Tamanis", 120);
}
}

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

10.6.1 再访工厂方法
看看在使用内部类时,Factory实例变得多么美妙呀:
package chapter10;
/*@name DotNew.java
* @describe 10.6.1 再访工厂方法
* @since 2013-06-24 14:52
* @author 张彪
*/
interface Service{
void method1();
void method2();
}
interface ServiceFactory{
Service getService();
}
class Implementation1 implements Service{
private Implementation1(){}
public void method1(){System.out.println("Implementation1.method1()");}
public void method2(){System.out.println("Implementation1.method2()");}
public static ServiceFactory factory=new ServiceFactory(){
public Service getService(){
return new Implementation1();
}
};
}
class Implementation2 implements Service{
private Implementation2(){}
public void method1(){System.out.println("Implementation2.method1()");}
public void method2(){System.out.println("Implementation2.method2()");}
public static ServiceFactory factory=new ServiceFactory(){
public Service getService(){
return new Implementation2();
}
};
}
public class Factories {
public static void serviceConsumer(ServiceFactory factory){
Service s=factory.getService();
s.method1();
s.method2();
}
public static void main(String[] args){
serviceConsumer(Implementation1.factory);
serviceConsumer(Implementation2.factory);
}
}

现在用于Implementation1和Implementation2的构造器都可以是private的,并且没有必要去创建作为工厂的具体名。另外,你经常只需要单一的工厂对象,因此在本历中它 被创建为Service实现中的一个static域。

10.7 嵌套类
如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为static。这通常称为嵌套类。想要理解static应用于内部类时的含义,就必须记住,普通的内部类对象隐式的保 存了一个引用,指向创建它的外围类对象。然后,当内部类为static时,就不是这样了,
当内部类为static时,嵌套类意味着:
1)要创建嵌套类的对象,并不需要外围类的对象
2)不能从嵌套类的对象中访问非静态的外围类对象
普通内部类和嵌套类的另一个区别为:普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有static数据和static字段,也不能包含嵌套类。但是嵌套类可 以包含所有这些东西。
package chapter10.innerclass;
import chapter10.Contents;
import chapter10.Destination;
public class Parcel11 {
private static class ParcelContents implements Contents{
private int i=11;
public int value(){return i;}
}
protected static class ParcelDestination implements Destination{
private String label;
public ParcelDestination(String label){this.label=label;}
public String readLabel(){return label;}
public static void f(){}
static int x=10;
static class AnotherLevel{
public static void f(){}
static int x=10;
}
}
public static Destination destination(String s){return new ParcelDestination(s);}
public static Contents contents(){return new ParcelContents();}
public static void main(String[] args){
Contents c=contents();
Destination d=destination("Tasmania");
d.readLabel();
}
}

在一个普通(非static)的内部类中,通过一个特殊的this引用可以连接到外围类的对象。嵌套类中没有这个特殊的this引用,这使得它类似于一个static方法。

10.7.1 接口内部的类
正常情况下,不能在内部类中放置任何代码,但嵌套类可以作为接口的一部分。你放到接口中的任何类型都自动地是public和static的。只是将嵌套类置于接口的命名空间内,这并 不违反接口的规则。你甚至可以在内部类中实现其外围接口。如下:
package chapter10.innerclass;
public interface ClassInInterface {
void howdy();
class Test implements ClassInInterface{
public void howdy(){
System.out.println("-------");
}
public static void main(String[] args){
new Test().howdy();
}
}
}

如果你想创建公共代码,使得他们可以被某个接口的所有不同实现所共用,那么使用内部的嵌套类会显得很方便。

10.7.2 从多层嵌套中访问外部类的成员
一个内部类被嵌套多少层并不重要------它能透明地访问所有它所嵌入的外围类的所以成员。 如下所示:
package chapter10.innerclass;
/*@name DotNew.java
* @describe 10.7.2 从多层嵌套中访问外部类的成员
* @since 2013-06-24 16:14
* @author 张彪
*/
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 m=new MNA();
MNA.A mnaa=m.new A(); //使用外部类.new来创建内部类对象
MNA.A.B mnab=mnaa.new B(); //使用外部类.new来创建内部类对象
mnab.h();
}
}

10.8 为什么需要内部类
使用内部类的原因:每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没用影响。

10.5.1 闭包与回调
闭包(closure)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。通过这个定义,可以看出内部类是面向对象的闭包,因为它不仅包含外围类对象的信 息,还自动拥有一个指向此外围类对象的引用,在此作用域内,内部类有权操作所有的成员,包括Private成员。
Java最引人争议的问题之一就是,人们认为Java应该包含某种类似指针的机制。以允许回调。
package chapter10.innerclass;
interface Incrementable{ void increment();}
class Callee1 implements Incrementable{
private int i=0;
public void increment(){
i++;
System.out.println();
}
}
class MyIncrement{
public void increment(){System.out.println("Other operation");}
static void f(MyIncrement mi){mi.increment();}
}
class Callee2 extends MyIncrement{
private int i=0;
public void increment(){
super.increment();
i++;
System.out.println(i);
}
private class Closure implements Incrementable{
public void increment(){
Callee2.this.increment();
}
}
Incrementable getCallbackReference(){
return new Closure();
}
}
class Caller{
private Incrementable CallbackReference;
Caller(Incrementable cbh){this.CallbackReference=cbh;}
void go(){CallbackReference.increment();}
}
public class Callbacks {
public static void main(String[] args){
Callee1 c1=new Callee1();
Callee2 c2=new Callee2();
MyIncrement.f(c2);
Caller call1=new Caller(c1);
Caller call2=new Caller(c2.getCallbackReference());
call1.go();
call2.go();
}
}

上面这个例子展示了外围类实现一个接口与内部类实现接口直接的区别。就代码而言,Callee1是简单的解决方法,Callee2继承MyIncrement类,并用内部类实现了Incrementable接口。


10.5.2 内部类与控制框架
在将要介绍的控制框架中(control framework)中,可以看到更多使用内部类的具体例子。
应用程序框架(application framework)就是被设计用于解决某类特定问题的一个类或一组类。

详见类chapter10\greenhouse\GreenHouseControllers.java

有关命令设计模式见类chapter10\greenhouse\GreenHouseController.java

10.9 内部类的继承
因为内部类的构造器必须连接到指向其外围类对象的引用,所以在继承内部类的时候,事情会变得有点复杂。问题在于,哪个执行外围类对象的“秘密的”引用必须被初始化,而在导出类中 不再存在可连接的默认对象。要解决这个问题,必须使用特殊的语法来说明他们之间的关系。
package chapter10.innerclass;
/*@name DotNew.java
* @describe 10.9 内部类的继承
* @since 2013-06-24 19:23
* @author 张彪
*/
class WithInner{
class Inner{}
}
public class InheritInner extends WithInner.Inner{
InheritInner(WithInner wi){
wi.super();
}
public static void main(String[] args){
WithInner w=new WithInner();
InheritInner h=new InheritInner(w);
}
}
可以 InheritInner类只是基础内部类,而不是外围类。但是当要生成一个构造器时,默认的构造器并不算好,而且不能只是传递一个指向外围类对象的引用 而已。此外,必须在构造器内部使用如下语法:
enclosingClassReference.super();

10.10 内部类可以被覆盖吗
package chapter10.innerclass;
/*@name DotNew.java
* @describe 10.10 内部类可以被覆盖吗
* @since 2013-06-24 19:34
* @author 张彪
*/
class Egg{
private Yolk y;
protected class Yolk{
public Yolk(){System.out.println("Egg.Yolk()");}
}
public Egg(){
System.out.println("New.Yolk()");
y=new Yolk();
}
}
public class BigEgg extends Egg{
public class Yolk{
public Yolk(){System.out.println("BigEgg.Yolk()");}
}
public static void main(String[] args){
BigEgg b=new BigEgg();
Yolk y=b.new Yolk();
}
}
/*New.Yolk()
Egg.Yolk()
BigEgg.Yolk()*/

注意:从输出的结果来看,符合程序的处理思维,个人认为是正常,但是好像书本上将的。。。。。。。有点晕。

package chapter10.innerclass;
/*@name DotNew.java
* @describe 10.10 明确继承某个内部类
* @since 2013-06-24 19:34
* @author 张彪
*/
class Egg2{
protected class Yolk{
public Yolk(){System.out.println("Egg2.Yolk()");}
public void f(){System.out.println("Egg2.Yolk.f()");}
}
private Yolk y=new Yolk();
public Egg2(){
System.out.println("New Egg2()");
}
public void insertYolk(Yolk yy){this.y=yy;}
public void g(){y.f();}
}
public class BigEgg2 extends Egg2{
public class Yolk extends Egg2.Yolk{ //明确地继承某个内部类
public Yolk(){System.out.println("BigEgg2.Yolk()");}
public void f(){System.out.println("BigEgg2.Yolk.f()");}
}
public BigEgg2(){
insertYolk(new Yolk());
}
public static void main(String[] args){
Egg2 e=new BigEgg2();
e.g();
}
}
/*Egg2.Yolk()
New Egg2()
Egg2.Yolk()
BigEgg2.Yolk()
BigEgg2.Yolk.f()*/

P:上面这个例子的输出结果看的有点晕乎。。。。。。。

10.11 局部内部类
可以在代码块中创建内部类,典型的方式是在一个方法体的里面创建。局部内部类不能有访问说明符,因为它不是外部类的一部分;但是它可以访问当前代码块中的常量以及此外围类的所有成员。
package chapter10.innerclass;
/*@name DotNew.java
* @describe 10.11 局部类
* @since 2013-06-24 20:09
* @author 张彪
*/
interface Counter{
int next();
}
public class LocalInnerClass {
private int count=0;
Counter getCounter1(final String name){
//a local inner class
class LocalCounter implements Counter{
public LocalCounter(){
//local inner class can hava a constructor
System.out.print("LocalCounter()");
}
public int next(){
System.out.println(name);
return count++;
}
}
return new LocalCounter();
}
//the same thing with an anonymous(匿名) inner class
Counter getCounter2(final String name){
return new Counter(){
//anonymous inner class cannot hava a named
{
System.out.println("Counter2()");
}
public int next(){
return count++;
}
};
}
public static void main(String[] args){
LocalInnerClass l=new LocalInnerClass();
Counter c1=l.getCounter1("Local inner"),
c2=l.getCounter2("Anonymous inner");
for(int i=0;i<5;i++){
System.out.println(c1.next());
}
}
}

上例中我们分别使用局部内部类和匿名内部类实现了同样的功能,他们具有相同的行为和能力。既然局部内部类的名字在方法外是不可见的,那为什么我们仍然使用局部内部类 而不是匿名内部类呢?唯一的理由是:我们需要一个已命名的构造器,或者需要重载构造器,而匿名内部类只能用于实例初始化。

10.12 内部类标识
内部类的文件也必须是以.class文件格式,而且内部类的文件命名有严格的规则:外围类的名字,加上“$”再加上内部类的名字。例如
LocalInnerClass$1LocalCounter.class
Parcel11$ParcelDestination.class
Parcel11$ParcelDestination$AnotherLevel.class

10.13 总结
随着时间的推移,读者将能够更好的识别在什么情况下使用接口,什么情况下使用内部类,或者两者同时同时,此时你至少应该已经理解了他们的语法和语义。

Good Luck!!!


2013-06-24 20:43 记 @tangxiacun.tianhequ.guanzhou
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值