V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
hansonwang99
V2EX  ›  推广

修炼 Java 之:内部类剖析

  •  
  •   hansonwang99 · 2018-04-13 13:17:58 +08:00 · 1635 次点击
    这是一个创建于 2176 天前的主题,其中的信息可能已经有所发展或是发生改变。

    file

    用 Thinkpad 25 周年纪念版打字确实很爽啊!


    内部类基本概念

    • 可将一个类的定义置于另一个类定义的内部
    • 内部类允许将逻辑相关的类组织在一起,并控制位于内部的类的可见性
    • 甚至可将内部类定义于一个方法或者任意作用域内!
    • 当然,内部类 ≠ 组合
    • 内部类拥有其外围类 所有元素的 访问权
    • 更有甚,嵌套多层的内部类能透明地访问所有它所嵌入的外围类的所有成员

    一个典型的例子:利用 Java 内部类 实现的 迭代器模式

    // 接口
    -------------------------------------------------------------
    public 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;
      } 
    
      // 迭代器类:实现了 Selector 接口的 内部类
      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(); // 获取 iterator !
        while( !selector.end() ) {
          print( selector.current() + " " );
          selector.next();
        }
      }
    }
    // 输出
    -------------------------------------------------------------
    0 1 2 3 4 5 6 7 8 9
    

    .this 与 .new 的使用场景

    .this 用于在内部类中生成对其外部类对象的引用之时,举例:

    public class DotThis {
      
      void f() { print("DotThis.f()"); }
      
      public class Inner { // 内部类
        public DotThis outer() { // 返回外部类对象的引用
          return DotThis.this; // 若直接返回 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(); // 输出 DotThis.f()
      }
    }
    

    .new 用于直接创建内部类的对象之时,距离:

    public class DotNew {
      public class Inner { } // 空内部类
      public static void main( String[] args ) {
        DotNew dn = new DotNew();
        DotNew.Inner dni = dn.new Inner(); //注意此处必须使用外部类的对象,而不能直接 DotNew.Inner dni = new DotNew.Inner()
      }
    }
    

    嵌套类( static 类型的内部类)

    嵌套类是无需依赖其外部类的对象的。非 static 内部类通过一个特殊的 this 链接到其外围类的对象,而 static 类型的内部类无此 this 引用。

    接口与内部类有着很有趣的关系: 放到接口中的任何类自动都是 public 且 static,即接口中的任何类都是嵌套类,我们甚至可以在接口的内部类中去实现其外围接口,举例:

    public interface ClassInInterface {
      void howdy();
      class Test implements ClassInInterface { // 类 Test 默认 static,所以是嵌套类
        public void howdy() {
          print("Howdy!");
        }
        public static void main( String[] args ) {
          new Test().howdy(); 
        }
      }
    }
    

    方法作用域 内的内部类

    可以称这类为 局部内部类

    方法中定义的内部类只能在方法内被使用,方法之外不可访问,举例:

    public class Parcel {  // parcel 是“包裹”之意
      
      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 ); // 只有在方法中才能访问内部类 PDestination
      }
    
      public static void main( String[] args ) {
        Parcel p = new Parcel();
        Destination d = p.destination( "Hello" );
        ...
      }
    }
    

    更进一步,可在任意作用域内定义内部类,举例:

    public class Parcel {
      
      private void internalTracking( boolean b ) {
        
        if( b ) { // 局部作用域中定义了内部类,作用域之外不可访问!
          class TrackingSlip {
            private String id;
            TrackingSlip( String s ) { id = s; }
            String getSlip() { return id; }
          }
        }
    
      }
      
      public void track() { interTracking( true ); }
    
      public static void main( String[] args ) {
        Parcel p = new Parcel();
        p.track();
      }
    }
    

    匿名内部类

    直观上看,这种内部类没有“名字”,举例:

    public class Parcel {
      
      public Contents contents() {
        return new Contents() { // 此即匿名内部类!!!
          private int i = 11;
          public int value() { return i; }
        }; // !!!注意这里必须要加分号!!!
      }
    
      public static void main( String[] args ) {
        Parcel p = new Parcel();
        Contents c = p.contents();
      }
    }
    

    若想将外部的参数传到匿名内部类中(典型的如将外部参数用于对匿名内部类中的定义字段进行初始化时)使用的话,该参数必须 final,举例:

    public class Parcel {
      
      public Destination destination( final String s ) { // final 必须!
        return new Destination() {
          private String label = s;
          public String readLabel() { return label; }
        }; // 分号必须!
      }
    
      public static void mian( String[] args ) {
        Parcel p = new Parcel();
        Destination d = p.destination("Hello");
      }
    }
    

    匿名内部类中不可能有命名的显式构造器,此时只能使用实例初始化的方式来模仿,举例(当然下面这个例子还反映了匿名内部类如何参与继承):

    // 基类
    ---------------------------------------------
    abstact class Base() {
      public Base( int i ) {
        print( "Base ctor, i = " + i );
      }
      public abstract void f();
    }
    
    //主类(其中包含了继承上面 Base 的派生匿名内部类!)
    ----------------------------------------------
    public class AnonymousConstructor {
      
      public static Base getBase( int i ) { // 该处参数无需 final,因为并未在下面的内部类中直接使用!
        return new Base(i){ // 匿名内部类
          { // 实例初始化语法!!!
            print("Inside instance initializer");
          }
          public void f() { 
            print( "In anonymous f()" );
          }
        }; // 分号必须!
      }
    
      public static void main( String[] args ) {
        Base base = getBase(47);
        base.f();
      }
    }
    
    // 输出
    ------------------------------------------
    Base ctor, i = 47 // 先基类
    Inside instance initializer // 再打印派生类
    In anonymous f()
    

    匿名内部类 + 工厂模式 = 更加简洁易懂:

    // Service 接口
    ---------------------------------------------------
    interface Service {
      void method1();
      void method2();
    }
    // ServiceFactory 接口
    ---------------------------------------------------
    interface ServiceFactory {
      Service getService();
    }
    // Service 接口的实现
    ---------------------------------------------------
    class Implementation1 implements Service {
      private Implementation1() {} // 构造函数私有
      public void method1() { print("Implementation1 method1"); }
      public void method2() { print("Implementation1 method2"); }
      public static ServiceFactory factory = 
        new ServiceFactory() {
          public Service getService() {
            return new Implementation1();
          }
        }; // 分号必须!!!
    }
    
    class Implementation2 implements Service {
      private Implementation2() {}
      public void method1() { print("Implementation2 method1"); }
      public void method2() { print("Implementation2 method2"); }
      public static ServiceFactory factory = 
        new ServiceFactory() {
          public Service getService() {
            return new Implementation2();
          }
        }; // 分号必须!!!
    }
    // 客户端代码
    ----------------------------------------------------
    public class Factories {
      public static void serviceConsumer( ServiceFactory fact ) {
        Service s = fact.getService();
        s.method1();
        s.method2();
      }
    
      public static void main( String[] args ) {
        serviceComsumer( Implementation1.factory );
        serviceComsumer( Implementation2.factory );
      }
    }
    

    总结:为什么需要内部类

    内部类可以独立地继承自一个接口或者类而无需关注其外围类的实现,这使得扩展类或者接口更加灵活,控制的粒度也可以更细!

    注意 Java 中还有一个细节:虽然 Java 中一个接口可以继承多个接口,但是一个类是不能继承多个类的!要想完成该特性,此时除了使用内部类来“扩充多重继承机制”,你可能别无选择,举例:

    class D { }               // 普通类
    abstract class E { }      // 抽象类
    
    class Z extend D {    // 外围类显式地完成一部分继承
      E makeE() {
        return new E() { }; // 内部类隐式地完成一部分继承
      }
    }
    
    public class MultiImplementation {
      static void takesD( D d ) { }
      static void takesE( E e ) { }
      public static void main( String[] args ) {
        Z z = new Z();
        takesD( z );
        takesE( z.makeE() );
      }
    }
    

    CodeSheep


    作者更多原创文章在 V2EX

    Thinking in java 系列博文目录:

    2 条回复    2018-04-13 22:51:25 +08:00
    jeffson
        1
    jeffson  
       2018-04-13 14:27:35 +08:00
    不错不错
    zonghua
        2
    zonghua  
       2018-04-13 22:51:25 +08:00
    没讲内部匿名类地内存回收的影响
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2914 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 00:30 · PVG 08:30 · LAX 17:30 · JFK 20:30
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.