【Java】内部类

摘要

java内部类可分为下面四种:
* 成员内部类
* 静态嵌套类
* 方法内部类
* 匿名内部类
为什么使用匿名类?内部类也会想普通类生成一个对应的class文件?带着这些问题往下看。

成员内部类

java中内部类可以访问外围类的成员,并且可以有多个内部类实例,多个内部类之间共享一个外围类实例。每当创建一个内部类对象时,内部类会保存一份指向外围类的引用,在内部类里面通过该引用访问外围类,这些编译器都已经帮我们做好了,所以在内部类可以直接访问外围类成员。

打个比方,将外围类比作一台计算机,其内存条可以看作是一个内部类,因为内存条是独立单位,一台计算机可以有多个内存条,每个内存条都可以使用外围类(计算机)的其他资源如CPU、磁盘等。下面代码展示了成员内部类的用法:

class Computer{
    private String disk;

    class Memeory{
        public Memeory(){
        }

        public void writeDisk(String s){
            disk = s;
        }
    }

    public Computer(String d){
        disk = d;
    }

    public Memeory createMemeory(){
        return new Memeory();
    }

    public void printDiskContent(){
        System.out.println(disk);
    }
}

public class Test{
    public static void main(String [] args){
        Computer c = new Computer("Test0");
        c.printDiskContent();

        Computer.Memeory m1 = c.createMemeory();
        m1.writeDisk("Test1");
        c.printDiskContent();

        Computer.Memeory m2 = c.new Memeory();
        m2.writeDisk("Test2");
        c.printDiskContent();
    }
}

输出结果:

Test0
Test1
Test2

Computer类有一个名为Memory的内部类,在main函数中,首先创建了一个Computer类的实例,然后打印其磁盘内容。接着先后两次调用Computer实例的createMemeory()方法创建内部类Memeory类的实例,有意思的是第二个Memory实例调用writeDisk()方法 覆盖了外围类中的”磁盘”内容 ,这一点说明内部类的不同实例之间是共享外围类成员的。

这里使用了两次方式来创建内部类,一种是在外围类中增加一个创建内部类的方法,另一种是通过外围类实例后面加.new的方式直接创建内部类,由于已经有外围类实例了编译器知道去哪里找要创建的内部类,所以不需要使用c.new Computer.Memeory()的方式来限定了(这样写编译不过)。

总结:

对于非静态的内部类,必须通过外围类的实例来创建内部类,并且可以创建多个内部类实例,这些内部类共享外围类实例的成员。

成员内部类的典型用途是用内部类来实现接口,看上去也可以直接使用外部类实现接口,但是使用内部类实现接口也有一些好处,例如在一个类中使用不同的内部类可以实现多个不同接口,也可以针对同一接口提供不同的内部类实现,而且这些内部类实现可以很好的隐藏起来。例如下面代码

interface Selector{
    int getValue();
    void next();
    boolean isEnd();
}

class Sequence{
    private int[] items;
    private int index;

    public Sequence(int size){
        items = new int[size];
        index = 0;
    }

    public void add(int item){
        if (index < items.length){
            items[index++] = item;
        }
    }

    // 内部类
    class SequenceSelector implements Selector{
        private int curIndex = 0;
        public int getValue() { return items[curIndex]; }
        public void next() { if (curIndex < items.length)  ++curIndex; }
        public boolean isEnd() { return curIndex == items.length; }
    }

    public Selector getSelector(){
        return new SequenceSelector();
    }
}


public class Test{
    public static void main(String [] args){
        Sequence seq = new Sequence(10);
        for (int i = 0; i < 10; ++i){
            seq.add(i);
        }

        Selector selector = seq.getSelector();
        while (!selector.isEnd()){
            System.out.println(selector.getValue());
            selector.next();
        }
    }
}

输出结果:

0
1
2
3
4
5
6
7
8
9

Sequence是一个序列类,其内部类SequenceSelector实现了Selector接口,通过getSelector()可以获取Selector接口的一个实现版本,你也可以很方便的添加内部类提供其他版本的Selector接口实现。

静态嵌套类

class OuterClass{
    static class InnerClass{
    }
}

public class Test{
    public static void main(String [] args){
        OuterClass out = new OuterClass();
        // InnerClass in = new InnerClass(); // error
        OuterClass.InnerClass in = new OuterClass.InnerClass();
    }
}

