内部类与组合是完全不同的概念。
组合:
public class A{};
public class B{
A a;
};
内部类:
public class B{
class A{};
};
初次看内部类有什么用处,感觉内部类就是把A的实现嵌套在B中,不能单独创建A,仅此而已。
不然,内部类还有其他的用途,当生成一个内部类的对象时,此对象与制造她的外围对象之间就有一种联系,她能访问外围对象的所有成员,比如B中有一个private int i,A也能访问。可能会问这有什么用处呢?组合就做不到啊。那内部如何实现的呢?内部类对象会秘密捕获一个指向外围类对象的引用,细节Java编译器帮助实现。
如果想再内部类中找到这个对外部类的引用,使用B .this即可。
不考虑静态内部类的情况下,如果你想创建一个内部类对象而绕过外部类那是不可能的,而主观想象的方式通过B.A a; 创建内部类对象也是错误的,应该先创建B对象,再使用.new语法。
下面一个话题是内部类和向上转型,thinking in Java里面原话是“内部类--某个接口的实现--能够完全不可见,并且不可用,所得到的只是指向基类或接口的引用,能很方便的隐藏实现细节”。说实话对于我这样的新手这话翻译的真是晦涩,我通过个列子解释一下:
假设有一个普通类A,interface一个接口H,如下
public interface H{ void f(); }
class A implements H{
void f(){//do something;}
}
对于通过使用classA向上转型使用H的人来说,classA是暴露在外的,当我们想隐藏其细节的时候,就使用内部类。
public interface H{ void f(); }
class B {
private class A implements H{
public void f(){//do something;}
//must be public,or will get "Cannot reduce the visibility of the inherited method"
void g(){//do something;}
}
}
这样的话,当我们
public static void main (string[] args) {
B b = new B();
H h = b.new A();
h.f();
//h.g(); !
}
因为A是private的,所以除了B没人能访问她,如果是protected的,那么只有B及其子类,还有B同一个包中的类能访问其他类都不能访问。对于客户端程序员来说,由于不能访问任何新增加的,原本不属于公共接口的方法,比如g(),所以拓展接口是没有价值的。
可以在一个方法里或者任意的作用域内定义内部类。两个理由:
1>如前所示,你实现了某类型的接口,于是可以创建并返回对其的引用
2>要解决一个复杂的问题,想要创建一个类来辅助,但不希望这个类是公共可用的
匿名内部类
//假设A是一个接口或一个基类
public class B{
public A geta() {
return new A() {
private i = 0;
};
}
public static void main(String[] args) {
B b = new B();
A a = b.geta();
}
}
我在创建对象的过程中直接插入了创建的这个对象的类的定义,这样直接创建一个继承自A的匿名类的对象,注意不要以为这个匿名类的名字叫A,A是她继承的父类或接口的名字,她是匿名的。通过new表达式返回的引用被自动向上转型成对A的引用。
如果基类A需要一个参数的构造器实现,只需将参数传入即可new A(x)。
public class B {
public H gethH(final String s) {
return new H() {
private String hp = s;
public String readp() {
return hp;
}
};
}
public static void main(String[] args) {
B b = new B();
String ss = "hjx";
H h = b.gethH(ss);
System.out.println(h.readp());
ss = "xjh";
System.out.println(h.readp());
}
}
如果希望她使用一个在其外部定义的对象,即A(String s)这样书上说编译器会要求这个参数是finial的,如果忘记了,就会得到一个编译器时错误消息,但我经过实操发现并不会,原因应该是编译器自动补充了finial,通过反编译即可了解,将ss进行修改并不会改变hp的值,甚至将return hp改成return s也不会有任何变化,因为自动补充了finial就默认把传入的ss当成了常量。不过如果基类是一个抽象类,直接通过实列初始化就能达到为匿名内部类创建一个构造器的效果,而这时候也不需要finial了,因为s会直接传递给匿名类的基类构造器,她不会在匿名类内部杯直接使用。
总结:匿名内部类与正规的继承相比有些受限,因为匿名内部类既可以拓展类,也可以实现接口,但是不能两者兼得,而且如果是实现接口,也只能实现一个接口。
嵌套类
如果不需要内部对象和外围类对象之间有联系,那么可以将内部类声明为static。前面说过,普通的内部类对对象隐式的保存了一个引用,指向创建她的外围类对象。然而当内部类是static时候,就不是这样了。这意味着:
1>要创建嵌套类的对象,并不需要其外围类的对象。
2>不能从嵌套类的对象中访问非静态的外围类对象。
3>普通内部类的字段和方法只能放在类的外部层次上,所以普通的内部类 不能有static数据和static字段,也不能包含嵌套类,但是嵌套类可以包含这些。这与C++嵌套类相似,只不过C++中那些类不能访问私有成员,而Java中可以访问。
因为类是static的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。你甚至可以在内部类中实现其外围接口,真是“本末倒置”。
public interface ClassInterface {
void f();
class Test implements ClassInterface {
public void f() {
system.out.println("x");
}
public static void main(String[] args) {
new Test().f();
}
}
}
这里书中又给出一个常用的好建议,在每个类中都写一个main()方法,用来测试这个类。这样做有一个缺点,那就是必须带着那些已经编译过的额外代码。如果这对你是个麻烦,那么就可以使用嵌套类来放置测试代码。
所以,我们为什么需要内部类呢?这里我引用书中原话,因为说的很清晰明了。“一般来说,内部类继承自某个类或实现某个接口,内部类的代码操作创建她的外围类的对象,所以可以认为内部类提供了某种进入外围类的接口。内部类必须要回答的一个问题是:如果只是需要一个对接口的引用,为什么不通过外围类实现那个接口呢?答案是:如果这能满足需求,就应该这么做。那么内部类与外围类实现这个接口有什么区别呢》答案是:后者不是总能享用到接口带来的方便,有需要用到接口的实现。所以,使用内部类最吸引人的是:每个内部类都能独立继承一个接口的实现,所以无论外围类是否已经继承了某个接口的实现,对于内部类都没有影响。“
class D{}
abstract class E{}
class Z extends D{
E makeE() {return new E(); }
}
public class Test {
static void takesD(D d) {}
static void takesE(E e) {}
public static void main(String[] args) {
Z z = new Z();
takesD(z);
takesE(z.makeE());
}
}
1>内部类可以有多个实列,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立。
2>在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。
3>创建内部类对象的时刻并不依赖外围类对象的创建
4>内部类并没有令人迷惑的”is-a"关系;她就是一个独立的实体
内部类的继承
class B {
class A{}
}
public class C extends B.A {
//!C() {} //won't compile
C(B b) {
b.super();
}
public static void main(String[] args) {
B b = new B();
C c = new C(b);
}
}
这里C只继承A,但当要生成一个构造器的时候,默认的构造器并不算好,而且不能只是传递一个指向外围类对象的引用。必须在构造器内使用如下语法:
enclosingClassReference.super();
内部类覆盖
class Egg {
private Yolk y;
protected class Yolk{
public Yolk() {
System.out.println("Egg.Yolk()");
}
}
public Egg() {
System.out.println("New Egg()");
y = new Yolk();
}
}
public class BigEgg extends Egg{
private Yolk y;
public class Yolk {
public Yolk() {
System.out.println("BigEgg.Yolk()");
}
}
public BigEgg() {
System.out.println("New BigEgg()");
y = new Yolk();
}
public static void main(String[] args) {
new BigEgg();
}
}
//output:
New Egg()
Egg.Yolk()
New BigEgg()
BigEgg.Yolk()
当继承了某个外围类的时候,内部类并没有发生什么特别神奇的变化,这两个内部类是完全独立的两个实体,在各自的命名空间内。当然也可以写public class Yolk extends Egg.Yolk {...,这样就表示明确继承某个内部类了。
内部类标识符
每个类都会生成一个.class文件,包含如何创建该类型的对象的全部信息,内部类也生成一个.class文件以包含他们的Class对象信息,规则:外围类的名字,加上“$“,在加上内部类的名字,如A$1B.class。如果内部类是匿名的,编译器会简单的生成一个数字作为其标识符。如果内部类是嵌套在别的内部类之中,只需将他们的名字加在其外围类标识符与”$“的后面。虽然这种命名方式格式简单而直接,但她还是很健壮的,足以应对绝大多数情况。因为这是Java的标准命名方式,所以产生的文件自动都是平台无关的。
参考文献:Thinking in Java Bruce Eckel