Java-内部类

将一个类的定义放在另一个类的定义内部,这就是内部类

1. 创建内部类

创建内部类的方式:把类的定义置于外围类的里面

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;
        }
    }

    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("Tasmania");
    }
}

更典型的情况是,外部类将有一个方法,该方法返回一个指向内部类的引用

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 contents(){
        return new Contents();
    }
    
    public void ship(String dest){
        Contents c = contents();
        Destination d = to(dest);
        System.out.println(d.readLabel());
    }

    public static void main(String[] args) {
        Parcel2 p = new Parcel2();
        p.ship("Tasmania");
        Parcel2 q = new Parcel2();
        Parcel2.Contents c = q.contents();
        Parcel2.Destination d = q.to("Borneo");
    }
}

如果想从外部类的非静态方法之外的任意位置创建某个内部类的对象(在静态方法内部创建某个内部类的对象),那么必须像在main()方法中那样,具体地指明这个对象的类型:OuterClassName.InnerClassName

2. 链接到外部类

当生成一个内部类的对象时,此对象与它的外部类对象之间产生了一种练习,所以它能访问其外围对象的所有成员,而不需要任何特殊条件

interface Selector{
    boolean end();
    Object current();
    void next();
}
public class Sequence {
    private Object[] items;
    private int next = 0;
    public Sequence(int size) {
        items = new Object[size];
    }
    public void add(Object x){
        if(next < items.length){
            items[next++] = x;
        }
    }
    private class SequenceSelector implements Selector{
        private int i = 0;
        public boolean end(){
            return i == items.length;
        }
        public Object current(){
            return items[i];
        }

        public void next(){
            if(i < items.length){
                i++;
            }
        }
    }

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

    public static void main(String[] args) {
        Sequence sequence = new Sequence(10);
        for(int i = 0; i <10;i++){
            sequence.add(Integer.toString(i));
        }
        Selector selector = sequence.selector();
        while (!selector.end()){
            System.out.println(selector.current() + " ");
            selector.next();
        }
    }
}

Sequence类只是一个固定大小的Object数组,以类的形式包装了起来。可以调用add()在序列末增加新的Object(只要还有空间)。要获取Sequence中的每一个对象,可以使用Selector接口。这是“迭代器”设计模式的一个例子。

Selector允许你检查序列是否到末尾了end(),访问当前对象current(),以及移到序列中的下一个对象next()。因为Selector是一个接口,所以别的类可以按他们自己的方式来实现这个接口,并且别的方法能以此接口为参数,来生成更加通用的代码。

SequenceSelector类中的方法end()、current()、next()都用到了objects,这是一个引用,它不是SequenceSelector的一部分,而是外围类中的private字段。然而内部类访问外部类的方法和字段,就像自己拥有他们一样。

所以内部类自动拥有对其外部类所有成员的访问权。这是如何做到的呢?当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密的捕获一个指向那个外围类对象的引用。然后,在你访问此外围类的成员时,就是用那个引用来选择外围类的成员。

还要注意的是:内部类的对象只能在其与外部类的对象相关联的情况下才能创建(在内部类是非static类时)

3. 使用.this与.new

如果生成对外部类对象的引用,可以使用外部类的名字后面紧跟着圆点和this。

public class DotThis {
    
    void f(){
        System.out.println("DotThis.f()");
    }
    
    public class Inner{
        public DotThis outer(){
            return DotThis.this;
        }
    }
    
    public Inner inner(){
        return new Inner();
    }

    public static void main(String[] args) {
        DotThis dt = new DotThis();
        DotThis.Inner dti = dt.inner();
        dti.outer().f();
    }
}

通过外部类对象创建其内部类的对象。必须在new表达式中提供对其外部类对象的引用。需要使用.new语法:

public class DotNew {
    public class Inner{}
    public static void main(String[] args){
        DotNew dn = new DotNew();
        DotNew.Inner dni = dn.new Inner();
    }
}

但如果创建的是静态内部类,那么就不需要对外部类对象的引用

4. 内部类与向上转型

内部类是某个接口的实现,这个实现能够完全不可见,并且不可用。使用时只得到指向父类或接口的引用,所以能够很方便的隐藏实现细节

public interface Destination { // 表示对外可用的接口,其实现在内部类
    String readLabel();
}
public interface Contents {  // 表示对外可用的接口,其实现在内部类
    int value();
}
class Parcel4{
    private class PContents implements Contents{ // 内部类实现接口Contents
        private int i = 11;
        public int value(){
            return i;
        }
    }

    protected class PDestination implements Destination{ // 内部类实现接口Destination
        private String label;
        private PDestination(String whereTo){
            label = whereTo;
        }
        public String readLabel(){
            return label;
        }
    }

    public Destination destination(String s){
        return new PDestination(s);
    }

    public Contents contents(){
        return new PContents();
    }
}
public class TestParcel {
    public static void main(String[] args) {
        Parcel4 p = new Parcel4();
        Contents c = p.contents(); // 创建内部类PContents的对象,指向接口的引用Contents
        Destination d = p.destination("Tasmania");  // 创建内部类PDestination的对象,指向接口的引用Destination
        // Parcel4.PContents pc = p.new PContens(); // 不能直接创建内部类因为,PContents是私有的
    }
}

5. 在方法和作用域内的内部类

可以在一个方法里面或者任意的作用域内定义内部类。这样做的原因是: 1)实现某类型的接口,于是创建并返回对其的引用 2)要解决一个复杂问题,通过创建一个类来辅助解决,但是又不希望这个类是公共可用的

除了在类中定义内部类,还可以实现: 1)定义在方法中的类 2)定义在作用域内的类,此作用域在方法的内部 3)实现了接口的匿名类 4)匿名类,扩展了非默认构造器的类 5)匿名类,执行字段初始化 6)匿名类,通过实例初始化实现构造(匿名类不可能有构造器)

第一例子展示定义在方法中的类,这被称作局部内部类

public interface Destination {
    String readLabel();
}
public class Parcel5 {
    public Destination destination(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) {
        Parcel5 p = new Parcel5();
        Destination d = p.destination("Tasmania");
    }
}

PDestination类是destination()方法的一部分,而不是Parcel5的一部分。所以,在destination()之外不能访问PDestination。注意出现在return语句中的向上转型--返回的是Destination的引用,它是PDestination的父类。虽然在destination()方法中定义了内部类PDestination,但并不意味着一旦destination()方法执行完毕,PDestination就不可用了

6. 匿名内部类

public interface Contents {
    int value();
}
public class Parcel7 {
    public Contents contents(){
        return new Contents() {
            private int i = 11;
            @Override
            public int value() {
                return i;
            }
        };
    }

    public static void main(String[] args) {
        Parcel7 p = new Parcel7();
        Contents c = p.contents();
    }
}

contents()方法将返回值的生成与表示这个返回值的类的定义结合在一起。并且这个类匿名的,没有名字。

在上面的匿名内部类中,使用了默认的构造器来生成Contents,下面的代码展示的是,如果父类需要一个有参数的构造器,应该怎么办?

public class Parcel8 {
    public Wrapping wrapping(int x){
        return new Wrapping(x){
            public int value(){
                return super.value() * 47;
            }
        };
    }
    public static void main(String[] args){
        Parcel8 p = new Parcel8();
        Wrapping w = p.wrapping(10);
        System.out.println(w.value());
    }
}
public class Wrapping {
    int i ;
    public Wrapping(int i){
        this.i = i;
    }

    public int value(){
        return i;
    }
}

只需简单地传递合适的参数给父类的构造器即可,这里是将x传进new Wrapping(x)。在匿名内部类末尾的分毫,并不是用来标记此内部类结束的。实际上,它标记的是表达式的结束。只不过这个表达式正好包含了匿名内部类而已。这与别的地方使用的分号是一致的

在匿名内部类中定义字段时,还能够对其执行初始化操作:

public class Parcel9 {
    public Destination destination(final String dest){
        return new Destination() {
            private String label = dest;
            @Override
            public String readLabel() {
                return label;
            }
        };
    }

    public static void main(String[] args) {
        Parcel9 p = new Parcel9();
        Destination d = p.destination("Tasmania");
    }
}
public interface Destination {
    String readLabel();
}

如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用是final的,就像你在destination()的参数中看到的那样,如果忘记了,会得到一个编译时错误。

通过实例初始化,能够达到为匿名内部类创建一个构造器的效果:

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");
            }
            @Override
            public void f() {
                System.out.println("In anonymous f()");
            }
        };
    }

    public static void main(String[] args) {
        Base base = getBase(47);
        base.f();
    }
}

在上面的例子中,不要求变量i一定是final,因为i被传递给匿名类的基类的构造器,它并不会在匿名类内部被直接使用

7. 嵌套类

如果不需要内部类对象与其外部类对象之间有练习,可以将内部类声明为static,这称为“嵌套类”,普通内部类与嵌套类的区别:普通的内部类对象隐式地保存了一个引用,指向创建它的外部类。然后,内部类是static的时,就不会这样了,嵌套类意味着: 1)要创建嵌套类的对象,并不需要其外部类的对象 2)不能从嵌套类的对象中访问其非静态的外部类对象 嵌套类和普通内部类还有一个区别。普通内部类的字段与方法,普通的内部类不能有static数据和static字段,也不能包含嵌套类。但是嵌套类可以包含这些东西。

public class Parcel11 {
    
    private static class ParcelContents implements Contents{
        private int i = 11;
        public int value(){
            return i;
        }
    }
    
    protected static class ParcelDestination implements Destination{
        private String label;
        private ParcelDestination(String whereTo){
            label = whereTo;
        }
        
        public String readLabel(){
            return label;
        }
        
        public static void f(){}
        static int x = 10;
        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");
    }
}

8. 内部类的继承

class WithInner{
    class Inner{}
}
public class InheritInner extends WithInner.Inner {
    InheritInner(WithInner wi){
        wi.super();
    }

    public static void main(String[] args) {
        WithInner wi = new WithInner();
        InheritInner ii = new InheritInner(wi);
    }
}

9. 内部类的继承

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 {
    public class Yolk{
        public Yolk(){
            System.out.println("BigEgg.Yolk()");
        }
    }

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

从上面的例子中可以看出,当继承了某个外部类的时候,内部类并没有发生什么特别神奇的变化。这两个内部类是完全独立的两个实体,各自在自己的命名空间内,当然,明确地继承某个内部类也是可以的:

class Egg2{
    protected class Yolk{
        public Yolk(){
            System.out.println("Egg2.Yolk()");
        }
        public void f(){
            System.out.println("Egg2.Yolk.f()");
        }
    }

    private Yolk y = new Yolk();
    public Egg2(){
        System.out.println("New Egg2()");
    }

    public void insertYolk(Yolk yy){
        y = yy;
    }

    public void g(){
        y.f();
    }
}
public class BigEgg2 extends Egg2{
    public class Yolk extends Egg2.Yolk{
        public Yolk(){
            System.out.println("BigEgg2.Yolk()");
        }
        public void f(){
            System.out.println("BigEgg2.Yolk.f()");
        }
    }

    public BigEgg2(){
        insertYolk(new Yolk());
    }

    public static void main(String[] args) {
        Egg2 e2 = new BigEgg2();
        e2.g();
    }
}

BigEgg2.Yokl通过extends Egg2.Yolk 明确地继承了此内部类,并且覆盖了其中地方法。

10. 局部内部类

可以在代码块里创建内部类,典型的方式是在一个方法体地里面创建。局部内部类不能有访问说明符,因为它不是外部类的一部分;但是它可以访问当前代码块内的常量,以及此外部类的所有成员。下面的例子对局部内部类与匿名内部类的创建进行了比较:

interface Counter{
    int next();
}
public class LocalInnerClass {
    private int count = 0;
    Counter getCounter(final String name){
        class LocalCounter implements Counter{
            public LocalCounter(){
                System.out.println("LocalCounter");
            }
            public int next(){
                System.out.println(name);
                return count++;
            }
        }
        return new LocalCounter();
    }

    Counter getCounter2(final String name){
        return new Counter() {
            {
                System.out.println("Counter()");
            }
            @Override
            public int next() {
                System.out.println(name);
                return count++;
            }
        };
    }

    public static void main(String[] args) {
        LocalInnerClass lic = new LocalInnerClass();
        Counter c1 = lic.getCounter("Local inner");
        Counter 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());
        }
    }
}

11. 内部类标识符

由于每个类在编译后产生一个.class文件,其中包含了如果创建该类型的对象的全部信息。(此信息产生一个“meta-class”,叫做Class对象)。内部类也必须生成一个.class文件以包含它们的Class对象信息。这些类文件的命名有严格的规则:外部类的名字,加上“$”,再加上内部类的名字。例如,LocalInnerClass.java生成的.class文件包括:

Counter.class // interface Counter{}
LocalInnerClass$1.class // 如果内部类是匿名的,编译器会简单地产生一个数字作为其标识符
LocalInnerClass$1LocalCounter.class // 局部内部类
LocalInnerClass.class // class LocalInnerClass{}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • MyBatis —— HelloWold

    2、创建maven+spring+springmvc项目,pom文件中需要注入mybatis-spring的依赖,mybatis-spring2.0与Sprin...

    桑鱼
  • Spring - 泛型依赖注入(9)

    桑鱼
  • springboot 2.0 + Redis

    Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件

    桑鱼
  • java学习day15---UDP,reflect,proxy

    1.UDP udp是一种不安全的协议 容易丢失数据 不需要建立连接 效率高 数据是以数据包的形式发送 数据 IP 端口

    曼路
  • java基础第十七篇之网络编程和装饰者模式

    1:网络概述 1.1 网络的发展Net 1964年,美国人—> 阿帕网—>以太网Internet 1.2 网络的通信协议 windows电脑,andro...

    海仔
  • SpringBoot几个注解MockMvcWireMockSwagger2@JsonViewHibernate Validator异常处理拦截方式上传下载异步处理RESTSpring Security

    只有特定名称或者类型的Bean(通过@ConditionalOnMissingBean修饰)不存在于BeanFactory中时才创建某个Bean

    spilledyear
  • java编程思想第四版第十章习题

    用户7798898
  • JAVA中的23种设计模式(GOF)

    volatile 保证数据的可见性,让线程内存数据的变化立刻显示到主存中,而且有序性可以避免指令重排,但是不保证原子性。

    HcodeBlogger
  • SpringMVC 中的Annotated Controllers

    上面透漏一下信息:是一个GET响应 查找web下的index模版,通过model将数据传递给模版引擎渲染

    大话swift
  • 设计模式整理 顶

    Iterator模式可以帮助我们分离具体的集合跟遍历,就是在代码中更换了集合,也可以不需要重新调用新集合的方法。

    算法之名

扫码关注云+社区

领取腾讯云代金券