5.面向?qū)ο笙?/h1>

以下是《瘋狂Java講義》中的一些知識衅胀,如有錯誤液荸,煩請指正拇砰。


Java8增強(qiáng)的包裝類

自動裝箱就是把一個基本類型的變量直接賦給對應(yīng)的包裝類變量食铐,自動拆箱則與之相反匕垫。
把字符串類型轉(zhuǎn)換成基本類型:

  • 除了Character之外的所有包裝類都提供了一個parseXxx(String s)靜態(tài)方法。
  • 利用包裝類提供的Xxx(String s)構(gòu)造器

String提供了多個重載的valueOf()方法虐呻,用于將基本類型轉(zhuǎn)換成字符串象泵∧海或者將基本類型與""進(jìn)行連接運(yùn)算,String intstr = 5+"";

public class Primitive2String
{
    public static void main(String[] args)
    {
        String intStr = "123";
        // 把一個特定字符串轉(zhuǎn)換成int變量
        int it1 = Integer.parseInt(intStr);
        int it2 = new Integer(intStr);
        System.out.println(it2);
        String floatStr = "4.56";
        // 把一個特定字符串轉(zhuǎn)換成float變量
        float ft1 = Float.parseFloat(floatStr);
        float ft2 = new Float(floatStr);
        System.out.println(ft2);
        // 把一個float變量轉(zhuǎn)換成String變量
        String ftStr = String.valueOf(2.345f);
        System.out.println(ftStr);
        // 把一個double變量轉(zhuǎn)換成String變量
        String dbStr = String.valueOf(3.344);
        System.out.println(dbStr);
        // 把一個boolean變量轉(zhuǎn)換成String變量
        String boolStr = String.valueOf(true);
        System.out.println(boolStr.toUpperCase());
    }
}

注意:將-128-127之間的同一個整數(shù)自動裝箱成Integer實例時偶惠,永遠(yuǎn)都是引用cache數(shù)組的同一個元素春寿,所以他們?nèi)肯嗟龋徊辉谶@個范圍的整數(shù)自動裝箱城Integer忽孽,只有兩個包裝類引用指向同一個對象時才相等绑改。

Java 7為所有包裝類增加一個新方法:compare(x,y)。該方法用于比較兩個包裝類實例兄一,當(dāng)x>y厘线,返回大于0的數(shù);當(dāng)x==y出革,返回0造壮;否則返回小于0的數(shù)骂束。還有其他的一些方法不做介紹了。

處理對象

打印對象和toString方法:toString方法是系統(tǒng)將會輸出該對象的“自我描述”信息楞抡,用以告訴外界對象具有的狀態(tài)信息析藕。
Object 類提供的toString方法總是返回該對象實現(xiàn)類的類名+@+hashCode值。這個并不能真正實現(xiàn)自我描述的功能竞慢,因此用戶必須在自定義類中重寫Object類的toString方法治泥。通常可返回類名[filed1=值1,field2=值2,...]

==和equals比較運(yùn)算符
==要求兩個引用變量指向同一個對象才會返回true居夹,不可用于比較類型上沒有父子關(guān)系的兩個對象败潦;對于基本類型變量,只要變量值相等返回true准脂。equals方法則允許用戶提供自定義的相等規(guī)則劫扒。Object類提供的equals方法判斷兩個對象相等的標(biāo)準(zhǔn)與==完全相同。因此開發(fā)者通常需要重寫equals方法狸膏。

字符串直接量與字符串對象
Java程序直接使用"hello"的字符串直接量時沟饥,JVM將會使用常量池管理這些字符串;當(dāng)使用new String("hello"),JVM會先使用常量池管理"hello"的字符串直接量贤旷,在調(diào)用String類的構(gòu)造器創(chuàng)建新的String對象广料,新創(chuàng)建得到String對象被保存在堆內(nèi)存中。

public class StringCompareTest
{
    public static void main(String[] args)
    {
        // s1直接引用常量池中的"瘋狂Java"
        String s1 = "瘋狂Java";
        String s2 = "瘋狂";
        String s3 = "Java";
        // s4后面的字符串值可以在編譯時就確定下來
        // s4直接引用常量池中的"瘋狂Java"
        String s4 = "瘋狂" + "Java";
        // s5后面的字符串值可以在編譯時就確定下來
        // s5直接引用常量池中的"瘋狂Java"
        String s5 = "瘋" + "狂" + "Java";
        // s6后面的字符串值不能在編譯時就確定下來幼驶,
        // 不能引用常量池中的字符串
        String s6 = s2 + s3;
        // 使用new調(diào)用構(gòu)造器將會創(chuàng)建一個新的String對象艾杏,
        // s7引用堆內(nèi)存中新創(chuàng)建的String對象
        String s7 = new String("瘋狂Java");
        System.out.println(s1 == s4); // 輸出true
        System.out.println(s1 == s5); // 輸出true
        System.out.println(s1 == s6); // 輸出false
        System.out.println(s1 == s7); // 輸出false
    }
}

equals方法重寫

class Person
{
    private String name;
    private String idStr;
    public Person(){}
    public Person(String name , String idStr)
    {
        this.name = name;
        this.idStr = idStr;
    }
    // 此處省略name和idStr的setter和getter方法。
    // name的setter和getter方法
    public void setName(String name)
    {
        this.name = name;
    }
    public String getName()
    {
        return this.name;
    }

    // idStr的setter和getter方法
    public void setIdStr(String idStr)
    {
        this.idStr = idStr;
    }
    public String getIdStr()
    {
        return this.idStr;
    }
    // 重寫equals()方法盅藻,提供自定義的相等標(biāo)準(zhǔn)
    public boolean equals(Object obj)
    {
        // 如果兩個對象為同一個對象
        if (this == obj)
            return true;
        // 只有當(dāng)obj是Person對象
        if (obj != null && obj.getClass() == Person.class)
        {
            Person personObj = (Person)obj;
            // 并且當(dāng)前對象的idStr與obj對象的idStr相等才可判斷兩個對象相等
            if (this.getIdStr().equals(personObj.getIdStr()))
            {
                return true;
            }
        }
        return false;
    }
}
public class OverrideEqualsRight
{
    public static void main(String[] args)
    {
        Person p1 = new Person("孫悟空" , "12343433433");
        Person p2 = new Person("孫行者" , "12343433433");
        Person p3 = new Person("孫悟飯" , "99933433");
        // p1和p2的idStr相等糜颠,所以輸出true
        System.out.println("p1和p2是否相等?"
            + p1.equals(p2));
        // p2和p3的idStr不相等萧求,所以輸出false
        System.out.println("p2和p3是否相等其兴?"
            + p2.equals(p3));
    }
}

注意instanceof當(dāng)前面對象是后面類的實例或者子類的實例時都將返回true,在重寫equals方法里不適用夸政。

類成員

即使通過null對象來訪問類成員元旬,程序也不會引發(fā)NullPointerException。
靜態(tài)初始化塊也是類成員的一種匀归。

單例類(Singleton)
如果一個類始終只能創(chuàng)建一個對象,稱為單例類体啰。
條件:

  1. 我們把該類的構(gòu)造器使用Private修飾荒勇,從而把該 類的所有構(gòu)造器隱藏起來沽翔。
  2. 則需要提供一個public方法作為該類的訪問點,用于創(chuàng)建該類的對象橘沥,且必須使用static修飾
  3. 該類還必須緩存已經(jīng)創(chuàng)建的對象威恼,必須用static修飾
class Singleton
{
    // 使用一個類變量來緩存曾經(jīng)創(chuàng)建的實例
    private static Singleton instance;
    // 將構(gòu)造器使用private修飾,隱藏該構(gòu)造器
    private Singleton(){}
    // 提供一個靜態(tài)方法斤蔓,用于返回Singleton實例
    // 該方法可以加入自定義的控制弦牡,保證只產(chǎn)生一個Singleton對象
    public static Singleton getInstance()
    {
        // 如果instance為null驾锰,表明還不曾創(chuàng)建Singleton對象
        // 如果instance不為null,則表明已經(jīng)創(chuàng)建了Singleton對象赏酥,
        // 將不會重新創(chuàng)建新的實例
        if (instance == null)
        {
            // 創(chuàng)建一個Singleton對象裸扶,并將其緩存起來
            instance = new Singleton();
        }
        return instance;
    }
}
public class SingletonTest
{
    public static void main(String[] args)
    {
        // 創(chuàng)建Singleton對象不能通過構(gòu)造器,
        // 只能通過getInstance方法來得到實例
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2); // 將輸出true
    }
}

final修飾符

用于表示修飾的類何荚、方法、變量不可變餐塘。final修飾的成員變量必須由程序員顯式地指定初始值戒傻。系統(tǒng)不會對final成員進(jìn)行隱式初始化需纳。
類變量:必須在靜態(tài)初始化塊或聲明該變量時指定初始值
實例變量:必須在非靜態(tài)初始化塊或聲明該變量時或構(gòu)造器中指定初始值。
使用final修飾局部變量時既可以在定義時指定默認(rèn)值口蝠,也可以不指定默認(rèn)值妙蔗。

final修飾基本類型和引用變量的區(qū)別
當(dāng)使用final修飾基本數(shù)據(jù)類型變時昙啄,不能對其重新賦值梳凛,不能被改變伶跷。但對引用類型的變量而言叭莫,它僅僅保存的是一個引用,final只能保證他的地址不變靖诗,但不能保證對象刊橘,所以引用類型完全可以改變他的對象促绵。

class Person
{
    private int age;
    public Person(){}
    // 有參數(shù)的構(gòu)造器
    public Person(int age)
    {
        this.age = age;
    }
    // 省略age的setter和getter方法
    // age的setter和getter方法
    public void setAge(int age)
    {
        this.age = age;
    }
    public int getAge()
    {
        return this.age;
    }
}
public class FinalReferenceTest
{
    public static void main(String[] args)
    {
        // final修飾數(shù)組變量,iArr是一個引用變量
        final int[] iArr = {5, 6, 12, 9};
        System.out.println(Arrays.toString(iArr));
        // 對數(shù)組元素進(jìn)行排序尖坤,合法
        Arrays.sort(iArr);
        System.out.println(Arrays.toString(iArr));
        // 對數(shù)組元素賦值场梆,合法
        iArr[2] = -8;
        System.out.println(Arrays.toString(iArr));
        // 下面語句對iArr重新賦值或油,非法
        // iArr = null;
        // final修飾Person變量罐脊,p是一個引用變量
        final Person p = new Person(45);
        // 改變Person對象的age實例變量萍桌,合法
        p.setAge(23);
        System.out.println(p.getAge());
        // 下面語句對p重新賦值上炎,非法
        // p = null;
    }
}

可執(zhí)行“宏替換”的final變量
對一個final變量來說寇损,不管它是類變量矛市、實例變量浊吏,還是局部變量,只要該變量滿足3個條件墩衙,這個final變量就不再是一個變量底桂,而是相當(dāng)于一個直接量。

  • 使用final修飾符修飾氛魁;
  • 在定義該final變量時指定了初始值捶码;
  • 該初始值可以在編譯時就被確定下來档押。
public class StringJoinTest
{
    public static void main(String[] args)
    {
        String s1 = "瘋狂Java";
        // s2變量引用的字符串可以編譯時就確定出來,
        // 因此s2直接引用常量池中已有的"瘋狂Java"字符串
        String s2 = "瘋狂" + "Java";
        System.out.println(s1 == s2);
        // 定義2個字符串直接量
        String str1 = "瘋狂";     //①
        String str2 = "Java";     //②
        // 將str1和str2進(jìn)行連接運(yùn)算
        String s3 = str1 + str2;
        System.out.println(s1 == s3);//false
    }
}

str1和str2只是普通變量粒没,編譯器不會執(zhí)行宏替換癞松;只要定義時添加final修飾,結(jié)果為true枫甲。注意對于final實例變量,只有定義該變量時指定初始值才會有宏變量的效果迎捺。

final方法
final 修飾的方法不可以被重寫抄沮。
final 修飾的方法僅僅是不能重寫,但它完全可以被重載蹋订。

public class PrivateFinalMethodTest
{
    private final void test(){}
}
class Sub extends PrivateFinalMethodTest
{
    // 下面方法定義將不會出現(xiàn)問題
    public void test(){}
}

final類
final 修飾的類不可以被繼承

抽象類

抽象方法和類都必須使用abstract來修飾椒功,含有抽象方法的類一定為抽象類丁屎,抽象類里也可以沒有抽象方法。
抽象類不能被實例化,可以通過其子類給他賦值;普通類里有的抽象里也有策肝。
定義抽象方法只需在普通方法上增加abstract修飾符,并把普通方法的方法體(也就是方法后花括號括起來的部分)全部去掉共虑,并在方法后增加分號即可。
注意:static和abstract不能同時修飾某個方法吼鳞,即沒有所謂的類抽象方法看蚜,即使有調(diào)用一個沒有方法體的方法也會引起錯誤赔桌。

抽象類的作用
抽象類代表了一種未完成的類設(shè)計,它體現(xiàn)的是一種模板。

接口

接口定義的是多個類共同的行為規(guī)范港粱,這些行為是與外部交流的通道氮凝,這就意味著接口里通常是定義一組公用的方法袍辞。
接口里不能包含普通方法,所有方法都是抽象方法,Java8允許定義默認(rèn)方法蛉威。

接口定義

[修飾符] interface 接口名 extends 父接口1,父接口2 
{
    零個到多個常量定義...
    零個到多個抽象方法定義...
    零個到多個內(nèi)部類、接口、枚舉定義...
    零個到多個默認(rèn)方法或類方法定義...
}

修飾符可以是public或者省略拴签。
常量都是:public static final修飾
實例方法都是:public abstract 修飾
類方法用public static修飾
默認(rèn)方法用public default修飾
內(nèi)部的類:public static
接口里面沒有構(gòu)造器和初始化塊稠氮。
類方法可以使用接口直接調(diào)用,默認(rèn)方法要通過使用接口的實例來調(diào)用尝江。

接口繼承
接口的繼承和類繼承不一樣易遣,接口完全支持多繼承,子接口擴(kuò)展某個父接口將會獲得父接口的所有抽像方法,類變量没炒。

使用接口
一個類實現(xiàn)了一個或多個接口之后弃衍,這個類必須完全實現(xiàn)這些接口里所定義的全部抽象方法(也就是重寫這些抽象方法)激涤;
否則功蜓,該類將保留從父接口那里繼承到的抽象方法,該類也必須定義成抽象類

接口和抽象類的相似性

  • 接口和抽象類都不能被實例化,它們都位于繼承樹的頂端劈榨,用于被其他類實現(xiàn)和繼承访递。
  • 接口和抽象類都可以包含抽象方法踪古,實現(xiàn)接口或繼承抽象類的普通子類都必須實現(xiàn)這些抽象方法辱魁。

接口與抽象類的區(qū)別

  • 接口里只能包含抽象方法,不同包含已經(jīng)提供實現(xiàn)的方法;抽象類則完全可以包含普通方法。
  • 接口里不能定義靜態(tài)方法瘸味;抽象類里可以定義靜態(tài)方法宫仗。
  • 接口里只能定義靜態(tài)常量屬性,不能定義普通屬性旁仿;抽象類里則既可以定義普通屬性锰什,也可以定義靜態(tài)常量屬性。
  • 接口不包含構(gòu)造器丁逝;抽象類里可以包含構(gòu)造器汁胆,抽象類里的構(gòu)造器并不是用于創(chuàng)建對象,而讓其子類調(diào)用這些構(gòu)造器來完成屬于抽象類的初始化操作霜幼。
  • 接口里不能包含初始化塊嫩码,但抽象類則完全可以包含初始化塊。
  • 一個類最多只能有一個直接父類罪既,包括抽象類铸题;但一個類可以直接實現(xiàn)多個接口,通過實現(xiàn)多個接口可以彌補(bǔ)Java單繼承的不足琢感。

內(nèi)部類

我們把一個類放在另一個類的內(nèi)部定義丢间,這個定義在其他類內(nèi)部的類就被稱為內(nèi)部類,有的也叫嵌套類驹针,包含內(nèi)部類的類也被稱為外部類有的也叫宿主類烘挫。內(nèi)部類提供了更好的封裝,內(nèi)部類成員可以直接訪問外部類的私有數(shù)據(jù)柬甥,因為內(nèi)部類被當(dāng)成其他外部類成員饮六。匿名內(nèi)部類適合用于創(chuàng)建那些僅需要一次使用的類。
區(qū)別:

  • 可以多使用修飾符private苛蒲、protected卤橄、static
  • 非靜態(tài)內(nèi)部類不能擁有靜態(tài)成員

非靜態(tài)內(nèi)部類
非靜態(tài)內(nèi)部類中可以訪問外部類的private成員,是因為在非靜態(tài)內(nèi)部類中保存了外部類對象的引用臂外。

public class DiscernVariable
{
    private String prop = "外部類的實例變量";
    private class InClass
    {
        private String prop = "內(nèi)部類的實例變量";
        public void info()
        {
            String prop = "局部變量";
            // 通過 外部類類名.this.varName 訪問外部類實例變量
            System.out.println("外部類的實例變量值:"
                + DiscernVariable.this.prop);
            // 通過 this.varName 訪問內(nèi)部類實例的變量
            System.out.println("內(nèi)部類的實例變量值:" + this.prop);
            // 直接訪問局部變量
            System.out.println("局部變量的值:" + prop);
        }
    }
    public void test()
    {
        InClass in = new InClass();
        in.info();
    }
    public static void main(String[] args)
    {
        new DiscernVariable().test();
    }
}

非靜態(tài)內(nèi)部類的成員只在內(nèi)部類范圍是可知的窟扑,并不能直接被外部類調(diào)用喇颁,如果外部類需要訪問,則必須顯示創(chuàng)建非靜態(tài)內(nèi)部類對象來調(diào)用其實例成員嚎货。非靜態(tài)內(nèi)部類不可以定義靜態(tài)成員橘霎。

靜態(tài)內(nèi)部類
如果用static修飾一個內(nèi)部類,稱為靜態(tài)內(nèi)部類厂抖。
靜態(tài)內(nèi)部類可以包含靜態(tài)成員茎毁,也可以包含非靜態(tài)成員。所以靜態(tài)內(nèi)部類不能訪問外部類的實例成員忱辅,只能訪問外部類的類成員七蜘。
靜態(tài)內(nèi)部類的對象寄存在外部類里,非靜態(tài)內(nèi)部類的對象寄存在外部類實例里
外部類依然不能直接訪問靜態(tài)內(nèi)部類的成員墙懂,但可以使用靜態(tài)內(nèi)部類的類名訪問靜態(tài)內(nèi)部類的類成員或者靜態(tài)內(nèi)部類對象作為調(diào)用者訪問靜態(tài)內(nèi)部類的實例成員橡卤。

使用內(nèi)部類

  1. 在外部類內(nèi)部使用內(nèi)部類-不要在外部類的靜態(tài)成員中使用非靜態(tài)內(nèi)部類,因為靜態(tài)成員不能訪問非靜態(tài)成員损搬。
  2. 在外部類以外使用非靜態(tài)內(nèi)部類碧库。
  • private 修飾的內(nèi)部類只能在外部類內(nèi)部使用。
  • 在外部類以外的地方使用內(nèi)部類巧勤,內(nèi)部類完整的類名應(yīng)該OuterClass.InnerClass.
  • 在外部類以外的地方使用非靜態(tài)內(nèi)部類創(chuàng)建對象的語法如下:OuterInstance.new InnerConstructor()嵌灰,創(chuàng)建非靜態(tài)內(nèi)部類實例以來于外部類實例。
class Out
{
    // 定義一個內(nèi)部類颅悉,不使用訪問控制符沽瞭,
    // 即只有同一個包中其他類可訪問該內(nèi)部類
    class In
    {
        public In(String msg)
        {
            System.out.println(msg);
        }
    }
}
public class CreateInnerInstance
{
    public static void main(String[] args)
    {
        Out.In in = new Out().new In("測試信息");
        /*
        上面代碼可改為如下三行代碼:
        使用OutterClass.InnerClass的形式定義內(nèi)部類變量
        Out.In in;
        創(chuàng)建外部類實例,非靜態(tài)內(nèi)部類實例將寄存在該實例中
        Out out = new Out();
        通過外部類實例和new來調(diào)用內(nèi)部類構(gòu)造器創(chuàng)建非靜態(tài)內(nèi)部類實例
        in = out.new In("測試信息");
        */
    }
}

創(chuàng)建靜態(tài)內(nèi)部類的子類時剩瓶,必須存在一個外部類對象驹溃,然后才能調(diào)用非靜態(tài)內(nèi)部類的構(gòu)造器。

public class SubClass extends Out.In
{
    //顯示定義SubClass的構(gòu)造器
    public SubClass(Out out)
    {
        //通過傳入的Out對象顯式調(diào)用In的構(gòu)造器
        out.super("hello");
    }
}

注意:非靜態(tài)內(nèi)部類的子類不一定是內(nèi)部類延曙,可以是外部類豌鹤。但其實例必須保留一個引用,指向父類所在外部類的對象枝缔。

  1. 在外部類以外使用靜態(tài)內(nèi)部類
    在外部類以外的地方使用靜態(tài)內(nèi)部類創(chuàng)建對象的語法如下:new OuterClass.InnerConstructer();
    使用靜態(tài)內(nèi)部類相對容易布疙,只要把外部類當(dāng)成靜態(tài)內(nèi)部類的包空間即可。所以魂仍,一般優(yōu)先考慮靜態(tài)內(nèi)部類拐辽。

注意:子類的內(nèi)部類不可能重寫父類的內(nèi)部類,因為即使內(nèi)部類類名相同擦酌,外部類空間不同,就不可能完全同名菠劝。

局部內(nèi)部類
如果把一個內(nèi)部類放在方法里定義赊舶,這就是局部內(nèi)部類,僅僅在這個方法里有效。局部內(nèi)部類不能在外部類以外的地方使用笼平,那么局部內(nèi)部類也不能使用訪部控制符和static修飾园骆。
局部內(nèi)部類使用很有限。

匿名內(nèi)部類
匿名內(nèi)部類適合創(chuàng)建那種只需要一次使用的類寓调,定義匿名內(nèi)部類的語法格式如下:

new 父類構(gòu)造器(實例列表) |實現(xiàn)接口)
{
      //匿名內(nèi)部類的 類體部分
}

必須繼承一個父類或者實現(xiàn)一個接口锌唾,但最多繼承一個父類或者實現(xiàn)一個接口。
兩個限制:匿名內(nèi)部類不能是抽象類夺英;匿名內(nèi)部類不能定義構(gòu)造器晌涕。

interface Product
{
    public double getPrice();
    public String getName();
}
public class AnonymousTest
{
    public void test(Product p)
    {
        System.out.println("購買了一個" + p.getName()
            + ",花掉了" + p.getPrice());
    }
    public static void main(String[] args)
    {
        AnonymousTest ta = new AnonymousTest();
        // 調(diào)用test()方法時痛悯,需要傳入一個Product參數(shù)余黎,
        // 此處傳入其匿名實現(xiàn)類的實例
        ta.test(new Product()
        {
            public double getPrice()
            {
                return 567.8;
            }
            public String getName()
            {
                return "AGP顯卡";
            }
        });
    }
}

如果局部變量被匿名內(nèi)部類訪問,該變量相當(dāng)于自動使用final修飾载萌。也就是effective final:對于內(nèi)部類訪問的局部變量可以用final修飾惧财,也可以不用,但是一次賦值后不能再次賦值扭仁。

interface A
{
    void test();
}
public class ATest
{
    public static void main(String[] args)
    {
        int age = 8;     // ①
        // 下面代碼將會導(dǎo)致編譯錯誤
        // 由于age局部變量被匿名內(nèi)部類訪問了垮衷,因此age相當(dāng)于被final修飾了
        age = 2;
        A a = new A()
        {
            public void test()
            {
                // 在Java 8以前下面語句將提示錯誤:age必須使用final修飾
                // 從Java 8開始,匿名內(nèi)部類乖坠、局部內(nèi)部類允許訪問非final的局部變量
                System.out.println(age);
            }
        };
        a.test();
    }
}

Lambda表達(dá)式

                                                                                  Lambda表達(dá)式主要作用就是代替匿名內(nèi)部類的繁瑣語法搀突。                                    它由三部分組成:
  • 形參列表。形參列表允許省略形參類型瓤帚。如果形參列表中只有一個參數(shù)描姚,甚至連形參列表的圓括號也可以省略。
  • 箭頭(->)戈次。
  • 代碼塊轩勘。如果代碼塊只有包含一條語句,Lambda表達(dá)式允許省略代碼塊的花括號怯邪,如果省略了代碼塊的花括號绊寻,這條語句不要用花括號表示語句結(jié)束。Lambda代碼塊只有一條return語句悬秉,甚至可以省略return關(guān)鍵字澄步。Lambda表達(dá)式需要返回值,而它的代碼塊中僅有一條省略了return的語句和泌,Lambda表達(dá)式會自動返回這條語句的值
interface Eatable
{
    void taste();
}
interface Flyable
{
    void fly(String weather);
}
interface Addable
{
    int add(int a , int b);
}
public class LambdaQs
{
    // 調(diào)用該方法需要Eatable對象
    public void eat(Eatable e)
    {
        System.out.println(e);
        e.taste();
    }
    // 調(diào)用該方法需要Flyable對象
    public void drive(Flyable f)
    {
        System.out.println("我正在駕駛:" + f);
        f.fly("【碧空如洗的晴日】");
    }
    // 調(diào)用該方法需要Addable對象
    public void test(Addable add)
    {
        System.out.println("5與3的和為:" + add.add(5, 3));
    }
    public static void main(String[] args)
    {
        LambdaQs lq = new LambdaQs();
        // Lambda表達(dá)式的代碼塊只有一條語句村缸,可以省略花括號。
        lq.eat(()-> System.out.println("蘋果的味道不錯武氓!"));
        // Lambda表達(dá)式的形參列表只有一個形參梯皿,省略圓括號
        lq.drive(weather ->
        {
            System.out.println("今天天氣是:" + weather);
            System.out.println("直升機(jī)飛行平穩(wěn)");
        });
        // Lambda表達(dá)式的代碼塊只有一條語句仇箱,省略花括號
        // 代碼塊中只有一條語句,即使該表達(dá)式需要返回值东羹,也可以省略return關(guān)鍵字剂桥。
        lq.test((a , b)->a + b);
    }
}

Lambda表達(dá)式與函數(shù)式接口
如果采用匿名內(nèi)部類語法來創(chuàng)建函數(shù)式接口的實例,只要實現(xiàn)一個抽象方法即可属提,在這種情況下即可采用Lambda表達(dá)式來創(chuàng)建對象权逗,該表達(dá)式創(chuàng)建出來的對象的目標(biāo)類型就是這個函數(shù)式接口。

Lambda表達(dá)式有如下兩個限制:

  • Lambda表達(dá)式的目標(biāo)類型必須是明確的函數(shù)式接口冤议。
  • Lambda表達(dá)式只能為函數(shù)式接口創(chuàng)建對象斟薇。Lambda表達(dá)式只能實現(xiàn)一個方法,因此它只能為只有一個抽象方法的接口(函數(shù)式接口)創(chuàng)建對象求类。

為了保證Lambda表達(dá)式的目標(biāo)類型是一個明確的函數(shù)式接口奔垦,可以有如下三種常見方式:

  • 將Lambda表達(dá)式賦值給函數(shù)式接口類型的變量。
  • 將Lambda表達(dá)式作為函數(shù)式接口類型的參數(shù)傳給某個方法尸疆。
  • 使用函數(shù)式接口對Lambda表達(dá)式進(jìn)行強(qiáng)制類型轉(zhuǎn)換椿猎。

方法引用與構(gòu)造器引用

如果Lambda表達(dá)式的代碼塊只有一條代碼,可以省略表達(dá)式中代碼塊的花括號寿弱,還可以在代碼塊中使用方法引用和構(gòu)造器引用犯眠。

種類 示例 說明 Lambda表達(dá)式
引用類方法 類名::類方法 函數(shù)式接口中被實現(xiàn)方法的全部參數(shù)傳給該類方法作為參數(shù)。 (a,b,...) -> 類名.類方法(a,b, ...)
引用特定對象的實例方法 特定對象::實例方法 函數(shù)式接口中被實現(xiàn)方法的全部參數(shù)傳給該實例方法作為參數(shù)症革。 (a,b, ...) -> 特定對象.實例方法(a,b, ...)
引用某類對象的實例方法 類名::實例方法 函數(shù)式接口中被實現(xiàn)方法的第一個參數(shù)作為調(diào)用者筐咧,后面的參數(shù)全部傳給該方法作為參數(shù)。 (a,b, ...) ->a.實例方法(b, ...)
引用構(gòu)造器 類名::new 函數(shù)式接口中被實現(xiàn)方法的全部參數(shù)傳給該構(gòu)造器作為參數(shù) (a,b, ...) ->new 類的構(gòu)造器(a,b, ...)
import javax.swing.*;
@FunctionalInterface
interface Converter{
    Integer convert(String from);
}
@FunctionalInterface
interface MyTest
{
    String test(String a , int b , int c);
}
@FunctionalInterface
interface YourTest
{
    JFrame win(String title);
}
public class MethodRefer
{
    public static void main(String[] args)
    {
        // 下面代碼使用Lambda表達(dá)式創(chuàng)建Converter對象
//      Converter converter1 = from -> Integer.valueOf(from);
//      // 方法引用代替Lambda表達(dá)式:引用類方法噪矛。
//      // 函數(shù)式接口中被實現(xiàn)方法的全部參數(shù)傳給該類方法作為參數(shù)量蕊。
//      Converter converter1 = Integer::valueOf;
//      Integer val = converter1.convert("99");
//      System.out.println(val); // 輸出整數(shù)99



        // 下面代碼使用Lambda表達(dá)式創(chuàng)建Converter對象
//      Converter converter2 = from -> "fkit.org".indexOf(from);
//      // 方法引用代替Lambda表達(dá)式:引用特定對象的實例方法。
//      // 函數(shù)式接口中被實現(xiàn)方法的全部參數(shù)傳給該方法作為參數(shù)艇挨。
//      Converter converter2 = "fkit.org"::indexOf;
//      Integer value = converter2.convert("it");
//      System.out.println(value); // 輸出2



        // 下面代碼使用Lambda表達(dá)式創(chuàng)建MyTest對象
//      MyTest mt = (a , b , c) -> a.substring(b , c);
        // 方法引用代替Lambda表達(dá)式:引用某類對象的實例方法残炮。
        // 函數(shù)式接口中被實現(xiàn)方法的第一個參數(shù)作為調(diào)用者,
        // 后面的參數(shù)全部傳給該方法作為參數(shù)缩滨。
//      MyTest mt = String::substring;
//      String str = mt.test("Java I Love you" , 2 , 9);
//      System.out.println(str); // 輸出:va I Lo



        // 下面代碼使用Lambda表達(dá)式創(chuàng)建YourTest對象
//      YourTest yt = (String a) -> new JFrame(a);
        // 構(gòu)造器引用代替Lambda表達(dá)式势就。
        // 函數(shù)式接口中被實現(xiàn)方法的全部參數(shù)傳給該構(gòu)造器作為參數(shù)。
        YourTest yt = JFrame::new;
        JFrame jf = yt.win("我的窗口");
        System.out.println(jf);
    }
}

