文档章节

Java编程思想: 内部类

fzyz_sb
 fzyz_sb
发布于 2016/07/23 12:22
字数 2521
阅读 55
收藏 3

内部类的基础知识 

内部类的定义

将一个类Inner的定义放在另一个类Outer的定义内部.  则Inner的具体类型为Outer.Inner

如果要引用Inner类型, 我们需要Outer.Inner, 即在类层次上, Inner是寄生于Outer的, 任何关于Inner的操作(如构造, 调用其方法)都需要通过Outer的实例对象生成一个Inner的对象(这样才能跟Outer.Inner类型关联起来)来进行操作.

public class Outer {
	public String s;
	Outer(String s) {
		this.s = s;
	}
	class Inner {
		public void show() {
			System.out.println(s);
			System.out.println("Inner show");
		}
	}
	public Inner inner() {
		return new Inner();
	}
	public static void main(String[] args) {
		Outer o = new Outer("???");
		Outer.Inner i = o.inner();
		i.show();
		System.out.println(o.s);
		
		// ERROR
//		Outer.Inner i1 = new Outer.Inner();

		Outer.Inner i1 = o.new Inner();
		i1.show();
	}
}

就像是任何类方法内部可以使用this来调用此类的所有成员一样. 内部类也同样使用隐式的"this指针"来访问外部类的所有成员,而不需要任何特殊条件(类似动态语言如Python,JavaScript的闭包原理).

.this与.new

如果要生成对外部类的引用, 需要.this

public class DotThis {
  void f() {
    System.out.println("DotThis.f()");
  }
  public class Inner {
    public DotThis outer() {
      return DotThis.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();
  }
}

这里不能使用return DotThis. 是因为DotThis是一个类, 而DotThis.this是一个引用对象,指的是当前内部类所引用的外部对象.

如果要创建内部类的对象, 则需要.new. 在拥有外部类对象之前不可能创建内部类对象的,这是因为内部类对象会暗暗的连接到创建它的外部类对象上.

public class DotNew {
  public class Inner {
    public void show() {
      System.out.println("Inner show");
    }
  }
  public static void main(String[] args) {
    DotNew dn = new DotNew();
    DotNew.Inner dni = dn.new Inner();
    dni.show();
  }
}

 

内部类与向上转型

内部类的一个用途在于: 实现一个接口. 这样内部类可向上转型为一个接口的对象:

interface A {
  String toString();
}
public class C {
  private class B implements A {
    public String toString() {
      return getClass().getName();
    }
  }
  public static void main(String[] args) {
    C c = new C();
    C.B b = c.new B();
    System.out.println(b);
  }
}

这种设计符合组合思想,接口的实现类成为具体类的内部对象,从而很好的隐藏其实现细节.

 

内部类的使用

定义在方法中的类/定义在作用域中的类

定义在方法或作用域中的类, 主要为了解决以下情况: 方法/作用域的逻辑过于复杂, 我们需要创建一个类来辅助解决, 但又不希望这个类是公共可用的.

以下笔记主要基于下例几点:

1. 一个定义在方法中的类.

2. 一个定义在作用域内的类,此作用域在方法的内部.

3. 一个实现了接口的匿名类.

4. 一个匿名类,它扩展了有非默认构造器的类.

5. 一个匿名类,它执行字段初始化.

6. 一个匿名类,它通过实例初始化实现构造.

定义在方法中的类

interface A {
  String toString();
}
public class C {
  public A show() {
    class B implements A {
      public String toString() {
        return getClass().getName();
      }
    }
    return new B();
  }
  public static void main(String[] args) {
    C c = new C();
    // C$1B
    System.out.println(c.show());
  }
}

定义在作用域内中的内部类

interface A {
  String toString();
}
public class C {
  public String show(boolean b) {
    if (b) {
      class B implements A {
        public String toString() {
          return getClass().getName();
        }
      }
      A a = new B();
      return a.toString();
    } else {
      return "error";
    }
  }
  public static void main(String[] args) {
    C c = new C();
    System.out.println(c.show(true));
    System.out.println(c.show(false));
  }
}

 

匿名内部类

interface Contents {
  int value();
}
public class Parcel7 {
  public Contents contents() {
    return new Contents() {
      private int i = 11;
      public int value() { return i; }
    };
  }
  public static void main(String[] args) {
    Parcel7 p = new Parcel7();
    Contents c = p.contents();
    System.out.println(c.value());
  }
}

对于匿名内部类的语法解析如下: new代表新建一个对象, 调用的是Contents()构造器, 其后增加的是类的实际定义.

备注: 这跟动态语言, 如Python/JavaScript的闭包一样.

上述的匿名内部类语法就是下述形式的简化形式:

interface Contents {
	int value();
}
public class Parcel7 {
	class MyCOntents implements Contents {
		private int i = 11;
		@Override
		public int value() {
			return i;
		}
	}

	public Contents contents() { return new MyCOntents(); }

	public static void main(String[] args) {
		Parcel7 p = new Parcel7();
		Contents c = p.contents();
		System.out.println(c.value());
	}
}

 

如果匿名内部类使用了外部的参数, 那么其参数必须命名为final禁止被修改. 而在匿名类中同样可以使用实例初始化(即下例代码中大括号{}部分)来达到类似构造器的效果:

interface Contents {
  int value();
}
public class Parcel7 {
  public Contents contents(final int value) {
    return new Contents() {
      private int i;
      {
        i = value;
      }
      public int value() { return i; }
    };
  }
  public static void main(String[] args) {
    Parcel7 p = new Parcel7();
    Contents c = p.contents(42);
    System.out.println(c.value());
  }
}

使用匿名内部类可以优化其工厂方法:

interface Service {
  void method1();
  void method2();
}
interface ServiceFactory {
  Service getService();
}
class Implementation1 implements Service {
  private Implementation1() {}
  public void method1() {
    System.out.println("Implementation1 method1.");
  }
  public void method2() {
    System.out.println("Implementation1 method2.");
  }
  public static ServiceFactory factory =
    new ServiceFactory() {
      @Override
      public Service getService() {
        return new Implementation1();
      }
    };
}
class Implementation2 implements Service {
  private Implementation2() {}
  public void method1() {
    System.out.println("Implementation2 method1.");
  }
  public void method2() {
    System.out.println("Implementation2 method2.");
  }
  public static ServiceFactory factory =
    new ServiceFactory() {
      @Override
      public Service getService() {
        return new Implementation2();
      }
    };
}
public class Factories {
  public static void serviceConsume(ServiceFactory fact) {
    Service s = fact.getService();
    s.method1();
    s.method2();
  }
  public static void main(String[] args) {
    serviceConsume(Implementation1.factory);
    serviceConsume(Implementation2.factory);
  }
}

 

嵌套类

使用static声明的内部类为嵌套类, 及它跟外围类的实例对象并没有任何的关联.

当内部类为static时,意味着:

1. 要创建嵌套类的对象, 并不需要其外围类的对象.

2. 不能从嵌套类的对象中访问非静态的外围类对象.

interface A {
  String toString();
}
public class C {
  public static class B implements A {
    public String toString() {
      return getClass().getName();
    }
  }
  public static void main(String[] args) {
    A a = new C.B();
    System.out.println(a);
  }
}

假设我们需要在接口中编写通用的公用代码, 用于不同接口实现的类所公用, 那么在接口中内嵌类是非常好的方法:

interface A{
  void howdy();
  class Test implements A{
    public void howdy() {
      System.out.println("Howdy");
    }
    public static void main(String[] args) {
      new Test().howdy();
    }
  }
}

public class ClassInInterface implements A{
  public void howdy() {}
  public static void main(String[] args) {
    A.Test t = new A.Test();
    t.howdy();
  }
}

我们通常在类中编写main来测试这个类. 如果嫌麻烦我们可以使用嵌套类来实现测试代码:

public class TestBed {
  public void f() {
    System.out.println("f()");
  }
  public static class Tester {
    public static void main(String[] args) {
      TestBed t = new TestBed();
      t.f();
    }
  }
}

执行: java TestBed$Tester即可测试.

 

为什么要使用内部类

主要原因在于: 每个内部类都能独立的继承自一个(接口的)实现, 所以无论外围类是否已经继承了某个(接口的)实现, 对于内部类都没有影响.

针对多重继承, 接口只解决了部分问题, 内部类使之得到完善.

考虑以下场景: 即必须在一个类中以某种方式实现两个接口. 这时候, 我们有两个选择: 要么使用单一类(全部implements两个接口), 要么使用内部类:

interface A {
	void show();
}
interface B {
	void func();
}

class X implements A, B {
	public void show() {
		System.out.println("X show");
	}
	public void func() {
		System.out.println("X func");
	}
}

class Y implements A {
	public void show() {
		System.out.println("Y show");
	}
	B makeB() {
		// 返回一个匿名类
		return new B() {
			public void func() {
				System.out.println("Y func");
			}
		};
	}
}
public class MultiInterfaces {
	static void takeA(A a) {
		a.show();
	}
	static void takeB(B b) {
		b.func();
	}
	public static void main(String[] args) {
		X x = new X();
		Y y = new Y();
		takeA(x);
		takeA(y);
		takeB(x);
		takeB(y.makeB());
	}
}

但如果拥有抽象类或具体类,而不是接口, 则只能使用内部类才能实现多重继承.

interface A {
	void show();
}
abstract class B {
	abstract void func();
}

class Y implements A {
	public void show() {
		System.out.println("Y show");
	}
	B makeB() {
		// 返回一个匿名类
		return new B() {
			public void func() {
				System.out.println("Y func");
			}
		};
	}
}
public class MultiInterfaces {
	static void takeA(A a) {
		a.show();
	}
	static void takeB(B b) {
		b.func();
	}
	public static void main(String[] args) {
		Y y = new Y();
		takeA(y);
		takeB(y.makeB());
	}
}

 

1. 内部类可以有多个实例, 每个实例都有自己的状态信息, 并且与其外围类对象的信息相互独立.

2. 在单个外围类中, 可以让多个内部类以不同的方式实现同一个接口, 或继承同一个类.

3. 创建内部类对象并不依赖于外围类对象的创建.

4. 内部类是独立的实体.

闭包与回调

闭包是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域. 通过此定义,可以看出内部类是面向对象的闭包, 因为它不仅包含外围类对象(创建内部类的作用域)的信息, 还自动拥有一个指向此外围类对象的引用, 在此作用域内, 内部类有权操作所有的成员, 包括private成员.

内部类的继承

在继承内部类的时候, 由于内部类关联一个外部类的实例, 所以大概格式如下:

class WithInner {
	class Inner{}
}
public class InheritInner extends WithInner.Inner {
	InheritInner(WithInner wi) {
		wi.super();
	}
	public static void main(String[] args) {
		WithInner wi = new WithInner();
		InheritInner ii = new InheritInner(wi);
	}
}

内部类的覆盖

两个内部类是独立的两个实体,各自在自己的命名空间, 它们需要具体的外部类实例进行引用.

class Egg {
	private Yolk y;
	protected class Yolk {
		public Yolk() {
			System.out.println("Egg.Yolk()");
		}
	}
	public Egg() {
		System.out.println("New Egg()");
		y = new Yolk();
	}
}
public class BigEgg extends Egg {
	public class Yolk {
		public Yolk() {
			System.out.println("BigEgg Yolk()");
		}
	}
	public static void main(String[] args) {
		//New Egg()
		//Egg.Yolk()
		new BigEgg();
	}
}

局部内部类

我们可以在一个方法体的里面创建一个内部类,局部内部类不能有访问说明符,因为它不是外围类的一部分,但是它可以访问当前代码块内的常量,以及此外围类的所有成员.

下例对局部内部类与匿名内部类的创建进行了比较:

interface Counter {
	int next();
}
public class LocalInnerClass {
	private int count = 0;
	Counter getCounter(final String name) {
		class LocalCounter implements Counter {
			public LocalCounter() {
				System.out.println("LocalCounter()");
			}
			public int next() {
				System.out.print(name);
				return count++;
			}
		}
		return new LocalCounter();
	}

	Counter getCounter2(final String name) {
		return new Counter() {
			{
				System.out.println("Counter");
			}
			@Override
			public int next() {
				System.out.print(name);
				return count++;
			}
		};
	}

	public static void main(String[] args) {
		LocalInnerClass lic = new LocalInnerClass();
		Counter c1 = lic.getCounter("Local inner ");
		Counter c2 = lic.getCounter2("Anonymous inner");
		for (int i = 0; i < 5; i++)
			System.out.println(c1.next());
		for (int i = 0; i < 5; i++)
			System.out.println(c2.next());
	}
}

输出:

LocalCounter()
Counter
Local inner 0
Local inner 1
Local inner 2
Local inner 3
Local inner 4
Anonymous inner5
Anonymous inner6
Anonymous inner7
Anonymous inner8
Anonymous inner9

使用局部类而不是匿名内部类的唯一理由是: 我们需要一个已命名的构造器,或者需要重载构造器, 而匿名内部类只能用于实例初始化.

 

© 著作权归作者所有

共有 人打赏支持
fzyz_sb
粉丝 408
博文 209
码字总数 447144
作品 0
武汉
程序员
【给初学者】Android学习路线

很多朋友都对学习路线问题感到迷茫,特别是还在上学的朋友们。在这里就详细的为大家介绍一下。 1.Java基础 很多朋友一上手就开始学习Android,似乎太着急了一些。Android应用程序开发是以Jav...

Jimmy Xie
2012/09/08
0
1
Java 8里面lambda的最佳实践

Java 8已经推出一段时间了,越来越多开发人员选择升级JDK,这条热门动弹里面看出,JDK7最多,其次是6和8,这是好事! 在8 里面Lambda是最火的主题,不仅仅是因为语法的改变,更重要的是带来了...

OSC闲人
2015/04/30
0
41
Java入门需掌握的30个基本概念

(1)Easy:Java的语法比C++的相对简单,另一个方面就是Java能使软件在很小的机器上运行,基础解释其和类库的支持的大小约为40kb,增加基本的标准库和线程支持的内存需要增加125kb。 (2)分布...

风一样
2011/08/03
0
0
【目录导航】JAVA零基础进阶之路

【JAVA零基础入门系列】(已完结)导航目录 Day1 开发环境搭建 Day2 Java集成开发环境IDEA Day3 Java基本数据类型 Day4 变量与常量 Day5 Java中的运算符 Day6 Java字符串 Day7 Java输入与输出...

MFrank
06/21
0
0
Scala学习笔记(1)-环境搭建

Scala是一种多范式的编程语言,设计初衷是要集成面向对象编程和函数式编程的各种特性。scala可以和java程序无缝拼接,因为scala文件编译后也是成为.class文件,并且在JVM上运行,有下下一代J...

山海经
2013/08/25
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

centos7安装redis及开机启动

配置编译环境: sudo yum install gcc-c++ 下载源码: wget http://download.redis.io/releases/redis-3.2.8.tar.gz 解压源码: tar -zxvf redis-3.2.8.tar.gz 进入到解压目录: cd redis-3......

hotsmile
34分钟前
0
0
Confluence 6 数据库和临时目录

数据库 所有的其他数据库,包括有页面,内容都存储在数据库中。如果你安装的 Confluence 是用于评估或者你选择使用的是 Embedded H2 Database 数据库。数据库有关的文件将会存储在 database...

honeymose
48分钟前
1
0
day62-20180820-流利阅读笔记

1.今日导读 2.带着问题听讲解 3.新闻正文(中英文对照) 4.重点词汇 5.拓展内容

aibinxiao
58分钟前
0
0
分布式锁实现及对比

一、问题介绍 日常工作中很多场景下需要用到分布式锁,例如:任务运行(多个节点同一时刻同一个任务只能在一个节点上运行(分片任务除外)),交易接受(前端交易请求发送时,可能由于两次提...

yangjianzhou
今天
3
0
【AI实战】快速掌握TensorFlow(二):计算图、会话

在前面的文章中,我们已经完成了AI基础环境的搭建(见文章:Ubuntu + Anaconda + TensorFlow + GPU + PyCharm搭建AI基础环境),以及初步了解了TensorFlow的特点和基本操作(见文章:快速掌握...

雪饼
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部