java炒冷饭系列11 方法和作用域内的内部类 与 匿名内部类

来源:互联网 发布:淘宝雪花代码在线生成 编辑:程序博客网 时间:2024/06/11 18:42

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

到目前为止,读者所看到的只是内部类的典型用途。通常,如要所读、写的代码包含了内部类,那么它们都是“平凡的”内部类,简单并且容易理解。然而,内部类的语法覆盖了大量其他的更加难以理解的技术。例如,可以在一个方法里面或者在任意的作用域内定义内部类。这么做有两个理由:

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

在后面的例子中,先前的代码将被修改,以用来实现:

  1. 一个定义在方法中的类
  2. 一个定义在作用域内的类,此作用域在方法的内部
  3. 一个实现了接口的匿名类
  4. 一个匿名类,它扩展了有非默认构造器的类
  5. 一个匿名类,它执行字段初始化
  6. 一个匿名类,它通过实例初始化实现构造(匿名类不可能有构造器)
    第一个例子展示了在方法的作用域内(而不是在其他类的作用域内)创建一个完整的类。这被称作局部内部类:
public class Parcel5 {    public Destination destination(String s){        class PDestination implements Destination{            private String label;            private PDestination(String whereTo) {                label = whereTo;            }            @Override            public String readLabel() {                return label;            }        }        return new PDestination(s);    }    public static void main(String[] args) {        Parcel5 p = new Parcel5();        Destination d = p.destination("Tasmania");    }}

PDestination类是destination()方法的一部分,而不是Parcel5的一部分。所以,在destination()之外不能访问PDestination。注意出现在return语句中的向上转型--返回的是Destination的引用,它是PDestination的基类。当然,在destination()中定义了内部类PDestination,并不意味着一旦dest()方法执行完毕,PDestination就不可用了。

你可以在同一个子目录下的任意类中对某个内部类使用类标识符PDestination,这并不会有命名冲突。

下面的例子展示了如何在任意的作用域嵌入一个内部类

public class Parcel6 {    private void internalTracking(boolean b) {        if (b) {            class TrackingSlip {                private String id;                public TrackingSlip(String id) {                    this.id = id;                }                String getSlip() {                    return id;                }            }            TrackingSlip ts = new TrackingSlip("Slip");            String slip = ts.getSlip();        }//        TrackingSlip ts = new TrackingSlip();    }    public void track() {        internalTracking(true);    }    public static void main(String[] args) {        Parcel6 p = new Parcel6();        p.track();    }}

TrackingSlip类被嵌入在if语句的作用域内,这并不是说该类的创建是有条件的,它其实与别的类一起编译过了。然而,在定义TrackingSlip的作用域之外,它是不可用的;除此这外,它与普通类一样。

匿名内部类

下面的例子看起来有点奇怪:

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 p = new Parcel7();        Contents c = p.contents();    }}

contents()方法将返回值的生成与表示这个返回值的类的定义结合在一起!另外,这个类是匿名的,它没有名字。更糟的是,看起来似乎是你正好创建一个Contents对象。但是然后(在到达语句结束的分号这前)你却说:”等一等,我想在这里插入一个类的定义。”
这种奇怪的语法指的是:“他建一个继承自Contents的匿名类的对象。”通过new表达式返回的引用被自动向上转型为对Contents的引用。上述匿名内部类的语法是下述形式的简化形式:

public class Parcel7b {    class MyContents implements Contents{        private int i = 11;        @Override        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();    }}

在这个匿名内部类中,使用了默认的构造器来生成Contents。下面的代码展示的是,如果你的基类需要一个有参数的构造器,应该怎么办:

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();          }        };    }    public static void main(String[] args) {        Parcel8 p = new Parcel8();        Wrapping w = p.wrapping(10);        int value = w.value();        System.out.println(value);    }}

只需简单地传递合适的参数给基类的构造器即可,这里是将x传进new Wrapping(x)。尽管Wrapping只是一个具有具体实现的普通类,但它还是被其导出类当作公共“接口”来使用
在匿名内部类末尾的分号,并不是用来标记此内部类结构的。实际上,它标记的是表达式的结束,只不过这个表达式正巧包含了匿名内部类罢了。因此,这与别的地方使用的分号是一致的。

在匿名类中定义字段时,还能够对其执行初始化操作

public class Parcel9 {    public Destination destination(final String dest) {        return new Destination() {            private String label = dest;            @Override            public String readLabel() {                return label;            }        };    }    public static void main(String[] args) {        Parcel9 p = new Parcel9();        Destination d = p.destination("Tasmania");    }}

如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用是final的,就像你在destination()的参数中看到的那样。如果你忘记了,将会得到一个编译时错误消息。

如果只是简单地给一个字段赋值,那么此例中的方法是很好的。但是,如果想做一些类似的构造器的行为,该怎么办呢?在匿名类中不可能有命名构造器(因为它根本没名字!),但是通过实例初始化,就能够达到为匿名内部类创建一个构造器的效果,就像这样

abstract class Base{    public Base(int i) {        System.out.println("Base constructor, i = " + i);    }    public abstract void f();}public class AnonymousConstructor {    public static Base getBase(int i) {        return new Base(i) {            @Override            public void f() {                System.out.println("In anonymous f()");            }        };    }    public static void main(String[] args) {        Base base = getBase(48);        base.f();    }}

在此例中,不要求变量i一定是final的。因为i被传递给匿名类的基类的构造器,它并不会在匿名类内部被直接使用。

下例是带实例初始化的“parcel”形式。注意destination()的参数必须是final的,因为它们是在匿名类内部使用的。

public class Parcel10 {    public Destination destination(final String dest, final float price) {        return new Destination() {            private int cost;            {                cost = Math.round(price);                if (cost > 0) {                    System.out.println("Over budget");                }            }            private String label = dest;            @Override            public String readLabel() {                return label;            }        };    }    public static void main(String[] args) {        Parcel10 p = new Parcel10();        Destination d = p.destination("Tasmania", 101.395F);    }}   

在实例初始化操作的内部,可以看到有一段代码,它们不能作为字段初始化动作的一部分来执行(就是if语句)。所以对于匿名而言,实例初始化的实际效果就是构造器。当然它受到了限制--你不能重载实例初始化方法,所以你仅有一个这样的构造器。
匿名内部类与正规的继承相比有些限制,因为匿名内部类即可以扩展类,也可以实现接口,但是不能两者兼备。而且如果是实现接口,也只能实现一个接口。

参考文献

《Java编程思想》10.5方法和作用域内的内部类
《Java编程思想》10.6匿名内部类