說起內部類,大多數人都知道但卻不怎麼用,常規使用中,最常用到的也就是匿名內部類,所以下面會理一理各種內部類的相關知識及用法。
內部類的定義
Java中,類通常作爲一個獨立的程序單元。但在某些情況下,把將一個類定義在另一個類中,這就叫做內部類,而包含了內部類的類就叫做外部類。
內部類的主要作用與注意點
- 內部類隱藏在外部類中,別的類無法輕易訪問,提供了更好的封裝。
- 內部類屬於外部類成員,可以直接訪問外部類的成員屬性和方法(包括私有數據),但外部類卻不能直接訪問內部類成員。
- 內部類可以使用private、protected來控制訪問權限;使用static修飾。反之,外部類不能使用這些修飾符。
- 有時候某些方法只用一次,沒必要單獨創建類,這時就可以採用匿名內部類。
- 如果非靜態內部類,是不能定義靜態成員的,就好像在普通代碼塊中無法定義靜態一樣。
成員內部類
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來區分,依舊需要使用外部對象來調用。
- 爲什麼不允許訪問實例變量/方法呢?從類加載的過程來看,靜態內部類依附於類,而使用實例變量/方法需要對象,所以直接引用外部實例會引發錯誤。
匿名內部類
- 匿名內部類就是沒有名字的內部類,相當於簡寫的內部類。既然沒名字,自然也就找不到,訪問修飾符就無意義。
- 創建匿名內部類時,會立即創建一個該類實例,然後該類就被銷燬了。
- 匿名內部類必須是繼承一個抽象類或者實現接口,然後匿名內部類就可以進行重寫。
- 匿名內部類只能用一次,不能重複調用。
- 創建匿名內部類和對象差不多,但實現內容是放在裏面,可以理解成帶內容的對象。
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();
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個,也是這個道理。局部內部類
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接口連接信息
}
}
上面這個實例估計看不出局部內部類有啥特點...我也沒辦法,因爲局部內部類實在是基本用不到,上面這個勉強體現了把類放在方法中,儘管使用的匿名內部類。