Java编程思想学习录(连载之:内部类)

Thinkpad 25 Anniversary

用thinkpad打字确实很爽啊!


Thinking in java系列博文目录:

注: 本文原载于 My Personal Blog:CodeSheep · 程序羊


内部类基本概念

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

一个典型的例子:利用 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() );
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,743评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,296评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,285评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,485评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,581评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,821评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,960评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,719评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,186评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,516评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,650评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,936评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,757评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,991评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,370评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,527评论 2 349

推荐阅读更多精彩内容

  • 今天整理一下内部类,其中包含了内部类的特殊形式,对比普通类有什么区别和作用,内部类和外围类之间的联系,内部类的扩展...
    _小二_阅读 608评论 0 3
  • 第一章 对象导论 对象具有状态、行为和标识。这意味着每一个对象都可以拥有内部数据和方法,并且每一个对象都可以唯一地...
    niaoge2016阅读 819评论 0 0
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,598评论 18 399
  • 03103 黄惠娟 情感,它能让人变得软弱,它也能让人变得坚强。我记得《我的爸爸叫焦尼》这本书是当时阿渡...
    儒清雅阅读 363评论 0 3
  • 作者:洁处无荒 2017年3月底,发现怀孕,家人和我很开心。一晃几个月了,时间过得真快,肚子也见大。 初期也没有特...
    刀笔伐心阅读 183评论 0 3