只要将内部类声明为static,那么该内部类的创建就不需要使用外围类实例了,可以直接通过new OuterClass.InnerClass()方式创建,这种情况下内部类只能访问外部类中的静态成员。

静态嵌套类与C++中的嵌套类很相似,嵌套类提供了一个类的定义,只是类定义的位置是在另一个类的内部。

方法内部类

方法内部类和普通内部类唯一区别在于其作用域不同,下面例子中,在类Stuff的方法中定义了一个内部类Triangle,其实现了接口ShapeTriangle仅在方法getShape()中可见。

interface Shape {
    void area(); 
}

class Stuff {

    public Shape getShape() {
        class Triangle implements Shape {
            public void area(){
                System.out.println("Triangle.area()");
            }
        }

        return new Triangle();
    }
}
public class Test{
    public static void main(String [] args){
        Stuff stuff = new Stuff();
        stuff.getShape().area();
    }
}

输出结果:

Triangle.area()

匿名内部类

匿名内部类顾名思义是没有名称的类。下面一个匿名内部类的例子,还是使用上面的代码稍加修改:


interface Shape {
    void area(); 
}

class Stuff {
    public Shape getShape() {
        // class Triangle implements Shape {
        //  public void area(){
        //      System.out.println("Triangle.area()");
        //  }
        // }

        // return new Triangle();

        return new Shape() {                            // 在这里定义
            public void area() {        
                System.out.println("Triangle.area()");   
            }
        };
    }

    public void test(){
    }
}

public class Test{
    public static void main(String [] args){
        Stuff stuff = new Stuff();
        stuff.getShape().area();
    }
}

输出结果:

Triangle.area()

getShape()方法中返回了一个”实现了接口Shape的匿名类”,在该匿名类创建的同时进行了定义,末尾的;是必须的,他表示return语句的结束。该匿名类和注释掉的代码是等价的,不过你不用再挠头皮去想给它起个什么名字了。

下面是一个含有带参构造函数的匿名类例子:

class Shape {
    private int size;
    public Shape(int x) {
        size = x;
    }

    public int getSize() {
        System.out.println("Shape.getSize()");
        return size;
    }

    public void printSize() {
        System.out.println(size);
    }
}

class Stuff {
    public Shape getShape(int x) {
        return new Shape(x) {

            { System.out.println("initialize"); }

            @Override
            public int getSize() {
                return super.getSize() * 10;
            }

            @Override
            public void printSize() {
                System.out.println(getSize());
            }
        };
    }
}

public class Test{
    public static void main(String [] args){
        Stuff stuff = new Stuff();
        stuff.getShape(99).printSize();
    }
}

输出结果:

initialize
Shape.getSize()
990

getShape()类中创建并返回了一个”继承自基类Shape的匿名类”,该匿名类的输入参数传递给基类带参构造函数完成基类的初始化,然后进入匿名类的定义部分,首先执行匿名类的代码块,接着是两个重写了基类相同签名的方法。

注意:在匿名类内部中使用外围类变量时,改外围类变量必须是final修饰的,否则编译器会提示错误。上面例子中虽然使用了外围变量,但是没有final修饰,这是因为变量传递给了匿名类的构造函数,不在匿名内部类内。

为什么使用匿名类?

每个内部类都能独立继承自一个(接口)实现,所以无论外围类是否已经继承了某个(接口)实现,对于内部类都没有影响。内部类为实现”多重继承”提供了解决方案,可以通过多个内部类独立继承自不同基类来实现。

使用外围类可以获得一些其他特性:

  • 内部类可以有多个实例,每个实例都有自己的状态信息,并且与外围类对象相互独立
  • 在单个外围类中,可以让多个内部类以不同方式实现同一个接口,或继承同一个类。
  • 创建内部类对象的时刻不依赖外部类对象
  • 内部类没有令人迷惑的”is-a”关系,它是一个独立个体

内部类标识符

java中每个类编译后都会产生一个class文件,其中包含了创建该类型对象的全部信息,同样的,内部类也必须生成一个class文件以包含创建它们的信息。

内部类生成的class文件有严格的命名规则:外围类名称 + "$" + 内部类名称。内部类如果是一个匿名类,编译器会简单的生成一个数字作为匿名内部类的名称。

例如 下面的Outer.java编译后会生成两个class文件:Outer.classOuter$Inner.class

public class Outer {
    public class Inner {

    }
    public static void main (String [] args) {
        return ;
    }
}

参考

[1] Java 编程思想

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值