一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|JAVA教程|ASP教程|

服務器之家 - 編程語言 - JAVA教程 - 詳解Java的閉包

詳解Java的閉包

2019-12-26 13:25goldensun JAVA教程

這篇文章主要介紹了詳解Java的閉包,作者從Lambda和默認方法等重要特性深入講解,極力推薦!需要的朋友可以參考下

 在2013年將發布的 JavaSE8 中將包含一個叫做 Lambda Project 的計劃,在今年6月份的 JSR-335 草案中有描述。

 JSR-335 將閉包引入了 Java 。閉包在現在的很多流行的語言中都存在,例如 C++、C# 。閉包允許我們創建函數指針,并把它們作為參數傳遞。在這篇文章中,我們將粗略的看一遍Java8的特性,并介紹Lambda表達式。而且我將試著放一些樣例程序來解釋一些概念和語法。

Java 編程語言給我們提供了接口的概念,接口里可以定義抽象的方法。接口定義了 API,并希望用戶或者供應商來實現這些方法。很多時候,我們并不為一些接口創建獨立的實現類,我們通過寫一個匿名內部類來寫一個內聯的接口實現。

匿名類使用的非常廣泛。匿名內部類使用的最常見的場景就是事件處理器了。其次匿名內部類還常被用在多線程的程序中,我們通常寫匿名內部類,而不是創建 Runnable/Callable 接口的實現類。

就像我們討論的一樣,一個匿名類就是一個內聯的給定的接口的實現。通常我們將這個實現類的對象作為參數傳遞給一個方法,然后這個方法將在內部調用傳遞過來的實現類的方法。故這種接口叫做回調接口,這些方法叫做回調方法。


雖然匿名類到處都在使用,但是他們還是有很多問題。第一個主要問題是復雜。這些類讓代碼的層級看起來很亂很復雜,也稱作 Vertical Problem 。第二,他們不能訪問封裝類的非 final 成員。this 這個關鍵字將變得很有迷惑性。如果一個匿名類有一個與其封裝類相同的成員名稱,內部變量將會覆蓋外部的成員變量,在這種情況下,外部的成員在匿名類內部將是不可見的,甚至不能通過 this 關鍵字來訪問。因為 this 關鍵字值得是匿名類對象本身而不是他的封裝類的對象。
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void anonymousExample() {
  String nonFinalVariable = "Non Final Example";
  String variable = "Outer Method Variable";
  new Thread(new Runnable() {
    String variable = "Runnable Class Member";
    public void run() {
      String variable = "Run Method Variable";
      //Below line gives compilation error.
      //System.out.println("->" + nonFinalVariable);
      System.out.println("->" + variable);
      System.out.println("->" + this.variable);
    }
  }).start();
}

輸出是:
 

?
1
2
->Run Method Variable
->Runnable Class Member

這個例子很好的說明了我上面所說的這個問題,而 Lambda 表達式幾乎解決了匿名內部類帶來的所有問題。在我們進一步探討 lambda 表達式之前,讓我們來看一看 Functional Interfaces。


Functional Interfaces

Functional Interfaces 是一個只有單個方法的接口,這代表了這個方法契約。

上面的定義中的只有一個實際上并沒有那么簡單。這段有些不懂,請讀者查看原文(The ‘Single' method can exist in the form of multiple abstract methods that are inherited from superinterfaces. But in that case the inherited methods should logically represent a single method or it might redundantly declare a method that is provided by classes like Object, e.g. toString.)

下面的例子清楚的展示了怎樣理解 Functional Interfaces 的概念。
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Runnable { void run(); }
// Functional
interface Foo { boolean equals(Object obj); }
// Not functional; equals is already an implicit member
interface Bar extends Foo {int compare(String o1, String o2); }
// Functional; Bar has one abstract non-Object method
interface Comparator {
 boolean equals(Object obj);
 int compare(T o1, T o2);
}
// Functional; Comparator has one abstract non-Object method
interface Foo {int m();  Object clone(); }
// Not functional; method Object.clone is not public
interface X { int m(Iterable arg); }
interface Y { int m(Iterable arg); }
interface Z extends X, Y {}
// Functional: two methods, but they have the same signature

大多數回調接口都是 Functional Interfaces。例如 Runnable,Callable,Comparator 等等。以前被稱作 SAM(Single Abstract Method)


Lambda 表達式

我們上邊說過,匿名類的一個主要問題是是代碼的層級看起來很亂,也就是 Vertical Problem 了,Lamdba 表達式實際上就是匿名類,只不過他們的結構更輕量,更短。Lambda 表達式看起來像方法。他們有一個正式的參數列表和這些參數的塊體表達。
 

?
1
2
3
(String s)-> s.lengh;
() -> 43;
(int x, int y) -> x + y;

上面的例子的意思是,第一個表達式接收一個 String 變量作為參數,然后返回字符串的長度。第二個不帶任何參數,并返回43。最后,第三個接受兩個整數 x 和 y ,并返回其和。

在看了許多文字后,終于,我可以給出第一個 Lambda 表達式的例子了,這個例子運行在 JavaSE8 的預覽版下:
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class FirstLambdaExpression {
  public String variable = "Class Level Variable";
  public static void main(String[] arg) {
    new FirstLambdaExpression().lambdaExpression();
  }
  public void lambdaExpression(){
    String variable = "Method Local Variable";
    String nonFinalVariable = "This is non final variable";
    new Thread (() -> {
      //Below line gives compilation error
      //String variable = "Run Method Variable"
      System.out.println("->" + variable);
      System.out.println("->" + this.variable);
    }).start();
  }
}

輸出是:
 

?
1
2
->Method Local Variable
->Class Level Variable


你可以比較一些使用 Lambda 表達式和使用匿名內部類的區別。我們可以清楚的說,使用 Lambda 表達式的方式寫匿名類解決了變量可見性的問題。你可以看一下代碼中的注釋, Lambda 表達式不允許創建覆蓋變量。

通常的 Lambda 表達式的語法包括一個參數列表,箭頭關鍵字"->"最后是主體。主體可以是表達式(單行語句)也可以是多行語句塊。如果是表達式,將被計算后返回,如果是多行的語句塊,就看起來跟方法的語句塊很相似了,可以使用 return 來指定返回值。break 和 continue  只能用在循環內部。

為什么選擇這個特殊的語法形式呢,因為目前 C# 和 Scala 中通常都是這種樣式,也算是 Lambda 表達式的通用寫法。這樣的語法設計基本上解決了匿名類的復雜性。但是與此同時他也是非常靈活的,例如,如果方法體是單個表達式,大括號和 return 語句都是不需要的。表達式的結果就是作為他自己的返回值。這種靈活性可以保持代碼簡潔。

 Lambda 表達式用作匿名類,因此他們可以靈活運用在其他模塊或在其他 Lambda 表達式(嵌套的 Lambda 表達式)。
 

?
1
2
3
4
5
6
7
8
//Lambda expression is enclosed within methods parameter block.
//Target interface type is the methods parameter type.
String user = doSomething(() -> list.getProperty(“propName”);
//Lambda expression is enclosed within a thread constructor
//target interface type is contructors paramter i.e. Runnable
new Thread (() -> {
  System.out.println("Running in different thread");
}).start();


如果你仔細看看 lambda 表達式,您將看到,目標接口類型不是一個表達式的一部分。編譯器會幫助推斷 lambda 表達式的類型與周圍環境。

Lambda 表達式必須有一個目標類型,而他們可以適配任意可能的目標類型。當目標類型是一個接口的時候,下面的條件必須滿足,才能編譯正確:

  •     接口應該是一個 functional interface
  •     表達式的參數數量和類型必須與 functional interface 中聲明的一致
  •     返回值類型必須兼容 functional interface 中方法的返回值類型
  •     拋出的異常表達式必須兼容 functional interface 中方法的拋出異常聲明

由于編譯器可以通過目標類型的聲明中得知參數類型和個數,所以在 Lambda 表達式中,可以省略參數類型聲明。
 

?
1
Comparator c = (s1, s2) -> s1.compareToIgnoreCase(s2);

而且,如果目標類型中聲明的方法只接收一個參數(很多時候都是這樣的),那么參數的小括號也是可以不寫的,例如:
 

?
1
ActionListenr listenr = event -> event.getWhen();


一個很明顯的問題來了,為什么 Lambda 表達式不需要一個指定的方法名呢?

答案是:Lambda 表達式只能用于 functional interface ,而 functional interface 只有一個方法。

當我們確定一個 functional interface 來創建 Lambda 表達式的時候,編譯器可以感知 functional interface 中方法的簽名,并且檢查給定的表達式是否匹配。

這種靈活的語法幫助我們避免了使用匿名類的 Vertical Problem ,而且不會帶來 Horizontal Problem(單行語句非常長)。

Lambda 表達式的語法是上下文相關的,但是這些并不是第一次出現。Java SE 7添加的diamond operators 也有這個概念,通過上下文推斷類型。
 

?
1
2
3
4
void invoke(Runnable r) {r.run()}
void Future invoke(Callable r) {return c.compute()}
//above are two methods, both takes parameter of type functional interface
Future s = invoke(() ->"Done"); //Which invoke will be called?


上面問題的答案是調用接收Callable參數的方法。在這種情況下編譯器會通過不同參數類型的重載解決。當有不止一個適用的重載方法,編譯器也檢查lambda表達式與相應的目標類型的兼容性。簡單的說,上面的invoke方法期望一個返回,但是只有一個invoke方法具有返回值。

Lambda表達式可以顯式的轉換為指定的目標類型,只要跟對應的類型兼容??匆幌孪旅娴某绦颍覍崿F了三種Callable,而且都將其轉換為Callable類型。
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class FirstSightWithLambdaExpressions {
  public static void main(String[] args) {
    List list = Arrays.asList(
      (Callable)()->"callable 1",
      (Callable) ()->"callable 2",
      (Callable) ()->"callable 3");
    ExecutorService e = Executors.newFixedThreadPool(2);
    List futures = null;
    try {
      futures = e.invokeAll(list);
      new FirstSightWithLambdaExpressions().dumpList(futures);
    } catch (InterruptedException | ExecutionException e1) {
      e1.printStackTrace();
    }
    e.shutdown();
  }
  public void dumpList(List list) throws InterruptedException,
       ExecutionException {
    for (Future future : list) {
      System.out.println(future.get());
    }
  }
}

正如我們前面討論的一樣,匿名類不能訪問周圍環境中非final的變量。但是Lambda表達式里就沒有這個限制。

目前,該定義的 functional interfaces 只適用于接口。我試著對一個只有一個抽象方法的抽象類創建一個 lambda 表達式,但出了一個編譯錯誤。按照 jsr - 335,未來版本的 lambda 表達式可能支持 Functional Classes。


方法引用
方法引用被用作引用一個方法而不調用它。
Lambda 表達式允許我們定義一個匿名的方法,并將它作為 Functional interface 的一個實例。方法引用跟 Lambda 表達式很像,他們都需要一個目標類型,但是不同的是方法引用不提供方法的實現,他們引用一個已經存在的類或者對象的方法。
 

?
1
2
3
4
5
System::getProperty
"abc"::length
String::length
super::toString
ArrayList::new

上面的語句展示了方法和構造函數的引用的通用語法。這里我們看到引入了一個新的操作符“::'(雙冒號)。我尚不清楚確切名稱為這個操作符,但是 JSR 指它作為分隔符,維基百科頁面是指它作為一個范圍解析操作符。作為我們的參考,本教程的范圍內,我們將簡單地將它作為分隔符。
目標引用或者說接收者被放在提供者和分隔符的后面。這形成了一個表達式,它能夠引用一個方法。在最后聲明上述代碼,該方法的名字是“new”。這個表達式引用的是 ArrayList 類的構造方法(下一節再討論構造方法的引用)

再進一步了解這個之前,我想讓你看一看方法引用的強大之處,我創建了一個簡單的 Employee 數組的排序程序。
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class MethodReference {
  public static void main (String[] ar){
    Employee[] employees = {new Employee("Nick"), new Employee("Robin"), new      Employee("Josh"), new Employee("Andy"), new Employee("Mark")};
    System.out.println("Before Sort:");
    dumpEmployee(employees);
    Arrays.sort(employees, Employee::myCompare);
    System.out.println("After Sort:");
    dumpEmployee(employees);
  }
  public static void dumpEmployee(Employee[] employees){
    for(Employee emp : Arrays.asList(employees)){
      System.out.print(emp.name+", ");
    }
    System.out.println();
  }
}
class Employee {
  String name;
  Employee(String name) {
   this.name = name;
  }
  public static int myCompare(Employee emp1, Employee emp2) {
    return emp1.name.compareTo(emp2.name);
  }
}

輸出是:
 

?
1
2
Before Sort: Nick, Robin, Josh, Andy, Mark,
After Sort: Andy, Josh, Mark, Nick, Robin,

輸出沒什么特別,Employee 是一個非常簡單的類,只有一個 name 屬性。靜態方法 myCompare 接收兩個 Employee 對象,返回他們名字的比較。

在 main 方法中我創建了一個不同的 employee 的數組,并且將它連同一個方法引用表達式( Employee::myCompare )傳遞給了 Arrays.sort 方法。

等一下,如果我們看 Javadoc 你會發現 sort 方法的第二個參數是 Comparator 類型的,但是我們卻傳遞了 Employee 的一個靜態方法引用。重要的問題就在這了,我既沒有讓 Employee 實現 Comparable 接口,也沒有寫一個獨立的 Comparator 類,但是輸出確實沒有任何問題。

讓我們來看一看這是為什么。 Arrays.sort 方法期望一個 Comparator 的實例,而這個 Comparator 是一個 functional interface  ,這就意味著他只有一個方法,就是 compare 了。這里我們同樣惡意傳一個 Lambda 表達式,在這個表達式中提供 compare 方法的實現。但是在我們的里中,我們的 Employee 類已經有了一個自己的比較方法。只是他們的名字是不一樣的,參數的類型、數量,返回值都是相同的,這里我們就可以創建一個方法引用,并將它傳遞給 sort 作為第二個參數。

當有多個相同的名稱的方法的時候,編譯器會根據目標類型選擇最佳的匹配。為了搞明白,來看一個例子:
 

?
1
2
3
4
5
6
7
public static int myCompare(Employee emp1, Employee emp2) {
 return emp1.name.compareTo(emp2.name);
}
//Another method with the same name as of the above.
public static int myCompare(Integer int1, Integer int2) {
 return int1.compareTo(int2);
}

我創建了兩個不同的數組,用作排序。
 

?
1
2
3
Employee[] employees = {new Employee("Nick"), new Employee("Robin"),
     new Employee("Josh"), new Employee("Andy"), new Employee("Mark")};
Integer[] ints = {1 , 4, 8, 2, 3, 8, 6};

現在,我執行下面的兩行代碼
 

?
1
2
Arrays.sort(employees, Employee::myCompare);
Arrays.sort(ints, Employee::myCompare);

這里,兩行代碼中的方法引用聲明都是相同的(Employee::myCompare),唯一不同的是我們傳入的數組,我們不需要傳遞一個含糊不清的標記用以知名那個方法作為方法引用,編譯器會幫助我們檢查第一個參數,并且智能的找到合適的方法。
不要被靜態方法誤導了哦,我們還可以創建實例方法的引用。對于靜態方法我們使用類名::方法名來寫方法引用,如果是實例方法的引用,則是對象::方法名。

上面的例子已經是相當不錯的了,但是我們不必為整型的比較單獨寫一個方法,因為Integer已經實現了Comparable并且提供了實現方法compareTo。所以我們直接使用下面這一行就行了:
 

?
1
Arrays.sort(ints, Integer::compareTo);

看到這里,你是否覺得有點迷惑?沒有?那我來讓你迷惑一下
這里, Integer 是一個類名(而不是一個像 new Integer() 一樣的實例),而 compareTo 方法卻是 Integer 類的成員方法(非靜態).如果你仔細看了我上面的描述就會知道,成員方法的方法引用::之前應該是對象,但是為什么這里的語句確實合法的。
答案是:這種類型的語句允許使用在一些特定的類型中。Integer是一個數據類型,而對于數據類型來說,這種語句是允許的。
如果我們將 Employee 的方法 myCompare 變成非靜態的,然后這樣使用:Employee::myCompare,就會出編譯錯誤:No Suitable Method Found。

構造方法引用
構造方法引用被用作引用一個構造方法而不實例化指定的類。
構造方法引用是 JavaSE 8 的一個新的特性。我們可以構造一個構造方法的引用,并且將它作為參數傳遞給目標類型。
當我們使用方法引用的時候,我們引用一個已有的方法使用他們。同樣的,在使用構造方法引用的時候,我們創建一個已有的構造方法的引用。
上一節中我們已經看到了構造方法引用的語法類名::new,這看起來很像方法引用。這種構造方法的引用可以分配給目標 functional interfaces 的實例。一個類可能有多個構造方法,在這種情況下,編譯器會檢查 functional interfaces 的類型,最終找到最好的匹配。
對我來說寫出第一個構造方法引用的程序有些困難,雖然我理解了他的語法,但是我卻不知道怎么使用它,以及它有什么用。最后,我花費了很久的努力,終于“啊,找到了...”,看看下面的程序吧。
 

?
1
2
3
4
5
6
7
8
9
10
11
12
public class ConstructorReference {
  public static void main(String[] ar){
    MyInterface in = MyClass::new;
    System.out.println("->"+in.getMeMyObject());
  }
}
interface MyInterface{
  MyClass getMeMyObject();
}
class MyClass{
  MyClass(){}
}

輸出是:
 

?
1
->com.MyClass@34e5307e

這看起來有點神奇是吧,這個接口和這個類除了接口中聲明的方法的返回值是 MyClass 類型的,沒有任何關系。

這個例子又激起了我心中的另一個問題:怎樣實例化一個帶參數的構造方法引用?看看下面的程序:
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ConstructorReference {
  public static void main(String[] ar){
    EmlpoyeeProvider provider = Employee::new;
    Employee emp = provider.getMeEmployee("John", 30);
    System.out.println("->Employee Name: "+emp.name);
    System.out.println("->Employee Age: "+emp.age);
  }
}
interface EmlpoyeeProvider{
  Employee getMeEmployee(String s, Integer i);
}
class Employee{
  String name;
  Integer age;
  Employee (String name, Integer age){
    this.name = name;
    this.age = age;
  }
}

輸出是:
 

?
1
2
->Employee Name: John
->Employee Age: 30

在看完這篇文章之前,讓我們再來看一看JavaSE8中的最酷的一個特性--默認方法(Default Methods)

默認方法(Default Methods)
JavaSE8 中將會引入一個叫做默認方法的概念。早起的 Java 版本的接口擁有非常嚴格的接口,接口包含了一些抽象方法的聲明,所有非抽象的實現類必須要提供所有這些抽象方法的實現,甚至是這些方法沒有用或者不合適出現在一些特殊的實現類中。在即將到來的Java 版本中,允許我們在接口中定義方法的默認實現。廢話不多說,看下面:
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class DefaultMethods {
 public static void main(String[] ar){
 NormalInterface instance = new NormalInterfaceImpl();
 instance.myNormalMethod();
 instance.myDefaultMethod();
 }
}
interface NormalInterface{
 void myNormalMethod();
 void myDefaultMethod () default{
 System.out.println("-> myDefaultMethod");
 }
}
class NormalInterfaceImpl implements NormalInterface{
 @Override
 public void myNormalMethod() {
 System.out.println("-> myNormalMethod");
 }
}

輸出是:
 

?
1
-> myDefaultMethod

上面的接口中聲明了兩個方法,但是這個接口的實現類只實現了其中一個,因為 myDefaultMethod 使用 default 修飾符標記了,而且提供了一個方法塊用作默認實現。通用的重載規則在這里仍然生效。如果實現類實現了接口中的方法,調用的時候將是調用類中的方法,否則,默認實現將被調用。

集成父接口的接口可以增加、改變、移除父接口中的默認實現。
 

?
1
2
3
4
5
6
7
8
9
10
11
12
interface ParentInterface{
 void initiallyNormal();
 void initiallyDefault () default{
 System.out.println("-> myDefaultMethod");
 }
}
interface ChildInterface extends ParentInterface{
 void initiallyNormal() default{
 System.out.println("now default - > initiallyNormal");
 }
 void initiallyDefault (); //Now a normal method
}

在這個例子中,ParentInterface  定義了兩個方法,一個是正常的,一個是有默認實現的,子接口只是簡單的反了過來,給第一個方法添加了默認實現,給第二個方法移除了默認實現。
設想一個類繼承了類 C ,實現了接口 I ,而且 C 有一個方法,而且跟I中的一個提供默認方法的方法是重載兼容的。在這種情況下,C中的方法會優先于I中的默認方法,甚至C中的方法是抽象的時候,仍然是優先的。
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class DefaultMethods {
 public static void main(String[] ar){
 Interfaxe impl = new NormalInterfaceImpl();
 impl.defaultMethod();
 }
}
class ParentClass{
 public void defaultMethod() {
 System.out.println("->ParentClass");
 }
}
interface Interfaxe{
 public void defaultMethod() default{
 System.out.println("->Interfaxe");
 }
}
class NormalInterfaceImpl extends ParentClass implements Interfaxe{}

輸出是:
 

?
1
->ParentClass

第二個例子是,我的類實現了兩個不同的接口,但是兩個接口中都提供了相同的具有默認實現的方法的聲明。在這種情況下,編譯器將會搞不清楚怎么回事,實現類必須選擇兩個的其中一個實現。這可以通過如下的方式來使用 super 來搞定。
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class DefaultMethods {
 public static void main(String[] ar){
 FirstInterface impl = new NormalInterfaceImpl();
 impl.defaultMethod();
 }
}
interface FirstInterface{
 public void defaultMethod() default{
 System.out.println("->FirstInterface");
 }
}
interface SecondInterface{
 public void defaultMethod() default{
 System.out.println("->SecondInterface");
 }
}
class NormalInterfaceImpl implements FirstInterface, SecondInterface{
 public void defaultMethod(){
 SecondInterface.super.defaultMethod();
 }
}

輸出是:
 

?
1
->SecondInterface

現在,我們已經看完了 Java  閉包的介紹。這個文章中,我們接觸到了 Functional Interfaces  和 Java Closure ,理解了 Java 的 Lambda 表達式,方法引用和構造方法引用。而且我們也寫出了 Lambda 表達式的 Hello World 例子。
JavaSE8 很快就要到來了,我將很高興的擁抱這些新特性,也許這些新特性還是有些迷惑不清,但是我相信,隨著時間的推移,會變得越來越好。

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 日韩精品欧美激情国产一区 | 9久爱午夜视频 | 波多野结衣52部合集在线观看 | 五月天在线视频观看 | 国产日韩一区二区三区在线播放 | 三级伦理影院 | 国产成人咱精品视频免费网站 | 国产精品久久久久久久久99热 | 精品国产福利一区二区在线 | 国产精品亚洲精品日韩已满 | 四虎1515hh.com| 成人网子 | 不良网站在线观看 | 免费超级乱淫视频播放性 | 国产chinese男同gay | 日本狠狠操 | 精品一区二区三区免费视频 | 岛国a香蕉片不卡在线观看 荡女淫春2古装 | 99er在线视频 | 免费看片黄色 | np高h疯狂黄暴宫口 narutomanga玖辛奈之乳 | 成人区精品一区二区毛片不卡 | 6个老师的尿奴 | 亚洲精品国产AV成人毛片 | 国产新疆成人a一片在线观看 | 国色天香社区视频免费观看3 | 国产亚洲sss在线观看 | 93版高校教师 | 女人爽到喷水的视频免费看 | 国产 国语对白 露脸正在播放 | 男人狂躁女人下面的视频免费 | 我和黑色丝袜班主任 | 草草视频在线观看 | 女老板用丝袜脚夹我好爽 | 日本免费三片在线播放 | 国产趴着打光屁股sp抽打 | 亚洲精品视频在线免费 | 午夜私人影院在线观看 | 精品亚洲视频在线 | 青草影院在线观看 | 久久亚洲高清观看 |