inner class in java

可以将一个类的定义放在另一个类的定义内部,这就是内部类。

先准备两个接口,代码如下。
接口Contents

package com.cn.thk.innerClass;

public interface Contents {
	Integer value();
}

接口Destination

package com.cn.thk.innerClass;

public interface Destination {
	String readLabel();
}

标识符

Java 中每一个类都会产生一个.class问价,其中包括了如果创建该类型的全部信息(此信息产生一个meta-class,叫做class对象),内部类也必须生成一个.class文件已包含他们的Class对象信息。这类文件的命名规则:外围类的名字,加上$,再加上内部类的名字。类似OuterClass$InnerClass.class。如果内部类是匿名的,编译器会简单的产生一个数字做为其标识符。

创建内部类

当生成一个内部类对象的时候,此对象与制造它的外围对象之间就有了一种联系,所以它能访问其外围对象的所有元素,而不需要任何特殊条件。这是如何做到?当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密捕获一个指向外围类对象的引用。然后当你访问此外部类的成员时,就是用哪个引用来选择外部类的成员。

创建内部类对象的时候,必须使用外部类的对象来创建内部类,而不是直接去 new。在拥有外部类对象之前是不可能创建内部类对象的。因为内部类对象会暗暗地连接到创建它的外部类对象上。

如果需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟原点和this。这样产生的引用自动地具有正确的类型。

需要注意的是普通内部类是不能有 static 方法和数据的。

接口 Selector

package com.cn.thk.innerClass;

public interface Selector {
    boolean end();
    Object current();
    void next();
}

外部类Sequence

package com.cn.thk.innerClass;

public class Sequence {
	private Object[] items;
	private Integer next = 0;
	public Sequence(Integer size){
		items = new Object[size];
	}
	public void add(Object x){
		if(next<items.length){
			items[next]=x;
			next+=1;
		}
	}
	// 所有内部类自动拥有对其外围类所有成员的访问权限。
	// 当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密捕获一个指向外围类对象的引用。
	// 然后当你访问此外部类的成员时,就是用哪个引用来选择外部类的成员
	class SequenceSelector implements Selector{
		private Integer i=0;
		/**
		 * 如果需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟原点和this.
		 * 这样就返回内部类对外部类对象的引用
		 * */
		public Sequence getOuterObject(){
			return Sequence.this;
		}
		@Override
		public boolean end() {
			return i==items.length;
		}
		
		@Override
		public Object current() {
			return items[i];
		}
		
		@Override
		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(i);
		}
		/**
		 * 创建内部类对象的时候,必须使用外部类的对象来创建内部类,而不是直接去 new。
		 * 在拥有外部类对象之前是不可能创建内部类对象的。因为内部类对象会暗暗地连接到创建它的外部类对象上。
		 * */
		Sequence.SequenceSelector selector = sequence.new SequenceSelector();
//		Sequence.SequenceSelector selector = (Sequence.SequenceSelector)sequence.selector();
		while (!selector.end()){
			System.out.println(selector.current());
			selector.next();
		}
		// 内部类对象引用的外部对象就是创建内部类对象时的外部对象
		System.out.println(sequence==selector.getOuterObject()); // true
	}
}

局部内部类

在方法或者作用域内定义的内部类,被称为局部内部类,局部内部类只能在方法范围内或者作用域范围内使用。

package com.cn.thk.innerClass;
public class Parcel {
	/*
	 * 定义在作用域内的内部类
	 * */
	public void internalTracking(boolean b) {
		// 这里并不是说该内部类的创建是有条件的,它其实和别的类一起编译过了,虽然已经和别的类一起编译过了,
		// TrackingSlip在作用域之外,依然是不可用的,除此之外,和普通类是一样的
		// 
		if (b) {
			class TrackingSlip {
				private String id;
				
				TrackingSlip(String s) {
					id = s;
				}
				
				String getSlip() {
					return id.toUpperCase();
				}
			}
			TrackingSlip trackingSlip = new TrackingSlip("slip bb cc");
			String s = trackingSlip.getSlip();
			System.out.println(s);
		}
	}
	
	/*
	 * 定义在方法内的内部类
	 * */
	public Destination destination(String label) {
		class PDestination implements Destination {
			private String label;
			
			public PDestination(String label) {
				this.label = label;
			}
			
			@Override
			public String readLabel() {
				return label;
			}
		}
		return new PDestination(label);
	}
}

匿名内部类

匿名内部类是局部内部类的一个特例。

如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用的是 final 的,因为参数是在匿名内部类内部使用的

package com.cn.thk.innerClass;
abstract class Base {
	public Base(int i) {
		System.out.println("Base constructor , i= " + i);
	}
	
	public abstract void f();
}
public class Parcel {
	/**
	 * 这里其实在创建一个继承自 Contents 接口的匿名内部类的对象。在这个匿名内部类中,使用了默认构造器来生成 Contents。
	 * 如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,
	 * 那么编译器会要求其参数引用的是 final 的,因为参数是在匿名内部类内部使用的
	 */
	public Contents contents(final Integer value) {
		return new Contents() {
			private Integer i = value;
			
			@Override
			public Integer value() {
				return i;
			}
		};
	}
	/**
	 * 在这个例子中,匿名内部类使用了有参构造器来生成对象。并通过实例初始化代码块来完成命名构造器的效果。
	 * 
	 * 在匿名类中不可能有命名构造器(因为它根本没有名字),但是通过实例初始化,就能够达到转为
	 * 内部内部类创建一个构造器的效果,在这里,不要求变量 i 是final,因为 i 被传递给匿名类的基类的构造器,
	 * 它并不会在匿名类内部直接使用。
	 */
	public Base getBase(int i) {
		return new Base(i) {
			{
				System.out.println("Inside instance init i  " + i);
			}
			
			@Override
			public void f() {
				System.out.println("In anonymous f()");
			}
		};
	}
}	

局部内部类VS匿名内部类

通过上面的示例可以看出,绝大多数情况下,两种内部类具有相同的行为和能力。有些情况局部内部类可能更有优势。

  • 需要命名构造器,或者需要重载构造器的时候,需要使用局部内部类,因为匿名内部类只能做到初始化实例
  • 当需要多个内部类对象的时候,局部内部类会使代码更好看。

嵌套类/静态内部类

如果不需要内部类对象和外部类对象有联系,那么内部类对象可以声明为static。这通常称之为嵌套类,又叫静态内部类。

普通的内部类对象隐式的保存了一个外部类对象的引用,可以自由使用外部类的所有变量和方法,而静态内部类的创建不需要外部类的对象,不能在内部访问外部类的非静态元素。静态内部类更像是一个static方法。

静态内部类用于强调内部类的实现细节相对于外部类独立,比如说想要创建嵌套类对象并不需要外部类的对象。静态内部类更像是个独立的类,只是借用外部类的壳隐藏一下自己,表示自己和外部类是一伙的。

接口内部的类

在接口中声明的任何类都自动的是publicstatic的,因为类是static的,只是将其置于接口的命名空间下,这并不违反规则,甚至可以在接口的内部类中实现其外围接口。

如果想要创建某些公共代码,使得他们可以被某个接口的所有不同实现共用,那么使用接口内部的嵌套类会很方便。

package com.cn.thk.innerClass;

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

实现多重继承


class D{}
abstract class E{}
class Z extends D{
    class F extends E{
        
    }
	E makeE(){
	    return new F(){};
	}
}

public class MuliImplementation{
    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())
    }
}

内部类的继承

因为内部类的构造器必须连接到指向外部类对象的引用,所以继承内部类的时候,那个指向外围类对象的秘密的引用必须被初始化,而在导出类中不再存在可连接的默认对象。要解决这个问题,必须使用特殊的语法来明确说明他们之间的关系。

package com.cn.thk.innerClass;

class WithInner{
	class Inner{}
}

public class InheritInner extends WithInner.Inner{
    // 传递一个外部类的引用,并都使用  withInner.super();  这样的语法,程序才能编译通过。
	InheritInner(WithInner withInner){
		withInner.super();
	}
	
	public static void main(String[] args) {
		WithInner withInner = new WithInner();
		InheritInner inheritInner = new InheritInner(withInner);
	}
}

内部类的覆盖

如果创建了一个内部类,然后继承其外围类并重新定义此内部类时,会发生什么呢?也就是说,内部类可以被覆盖吗?这看起来似乎是个很有用的思想,但是“覆盖”内部类就好像它是外围类的一个方法,其实并不起什么作用。

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

默认的构造器是编译器自动生成的,这里是调用基类的默认构造器。你可能认为既然创建了BigEgg的对象,那么所使用的应该是“覆盖后”的Yolk版本,但从输出中可以看到实际情况并不是这样的。
这个例子说明,当继承了某个外围类的时候,内部类并没有发生什么特别神奇的变化。这两个内部类是完全独立的两个实体,各自在自己的命名空间内。当然,明确地继承某个内部类也是可以的:

class Egg2 {
	private Yolk y = new Yolk();
 
	public Egg2() {
		// TODO Auto-generated constructor stub
		System.out.println("new Egg2()");
	}
 
	protected class Yolk {
		public Yolk() {
			// TODO Auto-generated constructor stub
			System.out.println("Egg2.Yolk()");
		}
 
		public void f() {
			System.out.println("Egg2.Yolk().f()");
		}
	}
 
	public void insertYolk(Yolk yy) {
		y = yy;
	}
 
	public void g() {
		y.f();
	}
 
}
 
public class BigEgg2 extends Egg2 {
 
	public BigEgg2() {
		// TODO Auto-generated constructor stub
		insertYolk(new Yolk());
	}
 
	public class Yolk extends Egg2.Yolk {
		public Yolk() {
			// TODO Auto-generated constructor stub
			System.out.println("BigEgg2.Yolk()");
		}
 
		public void f() {
			System.out.println("BigEgg2.Yolk().f()");
		}
	}
 
	public static void main(String[] args) {
		Egg2 e2 = new BigEgg2();
		e2.g();
	} 
}

现在BigEgg2.Yolk()通过extends Egg2.Yolk()明确地继承了此内部类,并且覆盖了其中的方法。insertYolk()方法允许BigEgg2将它自己的Yolk对象向上转型为Egg2中的引用y。所以当g()调用y.f()时,覆盖后的新版本的f()被执行。第二次调用Egg2.Yolk(),结果是BigEgg2.Yolk的构造器调用了其基类的构造器。可以看到在调用g()的时候,新版的f()被调用了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值