Lambda表達(dá)式與匿名內(nèi)部類
相同點:

  • Lambda表達(dá)式與匿名內(nèi)部類一樣脉漏,都可以直接訪問“effectively final”的局部變量苞冯,以及外部類的成員變量(包括實例變量和類變量)。
  • Lambda表達(dá)式創(chuàng)建的對象與匿名內(nèi)部類生成的對象一樣侧巨,都可以直接調(diào)用從接口繼承得到的默認(rèn)方法舅锄。

區(qū)別:

  • 匿名內(nèi)部類可以為任意接口創(chuàng)建實例——不管接口包含多少個抽象方法,只要匿名內(nèi)部類實現(xiàn)所有的抽象方法即可司忱。但Lambda表達(dá)式只能為函數(shù)式接口創(chuàng)建實例巧娱。
  • 匿名內(nèi)部類可以為抽象類碉怔、甚至普通類創(chuàng)建實例烘贴,但Lambda表達(dá)式只能為函數(shù)式接口創(chuàng)建實例禁添。
  • 匿名內(nèi)部類實現(xiàn)的抽象方法的方法體允許調(diào)用接口中定義的默認(rèn)方法;但Lambda表達(dá)式的代碼塊不允許調(diào)用接口中定義的默認(rèn)方法桨踪。

使用Lambda表達(dá)式調(diào)用Arrays的類方法
Arrays類的有些方法需要Comparator老翘、XxxOperator、XxxFuncton等接口實例锻离,這些接口都是函數(shù)式接口铺峭,因此可以使用Lambda表達(dá)式調(diào)用Arrays方法。

import java.util.Arrays;
public class LambdaArrays
{
    public static void main(String[] args)
    {
        String[] arr1 = new String[]{"java" , "fkava" , "fkit", "ios" , "android"};
        Arrays.parallelSort(arr1, (o1, o2) -> o1.length() - o2.length());
        System.out.println(Arrays.toString(arr1));
        int[] arr2 = new int[]{3, -4 , 25, 16, 30, 18};
        // left代表數(shù)組中前一個所索引處的元素汽纠,計算第一個元素時卫键,left為1
        // right代表數(shù)組中當(dāng)前索引處的元素
        Arrays.parallelPrefix(arr2, (left, right)-> left * right);
        System.out.println(Arrays.toString(arr2));
        long[] arr3 = new long[5];
        // operand代表正在計算的元素索引
        Arrays.parallelSetAll(arr3 , operand -> operand * 5);
        System.out.println(Arrays.toString(arr3));
    }
}

枚舉類

實例有限而且固定的類被稱為枚舉類
枚舉類是一種特殊的類,它一樣可以有自己的方法和屬性虱朵,可以實現(xiàn)一個或者多個接口莉炉,也可以定義自己的構(gòu)造器。一個Java源文件中最多只能定義一個public訪問權(quán)限的枚舉類碴犬,且該Java源文件也必須和該枚舉類的類名相同絮宁。

與普通類的區(qū)別

  • 枚舉類可以實現(xiàn)一個或多個接口,使用enum定義的枚舉類默認(rèn)繼承了java.lang.Enum類服协,而不是繼承Object類绍昂。其中java.lang.Enum類實現(xiàn)了java.lang.Serializable和java.lang. Comparable兩個接口。
  • 枚舉類的構(gòu)造器只能使用private訪問控制符偿荷,如果省略了其構(gòu)造器的訪問控制符窘游,則默認(rèn)使用private修飾;如果強(qiáng)制指定訪問控制符跳纳,則只能指定private修飾符忍饰。
  • 枚舉類的所有實例必須在枚舉類中顯式列出,否則這個枚舉類將永遠(yuǎn)都不能產(chǎn)生實例棒旗。列出這些實例時系統(tǒng)會自動添加public static final修飾喘批,無需程序員顯式添加。
  • 所有枚舉類都提供了一個values方法铣揉,該方法可以很方便地遍歷所有的枚舉值饶深。
public enum SeasonEnum
{
    // 在第一行列出4個枚舉實例
    SPRING,SUMMER,FALL,WINTER;
}
public class EnumTest
{
    public void judge(SeasonEnum s)
    {
        // switch語句里的表達(dá)式可以是枚舉值
        switch (s)
        {
            case SPRING:
                System.out.println("春暖花開,正好踏青");
                break;
            case SUMMER:
                System.out.println("夏日炎炎逛拱,適合游泳");
                break;
            case FALL:
                System.out.println("秋高氣爽敌厘,進(jìn)補(bǔ)及時");
                break;
            case WINTER:
                System.out.println("冬日雪飄,圍爐賞雪");
                break;
        }
    }
    public static void main(String[] args)
    {
        // 枚舉類默認(rèn)有一個values方法朽合,返回該枚舉類的所有實例
        for (SeasonEnum s : SeasonEnum.values())
        {
            System.out.println(s);
        }
        // 使用枚舉實例時俱两,可通過EnumClass.variable形式來訪問
        new EnumTest().judge(SeasonEnum.SPRING);
    }
}

