Java内部类

内部类与组合是完全不同的概念。

组合:

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

  • 24
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

想念你的程客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值