SE高階(19):內部類的使用方式、應用場景和注意點

說起內部類,大多數人都知道但卻不怎麼用,常規使用中,最常用到的也就是匿名內部類,所以下面會理一理各種內部類的相關知識及用法。


 內部類的定義 

     Java中,類通常作爲一個獨立的程序單元。但在某些情況下,把將一個類定義在另一個類中,這就叫做內部類,而包含了內部類的類就叫做外部類。


 內部類的主要作用與注意點 

  1. 內部類隱藏在外部類中,別的類無法輕易訪問,提供了更好的封裝。
  2. 內部類屬於外部類成員,可以直接訪問外部類的成員屬性和方法(包括私有數據),但外部類卻不能直接訪問內部類成員。
  3. 內部類可以使用private、protected來控制訪問權限;使用static修飾。反之,外部類不能使用這些修飾符。
  4. 有時候某些方法只用一次,沒必要單獨創建類,這時就可以採用匿名內部類。
  5. 如果非靜態內部類,是不能定義靜態成員的,就好像在普通代碼塊中無法定義靜態一樣。
     上面只是簡要說明,但內部類又分爲成員內部類、靜態內部類、匿名內部類和方法內部類(局部內部類)。各自的應用場景、使用方法和訪問權限皆有不同之處,在下面,將會對四個內部類的要點進行分析,附帶實例。


  成員內部類                                                                                            

public class Demo01 {
	private int a = 20; 
	public static int b = 20; //靜態變量
	private void testInstance() {System.out.println("外部類實例方法");}
	private static void testStatic() {System.out.println("外部類靜態方法");}
	//打印a b 值
	void printAttribute() {
		System.out.println("外部類:" + "a:" + a + " , b:" + b);
	}
	//成員內部類,權限默認,可被同類、同包訪問
	class InnerClass{
		private int a = 10;	
//		public static void test() {}//error
		//訪問外部類成員
		public void changeOutside() {
			a = a - 10;
			b = b - 10;
			//能訪問外部類靜態方法和實例
			testInstance();
			testStatic();
			System.out.println("內部類:" + "a:" + a + " , b:" + b);
		}
	}
	//返回一個內部類對象
		public InnerClass getInnerClass(){
			return new InnerClass();
		}
}
//測試
public class Test {
	public static void main(String[] args) {
		Demo01 d = new Demo01();
		//創建內部類對象
		Demo01.InnerClass ic = d.new InnerClass();
		ic.changeOutside();
		d.printAttribute();
	}
}
//輸出結果:
外部類實例方法
外部類靜態方法
內部類:a:0 , b:10
外部類:a:20 , b:10

成員內部類實例解析 

  • 從實例來看,創建內部類對象時需要依託於外部類對象的存在,因爲成員內部類在類中等同於一個成員,所以需要外部類對象才能對其訪問。
  • 在上面的changeOutside()方法中,因爲成員內部類等同於成員,所以能訪問所有外部類成員,包括靜態。但是不能創建靜態成員和方法。
  • 如果成員內部類和外部類的屬性名相同,那麼默認使用內部類的屬性,如同上面a、b值一樣。如果要訪問外部類同名屬性,用this來指定。
		//創建內部類對象
		Demo01.InnerClass ic = d.new InnerClass();
		System.out.println(ic.getClass());
		//輸出結果:
		class com.InnerClass.test.Demo01$InnerClass
根據上面代碼,可以看到內部類的class文件就是上面所打印的結構,從名字的命名也能看出內部類和外部類的關係。



  靜態內部類                                                                                            

public class outClass {
	private int insVar = 555;
	private String a = "外部A"; //同名變量,用於測驗
	private static int staticVar = 333;
		
	//靜態內部類
	static class InnerClass{
		//能創建靜態變量和方法
		private static String a = "內部A";
		public static void test() {
			//訪問同名變量
			System.out.println("a: " + a);
			System.out.println("外部類 a: " + new outClass().a); //訪問外部類同名實例變量,this不能使用
			//訪問外部靜態變量
			System.out.println("外部類 staticVar : " + staticVar);
		}
	}
}

//測試
public static void main(String[] args) {
		InnerClass inn = new InnerClass();//可以直接創建
		inn.test();
		//輸出結果:
		a: 內部A
		外部類 a: 外部A
		外部類 staticVar : 333
	}

靜態內部類實例解析 

  • 靜態內部類能夠直接創建對象,不像成員內部類依附於對象而存在,這說明靜態內部類依附於類本身,而非對象。
  • 靜態內部類不能有非靜態的成員/方法,如果要引用外部類實例變量/方法,可以採用外部對象.變量/方法的形式來引用。
  • 遇到同名變量和方法時,不能像成員內部類一樣用this來區分,依舊需要使用外部對象來調用。
  • 爲什麼不允許訪問實例變量/方法呢?從類加載的過程來看,靜態內部類依附於類,而使用實例變量/方法需要對象,所以直接引用外部實例會引發錯誤。


  匿名內部類                                                                                            

  • 匿名內部類就是沒有名字的內部類,相當於簡寫的內部類。既然沒名字,自然也就找不到,訪問修飾符就無意義。
  • 創建匿名內部類時,會立即創建一個該類實例,然後該類就被銷燬了。
  • 匿名內部類必須是繼承一個抽象類或者實現接口,然後匿名內部類就可以進行重寫。
  • 匿名內部類只能用一次,不能重複調用。
  • 創建匿名內部類和對象差不多,但實現內容是放在裏面,可以理解成帶內容的對象。
