內部類
一個定義在另一個類中的類,叫作內部類
1. 概述
內部類允許你把一些邏輯相關的類組織在一起,并控制位于內部的類的可見性,這么看來,內部類就像是一種代碼隱藏機制:將類置于其他類的內部,從而隱藏名字與組織代碼的模式,
2. 創建內部類
創建內部類的方式就如同你想的一樣,把類的定義置于外部類里面
public class Outer {
// 其他一些成員宣告
...
public class Inner {
// 其他一些成員宣告
...
}
}
3. 鏈接外部類
內部類與其外部類之間存在一種聯系,通過這個聯系可以訪問外部類的所有成員,而不需要任何特殊條件,可以這么說,內部類擁有其外部類的所有元素的訪問權,
這是如何做到的呢?當某個外部類的物件創建一個內部類物件時,該內部類物件會秘密地捕獲一個指向外部類物件的參考,在你訪問外部類成員時,可以通過這個參考選擇外部類的成員,所有這些實作的細節由編譯器幫忙處理,大多數時候都無需程式員關心,
4. 使用 .this 和 .new
如果你需要生成對外部類物件的參考,可以使用外部類的名字后面緊跟圓點和 this
public class Outer {
public class Inner {
public Outer getOuter() {
return Outer.this;
}
}
public static void main(String[] args) {
Outer outer = new Outer();
Outer outer2 = outer.getOuter();
}
}
如果你想創建某個內部類物件,必須在 new 運算式中提供其外部類的參考,這時需要使用 .new 語法
public class Outer {
public class Inner {}
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
}
}
如果從外部類的非靜態方法之外的位置創建某個內部類物件,要具體指明這個內部類物件的型別:OuterClassName.InnerClassName(在外部類的靜態方法中也可以直接指明型別 InnerClassName)
5. 向上轉型
當將內部類向上轉型為其基類(介面),可以很方便地隱藏實作細節,因為外界所得到的只是指向基類或介面的參考,
// 這是一個介面
public interface Content {...}
class Outer {
// 注意這里將訪問修飾符設為 private
private class PContent implements Content {
...
}
public Content getContent() {
return new PContent();
}
}
在 Outer 類中,內部類 PContent 是私有的,除了 Outer 外沒有人可以訪問,這意味著,客戶端程式員如果想了解這些內部類成員,那是要受限制的,客戶端程式員無法訪問任何新增的、不屬于公共介面的方法,也不能向下轉型,因為不能訪問其名字,
6. 多重嵌套內部類
無論一個內部類被嵌套多少層,它都能透明地訪問所有它所嵌入的外部類的所有成員
class MNA {
private void f() {}
class A {
private void g(){}
public class B {
void h() {
g();
f();
}
}
}
}
public class TestClass {
public static void main(String[] args) {
MNA mna = new MNA();
MNA.A mnaa = mna.new A();
MNA.A.B mnaab = mnaa.new B();
mnaab.h();
}
}
成員內部類
成員內部類是最普通的內部類,它和外部類的成員變數一樣,定義在類的內部,可以宣告訪問修飾符,成員內部類有如下特性:
- 不可以定義靜態成員和方法,因為內部類是外部類的一個成員,只有當外部類初始化時,內部類才能初始化,靜態變數屬于類級別,在類加載的時候就初始化,所以兩者本身在語法上就有矛盾,但可以定義常量,
- 可以無條件訪問外部類的所有成員屬性和成員方法(包括 private 成員和靜態成員),當成員內部類擁有和外部類同名的成員變數或者方法時,會發生隱藏現象,即默認情況下訪問的是成員內部類的成員,如果要訪問外部類的同名成員,需要以下面的形式進行訪問:
OuterClassName.this.innerClassMember
區域內部類
在方法的作用域內(而不是在其他類的作用域內)創建一個完整的類,這個類被稱作區域內部類,由于是定義在方法內,所以不能宣告訪問修飾符,作用范圍僅在宣告類的代碼塊中,但這并不意味著一旦方法作用域執行完畢,區域內部類就不可用了,區域內部類有如下特性:
- 區域內部類同樣不可以定義靜態成員和方法
- 可以無條件訪問外部類的所有成員屬性和成員方法,但不能訪問方法體內的區域變數,除非定義為 final 常量(仔細想想就知道了,內部類是依靠對外部類的參考進行訪問的)
public class Outer {
public Outer method() {
class Inner {
...
}
}
}
匿名內部類
只用一次的沒有名字的類,具體看下面的例子:
public class Outer {
public Content method() {
return new Content() {
private int i = 11;
@Override
public int getValue() {
return i
}
}
}
}
這種奇怪的語法指的是:創建一個繼承自 Content 的匿名類的物件,通過 new 運算式回傳的這個參考被自動向上轉型為 Content 的參考,上述匿名內部類是下述形式的一種簡化形式,也就是說,匿名內部類可以看作對成員內部類或區域內部類的一種簡寫:
public class Outer {
class MyContent implements Content {
private int i = 11;
@Override
public int getValue() {
return i
}
}
public Content method() {
return new MyContent();
}
}
匿名內部類沒有構造器(因為它根本沒有名字),如果我們希望為匿名內部類做一些初始化操作,那該怎么辦呢?如果該匿名內部類的基類構造器有引數,則可以直接使用,但編譯器會要求其引數參考是 final(即使你不加,Java8 也會為我們自動加上 final),或者通過代碼塊,模仿構造器的行為來實作初始化,
public class Outer {
public Content method() {
return new Content(final int a, final int b) {
private int i = a;
private int j;
{
j = b;
}
}
}
}
靜態內部類
如果不需要內部類物件與其外部類物件之間有聯系,可以將內部類宣告為 static,既然靜態內部類不需要與外部類有聯系,那么也就沒有指向外部類的 this 參考,更像一個獨立的類,這意味著:
- 要創建靜態內部類的物件,并不需要其外部類的物件
- 不能訪問非靜態的外部類物件,只能訪問外部類中的 static 所修飾的成員變數或者是方法
public class Outer {
public static class Inner {
private String label;
public String method1() {
...
}
static int x = 10;
public static void method2() {
...
}
}
}
靜態內部類可以作為介面的一部分,因為你放到介面中的任何類都自動是 public 和 static 的,甚至可以在內部類中實作其外部類介面,
public interface ClassInterface {
void method1();
class InnerClass implements ClassInterface {
@Override
public void method1() {
...
}
}
}
為什么需要內部類?
內部類的第一個用途就是可以操作創建它的外部類的物件,典型的例子就是集合中常使用的迭代器 iterator,這只是其中之一,使用內部類最吸引人的原因莫過于:
每個內部類都能獨立地繼承自某個類或實作某個介面,無論外部類是否繼承了某個類或實作某個介面
基于這個特性,使得多重繼承成為可能,我們可以讓多個內部類以不同的方式實作不同的介面,或繼承不同的類,下面這個例子使用了匿名內部類,可能你們已經習以為常,但別忘了匿名內部類其實就是內部類的一種簡寫形式,
class D {}
abstract class E {}
class Z extend D {
E getE() {
return new E() {};
}
}
public class TestClass {
static void setD(D d){}
static void setE(E e){}
public static void main(String[] args) {
Z z = new Z();
setD(z);
setE(z.getE);
}
}
另外,內部類也是實作回呼的絕佳選擇,通過內部類可以實作一個閉包,所謂閉包就是一個可呼叫的物件,它記錄了一些資訊,在需要提供閉包來實作回呼時,我們可以采用匿名內部類的方式,
繼承內部類
因為內部類的構造器必須連接到其外部類物件的參考,所以在繼承內部類時,那個指向外部類物件的參考必須被初始化,而在派生類中又不再存在這個參考了,要解決這個問題,必須使用特殊的語法來明確說清它們之間的關系,
class WithInner {
class Inner {}
}
public class InheritInner extends WithInner.Inner {
InheritInner(WithInner wi) {
wi.super();
}
public static void main(String[] args) {
WithInner wi = new WithInner();
InheritInner li = new InheritInner(wi);
}
}
構造器要傳遞一個指向外部類物件的參考,此外,還必須顯式地呼叫基類構造器,
內部類可以被覆寫嗎?答案是不能,如果創建一個內部類,然后繼承其外部類并重新定義此內部類,并不會覆寫該內部類,兩個內部類是完全獨立的,各自在自己的命名空間,
內部類識別符號
每個類編譯后都會產生一個 .class 檔案,內部類也會生成一個 .class 檔案,但其命名有嚴格的規則:外部類的名字 + $ + 內部類的名字,例如 OuterClass.java 生成的 .class 檔案包括:
OuterClass.class
OuterClass$InnerClass.class
如果內部類是匿名的,編譯器會簡單地生成一個數字作為其識別符號,如果內部類嵌套在別的內部類之中,只需直接將它們的名字在外部類識別符號與 $ 的后面,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/190081.html
標籤:Java
