首先在寫完這篇博客時感謝幾位作者的文章:
https://www.cnblogs.com/txbblog/p/10364558.html
https://blog.csdn.net/qq_40990854/article/details/81067371
今天看《Thinking in Java》時候看到這裏:
public class Parcel7 {
public Contents contents() {
return new Contents() { // Insert a class definition
private int i = 11;
public int value() { return i; }
}; // Semicolon required in this case
}
public static void main(String[] args) {
Parcel7 p = new Parcel7();
Contents c = p.contents();
}
} ///:~
然後自己敲了發現有報錯:
自然作者大大在後面有一定程度的解釋:創建一個繼承自Contents的匿名內部類的對象
但是懷着疑問我又去查了一下匿名類。
經過查閱資料我們總結一下匿名類和匿名對象,之後可能你看作者的代碼可能會理解。
一、匿名對象
匿名對象:通俗的來說就是——沒有名字的對象!(這是最重要的概念)
特徵:
1.語法上: 只創建對象,但是不用變量來接收
2. 匿名對象的使用:
(1).匿名對象也是一個對象,具有對象的所有功能
(2).每一次使用匿名對象時,都是一個新的對象, 每次創建匿名對象都是不同的對象,一個匿名對象,只能使用一次,即匿名對象只能調用一次
Eg:
package test;
public class TestAnonymousClass {
public void print() {
}
public static void main(String[] args) {
// TODO Auto-generated method stub
new TestAnonymousClass().print();
}
}
在這裏的重點是:
new TestAnonymousClass(); //匿名對象的創建
new TestAnonymousClass().print(); //匿名對象調用方法
在這裏我們沒有像平常時候那樣去new一個新對象,平時我們會如下做:
TestAnonymousClass() test = new TestAnonymousClass();
test.print();
我們可以看到如果我們想要創建一個匿名對象的話我們只需要去:new 類名(參數列表);
如果我們想要去調用自己創建的方法或者變量的話直接:new 類名(參數列表).方法名(參數列表);
3.那麼我們想要知道匿名對象有什麼好處那?
匿名對象的好處在以下場景應用會比較便利:
(1)、創建匿名對象直接調用方法,沒有變量名
new Scanner(System.in);
(2)、一旦調用兩次方法,就是創建了兩個對象,造成空間浪費
new Scanner(System.in).nextInt();
new Scanner(System.in).nextInt();
PS:一個匿名對象,只能使用一次。
Eg:
package test;
public class TestAnonymousClass {
String name;
public static void main(String[] args) {
// TODO Auto-generated method stub
new TestAnonymousClass().name = "Tuo";
System.out.println("The result is: "+new TestAnonymousClass().name);
}
}
在上面的代碼中我們加載了匿名對象並給這個類的“name”變量一個值,但是如果你去打印它就會發現:
這是因爲:
匿名對象在創建完之後,直接被系統釋放掉了。也就是說,在類中的全局變量是隨着類的加載而加載,這樣,在使用new TestAnonymousClass().name時,由於是匿名的,類就終結了,因此上一個初始化name屬性的“tuo”就消失了。
如果不想讓初始化的屬性值消失,那麼您可以在創建類時,將想保留的屬性定義爲靜態的!
就像這樣:
package test;
public class TestAnonymousClass {
static String name;
public static void main(String[] args) {
// TODO Auto-generated method stub
new TestAnonymousClass().name = "Tuo";
System.out.println("The result is: "+new TestAnonymousClass().name);
}
}
(3)、匿名對象可以作爲方法的參數和返回值
1)作爲參數
public class Test {
public static void input(Scanner sc){
System.out.println(sc);
}
public static void main(String[] args) {
// 普通方式
Scanner sc = new Scanner(System.in);
input(sc);
//匿名對象作爲方法接收的參數
input(new Scanner(System.in));
}
}
2)作爲返回值(這個大家應該比較熟悉,在《Thinking in Java》中也比較常見)
public class Test2 {
public static void main(String[] args) {
// 普通方式
Scanner sc = getScanner();
}
public static Scanner getScanner(){
//普通方式
//Scanner sc = new Scanner(System.in);
//return sc;
//匿名對象作爲方法返回值
return new Scanner(System.in);
}
}
二、匿名類(匿名內部類)
如果要執行的任務需要一個對象,但卻不值得創建全新的對象(原因可能是所需的類過於簡單,或者是由於它只在一個方法內部使用),匿名類就顯得非常有用。
1、定義匿名內部類的語法格式如下:
new 父類構造器(實參列表) | 實現接口(){
//匿名內部類的類體部分
}
(1)從上面的定義可以看出,匿名內部類必須繼承一個父類,或實現一個接口,但最多隻能繼承一個父類,或實現一個接 口。(這個能夠很好地解釋我剛開始遇到的問題)
(2)兩條規則:
1)匿名內部類不能是抽象類。
2)匿名內部類不能定義構造器。由於匿名內部類沒有類名,所以無法定義構造器,但匿名內部類可以初始化塊, 可以通過初始化塊來完成構造器需要完成的工作。
2、具體實現有以下三種方式:
(1)、最常用的創建匿名內部類的方式是創建某個接口類型的對象。
package test;
interface Purchase{
public double getPrice();
public String getName();
}
public class TestAnonymousClass {
public void test(Purchase p){
System.out.println("購買了" + p.getName()
+ ",花費了" + p.getPrice());
}
public static void main(String[] args) {
// TODO Auto-generated method stub
TestAnonymousClass test = new TestAnonymousClass();
/**
調用test()方法時,需要傳入一個Product參數,
此處傳入其匿名內部類的實例
*/
test.test(new Purchase(){
public double getPrice(){
return 567.8;
}
public String getName(){
return "顯卡";
}
});
}
}
在這裏我們需要注意的是註釋的文字以及下面的代碼即:
new Purchase(){
public double getPrice(){
return 567.8;
}
public String getName(){
return "顯卡";
}
}
Ps:
定義匿名內部類無須class關鍵字,而是在定義匿名內部類時直接生成該匿名內部類的對象。
最後貼出我們打印的內容:
(2)、當通過接口來創建匿名內部類時,匿名內部類不能顯示創建構造器,因此匿名內部類裏只有一個隱式的無參構造器, 故new接口名後的括號裏不能傳入參數值。如果通過繼承父類來創建匿名內部類時,匿名內部類將擁有和父類相似的 構造器,此處的相似指的是擁有相同的形參列表。
package test;
abstract class Device{
private String name;
public abstract double getPrice();
public Device(){}
public Device(String name){
this.name = name;
}
// 此處省略了name的setter和getter方法
public void setName(String name){
this.name = name;
}
public String getName(){
return this.name;
}
}
public class TestAnonymousClass {
public void test(Device d){
System.out.println("購買了" + d.getName()
+ ",花費了" + d.getPrice());
}
public static void main(String[] args){
TestAnonymousClass test = new TestAnonymousClass();
// 調用有參數的構造器創建Device匿名實現類的對象
test.test(new Device("電子示波器"){
public double getPrice(){
return 67.8;
}
});
// 調用無參數的構造器創建Device匿名實現類的對象
Device d = new Device(){
// 初始化塊
{
System.out.println("匿名內部類的初始化塊...");
}
// 實現抽象方法
public double getPrice(){
return 56.2;
}
// 重寫父類的實例方法
public String getName(){
return "鍵盤";
}
};
test.test(d);
}
}
這裏主要注意的代碼即:
test.test(new Device("電子示波器"){
public double getPrice(){
return 67.8;
}
});
// 調用無參數的構造器創建Device匿名實現類的對象
Device d = new Device(){
// 初始化塊
{
System.out.println("匿名內部類的初始化塊...");
}
// 實現抽象方法
// 重寫父類的實例方法
public String getName(){
return "鍵盤";
}
};
Ps:由於匿名內部類不能是抽象類,所以匿名內部類必須實現它的抽象父類或接口裏包含的所有抽象方法。如果有需要,也可以 重寫父類中的普通方法
如果我們刪除上面代碼main方法中對 public abstract double getPrice() 的重寫,就會報錯:
我們貼出結果:
(3)、在Java 8之前,Java要求被局部內部類、匿名內部類訪問的局部變量必須使用final修飾,從Java 8開始這個限制取消 了,Java 8更加智能:如果局部變量被匿名內部類訪問,那麼該局部變量相對於自動使用了final修飾。
package test;
interface A{
void test();
}
public class TestAnonymousClass {
public static void main(String[] args){
int age = 8; // ①
/**
* 下面代碼將會導致編譯錯誤
* 由於age局部變量被匿名內部類訪問了,因此age相當於被final修飾了age = 2;
* */
A a = new A(){
public void test(){
/**
* 在Java 8以前下面語句將提示錯誤:age必須使用final修飾
* 從Java 8開始,匿名內部類、局部內部類允許訪問非final的局部變量
*/
System.out.println(age);
}
};
a.test();
}
}
Ps:Java 8將這個功能稱爲“effectively final”,它的意思是對於被匿名內部類訪問的局部變量,可以用final修飾,也可以不用 final修飾,但必須按照有final修飾的方式來使用——也就是一次賦值後,以後不能重新賦值。
結果: