《Java程式設計思想》筆記07——內部類

CosmosLeung發表於2019-02-21

一、建立內部類

/**
 * 內部類的基本使用
 * @author LiangYu
 * 2018-01-09
 */
public class InnerClassTest {
	@Test
	public void test() throws Exception {
		ShowClass show1 = new ShowClass();
		show1.doSomething();
		ShowClass show2 = new ShowClass();
		//如果是在靜態方法中,則需要強制寫成:外部類.內部類
		ShowClass.InnerClass1 innerClass1 = show2.getInnerClass1();
		ShowClass.InnerClass2 innerClass2 = show2.getInnerClass2();
	}
}

class ShowClass{
	class InnerClass1{
		public InnerClass1() {
			System.out.println("InnerClass1");
		}
	}
	
	class InnerClass2{
		public InnerClass2(){
			System.out.println("InnerClass2");
		}
	}
	
	public InnerClass1 getInnerClass1(){
		return new InnerClass1();
	}
	
	public InnerClass2 getInnerClass2(){
		return new InnerClass2();
	}
	
	public void doSomething(){
		InnerClass1 innerClass1 = getInnerClass1();
		InnerClass2 innerClass2 = getInnerClass2();
		System.out.println("doSomething");
	}
}
複製程式碼

所謂的內部類其實就是一個普通類巢狀在內部。

二、連結到外部類

/**
 * 內部類與外部類的連結(以及介面卡模式的使用)
 * @author LiangYu
 * 2018-01-09
 */
public class InnerClassTest2 {
	@Test
	public void test() throws Exception {
		TestList testList = new TestList(5);
		for(int i = 0 ; i < 5 ; i++){
			testList.add(Integer.toString(i));
		}
		Selector selector = testList.selector();
		while(!selector.end()){
			System.out.println(selector.current());
			selector.next();
		}
	}
}

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

class TestList{
	private String[] items;
	private int next = 0;
	public TestList(int size){
		items = new String[size];
	}
	public void add(String text){
		if(next < items.length){
			items[next] = text;
			next++;
		}
	}
	
	private class ListSelector implements Selector{
		private int i = 0;
		@Override
		public boolean end() {
			if(items.length == i){
				return true;
			}
			return false;
		}

		@Override
		public void next() {
			if(i < items.length){
				i++;
			}
		}

		@Override
		public String current() {
			return items[i];
		}
		
	}
	
	public Selector selector(){
		return new ListSelector();
	}
}

複製程式碼

內部類ListSelector使用了外部類中的私有變數items,由此可以看出,外部類物件擁有建立它外部類物件的所有成員的訪問權。

原理:外部類建立內部類物件時,內部類物件會捕獲一個指向外部類物件的引用,通過這個引用可以訪問外部類的所有成員。

二、使用.this和.new


/**
 * .this和.new的使用
 * @author LiangYu
 * 2018-01-09
 */
public class InnerClassTest3 {
	@Test
	public void test() throws Exception {
		OuterClass outerClass = new OuterClass();
		InnerClass innerClass = outerClass.getInnerClass();
		OuterClass outerClass2 = innerClass.getOuterClass();
	}
}

class OuterClass{
	public OuterClass() {
		System.out.println("Constructor OuterClass");
	}
	
	class InnerClass{
		public InnerClass() {
			System.out.println("Constructor InnerClass");
		}
		
		public OuterClass getOuterClass(){
			return OuterClass.this; //通過.this的方式,獲取外部類物件
		}
	}
	
	public InnerClass getInnerClass(){
		return this.new InnerClass();  //通過.new的方式,獲取內部類的物件
	}
}
複製程式碼

三、內部類與向上轉型

/**
 * 內部類與向上轉型
 * @author LiangYu
 * 2018-01-10
 */
public class InnerUpCastTest {
	
	@Test
	public void test() throws Exception {
		InnerExample1 example = new InnerExample1();
		ShowNumber showNumber = example.showNumberImpl();
		ShowText showText = example.showTextImpl("hello");
		InnerExample1.ShowNumberImpl showNumber2 = example.new ShowNumberImpl();
//		InnerExample1.ShowText showText2 = example.new ShowTextImpl();   編譯失敗,因為內部類是私有的
	}
}

interface ShowText{
	String getText();
}

interface ShowNumber{
	int value();
}

class InnerExample1{
	private class ShowTextImpl implements ShowText{
		private String text;
		private ShowTextImpl(String text) {
			this.text = text;
		}
		@Override
		public String getText() {
			return text;
		}
	}
	
	protected class ShowNumberImpl implements ShowNumber{
		private int i = 11;
		
		@Override
		public int value(){
			return i;
		}
	}
	
	public ShowTextImpl showTextImpl(String s){
		return new ShowTextImpl(s);
	}
	
	public ShowNumberImpl showNumberImpl(){
		return new ShowNumberImpl();
	}
}
複製程式碼

private內部類可以完全阻止任何依賴於型別的編碼,並且完全隱藏了實現細節。只需要通過showTextImpl()、showNumberImpl()來獲取物件。

四、在方法和作用域內的內部類


/**
 * 通過區域性內部類的方法,對之前的程式碼進行修改
 * @author LiangYu
 * 2018-01-10
 */
public class InnerUpCastText2 {
	@Test
	public void test() throws Exception {
		InnerExample2 example2 = new InnerExample2();
		ShowNumber showNumber = example2.showNumber();
		ShowText showText = example2.showText("haha");
		System.out.println(showNumber.value());
		System.out.println(showText.getText());
	}
}

class InnerExample2{
	public ShowText showText(String s){
		class ShowTextImpl implements ShowText{
			private String text;
			public ShowTextImpl(String text) {
				this.text = text;
			}
			@Override
			public String getText() {
				return text;
			}
		}
		return new ShowTextImpl(s);
	}
	public ShowNumber showNumber(){
		class ShowNumberImpl implements ShowNumber{
			private int number = 11;
			@Override
			public int value() {
				return number;
			}
		}
		return new ShowNumberImpl();
	}
}
複製程式碼

區域性內部類與其他類共同編譯,但是隻在作用域內可用,在其他作用域中使用相同的類名不會有命名衝突。

五、匿名內部類

/**
 * 通過匿名內部類的方式修改之前的程式碼
 * @author LiangYu
 * 2018-01-10
 */
public class InnerUpCastTest3 {
	@Test
	public void test() throws Exception {
		InnerExample3 example3 = new InnerExample3();
		ShowNumber showNumber = example3.showNumber();
		ShowText showText = example3.showText("hehe");
		System.out.println(showNumber.value());
		System.out.println(showText.getText());
	}
}


class InnerExample3{
	public ShowText showText(String text){
		return new ShowText() {
			@Override
			public String getText() {
//				text = "hehehe";  Local variable text defined in an enclosing scope must be final or effectively final

				return text;
			}
		};
	}
	
	public ShowNumber showNumber(){
		return new ShowNumber() {
			private int num = 11;
			@Override
			public int value() {
				return num;
			}
		};
	}
}
複製程式碼

在showNumber()方法的內部,在返回了一個ShowNumber引用的時候,插入了一個類的定義。這裡實際的情況是,建立了一個繼承自ShowNumber介面的匿名類的物件,通過new表示式返回的時候,實際上已經向上轉型為對ShowNumber的引用了。

初始化的過程:在例項初始化的內部,實現了匿名類的初始化。

在程式碼中,text變數無法被修改,提示錯誤資訊:“Local variable text defined in an enclosing scope must be final or effectively final”

即:內部類使用外部類的物件被看作是final的。即:初始化值之後不能被修改。

對於final型別來說: 編譯器編譯後,final型別是常量,被儲存到了常量池中,在匿名內部類中使用該變數的地方都被替換成了具體的常量值,關於外部類的變數的資訊,內部類是不知道的。

對於非final型別來說:傳入內部類的僅僅只是傳值操作,所以在匿名內部類中改變這個值是無效的。如果在外部類中修改這個值,那麼匿名內部類得到的引數值可能已經不是期望中的那個值。所以,在內部類使用外部類的變數時,不允許做任何修改才會避免所以問題。

JAVA8版本對於非final型別會進行檢查,要求不允許修改。

六、巢狀類

如果不想使內部類物件與外部類物件相互聯絡,那麼可以將內部類宣告為static,這就是巢狀類。

非靜態內部類必須通過外部類物件建立並且獲得一個該外部類物件的引用。

對於靜態內部類:

1.建立靜態內部類的物件並不需要外部類的物件。

2.靜態內部類中不能使用外部類的非靜態成員(因為沒有外部類的引用)

3.靜態內部類中可以建立靜態類中的資料和欄位(普通內部類不可以,因為它通過外部類物件建立,不屬於類成員)

/**
 * 巢狀類(靜態內部類的使用)
 * @author LiangYu
 * 2018-01-10
 */
public class StaticInnerClassTest {
	@Test
	public void test() throws Exception {
		//可以直接使用
		InnerStaticClass innerStaticClass = new InnerStaticClass();
		System.out.println(innerStaticClass.getNum());
	}
}

class InnerClassExample4{
	 static class InnerStaticClass{
		static int num = 11; //可以建立靜態變數
		int getNum(){
			return num;
		}
	 }
}
複製程式碼

七、介面內部的類

/**
 * 介面中的內部類
 * @author LiangYu
 * 2018-01-10
 */
public class InterfaceInnerClassTest {
	@Test
	public void test() throws Exception {
		InnerClassInterface.ShowText showText = new InnerClassInterface.ShowText();
		showText.sayHello();
	}
}

interface InnerClassInterface{
	void sayHello();
	static class ShowText implements InnerClassInterface{
		@Override
		public void sayHello() {
			System.out.println("Hello World");
		}
		
	}
}
複製程式碼

巢狀類可以實現它名稱空間下的介面。介面中的內部類即使是靜態的也不能夠直接使用,需要“介面.類名”的形式

八、為什麼需要內部類

內部類可以有效的實現“多重繼承”

/**
 * 多繼承
 * @author LiangYu
 * 2018-01-10
 */
