整理了一下內部類的相關知識,算是比較全,比較基礎的,但願你們一塊兒學習進步。 html
在Java中,能夠將一個類的定義放在另一個類的定義內部,這就是內部類。內部類自己就是類的一個屬性,與其餘屬性 定義方式一致。java
一個內部類的例子:android
public class Outer {
private int radius = 1;
public static int count = 2;
public Outer() {
}
class inner{
public void visitOuter() {
System.out.println("visit outer private member variable:" + radius);
System.out.println("visit outer static variable:" + count);
}
}
}
複製代碼
內部類能夠分爲四種:成員內部類、局部內部類、匿名內部類和靜態內部類。面試
定義在類內部的靜態類,就是靜態內部類。算法
public class Outer {
private static int radius = 1;
static class StaticInner {
public void visit() {
System.out.println("visit outer static variable:" + radius);
}
}
}
複製代碼
靜態內部類能夠訪問外部類全部的靜態變量,而不可訪問外部類的非靜態變量;靜態內部類的建立方式,new 外部類.靜態內部類()
,以下:數據庫
Outer.StaticInner inner = new Outer.StaticInner();
inner.visit();
複製代碼
定義在類內部,成員位置上的非靜態類,就是成員內部類。編程
public class Outer {
private static int radius = 1;
private int count =2;
class Inner {
public void visit() {
System.out.println("visit outer static variable:" + radius);
System.out.println("visit outer variable:" + count);
}
}
}
複製代碼
成員內部類能夠訪問外部類全部的變量和方法,包括靜態和非靜態,私有和公有。成員內部類依賴於外部類的實例,它的建立方式外部類實例.new 內部類()
,以下:設計模式
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.visit();
複製代碼
定義在方法中的內部類,就是局部內部類。bash
public class Outer {
private int out_a = 1;
private static int STATIC_b = 2;
public void testFunctionClass(){
int inner_c =3;
class Inner {
private void fun(){
System.out.println(out_a);
System.out.println(STATIC_b);
System.out.println(inner_c);
}
}
Inner inner = new Inner();
inner.fun();
}
public static void testStaticFunctionClass(){
int d =3;
class Inner {
private void fun(){
// System.out.println(out_a); 編譯錯誤,定義在靜態方法中的局部類不能夠訪問外部類的實例變量
System.out.println(STATIC_b);
System.out.println(d);
}
}
Inner inner = new Inner();
inner.fun();
}
}
複製代碼
定義在實例方法中的局部類能夠訪問外部類的全部變量和方法,定義在靜態方法中的局部類只能訪問外部類的靜態變量和方法。局部內部類的建立方式,在對應方法內,new 內部類()
,以下:ide
public static void testStaticFunctionClass(){
class Inner {
}
Inner inner = new Inner();
}
複製代碼
匿名內部類就是沒有名字的內部類,平常開發中使用的比較多。
public class Outer {
private void test(final int i) {
new Service() {
public void method() {
for (int j = 0; j < i; j++) {
System.out.println("匿名內部類" );
}
}
}.method();
}
}
//匿名內部類必須繼承或實現一個已有的接口
interface Service{
void method();
}
複製代碼
除了沒有名字,匿名內部類還有如下特色:
匿名內部類建立方式:
new 類/接口{
//匿名內部類實現部分
}
複製代碼
咱們爲何要使用內部類呢?由於它有如下優勢:
public class Outer {
private int radius = 1;
protected void test(){
System.out.println("我是外部類方法");
}
class Inner {
public void visit() {
System.out.println("訪問外部類變量" + radius);
test();
}
}
}
複製代碼
咱們能夠看到,內部類Inner是能夠訪問外部類Outer的私有變量radius或者方法test的。
當內部類使用 private修飾時,這個類就對外隱藏了。當內部類實現某個接口,而且進行向上轉型,對外部來講,接口的實現已經隱藏起來了,很好體現了封裝性。
//提供的接口
interface IContent{
String getContents();
}
public class Outer {
//私有內部類屏蔽實現細節
private class PContents implements IContent{
@Override
public String getContents() {
System.out.println("獲取內部類內容");
return "內部類內容";
}
}
//對外提供方法
public IContent getIContent() {
return new PContents();
}
public static void main(String[] args) {
Outer outer=new Outer();
IContent a1=outer.getIContent();
a1.getContents();
}
}
複製代碼
咱們能夠發現,Outer外部類對外提供方法getIContent,用內部類實現細節,再用private修飾內部類,屏蔽起來,把Java的封裝性表現的淋漓盡致。
咱們知道Java世界中,一個類只能有一個直接父類,即以單繼承方式存在。可是內部類讓「多繼承」成爲可能:
- 通常來講,內部類繼承某個類或者實現某個接口,內部類的代碼操做建立它的外圍類的對象。內部類提供了某種進入其外圍類的窗口。
- 每一個內部類均可以隊裏的繼承自一個(接口的)實現,因此不管外圍類是否已經繼承了某個(接口的)實現,對於內部類沒有影響
- 接口解決了部分問題,一個類能夠實現多個接口,內部類容許繼承多個非接口類型(類或抽象類)。
一份來自Java編程思想,內部類實現「多繼承」的溫暖以下:
class D {}
abstract class E{}
class Z extends D {
E makeE(){ return new E() {}; }
}
public class MultiImplementation {
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());
}
}
複製代碼
代碼中出現了一個類D,一個抽象類E。而後,用類Z繼承D,內部類構造返回E。所以,當你無論要的是D仍是E,Z均可以應付,「多繼承」的特色完美表現出來。
什麼是回調?假設有兩個類A和B,在A中調用B的一個方法b,而b在執行又調用了A的方法c,則c就稱爲回調函數。
//定義一個CallBack接口
public interface CallBack {
void execute();
}
public class TimeTools {
/**
* 測試函數調用時長,經過定義CallBack接口的execute方法
* @param callBack
*/
public void testTime(CallBack callBack) {
long beginTime = System.currentTimeMillis(); //記錄起始時間
callBack.execute(); ///進行回調操做
long endTime = System.currentTimeMillis(); //記錄結束時間
System.out.println("[use time]:" + (endTime - beginTime)); //打印使用時間
}
public static void main(String[] args) {
TimeTools tool = new TimeTools();
tool.testTime(new CallBack(){
//匿名內部類,定義execute方法
public void execute(){
TestTimeObject testTimeObject = new TestTimeObject();
testTimeObject.testMethod();
}
});
}
}
複製代碼
在調用testTime()測時間的時候,用匿名內部類實現一個方法execute(),在該方法內搞事情(執行目標函數),執行完後,又回到testTime方法,很好了實現測試函數調用時長的功能。顯然,匿名內部類讓回調實現變得簡單。
每一個內部類都會產生一個.class文件,其中包含了如何建立該類型的對象的所有信息。內部類也必須生成一個.class文件以包含它們的Class對象信息。內部類文件的命名有嚴格規則:外圍類的名字+$+內部類的名字。
一個簡單例子:
public class Outer {
class Inner{
}
}
複製代碼
javac Outer.java編譯完成後, 生成的class文件以下:
若是內部類是匿名的,編譯器會簡單地產生一個數字做爲其標識符。若是內部類是嵌套在別的內部類之中(靜態內部類),只需直接將它們的名字加在其外圍類標誌符與「$」的後面。
由上一小節,咱們知道內部類能夠訪問外部類的成員,包括私有數據。那麼它是怎麼作到的呢?接下來揭曉答案。
先看這個簡單地例子:
public class Outer {
private int i = 0;
class Inner{
void method(){
System.out.println(i);
}
}
}
複製代碼
一個外部類Outer,一個外部類私有屬性i,一個內部類Inner,一個內部類方法method。內部類方法訪問了外部類屬性i。
先編譯,javac Outer.java,生成.class文件,以下:
javap -classpath . -v Outer$Inner
,反編譯Outter$Inner.class文件獲得如下信息:
咱們能夠看到這一行,它是一個指向外部類對象的指針:
final innerclass.Outer this$0;
複製代碼
雖然編譯器在建立內部類時爲它加上了一個指向外部類的引用, 可是這個引用是怎樣賦值的呢?編譯器會爲內部類的構造方法添加一個參數,進行初始化, 參數的類型就是外部類的類型,以下:
innerclass.Outer$Inner(innerclass.Outer);
複製代碼
成員內部類中的Outter this&0 指針便指向了外部類對象,所以能夠在成員內部類中隨意訪問外部類的成員。
局部內部類和匿名內部類訪問局部變量的時候,爲何變量必需要加上final呢?它內部原理是什麼呢?
先看這段代碼:
public class Outer {
void outMethod(){
final int a =10;
class Inner {
void innerMethod(){
System.out.println(a);
}
}
}
}
複製代碼
反編譯(Outer$1Inner)獲得如下信息
咱們在內部類innerMethod方法中,能夠看到如下這條指令:
3: bipush 10
複製代碼
以上例子,爲何要加final呢?是由於生命週期不一致, 局部變量直接存儲在棧中,當方法執行結束後,非final的局部變量就被銷燬。而局部內部類對局部變量的引用依然存在,若是局部內部類要調用局部變量時,就會出錯。加了final,能夠確保局部內部類使用的變量與外層的局部變量區分開,解決了這個問題。
咱們再來看一段代碼,其實就是把變量a挪到傳參方式進來
public class Outer {
void outMethod(final int a){
class Inner {
void innerMethod(){
System.out.println(a);
}
}
}
}
複製代碼
反編譯可得
那麼,新的問題又來了,既然在innerMethod方法中訪問的變量a和outMethod方法中的變量a不是同一個變量,當在innerMethod方法中修改a會怎樣?那就會形成數據不一致的問題了。
怎麼解決呢?使用final修飾符,final修飾的引用類型變量,不容許指向新的對象,這就解決數據不一致問題。注意: 在Java8 中,被局部內部類引用的局部變量,默認添加final,因此不須要添加final關鍵詞。
通常咱們在哪些場景下使用內部類呢?
一些算法多的場合,也能夠藉助內部類,如:
Arrays.sort(emps,new Comparator(){
Public int compare(Object o1,Object o2)
{
return ((Employee)o1).getServedYears()-((Employee)o2).getServedYears();
}
});
複製代碼
若是一些語句塊,包括if…else語句,case語句等等比較多,很差維護擴展,那麼就能夠藉助內部類+設計模式解決。
適當的使用內部類,可使得你的代碼更加靈活和富有擴展性。如JDK的lamda表達式,用內部類很是多,代碼優雅不少。以下
// JDK8 Lambda表達式寫法
new Thread(() -> System.out.println("Thread run()")).start();
複製代碼
若是一個類,不能爲其餘的類使用;或者出於某種緣由,不能被其餘類引用。那咱們就能夠考慮把它實現爲內部類。數據庫鏈接池就是這樣一個典型例子。
最後,咱們來看一道經典內部類面試題吧。
public class Outer {
private int age = 12;
class Inner {
private int age = 13;
public void print() {
int age = 14;
System.out.println("局部變量:" + age);
System.out.println("內部類變量:" + this.age);
System.out.println("外部類變量:" + Outer.this.age);
}
}
public static void main(String[] args) {
Outer.Inner in = new Outer().new Inner();
in.print();
}
}
複製代碼
運行結果: