您的位置:首页 > 编程语言 > Java开发

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

2017-06-21 09:53 363 查看

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

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

如前所示,你实现了某类型的接口,于是可以创建并返回对其的引用。

你要解决一个复杂的问题,想创建一个类来辅助你的解决方案,但是又不希望这个类是公共可用的。

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

一个定义在方法中的类

一个定义在作用域内的类,此作用域在方法的内部

一个实现了接口的匿名类

一个匿名类,它扩展了有非默认构造器的类

一个匿名类,它执行字段初始化

一个匿名类,它通过实例初始化实现构造(匿名类不可能有构造器)

第一个例子展示了在方法的作用域内(而不是在其他类的作用域内)创建一个完整的类。这被称作局部内部类:

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);
}

e33c
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匿名内部类
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: