函式式介面
理解Functional Interface(函式式介面,以下簡稱FI)是學習Java8 Lambda表示式的關鍵所在,所以放在最開始討論。FI的定義其實很簡單:任何介面,如果只包含唯一一個抽象方法,那麼它就是一個FI。為了讓編譯器幫助我們確保一個介面滿足FI的要求(也就是說有且僅有一個抽象方法),Java8提供了@FunctionalInterface註解。舉個簡單的例子,Runnable介面就是一個FI,下面是它的原始碼:
@Functional Interface
public interface Runnable{
public abstract void run ();
}
public interface Runnable {
public abstract void run ();
}
複製程式碼
ps: 上述兩種都是函式式介面,在Java 8 提供新的註解Function Interface
宣告一個介面為函式式介面,宣告之後這個介面必須符合函式式介面的規範。@FunctionalInterface 對於介面是不是函式式介面沒有影響,但該註解知識提醒編譯器去檢查該介面是否僅包含一個抽象方法。
下面的用法就是錯誤:
@Function Interface
public interface test{
public void test1();
public void test2();
複製程式碼
ps: 介面中宣告的方法預設是抽象的,使用註解後,編譯器自動檢查發現存在兩個抽象方法,會報錯。
函式式介面的規範
- 函式式介面裡是可以包含預設方法,因為預設方法不是抽象方法,其有一個預設實現,所以是符合函式式介面的定義的;
@FunctionalInterface
interface GreetingService
{
void sayMessage(String message);
default void doSomeMoreWork1()
{
// Method body
}
default void doSomeMoreWork2()
{
// Method body
}
}
複製程式碼
- 函式式介面裡是可以包含靜態方法,因為靜態方法不能是抽象方法,是一個已經實現了的方法,所以是符合函式式介面的定義的;
@FunctionalInterface
interface GreetingService
{
public void sayMessage(String message);
public static void printHello(){
System.out.println("Hello");
}
}
複製程式碼
- 函式式介面裡是可以包含Object裡的public方法,這些方法對於函式式介面來說,不被當成是抽象方法(雖然它們是抽象方法);因為任何一個函式式介面的實現,預設都繼承了Object類,包含了來自java.lang.Object裡對這些抽象方法的實現。
@FunctionalInterface
interface GreetingService
{
void sayMessage(String message);
@Override
boolean equals(Object obj);
}
複製程式碼
Java 內部類
為什麼講內部類,因為在 lamada 表示式又與 Java 內部類應用存在相似之處,特別是匿名內部類。在學習這部分前專門又複習了一遍內部類的概念。
分類
成員內部類
區域性內部類
靜態內部類
匿名內部類
成員內部類
- 定義成員內部類後在建立該內部類的物件是不同於普通類的,成員內部類是其外部類的屬性。因此在建立時必須首先建立其外部類物件,再建立內部類的物件。
內部類 物件名 = 外部類物件.new 內部類( );
- 外部類是不能直接使用內部類的成員和方法滴,可先建立內部類的物件,然後通過內部類的物件來訪問其成員變數和方法。
- 可先建立內部類的物件,然後通過內部類的物件來訪問其成員變數和方法。
public class Outer {
private static int i = 1;
private int j = 10;
private int k = 20;
public static void outerF1() {
}
/**
* 外部類的靜態方法訪問成員內部類,與在外部類外部訪問成員內部類一樣
*/
public static void outerF4() {
//step1 建立外部類物件
Outer out = new Outer();
//step2 根據外部類物件建立內部類物件
Inner inner = out.new Inner();
//step3 訪問內部類的方法
inner.innerF1();
}
public static void main(String[] args) {
/*
* outerF4();該語句的輸出結果和下面三條語句的輸出結果一樣
*如果要直接建立內部類的物件,不能想當然地認為只需加上外圍類Outer的名字,
*就可以按照通常的樣子生成內部類的物件,而是必須使用此外圍類的一個物件來
*建立其內部類的一個物件:
*Outer.Inner outin = out.new Inner()
*因此,除非你已經有了外圍類的一個物件,否則不可能生成內部類的物件。因為此
*內部類的物件會悄悄地連結到建立它的外圍類的物件。如果你用的是靜態的內部類,
*那就不需要對其外圍類物件的引用。
*/
Outer out = new Outer();
Outer.Inner outin = out.new Inner();
outin.innerF1();
}
public void outerF2() {
}
/**
* 外部類的非靜態方法訪問成員內部類
*/
public void outerF3() {
Inner inner = new Inner();
inner.innerF1();
}
/**
* 成員內部類中,不能定義靜態成員
* 成員內部類中,可以訪問外部類的所有成員
*/
class Inner {
// static int innerI = 100;內部類中不允許定義靜態變數
// 內部類和外部類的例項變數可以共存
int j = 100;
int innerI = 1;
void innerF1() {
System.out.println(i);
//在內部類中訪問內部類自己的變數直接用變數名
System.out.println(j);
//在內部類中訪問內部類自己的變數也可以用this.變數名
System.out.println(this.j);
//在內部類中訪問外部類中與內部類同名的例項變數用外部類名.this.變數名
System.out.println(Outer.this.j);
//如果內部類中沒有與外部類同名的變數,則可以直接用變數名訪問外部類變數
System.out.println(k);
outerF1();
outerF2();
}
}
}
複製程式碼
- 區域性內部類
在方法中定義的內部類稱為區域性內部類。與區域性變數類似,區域性內部類不能有訪問說明符,因為它不是外圍類的一部分,但是它可以訪問當前程式碼塊內的常量,和此外圍類所有的成員。
public class Outer {
private int s = 100;
private int outI = 1;
public static void main(String[] args) {
// 訪問區域性內部類必須先有外部類物件
Outer out = new Outer();
out.f(3);
}
public void f(final int k) {
final int s = 200;
int i = 1;
final int j = 10;
/**
* 定義在方法內部
*/
class Inner {
// 可以定義與外部類同名的變數
int s = 300;
int innerI = 100;
// static int m = 20; 不可以定義靜態變數
Inner(int k) {
innerF(k);
}
void innerF(int k) {
// java如果內部類沒有與外部類同名的變數,在內部類中可以直接訪問外部類的例項變數
System.out.println(outI);
// 可以訪問外部類的區域性變數(即方法內的變數),但是變數必須是final的
System.out.println(j);
//System.out.println(i);
// 如果內部類中有與外部類同名的變數,直接用變數名訪問的是內部類的變數
System.out.println(s);
// 用this.變數名訪問的也是內部類變數
System.out.println(this.s);
// 用外部類名.this.內部類變數名訪問的是外部類變數
System.out.println(Outer.this.s);
}
}
new Inner(k);
}
}
複製程式碼
- 靜態內部類(巢狀類)
如果你不需要內部類物件與其外圍類物件之間有聯絡,那你可以將內部類宣告為
static
。這通常稱為巢狀類(nested class)。想要理解static應用於內部類時的含義,你就必須記住,普通的內部類物件隱含地儲存了一個引用,指向建立它的外圍類物件。然而,當內部類是static的時,就不是這樣了。 要建立巢狀類的物件,並不需要其外圍類的物件。 不能從巢狀類的物件中訪問非靜態的外圍類物件。
public class Outer {
private static int i = 1;
private int j = 10;
public static void outerF1() {
}
public static void main(String[] args) {
new Outer().outerF3();
}
public void outerF2() {
}
public void outerF3() {
// 外部類訪問內部類的靜態成員:內部類.靜態成員
System.out.println(Inner.inner_i);
Inner.innerF1();
// 外部類訪問內部類的非靜態成員:例項化內部類即可
Inner inner = new Inner();
inner.innerF2();
}
/**
* 靜態內部類可以用public,protected,private修飾
* 靜態內部類中可以定義靜態或者非靜態的成員
*/
static class Inner {
static int inner_i = 100;
int innerJ = 200;
static void innerF1() {
// 靜態內部類只能訪問外部類的靜態成員(包括靜態變數和靜態方法)
System.out.println("Outer.i" + i);
outerF1();
}
void innerF2() {
// 靜態內部類不能訪問外部類的非靜態成員(包括非靜態變數和非靜態方法)
// System.out.println("Outer.i"+j);
// outerF2();
}
}
}
複製程式碼
匿名內部類
這是我們今天的主角,匿名內部類, 字面意思沒有名字的類
{}
。匿名內部作為最特殊的內部類,需要講解的內容。(think in java)
為什麼使用匿名內部類:
- 只用到類的一個例項
- 類在定義後馬上用到
- 類非常小(SUN推薦是在4行程式碼以下)
- 給類命名並不會導致你的程式碼更容易被理解
在使用匿名內部類時,要記住以下幾個原則:
- 匿名內部類一般不能有構造方法。
- 匿名內部類不能定義任何靜態成員、方法和類。
- 匿名內部類不能是public,protected,private,static。
- 只能建立匿名內部類的一個例項。
- 一個匿名內部類一定是在new的後面,用其隱含實現一個介面或實現一個類。
- 因匿名內部類為區域性內部類,所以區域性內部類的所有限制都對其生效。
你可能見過如下的程式碼:
List<Integer> var1 = new ArrayList<Integer>()
{
{
add(1);
add(2);
}
};
複製程式碼
就是用到匿名類的語法糖。
// 在方法中返回一個匿名內部類
public class Parcel6 {
public static void main(String[] args) {
Parcel6 p = new Parcel6();
Contents c = p.cont();
}
public Contents cont() {
return new Contents() {
private int i = 11;
public int value() {
return i;
}
}; // 在這裡需要一個分號
}
}
複製程式碼
cont()方法將下面兩個動作合併在一起:返回值的生成,與表示這個返回值的類的定義。
return new Contents()
但是,在到達語句結束的分號之前,你卻說:“等一等,我想在這裡插入一個類的定義”:
這種奇怪的語法指的是:“建立一個繼承自Contents的匿名類的物件。”通過new 表示式返回的引用被自動向上轉型為對Contents的引用。匿名內部類的語法是下面例子的簡略形式:
class MyContents implements Contents {
private int i = 11;
public int value() {
return i;
}
}
return new MyContents();
複製程式碼
上述這類寫法是最常見。
在Java中,通常就是編寫另外一個類或類庫的人規定一個介面,然後你來實現這個介面,然後把這個介面的一個物件作為引數傳給別人的程式,別人的程式必要時就會通過那個介面來呼叫你編寫的函式,執行後續的一些方法。
public class CallBack {
public static void main(String[] args) {
CallBack callBack = new CallBack();
callBack.toDoSomethings(100, new CallBackInterface() {
public void execute() {
System.out.println("我的請求處理成功了");
}
});
}
public void toDoSomethings(int a, CallBackInterface callBackInterface) {
long start = System.currentTimeMillis();
if (a > 100) {
callBackInterface.execute();
} else {
System.out.println("a < 100 不需要執行回撥方法");
}
long end = System.currentTimeMillis();
System.out.println("該介面回撥時間 : " + (end - start));
}
}
public interface CallBackInterface {
void execute();
}
複製程式碼
Java裡的回撥,可以說是匿名內部類精彩表演,優美的編碼風格,真是讓人陶醉~ this is so amazing 。
經過上述的鋪墊引出下面的主角 lamada 表示式實現函式式介面。
Lambda語法糖
為了能夠方便、快捷、幽雅的建立出FI的例項,Java8提供了Lambda表示式這顆語法糖。下面我用一個例子來介紹Lambda語法。假設我們想對一個List按字串長度進行排序,那麼在Java8之前,可以藉助匿名內部類來實現:
List<String> words = Arrays.asList("apple", "banana", "pear");
words.sort(new Comparator<String>() {
@Override
public int compare(String w1, String w2) {
return Integer.compare(w1.length(), w2.length());
}
});
複製程式碼
上面的匿名內部類簡直可以用醜陋來形容,唯一的一行邏輯被五行垃圾程式碼淹沒。根據前面的定義(並檢視Java原始碼)可知,Comparator是個FI,所以,可以用Lambda表示式來實現:
words.sort((String w1, String w2) -> {
return Integer.compare(w1.length(), w2.length());
});
複製程式碼
ps: 看起來像一個匿名的方法,實際就是一個匿名類物件的引用,程式碼看起來更加簡潔。可以認為 lamada表示式實現了介面的抽象方法,因為函式式介面預設只有一個抽象方法。