1. 程式人生 > 實用技巧 >【JAVA核心技術】第6章 介面、lambda表示式與內部類

【JAVA核心技術】第6章 介面、lambda表示式與內部類

介面

  • 在Java中,介面不是類,而是對希望符合這個介面的類的一組需求,因而也不能使用new例項化一個介面,但是卻能宣告介面的變數,介面變數必須引用實現了這個介面的類物件。eg:

Comparablex;//ok
x=newEmployee;//Employee是Comprable介面的一個實現類
  • 介面絕不會有例項欄位,但是可以允許存在常量。 Java 8 之後允許在介面中提供簡單方法,但這些方法不允許引用例項欄位——介面沒有例項。

  • 介面允許擴充套件。

publicinterfacePoweredextendsMoveable{
doublemilesPerGallon();
doubleSPEED_LIMIT=90;//介面中欄位預設是publicstaticfinal,不必多餘標記。
}
  • 介面與抽象類的區別:

    使用抽象類表示通用屬性存在一個嚴重的問題:每個類只能擴充套件一個類,如:

classEmployeeextendsPerson,Comparable//Error
classEmployeeextendsPersonimplementsComparable//Ok

lambda表示式

  • lambda表示式是一個可傳遞的程式碼塊,可以在以後執行一次或多次。

  • Lambda 規定介面中只能有一個需要被實現的方法,不是規定介面中只能有一個方法。

    jdk 8中有另一個新特性:default, 被default修飾的方法會有預設實現,不是必須被實現的方法,所以不影響Lambda表示式的使用。

  • lambda 表示式只能引用值不會改變的變數。

  • 如果在lambda中引用一個變數,而這個變數可能在外部改變,這也是不合法的。如:

for(inti=1;i<=100;i++){
ActionListenerlistener=event->
{
System.out.println(i);//錯誤,無法引用變化的i
};
newTimer(1000,listener).start();
}

  • lambda表示式形式:引數,箭頭(->),表示式。如果無法用一個表示式完成,可以將要實現的程式碼放在{}中,幷包含顯式的return語句,如:

(Stringfirst,Stringsecond)->
{
if(first.length()<second.length())return-1;
elseif(first.length()>second.length())return1;
elsereturn0;
}

即使lambda表示式沒有引數,也要提供空括號,eg:

()->{for(inti=0;i<100;i++)System.out.println(i);}

如果可以推斷出lambda表示式的引數型別,則可以忽略其型別,eg:

Comprator<String>comp=(first,second)//編譯器可以推匯出first和second必然是字串
->first.length()-second.length();

如果lambda表示式只有一個引數,並且可以推斷出引數型別,那麼甚至可以省略小括號,eg:

ActionListenerlistener=event->
System.out.println("Thisisalistener");

lambda表示式的返回型別總是會由上下文推導得到,無須指定lambda表示式的返回型別。

函式式介面

  • 對於只有一個抽象方法的介面,需要這種介面的物件時,就可以提供一個lambda表示式。這種介面稱為函式式介面

  • 如Arrays.sort方法,其第二個引數需要一個Comparator例項,Comparator就是一個只有一個方法的介面,所以可以提供一個lambda表示式,eg:

Arrays.sort(words,(first,second)->first.length()-second.length());

Java API中提供的常用的函式式介面

方法引用

方法引用可以在某些條件成立的情況下,更加簡化lambda表示式的宣告方法引用語法格式有以下三種:

  • objectName::instanceMethod

  • ClassName::staticMethod

  • ClassName::instanceMethod

前兩種方式類似,等同於把 lambda 表示式的引數直接當成 instanceMethod/staticMethod 的引數來呼叫。比如 System.out::println 等同於 x->System.out.println(x);Math::max 等同於 (x, y)->Math.max(x,y) 。

最後一種方式,等同於把lambda表示式的第一個引數當成 instanceMethod 的目標物件,其他剩餘引數當成該方法的引數。比如 String::toLowerCase 等同於 x -> x.toLowerCase()。

//Funciton
Lambda表示式:(Applea)->a.getWeight()
等價的方法引用:Apple::getWeight
//Conusmer
Lambda表示式:()->Thread.currentThread().dumpStack()
等價的方法引用:Thread.currentThread()::dumpStack
//BiFunction
Lambda表示式:(str,i)->str.substring(i)
等價的方法引用:String::substring
//Function
Lambda表示式:(Strings)->System.out.println(s)
等價的方法引用:System.out::printl

只有當lambda表示式的體只調用一個方法而不做其他操作時,才可以把lambda表示式重寫為方法引用。如以下表達式:

s->s.length==0

這裡有一個方法呼叫,但是還有一個比較,因而這裡不能使用方法引用。

構造器引用

構造器引用語法如下:ClassName::new,把lambda表示式的引數當成ClassName構造器的引數 。例如BigDecimal::new等同於x->new BigDecimal(x)。

//無參建構函式
Supplier<Apple>c1=()->newApple();
Supplier<Apple>c1=Apple::new;
//一元建構函式
Function<Integer,Apple>c2=(weight)->newApple(weight);
Function<Integer,Apple>c2=Apple::new;
//二元建構函式
BiFunction<Integer,String,Apple>c3=(weight,color)->newApple(weight,color);
BiFunction<Integer,String,Apple>c3=Apple::new;

內部類

內部類是定義在另一個類中的類。

  • 內部類可以對同一個包中的其他類隱藏

  • 內部類方法可以訪問定義這個類作用域中的資料,包括原本私有的資料

  • 使用內部類最吸引人的原因是:每個內部類都能獨立地繼承一個(介面的)實現,所以無論外圍類是否已經繼承了某個(介面的)實現,對於內部類都沒有影響。在實際問題中我們會遇到一些介面無法解決或難以解決的問題,此時我們可以使用內部類繼承某個具體的或抽象的類,間接解決類無法多繼承引起的一系列問題。

  • 內部類可以用多個例項,每個例項都有自己的狀態資訊,並且與其他外圍物件的資訊相互獨立。

  • 內部類並沒有令人迷惑的“is-a”關係,他就是一個獨立的實體。

  • 內部類提供了更好的封裝,除了該外圍類,其他類都不能訪問

  • 建立內部類物件的時刻並不依賴於外圍類物件的建立。


其他參考資料:

[1]Lambda表示式詳解