小白学Java:内部类

小白学Java:内部类

内部类是封装的一种形式,是定义在类或接口中的类。编程

内部类的分类

成员内部类

即定义的内部类做为外部类的一个普通成员(非static),就像下面这样:设计模式

public class Outer {
    class Inner{
        private String id = "夏天";

        public String getId() {
            return id;
        }
    }

    public Inner returnInner(){
        return new Inner();
    }
    public void show(){
        Inner in = new Inner();
        System.out.println(in.id);
    }
}

咱们经过以上一个简单的示例,能够得出如下几点:ide

  • Inner类就是内部类,它的定义在Outer类的内部。
  • Outer类中的returnInner方法返回一个Inner类型的对象
  • Outer类中的show方法经过咱们熟悉的方式建立了Inner示例并访问了其私有属性。

能够看到,咱们像使用正常类同样使用内部类,但实际上,内部类有许多奥妙,值得咱们去学习。至于内部类的用处,咱们暂且不谈,先学习它的语法也不迟。咱们在另一个类中再试着建立一下这个Inner对象吧:学习

class OuterTest{
    public static void main(String[] args) {
        //!false:Inner in = new Inner();
        Outer o = new Outer();
        o.show();
        Outer.Inner in = o.returnInner();
        //!false: can't access --System.out.println(in.id);
        System.out.println(in.getId());
    }
}

哦呦,有意思了,咱们在另外一个类OuterTest中再次测试咱们以前定义的内部类,结果出现了很是明显的变化,咱们陷入了沉思:测试

  • 咱们不可以像以前同样,用Inner in = new Inner();建立内部类实例。
  • 不要紧,咱们能够经过Outer对象的returnInner方法,来建立一个实例,成功!
  • 须要注意的是:咱们若是须要一个内部类类型的变量指向这个实例,咱们须要明确指明类型为:Outer.Inner,即外部类名.内部类名
  • 好啦,获得的内部类对象,咱们试着直接去访问它的私有属性!失败!
  • 那就老老实实地经过getId方法访问吧,成功!

说到这,咱们大概就能猜想到:内部类的存在能够很好地隐藏一部分具备联系代码,实现了那句话:我想让你看到的东西你随便看,不想让你看的东西你想看,门都没有。this

连接到外部类

其实咱们以前在分析ArrayList源码的时候,曾经接触过内部类。咱们在学习迭代器设计模式的时候,也曾领略过内部类带了的奥妙之处。下面我经过《Java编程思想》上:经过一个内部类实现迭代器模式的简单案例作相应的分析与学习:
首先呢,定义一个“选择器”接口:设计

interface Selector {
    boolean end();//判断是否到达终点
    void next();//移到下一个元素
    Object current();//访问当前元素
}

而后,定义一个序列类Sequence:指针

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;
        @Override
        public boolean end() {
            return i == items.length;
        }
        @Override
        public void next() {
            if (i < items.length) {
                i++;
            }
        }
        @Override
        public Object current() {
            return items[i];
        }
    }
    //向上转型为接口,隐藏实现的细节
    public Selector selector() {
        return new SequenceSelector();
    }
}
  • 内部类SequenceSelector以private修饰,实现了Selector接口,提供了方法的具体实现。
  • 内部类访问外部类的私有成员items,能够得出结论:内部类自动拥有对其外部类全部成员的访问权。

当内部类是非static时,当外部类对象建立了一个内部类对象时,内部类对象会产生一个指向外部类的对象的引用,因此非static内部类能够看到外部类的一切。code

  • 外部类Sequenceselector方法返回了一个内部类实例,意思就是用接口类型接收实现类的实例,实现向上转型,既隐藏了实现细节,又利于扩展。

咱们看一下具体的测试方法:

public static void main(String[] args) {
        Sequence sq = new Sequence(10);
        for (int i = 0; i < 10; i++) {
            sq.add(Integer.toString(i));
        }
        //产生咱们设计的选择器
        Selector sl = sq.selector();

        while (!sl.end()) {
            System.out.print(sl.current() + " ");
            sl.next();
        }
    }
  • 隐藏实现细节:使用Sequence序列存储对象时,不须要关心内部迭代的具体实现,用就完事了,这正是内部类配合迭代器设计模式体现的高度隐藏。
  • 利于扩展:咱们若是要设计一个反向迭代,能够在Sequence内部再定义一个内部类,并提供Selector接口的实现细节,及其利于扩展,妙啊。

