十三、内部类使用详解(上)

概述

将一个类的定义放在一个类的内部,这就是内部类。内部类可以实现将一些逻辑相关的类组合在一起,并且可以控制类中的类的可视性

举个栗子,在面向对象思维中,一个类即是一种类型,用类可以定义对象,现实中任何事物都可以被抽象为类与对象,例如,我们平时用的计算机,可以抽象为computer类,如果想要一台计算机,则new一个即可,这里我们要关注的是这个类的组成,现实中,计算机由各种零件组成(CPU、内存、硬盘、显示器等),这些零部件之间相互通信各自完成自己的任务,最终使得你可以使用计算机。那么我们该如何更好的抽象出这种内外嵌套的结构呢?

没错,对于计算机内部的零部件,也可以作为类,而内部类的特点正好符合这种逻辑,也就是说,计算机必须依赖这些零部件,而内部类(这里指普通内部类)的特点之一也是必须与外围类对象直接关联。对于计算机内部的零部件你通常情况下是接触不到的,而内部类也同样可以进行限制其对外的可视性,即通过private等关键字实现。

内部类最常见的实现形式是成员内部类,其次还有静态内部类、局部内部类、匿名内部类。

内部类特点解析

以最常见的成员内部类为例,其特性如下:

  1. 内部类对象可访问外围类所有成员
  2. 内部类方法通过.this获取外部类的引用
  3. 外围类对象通过.new创建内部类对象(此处内部类必须为public

内部类对象访问外部类成员

public class Sequence {
    private Object [] items;
    private int count = 0;
    public Sequence(int size) {
        items = new Object[size];
    }
    public void add(Object obj) {
        items[count++] = obj;
    }
    
    
    public class SequenceSelector {
        private int i = 0;
        public boolean isEnd() {
            return i == items.length;
        }
        public Object current() {
            return items[i];
        }
        public void next() {
            if(i < items.length) {
                i++;
            }
        }
    }
    
    public static void main(String [] args) {
        Sequence seq = new Sequence(10);
        for(int i=0; i<10; i++) {
            seq.add(Integer.toString(i));
        }
        //此处seq.new的用法在后面解释,现在就当做是普通new即可
        SequenceSelector selector = seq.new SequenceSelector();
        
        while(!selector.isEnd()) {
            System.out.println(selector.current());
            selector.next();
        }
    }
}

上述代码中,我们实现了一个可存放任意对象的列表Sequence,并在其内部实现了一个迭代器SequenceSelector,创建迭代器对象即可实现对Sequence对象中元素的遍历,即itemsprivate的。

很显然,这样设计并不好,因为列表的种类很多的话,我们在使用某个列表并且需要其迭代器来遍历时,必须要知道该列表的迭代器类。思考如何进行设计优化?

.this与.new的使用
在上一部分代码中已经使用到了.new,如果你单单只用new来创建内部类对象,编译器就会报错,为什么呢?其实很好理解,以开头计算机的栗子说,就是如果没有计算机,单独给你个 CPU 是没有任何意义的,也就是说内部类的对象必须要与外围类对象建立联系,独立存在的内部类对象是毫无意义的(静态内部类除外,后面会讲)。

言归正传,我们来瞧瞧.this.new的使用:

public class test1 {
    public void fun(){
        System.out.println("fun()");
    }
    public class inner {
        //此方法返回外围对象的引用
        public test1 DoThis() {
            return test1.this;
        }
    }
    
    pubic static void main(String [] args) {
        test1 t = new test1();
        //使用.new创建内部类
        test1.inner dti = t.new inner();
        dti.DoThis().fun();
    }
}
/*Output:
fun()
*/

内部类对象的向上转型

在开头的第一块代码中我们实现了一个列表Sequence,但是使用时却发现并没有想象中这么好用,因为不同类型的列表的迭代器不同,在使用时就必须要知道特定类的迭代器名称,如何才能改进呢?

理想状态应该是所有列表的类都共用一个迭代器类型,但是各自又可以实现对迭代器的个性化,由此可以想到继承,考虑到迭代器脱离列表就没有意义,因此将迭代器抽象成接口,接着在每个列表类中以内部类的形式实现各自的迭代器。

详见改进后的代码:

interface Selector {
    public boolean isEnd();
    public Object current();
    public void next();
}

public class Sequence {
    private Object [] items;
    private int count = 0;
    public Sequence(int size) {
        items = new Object[size];
    }
    public void add(Object obj) {
        items[count++] = obj;
    }
    
    
    private class SequenceSelector implements Selector{
        private int i = 0;
        public boolean isEnd() {
            return i == items.length;
        }
        public Object current() {
            return items[i];
        }
        public void next() {
            if(i < items.length) {
                i++;
            }
        }
    }
    public Selector getSelector() {
        return new SequenceSelector();
    }
    
    public static void main(String [] args) {
        Sequence seq = new Sequence(10);
        for(int i=0; i<10; i++) {
            seq.add(Integer.toString(i));
        }
        
        Selector selector = seq.getSelector();
        
        while(!selector.isEnd()) {
            System.out.println(selector.current());
            selector.next();
        }
    }
}

上述代码中,新增了Selector接口,作为迭代器的抽象接口,并且将内部类私有化,新增getSelector()方法获取指定对象的迭代器。

在获得迭代器对象时,是无法确切知道迭代器的具体类型的,这对于客户端程序员使用来说,很好的隐藏了迭代器的实现细节,毕竟客户端程序员并不需要关心实现细节。

静态内部类和局部内部类

到此为止,我们讲到的都是内部类最普通的用法,在类的内部,除了成员变量的位置,还有方法内部。在方法内部的内部类称作局部内部类

普通的内部类加上static关键字修饰后,称为静态内部类

静态内部类
如果不需要内部类对象和外部类对象有关联,那么将内部类设为静态的即可,这被称为嵌套类

区别于普通内部类,嵌套类中可以存在static变量和方法,在使用时不需要存在外部类的对象。因此你可以理解为普通内部类中隐藏了外部类对象的引用(即.this),嵌套类中没有这个引用。

那么嵌套类的作用是什么呢?

1.作为接口内部的类
正常情况下,接口内部是不允许存在任何代码的,但是可以存在嵌套类,因为嵌套类是static的,只是将嵌套类置于接口的命名空间内,不违反接口的规则。

public interface testInterface {
    public void fun();
    
    class testInterfaceImpl implements testInterface {
        void fun() {
            System.out.println("fun");
        }
    }
}

2.从多层嵌套中访问外部类成员
一个内部类被嵌套了多层后,它依然能够访问所有外部类的所有成员。

class MNA {
    private void fun() {
        System.out.println("fun");
    }
    
    class A {
        private void gun() {
            System.out.println("gun");
        }
        
        public class B {
            void hun() {
                System.out.println("hun");
                fun();
                gun();
            }
        }
    }
}

public class test6 {
    public static void main(String [] args) {
        MNA mna = new MNA();
        MNA.A mnaa = mna.new A();
        MNA.A.B mnaab = mnaa.new B();
        mnaab.hun();
    }
}
/*Output:
hun
fun
gun

*/

局部内部类
局部内部类的作用范围是局部的,例如,在某个方法中定义了局部内部类,那么方法以外的地方是不允许使用该内部类的。

interface Iinner {
    String readLabel();
}

public class test2 {
    public Iinner getInner(String s) {
    
        class inner implements Iinner {
            private String label;
            private inner(String whereTo) {
                label = whereTo;
            }
            public String readLabel() {
                return label;
            }
        }
        
        return new inner(s);
    }
    
    public static void main(String [] args) {
        test2 t = new test2();
        Iinner in = t.getInner("abc");
        System.out.println(in.readLabel());
    }
}
/*Output:
abc
*/

匿名内部类

如果我们只需要某个接口的一个对象,那么就可以使用匿名内部类来简化代码的编写。

interface Contents {
    int value();
}

public class test3 {
    class MyContents implement Contents {
        private int i = 11;
        public int value() {
            return i;
        }
    }
    
    public Contents getContents() {
        return new MyContents();
    }
    
    public static void main(String [] args) {
        test3 t3 = new test3();
        System.out.println(t3.getContents().value());
    }
}

假设上述代码中的内部类MyContents只存在于getMyContents()方法中使用,那么我们可以使用匿名内部类的形式将其简化:

interface Contents {
    int value();
}

public class test3 {
    
    public Contents getContents() {
        return new Contents(){
            private int i = 11;
            public int value() {
                return i;
            }
        };
    }
    
    public static void main(String [] args) {
        test3 t3 = new test3();
        System.out.println(t3.getContents().value());
    }
}

通过例子,可以发现,匿名内部类是一个没有类名的类,通过new关键字创建的对象被自动向上转型为基类的引用。

思考一下,如果创建匿名类对象时,需要参数该怎么做呢?

class Iinner {
    private String label;
    Iinner(String s) {
        label = s;
    }
    String readLabel(){
        return label;
    }
}

public class test4 {
    public Iinner getInner(String s) {
        return new Iinner(s) {
            public String readLabel() {
                //调用父类方法获取成员变量
                return super.readLabel() + 10;
            }
        };
    }
    
    public static void main(String [] args) {
        test4 t = new test4();
        Iinner in = t.getInner("abc");
        System.out.println(in.readLabel());
    }
}

通过在父类中创建对应的构造方法,可以在创建匿名类对象时进行传参。

需要注意的是,定义一个类,在类的末尾不需要分号,这里创建匿名类对象时末尾的分号是指new语句的结尾,而不是类的结束符。

方法中使用匿名类创建对象时,若匿名类使用了方法中的参数,则参数必须为 final

class Iinner {
    private String label;
    Iinner(String s) {
        label = s;
    }
    String readLabel(){
        return label;
    }
}

public class test5 {
    public Iinner getInner(String s, final int i) {
        return new Iinner(s) {
            public String readLabel() {
                return super.readLabel() + i;
            }
        };
    }
    
    public static void main(String [] args) {
        test5 t = new test5();
        Iinner in = t.getInner("abc", 10);
        System.out.println(in.readLabel());
    }
}

上例中可以发现,在匿名内部类中的方法中使用外部类方法的参数,参数必须为final,但是参数作为匿名内部类的构造方法参数则不需要声明为final,因为这个参数并没有在匿名内部类里面使用。

匿名内部类改造工厂模式

通过使用匿名内部类,工厂模式的实现代码变得更加优雅了。

interface Service {
    void fun1();
    void fun2();
}
interface ServiceFactory {
    Service getService();
}

class ServiceImpl1 implements Service {
    public static ServiceFactory factory = new ServiceFactory(){
        public Service getService() {
            return new ServiceImpl1();
        }
    };

    private ServiceImpl1() {}
    public void fun1() {
        System.out.println("ServiceImpl1-fun1");
    }
    public void fun2() {
        System.out.println("ServiceImpl1-fun2");
    }
}

class ServiceImpl2 implements Service {
    //每个服务类通过匿名内部类的方式得到特定工厂类
    public static ServiceFactory factory = new ServiceFactory(){
        public Service getService() {
            return new ServiceImpl2();
        }
    };

    private ServiceImpl2() {}
    public void fun1() {
        System.out.println("ServiceImpl2-fun1");
    }
    public void fun2() {
        System.out.println("ServiceImpl2-fun2");
    }
}

public class test {
    //消费者,从工厂得到服务类
    public static void serviceConsumer(ServiceFactory factory) {
        Service s = factory.getService();
        s.fun1();
        s.fun2();
    }
    
    public static void main(String [] args) {
        serviceConsumer(ServiceImpl1.factory);// 将特定服务类的工厂交给消费者
        serviceConsumer(ServiceImpl2.factory);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值