美文网首页
第十章 内部类

第十章 内部类

作者: 浩林Leon | 来源:发表于2017-12-20 23:01 被阅读1次

    内部类有重要的特性,把一些逻辑相关的类组织在一起,并控制内部的类的可见性.内部类和组合是完全不同的概念.
    内部类的常见情况是: 外部类有一个方法,返回指向内部类的引用.

    10.2链接到外部类

    当生成一个内部类对象时,此对象有访问制造他的外围对象(enclosing Object)有了一种联系,它能访问外围对象的所有成员,不需要任何特殊条件.此外内部类也拥有外围类的所有元素的访问权限.

    package tinking_in_java.innerClass;
    /**
     * Created by leon on 17-12-15.
     */
    public interface Selector {
        boolean end();
        void next();
        Object current();
    }
    
    package tinking_in_java.innerClass;
    /**
     * Created by leon on 17-12-15.
     */
    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 getSelector() {
            return new SequenceSelector();
        }
        public static void main(String[] args) {
            Sequence sequence = new Sequence(10);
            for (int i = 0; i < 10; i++) {
                sequence.add(i);
            }
            Selector selector = sequence.getSelector();
            while (!selector.end()) {
                System.out.print(selector.current()+" ");
                selector.next();
            }
        }
    }
    //output---
    0 1 2 3 4 5 6 7 8 9 
    

    上述代码是迭代器模式.迭代器模式,提供一个迭代接口Selector 包含 next(),current(),end() 3个协议,由其他具有容器类型的数据结构自己区根据 selector接口各自迭代.可以达到不同数据结构容器可以按照相同规则进行遍历数据.
    内部类private SelectorSequence 可以自动拥有外部类items的访问.这是怎么做到的呢?编译器会自动处理所有的细节.内部类的对象只能在与其外部类对象相关联的情况下才能被创建(当内部类是非静态类时).

    10.3使用.this 和.new

    在内部类中返回外部类引用可以采用.this形式直接返回.例如:

    package tinking_in_java.innerClass;
    /**
     * Created by leon on 17-12-15.
     */
    public class Dotthis {
        void f() {
            System.out.println("Out---");
        }
        public class Inner {
            public Dotthis outer() {
                return Dotthis.this;
            }
        }
        public Inner getInner() {
            return new Inner();
        }
        public static void main(String[] args) {
            Dotthis dotthis = new Dotthis();
            Dotthis.Inner inner = dotthis.getInner();
            inner.outer().f();
        }
    }
    //output---
    Out---
    

    如果在外部(其他类)想创建内部对象 可以通过.new,例如

    public static void main(String[] args) 
       Dotthis dotthis = new Dotthis();
        Dotthis.Inner inner = dotthis.new Inner();
        Dotthis.Inner inner2 = new Dotthis().new Inner();
        inner.outer().f();
        inner2.outer().f();
    }
    

    如果内部类是 静态内部类(嵌套类)则可以直接new Inner();在其他的第三方类可以通过new 外部类.静态内部类()的方式(new Dotthis.Inner();)

    package tinking_in_java.innerClass;
    /**
     * Created by leon on 17-12-15.
     */
    public class Dotthis {
        void f() {
            System.out.println("Out---");
        }
        public static class Inner {
            public void in() {
                System.out.println("in----");
            }
        }
        public Inner getInner() {
            return new Inner();
        }
        public static void main(String[] args) {
            Dotthis dotthis = new Dotthis();
            Dotthis.Inner inner = new Inner();
            Dotthis.Inner inner2 = new Inner();
            inner.in();
        }
    

    inner1 和 inner2并不是指向同一个 对象.

    10.4内部类和向上转型

    当一个类只想对外提供具有某种接口的功能(返回基类或接口引用),但是想隐藏具体的实现细节.那么就内部类就有了用武之地(当将内部类向上转型为基类,尤其是转型为接口,内部类可以发挥作用).

    package tinking_in_java.innerClass;
    /**
     * Created by leon on 17-12-15.
     */
    public interface Contents {
        int value();
    }
    package tinking_in_java.innerClass;
    /**
     * Created by leon on 17-12-15.
     */
    public interface Describ {
        String readLabel();
    }
    package tinking_in_java.innerClass;
    /**
     * Created by leon on 17-12-15.
     */
    public class Parcel4 {
        private class PContent implements Contents {
            @Override
            public int value() {
                return 20;
            }
        }
        private class PDescrib implements Describ {
            String label;
            public PDescrib(String label) {
                this.label = label;
            }
            @Override
            public String readLabel() {
                return label;
            }
        }
        public Describ getDecib(String label) {
            return new PDescrib(label);
        }
        public Contents getContens() {
            return new PContent();
        }
        public static void main(String[] args) {
            Parcel4 parcel4 = new Parcel4();
            Contents contents = parcel4.getContens();
            Describ describ = parcel4.getDecib("hello ");
            System.out.println(describ.readLabel() + contents.value());
        }
    }
    //output---
    hello 20
    

    10.5在方法和作用域内的内部类

    通常,如果读写类中包含的内部类,那么他们都是”平凡的”内部类,简单且容易理解.然而有些更加难理解的技术,例如:在一个方法或者任意作用域里面定义内部类.这么做有两个理由:

    1.实现了某一个类型的接口,创建并返回接口的引用.(通常是希望创建局部的权限,希望在这个区域之外不能访问和修改)
    2.要解决一个复杂的问题,想创建一个辅助类帮助,但不希望这个类是公用的.

    一般可以在下面这些地方使用:

    1. 定义在方法中
    2. 定义在作用域内,此作用域在方法内有效
    3. 实现接口的匿名类
    4. 一个匿名类,扩展了非默认构造器的类
    5. 一个匿名类,执行了字段初始化
    6. 一个匿名类,通过实例初始化实现构造(匿名类不可能有构造器)
      解释:
      1.在方法内部创建的内部类,只有在该方法内可以调用,其他地方是不能调用的,因为此内部类是方法的一部分,而不是之外的外部类的一部分.在其他地方可以出现同名的类,不会冲突.
      2.在作用域之内的,编译的时候和其他的类一起同时编译,但是除了在此作用域之外是不可访问的,其他的和普通类一样.可以在if,switch...case..中.

    10.6匿名内部类

    package tinking_in_java.innerClass;
    /**
     * Created by leon on 17-12-15.
     */
    public class Parcel8 {
        //传递构造函数参数
        public Wrapping getWrap(int value) {
            return new Wrapping(value) {
                @Override
                public int getValue() {
                    return super.getValue() * 3;
                }
            };
        }
        //创建新的字段,初始化
        public Describ getDesrib(String describ) {
            String out = "sss";
            return new Describ() {
                private String label = describ;
                @Override
                public String readLabel() {
                    return label + out;
                }
            };
        }
        //进行匿名类里面做构造器的行为,
        public Base getBase(int base) {
            return new Base(base) {
                //这里是实例初始化 形式
                {
                    System.out.println("inner Constructor");
                    y = base;
                }
                @Override
                public void f() {
                    System.out.println("inner y=" + y);
                }
            };
        }
        public static void main(String[] args) {
            Parcel8 parcel8 = new Parcel8();
            Wrapping wrapping = parcel8.getWrap(1);
            System.out.println(wrapping.getValue());
            Describ describ = parcel8.getDesrib("parcel8");
            System.out.println(describ.readLabel());
            Base base = parcel8.getBase(300);
            base.f();
        }
    }
    
    
    
    package tinking_in_java.innerClass;
    /**
     * Created by leon on 17-12-15.
     */
    public class Wrapping {
        private int i;
        Wrapping(int i) {
            this.i = i;
        }
        public int getValue() {
            return i;
        }
    }
    
    package tinking_in_java.innerClass;
    /**
     * Created by leon on 17-12-15.
     */
    public interface Describ {
        String readLabel();
    }
    
    package tinking_in_java.innerClass;
    /**
     * Created by leon on 17-12-15.
     */
    public abstract class Base {
        public Base(int i) {
            System.out.println("Base Constructor" + i);
        }
        
        public abstract void f();
    }
    
    

    10.6.1 利用内部匿名类构造工厂方法优化.

    (把生产工厂合并到具体的产品中,避免为每个产品单独创建一个工厂类)
    工厂方法 原理UML


    image.png

    以下是标准工厂方法和 采用匿名内部类优化的工厂方法对比:


    image.png
    其中:pencil,pencilFactory 是标准工厂方法,pen是改良后的.
    发现改良后的 少了一个PenFactory类.
    Ifactory.java
    package tinking_in_java.innerClass.FactoryMethod;
    /**
     * Created by leon on 17-12-16.
     */
    public interface IFactory {
        IProduct creatProduct();
    }
    
    Iproduct.java
    package tinking_in_java.innerClass.FactoryMethod;
    /**
     * Created by leon on 17-12-16.
     */
    public interface IProduct {
        void write();
    }
    Pencil.java
    package tinking_in_java.innerClass.FactoryMethod;
    /**
     * Created by leon on 17-12-16.
     */
    public class Pencil implements IProduct {
        String factory;
        public Pencil(String factoryName) {
            factory = factoryName;
        }
        @Override
        public void write() {
            System.out.println(factory + "制造,用铅笔写字");
        }
    }
    
     PencilFactory.java
    package tinking_in_java.innerClass.FactoryMethod;
    /**
     * Created by leon on 17-12-16.
     */
    public class PencilFactory implements IFactory {
        @Override
        public IProduct creatProduct() {
            return new Pencil("福建");
        }
    }
    Pen.java
     package tinking_in_java.innerClass.FactoryMethod;
    /**
    * Created by leon on 17-12-16.
     */
    public class Pen implements IProduct {
        String factory;
        public Pen(String factory) {
            this.factory = factory;
        }
        @Override
        public void write() {
            System.out.println(factory + "制造,使用圆珠笔写字");
        }
        public static IFactory penFactory = new IFactory() {
            @Override
            public IProduct creatProduct() {
                return new Pen("上海");
            }
        };
    }
    
    Client.java
    package tinking_in_java.innerClass.FactoryMethod;
    /**
     * Created by leon on 17-12-16.
     */
    public class Client {
        public void write() {
            //标准的工厂方法
            IFactory factory = new PencilFactory();
            IProduct pencil = factory.creatProduct();
            pencil.write();
            //用匿名内部类改造后的 工厂方法.
            IProduct pen = Pen.penFactory.creatProduct();
            pen.write();
        }
        public static void main(String[] args) {
            Client client = new Client();
            client.write();
        }
    }
    

    10.7嵌套类

    如果不需要内部对象与外围类对象有关系,可以采用将内部类申明为static.这通常称嵌套类,因为是static类型,嵌套类不保存外围对象的this引用,所以嵌套类是无法访问外围类的非static类型的变量.

    1.创建嵌套对象,不需要依赖其外围对象. 可以直接new XXX.ssss();
    2.不能从嵌套类中访问外围对象的非静态变量(对象)
    嵌套类和普通内部类的区别.普通内部类 里面不能有静态变量static xxx(可以有静态常量static final xxx,不能是对象),也不能包含嵌套类.
    但是嵌套类具备这些限制.可以使用static xxx和static final xxx

    嵌套类可以用于作为测试代码:编译的时候会单独生成一个NetsTest$Test.class ,打包时可以把这个删除即可,可以方便的测试

    package tinking_in_java.innerClass;
    /**
     * Created by leon on 17-12-16.
     */
    public class NetsTest {
        public void test() {
            System.out.println("test----");
        }
        public static class Test {
            public static void main(String[] args) {
                NetsTest netsTest = new NetsTest();
                netsTest.test();
            }
        }
    }
    

    10.7.1 接口内部的类(接口嵌套类)

    通常不能在接口中实现方法,但是可以在接口中定义嵌套类(因为在接口中).可以实现外围接口.通常接口内的常量,class 都会自动是 public static .原因在 :

    static修饰就表示它属于类的,随的类的加载而存在的,如果是非static的话,就表示属于对象的,只有建立对象时才有它,而接口是不能建立对象的,所以接口的常量必须定义为static

    package tinking_in_java.innerClass;
    /**
     * Created by leon on 17-12-16.
     */
    public interface innerInterface {
        void show();
        class inner implements innerInterface {
            @Override
            public void show() {
                System.out.println("show in interface");
            }
            public static void main(String[] args) {
                inner inner = new inner();
                inner.show();
            }
        }
    }
    

    10.7.2多重内部类

    一个内部类嵌套多少层不重要,意思是可以嵌套任意个.但是都是会关联前一个父对象.

    10.8为什么需要内部类

    内部类可以继承或者实现某些接口,内部类可以操作和访问外围类对象.所以说内部类提供了某种进入外围类的窗口.内部类可以独立继承一个(接口)的实现,与外围类是否已经继承没有关系.
    用内部类解决需要多重继承(继承多个具体类或抽象类)问题.
    如果不需要解决”多重继承”的问题可以使用其他方法.

    内部类还有一些特性:
    1.内部类可以有多个实例,并且独立,保持和外围类信息相互独立.
    2.单个外围类 可以让多个内部类以不同的方式实现同一接口,或继承同一类.
    3.创建内部类的时刻,不依赖外围类对象的创建.(内部类和外围类是同时创建的)
    4.对于实现接口或者继承关系,内部类不需要使得外围类符合is-a关系,只需要内部类符合即可.(例如在 迭代器模式中的Sequence 和selector的关系)

    10.8.1 闭包和回调(同一个意思的不同说法)

    10.9内部类的继承

    继承内部类的构造器必须指向外围对象的引用,在继承内部类的时候会比较复杂.问题在于:指向外围对象的秘密引用必须初始化,而导出类中不存在可链接的默认对象.必须采用特殊的语法.
    在导出类构造函数加外围类对象,并且 .super();

    package tinking_in_java.innerClass;
    /**
     * Created by leon on 17-12-16.
     */
    class WithInner {
        public class inner {
        }
    }
    public class InnerHertInner {
        public class InnerHerit extends WithInner.inner {
            public InnerHerit(WithInner withInner) {
                withInner.super();
            }
        }
        public static void main(String[] args) {
            InnerHertInner innerHertInner = new InnerHertInner();
        }
    }
    

    10.10内部类可以覆盖吗?

    他们是独立的类,不会有影响.

    相关文章

      网友评论

          本文标题:第十章 内部类

          本文链接:https://www.haomeiwen.com/subject/okonwxtx.html