Tuesday 19 August 2008

内嵌接口 内部类 匿名类 内嵌类

内嵌接口与内部类(nesting interface and inner class)
Nesting 接口和内部类 有诸多有趣的特性。尽管我们平时用的不是很多,但在很多时候能够让你的设计更优雅些。
这里我采用了 java思想变成里面的例子,因为该例子简单易懂,毕竟是个牛人写的呵呵(为了演示方便,我修改了一些内容)
下面是一个claass A类
class A
{
interface B
{
void f();
}
public class BImp implements B
{
public void f() {}
}
private class BImp2 implements B
{
public void f() {}
}
public interface C
{
//(7)private interface M{}
void f();
}
class CImp implements C
{
public void f() {}
}
private class CImp2 implements C
{

public void f() {}
}
private interface D
{
//(8)private interface M{}

void f();
}
private class DImp implements D
{
public void f() {}
}
public class DImp2 implements D
{
public void f() {}
}

public D getD()
{
return new DImp2();
}
private D dRef;
public void receiveD(D d)
{
dRef = d;
dRef.f();
}
}

interface E
{
interface G
{
void f();
}
public interface H
{
void f();
}
void g();

}


接着是一个测试类

public class NestingInterfaces {
public class BImp implements A.B {
public void f() {}
}
class CImp implements A.C {
public void f() {}
}
// Cannot implement a private interface except
// within that interface's defining class:
//! class DImp implements A.D {
//! public void f() {}
//! }
class EImp implements E {
public void g() {}
}
class EGImp implements E.G {
public void f() {}
}
class EImp2 implements E {
public void g() {}
class EG implements E.G {
public void f() {}
}
}
public static void main(String[] args) {
A a = new A();
// Can't access A.D:
//(1)A.D ad=a.getD();
//(2) A.DImp2 ad =(A.DImp2) a.getD();
//(3) A.B ab=a.new BImp();
//(4) a.getD().f();
//(5) ((A.Dimp2)a.getD()).f();



// Cannot access a member of the interface:
//! a.getD().f();
// Only another A can do anything with getD():
A a2 = new A();
a2.receiveD(a.getD());
}
} ///:~

在举例之前,我们先看看A类。A类是完全正确的。内嵌类的接口可以有访问修饰符。这和我们平时接触的接口有很大区别,而且修饰符也对其他类的访问有很大的影响。在A内部,大部分访问时不成问题的。但在NestingInterfaces类里面就有许多值得思考的地方了

我们做的主要测试都在第二个类的main 方法里面。我将它们注释掉,并且标上记号,方便讲解。
(1) 会报A.D不可见。原因是D为私有接口。
(2) 这个没有问题。原因在于Dimp2是公有类 如果是Dimp就不行了。另外虽然getD ()方法内部是return new Dimp2();当时返回的仍然是A.D,所以需要类型转换。这个我也没弄明白。
(3) 这句也没问题。
(4) 这个仍然提示A.D不可见。问题同(2)
(5) 这个可以运行
另外需要注意的一个问题是
A类中的(7),(8)都会提示应该声明为public,说明了接口套接口时,内部接口必须为public ,当然你也可以省略修饰词,他会自动为public。


上面的例子是关于内嵌接口的。下面我们再看看内部类有什么特点。
先看第一个演示的类

public class Parcel1 {
class Contents {
private int i = 11;
public int value() { return i; }
}
class Destination {
private String label;
Destination(String whereTo) {
label = whereTo;
}
String readLabel() { return label; }
}
// Using inner classes looks just like
// using any other class, within Parcel1:
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) {
Parcel1 p = new Parcel1();
p.ship("Tanzania");
}
}
正如注释所说的,使用内部类就如同你使用其他一般的类一样,前提条件是你在外部类中使用他们,这里的外部类是Parcel1.
第二个例子:
public class Parcel2 {
class Contents {
private int i = 11;
public int value() { return i; }
}
class Destination {
private String label;
Destination(String whereTo) {
label = whereTo;
}
String readLabel() { return label; }
}
public Destination to(String s) {
return new Destination(s);
}
public Contents cont() {
return new Contents();
}
public void ship(String dest) {
Contents c = cont();
Destination d = to(dest);
System.out.println(d.readLabel());
}
public static void main(String[] args) {
Parcel2 p = new Parcel2();
p.ship("Tanzania");
Parcel2 q = new Parcel2();
// Defining references to inner classes:
Parcel2.Contents c = q.cont();
Parcel2.Destination d = q.to("Borneo");
}
}

现在我们可以看到得出一个结论:
除了在外部类的非静态方法中,如果我们需要实例化某一个内部类,那么实例化的形式是OuterClassName.InnerClassName;
下面仍然是类是于上面的例子,但做了一些变动:

public interface Contents {
int value();
}
public interface Destination {
String readLabel();
}
class Parcel3 {
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 dest(String s) {
return new PDestination(s);
}
public Contents cont() {
return new PContents();
}
}

public class TestParcel {
public static void main(String[] args) {
Parcel3 p = new Parcel3();
Contents c = p.cont();
Destination d = p.dest("Tanzania");
Parcel3.PContents pc = p.new PContents(); //错误,无法实例化
}
}
正如在源代码中最后一句所暗示的,我们可以给内部类添加访问修饰符,我们知道,平常的类我们只能是public 或是包属性。而不能是private或者protected。但是内部类却可以。另外,上面代码和上上面代码还有一个不同点是Content 和Destination 类都实现了各自的接口。从代码封装来看,我们根本无法知道具体的实现类,这也许是内部类另外一个有用只处。

上面的都是一些内部类基础的性质。下面我们谈谈他的高级一点的东西,其中也涉及到了匿名类的一些东西。(PS:有时候我们也别他深究,比如学汉字,只要学会5000个基本的就好了,没必要把康熙字典中的五万个汉字都背下来,所以下面的了解就好)
public class Parcel4 {
public Destination dest(String s) {
class PDestination implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
public String readLabel() { return label; }
}
return new PDestination(s);
}
public static void main(String[] args) {
Parcel4 p = new Parcel4();
Destination d = p.dest("Tanzania");
//(9)Destination d=p.new Destination(“kitty”);
}
}
这一段代码 不知大家看出和上一段的代码之间的区别没。对,Pdestinatio类被放在外部类的一个方法里面。其实,内部类可以放在外部类的任何一个作用域中。
自然由于有作用域的缘故,那就应该注意他的生命周期。比如(9)中的代码就是错误的,应为编译器根本无法解析Destination.

下面我们再看看匿名类的用法与注意事项:
public class Parcel6 {
public Contents cont() {
return new Contents() {
private int i = 11;
public int value() { return i; }
}; //注意这里有个分号
}
public static void main(String[] args) {
Parcel6 p = new Parcel6();
Contents c = p.cont();
}
}

注意到匿名类的语法:第一 new 后面接的是要实现的接口,当然也可以是一个类。第二,后面接你具体实现Contens接口的代码或者类中需要的方法。第三,匿名类的结尾需要分号标志结束。

下面嗨哟一段代码
public class Parcel8 {
// Argument must be final to use inside
// anonymous inner class:
public Destination dest(final String dest) {
return new Destination() {
private String label = dest;
public String readLabel() { return label; }
};
}
public static void main(String[] args) {
Parcel8 p = new Parcel8();
Destination d = p.dest("Tanzania");
}
}
如果在方法dest中传递的形参如果不是final类的话,编译器就会报错。这也是很重要的一点,往往会被人忽略,不过幸运的是编译器也会提示你,你很快就会纠正过来。不过下面就有一个有趣的是奇怪了,让我们来看看---
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) {
{
System.out.println("Inside instance initializer");
}
public void f() {
System.out.println("In anonymous f()");
}
};
}
public static void main(String[] args) {
Base base = getBase(47);
base.f();

}
}
你会惊讶的发现,传递给Base的i 竟然不是final的。很郁闷吧,呵呵,其实你加上final也是没有问题的。但是为什么能够传递一个非final的值,原因很简单,编译器规定,如果这个数十给构造函数的话,并且没有在匿名函数起的地方使用,那么可以不是final的。不过我的建议是为了简单起见,我们不妨加上final。
PS:内部类有几个限制:一,内部类不能再嵌套一个内部类,第二,内部类不允许有静态方法与变量

此外,内部类还有很大的好处,他能够无条件的访问外部类的一切,包括私有变量。这在图形设计里面用的很广泛。比如你的一个button按钮需要实现一个监听器用来实现事件处理,那么你需要一个监听器类,如果你把它当成一个通常的类来用的话,那么监听器类如果需要访问被监听类的成员便只能通过构造函数或者传递一个类实例进去,并且要提供相应的访问它私有成员变量的公共方法。而使用内部类就可以简单的解决这一个问题,我们来看一下一个具体的实例:
package com.allwefantasy.java2d;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.*;

public class Button extends JFrame
{
private JButton jbutton;
private Container container;
private JTextArea jtext;
class buttonLisener implements ActionListener
{

public void actionPerformed(ActionEvent e)
{
if(e.getSource()==jbutton)
{
jtext.setText("awesome,get it");
}

}

}
public Button()
{
container=getContentPane();
jbutton=new JButton("kitty");
jtext=new JTextArea();
jbutton.addActionListener(new buttonLisener());
container.add(jbutton,BorderLayout.SOUTH);
container.add(jtext,BorderLayout.NORTH);
}
public static void main(String[] args)
{
Button button=new Button();
button.setBounds(100, 100,200, 300);
button.setVisible(true);
button.setDefaultCloseOperation(DISPOSE_ON_CLOSE);

button.addWindowListener(new WindowAdapter()
{
public void windowClosed(WindowEvent e)
{
System.exit(0);

}
});
}
}
通过观察我们可以发现,在内部类buttonLisener中,我们设置button类的JtextArea的值的时候我们是直接调用jtext.setText("awesome,get it");并没有传递外部类的任何参数进去。倘若我们将内部类作为一个平常的类定义,那就很麻烦了。另外值得一提的是,如果将内部类当作一个一个内部匿名类的话,将可以简化代码。如上上面的代码可以改成如下的:
jbutton.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
if(e.getSource()==jbutton)
{
jtext.setText("awesome,get it");
}

}
});
同时删除上一个内部类。不过个人不喜欢这样,因为感觉这样的话可读性变差。

写了一个早上了。呵呵本唉想留在后面写的,不过还是一鼓作气的写完比较好。
最好一个议题是:如果我觉得内部类能够访问外部类的一切,这总是让人觉得不爽,那我们怎么办?
给内部类添加一个static 描述符。Thinking in java 的作者将此事的内部类成为nesting class(内嵌类)(PS:不知道这么翻译是否准备去 呵呵)
(PS again 前面我讲的一些限制在内嵌类中并不是限制)

内部类还有很多性质,大家可以多多讨论。对于这篇文章大家如有疑问或者认为哪里有错误可以给我邮件:allwefantasy@gmail.com

No comments: