一:什麼是內部類?
(1) 在類中定義一個類(私有內部類,靜態內部類)
(2) 在方法中定義一個類(局部內部類,匿名內部類)
二、爲什麼需要內部類?
主要原因有以下幾點:- 內部類方法可以訪問該類定義所在的作用域的數據,包括私有的數據
- 內部類可以對同一個包中的其他類隱藏起來,一般的非內部類,是不允許有 private 與protected權限的。
- 當想要定義一個回調函數且不想編寫大量代碼時,使用匿名內部類比較便捷、方便編寫線程代碼
- 每個內部類都能獨立的繼承一個接口的實現,所以無論外部類是否已經繼承了某個(接口的)實現,對於內部類都沒有影響。內部類使得多繼承的解決方案變得完整。 每個內部類都能獨立地繼承自一個(接口的)實現,所以無論外圍類是否已經繼承了某個(接口的)實現,對於內部類都沒有影響。大家都知道Java只能繼承一個類,它的多重繼承在我們沒有學習內部類之前是用接口來實現的。但使用接口有時候有很多不方便的地方。比如我們實現一個接口就必須實現它裏面的所有方法。而有了內部類就不一樣了。它可以使我們的類繼承多個具體類或抽象類。
二、內部類解析
1、成員內部類
- 即在一個類中直接定義的內部類, 成員內部類與普通的成員沒什麼區別,可以與普通成員一樣進行修飾和限制。成員內部類不能含有static的變量和方法。
- 成員內部類可以無條件訪問外部類的所有成員屬性和成員方法(包括private成員和靜態成員)。
- 在外部類中如果要訪問成員內部類的成員,必須先創建一個成員內部類的對象,再通過指向這個對象的引用來訪問。
- 由於成員內部類看起來像是外部類的一個成員,所以可以像類的成員一樣擁有多種權限修飾。可以擁有private訪問權限、protected訪問權限、public訪問權限及包訪問權限。如果成員內部類用private修飾,則只能在外部類的內部訪問,如果用public修飾,則任何地方都能訪問;如果用protected修飾,則只能在同一個包下或者繼承外部類的情況下訪問;如果是默認訪問權限,則只能在同一個包下訪問。這一點和外部類有一點不一樣,外部類只能被public和包訪問兩種權限修飾。
package innerclass;
public class OutClassT1 {
private String outName = "outName";
private int sameAge = 10;
/**
* 不能定義靜態成員、可以訪問外部類的所有成員、內部類和外部類的實例變量可以共存
*/
class InnerClass {
public String innerName = "innerName";
//內部類和外部類的實例變量可以共存
public int sameAge = 20;
public void write() {
InnerClass clazz = new InnerClass();
//成員內部類可以任意訪問外部類的成員變量
System.out.println("outName=" + outName);
//在內部類中訪問內部類自己的變量
System.out.println("innerName=" + innerName);
//在內部類中訪問內部類自己的變量也可以用this.變量名
System.out.println("sameAge=" + this.sameAge);
//在內部類中訪問外部類中與內部類同名的實例變量用外部類名.this.變量名
System.out.println("sameAge=" + OutClassT1.this.sameAge);
}
}
/**
* 外部類非靜態方法獲取內部類實例變量,可以直接new,因爲非靜態方法是屬於外部類實例對象的,調用該方法的時候說明外部類實例對象已經存在了
* 在外部一般通過這種方法獲取內部類實例的
*/
public InnerClass getInnerClass() {
InnerClass innerClass = new InnerClass();
return innerClass;
}
/**
* 外部類靜態方法訪問內部類的實例變量,必須new外部類實例,再通過外部類的實例變量獲取內部類實例
* 原因:成員內部類是依附外部類而存在的,也就是說,如果要創建成員內部類的對象,前提是必須存在一個外部類的對象
*/
public static void callInnerPro() {
//InnerClass innerClass = new OutClassT1().getInnerClass();
InnerClass innerClass = new OutClassT1().new InnerClass();
System.out.println(innerClass.innerName);
}
public static void main(String[] args) {
/**
* 也可以通過如下兩種方式獲取內部類實例
* 成員內部類是依附外部類而存在的,也就是說,如果要創建成員內部類的對象,前提是必須存在一個外部類的對象。
*/
//InnerClass innerClass = new OutClassT1().new InnerClass();
InnerClass innerClass = new OutClassT1().getInnerClass();
innerClass.write();
new OutClassT1().callInnerPro();
}
}
- 爲什麼外部類可以創建內部類的對象?並且內部類能夠方便的引用到外部類對象?
class innerclass.OutClassT1$InnerClass extends java.lang.Object{
//域
public java.lang.String innerName;
public int sameAge;
final innerclass.OutClassT1 this$0;
//構造器
InnerClass(innerclass.OutClassT1);
//方法
public void write( );
}
編譯器會默認爲成員內部類添加了一個指向外部類對象的引用。我們在定義的內部類的構造器是無參構造器,編譯器還是會默認添加一個參數,該參數的類型爲指向外部類對象的一個引用,所以成員內部類中的this$0 指針便指向了外部類對象,因此可以在成員內部類中隨意訪問外部類的成員。從這裏也間接說明了成員內部類是依賴於外部類的,如果沒有創建外部類的對象,則無法對this&0引用進行初始化賦值,也就無法創建成員內部類的對象了。
- 爲什麼內部類可以引用外部類的私有域?
對OutClassT1進行java解析發現:爲外部類的私有變量都添加了靜態方法:
public class innerclass.OutClassT1 extends java.lang.Object{
//域
private java.lang.String outName;
private int sameAge;
//構造器
public OutClassT1( );
//方法
public static void main(java.lang.String[]);
static int access$100(innerclass.OutClassT1);
static java.lang.String access$000(innerclass.OutClassT1);
public innerclass.OutClassT1$InnerClass getInnerClass( );
public static void callInnerPro( );
}
static int access$100(innerclass.OutClassT1);
static java.lang.String access$000(innerclass.OutClassT1);
它將返回值作爲參數傳遞給他的對象域data。這樣內部類InnerClass中的打印語句:System.out.println(outName);
實際上運行的時候調用的是: System.out.println(this$0.access$000(OutClassT1));
總結一下編譯器對類中內部類做的手腳吧:
(1) 在內部類中偷偷摸摸的創建了包可見構造器,從而使外部類獲得了創建權限。
(2) 在外部類中偷偷摸摸的創建了訪問私有變量的靜態方法,從而 使 內部類獲得了訪問權限。
這樣,類中定義的內部類無論私有,公有,靜態都可以被包圍它的外部類所訪問。
2、局部內部類
- 在方法中定義的內部類稱爲局部內部類。
- 局部內部類就像是方法裏面的一個局部變量一樣,是不能有public、protected、private以及static修飾符的。因爲它不是外圍類的一部分,但是它可以訪問當前代碼塊內的常量,和此外圍類所有的成員。
- 可以定義與外部類同名的變量、局部內部類中不可以定義靜態變量、局部內部類中可以訪問外部類的變量。
- 因爲方法中的內部類沒有訪問修飾符, 所以方法內部類對包圍它的方法之外的任何東西都不可見,故:局部內部類只能在定義該內部類的方法內實例化,不可以在此方法外對其實例化。
- 局部內部類對象不能使用該內部類所在方法的非final局部變量。
public class OutClassT3 {
public String outName = "outName";
private String sameName= "SameName from out";
public void outMethod(final String outMethodInput) {
final String outMethodName = "outMethodName";
/**
* 局部內部類就像是方法裏面的一個局部變量一樣,是不能有public、protected、private以及static修飾符的
* 可以定義與外部類同名的變量
* 局部內部類中不可以定義靜態變量
* 可以訪問外部類的變量
* 訪問所在外部類方法的變量,則該變量必須是final類型的
*/
class InnerClass {
//可以定義與外部類同名的變量
public String sameName = "SameName from inner";
//局部內部類中不可以定義靜態變量
//public static int age;
void innerMethod() {
//可以直接訪問外部類的變量
System.out.println("outName=" + outName);
//可以直接訪問自己的變量
System.out.println("sameName=" + sameName);
//訪問和內部類變量重名的外部類變量
System.out.println("sameName=" + OutClassT3.this.sameName);
//訪問所在外部類方法的變量,則該變量必須是final類型的
System.out.println("outMethodName=" + outMethodName);
//訪問所在外部類方法的變量,則該變量必須是final類型的
System.out.println("outMethodInput=" + outMethodInput);
}
}
/**
* 局部內部類只能在定義該內部類的方法內實例化,不可以在此方法外對其實例化。
*/
new InnerClass().innerMethod();
}
public static void main(String[] args) {
OutClassT3 outClassT3 = new OutClassT3();
outClassT3.outMethod("outMethodInput");
}
}
3、匿名內部類
簡單地說:匿名內部類就是沒有名字的內部類。匿名內部類是唯一一種沒有構造器的類。正因爲其沒有構造器,所以匿名內部類的使用範圍非常有限,大部分匿名內部類用於接口回調。一般來說,匿名內部類用於繼承其他類或是實現接口,不需要增加額外的方法,只是對繼承方法的實現或是重寫。
比較常用的場景是:爲組件添加監聽功能、多線程開發。
- 什麼情況下需要使用匿名內部類?如果滿足下面的一些條件,使用匿名內部類是比較合適的:
- 只用到類的一個實例。
- 類在定義後馬上用到。
- 類非常小(SUN推薦是在4行代碼以下)
- 給類命名並不會導致你的代碼更容易被理解。
- 匿名內部類不能有構造方法。
- 匿名內部類不能定義任何靜態成員、方法和類。
- 匿名內部類不能是public,protected,private,static。
- 只能創建匿名內部類的一個實例。
- 一個匿名內部類一定是在new的後面,用其隱含實現一個接口或實現一個類。
- 因匿名內部類爲局部內部類,所以局部內部類的所有限制都對其生效。
4、靜態內部類(也稱之爲嵌套類)
如果不需要內部類對象與其外圍類對象之間有聯繫,可以將內部類聲明爲static。
普通的內部類對象隱含地保存了一個引用,指向創建它的外圍類對象。然而,當內部類是static的時,靜態內部類是不需要依賴於外部類,這意味着:
- 要創建嵌套類的對象,並不需要其外圍類的對象。
- 不能從嵌套類的對象中訪問非靜態的外圍類對象。
生成一個靜態內部類不需要外部類成員:這是靜態內部類和成員內部類的區別。
public class OutClassT3 {
public String outName = "outName";
private String sameName= "SameName from out";
public void outMethod(final String outMethodInput) {
final String outMethodName = "outMethodName";
/**
* 局部內部類就像是方法裏面的一個局部變量一樣,是不能有public、protected、private以及static修飾符的
* 可以定義與外部類同名的變量
* 局部內部類中不可以定義靜態變量
* 可以訪問外部類的變量
* 訪問所在外部類方法的變量,則該變量必須是final類型的
*/
class InnerClass {
//可以定義與外部類同名的變量
public String sameName = "SameName from inner";
//局部內部類中不可以定義靜態變量
//public static int age;
void innerMethod() {
//可以直接訪問外部類的變量
System.out.println("outName=" + outName);
//可以直接訪問自己的變量
System.out.println("sameName=" + sameName);
//訪問和內部類變量重名的外部類變量
System.out.println("sameName=" + OutClassT3.this.sameName);
//訪問所在外部類方法的變量,則該變量必須是final類型的
System.out.println("outMethodName=" + outMethodName);
//訪問所在外部類方法的變量,則該變量必須是final類型的
System.out.println("outMethodInput=" + outMethodInput);
}
}
/**
* 局部內部類只能在定義該內部類的方法內實例化,不可以在此方法外對其實例化。
*/
new InnerClass().innerMethod();
}
public static void main(String[] args) {
OutClassT3 outClassT3 = new OutClassT3();
outClassT3.outMethod("outMethodInput");
}
}
public class Test {
public static void main(String[] args) {
/**
* 需要通過生成外部類對象來生成內部類實例
*/
OutClassT2.StaticInnerClass innerClass = new OutClassT2.StaticInnerClass();
//這裏只能調用public類型的方法
innerClass.innerMethod();
//不能調用私有方法,這裏方法的修飾符和平時普通類的修飾符意義一樣
//innerClass.privatInnerMethod();
// 調用靜態內部類的靜態方法,直接調用
OutClassT2.StaticInnerClass.staticInnerMethod();
}
}
參考:
https://www.cnblogs.com/ITtangtang/p/3980460.html
http://www.cnblogs.com/dolphin0520/p/3811445.html
https://www.cnblogs.com/yanzige/p/5377332.html
http://blog.51cto.com/android/384809
《java編程思想》
附:JAVA分析器代碼:
package tools;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Scanner;
public class ClassAnalyzer {
private static final String tab = " ";//縮進
/**
* 調用該方法,並傳遞class文件名稱即可,比如ClassAnalyzer.analyzer("OutClassT1");//OutClassT1對應的是編譯後生成的class文件名稱
* @param className
* @throws ClassNotFoundException
*/
public static void analyzer(String className) throws ClassNotFoundException {
Class c = Class.forName(className);
System.out.print(Modifier.toString(c.getModifiers()));
System.out.print(" ");
System.out.print(c.toString());
Class superC = c.getSuperclass();
if (superC != null) {
System.out.print(" extends " + superC.getName());
}
System.out.println("{");//類開始括號
//打印域
System.out.println(tab + "//域");
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
printField(field);
}
//打印構造器
System.out.println(tab + "//構造器");
Constructor[] constructors = c.getDeclaredConstructors();
for (Constructor constructor : constructors) {
printConstructor(constructor);
}
//打印方法
System.out.println(tab + "//方法");
Method[] methods = c.getDeclaredMethods();
for (Method method : methods) {
printMethod(method);
}
System.out.println("}");//類結束括號
}
//打印域
private static void printField(Field field) {
System.out.print(tab);
System.out.print(Modifier.toString(field.getModifiers()));
System.out.print(" ");
Class fieldType = field.getType();
if (fieldType.isArray()) {
System.out.print(getArrayTypeName(fieldType));
} else {
System.out.print(field.getType().getName());
}
System.out.print(" ");
System.out.print(field.getName());
System.out.println(";");
}
//打印構造器
private static void printConstructor(Constructor constructor) {
System.out.print(tab);
System.out.print(Modifier.toString(constructor.getModifiers()));
System.out.print(" ");
System.out.print(constructor.getDeclaringClass().getSimpleName());
Class[] varTypes = constructor.getParameterTypes();
System.out.print("(");
printParameters(varTypes);
System.out.println(");");
}
//打印方法
private static void printMethod(Method method) {
System.out.print(tab);
System.out.print(Modifier.toString(method.getModifiers()));
System.out.print(" ");
Class returnType = method.getReturnType();
if (returnType.isArray()) {
System.out.print(getArrayTypeName(returnType));
} else {
System.out.print(method.getReturnType().getName());
}
System.out.print(" ");
System.out.print(method.getName());
System.out.print("(");
Class[] varTypes = method.getParameterTypes();
printParameters(varTypes);
System.out.print(")");
//聲明拋出的異常
Class[] exceptionType = method.getExceptionTypes();
if (exceptionType.length != 0) {
System.out.print(" throws ");
for (int i = 0; i < exceptionType.length; i++) {
System.out.print(exceptionType[i].getName());
if (i < (exceptionType.length - 1)) {
System.out.print(",");
}
}
}
System.out.println(";");
}
//打印構造器和方法的參數列表
private static void printParameters(Class[] varTypes) {
if (varTypes.length > 0) {
for (int i = 0; i < varTypes.length; i++) {
if (varTypes[i].isArray()) {
System.out.print(getArrayTypeName(varTypes[i]));
} else {
System.out.print(varTypes[i].getName());
}
if (i < (varTypes.length - 1)) {
System.out.print(", ");
}
}
} else {
System.out.print(" ");
}
}
public static String getArrayTypeName(Class type) {
StringBuffer buffer = new StringBuffer(getArrayType(type).getName());
int dimension = countArrayDimension(type);
for (int i = 1; i <= dimension; i++) {
buffer.append("[]");
}
return buffer.toString();
}
public static int countArrayDimension(Class type) {
int dimension = 0;
if (type.isArray()) {
Class tempType = type;
while ((tempType = tempType.getComponentType()) != null) {
dimension++;
}
}
return dimension;
}
public static Class getArrayType(Class type) {
Class arrayType = null;
if (type.isArray()) {
Class tempType = type.getComponentType();
do {
arrayType = tempType;
} while ((tempType = tempType.getComponentType()) != null);
}
return arrayType;
}
public static void main(String[] args) {
try {
// Scanner in = new Scanner(System.in);
// System.out.print("Input class name:");
// String className = in.next();
// in.close();
System.out.println("args = [" + 11111 + "]");
String className = "com.wisely.learn.generic.DateInterval";
analyzer(className);
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
}
}