<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    John Jiang

    a cup of Java, cheers!
    https://github.com/johnshajiang/blog

       :: 首頁(yè) ::  :: 聯(lián)系 :: 聚合  :: 管理 ::
      131 隨筆 :: 1 文章 :: 530 評(píng)論 :: 0 Trackbacks
    Java 8的語(yǔ)言變化
    --理解Lambda表達(dá)式和變化的接口類(lèi)是如何使Java 8成為新的語(yǔ)言
    本文是IBM developerWorks中的一篇介紹Java 8關(guān)鍵新特性的文章,它主要關(guān)注Lambda表達(dá)式和改進(jìn)的接口。(2014.04.19最后更新)

        Java 8包含了一組重要的新的語(yǔ)言特性,使你能夠更方便地構(gòu)造程序。Lambda表達(dá)為內(nèi)聯(lián)的代碼塊定義了一種新的語(yǔ)法,給予你與匿名內(nèi)部類(lèi)相同的靈活性,但又沒(méi)有那么多模板代碼。接口的改變使得能夠?yàn)橐延薪涌诩尤胄碌奶匦裕槐卮蚱片F(xiàn)有代碼的兼容性。了解這些語(yǔ)言變化是怎樣一起工作的,請(qǐng)閱讀本系列另一篇文章"Java 8并發(fā)基礎(chǔ)",可以看到如何在Java 8流中使用Lambda。
        Java 8的最大改變就是增加了對(duì)Lambda表達(dá)式的支持。Lambda表達(dá)式一種通過(guò)引用進(jìn)行傳遞的代碼塊。它類(lèi)似于某些其它語(yǔ)言的閉包:代碼實(shí)現(xiàn)了一個(gè)功能,可以傳入一個(gè)或多個(gè)參數(shù),還可以返回一個(gè)結(jié)果值。閉包被定義在一個(gè)上下文中,它可以訪問(wèn)(在Lambda中是只讀訪問(wèn))上下文中的值。
        如果你不熟悉閉包,也不必?fù)?dān)心。Java 8的Lambda表達(dá)式是幾乎每個(gè)Java開(kāi)發(fā)者都熟悉的匿名內(nèi)部類(lèi)的一個(gè)高效版規(guī)范。如果你只想在一個(gè)位置實(shí)現(xiàn)一個(gè)接口,或是創(chuàng)建一個(gè)基類(lèi)的子類(lèi)時(shí),匿名內(nèi)部類(lèi)為此提供了一種內(nèi)聯(lián)實(shí)現(xiàn)。Lambda表達(dá)式也用于相同的方式,但是它使用一種縮略的語(yǔ)法,使得這些實(shí)現(xiàn)比一個(gè)標(biāo)準(zhǔn)的內(nèi)部類(lèi)定義更為簡(jiǎn)潔。
        在本文中,你將看到如何在不同的場(chǎng)景下使用Lambda表達(dá)式,并且你會(huì)學(xué)到與Java接口定義相關(guān)的擴(kuò)展。在本文章的姊妹篇JVM并發(fā)系列的"Java 8并發(fā)基礎(chǔ)"一文中,可以看到更多使用Lambda表達(dá)式的例子,包括在Java 8流特性中的應(yīng)用。

    進(jìn)入Lambda
        Lambda表達(dá)式就是Java 8所稱(chēng)的函數(shù)接口的實(shí)現(xiàn):一個(gè)接口只定義一個(gè)抽象方法。只定義一個(gè)抽象方法的限制是非常重要的,因?yàn)長(zhǎng)ambda表達(dá)式的語(yǔ)法并不會(huì)使用方法名。相反,該表達(dá)式會(huì)使用動(dòng)態(tài)類(lèi)型識(shí)別(匹配參數(shù)和返回類(lèi)型,很多動(dòng)態(tài)語(yǔ)言都這么做)去保證提供的Lambda能夠與期望的接口方法兼容。
        在清單1所示的簡(jiǎn)單例子中,一個(gè)Lambda表達(dá)式被用來(lái)對(duì)Name實(shí)例進(jìn)行排序。main()方法中的第一個(gè)代碼塊使用一個(gè)匿名內(nèi)部類(lèi)去實(shí)現(xiàn)Comparator<Name>接口,第二個(gè)語(yǔ)句塊則使用Lambda表達(dá)式。
    清單1. 比較Lambda表達(dá)式與匿名內(nèi)部類(lèi)
    public class Name {
        
    public final String firstName;
        
    public final String lastName;

        
    public Name(String first, String last) {
            firstName 
    = first;
            lastName 
    = last;
        }

        
    // only needed for chained comparator
        public String getFirstName() {
            
    return firstName;
        }

        
    // only needed for chained comparator
        public String getLastName() {
            
    return lastName;
        }

        
    // only needed for direct comparator (not for chained comparator)
        public int compareTo(Name other) {
            
    int diff = lastName.compareTo(other.lastName);
            
    if (diff == 0) {
                diff 
    = firstName.compareTo(other.firstName);
            }
            
    return diff;
        }
        
    }

    public class NameSort {
        
        
    private static final Name[] NAMES = new Name[] {
            
    new Name("Sally""Smith"),
            
        };
        
        
    private static void printNames(String caption, Name[] names) {
            
        }

        
    public static void main(String[] args) {

            
    // sort array using anonymous inner class
            Name[] copy = Arrays.copyOf(NAMES, NAMES.length);
            Arrays.sort(copy, 
    new Comparator<Name>() {
                @Override
                
    public int compare(Name a, Name b) {
                    
    return a.compareTo(b);
                }
            });
            printNames(
    "Names sorted with anonymous inner class:", copy);

            
    // sort array using lambda expression
            copy = Arrays.copyOf(NAMES, NAMES.length);
            Arrays.sort(copy, (a, b) 
    -> a.compareTo(b));
            printNames(
    "Names sorted with lambda expression:", copy);
            
        }
    }
        在清單1中,Lambda被用于取代匿名內(nèi)部類(lèi)。這種匿名內(nèi)部類(lèi)在應(yīng)用中非常普遍,所以Lambda表達(dá)式很快就贏得了Java8程序員們的青睞。(在本例中,同時(shí)使用匿名內(nèi)部類(lèi)和Lambda表達(dá)式去實(shí)現(xiàn)Name類(lèi)中的一個(gè)方法,以方便對(duì)這兩種方法進(jìn)行比較。如果在Lambda中對(duì)compareTo()方法進(jìn)行內(nèi)聯(lián)的話,該表達(dá)式將會(huì)更加簡(jiǎn)潔。)

    標(biāo)準(zhǔn)的函數(shù)式接口
        為了應(yīng)用Lambda,新的包java.util.function中定義了廣泛的函數(shù)式接口。它們被歸結(jié)為如下幾個(gè)類(lèi)別:
        函數(shù):使用一個(gè)參數(shù),基于參數(shù)的值返回結(jié)果。
        謂語(yǔ):使用一個(gè)參數(shù),基于參數(shù)的值返回布爾結(jié)果。
        雙函數(shù):使用兩個(gè)參數(shù),基于參數(shù)的值返回結(jié)果。
        供應(yīng)器:不使用任何參數(shù),但會(huì)返回結(jié)果。
        消費(fèi)者:使用一個(gè)參數(shù),但不返回任何結(jié)果。
    多數(shù)類(lèi)別都包含多個(gè)不同的變體,以便能夠作用于基本數(shù)據(jù)類(lèi)型的參數(shù)和返回值。許多接口所定義的方法都可被用于組合對(duì)象,如清單2所示:
    清單2. 組合謂語(yǔ)
    // use predicate composition to remove matching names
    List<Name> list = new ArrayList<>();
    for (Name name : NAMES) {
        list.add(name);
    }
    Predicate
    <Name> pred1 = name -> "Sally".equals(name.firstName);
    Predicate
    <Name> pred2 = name -> "Queue".equals(name.lastName);
    list.removeIf(pred1.or(pred2));
    printNames(
    "Names filtered by predicate:", list.toArray(new Name[list.size()]));
        清單2定義了一對(duì)Predicate<Name>變量,一個(gè)用于匹配名為Sally的名字,另一個(gè)用于匹配姓為Queue的名字。調(diào)用方法pred1.or(pred2)會(huì)構(gòu)造一個(gè)組合謂語(yǔ),該謂語(yǔ)先后使用了兩個(gè)謂語(yǔ),當(dāng)它們中的任何一個(gè)返回true時(shí),這個(gè)組合謂語(yǔ)就將返回true(這就相當(dāng)于早期Java中的邏輯操作符||)。List.removeIf()方法就應(yīng)用這個(gè)組合謂語(yǔ)去刪除列表中的匹配名字。
        Java 8定義了許多有用的java.util.function包中接口的組合接口,但這種組合并不都是一樣的。所有的謂語(yǔ)的變體(DoublePredicate,IntPredicate,LongPredicate和Predicate<T>)都定義了相同的組合與修改方法:and(),negate()和or()。但是Function<T>的基本數(shù)據(jù)類(lèi)型變體就沒(méi)有定義任何組合與修改方法。如果你擁有使用函數(shù)式編程語(yǔ)言的經(jīng)驗(yàn),那么你可能就發(fā)會(huì)發(fā)現(xiàn)這些不同之處和奇怪的忽略。

    改變接口
        在Java 8中,接口(如清單1的Comparator)的結(jié)構(gòu)已發(fā)生了改變,部分原因是為了讓Lambda更好用。Java 8之前的接口只能定義常量,以及必須被實(shí)現(xiàn)的抽象方法。而Java 8中的接口則能夠定義靜態(tài)與默認(rèn)方法。接口中的靜態(tài)方法與抽象類(lèi)中的靜態(tài)方法是完全一樣的。默認(rèn)方法則更像舊式的接口方法,但提供了該方法的一個(gè)實(shí)現(xiàn)。該方法實(shí)現(xiàn)可用于該接口的實(shí)現(xiàn)類(lèi),除非它被實(shí)現(xiàn)類(lèi)覆蓋掉了。
        默認(rèn)方法的一個(gè)重要特性就是它可以被加入到已有接口中,但又不會(huì)破壞已使用了這些接口的代碼的兼容性(除非已有代碼恰巧使用了相同名字的方法,并且其目的與默認(rèn)方法不同)。這是一個(gè)非常強(qiáng)大的功能,Java 8的設(shè)計(jì)者們利用這一特性為許多已有Java類(lèi)庫(kù)加入了對(duì)Lambda表達(dá)式的支持。清單3就展示了這樣的一個(gè)例子,它是清單1中對(duì)名字進(jìn)行排序的第三種實(shí)現(xiàn)方式。
    清單3. 鍵-提取比較器鏈
    // sort array using key-extractor lambdas
    copy = Arrays.copyOf(NAMES, NAMES.length);
    Comparator
    <Name> comp = Comparator.comparing(name -> name.lastName);
    comp 
    = comp.thenComparing(name -> name.firstName);
    Arrays.sort(copy, comp);
    printNames(
    "Names sorted with key extractor comparator:", copy);
        清單3首先展示了如何使用新的Comparator.comparing()靜態(tài)方法去創(chuàng)建一個(gè)基于鍵-提取(Key-Extraction) Lambda的比較器(從技術(shù)上看,鍵-提取Lambda就是java.util.function.Function<T,R>接口的一個(gè)實(shí)例,它返回的比較器的類(lèi)型適用于類(lèi)型T,而提取的鍵的類(lèi)型R則要實(shí)現(xiàn)Comparable接口)。它還展示了如何使用新的Comparator.thenComparing()默認(rèn)方法去組合使用比較器,清單3就返回了一個(gè)新的比較器,它會(huì)先按姓排序,再按名排序。
        你也許期望能夠?qū)Ρ容^器進(jìn)行內(nèi)聯(lián),如:
    Comparator<Name> comp = Comparator.comparing(name -> name.lastName)
        .thenComparing(name 
    -> name.firstName);
    但不幸地是,Java 8的類(lèi)型推導(dǎo)不允許這么做。為從靜態(tài)方法中得到期望類(lèi)型的結(jié)果,你需要為編譯器提供更多的信息,可以使用如下任何一種形式:
    Comparator<Name> com1 = Comparator.comparing((Name name1) -> name1.lastName)
        .thenComparing(name2 
    -> name2.firstName);
    Comparator
    <Name> com2 = Comparator.<Name,String>comparing(name1 -> name1.lastName)
        .thenComparing(name2 
    -> name2.firstName);
        第一種方式在Lambda表達(dá)式中加入?yún)?shù)的類(lèi)型:(Name name1) -> name1.lastName。有了這個(gè)輔助信息,編譯才能知道下面它該做些什么。第二種方式是告訴編譯器要傳遞給Function接口(在此處,該接口通過(guò)Lambda表達(dá)式實(shí)現(xiàn))中comparing()方法的泛型變量T和R的類(lèi)型。
        能夠方便地構(gòu)建比較器以及比較器鏈?zhǔn)荍ava 8中很有用的特性,但它的代價(jià)是增加了復(fù)雜度。Java 7的Comparator接口定義了兩個(gè)方法(compare()方法,以及遍布于每個(gè)對(duì)象中的equals()方法)。而在Java 8中,該接口則定義了18個(gè)方法(除了原有的2個(gè)方法,還新加入了9個(gè)靜態(tài)方法和7個(gè)默認(rèn)方法)。你將發(fā)現(xiàn),為了能夠使用Lambda而造成的這種接口膨脹會(huì)重現(xiàn)于相當(dāng)一部分Java標(biāo)準(zhǔn)類(lèi)庫(kù)中。

    像Lambda那樣使用已有方法
        如果一個(gè)存在的方法已經(jīng)實(shí)現(xiàn)了你的需求,你可以直接使用一個(gè)方法引用對(duì)它進(jìn)行傳遞。清單4展示了這種方法。
    清單4. 對(duì)已有方法使用Lambda

    // sort array using existing methods as lambdas
    copy = Arrays.copyOf(NAMES, NAMES.length);
    comp 
    = Comparator.comparing(Name::getLastName).thenComparing(Name::getFirstName);
    Arrays.sort(copy, comp);
    printNames(
    "Names sorted with existing methods as lambdas:", copy);
        清單4做著與清單3相同的事情,但它使用了已有方法。使用Java 8的形為"類(lèi)名:方法名"的方法引用語(yǔ)法,你可以使用任意方法,就像Lambda表達(dá)式那樣。其效果就與你定義一個(gè)Lambda表達(dá)式去調(diào)用該方法一樣。對(duì)類(lèi)的靜態(tài)方法,特定對(duì)象或Lambda輸入類(lèi)型的實(shí)例方法(如在清單4中,getFirstName()和getLastName()方法就是Name類(lèi)的實(shí)例方法),以及類(lèi)構(gòu)造器,都可以使用方法引用。
        方法引用不僅方便,因?yàn)樗鼈儽仁褂肔ambda表達(dá)式可能更高效,而且為編譯器提供了更好的類(lèi)型信息(這也就是為什么在上一節(jié)的Lambda中使用.thenComparing()構(gòu)造Comparator會(huì)出現(xiàn)問(wèn)題,而在清單4卻能正常工作)。如果既可以使用對(duì)已有方法的方法引用,也可以使用Lambda表達(dá)式,請(qǐng)使用前者。

    捕獲與非捕獲Lambda
        你在本文中已見(jiàn)過(guò)的Lambda表達(dá)式都是非捕獲的,意即,它們都是把傳入的值當(dāng)作接口方法參數(shù)使用的簡(jiǎn)單Lambda表達(dá)式。Java 8的捕獲Lambda表達(dá)式則是使用外圍環(huán)境中的值。捕獲Lambda類(lèi)似于某些JVM語(yǔ)言(如Scala)使用的閉包,但Java 8的實(shí)現(xiàn)與之有所不同,因?yàn)閬?lái)自在外圍環(huán)境中的值必須聲明為final。也就是說(shuō),這些值要么確實(shí)為final(就如同以前的Java版本中由匿名內(nèi)部類(lèi)所引用的值),要么在外圍環(huán)境中不會(huì)被修改。這一規(guī)范適用于Lambda表達(dá)式和匿名內(nèi)部類(lèi)。有一些方法可以繞過(guò)對(duì)值的final限制。例如,在Lambda中僅使用特定變量的當(dāng)前值,你可以添加一個(gè)新的方法,把這些值作為方法參數(shù),再將捕獲的值(以恰當(dāng)?shù)慕涌谝眠@種形式)返回給Lambda。如果期望一個(gè)Lambda去修改外圍環(huán)境中的值,那么可以用一個(gè)可修改的持有器類(lèi)(Holder)對(duì)這些值進(jìn)行包裝。
        相比于捕獲Lambda,可以更高效地處理非捕獲Lambda,那是因?yàn)榫幾g能夠把它生成為類(lèi)中的靜態(tài)方法,而運(yùn)行時(shí)環(huán)境可以直接內(nèi)聯(lián)的調(diào)用這些方法。捕獲Lambda也許低效一些,但在相同上下文環(huán)境中它至少可以表現(xiàn)的和匿名內(nèi)部類(lèi)一樣好。

    幕后的Lambda
        Lambda表達(dá)式看起來(lái)像匿名內(nèi)部類(lèi),但它們的實(shí)現(xiàn)方法不同。Java的內(nèi)部類(lèi)有很多構(gòu)造器;每個(gè)內(nèi)部類(lèi)都會(huì)有一個(gè)字節(jié)碼級(jí)別的獨(dú)立類(lèi)文件。這就會(huì)產(chǎn)生大量的重復(fù)代碼(大部分是在常量池實(shí)體中),類(lèi)加載時(shí)會(huì)造成大量的運(yùn)行時(shí)開(kāi)銷(xiāo),哪怕只有少量的代碼也會(huì)有如此后果。
        Java 8沒(méi)有為L(zhǎng)ambda生成獨(dú)立的類(lèi)文件,而是使用了在Java 7中引入的invokedynamic字節(jié)碼指令。invokedynamic作用于一個(gè)啟動(dòng)方法,當(dāng)該方法第一次被調(diào)用時(shí)它會(huì)轉(zhuǎn)而去創(chuàng)建Lambda表達(dá)式的實(shí)現(xiàn)。然后,該實(shí)現(xiàn)會(huì)被返回并被直接調(diào)用。這樣就避免了獨(dú)立類(lèi)文件帶來(lái)的空間開(kāi)銷(xiāo),以及加載類(lèi)的大量運(yùn)行時(shí)開(kāi)銷(xiāo)。確切地說(shuō),Lambda功能的實(shí)現(xiàn)被丟給了啟動(dòng)程序。目前Java 8生成的啟動(dòng)程序會(huì)在運(yùn)行時(shí)為L(zhǎng)ambda創(chuàng)建一個(gè)新類(lèi),但在將來(lái)會(huì)使用不同的方法去實(shí)現(xiàn)。
        Java 8使用的優(yōu)化使得通過(guò)invokedynamic指令實(shí)現(xiàn)的Lambda在實(shí)際中運(yùn)行正常。多數(shù)其它的JVM語(yǔ)言,包括Scala (2.10.x),都會(huì)為閉包使用編譯器生成的內(nèi)部類(lèi)。在將來(lái),這些語(yǔ)言可能會(huì)轉(zhuǎn)而使用invokedynamic指令,以便利用到Java 8(及其后繼版本)的優(yōu)化。

    Lambda的局限
        如在本文開(kāi)始時(shí)我所提到的,Lambda表達(dá)式總是某些特殊函數(shù)式接口的實(shí)現(xiàn)。你可以?xún)H把Lambda當(dāng)作接口引用去傳遞,而對(duì)于其它的接口實(shí)現(xiàn),你也可以只是把Lambda當(dāng)作這些特定接口去使用。清單5展示了這種局限性,在該示例使用了一對(duì)相同的(名稱(chēng)除外)函數(shù)式接口。Java 8編譯接受String::lenght來(lái)作為這兩個(gè)接口的Lambda實(shí)現(xiàn)。但是,在一個(gè)Lambd表達(dá)式被定義為第一個(gè)接口的實(shí)例之后,它不能夠用于第二個(gè)接口的實(shí)例。
    清單5. Lambda的局限
    private interface A {
        
    public int valueA(String s);
    }
    private interface B {
        
    public int valueB(String s);
    }
    public static void main(String[] args) {
        A a 
    = String::length;
        B b 
    = String::length;

        
    // compiler error!
        
    // b = a;

        
    // ClassCastException at runtime!
        
    // b = (B)a;

        
    // works, using a method reference
        b = a::valueA;
        System.out.println(b.valueB(
    "abc"));
    }
        任何對(duì)Java接口概念有所了解的人都不會(huì)對(duì)清單5中的程序感到驚訝,因?yàn)槟蔷褪荍ava接口一直所做的事情(除了最后一點(diǎn),那是Java 8新引入的方法引用)。但是使用其它函數(shù)式編程語(yǔ)言,例如Scala,的開(kāi)發(fā)者們則會(huì)認(rèn)為接口的這種限制是不自然的。
        函數(shù)式編程語(yǔ)言是用函數(shù)類(lèi)型,而不是接口,去定義變量。在這些編程語(yǔ)言中會(huì)很普遍的使用高級(jí)函數(shù):把函數(shù)作為參數(shù)傳遞給其它的函數(shù),或者把函數(shù)當(dāng)作值去返回。其結(jié)果就是你會(huì)得到比Lambda更為靈活的編程風(fēng)格,這包括使用函數(shù)去組合其它函數(shù)以構(gòu)建語(yǔ)句塊的能力。因?yàn)镴ava 8沒(méi)有定義函數(shù)類(lèi)型,你不能使用這種方法去組合Lambda表達(dá)式。你可以組合接口(如清單3所示),但只能是與Java 8中已寫(xiě)好的那些接口相關(guān)的特定接口。僅在新的java.util.function包內(nèi),就特殊設(shè)定了43個(gè)接口去使用Lambda。把它們加入到數(shù)以百計(jì)的已有接口中,你將看到這種方法在組合接口時(shí)總是會(huì)有嚴(yán)重的限制。
        使用接口而不是在向Java中引入函數(shù)類(lèi)型是一個(gè)精妙的選擇。這樣就在防止對(duì)Java類(lèi)庫(kù)進(jìn)行重大改動(dòng)的同時(shí)也能夠?qū)σ延蓄?lèi)庫(kù)使用Lambda表達(dá)式。它的壞作用就是對(duì)Java 8造成了極大的限制,它只能稱(chēng)為"接口編程"或是類(lèi)函數(shù)式編程,而不是真正的函數(shù)式編程。但依靠JVM上其它語(yǔ)言,也包括函數(shù)式語(yǔ)言,的優(yōu)點(diǎn),這些限制并不可怕。

    結(jié)論
        Lambda是Java語(yǔ)言的最主要擴(kuò)展,伴著它們的兄弟新特性--方法引用,隨著程序被移植到Java 8,Lambda將很快成為所有Java開(kāi)發(fā)者不可或缺的工具。當(dāng)與Java 8流結(jié)合起來(lái)時(shí),Lambda就特別有用。查看文章"JVM并發(fā): Java 8并發(fā)基礎(chǔ)",可以了解到將Lambda和流結(jié)合起來(lái)使用是如何簡(jiǎn)化并發(fā)編程以及提高程序效率的。
    posted on 2014-04-19 23:48 John Jiang 閱讀(3156) 評(píng)論(0)  編輯  收藏 所屬分類(lèi): JavaSEJava翻譯
    主站蜘蛛池模板: 亚洲V无码一区二区三区四区观看 亚洲αv久久久噜噜噜噜噜 | yellow免费网站| 三级毛片在线免费观看| 久久国产精品萌白酱免费| 国产精品视频免费| 国产精品自在自线免费观看| 亚洲无码高清在线观看| 久久精品亚洲一区二区| 亚洲精品午夜国产va久久| 青青青视频免费观看| 久久青草91免费观看| 成人免费无遮挡无码黄漫视频| xvideos亚洲永久网址| 亚洲av之男人的天堂网站| 精品丝袜国产自在线拍亚洲| 免费人成又黄又爽的视频在线电影| 免费毛片在线看不用播放器| 国产片AV片永久免费观看| 日韩中文无码有码免费视频 | 亚洲性69影院在线观看| 国产成人精品日本亚洲语音| 免费无码黄网站在线看| 国产一精品一AV一免费孕妇| 久久亚洲国产成人影院网站 | 亚洲日本人成中文字幕| 四虎国产精品永免费| 131美女爱做免费毛片| 免费成人黄色大片| 亚洲综合激情另类小说区| 黄人成a动漫片免费网站| 最近免费中文字幕mv电影| 波多野结衣中文一区二区免费| 日本久久久久亚洲中字幕| 白白色免费在线视频| 亚洲成人免费网址| 亚洲无码视频在线| 亚洲夂夂婷婷色拍WW47| 可以免费观看的国产视频| 免费一级毛片在线观看| 亚洲国产美女在线观看| 久久精品成人免费观看97|