第10章 内部类
所谓内部类,就是把一个类的定义放在另外一个类的内部。(前面Cha9中的接口就可以这么做,类当然也可以这么做。)
官方定义给的是:内部类允许把逻辑相关的类组织在一起,并控制内部类的可见性。
10.1 创建内部类
书上给了两个例子,这里我们看一下第2个例子。
这个例子给出了一个经典的情况,外部类有一个方法,返回一个指向内部类的应用。正如下面代码中的 to() 和 contents() 方法那样。
package innerClasses;
public class Parcel2 {
class Contents{
private int i = 11;
public int value(){return i;}
}
class Destination{
private String label;
Destination(String whereTo){
label = whereTo;
}
String readLabel(){return label;}
}
public Destination to(String s){ // to()方法用来生成Des类
return new Destination(s);
}
public Contents contents(){return new Contents();} // 同上
public void ship(String dest){
Contents c = contents();
Destination d = to(dest);
System.out.println(d.readLabel());
}
public static void main(String args[]){
Parcel2 p = new Parcel2();
p.ship("Tasmania");
Parcel2 q = new Parcel2();
//
Parcel2.Contents c = q.contents();
Parcel2.Destination d = q.to("FUxing");
}
}
/*output
Tasmania
*/
10.2 链接到外部类
生成一个内部类的对象时,内部类可以访问外围类的所有方法和字段。看下面这个例子。
package innerClasses;
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;
}
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(); // 向上转型
while(!selector.end()){ // 绑定调用方法
System.out.print(selector.current() +" ");
selector.next();
}
}
}
/*outputs
0 1 2 3 4 5 6 7 8 9
*/
10.3 使用 .this 与 .new
生成对 外部类对象的引用,可以使用 ClassName.this
package innerClasses;
public class DotThis {
void f(){System.out.println("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(); // 内部类对象的 outer()方法通过
// 对外部类对象的引用生成一个外部类,调用其 f()方法
}
}
/*outputs
DotThis.f()
*/
如果要创建某个内部类对象,需要使用 [外部对象.new 内部类名]
package innerClasses;
public class Parcel3 {
class Contents{
private int i = 11;
public int value(){return i;}
}
class Destination{
private String lable;
Destination(String whereTo) {lable = whereTo;}
String readLable(){ return lable;}
}
public static void main(String args[]){
Parcel3 p = new Parcel3();
// 创建 内部类对象 使用 外部类对象.new 内部类对象 语法。
Parcel3.Contents c = p.new Contents();
Parcel3.Destination d = p.new Destination("Fuxing");
}
}
10.4 内部类与向上转型
10.2 中的代码例子中就包含向上转型。
这里书上给的代码说明了一下内部私有类的保护类,和私有类与保护类的意思基本一样。
10.5 在方法和作用域内的内部类(局部内部类)
这里说了两个点:
- 方法内部的类
package innerClasses;
// 局部内部类的 Demo
// 方法内部的类
public class Parcel5 {
public Destination destination(String s){ // dest() 方法
class PDestination implements Destination{ // 局部内部类,只能在方法内部访问
private String label;
private PDestination(String whereTo){
label = whereTo;
}
public String readLabel(){return label;}
}
return new PDestination(s); // 方法内部访问局部内部类
// 这并不是 dest()方法执行完,局部内部类就不可用了
}
public static void main(String args[]){
Parcel5 p = new Parcel5();
Destination d = p.destination("FUxing");
}
}
- 作用域内部的类,与上面唯一的不同就是不能在范围之外调用。
package innerClasses;
// 在任意作用域内嵌入一个内部类
public class Parcel6 {
private void internalTracking(boolean b){
if(b){
class TrackingSlip{
private String id;
TrackingSlip(String s){
id = s;
}
String getSlip(){return id;}
}
TrackingSlip ts = new TrackingSlip("Slip");
String s = ts.getSlip();
}
// 不能在范围外使用
//! TrackingSlip ts = new TrackingSlip("x")
}
public void track(){internalTracking(true);} // 通过调用方法来调用局部内部类
public static void main(String args[]){
Parcel6 p = new Parcel6();
p.track();
}
}
10.5 匿名内部类
很多时候,我们有些类只是用一次,这时候再去写一个类的定义,岂不是很麻烦。所以就有了匿名内部类的概念,就是说没有名字的内部类,只能使用一次,通常用简化代码编写。
将 内部类的 生成 和 定义 结合在一起。
这个东西第一次理解有一点抽象:
package innerClasses;
// 匿名内部类
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();
}
}
/* 将返回值的生成 和 返回值类的定义 结合在一起
* 上面的代码是下面的简写版本
* public MyContents implements Contents{
* private int i = 11;
* public int value(){return i;}
* }
* public Contents contents(){return new MyContents();}
*/
注意这里的类是匿名的。这种语法好像是说,正在创建一个新的Contents对象,但是(在分号结束之前),你却说:等一等,在这里加入一个类的定义(大括号中的就是匿名类的定义)。
- 如果匿名内部类定义的内部直接使用外部的参数,那么这个参数需要被定义成为 final 的。
package innerClasses;
// 匿名内部类内部使用到外部的参数,这些参数需要用 final 修饰
public class Parcel10 {
public Destination destination(final String dest, final float price){
return new Destination(){
private int cost;
{ // 匿名内部类中,需要有 花括号括起来这些没有方法名的代码
cost = Math.round(price);
if(cost>100)
System.out.println("Over budget");
}
private String label;
public String readLabel(){return label;}
};
}
public static void main(String args[]){
Parcel10 p = new Parcel10();
Destination d = p.destination("Fuxing",200);
}
}
/*output
Over budget
*/
- 使用实例初始化,实现匿名内部类的构造器
package innerClasses;
// 含参构造器的匿名内部类
abstract class Base{
public Base(int i){
System.out.println("Base Cons i: " + i);
}
public abstract void f();
}
public class AnonymousCons {
public static Base getBase(int i){
return new Base(i){ // 这里的 i 没有直接在匿名类内部使用。
// 不用加 final 参数
{System.out.println("内部实例初始化");}
public void f(){System.out.println("内部匿名f()");}
};
}
public static void main(String args[]){
Base base = getBase(47); // 静态方法直接调用
base.f();
}
}
/*output
Base Cons i: 47
内部实例初始化
内部匿名f()
*/
10.5.1 用匿名内部类实现工厂方法
之前在第9章中说过工厂方法,所谓工厂方法就是用一个方法来生成接口对象。
这里也是一样的,不过用匿名内部类的方式来实现,也是把 内部类 生成 和 定义 结合在一起。看下面的代码(对比之前第9章中的代码)
package innerClasses;
// 用匿名内部类来实现工厂方法
interface Service{
void method1();
void method2();
}
interface ServiceFact{
Service getService();
}
class Implementation1 implements Service{
private Implementation1(){}
public void method1() {System.out.println("实现1.方法1");}
public void method2() {System.out.println("实现1.方法2");}
public static ServiceFact factory =
new ServiceFact(){ // 将 内部类的生成 和 定义 结合在一起。
public Service getService(){
return new Implementation1();}
};
}
class Implementation2 implements Service{
private Implementation2(){}
public void method1() {System.out.println("实现2.方法1");}
public void method2() {System.out.println("实现2.方法2");}
public static ServiceFact factory = // 这里用 static 声明单一静态变量
new ServiceFact(){
public Service getService(){
return new Implementation2();
}
};
}
public class Factories {
public static void serviceConsumer(ServiceFact fact){
Service s = fact.getService();
s.method1();
s.method2();
}
public static void main(String args[]){
serviceConsumer(Implementation1.factory);
serviceConsumer(Implementation2.factory); // 静态变量
}
}
/*output
实现1.方法1
实现1.方法2
实现2.方法1
实现2.方法2
*/
10.7 嵌套类
之前我们说到,创建一个内部类对象的时候需要用 [外部类对象.new 内部类名]这样的方法。而当不需要内部类对象和外部类对象之间的关系的时候,我们就需要用到嵌套类。即内部类声明为 static。
嵌套类意味着:
- 创建嵌套类对象,不需要外围类对象
- 不能从嵌套类对象中访问非静态的外围类对象
package innerClasses;
// 嵌套类,是 static 的类,可以直接调用,不用外部类对象来生成内部类对象
public class Parcel_11 {
private static class ParcelContents implements Contents{
private int i = 11;
public int value(){return i;}
}
protected static class ParcelDestination implements Destination{
private String label;
private ParcelDestination(String whereTo){
label = whereTo;
}
public String readLabel(){ return label; }
// 可以含有 static 元素
public static void f(){}
static int x = 10;
}
public static Destination destination(String s){
return new ParcelDestination("Fuxing");
}
public static Contents contents(){return new ParcelContents();}
public static void main(String args[]){
// 在这个main()函数中,没有任何 Parcell_11对象时必须的
// 这就是 static 类的优势
Contents c = contents();
Destination d = destination("Fuxing");
}
}
10.7.1 接口内部的类
之前说过,接口的内部只能有方法名,而不能有其他代码。这里有一个特例,嵌套类可以存在于接口内部。接口中的类都自动设置为public 和 static。
知道有这个东西就行了
10.7.2 从多层嵌套类中访问外部类的成员
意思是说,不管嵌套多少层,内部类都可以访问外层类的所有方法和数据成员,正如 10.2 所讲的那样。
10.8 为什么需要内部类
官方定义是:
每个内部类都可以独立的继承自一个(接口的)实现,无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
基于上面这个定义,内部类在实现 多继承 结构时,优点就显现出来了。
书上给了两个例子:
- 这个例子中,使用单一类和内部类都可以实现多继承结构。
package innerClasses;
interface A {}
interface B {}
class X implements A,B{} // 单一类实现多继承
class Y implements A{ // 内部类实现多继承
B makeB(){ // 匿名内部类
return new B(){};
};
}
public class MultipleInterface {
static void takesA(A a){}
static void takesB(B b){}
public static void main(String args[]){
X x = new X();
Y y = new Y();
takesA(x);
takesA(y);
takesB(x);
takesB(y.makeB());
}
}
- 如果是含有抽象的类或者具体的类,而不是接口,就只能用内部类的形式来实现多继承。
package innerClasses;
class D{}
abstract class E{}
class Z extends D{
E getE(){
return new E(){}; // 因为这个是没有实现的抽象类,
// 所以要加这个大括号
};
}
public class MultipleImple {
static void takesD(D d){};
static void takesE(E e){};
public static void main(String args[]){
Z z = new Z();
takesD(z);
takesE(z.getE());
}
}
记住内部类在实现多继承结构时候有着独特的优势。
10.8.1 闭包 closure 与 回调 callback
这一节书上说的东西基本看不懂。给的例子也不懂要说什么意思,只是明白运行过程而已。
之后在别人的博文中大概理解了一下闭包和回调。
- 闭包:闭包能够将一个 方法 作为一个 变量 去存储,这个方法有能力去访问所在类的自由变量。
具体关于闭包的东西还没看懂
- 回调:类A的 a()方法调用 类 B 的 b() 方法,然后B中的方法(可以是b方法)再调用 A 的 c() 方法。
参考 如何理解回调
10.9 内部类的继承
既然是类,就可以继承。但是内部类的继承遵循一定的规则,必须在构造器内使用特殊语法,使得继承内部类指向外围类的引用。
package innerClasses;
class Mike{
class Inner{}
}
// 继承内部类
public class InheritInnerClass extends Mike.Inner{
InheritInnerClass(Mike m){ // 构造函数一定写成这样的语法
m.super();
}
public static void main(String args[]){
Mike m = new Mike();
InheritInnerClass i = new InheritInnerClass(m);
}
}
10.11 局部内部类
上面10.5小节提到过,对于定义在方法和域内的内部类,叫做局部内部类。书上在这里专门给了一个小节,来对比了一下局部内部类和匿名内部类的创建方式的不同之处。
package innerClasses;
// 对比一下局部内部类和匿名内部类创建方式的区别
interface Counter{
int next();
}
public class LocalInnerClass {
private int count = 0;
Counter getCount(final String name){ // 之前说过这里必须是final,供内部类调用
// 一个局部内部类
class LocalCounter implements Counter{
public LocalCounter(){
// 局部内部类可以有一个构造器
System.out.println("LocalCounter()");
}
public int next(){
System.out.println(name);
return count++;
}
}
return new LocalCounter();
}
/*上面是局部内部类的创建方式,下面看匿名类的创建方式*/
Counter getCount2(final String name){
return new Counter(){
{System.out.println("Counter");}
public int next(){
System.out.println(name);
return count++;
}
};
}
public static void main(String args[]){
LocalInnerClass lic = new LocalInnerClass();
Counter
c1 = lic.getCount("局部内部类"),
c2 = lic.getCount2("匿名内部类");
for(int i=0;i<5;i++) System.out.print(c1.next());
for(int i=0;i<5;i++) System.out.print(c2.next());
}
}
其实上述代码中的两种情况之前都讲过了。这里大概看一下就行了。
10.13 总结
总的来说,这一项内部类讲了很多东西,感觉有点乱,也不是很有条理,不是很明白作者的逻辑。
除了书上,还可以通过关于内部类的总结来学习一下,该博文将内部类总结为:成员内部类、局部内部类、匿名内部类、静态内部类这四种。其实在本章的读书笔记中都有,不过正如之前所说,很乱。可以参考别人的总结学习一下,事半功倍。
网友评论