public class MultiinheritanceTest {
	@Test
	public void test() throws Exception {
		NultiinheritanceExample example = new NultiinheritanceExample();
		String msg = "QwErTyUiOpAsDfGhJkL";
		example.showMessage(msg);
		B upperB = example.getUpper(msg);
		B lowerB = example.getLower(msg);
		upperB.changeText();
		lowerB.changeText();
	}
}

class A{
	void showMessage(String msg){
		System.out.println("A "+msg);
	}
}

abstract class B{
	abstract void changeText();
}

//實現了多重繼承
class NultiinheritanceExample extends A{
	B getUpper(String text){
		return new B() {
			@Override
			void changeText() {
				System.out.println(text.toUpperCase());
			}
		};
	}
	
	B getLower(String text){
		return new B() {
			@Override
			void changeText() {
				System.out.println(text.toLowerCase());
			}
		};
	}
}
複製程式碼

8-1 閉包與回撥

閉包是一個可呼叫的物件,它記錄了一些資訊,這些資訊來自於建立它的作用域。

內部類是物件導向的閉包,它不僅包含了外部類物件的資訊,還自動擁有一個指向該外部類物件的引用,在此作用域下,內部類有權操作所有的成員,包括private成員。

Java可以通過內部類提供的閉包功能,實現回撥

/**
 * 回撥功能:模擬Android系統按鈕點選的回撥機制
 * @author LiangYu
 * 2018-01-10
 */
public class CallbackTest {
	@Test
	public void test() throws Exception {
		Activity activity = new Activity();
		activity.click();
	}
}

interface OnClickListener{
	void onClick();
}

class Button{
	private OnClickListener onClickListener;
	
	void setOnClickListener(OnClickListener onClickListener){
		this.onClickListener = onClickListener;
		
	}
	
	//執行Click
	void performClick(){
		if(onClickListener != null){
			onClickListener.onClick();
		}
	}
}

class Activity{
	Button button1 = new Button();
	Button button2 = new Button();
	void click(){
		button1.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick() {
				System.out.println("點選1號按鈕");
			}
		});
		button2.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick() {
				System.out.println("點選2號按鈕");
			}
		});
		button1.performClick();
		button2.performClick();
	}
}
複製程式碼

所謂的回撥,其實就是先實現了抽象方法,但是不執行,直到某事件被觸發後才會執行。

九、內部類的繼承

/**
 * 內部類被繼承
 * @author LiangYu
 * 2018-01-10
 */
public class InnerClassExtendsTest {
	@Test
	public void test() throws Exception {
		SonInner sonInner = new SonInner(new ExampleClass5(666));
	}
}

class ExampleClass5{
	public ExampleClass5(int i){
		System.out.println(i);
	}
	class InnerClass{
	}
}

class SonInner extends InnerClass{
	public SonInner(ExampleClass5 exampleClass5) {
		exampleClass5.super();
	}
}
複製程式碼

在繼承內部類的類中,必須獲取一個建立內部類的外部類物件的引用,通過這個引用呼叫內部類父類進行初始化操作。

十、內部類被覆蓋

/**
 * 內部類被覆蓋
 * @author LiangYu
 * 2018-01-11
 */
public class CoverInnerTest {
	@Test
	public void test() throws Exception {
		Parent parent = new Child();
		parent.parentMethod();
	}
}

class Parent{ 
	private InnerClass innerParent = new InnerClass();  //先於構造器之前,初始化變數
	class InnerClass{
		public InnerClass() {
			System.out.println("Parent Inner Constructor");
		}
		public void method(){
			System.out.println("Parent Inner Method");
		}
	}
	public Parent() {
		System.out.println("Parent Constructor");
	}
	
	public void changeInner(InnerClass innerParent){
		innerParent = this.innerParent;
	}
	
	public void parentMethod(){
		innerParent.method();
	}
}

class Child extends Parent{
	class InnerClass extends Parent.InnerClass{
		public InnerClass(){
			System.out.println("Child Inner Constructor");
		}
		public void method(){
			System.out.println("Child Inner Method");
		}
	}
	public Child() {
		changeInner(new InnerClass());
	}
}
複製程式碼

輸出結果:

Parent Inner Constructor

Parent Constructor

Parent Inner Constructor

Child Inner Constructor

Parent Inner Method

過程分析:

1.在test()方法中,首先new Child(),呼叫了Child構造器,然而,Child繼承自Parent,因此,首先初始化Parent。

2.在初始化Parent類的時候,首先對欄位innerParent初始化。所以,通過new InnerClass()呼叫了父類內部類的構造方法,因此,輸出Parent Inner Constructor

3.執行父類構造方法,輸出Parent Constructor

4.返回到子類中,執行子類構造方法:changeInner(new InnerClass()),首先執行方法中的new InnerClass()

5.呼叫子類的內部類建構函式,由於子類內部類繼承自父類內部類,因此,首先執行父類內部類的構造方法

6.執行父類內部類的構造方法,輸出Parent Inner Constructor

7.返回到子類的內部類,輸出Child Inner Constructor

8.此時,Child類已經構造完畢,由test()方法中,向上轉型為Parent類。

9.執行parent.parentMethod(),呼叫innerParent.method(),輸出Parent Inner Method

相關文章