Java---浅析内部类

内部类就是将类B的定义放在类A定义的内部,A是外部类,B是内部类。
先来看一下内部类和外部类之间的关系。

public class Outer {

    private String workNumber;

    private String name;

    public Outer() {
        workNumber = "O12345";
        name = "Frank";
    }

    public class InnerClass {
        public InnerClass() {
            workNumber = "I12345";
            name = "Franklin";
        }

        private void show() {
            System.out.println(String.format("workNumber: %s\nname: %s", workNumber, name));
        }
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        Outer out = new Outer();
        Outer.InnerClass inner = out.new InnerClass();
        inner.show();
    }

}

虽然外部类Outer的成员变量workNumbername是私有的,但是内部类InnerClass可以直接访问这两个成员变量。这是因为创建内部类对象时,内部类对象会保存一个指向外部类对象的引用,可以通过这个引用来选择外部类的成员。
在Debug模式下,可以看到内部类持有外部类的引用。↓
pic1

内部类与向上转型

先看代码

public class Outer {

    public Animal getPanda() {
        return new Panda();
    }

    public Plant getTree() {
        return new Tree();
    }

    private class Panda implements Animal {
        @Override
        public String getAniName() {
            return "Panda";
        }
    }

    protected class Tree implements Plant {
        @Override
        public String getPlantName() {
            return "Tree";
        }
    }
}

public class JavaMain {
    public static void main(String[] args) {
        Outer out = new Outer();

        Outer.Tree tree = out.new Tree();
        System.out.println(tree.getPlantName());

        // Outer.Panda panda = out.new Panda(); 编译错误
        Animal panda = out.getPanda();
        System.out.println(panda.getAniName());
    }
}

首先定义了两个接口AnimalPlant,并声明了方法getAniName()getPlantName()
Outer类中,定义了两个内部类PandaTreePanda类被定义成private的,Tree类被定义成protected的。
如果这两个内部类,没有实现接口。因为Panda类是私有的,所以在创建Panda类的对象时,会报编译错误。
如果想创建Panda类的对象,可以先实现接口Animal。创建对象时,将内部类Panda向上转型为接口类型Animal。这样就可以得到私有内部类的对象,还完全隐藏了内部类的实现细节。
由于普通类不能声明为privateprotected,如果想隐藏类的实现细节(封装),可以采用私有内部类+向上转型接口的方式来实现。

下面介绍一下内部类的分类,Java中的内部类分为成员内部类、局部内部类、匿名内部类和静态内部类。

成员内部类

成员内部类是最普通的内部类,文章开头介绍内部类时,使用的代码中的内部类就属于成员内部类。
在内部类中可以直接访问外部类的成员对象,如果内部类和外部类中有同名的成员变量时,该如何处理?

public class Outer {

    private String workNumber;

    private String name;

    public Outer() {
        workNumber = "O12345";
        name = "Frank";
    }

    public class InnerClass {
        private String name;

        public InnerClass() {
            workNumber = "I12345";
            name = "Franklin";
        }

        private void show() {
            String name = "tmp";
            System.out.println("局部变量:" + name);
            System.out.println("内部类变量:" + this.name);
            System.out.println("外部类变量:" + Outer.this.name);
        }
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        Outer out = new Outer();
        Outer.InnerClass inner = out.new InnerClass();
        inner.show();
    }

}

在内部类中用this来引用内部类的成员变量,Outer.this来引用外部类的成员变量。

局部内部类

局部内部类分为方法内的内部类和作用域内的内部类。局部内部类主要用于解决比较复杂的问题时,需要创建一个类来解决特定的问题,但又不希望这个类是公共可用的。

  • 方法内部类1
public class Outer {

    public Destination dest(String s) {
        // 注意这里不指定访问权限修饰符
        class PDestination implements Destination {
            private String label;

            private PDestination(String whereTo) {
                label = whereTo;
            }
            @Override
            public String readLabel() {
                return label;
            }
        }
        return new PDestination(s);
    }

    public static void main(String[] args) {
        Outer out = new Outer();
        Destination obj = out.dest("shenyang");
        System.out.println(obj.readLabel());
    }
}
  • 作用域内部类2
public class Outer {

    private void internalTracking(boolean b) {
        if (b) {
            // 注意这里不指定访问权限修饰符
            class TrackingSlip {
                private String id;
                TrackingSlip(String s) {
                    id = s;
                }
                String getSlip() {
                    return id;
                }
            }
            TrackingSlip ts = new TrackingSlip("slip");
            String str = ts.getSlip();
        }
    }

    public void track() {
        internalTracking(true);
    }

    public static void main(String[] args) {
        Outer out = new Outer();
        out.track();
    }

}

匿名内部类

顾名思义,匿名内部类是没有名字的,也没有访问修饰符。直接使用new来生成一个对象引用。

public interface Animal {
    String getAniName(String area);
}

public class Outer {

    public Animal getAniObj() {
        return new Animal() {
            @Override
            public String getAniName() {
                return "Tiger";
            }
        };
    }

    public static void main(String[] args) {
        Outer out = new Outer();
        Animal aniObj = out.getAniObj();
        System.out.println(aniObj.getAniName());
    }
}

out.getAniObj()返回的是Animal类型的引用,实际上是匿名内部类实现了Animal接口,并将返回的引用向上转型为Animal类型的引用。

如果匿名内部类继承的基类的构造函数中有参数时,可以这样来处理。

public class Pet {
    private String type;

    public Pet() {
        type = "No Pet";
    }

    public Pet(String petType) {
        type = petType;
    }

    public String getType() {
        return type;
    }
}

public class Outer {

    public Pet getPetObj(String area) {
        return new Pet(area) {
            public String getType() {
                return "德国" + super.getType();
            }
        };
    }

    public static void main(String[] args) {
        Outer out = new Outer();
        Pet petObj = out.getPetObj("牧羊犬");
        System.out.println(petObj.getType());
    }

}

只需要将参数传给基类的构造函数就可以。执行new Pet(area)时,Pet的构造函数会被调用。

匿名内部类特性

1.匿名内部类总是默认实现接口或者继承其他类。
2.匿名内部类没有构造方法,只有一个实例。
3.匿名内部类中不能定义静态成员和静态方法。

静态内部类

静态内部类是用static修饰的内部类,在Oracle官方文档中被叫作非静态嵌套类。静态内部类没有隐性的保存外部类的引用,所以静态内部类的对象和外部类对象之间没有联系,也就不能访问外部类中的非静态对象。

public class Outer {
    private static String name;

    private String number;

    public Outer(String name, String number) {
        Outer.name = name;
        this.number = number;
    }

    // 静态内部类
    private static class InnerStaticClass {
        
        private void show() {
            System.out.println("Static Nested Class:" + name);
            // Non-static field 'number' cannot be referenced from a static context
            // 只能访问外部类的静态成员
            //System.out.println("Static Nested Class:" + number);
        }
    }

    // 非静态内部类
    private class InnerClass {
        // Inner classes cannot have static declarations
        // 非静态内部类中不能有静态成员
        //private static String staticVal;

        private void show() {
            // 非静态内部类中可以调用外部类的成员
            System.out.println("Inner Class(name):" + name);
            System.out.println("Inner Class(number):" + number);
        }
    }

    private void show() {
        // 静态内部类可以直接创建实例
        new InnerStaticClass().show();
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        Outer out = new Outer("Franklin", "I12345");
        out.show();
        Outer.InnerClass inner = out.new InnerClass();
        inner.show();
    }

}

通过上面的代码可以看到,非静态内部类中不能定义静态成员(变量,方法)。否则会出现编译错误。
外部类在加载的时候,并不会加载非静态内部类,而是在使用时才会被加载。
如果非静态内部类中存在静态成员的话,就会出现非静态内部类没有加载,但是试图在内存中创建内部类中的静态属性和方法。这显然是矛盾的,所以在非静态内部类中不能定义任何的静态成员。

总结

内部类的存在让多重继承的实现方式变得更完整。一个类可以实现多个接口,但是如果想继承多个类,就需要引入内部类。
内部类还有很多实用技巧,需要我们在以后逐渐去发掘。


  1. 代码源自 Thinking In Java Third Edition P210 ↩︎

  2. 代码源自 Thinking In Java Third Edition P211 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值