枚舉類的屬性饱狂、方法和構(gòu)造器
枚舉類也是一種類,只是它是一種比較特殊的類宪彩,因此它一樣可以使用屬性和方法休讳。
枚舉類通常應(yīng)該設(shè)計成不可變類,也就說它的屬性值不應(yīng)該允許改變尿孔,這樣會更安全俊柔,而且代碼更加簡潔。為此活合,我們應(yīng)該將枚舉類的屬性都使用private final修飾雏婶。
一旦為枚舉類顯式定義了帶參數(shù)的構(gòu)造器,則列出枚舉值時也必須對應(yīng)地傳入?yún)?shù)白指。

public enum Gender
{
    // 此處的枚舉值必須調(diào)用對應(yīng)構(gòu)造器來創(chuàng)建
    MALE("男"),FEMALE("女");
    private final String name;
    // 枚舉類的構(gòu)造器只能使用private修飾
    private Gender(String name)
    {
        this.name = name;
    }
    public String getName()
    {
        return this.name;
    }
}

實現(xiàn)接口的枚舉類
枚舉類也可以實現(xiàn)一個或多個接口留晚。與普通類實現(xiàn)一個或多個接口完全一樣,枚舉類實現(xiàn)一個或多個接口時告嘲,也需要實現(xiàn)該接口所包含的方法错维。
如果需要每個枚舉值在調(diào)用同一個方法時呈現(xiàn)出不同的行為方式,則可以讓每個枚舉值分別來實現(xiàn)該方法状蜗,每個枚舉值提供不同的實現(xiàn)方式需五,從而讓不同枚舉值調(diào)用同一個方法時具有不同的行為方式。

public enum Gender implements GenderDesc{
    //調(diào)用構(gòu)造器創(chuàng)建枚舉值
    MALE("男")
    //下面是類體
    {
        public void info(){
            System.out.println("枚舉值代表男性");
        }
    },  
    FEMALE("女"){
        public void info(){
            System.out.println("枚舉值代表男性");
        }
    };
    
    private final String name;
    private Gender(String name)
    {
        this.name = name;
    }
    public String getName()
    {
        return this.name;
    }
    
}

創(chuàng)建MALE轧坎、FEMALE枚舉值時不是直接創(chuàng)建Gender實例宏邮,而是創(chuàng)建Gender匿名子類的實例,缸血。注意并不是所有的枚舉類都是用了final修飾蜜氨,非抽象的枚舉類才默認(rèn)使用final修飾。

包含抽象方法的枚舉類
可以在枚舉類里定義一個抽象方法捎泻,然后把這個抽象方法交給各枚舉值去實現(xiàn)即可飒炎。
枚舉類里定義抽象方法時無需顯式使用abstract關(guān)鍵字將枚舉類定義成抽象類,但因為枚舉類需要顯式創(chuàng)建枚舉值笆豁,而不是作為父類郎汪,所以定義每個枚舉值時必須為抽象方法提供實現(xiàn),否則將出現(xiàn)編譯錯誤闯狱。

public enum Operation
{
    PLUS
    {
        public double eval(double x , double y)
        {
            return x + y;
        }
    },
    MINUS
    {
        public double eval(double x , double y)
        {
            return x - y;
        }
    },
    TIMES
    {
        public double eval(double x , double y)
        {
            return x * y;
        }
    },
    DIVIDE
    {
        public double eval(double x , double y)
        {
            return x / y;
        }
    };
    // 為枚舉類定義一個抽象方法
    // 這個抽象方法由不同的枚舉值提供不同的實現(xiàn)
    public abstract double eval(double x, double y);
    public static void main(String[] args)
    {
        System.out.println(Operation.PLUS.eval(3, 4));
        System.out.println(Operation.MINUS.eval(5, 4));
        System.out.println(Operation.TIMES.eval(5, 4));
        System.out.println(Operation.DIVIDE.eval(5, 4));
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者

  • 序言:七十年代末煞赢,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子哄孤,更是在濱河造成了極大的恐慌照筑,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異凝危,居然都是意外死亡波俄,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進(jìn)店門蛾默,熙熙樓的掌柜王于貴愁眉苦臉地迎上來懦铺,“玉大人,你說我怎么就攤上這事趴生》浚” “怎么了?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵苍匆,是天一觀的道長。 經(jīng)常有香客問我棚菊,道長浸踩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任统求,我火速辦了婚禮检碗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘码邻。我一直安慰自己折剃,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布像屋。 她就那樣靜靜地躺著怕犁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪己莺。 梳的紋絲不亂的頭發(fā)上奏甫,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天,我揣著相機(jī)與錄音凌受,去河邊找鬼阵子。 笑死,一個胖子當(dāng)著我的面吹牛胜蛉,可吹牛的內(nèi)容都是我干的挠进。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼誊册,長吁一口氣:“原來是場噩夢啊……” “哼领突!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起解虱,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤攘须,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體于宙,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡浮驳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了捞魁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片至会。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖谱俭,靈堂內(nèi)的尸體忽然破棺而出奉件,到底是詐尸還是另有隱情,我是刑警寧澤昆著,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布县貌,位于F島的核電站,受9級特大地震影響凑懂,放射性物質(zhì)發(fā)生泄漏煤痕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一接谨、第九天 我趴在偏房一處隱蔽的房頂上張望摆碉。 院中可真熱鬧,春花似錦脓豪、人聲如沸巷帝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽楞泼。三九已至,卻和暖如春历谍,著一層夾襖步出監(jiān)牢的瞬間现拒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工望侈, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留印蔬,地道東北人。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓脱衙,卻偏偏與公主長得像侥猬,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子捐韩,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內(nèi)容