内部类
引言:
所谓内部类就是定义在另一个类中的类称之为内部类
利用内部类可以将逻辑上存在关联的类组织在一起,而且可以控制一个类在另一个类内的可见性,有一点需要记住,内部类和组合是完全不一样的。
内部类看起来像是一种简单的代码隐藏机制,他将一个类的代码放入到另一个类中,对外界隐藏内部的细节,但是内部类的作用不仅仅只限于此,他可以了解包围他的类,并能与之通信,而且使用内部类编写的代码更加的清晰,优雅。
1.创建内部类
创建内部类的方式就是把类定义放在一个包围它的类之中.
代码示例
public class Inner {
//定义第一个内部类
class One {
private Integer i = 11;
public int value(){
return i;
}
}
//定义第二个内部类
class Two {
private String a;
public Two(String a) {
this.a = a;
}
String readA(){
return a;
}
}
public void read(String c){
One one = new One();
Two two = new Two(c);
System.out.println(two.readA());
}
public static void main(String[] args) {
Inner inner = new Inner();
inner.read("这是通过内部类方法得到的数据");
}
}
输出结果:
这是通过内部类方法得到的数据
在read()方法中使用内部类Two的readA()方法时,看上去和普通类没什么不同。表面上唯一的不同就是这些类的名字都是嵌套在Inner类之中的。
在创建内部类中,更普遍的一种情况是,外部类有一个方法,该方法返回一个指向内部类的引用,以下toOne()和toTwo()方法中看到的一样,
代码示例:
public class Inner {
//定义第一个内部类
class One {
private Integer i = 11;
public int value(){
return i;
}
}
//定义第二个内部类
class Two {
private String a;
public Two(String a) {
this.a = a;
}
String readA(){
return a;
}
}
public Two toTwo(String s){
return new Two(s);
}
public One toOne(){
return new One();
}
public void read(String c){
One one = new One();
Two two = new Two(c);
System.out.println(two.readA());
}
public static void main(String[] args) {
Inner inner = new Inner();
//定义指向内部类的引用
One one = inner.toOne();
Two two = inner.toTwo("这是初始化构造器传递的参数");
System.out.println(two.readA());
}
}
正如代码所示,要在外部类的非静态方法之外的任何地方创建内部类的对象,必须要像在main()中看到的那样,要将对象的类型指定为OuterClassName(外部类名).InnerClasssName(内部类名)。
2.到外部类的链接
当创建一个内部类时,这个内部列的对象中会隐含一个链接,指向用于创建该对象的外围对象。通过该链接,无需任何的特殊条件,内部类独享就可以访问外围对象的成员。此外,内部类拥有对外围对象所有元素的访问权。
interface Selector{
boolean end();
Object current();
void next();
}
public class Nummer {
private Object[] items;
private int next = 0;
//Number类构造器的作用是对类成员数组进行初始化。
public Nummer(int size) {
this.items = new Object[size];
}
//添加数据的方法
public void add(Object o){
if (next < items.length)
items[next++] = o;
}
private class NummberSelector implements Selector {
private int i = 0;
@Override
public boolean end() {
return i == items.length ;
}
@Override
public Object current() {
return items[i];
}
@Override
public void next() {
if (i<items.length)
i++;
}
}
public Selector selector(){
return new NummberSelector();
}
public static void main(String[] args) {
Nummer nummer = new Nummer(10);
for (int i = 0; i < 10; i++){
//将基本类型装箱
nummer.add(Integer.toString(i));
}
Selector selector = nummer.selector();
while (!selector.end()){
System.out.print(selector.current());
selector.next();
}
}
}
输出结果
0123456789
Nummer是以类的形式包装起来的定长Object数组。可以调用add方法向数组的序列末尾添加新的数组(假设还有空间)。要取得Nummer中的每一个对象,可以使用名为Selector的接口,熟悉设计模式的小伙伴很容易认出来这是一个迭代器设计模式的一个例子。通过这个接口,可以检查是否到达数组的末尾,访问当前对象,以及移动指针指向下一个对象。因为Selector是一个接口,所以其他类可以使用自己的方式来实现该接口,而且其他方法也可以以该接口作为该接口为参数,来创建更通用的代码。
这里NummberSelector是一个提供了Selector功能的private类,在main方法中,我们可以看到创建了一个Nummer对象,之后向其中加入了一String对象。然后通过调用selector方法,生成一个Selector对象,他被用于在nummer中移动和选择每一个元素。
乍一看, NummberSelector的创建就和另一个内部类一样,但是仔细研究一下就会发现,在这个类中实现的接口中,重写的三个方法中每一个都用到了外部类中的字段量items,这个引用并不是NummberSelector类的一不跟,而是外部对象的一个Private字段。这是内部类一个优点,就是可以访问外围对象的所有方法和字段,好像拥有他们一样。
因此,内部类可以自动访问外围类的所有成员,那么这是怎么做到的呢?
对于负责创建内部类对象的特定外围对象而言,内部类对象偷偷获取了一个指向它的引用。然后当你在内部类中要访问外部类的成员时,该引用就会被用于选择相应的成员。当然这些都是编译器为你处理的细节,你无需操心。
现在你可以看到,内部类的对象只能与其外围类的对象关联创建(前提是内部类为非static),内部类的对象在构造时,需要一个指向外围类对象的引用,如果编译器无法访问这个引用,他就会报错,不过这种情况大多不需要程序员来干预。
3.使用.this和.new
要生成外部类对象的引用,可以使用外部类的名字,后面加上.this。这样生成的引用会自动具有正确的类型,而且是可以在编译时确定并检查的,所以没有任何运行时开销。下面的示例演示了如何使用.this。
代码示例
public class DoThis {
void f() {
System. out. println("DoThis. f()");
}
public class Inner {
public DoThis outer(){
return DoThis.this;
//如果直接写"this",引用的会是Inner的"this"
}}
public Inner inner(){
return new Inner();
}
public static void main(String[]args){
DoThis dt =new DoThis();
//创建内部类实例
DoThis.Inner dti =dt.inner();
//通过内部类的方法返回一个外部类的对象,在通过此对象调用f方法。
dti.outer().f( );
}
}
输出结果
DoThis.f()
有时我们想让其他某个对象来创建它的某个内部类的对象。要实现这样的功能,可以使用. new语法,在new表达式中提供指向其他外部类对象的引用,就像下面这样:
代码示例
public class DoNew {
public class Ineer{}
public static void main(String[] args) {
//创建外部类实例
DoNew doNew = new DoNew();
//通过外部类实例创建内部类实例
DoNew.Ineer ineer = doNew.new Ineer();
}
}
正如上面看到的一样,我们想要创建一个内部类的实例必须要先有一个外部类的实例,可以理解成内部类的实例创建是依托于外部类创建的。
这是因为内部类的对象会暗中连接到用于创建他的外部类对象。当然,如果你创建的是嵌套类(static修饰的内部类),他就不需要指向外部类的引用,如下所示:
代码示例
public class DoNew {
public static class Ineer{}
public static void main(String[] args) {
//不需要外部类的实例就可以创建实例
DoNew.Ineer ineer = new Ineer();
}
}
4.内部类和向上转型
当需要向上转型为父类,特别是接口时,内部类就更有吸引力了。(从实现某个接口的对象生成一个该接口类型的引用,其效果和向上转型为某个父类在本质上是一样的)这是因为,内部类(接口的实现)对外部而言可以是不可见、不可用的,这便于隐藏实现。外部获得的只是一个指向父类或接口的引用。
现在我们定义两个接口,其中各定义一个方法,有一点需要提醒接口会自动将其成员设置成public:
interface OneIn{
String readA();
}
interface TwoIn{
int value();
}
当得到一个指向接口的引用的时候,你无法找到其确切的类型,如以下示例
代码示例
public class TestIn {
public static void main(String[] args) {
Inner4 inner4 = new Inner4();
TwoIn twoIn = inner4.twoIn();
OneIn oneIn = inner4.oneIn("创建内部类实例");
//不能使用inner4.Two访问private类
}
}
class Inner4{
private class Two implements TwoIn{
private int i = 11;
@Override
public int value() {
return i;
}
}
protected final class One implements OneIn{
private String a;
public One(String a) {
this.a = a;
}
@Override
public String readA() {
return a;
}
}
public OneIn oneIn(String s){
return new One(s);
}
public TwoIn twoIn(){
return new Two();
}
}
interface OneIn{
String readA();
}
interface TwoIn{
int value();
}
如以上示例所示:
在Inner4中,内部类One是private的,所以只有Inner4能访问它。
普通类(非内部类)无法声明为private的或protected的,它们只能被给予public或包访问权限。
Two是protected的,所以它只能被Inner4、相同包中的类(因为protected 也给予了包访问权限),以及Inner4的子类访问。这意味着使用者对这些成员的了解是有限的,对它们的访问权也是有限的。
事实上,你甚至不能向下转型为private的内部类(除非有继承关系,否则也不能向下转型为protecțed的内部类),因为无法访问其名字,就像在TestIn中看到的那样。
private 内部类为类的设计者提供了一种方式,可以完全阻止任何与类型相关的编码依赖,并且可以完全隐藏实现细节。此外,从使用者的角度来看,因为无法访问public 接口之外的任何方法,所以接口的扩展对他们而言并没有什么用处。这也为Java 编译器提供了一个生成更高效代码的机会。
5.在方法中和作用域中的内部类
到目前为止,你所看到的都是内部类的典型用法。一般而言,你会编写或读到的内部类都很简单,并不难理解。不过内部类的语法中还有很多更难理解的技术。
内部类可以在一个方法内或者任何一个作用域内创建。这么做有两个理由:
1.正如先前所演示的,你要实现某种接口,以便创建和返回一个引用;
2.你在解决一个复杂的问题,在自己的解决方案中创建了一个类来辅助,但是你不想让它公开。
在下面的示例中,我们可以修改前面的代码来使用:
1.在方法中定义的类;
2.在方法中的某个作用域内定义的类;
3.实现某个接口的匿名类;
4.这样的匿名类——它继承了拥有非默认构造器的类;
5.执行字段初始化的匿名类;
6.它通过实例初始化来执行构造(匿名内部类不可能有构造器)的匿名类。
第一个示例演示了如何在一个方法的作用域内(而不是在另一个类的作用域内)创建一个完整的类。这叫作局部内部类。
public class TestIn {
public static void main(String[] args) {
Inner4 inner4 = new Inner4();
OneIn oneIn = inner4.creatOne("创建局部内部类实例");
//不能使用inner4.Two访问private类
}
}
class Inner4{
public OneIn creatOne(String s){
//定义在方法内部,是一个局部内部类
final class One implements OneIn{
private String a;
public One(String a) {
this.a = a;
}
@Override
public String readA() {
return a;
}
}
return new One(s);
}
One类是creatOne()的一部分,而不是Inner4的一部分。因此,One在creatOne()外无法访问。在return语句中的向上转型意味着creatOne()中只传出了一个指向OneIn接口的引用。One类的名字被放在了creatOne()中这一事实,并不意味着一旦creatOne()方法返回,得到的One就不是一个合法的对象了。
在同一子目录下的每个类内,你都可以使用类标识符One来命名内部类,而不会产生名字冲突。
接下来看看如何在任何作用域内嵌入一个内部类:
public class Inner5 {
private void internalTracking(boolean b){
if(b) {
class TrackingSlip {
private String id;
TrackingSlip(String s){
id = s;
}
String getId(){return id;}
}
TrackingSlip ts =new TrackingSlip("slip");
String s =ts.getId();
}
//这里不能使用,已经出了作用域:
//-TrackingSlip ts =new TrackingSlip("x");
}
public void track(){internalTracking(true);}
public static void main(String[]args){
Inner5 p =new Inner5();
p. track();
}
}
TrackingSlip类被嵌入了一个if语句的作用域内。这并不是说该类的创建是有条件的,它会和其他所有代码一起编译。然而,它在定义它的作用域之外是不可用的。除此之外,它看上去就像普通的类一样。
6.匿名内部类
先看示例:
代码示例
public class Inner6 {
//TwoIn为之前示例定义的接口
public TwoIn contents(){
return new TwoIn() {//插入类定义,实现接口
private int i =11;
@Override
public int value(){return i;}
};//分号是必需的
}
public static void main(String[]args){
Inner6 p=new Inner6();
TwoIn c =p. contents();
}
}
contents()方法将返回值的创建和用于表示该返回值的类的定义结合了起来。此外,这个类没有名字——它是匿名的。看起来你正在创建一个Contents对象,但是在到达分号之前,你说:“等等,我想插入一个类定义。”
这个奇怪的语法,意思是“创建一个实现TwoIn接口的匿名类的对象”。通过new表达式返回的引用会被自动地向上转型为一个TwoIn引用。匿名内部类语法是以下代码的简略形式:
public class Inner6 {
//TwoIn为之前示例定义的接口
class Two implements TwoIn {
private int i =11;
@Override
public int value(){return i;}
}
public TwoIn contents(){
return new Two();
}
public static void main(String[]args){
Inner6 p=new Inner6();
TwoIn c =p. contents();
}
}
7.嵌套类
如果不需要内部类对象和外部类对象之间的连接,可以将内部类设置为static的。我们通常称之为嵌套类。要理解static应用于内部类时的含义,请记住,普通内部类对象中隐式地保留了一个引用,指向创建该对象的外部类对象。对于static的内部类来说,情况就不是这样了。嵌套类意味着:
1.不需要一个外部类对象来创建嵌套类对象;
2.无法从嵌套类对象内部访问非static的外部类对象。
从另一方面来看,嵌套类和普通内部类还有些不同。普通内部类的字段和方法,只能放在类的外部层次中,所以普通内部类中不能有static数据、static字段,也不能包含嵌套类。但是嵌套类中可以包含所有这些内容:
代码示例:
public class Parcelll {
private static class
ParcelContents implements Contents {
private int i =11;
@Override
public int value(){return i;}
}
protected static final class ParcelDestination implements Destination {
private String label;
private ParcelDestination(String whereTo){
label =whereTo;
}
@Override
public String readLabel(){return label;}
//嵌套类可以包含其他静态元素
public static void f(){}
static int x =1θ;
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");
}
}
在main()中并不需要Parcel11对象;相反,我们使用选择static成员的普通语法来调用方法,这些方法返回指向Contents和Destination类型的引用。
普通内部类(非static的)可以使用特殊的this引用来创建指向外部类对象的连接。而嵌套类没有特殊的this引用,这使它和static方法类似。
8.接口中的类
嵌套类可以是接口的一部分。放到接口中的任何类都会自动成为public和static的。因为类是static的,所以被嵌套的类只是放在了这个接口的命名空间内。甚至可以在内部类内实现包围它的这个接口,就像这样:
interface ClassIn{
void howdy();
class Test implements ClassIn{
@Override
public void howdy() {
System.out.println("这是接口中的内部类!");
}
}
public static void main(String[] args) {
new Test().howdy();
}
}
输出结果
这是接口中的内部类!
当你要创建供某个接口的所有不同实现使用的公用代码时,将一个类嵌入这个接口中会非常方便。
9. 为什么需要内部类
在这里我们已经介绍了很多描述内部类工作方式的语法和语义,但这并没有回答“为什么需要内部类”这个问题。为什么Java的设计者要费尽心思地增加这个基本的语言特性呢?
通常情况下,内部类继承自某个类或实现某个接口,内部类中的代码会操作用以创建该内部类对象的外部类对象。内部类提供了进入其外部类的某种窗口。
内部类的核心问题是,如果只需要一个指向某个接口的引用,那为什么不直接让外部类实现这个接口呢?
答案是“如果你只需要这个,那就这么做”。
那么到底是让内部类来实现接口,还是让外部类来实现同样的接口,区别在哪里呢?答案是,我们并不是总能享受到接口的便捷性,有时还要处理多个实现。因此,之所以要引入内部类,最令人信服的理由如下:
每个内部类都可以独立地继承自一个实现。因此,外部类是否已经继承了某个实现,对内部类并没有限制。
如果没有内部类提供的这种事实上能继承多个具体类或抽象类的能力,有些设计或编程问题会非常棘手。所以从某种角度上讲,内部类完善了多重继承问题的解决方案。接口解决了一部分问题,但内部类实际上支持了“多重实现继承”。
也就是说,内部类实际上支持我们继承多个非接口类型。
为了更详细地了解这一点,请考虑这样一种情况:在一个类内必须以某种形式实现两个接口。由于接口的灵活性,你有两个选择:一个单独的类或一个内部类。
interface A {}
interface B {}
class X implements A,B {}
class Y implements A {
B makeB() {
//匿名内部类:
return new B(){};
}
}
class MultiInterfaces {
static void takesA(A a){}
static void takesB(B b){}
public static void main(String[]args){
X x = new X();
Y y = new Y();
takesA(x) ;
takesA(y);
takesB(x);
takesB(y. makeB());
}
}
这里假定无论哪种方式,我们的代码结构都是有逻辑意义的。你通常会从问题的本质中得到某种启发,知道应该使用单独的类还是内部类。但是如果没有任何其他限制,从实现的角度看,前面示例中的方法并没有太大区别,都可以用。
如果使用的是抽象类或具体类,而不是接口,而且你的类必须以某种方式实现这两者,那就只能使用内部类了:
class D {}
abstract class E {}
class Z extends D {
E makeE() { return new E() {};}
}
class MultiImplementation {
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”关系;它是独立的实体。
举个例子,如果Nummer. java没有使用内部类,因为Nummer实现了Selector接口,因此你会说“一个nummer是一”Selector”,而且对于某个特定的Nummer,你只能使用一种Selector。你可以轻松地使用另一种内部类实现接口,得到一个在序列中从后往前移动的Selector。这种灵活性只在内部类中才有。
10.闭包与回调
闭包(closure)是一个可调用的对象,它保留了来自它被创建时所在的作用域的信息。
从这个定义中,可以看到内部类是面向对象的闭包,因为它不仅包含外围类对象(“它初创建时所在的作用域”)的每一条信息,而且它自动持有着对整个外围类对象的引用。它有权操作外部对象中的所有成员,甚至是private成员。
在Java 8之前,要生成类似闭包的行为,唯一的方法是通过内部类。现在Java 8中有了lambda表达式,它也有闭包行为,但语法更漂亮、更简洁,尽管与内部类闭包相比,你应该首选lambda表达式,但是因为你可能会接触使用了内部类方式的Java 8之前的代码,所以理解它仍然是有必要的。
人们认为Java应该包含某种指针机制,一个最有说服力的论据就是支持回调(callback)。
通过回调,我们可以给其他某个对象提供一段信息,以支持它在之后的某个时间点调用回原始的对象中。这个概念非常强大。然而,如果回调是用指针实现的,我们只能寄希望于程序员操作正确,不要误用指针。正如你所看到的,Java往往会更为谨慎,所以并没有在语言中引入指针。
通过内部类来提供闭包是很好的解决方案,比指针更灵活,也更安全。下面是一个示例:
代码示例
interface Incrementable {
void increment();
}
//实现这个接口
class Calleel implements Incrementable {
private int i =0;
@Override public void increment(){
i++;
System. out. println(i);
}
}
class MyIncrement {
public void increment(){
System. out. println("Other operation");
}
static void f(MyIncrement mi){mi.increment();}
}
//如果我们的类必须以其他某种方式实现increment(),
//则必须使用内部类:
class Callee2 extends MyIncrement {
private int i =0;
@Override
public void increment(){
super. increment();
i++;
System. out. println(i);
}
//内部类实现increment方法,方法体调用外围increment()方法
private class Closure implements Incrementable {
@Override public void increment(){
//需要指定调用外围类方法,否则会无限递归:
Callee2. this. increment();
}
}
Incrementable getCallbackReference(){
return new Closure();
}
}
class Caller {
private Incrementable callbackReference;
Caller(Incrementable cbh){
callbackReference =cbh;
}
void go() {
callbackReference.increment();
}
}
public class Callbacks {
public static void main(String[] args) {
Calleel c1 = new Calleel();
Callee2 c2 = new Callee2();
MyIncrement.f(c2);//调用了Callee2的increment()方法输出Other operation 和 1
Caller callerl = new Caller(c1);
Caller caller2 = new Caller(c2.getCallbackReference());
callerl.go( );//调用了Callee1的increment()方法输出 1
callerl.go( );//调用了Callee1的increment()方法输出 累加为2
caller2.go( ) ;//回调,回到caller2的increment()方法,输出为Other operation 和 2
caller2.go( );//再次执行,输出为Other operation 和 3
}
}
输出结果
Other operation
1
1
2
Other operation
2
Other operation
3
这个示例展示了在外围类中实现接口和在内部类中实现接口的进一步区别。就代码而言,Callee1显然是更简单的解决方案。Callee2继承自MyIncrement,而MyIncrement已经有一个不同increment()方法,它所做的事情并不是Incrementable接口所期望的。当MyIncrement被继承到Callee2中时,increment()不能再为满足Incrementable接口的需要而重写,所以我们只能使用内部类来提供单独的实现。还要注意的是,当创建内部类时,并没有增加或修改外围类的接口。
除了getCallbackReference(),Callee2中的成员都是private的。要想建立与外部世界的任何连接,接口Incrementable都是必不可少的。在这里你可以看到接口是如何支持接口与实现完全分离的。
内部类Closure实现了Incrementable,用来提供一个指回Callee2中的钩子,但这是一个安全的钩子。不管是谁获得这个Incrementable引用,都只能调用increment(),没有其他能力(因此不像指针那样可能会失去控制)。
Caller的构造器接受一个Incrementable引用(不过可以在任何时候捕获回调引用),在之后的某个时刻,使用该引用“回调”进入Callee类中。
回调的价值在于其灵活性——你可以在运行时动态地决定调用哪些方法。例如,在用户界面中,经常到处都是回调,以实现GUI功能。
11.内部类可以重写吗
先在某个外围类中创建一个内部类,然后新创建一个类,使其继承该外围类,并在其中重新定义之前的内部类,这会发生什么呢?
换句话说,是否有可能“重写”整个内部类?
这看上去像一个很强大的概念,但是把内部类当成外围类中的其他方法一样重写,并没有什么实际意义。
代码示例
class Egg {
private Yolk y;
protected class Yolk {
public Yolk() {
System.out.println("Egg. Yolk()");
}
}
Egg() {
System.out.println("New Egg()");
y = new Yolk();
}
}
class BigEgg extends Egg {
public class Yolk {
public Yolk() {
System.out.println("BigEgg. Yolk()");
}
}
public static void main(String[] args) {
new BigEgg();
}
}
输出结果
New Egg()
Egg. Yolk()
无参构造器是由编译器自动合成的,这里调用了基类的无参构造器。你可能会认为,因为创建了一个BigEgg,所以应该使用Yolk的“重写”版本,但事实并非如此,正如你从输出中看到的那样。
11.局部内部类
内部类也可以在代码块内创建,通常是在方法体内。局部内部类不能使用访问权限修饰符,因为它不是外围类的组成部分,但是它可以访问当前代码块中的常量,以及外围类中的所有成员。下面通过一个示例来对比一下局部内部类和匿名内部类的创建:
代码示例
interface Counter {
int next();
}
class LocalInnerClass {
private int count =0;
Counter getCounter(final String name){
//一个局部内部类:
class LocalCounter implements Counter {
LocalCounter(){
//局部内部类可以有一个构造器
System. out. println("LocalCounter()");
}
@Override
public int next() {
System. out. print(name);//访问局部的final变量
return count++;
}
}
return new LocalCounter();
}
//使用匿名内部类实现同样的功能
Counter getCounter2(final String name){
return new Counter() {
//匿名内部类不能有具名的构造器
//只有一个实例初始化部分
{System.out.println("Counter");
}
@Override
public int next() {
System.out.print(name);//访问局部final变量
return count++;
}
};
}
public static void main(String[]args){
LocalInnerClass lic =new LocalInnerClass();Counter
c1 =lic. getCounter("Local inner "),
c2 =lic. getCounter2("Anonymous inner ");
for( int i = 0;i < 5;i++)
System. out. println(c1. next());
for(int i = 0;i < 5;i++)
System. out. println(c2. next());
}
}
输出结果
LocalCounter()
Counter
Local inner 0
Local inner 1
Local inner 2
Local inner 3
Local inner 4
Anonymous inner 5
Anonymous inner 6
Anonymous inner 7
Anonymous inner 8
Anonymous inner 9
Counter返回的是序列中的下一个值。这里采用了两种实现方式,一种是局部内部类(LocalCounter),另一种是匿名内部类(getCounter2()的返回值)。二者的行为和功能是一样的。局部内部类的名字在方法外是无法使用的。局部内部类允许我们定义具名的构造器以及重载版本,而匿名类只能使用实例初始化。
注意,局部内层类允许我们创建该类的多个对象,而匿名内部类通常用于返回该类的一个实例。
12.内部类标识符
在编译完成后,每个类都会生成一个. class文件,其中保存着有关如何创建该类型对象的所有信息。在加载时,每个类文件会产生一个叫作Class对象的元类(meta-class )。
你可能猜到了,内部类也会生成. class文件,以包含其Class对象所需要的信息。这些文件/类的命名遵循一个公式:外围类的名字,加上$,再加上内部类的名字。例如,LocallnnerClass. java创建的. class文件包括:
Counter. class
LocalInnerClass$1. class
LocalInnerClass$1LocalCounter. class
LocalInnerClass. class
如果内部类是匿名的,编译器会以数字作为内部类标识符。如果内部类嵌套在其他内部类之内,它们的名字会被附加到其外围类标识符和$之后。
这种生成内部名称的方案既简单又直接。它也非常稳健,可以处理大多数情况。这是Java的标准命名机制,所以生成的文件自动是平台无关的。(请注意,为了让内部类正常工作,Java编译器会以各种各样的方式修改你的内部类。)
总结
与我们在很多面向对象编程语言中看到的概念相比,接口和内部类更为复杂。例如,C++中就没有这样的概念。将这两者结合起来,就能解决C++尝试用多重继承特性来解决的问题。然而,C++中的多重继承使用起来相当困难,与之相比,Java的接口和内部类用起来更方便。
尽管这些特性本身还算简单,但是就像多态性一样,如何使用这些特性是一个设计问题。随着时间的推移,你会更善于识别在什么情况下应该使用接口或内部类,抑或是两者一起使用。但是此时此刻,你至少应该熟悉其语法和语义。随着在使用中不断见到这些语言特性,你最终会把它们内化在自己的心里。