.new和.this

咱们稍微修改一下最初的Outer:

public class Outer {
    String id = "乔巴";
    class Inner{
        private String id = "夏天";

        public String getId() {
            return id;
        }
        public String getOuterId(){
            return Outer.this.id;
        }
        public Outer returnOuter(){
            return Outer.this;
        }
    }
    public static void main(String[] args) {
        Outer o = new Outer();
        System.out.println(o.new Inner().getId());//夏天
        System.out.println(o.new Inner().getOuterId());//乔巴
    }
}
  • 在内部类Inner体内添加了returnOuter的引用,return Outer.this;,即外部类名.this
  • 咱们能够发现,内部类内外具备同名的属性,咱们在内部类中,不加任何修饰的状况下默认调用内部类里的属性,咱们能够经过引用的形式访问外部类的id属性,即Outer.this.id

咱们来测试一波:

public static void main(String[] args) {
        Outer.Inner oi = new Outer().new Inner();
        System.out.println(oi.getId());//夏天
        Outer o = oi.returnOuter();
        System.out.println(o.id);//乔巴
    }
  • 外部类产生内部类对象的方法已经被咱们删除了,这时咱们若是想要经过外部类对象建立一个内部类对象:Outer.Inner oi = new Outer().new Inner();,即在外部类对象后面用.new 内部类构造器

咱们对内部类指向外部类对象的引用进行更加深刻的理解与体会,咱们会发现,上面的代码在编译以后,会产生两个字节码文件:Outer$Inner.classOuter.class。咱们对Outer$Inner.class进行反编译:
136jn1.png
确实,内部类在建立的过程当中,依靠外部类对象,并且会产生一个指向外部类对象的引用

局部内部类

方法做用域内部类

即在方法做用域内建立一个完整的类。

public class Outer {
    public TestOuter test(final String s){
        class Inner implements TestOuter{
            @Override
            public void testM() {
                //!false: s+="g";
                System.out.println(s);
            }
        }
        return new Inner();
    }
    public static void main(String[] args) {
        Outer o = new Outer();
        o.test("天乔巴夏").testM();//天乔巴夏
    }
}
interface TestOuter{
    void testM();
}

须要注意两点:

  • 此时Inner类是test方法的一部分,Outer不能在该方法以外访问Inner。
  • 方法传入的参数s和方法内自己的局部变量都须要以final修饰,不能被改变!!!

JDK1.8以后能够不用final显式修饰传入参数和局部变量,但其自己仍是至关于final修饰的,不可改变。咱们去掉final,进行反编译:
136OXR.png

任意做用域内的内部类

能够将内部类定义在任意的做用域内:

public class Outer {
    public void test(final String s,final int value){
        final int a = value;
        if(value>2){
            class Inner{
                public void testM() {
                    //!false: s+="g";
                    //!false: a+=1;
                    System.out.println(s+", "+a);
                }
            }
            Inner in = new Inner();
            in.testM();
        }
        //!false:Inner i = new Inner();
    }
    public static void main(String[] args) {
        Outer o = new Outer();
        o.test("天乔巴夏",3);
    }
}

一样须要注意的是:

  • 内部类定义在if条件代码块中,并不意味着建立该内部类有相应的条件。内部类一开始就会被建立,if条件只决定能不能用里头的东西。
  • 如上所示,if做用域以外,编译器就不认识内部类了,由于它藏起来了。

静态内部类

即用static修饰的成员内部类,归属于类,即它不存在指向外部类的引用

public class Outer {
    static int a = 5;
    int b = 6;
    static class Inner{
        static int value;
        public void show(){
            //!false System.out.println(b);
            System.out.println(a);
        }
    }
}
class OuterTest {
    public static void main(String[] args) {
        Outer.Inner oi = new Outer.Inner();
        oi.show();
    }
}

须要注意的是:

  • 静态内部类也能够定义非静态的成员属性和方法。
  • 静态内部类对象的建立不依靠外部类的对象,能够直接经过:new Outer.Inner()建立内部类对象。
  • 静态内部类中能够包含静态属性和方法,而除了静态内部类以外,即咱们上面所说的全部的内部类内部都不能有(可是能够有静态常量static final修饰)。
  • 静态内部类不能访问非静态的外部类成员。
  • 最后,咱们反编译验证一下:

136Lc9.png

匿名内部类

这个类型的内部类,看着名字就怪怪的,咱们先看看一段违反咱们认知的代码:

public class Outer {
    public InterfaceInner inner(){
    //建立一个实现InterfaceInner接口的是实现类对象
        return new InterfaceInner() {
            @Override
            public void show() {
                System.out.println("Outer.show");
            }
        };
    }
    public static void main(String[] args) {
        Outer o = new Outer();
        o.inner().show();
    }
}
interface InterfaceInner{
    void show();
}

真的很是奇怪,乍一看,InterfaceInner是个接口,而Outer类的inner方法怎么出现了new InterfaceInner()的字眼呢?接口不是不能建立实例对象的么?

确实,这就是匿名内部类的一个使用,其实inner方法返回的是实现了接口方法的实现类对象,咱们能够看到分号结尾,表明一个完整的表达式,只不过表达式包含着接口实现,有点长罢了。因此上面匿名内部类的语法其实就是下面这种形式的简化形式:

public class Outer {   
    class Inner implements InterfaceInner{
        @Override
        public void show(){
            System.out.println("Outer.show");
        }
    }
    public InterfaceInner inner(){ 
        return new Inner();  
    }
    public static void main(String[] args) {
        Outer o = new Outer();
        o.inner().show();
    }
}
interface InterfaceInner{
    void show();
}

不只仅是接口,普通的类也能够被看成“接口”来使用:

public class Outer {
    public OuterTest outerTest(int value) {
        //参数传给匿名类的基类构造器
        return new OuterTest(value) {
            
            @Override
            public int getValue() {
                return super.getValue() * 10;
            }
        };
    }
    public static void main(String[] args) {
        Outer o = new Outer();
        System.out.println(o.outerTest(10).getValue());//100
    }
}
class OuterTest {
    public int value;
    OuterTest(int value) {
        this.value = value;
    }
    public int getValue() {
        return value;
    }
}

须要注意的是:

  • 匿名类既能够扩展类,也能够实现接口,固然抽象类就再也不赘述了,普通类均可以,抽象类就更能够了。但不能同时作这两件事,且每次最多实现一个接口。
  • 匿名内部类没有名字,因此自身没有构造器。
  • 针对类而言,上述匿名内部类的语法就代表:建立一个继承OuterTest类的子类实例。因此能够在匿名内部类定义中调用父类方法与父类构造器。
  • 传入的参数传递给构造器,没有在类中直接使用,能够不用在参数前加final。

内部类的继承

内部类能够被继承,可是和咱们普通的类继承有些出处。具体来看一下:

public class Outer {
    class Inner{
        private int value = 100;
        Inner(){
        }
        Inner(int value){
            this.value = value;
        }
        public void f(){
            System.out.println("Inner.f "+value);
        }
    }
}
class TestOuter extends Outer.Inner{
    TestOuter(Outer o){
        o.super();
    }
    TestOuter(Outer o,int value){
        o.super(value);
    }

    public static void main(String[] args) {
        Outer o = new Outer();
        TestOuter tt = new TestOuter(o);
        TestOuter t = new TestOuter(o,10);
        tt.f();
        t.f();
    }
}

咱们能够发现的是:

  • 一个类继承内部类的形式:class A extends Outer.Inner{}
  • 内部类的构造器必须连接到指向外部类对象的引用上,o.super();,即都须要传入外部类对象做为参数。

内部类有啥用

能够看到的一点就是,内部类内部的实现细节能够被很好地进行封装。并且Java中存在接口的多实现,虽然必定程度上弥补了Java“不支持多继承”的特色,但内部类的存在使其更加优秀,能够看看下面这个例子:

//假设A、B是两个接口
class First implements A{
    B makeB(){
        return new B() {
        };
    }
}

这是一个经过匿名内部类实现接口功能的简单的例子。对于接口而言,咱们彻底能够经过下面这样进行,由于Java中一个类能够实现多个接口:

class First implements A,B{
}

可是除了接口以外,像普通的类,像抽象类,均可以定义独立的内部类去单独继承并实现,使用内部类使“多重继承”更加完善


因为后面的许多内容尚未涉及到,学习到,因此总结的比较浅显,并无作特别深刻,特别真实的场景模拟,以后有时间会再作系统性的总结。若是有叙述错误的地方,还望评论区批评指针,共同进步。
参考:
《Java 编程思想》
https://stackoverflow.com/questions/70324/java-inner-class-and-static-nested-class?r=SearchResults

相关文章
相关标签/搜索