實例1
public class outClass {
	public static void main(String[] args) {
		//常規執行方法形式
		new Test().start();
	}	
	
}
//用一個線程來重複打印i
class Test extends Thread {
	@Override
	public void run() {
		for(int i = 0; i < 50; i++) {
			System.out.println(Thread.currentThread().getName() + " , i:" + i);
		}
	}
}
上面實例使用常規方式:定義類->定義方法->調用方法,但如果這方法只用一次,用完就不管了,那麼去創建類來定義方法不僅顯得很麻煩,而且還佔空間,所以匿名內部類就是爲了解決該類問題而用。改用匿名內部類如下:
//使用匿名內部類,實現同樣的功能,但是代碼量大大降低。
		new Thread() {
			@Override
			public void run() {
				for(int i = 0; i < 50; i++) {
					System.out.println(Thread.currentThread().getName() + " , i:" + i);
				}
			}
		}.start();

實例2
interface Command {
	void processArr(int[] arr);
}

public class outClass {
	public static void main(String[] args) {
		int[] arr  = {24,5,12,3,34};
		
		dealArr(arr, new addCommand());
		dealArr(arr, new getMaxCommand());
	}	
	
	//使用命令模式,根據相應命令來處理數組
	public static void dealArr(int[] arr, Command cmd) {
		cmd.processArr(arr);
	}
}
//累計數組之和
class addCommand implements Command {
	@Override
	public void processArr(int[] arr) {
		int sum = 0;
		for(int i = 0; i < arr.length; i++) {
			sum += arr[i];
		}
		System.out.println("數組總和:" + sum);
	} 
}
//得到數組最大值
class getMaxCommand implements Command {
	@Override
	public void processArr(int[] arr) {
		int sum = arr[0];
		for(int i = 1; i < arr.length; i++) {
			if(arr[i] > sum) {
				sum = arr[i];
			}
		}
		System.out.println("數組最大值:" + sum);
	} 
}
實例2使用了簡單的命令模式來處理數組,但從上面可以看出,代碼量很多。因爲只使用一次,所以改用匿名內部類,如下:
	//爲了簡化,所以省略一些不必要的代碼,只保留關鍵代碼
	dealArr(arr, new Command() {		
			@Override
			public void processArr(int[] arr) {
				//累計數組和的代碼
			}
		});
		
		dealArr(arr, new Command() {
			@Override
			public void processArr(int[] arr) {
				//得到數組最大值的代碼		
			}
		});
同上面比較,減少了創建兩個類的代碼量,代碼顯得簡潔多了。但也有缺點,那就是方法過多時就沒必要使用匿名內部類了。就像使用匿名內部類最頻繁的綁定事件,方法最多2、3個,也是這個道理。



  局部內部類                                                                                            

       方法內部類也叫作局部內部類,可以定義在方法和作用域中,例如普通代碼塊、for循環、while循環中,局部內部類的訪問僅限於方法內或者該作用域內,這意味着不能被public、protected、private和static修飾。
由於方法內部類很少使用,所以實在沒什麼好例子,下面就以一個動態代理的例子來演示一下:
interface USB{
	void connect(); //連接
}
//實現類
class Kingson implements USB{
	@Override
	public void connect() {
		System.out.println("金士頓USB開始連接");
	}
	
}
class methodClass {
	private Object target;
	//對指定對象進行代理,並返回代理對象
	public Object getProxy(Object target){
		this.target = target;
		Object proObj = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),							   
							   new InvocationHandler() { //看起來像是在方法中使用匿名內部類....		
									@Override
									public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
										System.out.println("打開電腦USB接口");
										method.invoke(target, args);
										System.out.println("記錄USB接口連接信息");
										return null;
									}
							  });
		return proObj; //返回生成的代理對象
	}
}
//測試類
public class Test {
	public static void main(String[] args) {
		Kingson ks = new Kingson();
		USB u = (USB)new methodClass().getProxy(ks);
		u.connect();
		//輸出結果:
		打開電腦USB接口
		金士頓USB開始連接
		記錄USB接口連接信息
	}
}
上面這個實例估計看不出局部內部類有啥特點...我也沒辦法,因爲局部內部類實在是基本用不到,上面這個勉強體現了把類放在方法中,儘管使用的匿名內部類。

  關於內部類的個人感想  

        從上面對四種內部類使用來看,內部類本質上就是對類的一種簡化,所以內部類能做的事,直接創建類也一定能做。所以無需糾結什麼地方該用內部類,什麼地方不用,再極端無外乎就是多創建一個類,如果不夠就兩個。當然,能簡便就簡便,但沒必要過於追求極端化,最終都是爲了解決問題,所以怎麼順手怎麼來。
發佈了61 篇原創文章 · 獲贊 24 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章