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

Thinkpad 25 Anniversary

用thinkpad打字确实很爽啊!

注: 本文首发于 My 公众号 CodeSheep ,可 长按扫描 下面的 小心心 来订阅 ↓ ↓ ↓

CodeSheep · 程序羊



内部类基本概念

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

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

1// 接口 2------------------------------------------------------------- 3public interface Selector { 4 boolean end(); 5 Object current(); 6 void next(); 7} 8// 外部类(集合类) + 内部类(迭代器类) 9------------------------------------------------------------- 10public class Sequence { // 外部类(代表一个集合类) 11 12 private Object[] items; 13 private int next = 0; 14 15 public Sequence( int size ) { 16 items = new Object[size]; 17 } 18 19 public void add( Object x ) { 20 if( next < items.length ) 21 items[next++] = x; 22 } 23 24 // 迭代器类:实现了 Selector接口的 内部类 25 private class SequenceSelector implements Selector { 26 private int i = 0; 27 public boolean end() { return i == items.length; } 28 public Object current() { return items[i]; } 29 public void next() { 30 if( i<items.length ) 31 ++i; 32 } 33 } 34 35 public Selector selector() { // 该函数也表明了:内部类也可以向上转型,这样在外部就隐藏了实现细节! 36 return new SequenceSelector(); 37 } 38 39 public static void main( String[] args ) { 40 Sequence sequence = new Sequence(10); 41 for( int i=0; i<10; ++i ) { // 装填元素 42 sequence.add( Integer.toString(i) ); 43 } 44 Selector selector = sequence.selector(); // 获取iterator! 45 while( !selector.end() ) { 46 print( selector.current() + " " ); 47 selector.next(); 48 } 49 } 50} 51// 输出 52------------------------------------------------------------- 530 1 2 3 4 5 6 7 8 9

.this 与 .new 的使用场景

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

1public class DotThis { 2 3 void f() { print("DotThis.f()"); } 4 5 public class Inner { // 内部类 6 public DotThis outer() { // 返回外部类对象的引用 7 return DotThis.this; // 若直接返回this,那指的便是内部类自身 8 } 9 } 10 11 public Inner inner() { return new Inner(); } 12 13 public static void main( String[] args ) { 14 DotThis dt = new DotThis(); 15 DotThis.Inner dti = dt.inner(); 16 dti.outer().f(); // 输出 DotThis.f() 17 } 18}

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

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

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

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

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

1public interface ClassInInterface { 2 void howdy(); 3 class Test implements ClassInInterface { // 类Test默认static,所以是嵌套类 4 public void howdy() { 5 print("Howdy!"); 6 } 7 public static void main( String[] args ) { 8 new Test().howdy(); 9 } 10 } 11}

方法作用域 内的内部类

可以称这类为 局部内部类

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

1public class Parcel { // parcel是“包裹”之意 2 3 public Destination destination( String s ) { 4 5 class PDestination implements Destination { // 方法中定义的内部类 6 private String label; 7 private PDestination( String whereTo ) { label = whereTo; } 8 public String readLabel() { return label; } 9 } 10 11 return new PDestination( s ); // 只有在方法中才能访问内部类PDestination 12 } 13 14 public static void main( String[] args ) { 15 Parcel p = new Parcel(); 16 Destination d = p.destination( "Hello" ); 17 ... 18 } 19}

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

1public class Parcel { 2 3 private void internalTracking( boolean b ) { 4 5 if( b ) { // 局部作用域中定义了内部类,作用域之外不可访问! 6 class TrackingSlip { 7 private String id; 8 TrackingSlip( String s ) { id = s; } 9 String getSlip() { return id; } 10 } 11 } 12 13 } 14 15 public void track() { interTracking( true ); } 16 17 public static void main( String[] args ) { 18 Parcel p = new Parcel(); 19 p.track(); 20 } 21}

匿名内部类

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

1public class Parcel { 2 3 public Contents contents() { 4 return new Contents() { // 此即匿名内部类!!! 5 private int i = 11; 6 public int value() { return i; } 7 }; // !!!注意这里必须要加分号!!! 8 } 9 10 public static void main( String[] args ) { 11 Parcel p = new Parcel(); 12 Contents c = p.contents(); 13 } 14}

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

1public class Parcel { 2 3 public Destination destination( final String s ) { // final必须! 4 return new Destination() { 5 private String label = s; 6 public String readLabel() { return label; } 7 }; // 分号必须! 8 } 9 10 public static void mian( String[] args ) { 11 Parcel p = new Parcel(); 12 Destination d = p.destination("Hello"); 13 } 14}

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

1// 基类 2--------------------------------------------- 3abstact class Base() { 4 public Base( int i ) { 5 print( "Base ctor, i = " + i ); 6 } 7 public abstract void f(); 8} 9 10//主类(其中包含了继承上面Base的派生匿名内部类!) 11---------------------------------------------- 12public class AnonymousConstructor { 13 14 public static Base getBase( int i ) { // 该处参数无需final,因为并未在下面的内部类中直接使用! 15 return new Base(i){ // 匿名内部类 16 { // 实例初始化语法!!! 17 print("Inside instance initializer"); 18 } 19 public void f() { 20 print( "In anonymous f()" ); 21 } 22 }; // 分号必须! 23 } 24 25 public static void main( String[] args ) { 26 Base base = getBase(47); 27 base.f(); 28 } 29} 30 31// 输出 32------------------------------------------ 33Base ctor, i = 47 // 先基类 34Inside instance initializer // 再打印派生类 35In anonymous f()

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

1// Service接口 2--------------------------------------------------- 3interface Service { 4 void method1(); 5 void method2(); 6} 7// ServiceFactory接口 8--------------------------------------------------- 9interface ServiceFactory { 10 Service getService(); 11} 12// Service接口的实现 13--------------------------------------------------- 14class Implementation1 implements Service { 15 private Implementation1() {} // 构造函数私有 16 public void method1() { print("Implementation1 method1"); } 17 public void method2() { print("Implementation1 method2"); } 18 public static ServiceFactory factory = 19 new ServiceFactory() { 20 public Service getService() { 21 return new Implementation1(); 22 } 23 }; // 分号必须!!! 24} 25 26class Implementation2 implements Service { 27 private Implementation2() {} 28 public void method1() { print("Implementation2 method1"); } 29 public void method2() { print("Implementation2 method2"); } 30 public static ServiceFactory factory = 31 new ServiceFactory() { 32 public Service getService() { 33 return new Implementation2(); 34 } 35 }; // 分号必须!!! 36} 37// 客户端代码 38---------------------------------------------------- 39public class Factories { 40 public static void serviceConsumer( ServiceFactory fact ) { 41 Service s = fact.getService(); 42 s.method1(); 43 s.method2(); 44 } 45 46 public static void main( String[] args ) { 47 serviceComsumer( Implementation1.factory ); 48 serviceComsumer( Implementation2.factory ); 49 } 50}

总结:为什么需要内部类

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

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

1class D { } // 普通类 2abstract class E { } // 抽象类 3 4class Z extend D { // 外围类显式地完成一部分继承 5 E makeE() { 6 return new E() { }; // 内部类隐式地完成一部分继承 7 } 8} 9 10public class MultiImplementation { 11 static void takesD( D d ) { } 12 static void takesE( E e ) { } 13 public static void main( String[] args ) { 14 Z z = new Z(); 15 takesD( z ); 16 takesE( z.makeE() ); 17 } 18}
点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )