日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

麻省理工18年春软件构造课程阅读08“可变性与不变性”

發布時間:2023/12/10 编程问答 65 豆豆
生活随笔 收集整理的這篇文章主要介紹了 麻省理工18年春软件构造课程阅读08“可变性与不变性” 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文內容來自MIT_6.031_sp18: Software Construction課程的Readings部分,采用CC BY-SA 4.0協議。

由于我們學校(哈工大)大二軟件構造課程的大部分素材取自此,也是推薦的閱讀材料之一,于是打算做一些翻譯工作,自己學習的同時也能幫到一些懶得看英文的朋友。另外,該課程的閱讀資料中有許多練習題,但是沒有標準答案,所給出的答案均為譯者所寫,有錯誤的地方還請指出。




譯者:李秋豪

審校:

V1.0 Fri Mar 23 17:20:24 CST 2018


本次課程的目標

  • 理解可變性(mutability)和可變對象
  • 學會判斷別名使用和理解可變性的危險
  • 利用不變性使代碼正確、易懂、可改動

譯者注:mutability還可以翻譯為“易變性”(that can change; likely to change),“易”似乎也能突出使用應該謹慎,但后來我還是覺得“可變”更準確,所以就譯為“可變性”。


創建和使用對象

譯者注:麻省理工理工是先教的Python,Java并沒有作為課程。所以這里提供了一組從Python到Java過渡資料。Java基礎好的朋友這節可以跳過。

From Ken Lambert’s tutorial From Python to Java, read the first 8 pages under Defining Classes:

  • Class Structure
  • Visibility Modifiers
  • Instance Variables and Constructors
  • Defining Other Constructors
  • Instance Methods
  • Method Overloading
  • Class (static) Variables and Methods
  • Symbolic Constants (final Variables)

Optional: if you want to see more examples, read these Java Tutorials pages:

  • Declaring Classes
  • Declaring Member Variables
  • Providing Constructors for Your Classes
  • Understanding Class Members
  • Using the this Keyword
  • Initializing Fields

閱讀小練習

Classes and objects

class Tortoise:def __init__(self):self.position = 0def forward(self):self.position += 1pokey = Tortoise() pokey.forward() print(pokey.position)

如果我們將 Tortoise 轉換為Java,應該怎么進行聲明?

public class Tortoise

Under construction

在Python中,我們通過聲明 __init__ 函數來初始化新的對象。

在Java中類似的聲明應該怎么寫?

public Tortoise()

我們應該怎么索引到一個新的 Tortoise 對象?

Tortoise t = new Tortoise()

Methodical

我們在一個 Tortoise 對象中聲明一個 forward 方法:

public void forward() {// self.position += 1 (Python) }

以下哪一行代碼可以達到代碼中注釋行的目的:

  • [x] position += 1;

  • [ ] self.position += 1;

  • [x] this.position += 1;

  • [ ] Tortoise.position += 1;

On your mark

在Python中,我們通過 self.position = 0 初始化 Tortoise 對象中 position 為0.

使用一行代碼將 position 初始化:

public class Tortoise {private int position = 0; // (1)static int position = 0; // (2)public Tortoise() {int position = 0; // (3)int self.position = 0; // (4)int this.position = 0; // (5)int Tortoise.position = 0; // (6)}// ... }
  • [x] 1

  • [ ] 2

  • [ ] 3

  • [ ] 4

  • [ ] 5

  • [ ] 6

或者用幾行初始化 position :

public class Tortoise {private int position; // (1)static int position; // (2)public Tortoise() {self.position = 0; // (3)this.position = 0; // (4)Tortoise.position = 0; // (5)}// ... }
  • [x] 1

  • [ ] 2

  • [ ] 3

  • [x] 4

  • [ ] 5

Get set

現在我們再聲明另一個方法 Tortoise :

public void jump(int position) {// set this Tortoise's position to the input value }

以下哪一行可以將代碼中注釋部分實現?

  • [ ] position = position;

  • [ ] position = this.position;

  • [x] this.position = position;

  • [ ] this.position = this.position;

Static vs. instance

假設我們想到記錄和 Tortoise 類及對象有關的信息,下面哪一個聲明是合理的?

記錄有多少個對象已經被創建了:

  • [ ] int numberOfTortoisesInWorld;
  • [x] static int numberOfTortoisesInWorld;
  • [ ] 在Java中不能這樣聲明

記錄tortoise對象中shell的顏色:

  • [x] Color shell;
  • [ ] static Color shell;
  • [ ] 在Java中不能這樣聲明

對象的母親和父親:

  • [x] Tortoise mother, father;
  • [ ] static Tortoise mother, father;
  • [ ] 在Java中不能這樣聲明


可變性

回憶之前我們討論過的“用快照圖理解值與對象”(譯者注:“Java基礎”),有一些對象的內容是不變的(immutable):一旦它們被創建,它們總是表示相同的值。另一些對象是可變的(mutable):它們有改變內部值對應的方法。

String 就是不變對象的一個例子,一個String 對象總是表示相同的字符串。而StringBuilder 則是可變的,它有對應的方法來刪除、插入、替換字符串內部的字符,等等。

因為 String 是不變的,一旦被創建,一個 String 對象總是有一樣的值。為了在一個 String 對象字符串后加上另一個字符串,你必須創建一個新的 String 對象:

String s = "a"; s = s.concat("b"); // s+="b" and s=s+"b" also mean the same thing

與此相對, StringBuilder 對象是可變的。這個類有對應的方法來改變對象,而不是返回一個新的對象:

StringBuilder sb = new StringBuilder("a"); sb.append("b");

所以這有什么關系呢?在上面這兩個例子中,我們最終都讓s和sb索引到了"ab" 。當對象的索引只有一個時,它們兩確實沒什么去唄。但是當有別的索引指向同一個對象時,它們的行為會大不相同。例如,當另一個變量t指向s對應的對象,tb指向sb對應的對象,這個時候對t和tb做更改就會導致不同的結果:

String t = s; t = t + "c";StringBuilder tb = sb; tb.append("c");

可以看到,改變t并沒有對s產生影響,但是改變tb確實影響到了sb ——這可能會讓編程者驚訝一下(如果他沒有注意的話)。這也是下面我們會重點討論的問題。

既然我們已經有了不變的 String 類,為什么還要使用可變的 StringBuilder 類呢?一個常見的使用環境就是當你要同時創建大量的字符串,例如:

String s = ""; for (int i = 0; i < n; ++i) {s = s + i; }

如果使用不變的字符串,這會發生很多“暫時拷貝”——第一個字符“0”實際上就被拷貝了n次,第二個字符被拷貝了n-1次,等等。總的來說,它會花費O(N^2)的時間來做拷貝,即使最終我們的字符串只有n個字符。

StringBuilder 的設計就是為了最小化這樣的拷貝,它使用了簡單但是聰明的內部結構避免了做任何拷貝(除非到了極限情況)。如果你使用StringBuilder ,可以在最后用 toString() 方法得到一個String的結果:

StringBuilder sb = new StringBuilder(); for (int i = 0; i < n; ++i) {sb.append(String.valueOf(i)); } String s = sb.toString();

優化性能是我們使用可變對象的原因之一。另一個原因是為了分享:程序中的兩個地方的代碼可以通過共享一個數據結構進行交流。

閱讀小練習

Follow me

一個 terrarium 的使用者可以更改紅色的 Turtle 對象嗎?

  • [ ] 不能,因為到 terrarium 的索引是不變的

  • [x] 不能,因為 Turtle 對象是不變的

  • [ ] 可以,因為從列表的0下標處到 Turtle 的索引是可變的。

  • [ ] 可以,因為 Turtle 對象是可變的

一個 george 的使用者可以更改藍色的 Gecko 對象嗎?

  • [ ] 不能,因為到george 的索引是不變的

  • [x] 不能,因為 Gecko 對象是不變的

  • [ ] 可以,因為從列表的1下標處到 Gecko 的索引是可變的。

  • [ ] 可以,因為 Gecko 對象是可變的

一個 petStore 的使用者可以使得另一個 terrarium 的使用者無法訪問藍色的 Gecko 對象嗎?選出最好的答案

  • [ ] 不能,因為到 terrarium 的索引是不變的

  • [ ] 不能,因為 Gecko 對象是不變的

  • [ ] 可以,因為到 petStore 的索引是可變的

  • [ ] 可以,因為 PetStore 對象是可變的

  • [x] 可以,因為 List 對象是可變的

  • [ ] 可以,因為從列表的1下標處到 Gecko 的索引是可變的。


可變性帶來的風險

可變的類型看起來比不可變類型強大的多。如果你在“數據類型商場”購物,為什么要選擇“無聊的”不可變類型而放棄強大的可變類型呢?例如 StringBuilder 應該可以做任何 String 可以做的事情,加上 set() 和 append() 這些功能。

答案是使用不可變類型要比可變類型安全的多,同時也會讓代碼更易懂、更具備可改動性。可變性會使得別人很難知道你的代碼在干嗎,也更難制定開發規定(例如規格說明)。這里舉出了兩個例子:

#1: 傳入可變對象

下面這個方法將列表中的整數相加求和:

/** @return the sum of the numbers in the list */ public static int sum(List<Integer> list) {int sum = 0;for (int x : list)sum += x;return sum; }

假設現在我們要創建另外一個方法,這個方法將列表中數的絕對值相加,根據DRY原則(Don’t Repeat Yourself),實現者寫了一個利用 sum()的方法:

/** @return the sum of the absolute values of the numbers in the list */ public static int sumAbsolute(List<Integer> list) {// let's reuse sum(), because DRY, so first we take absolute valuesfor (int i = 0; i < list.size(); ++i)list.set(i, Math.abs(list.get(i)));return sum(list); }

注意到這個方法直接改變了數組 —— 這對實現者來說很合理,因為利用一個已經存在的列表會更有效率。如果這個列表有幾百萬個元素,那么你節省內存的同時也節省了大量時間。所以實現者的理由很充分:DRY與性能。

但是使用者可能會對結果很驚奇,例如:

// meanwhile, somewhere else in the code... public static void main(String[] args) {// ...List<Integer> myData = Arrays.asList(-5, -3, -2);System.out.println(sumAbsolute(myData));System.out.println(sum(myData)); }

閱讀小練習

Risky #1

上面的代碼會打印出哪兩個數?

10

10?

讓我們想想這個問題的關鍵點:

  • 遠離bug?在這個例子中,很容易就會把指責轉向 sum-Absolute() 的實現者,因為他可能違背了規格說明。但是,傳入可變對象真的(可能)會導致隱秘的bug。只要有一個程序員不小心將這個傳入的列表更改了(例如為了復用或性能),程序就可能會出錯,而且bug很難追查。
  • 易懂嗎?當閱讀 main()的時候,你會對 sum() 和 sum-Absolute()做出哪些假設?對于讀者來說,他能清晰的知道 myData 會被更改嗎?

#2: 返回可變對象

我們剛剛看到了傳入可變對象可能會導致問題。那么返回一個可變對象呢?

Date是一個Java內置的類, 同時 Date也正好是一個可變類型。假設我們寫了一個判斷春天的第一天的方法:

/** @return the first day of spring this year */ public static Date startOfSpring() {return askGroundhog(); }

這里我們使用了有名的土撥鼠算法 (Harold Ramis, Bill Murray, et al. Groundhog Day, 1993).

現在使用者用這個方法來計劃他們的派對開始時間:

// somewhere else in the code... public static void partyPlanning() {Date partyDate = startOfSpring();// ... }

這段代碼工作的很好。不過過了一段時間,startOfSpring()的實現者發現“土撥鼠”被問的不耐煩了,于是打算重寫startOfSpring() ,使得“土撥鼠”最多被問一次,然后緩存下這次的答案,以后直接從緩存讀取:

/** @return the first day of spring this year */ public static Date startOfSpring() {if (groundhogAnswer == null) groundhogAnswer = askGroundhog();return groundhogAnswer; } private static Date groundhogAnswer = null;

(思考:這里緩存使用了private static修飾符,你認為它是全局變量嗎?)

另外,有一個使用者覺得startOfSpring()返回的日期太冷了,所以他把日期延后了一個月:

// somewhere else in the code... public static void partyPlanning() {// let's have a party one month after spring starts!Date partyDate = startOfSpring();partyDate.setMonth(partyDate.getMonth() + 1);// ... uh-oh. what just happened? }

(思考:這里還有另外一個隱秘的bug——partyDate.getMonth() + 1,你知道為什么嗎?)

這兩個改動發生后,你覺得程序會出現什么問題?更糟糕的是,誰會先發現這個bug呢?是這個 startOfSpring() ,還是 partyPlanning() ? 或是在另一個地方使用 startOfSpring()的無辜者?

閱讀小練習

Risky #2

我們不知道Date具體是怎么存儲月份的,所以這里用抽象的值 ...march... 和 ...april... 表示,Date中有一個mounth索引到這些值上。

以下哪一個快照圖表現了上文中的bug?

  • [ ]

  • [ ]

  • [ ]

  • [x]

  • [ ]

Understanding risky example #2

partyPlanning 在不知不覺中修改了春天的起始位置,因為 partyDate 和 groundhogAnswer 指向了同一個可變Date 對象 。

更糟糕的是,這個bug可能不會在這里的 partyPlanning() 或 startOfSpring() 中出現。而是在另外一個調用 startOfSpring()的地方出現,得到一個錯誤的值然后繼續進行運算。

上文中的緩存 groundhogAnswer 是全局變量嗎?

  • [ ] 是全局變量,這是合理的

  • [ ] 是全局變量,這是不合理的

  • [x] 不是全局變量

A second bug

上文中的代碼在加上1月的時候存在另一個bug,請閱讀 Java API documentation for Date.getMonth 和 setMonth.

對于 partyDate.getMonth() ,它的哪一個返回值會導致bug的發生?

11

NoSuchMonthException

上面關于 Date.setMonth 文檔中說: month: the month value between 0-11.那么當這個bug觸發的時候可能會發生什么?

  • [x] 這個方法不會做任何事情

  • [x] 這個方法會按照我們原本的想法運行

  • [x] 這個方法會使得 Date 對象不可用,并報告一個錯誤的值

  • [ ] 這個方法會拋出一個已檢查異常

  • [x] 這個方法會拋出一個未檢查異常

  • [x] 這個方法會將時間設置為9/9/99

  • [x] 這個方法會使得其他的 Date 對象也不可用

  • [x] 這個方法永遠不會返回

SuchTerribleSpecificationsException

在關于 Date 的文檔中,有一句話是這樣說的,“傳入方法的參數并不一定要落在指定的區域內,例如傳入1月32號意味著2月1號”。

這看起來像是前置條件...但它不是的!

下面哪一個選項表現了Date這個特性是不合理的?

  • [ ] 不要寫重復的代碼 (DRY)
  • [x] 快速失敗/報錯
  • [ ] 土撥鼠算法
  • [ ] 使用異常報告特殊結果
  • [ ] 使用前置條件限制使用者


關鍵點:

  • 遠離bug? 沒有,我們產生了一個隱晦的bug。
  • 可改動? 很顯然,這里的可改動指的是我們可以改動一部分代碼而不用擔心其他代碼的改動,而不是可變對象本身的可改動性。在上面的例子中,我們在程序的兩個地方做了改變,結果導致了一個隱晦的bug。

在上面舉出的兩個例子( List<Integer> 和 Date )中,如果我們采用不可變對象,這些問題就迎刃而解了——這些bug在設計上就不可能發生。

事實上,你絕對不應該使用Date !而是使用 包java.time: LocalDateTime, Instant, 等等這些類,它們規格說明都保證了對象是不可變的。

這個例子也說明了使用可變對象可能會導致性能上的損失。因為為了在不修改規格說明和接口的前提下避開這個bug,我們必須讓startOfSpring() 返回一個復制品:

return new Date(groundhogAnswer.getTime());

這樣的模式稱為防御性復制 ,我們在后面講抽象數據類型的時候會講解更多關于防御性復制的東西。這樣的方法意味著 partyPlanning() 可以自由的操控startOfSpring()的返回值而不影響其中的緩存。但是防御性復制會強制要求 startOfSpring() 為每一個使用者復制相同數據——即使99%的內容使用者都不會更改,這會很浪費空間和時間。相反,如果我們使用不可變類型,不同的地方用不同的對象來表示,相同的地方都索引到內存中同一個對象,這樣會讓程序節省空間和復制的時間。所以說,合理利用不變性對象(譯者注:大多是有多個變量索引的時候)的性能比使用可變性對象的性能更好。


別名會讓可變類型存在風險

事實上,如果你只在一個方法內使用可變類型而且該類型的對象只有一個索引,這時并不會有什么風險。而上面的例子告訴我們,如果一個可變對象有多個變量索引到它——這也被稱作“別名”,這時就會有產生bug的風險。

閱讀小練習

Aliasing 1

以下代碼的輸出是什么?

List<String> a = new ArrayList<>(); a.add("cat"); List<String> b = a; b.add("dog"); System.out.println(a); System.out.println(b);
  • [ ] ["cat"]

    `["cat", "dog"]`
  • [x] ["cat", "dog"]

    `["cat", "dog"]`
  • [ ] ["cat"]

    `["cat"]`
  • [ ] ["dog"]

    `["dog"]`

現在試著使用快照圖將上面的兩個例子過一遍,這里只列出一個輪廓:

  • 在 List 例子中,一個相同的列表被list(在 sum 和 sumAbsolute中)和myData(在main中)同時索引。一個程序員(sumAbsolute的)認為更改這個列表是ok的;另一個程序員(main)希望列表保持原樣。由于別名的使用,main的程序員得到了一個錯誤的結果。
  • 而在Date的例子中,有兩個變量 groundhogAnswer 和 partyDate索引到同一個Date對象。這兩個別名出現在程序的不同地方,所以不同的程序員很難知道別人會對這個Date對象做哪些改變。

先在紙上畫出快照圖,但是你真正的目標應該是在腦海中構建一個快照圖,這樣以后你在看代碼的時候也能將其“視覺化”。


更改參數對象的(mutating)方法的規格說明

從上面的分析來看,我們必須使用之前提到過的格式對那些會更改參數對象的方法寫上特定的規格說明。

下面是一個會更改參數對象的方法:

static void sort(List<String> lst) - requires:nothing - effects:puts lst in sorted order, i.e. lst[i] ≤ lst[j] for all 0 ≤ i < j < lst.size()

而這個是一個不會更改參數對象的方法:

static List<String> toLowerCase(List<String> lst) - requires:nothing - effects:returns a new list t where t[i] = lst[i].toLowerCase()

如果在effects內沒有顯式強調輸入參數會被更改,在本門課程中我們會認為方法不會修改輸入參數。事實上,這也是一個編程界的一個約定俗成的規則。


對列表和數組進行迭代

接下來我們會看看另一個可變對象——迭代器 。迭代器會嘗試遍歷一個聚合類型的對象,并逐個返回其中的元素。當你在Java中使用for (... : ...)這樣的遍歷元素的循環時,其實就隱式的使用了迭代器。例如:

List<String> lst = ...; for (String str : lst) {System.out.println(str); }

會被編譯器理解為下面這樣:

List<String> lst = ...; Iterator<String> iter = lst.iterator(); while (iter.hasNext()) {String str = iter.next();System.out.println(str); }

一個迭代器有兩種方法:

  • next() 返回聚合類型對象的下一個元素
  • hasNext() 測試迭代器是否已經遍歷到聚合類型對象的結尾

注意到next() 是一個會修改迭代器的方法(mutator method),它不僅會返回一個元素,而且會改變內部狀態,使得下一次使用它的時候會返回下一個元素。

感興趣的話,你可以讀讀Java API中關于迭代器的定義 .

MyIterator

為了更好的理解迭代器是如何工作的,這里有一個ArrayList<String>迭代器的簡單實現:

/*** A MyIterator is a mutable object that iterates over* the elements of an ArrayList<String>, from first to last.* This is just an example to show how an iterator works.* In practice, you should use the ArrayList's own iterator* object, returned by its iterator() method.*/ public class MyIterator {private final ArrayList<String> list;private int index;// list[index] is the next element that will be returned// by next()// index == list.size() means no more elements to return/*** Make an iterator.* @param list list to iterate over*/public MyIterator(ArrayList<String> list) {this.list = list;this.index = 0;}/*** Test whether the iterator has more elements to return.* @return true if next() will return another element,* false if all elements have been returned*/public boolean hasNext() {return index < list.size();}/*** Get the next element of the list.* Requires: hasNext() returns true.* Modifies: this iterator to advance it to the element * following the returned element.* @return next element of the list*/public String next() {final String element = list.get(index);++index;return element;} }

MyIterator 使用到了許多Java的特性,例如構造體,static和final變量等等,你應該確保自己已經理解了這些特性。參考: From Python to Java 或 Classes and Objects in the Java Tutorials

上圖畫出了 MyIterator 初始狀態的快照圖。

注意到我們將list的索引用雙箭頭表示,以此表示這是一個不能更改的final索引。但是list索引的 ArrayList 本身是一個可變對象——內部的元素可以被改變——將list聲明為final并不能阻止這種改變。

那么為什么要使用迭代器呢?因為不同的聚合類型其內部實現的數據結構都不相同(例如連接鏈表、哈希表、映射等等),而迭代器的思想就是提供一個訪問元素的通用中間件。通過使用迭代器,使用者只需要用一種通用的格式就可以遍歷訪問聚合類的元素,而實現者可以自由的更改內部實現方法。大多數現代語言(Python、C#、Ruby)都使用了迭代器。這是一種有效的設計模式 (一種被廣泛測試過的解決方案)。我們在后面的課程中會看到很多其他的設計模式。

閱讀小練習

MyIterator.next signature

迭代器的實現中使用到了實例方法(instance methods),實例方法是在一個實例化對象上進行操作的,它被調用時會傳入一個隱式的參數this (就像Python中的self一樣),通過這個this該方法可以訪問對象的數據(fields)。

我們首先看看 MyIterator中的 next 方法:

public class MyIterator {private final ArrayList<String> list;private int index;.../*** Get the next element of the list.* Requires: hasNext() returns true.* Modifies: this iterator to advance it to the element * following the returned element.* @return next element of the list*/public String next() {final String element = list.get(index);++index;return element;} }

next的輸入是什么類型?

  • [ ] void – 沒有輸入

  • [ ] ArrayList

  • [x] MyIterator

  • [ ] String

  • [ ] boolean

  • [ ] int

next的輸出是什么類型?

  • [ ] void – 沒有輸出

  • [ ] ArrayList

  • [ ] MyIterator

  • [x] String

  • [ ] boolean

  • [ ] int

MyIterator.next precondition

next 有前置條件 requires: hasNext() returns true.

next的哪一個輸入被這個前置條件所限制?

  • [ ] 都沒有被限制

  • [x] this

  • [ ] hasNext

  • [ ] element

當前置條件不滿足時,實現的代碼可以去做任何事。具體到我們的實現中,如果前置條件不滿足,代碼會有什么行為?

  • [ ] 返回 null

  • [ ] 返回列表中其他的元素

  • [ ] 拋出一個已檢查異常

  • [x] 拋出一個非檢查異常

MyIterator.next postcondition

next的一個后置條件是 @return next element of the list.

next 的哪一個輸出被這個后置條件所限制?

  • [ ] 都沒有被限制

  • [ ] this

  • [ ] hasNext

  • [x] 返回值

next 的另外一個后置條件是 modifies: this iterator to advance it to the element following the returned element.

什么會被這個后置條件所限制?

  • [ ] 都沒有被限制

  • [x] this

  • [ ] hasNext

  • [ ] 返回值


可變性對迭代器的損害

現在讓我們試著將迭代器用于一個簡單的任務。假設我們有一個MIT的課程代號列表,例如["6.031", "8.03", "9.00"] ,我們想要設計一個 dropCourse6 方法,它會將列表中所有以“6.”開頭的代號刪除。根據之前所說的,我們先寫出如下規格說明:

/*** Drop all subjects that are from Course 6. * Modifies subjects list by removing subjects that start with "6."* * @param subjects list of MIT subject numbers*/ public static void dropCourse6(ArrayList<String> subjects)

注意到 dropCourse6 顯式的強調了它會對參數 subjects 做修改。

接下來,根據測試優先編程的原則,我們對輸入空間進行分區,并寫出了以下測試用例:

// Testing strategy: // subjects.size: 0, 1, n // contents: no 6.xx, one 6.xx, all 6.xx // position: 6.xx at start, 6.xx in middle, 6.xx at end// Test cases: // [] => [] // ["8.03"] => ["8.03"] // ["14.03", "9.00", "21L.005"] => ["14.03", "9.00", "21L.005"] // ["2.001", "6.01", "18.03"] => ["2.001", "18.03"] // ["6.045", "6.031", "6.813"] => []

最后,我們實現dropCourse6方法:

public static void dropCourse6(ArrayList<String> subjects) {MyIterator iter = new MyIterator(subjects);while (iter.hasNext()) {String subject = iter.next();if (subject.startsWith("6.")) {subjects.remove(subject);}} }

但是當我們測試的時候,最后一個例子報錯了:

// dropCourse6(["6.045", "6.031", "6.813"]) // expected [], actual ["6.031"]

dropCourse6 似乎沒有將列表中的元素清空,為什么?為了追查bug是在哪發生的,我們建議你畫出一個快照圖,并逐步模擬程序的運行。

閱讀小練習

Draw a snapshot diagram

現在畫出一個初始(代碼未執行)快照圖。你需要參考上面MyIterator 類和 dropCourse6() 方法的代碼實現。

在你的初始快照圖中有哪些標簽?

  • [ ] iter

  • [ ] index

  • [x] list

  • [x] subjects

  • [ ] subject

  • [x] ArrayList

  • [ ] List

  • [ ] MyIterator

  • [x] String

  • [ ] dropCourse6

現在執行第一條語句 MyIterator iter = new MyIterator(subjects); ,你的快照圖中又有哪些標簽?

  • [x] iter

  • [x] index

  • [x] list

  • [x] subjects

  • [ ] subject

  • [x] ArrayList

  • [ ] List

  • [x] MyIterator

  • [x] String

  • [ ] dropCourse6

Entering the loop

現在執行接下來的語句String subject = iter.next().,你的快照圖中添加了什么東西?

  • [ ] 一個從 subject 到ArrayList 0 下標的箭頭

  • [ ] 一個從 subject 到ArrayList 1 下標的箭頭

  • [ ] 一個從index 到 0 的箭頭

  • [x] 一個從index 到 1 的箭頭

這個時候subject.startsWith("6.") 返回是什么?

  • [x] 真,因為 subject 索引到了字符串 "6.045"

  • [ ] 真,因為 subject 索引到了字符串 "6.031"

  • [ ] 真,因為 subject 索引到了字符串 "6.813"

  • [ ] 假,因為 subject 索引到了其他字符串

Remove an item

現在畫出在 subjects.remove(subject)語句執行后的快照圖。

現在ArrayList subjects 是什么樣子?

  • [ ] 下標0對應 "6.045"

  • [x] 下標0對應 "6.031"

  • [ ] 下標0對應 "6.813"

  • [ ] 沒有下標0

  • [ ] 下標1對應 "6.045"

  • [ ] 下標1對應 "6.031"

  • [x] 下標1對應 "6.813"

  • [ ] 沒有下標1

  • [ ] 下標2對應 "6.045"

  • [ ] 下標2對應 "6.031"

  • [ ] 下標2對應 "6.813"

  • [x] 沒有下標2

Next iteration of the loop

現在進行下一次循環,執行語句 iter.hasNext() 和String subject = iter.next() ,此時 subject.startsWith("6.") 的返回是什么?

  • [ ] 真,因為 subject 索引到了字符串 "6.045"
  • [ ] 真,因為 subject 索引到了字符串 "6.031"
  • [x] 真,因為 subject 索引到了字符串 "6.813"
  • [ ] 假,因為 subject 索引到了其他字符串

在這個測試用例中,哪一個ArrayList中的元素永遠不會被 MyIterator.next() 返回?

  • [ ] "6.045"

  • [x] "6.031"

  • [ ] "6.813"

如果你想要解釋這個bug是如何發生的,以下哪一些聲明會出現在你的報告里?

  • [x] list 和 subjects 是一對別名,它們都指向同一個 ArrayList 對象.

  • [x] 一個列表在程序的兩個地方被使用別名,當一個別名修改列表時,另一個別名處不會被告知。

  • [ ] 代碼沒有檢查列表中奇數下標的元素。

  • [x] MyIterator 在迭代的時候是假設迭代對象不會發生更改的。

其實,這并不是我們設計的 MyIterator帶來的bug。Java內置的 ArrayList 迭代器也會有這樣的問題,在使用for遍歷循環這樣的語法糖是也會出現bug,只是表現形式不一樣,例如:

for (String subject : subjects) {if (subject.startsWith("6.")) {subjects.remove(subject);} }

這段代碼會拋出一個 Concurrent-Modification-Exception異常,因為這個迭代器檢測到了你在對迭代對象進行修改(你覺得它是怎么檢測到的?)。

那么應該怎修改這個問題呢?一個方法就是使用迭代器的 remove() 方法(而不是直接操作迭代對象),這樣迭代器就能自動調整迭代索引了:

Iterator iter = subjects.iterator(); while (iter.hasNext()) {String subject = iter.next();if (subject.startsWith("6.")) {iter.remove();} }

事實上,這樣做也會更有效率,因為 iter.remove() 知道要刪除的元素的位置,而 subjects.remove() 對整個聚合類進行一次搜索定位。

但是這并沒有完全解決問題,如果有另一個迭代器并行對同一個列表進行迭代呢?它們之間不會互相告知修改!

閱讀小練習

Pick a snapshot diagram

以下哪一個快照圖描述了上面所述并行bug的發生?

  • [ ]

  • [ ]

  • [x]

  • [ ]

  • [ ]


變化與契約(contract)

可變對象會使得契約(例如規格說明)變得復雜

這也是使用可變數據結構的一個基本問題。一個可變對象有多個索引(對于對象來說稱作“別名”)意味著在你程序的不同位置(可能分布很廣)都依賴著這個對象保持不變。

為了將這種限制放到規格說明中,規格不能只在一個地方出現,例如在使用者的類和實現者的類中都要有。現在程序正常運行依賴著每一個索引可變對象的人遵守相應制約。

作為這種非本地制約“契約”,想想Java中的聚合類型,它們的文檔都清楚的寫出來使用者和實現者應該遵守的制約。試著找到它對使用者的制約——你不能在迭代一個聚合類時修改其本身。另外,這是哪一層類的責任?Iterator? List? Collection? 你能找出來嗎?

同時,這樣的全局特性也會使得代碼更難讀懂,并且正確性也更難保證。但我們不得不使用它——為了性能或者方便——但是我們也會為安全性付出巨大的代價。

可變對象降低了代碼的可改動性

可變對象還會使得使用者和實現者之間的契約更加復雜,這減少了實現者和使用者改變代碼的自由度。這里舉出了一個例子。

下面這個方法在MIT的數據庫中查找并返回用戶的9位數ID:

/*** @param username username of person to look up* @return the 9-digit MIT identifier for username.* @throws NoSuchUserException if nobody with username is in MIT's database*/ public static char[] getMitId(String username) throws NoSuchUserException { // ... look up username in MIT's database and return the 9-digit ID }

假設有一個使用者:

char[] id = getMitId("bitdiddle"); System.out.println(id);

現在使用者和實現者都打算做一些改變: 使用者覺得要照顧用戶的隱私,所以他只輸出后四位ID:

char[] id = getMitId("bitdiddle"); for (int i = 0; i < 5; ++i) {id[i] = '*'; } System.out.println(id);

而實現者擔心查找的性能,所以它引入了一個緩存記錄已經被查找過的用戶:

private static Map<String, char[]> cache = new HashMap<String, char[]>();public static char[] getMitId(String username) throws NoSuchUserException { // see if it's in the cache alreadyif (cache.containsKey(username)) {return cache.get(username);}// ... look up username in MIT's database ...// store it in the cache for future lookupscache.put(username, id);return id; }

這兩個改變導致了一個隱秘的bug。如上圖所示,當使用者查找 "bitdiddle" 并得到一個字符數組后,實現者也緩存的是這個數組,他們兩個實際上索引的是同一個數組(別名)。這意味著用戶用來保護隱私的代碼會修改掉實現者的緩存,所以未來調用 getMitId("bitdiddle") 并不會返回一個九位數,例如 “928432033” ,而是修改后的 “*****2033”。

共享可變對象會增加契約的復雜度,想想,如果這個錯誤被交到了“軟件工程法庭”審判,哪一個人會為此承擔責任呢?是修改返回值的使用者?還是沒有保存好返回值的實現者?

下面是一種寫規格說明的方法:

public static char[] getMitId(String username) throws NoSuchUserException - requires:nothing - effects:returns an array containing the 9-digit MIT identifier of username, or throws NoSuchUser-Exception if nobody with username is in MIT’s database. Caller may never modify the returned array.

這是一個下下策這樣的制約要求使用者在程序中的所有位置都遵循不修改返回值的規定!并且這是很難保證的。

下面是另一種寫規格說明的方法:

public static char[] getMitId(String username) throws NoSuchUserException - requires:nothing - effects:returns a new array containing the 9-digit MIT identifier of username, or throws NoSuchUser-Exception if nobody with username is in MIT’s database.

這也沒有完全解決問題. 雖然這個規格說明強調了返回的是一個新的數組,但是誰又知道實現者在緩存中不是也索引的這個新數組呢?如果是這樣,那么用戶對這個新數組做的更改也會影響到未來的使用。This spec at least says that the array has to be fresh. But does it keep the implementer from holding an alias to that new array? Does it keep the implementer from changing that array or reusing it in the future for something else?

下面是一個好的多的規格說明:

public static String getMitId(String username) throws NoSuchUserException - requires:nothing - effects:returns the 9-digit MIT identifier of username, or throws NoSuchUser-Exception if nobody with username is in MIT’s database.

通過使用不可變類型String,我們可以保證使用者和實現者的代碼不會互相影響。同時這也不依賴用戶認真閱讀遵守規格說明。不僅如此,這樣的方法也給了實現者引入緩存的自由。

閱讀小練習

給出以下代碼:

public class Zoo {private List<String> animals;public Zoo(List<String> animals) {this.animals = animals;}public List<String> getAnimals() {return this.animals;} }

Aliasing 2

下面的輸出會是什么?

List<String> a = new ArrayList<>(); a.addAll(Arrays.asList("lion", "tiger", "bear")); Zoo zoo = new Zoo(a); a.add("zebra"); System.out.println(a); System.out.println(zoo.getAnimals());
  • [x] ["lion", "tiger", "bear", "zebra"]

    `["lion", "tiger", "bear", "zebra"]`
  • [ ] ["lion", "tiger", "bear", "zebra"]

    `["zebra", "lion", "tiger", "bear", "zebra"]`
  • [ ] ["lion", "tiger", "bear"]

    `["lion", "tiger", "bear", "zebra"]`
  • [ ] ["lion", "tiger", "bear", "zebra"]

    `["lion", "tiger", "bear"]`

Aliasing 3

接著上面的問題,下面的輸出會是什么?

List<String> b = zoo.getAnimals(); b.add("flamingo"); System.out.println(a);
  • [ ] ["lion", "tiger", "bear"]

  • [ ] ["lion", "tiger", "bear", "zebra"]

  • [x] ["lion", "tiger", "bear", "zebra", "flamingo"]

  • [ ] ["lion", "tiger", "bear", "flamingo"]


有用的不可變類型

既然不可變類型避開了許多危險,我們就列出幾個Java API中常用的不可變類型:

  • 所有的原始類型及其包裝都是不可變的。例如使用BigInteger和 BigDecimal 進行大整數運算。

  • 不要使用可變類型 Date ,而是使用 java.time 中的不可變類型。

  • Java中常見的聚合類 — List, Set, Map — 都是可變的:ArrayList, HashMap等等。但是 Collections 類中提供了可以獲得不可修改版本(unmodifiable views)的方法:

    • Collections.unmodifiableList
    • Collections.unmodifiableSet
    • Collections.unmodifiableMap

    你可以將這些不可修改版本當做是對list/set/map做了一下包裝。如果一個使用者索引的是包裝之后的對象,那么 add, remove, put這些修改就會觸發 Unsupported-Operation-Exception異常。

    當我們要向程序另一部分傳入可變對象前,可以先用上述方法將其包裝。要注意的是,這僅僅是一層包裝,如果你不小心讓別人或自己使用了底層可變對象的索引,這些看起來不可變對象還是會發生變化!

  • Collections 也提供了獲取不可變空聚合類型對象的方法,例如Collections.emptyList

閱讀小練習

給出以下代碼:

List<String> arraylist = new ArrayList<>(); arraylist.add("hello"); List<String> unmodlist = Collections.unmodifiableList(arraylist); // unmodlist should now always be [ "hello" ]

Unmodifiable

會出現什么類型的錯誤?

unmodlist.add("goodbye"); System.out.println(unmodlist);

動態錯誤

Unmodifiable?

輸出是什么?

arraylist.add("goodbye"); System.out.println(unmodlist);

[ “hello” “goodbye” ]

Immutability

以下哪些選項是正確的?

  • [ ] 如果一個類的所有索引都被final修飾,它就是不可變的

  • [x] 如果一個類的所有實例化數據都不會改變,它就是不可變的

  • [x] 不可變類型的數據可以被安全的共享

  • [ ] 通過使用防御性復制,我們可以讓對象變成不可變的

  • [ ] 不可變性使得我們可以關注于全局而非局部代碼


總結

在這篇閱讀中,我們看到了利用可變性帶來的性能優勢和方便,但是它也會產生很多風險,使得代碼必須考慮全局的行為,極大的增加了規格說明設計的復雜性和代碼編寫、測試的難度。

確保你已經理解了不可變對象(例如String)和不可變索引(例如 final 變量)的區別。畫快照圖能夠幫助你理解這些概念:其中對象用圓圈表示,如果是不可變對象,圓圈有兩層;索引用一個箭頭表示,如果索引是不可變的,用雙箭頭表示。

本文最重要的一個設計原則就是不變性 :盡量使用不可變類型和不可變索引。接下來我們還是將本文的知識點和我們的三個目標聯系起來:

  • 遠離bug.不可變對象不會因為別名的使用導致bug,而不可變索引永遠指向同一個對象,也會減少bug的發生。
  • 易于理解. 因為不可變對象和索引總是意味著不變的東西,所以它們對于讀者來說會更易懂——不用一邊讀代碼一邊考慮這個時候對象或索引發生了哪些改動。
  • 可改動性. 如果一個對象或者索引不會在運行時發生改變,那么依賴于這些對象的代碼就不用在其他代碼更改后進行審查。

轉載于:https://www.cnblogs.com/liqiuhao/p/8631733.html

總結

以上是生活随笔為你收集整理的麻省理工18年春软件构造课程阅读08“可变性与不变性”的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

国内一级片在线观看 | 亚洲片在线 | 久久综合五月婷婷 | 精品国产乱码一区二区三区在线 | 亚洲国产精品小视频 | 成人国产一区二区 | www.色午夜,com| 日韩成人在线一区二区 | 日韩电影中文 | 九七在线视频 | 中文字幕乱码亚洲精品一区 | 午夜av免费| 亚洲97在线 | 伊人看片 | 黄色免费观看网址 | 日韩三级视频 | 亚洲年轻女教师毛茸茸 | 日韩av一区二区三区在线观看 | 久久91久久久久麻豆精品 | 在线免费国产视频 | 欧美激情综合五月色丁香 | 日韩理论在线视频 | 香蕉色综合 | 免费在线观看黄网站 | 四虎永久免费在线观看 | 制服丝袜一区二区 | 久久久视频在线 | 久久视频免费在线观看 | 精品v亚洲v欧美v高清v | 五月婷综合网 | 免费福利片 | 久久精品国产美女 | 一区二区三区精品久久久 | 成人在线视频一区 | 久久激情日本aⅴ | 日韩午夜电影 | 少妇性色午夜淫片aaaze | 日韩欧美在线观看一区二区三区 | 人人澡人人爽欧一区 | 国产精品福利一区 | 国内99视频| 久久精品人 | 成人黄色大片 | 国产传媒中文字幕 | 欧美午夜a | 成人av免费看 | 久久久久久国产精品免费 | 日韩综合一区二区三区 | 国产精品综合久久久久久 | 精品在线播放 | 激情五月婷婷 | 日韩成年视频 | 能在线观看的日韩av | 国产精品久久久精品 | 91插插插网站 | 亚洲精品天天 | 一区二区中文字幕在线播放 | 91九色最新地址 | 一区二区三区四区免费视频 | 99久久精品免费看国产四区 | 在线观看精品一区 | 国产精品久久久久久电影 | 婷婷六月激情 | 天天干天天想 | 免费高清在线一区 | 国内一级片在线观看 | 午夜精品一区二区三区在线播放 | 日韩高清不卡一区二区三区 | 国产97碰免费视频 | 亚洲精区二区三区四区麻豆 | 天天干天天干天天射 | 亚洲九九 | 久久国产区 | 九九免费视频 | 亚洲理论在线 | 国产成人一区在线 | 天天爱综合 | 国产91精品一区二区麻豆亚洲 | 天天操天天添天天吹 | 日b视频在线观看网址 | 久久久久久蜜av免费网站 | 国产成人一区二区在线观看 | 国产视频2| 色成人亚洲 | 免费久久网站 | 欧美黄色特级片 | 成人在线超碰 | se视频网址 | 国产精品毛片久久久久久 | 亚洲最新视频在线播放 | 欧美精品国产综合久久 | 丁香色婷| 波多野结衣视频在线 | 99精品黄色片免费大全 | 成人黄大片视频在线观看 | 国产无套视频 | 久久美女免费视频 | 亚洲视频网站在线观看 | 亚洲精色 | 久久97超碰| 日韩av影视在线观看 | 欧美va天堂在线电影 | 色偷偷中文字幕 | 亚洲精品视频在线观看免费视频 | 激情婷婷色 | 国产探花视频在线播放 | 91精品无人成人www | 91尤物国产尤物福利在线播放 | 亚洲波多野结衣 | 激情网综合 | 99久久99久久精品国产片果冰 | 天天玩天天操天天射 | 深夜免费网站 | 午夜三级影院 | 欧美日韩一区二区三区免费视频 | 日韩影视在线观看 | 一区二区三区高清在线观看 | 91精品一区二区三区久久久久久 | 国产成人久久av | 天堂网中文在线 | 国产视频资源在线观看 | 国产精品久久久久久久婷婷 | 成人播放器 | 色噜噜在线观看视频 | 久久久久亚洲最大xxxx | 色婷婷国产 | a√资源在线 | 亚洲综合五月天 | 亚洲综合婷婷 | 成人综合日日夜夜 | 国产va饥渴难耐女保洁员在线观看 | 91日韩精品 | 天堂久久电影网 | 日韩在线视频免费看 | 国产小视频在线 | 超碰在线97观看 | 亚洲国产免费 | 国产精品成人一区二区三区 | 日日夜夜人人精品 | 日韩av看片 | 精品美女视频 | 波多在线视频 | 亚洲乱码精品久久久久 | www操操操 | 欧美韩国日本在线 | 特级黄色一级 | 国产精品观看 | 国产护士hd高朝护士1 | 国产成人高清在线 | 激情偷乱人伦小说视频在线观看 | 激情欧美一区二区免费视频 | 精品国产a| 成人免费91 | 黄色国产成人 | 久久99久久99久久 | 欧美五月婷婷 | 悠悠av资源片 | 成年人在线免费看片 | 精品国产一二三四区 | 99爱视频在线观看 | 免费观看一级 | 欧美成人亚洲 | 日韩精品一区二区免费视频 | 亚洲精品www | 天天操天天干天天爱 | 久久这里只有精品视频99 | 国产欧美日韩精品一区二区免费 | 色网站免费在线观看 | 国产精品黄色 | 亚洲精品456在线播放 | 91精品久久久久久粉嫩 | 天天射一射| 免费在线观看亚洲视频 | 亚洲精品自拍视频在线观看 | 日韩午夜电影院 | 日韩成人不卡 | 激情视频一区二区三区 | 在线观看一区 | 日韩色在线 | 91丨九色丨国产在线观看 | 黄色小说免费观看 | 97偷拍视频| 丁香花中文在线免费观看 | 亚洲精品欧洲精品 | 欧美一区二区三区不卡 | 精品在线观看一区二区三区 | 欧美韩国日本在线 | 精品久久五月天 | 中文字幕精品一区二区精品 | 99视频国产精品免费观看 | 日韩免费小视频 | 91精品办公室少妇高潮对白 | 九九三级毛片 | 久久久久久免费毛片精品 | 欧美日韩精品区 | 免费观看视频的网站 | 国产精品一区二区三区观看 | 欧美日本在线观看视频 | 在线观看免费91 | 亚洲成人动漫在线观看 | 国产精品永久久久久久久久久 | 亚州人成在线播放 | 99色婷婷 | 在线韩国电影免费观影完整版 | 色综合久久久久综合99 | 999国产 | 国产精品亚洲精品 | 麻豆91在线 | 日韩中文字幕亚洲一区二区va在线 | 综合色爱| 人人爱夜夜操 | 欧美精品久久久久久久 | 免费视频黄| 久久99久久99 | 国产精品久久久久久久久搜平片 | 国产在线精品区 | 国产色拍拍拍拍在线精品 | 国产在线视频不卡 | 少妇bbb搡bbbb搡bbbb| 国产成人精品国内自产拍免费看 | 在线小视频国产 | 中文字幕在线视频精品 | 欧美在线视频日韩 | 亚洲精品美女在线观看播放 | 午夜少妇 | 国产在线观看你懂得 | 久久亚洲福利 | 91av在线视频播放 | 成人综合婷婷国产精品久久免费 | 国产一区视频导航 | 亚州av免费 | 国产精品涩涩屋www在线观看 | 奇米网在线观看 | 伊人导航 | 国产精品99精品 | 精品国产一区二区三区免费 | 国产一区二区三精品久久久无广告 | 中文字幕在线影院 | 香蕉视频4aa | 久久在线精品视频 | 九九影视理伦片 | 一区二区三区在线观看中文字幕 | 手机在线小视频 | 日本精品一区二区 | 久久最新 | 久久久电影网站 | 亚洲黄色一级电影 | 美女网站色免费 | 九九九九色 | 色射色 | 国产精品精品视频 | 久久蜜臀一区二区三区av | 五月天激情综合网 | 色婷婷www | 91精品视频网站 | 欧美激情综合五月色丁香小说 | 欧美色噜噜噜 | 久久99热精品 | 开心激情五月网 | av日韩在线网站 | 成年人天堂com | 国产在线色视频 | 中文字幕在线观看完整 | 久久免费99精品久久久久久 | 中字幕视频在线永久在线观看免费 | a天堂一码二码专区 | 国产亚洲精品福利 | 天天se天天cao天天干 | 热久久视久久精品18亚洲精品 | 黄色影院在线观看 | 婷婷久久一区 | 手机看片福利 | 亚洲精品乱码久久久久久蜜桃不爽 | 91porny九色91啦中文 | 成人午夜免费福利 | av黄色免费网站 | 狠狠艹夜夜干 | 久久精品久久久久电影 | 欧美日韩亚洲精品在线 | 在线日韩| 日日操日日插 | 久久在线观看视频 | 国产精品国产亚洲精品看不卡15 | 一区二区三区在线免费播放 | 久草在线视频新 | 精品99视频 | 91av在线播放| 色综合中文综合网 | 香蕉国产91| 高清久久久久久 | 99九九99九九九视频精品 | 久久久久久久久久久久久影院 | 美女视频a美女大全免费下载蜜臀 | 久久99国产精品自在自在app | av亚洲产国偷v产偷v自拍小说 | 国产小视频在线 | 一区二区视频欧美 | 久9在线 | 黄色app网站在线观看 | 色综合色综合久久综合频道88 | 国产色就色 | av青草| 91精品视频免费在线观看 | 色婷婷激情 | 久久国产美女视频 | 精品成人免费 | 亚洲精品在线免费观看视频 | 色婷婷99| 蜜桃视频成人在线观看 | 有码一区二区三区 | 不卡av在线 | 色综合网 | 91日韩精品| 一区二区三区免费在线观看视频 | av看片网址 | 草久久久久久 | 黄色软件网站在线观看 | 综合网欧美 | 日韩久久一区 | 激情五月开心 | 超碰97在线看 | 成人va在线观看 | 中文字幕第一 | 久草在线手机视频 | 日韩一区二区三区视频在线 | 久久天天拍| 精品国产自在精品国产精野外直播 | 日日碰狠狠躁久久躁综合网 | 久久社区视频 | 日韩影片在线观看 | 丁香婷婷久久久综合精品国产 | 一区二区三区四区久久 | www.激情五月.com | 国产美女黄网站免费 | 国产精品一区二区中文字幕 | 久久字幕精品一区 | 久久久受www免费人成 | 久久午夜剧场 | 青青草国产精品视频 | 欧美精品一区二区免费 | 日韩av网页| 免费观看一区 | 日日夜夜免费精品视频 | av片一区 | 青青久草在线视频 | 国产亚洲综合性久久久影院 | 在线观看中文字幕第一页 | av免费在线网站 | 一级欧美黄 | 91黄色在线观看 | 最新中文字幕在线资源 | 91亚洲精品久久久蜜桃 | 国内揄拍国内精品 | 久久精品国产免费看久久精品 | japanesexxxhd奶水| 99999精品| 久久久亚洲麻豆日韩精品一区三区 | 国产久视频 | 九九久久精品 | 成人av在线网址 | 色爱区综合激月婷婷 | 在线只有精品 | 久久久亚洲精华液 | 97精品国产一二三产区 | 日韩精品久久久久 | 精品国产视频一区 | 成人毛片在线视频 | 国产成在线观看免费视频 | 欧美一级片在线观看视频 | 久久久久久高潮国产精品视 | 亚洲精品一区二区精华 | 99se视频在线观看 | 日韩av成人免费看 | 亚洲国产成人在线播放 | 丁香花在线视频观看免费 | 1区2区视频| 久久综合狠狠综合久久激情 | 欧美一二三在线 | 一级黄色片在线 | 欧美视频www| 91成人久久 | 国产精品一区二区久久久 | 国产精品黄色 | 亚洲一区av | 精品一区二区三区久久 | 国产原创在线观看 | 欧美精品三级 | 日韩av伦理片 | 国产成人99av超碰超爽 | 色婷婷综合久久久久中文字幕1 | 国产一卡在线 | 99 久久久久| 国产一区成人在线 | 在线免费观看视频a | 成年人黄色免费网站 | 五月开心综合 | 免费高清在线一区 | 久久免费视频6 | av免费在线网 | av 在线观看 | 欧美另类巨大 | av电影免费| 成人在线播放网站 | 日韩免费在线观看 | 偷拍视频一区 | 精品96久久久久久中文字幕无 | 九九视频在线播放 | 超碰在线人人艹 | 日韩电影一区二区三区在线观看 | 在线观看的a站 | 国产精品美女免费看 | 国产在线视频不卡 | 男女激情麻豆 | 超碰日韩 | 久久免费影院 | 国产黄色看片 | 在线免费观看羞羞视频 | 成人免费影院 | 国产一区二区在线免费播放 | 亚洲作爱视频 | 久草香蕉在线 | 免费无遮挡动漫网站 | 久久精品成人欧美大片古装 | 国产精品综合在线观看 | 精品影院一区二区久久久 | av在线成人 | 狠狠色丁婷婷日日 | 久久福利精品 | 久久成人国产 | 最新的av网站 | 91九色网站 | 91麻豆精品国产91久久久久 | 九九色在线观看 | 豆豆色资源网xfplay | 精品99久久 | 亚洲一区二区三区在线看 | 国产精品av在线免费观看 | 中文字幕在线一区观看 | 国产在线毛片 | 日韩三级av | 亚洲精品999 | av黄色免费在线观看 | 欧美日韩国产精品一区二区 | 丁香五婷 | 一区二区三区国产欧美 | 日韩国产在线观看 | 亚洲国产精久久久久久久 | 中文字幕永久免费 | 免费福利在线 | 国产亚洲精品久久久久久久久久久久 | 91av在线免费 | 热99久久精品 | 婷婷精品国产欧美精品亚洲人人爽 | 在线91视频 | 亚洲精品成人免费 | 久久人人爽人人爽人人片 | 又长又大又黑又粗欧美 | av免费观看网址 | 久久久午夜电影 | 日本黄色免费在线观看 | 日韩黄色免费电影 | 中文字幕91 | 国产九九精品视频 | 亚洲午夜久久久久久久久久久 | 国产不卡精品 | 91视频久久久 | 免费看的黄色录像 | 国产精品成人自产拍在线观看 | 91久久国产综合精品女同国语 | 中文字幕第一页在线播放 | 国产偷国产偷亚洲清高 | 国产女人18毛片水真多18精品 | 在线观看免费高清视频大全追剧 | 干干干操操操 | 在线综合色 | 久久国产精品99久久久久久老狼 | 久久免费视频这里只有精品 | 色多多污污 | 91精品国产三级a在线观看 | www.福利| 久久久电影网站 | 99综合视频 | 免费在线观看a v | 国产精品久久久久久电影 | 91麻豆网站 | 九九九热精品 | 国产91av视频在线观看 | 91天天视频| 成x99人av在线www | 国产亚洲亚洲 | 日韩在线视频一区二区三区 | 国产999精品久久久久久麻豆 | 欧美 国产 视频 | 久久成年人 | 国产视频精品视频 | 久久久久久久久国产 | 91麻豆精品国产91久久久无需广告 | 日韩一区二区免费在线观看 | 夜夜爽天天爽 | 中文字幕电影一区 | 日韩丝袜在线 | 久章草在线观看 | 免费午夜在线视频 | 日日干日日色 | 亚洲午夜大片 | 中文字幕在线视频免费播放 | 亚洲欧洲国产日韩精品 | 国产精品综合在线观看 | 国产最新福利 | 精品一区二区日韩 | 国产无吗一区二区三区在线欢 | 天天色天天操综合网 | 九九九九九九精品 | 91在线精品秘密一区二区 | 久久爱导航 | 在线精品视频免费播放 | 色综合久久五月天 | 国产精国产精品 | www.香蕉 | www.狠狠操.com| 国产精品原创av片国产免费 | 麻豆视频在线免费观看 | 偷拍福利视频一区二区三区 | 久久久五月婷婷 | 久久久久成人精品 | 国产精品久久久久久999 | 欧美天天干 | 中文字幕一区二 | 爱色婷婷 | www.激情五月.com | 国产中文在线视频 | 国产成人一区三区 | 最近高清中文字幕在线国语5 | 五月天丁香视频 | 日韩天天干 | 午夜精选视频 | 91精品国产三级a在线观看 | 亚洲精品99久久久久久 | 五月婷婷黄色网 | 久久久久久久久久久福利 | www.亚洲精品视频 | 久久精品黄 | 国产黄色免费 | 亚洲激情视频在线 | 亚洲精品视频免费在线观看 | av短片在线观看 | 一区二区三区中文字幕在线 | 在线亚洲免费视频 | 久久午夜羞羞影院 | 国产成本人视频在线观看 | 亚洲妇女av | 亚洲女同ⅹxx女同tv | 国产黄色视| 婷婷av综合 | 成人黄色小说网 | av大全在线免费观看 | 久久久91精品国产一区二区精品 | 欧美91精品久久久久国产性生爱 | 成人午夜在线观看 | 黄色三级在线观看 | www.99在线观看 | 欧美另类性 | 久久毛片网 | 手机色在线 | 天天色天天草天天射 | 久久成人午夜视频 | 中文字幕免费观看全部电影 | 亚洲欧美视频网站 | 欧美国产日韩一区二区三区 | 蜜臀久久99精品久久久酒店新书 | 久久久国产精品电影 | 精品久久久久久久久中文字幕 | 96超碰在线 | 丁香婷婷激情五月 | 精品国产一区二区三区久久影院 | 免费在线观看日韩 | 日本性生活一级片 | 美州a亚洲一视本频v色道 | 免费观看成人网 | 日韩一级黄色大片 | 久久激情五月丁香伊人 | 黄色av三级在线 | 国产a国产 | 久久人人爽人人爽人人片av免费 | 日本夜夜草视频网站 | 日韩天堂在线观看 | 日韩黄色在线观看 | 亚洲欧美精品一区 | 久久av网| 亚洲电影自拍 | 91丨九色丨91啦蝌蚪老版 | 黄色av成人在线观看 | 国产在线播放一区 | 亚洲国产大片 | 国产精品乱码一区二区视频 | 中国一区二区视频 | 成人全视频免费观看在线看 | 亚洲爱av | 免费观看成人网 | 久久久久久免费毛片精品 | 久久99网站 | 欧洲精品码一区二区三区免费看 | 天天射综合网视频 | 人人澡人人舔 | 国产原厂视频在线观看 | 97国产大学生情侣白嫩酒店 | 91cn国产在线 | 一区中文字幕在线观看 | 日本黄区免费视频观看 | 日韩在线观看av | 午夜国产在线观看 | 成人超碰在线 | 五月婷婷综合网 | 97网站| 欧美国产亚洲精品久久久8v | 网站在线观看你们懂的 | 综合天天 | 亚洲国产伊人 | 91桃色国产在线播放 | 日韩三区在线观看 | 国产九九九精品视频 | 日日夜夜天天久久 | 超碰99在线 | www.国产视频| 久久精品高清 | 久久久久国产成人免费精品免费 | 夜夜操天天 | 亚洲精选99| 日韩在线视频观看免费 | 日韩,精品电影 | 欧美 亚洲 另类 激情 另类 | 亚洲欧洲国产日韩精品 | 免费在线视频一区二区 | 黄色午夜网站 | 欧美色图88| 国产精品你懂的在线观看 | 在线中文字幕视频 | 一区在线播放 | 五月天激情综合 | www.eeuss影院av撸 | 91手机电视 | 日韩福利在线观看 | 一区二区三区在线影院 | 国产黄色片在线 | 久久福利在线 | 97精品国自产拍在线观看 | 亚洲欧美视频一区二区三区 | 成人超碰97| 国产精品18久久久久vr手机版特色 | 成人免费看黄 | 91九色视频在线播放 | 日韩精品中文字幕av | 中文字幕av在线不卡 | 国产色小视频 | 天天躁日日躁狠狠躁av麻豆 | 欧美精品v国产精品v日韩精品 | 国内久久精品视频 | 久久精品8 | av电影一区| 久久草草热国产精品直播 | 园产精品久久久久久久7电影 | 在线a亚洲视频播放在线观看 | 亚洲一区二区精品3399 | 全久久久久久久久久久电影 | www九九热| 欧美一区视频 | 久久久免费少妇 | 五月婷婷色丁香 | 粉嫩av一区二区三区入口 | 午夜婷婷综合 | 日色在线视频 | 日韩av电影中文字幕在线观看 | 国产成人在线网站 | 97影视| 99精品黄色 | 欧美a级一区二区 | 国产涩图 | 欧美二区视频 | 精品久久免费看 | 欧美精品在线一区二区 | 久久九九精品久久 | 国产精品99久久久精品 | 久草在线最新免费 | 很污的网站 | 国产精品乱码久久久久 | 亚洲成人精品国产 | 在线播放 日韩专区 | 超碰国产人人 | 久久亚洲私人国产精品 | 粉嫩一区二区三区粉嫩91 | 中文字幕在线观看一区二区三区 | 黄在线免费看 | 亚洲激情在线视频 | 91免费在线视频 | 午夜在线免费观看 | 国产美女视频免费观看的网站 | 国产乱码精品一区二区三区介绍 | www.婷婷com| 国产性xxxx | av片在线观看免费 | 最新av网站在线观看 | 激情久久综合 | 国产国产人免费人成免费视频 | 亚洲一区二区三区四区精品 | 97精产国品一二三产区在线 | 久久中文字幕视频 | 亚洲国产电影在线观看 | 欧美日韩另类在线观看 | 日韩三级中文字幕 | 久久久久久久久久久精 | 亚洲乱亚洲乱亚洲 | 久久国色夜色精品国产 | 精品视频在线免费观看 | 日韩精品一区二区三区视频播放 | а天堂中文最新一区二区三区 | 热久久在线视频 | 九色在线| 国产手机在线观看视频 | 全久久久久久久久久久电影 | 国产福利一区二区三区在线观看 | 青青草国产精品视频 | 一级黄色毛片 | 精品一区中文字幕 | 日本在线观看中文字幕无线观看 | 国产中文字幕av | 狠狠狠狠狠狠天天爱 | 亚洲午夜激情网 | av官网在线 | 亚州av网站大全 | 少妇视频在线播放 | 狠狠久久 | 亚洲国产成人在线播放 | 成人动态视频 | 国产精品一区一区三区 | 国产色区 | 国产精品九九久久99视频 | 日本韩国精品一区二区在线观看 | 麻豆91在线播放 | 亚洲美女视频在线 | 免费99精品国产自在在线 | 亚洲国产一区在线观看 | 天天射狠狠干 | 黄色网在线播放 | 精品国产乱码久久久久久1区2匹 | 免费a级观看 | 欧美日韩伦理一区 | 久久国产精品99久久久久久老狼 | 欧美日韩在线精品 | 欧美一区二区三区特黄 | 国产xx视频 | 免费欧美精品 | 亚洲精品久久激情国产片 | 在线精品观看国产 | 操碰av| 国产免费嫩草影院 | 成人久久久精品国产乱码一区二区 | 特级免费毛片 | 国产馆在线播放 | 激情久久五月 | 久久久久久久久久久久久影院 | 热久久免费视频 | 日韩av有码在线 | 9ⅰ精品久久久久久久久中文字幕 | 91av免费观看 | 国产黄色观看 | 97激情影院 | 国产剧情一区 | 国产中的精品av小宝探花 | 96亚洲精品久久 | 丁香婷婷综合网 | 91禁在线观看 | 国产精品久久久一区二区 | 国产精品视频久久久 | 久久久久日本精品一区二区三区 | 亚洲最新av网址 | 十八岁免进欧美 | 99精品视频免费看 | 在线播放视频一区 | av专区在线 | 夜夜操天天干, | 天天综合狠狠精品 | 日韩高清免费在线观看 | 欧美xxxxx在线视频 | 天堂中文在线播放 | 国产一级黄色免费看 | 成人黄色小说视频 | 久久天堂亚洲 | 久久免费精品视频 | 91丨九色丨91啦蝌蚪老版 | 国产精品一区二区免费在线观看 | av在线影片 | 丁香五婷 | 果冻av在线| 精品一区二区电影 | 亚洲精品在线一区二区三区 | 国色天香在线观看 | 看国产黄色片 | av色一区 | 亚洲午夜激情网 | 黄色网址a| 超级碰碰碰免费视频 | 成人av一二三区 | 天天艹日日干 | 亚洲精品乱码久久久一二三 | 91精品国自产在线观看 | 国产亚洲激情视频在线 | 精品久久久久一区二区国产 | 色视频网站免费观看 | 一区二区三区四区精品视频 | 色综合久久88色综合天天人守婷 | 成人午夜电影在线 | 日韩在线视频一区二区三区 | 国产最新精品视频 | 亚洲日韩欧美一区二区在线 | 亚洲电影一区二区 | av中文字幕在线免费观看 | 成年人黄色免费网站 | 91最新网址在线观看 | 亚洲特级毛片 | 免费国产ww | 美国人与动物xxxx | 92精品国产成人观看免费 | 中文字幕在线久一本久 | 国产又粗又硬又爽的视频 | 久草在线这里只有精品 | 98久9在线 | 免费 | 91成人欧美| 亚洲女裸体 | 亚洲精品国产自产拍在线观看 | 久久国产一区二区 | 久久精品99久久久久久2456 | 色婷婷国产在线 | 色综合久久88色综合天天人守婷 | 欧美精品久久久久久 | 狠狠色伊人亚洲综合成人 | 婷婷网五月天 | 亚洲精品乱码久久久久久高潮 | 中文字幕日本在线观看 | 天天插夜夜操 | 精品国产一区二区三区蜜臀 | 国产男男gay做爰 | 日韩性xxxx | 亚洲综合色视频在线观看 | 欧美一区二区三区在线看 | 日韩免费一二三区 | 精品国产一二区 | 国产高清中文字幕 | 久久综合五月天婷婷伊人 | 最近中文国产在线视频 | 中文字幕精品一区 | 日本一区二区三区视频在线播放 | www.干| 欧美日韩一级在线 | 人交video另类hd | 99久久网站 | 日韩有码在线观看视频 | 在线观看视频91 | 国产片免费在线观看视频 | 国产午夜三级一区二区三 | 精品亚洲一区二区 | 久久人91精品久久久久久不卡 | 久久av在线播放 | av免费在线免费观看 | 激情av一区二区 | 99热 精品在线 | 激情五月婷婷综合 | 精品在线观看一区二区三区 | 日韩精品在线看 | 国产精品综合在线观看 | 免费在线一区二区三区 | 97日日碰人人模人人澡分享吧 | 国产在线观看黄 | 久久精品视频播放 | 色噜噜日韩精品一区二区三区视频 | 成人av电影在线 | 色全色在线资源网 | 操操操com | 久久影院一区 | 日韩在线网址 | 99色婷婷| 超碰在线资源 | 国产精品毛片久久久 | 久久久久国产一区二区三区四区 | 九色在线视频 | 91丨九色丨勾搭 | 99精品在线视频播放 | 亚洲激精日韩激精欧美精品 | 日本三级中文字幕在线观看 | 激情五月开心 | 三三级黄色片之日韩 | 久久综合狠狠综合久久综合88 | 91麻豆免费视频 | 美女视频黄是免费的 | 亚洲精品高清视频 | 99国产一区二区三精品乱码 | 欧美日本中文字幕 | 亚洲国产wwwccc36天堂 | 激情综合网五月激情 | 国语黄色片 | 日韩在线观看视频在线 | 亚洲激精日韩激精欧美精品 | 玖玖在线视频观看 | 最近2019年日本中文免费字幕 | 3d黄动漫免费看 | 99精品视频在线免费观看 | 天天干天天操av | 成年人免费电影在线观看 | 国产自在线观看 | 91中文字幕永久在线 | 国产一级片免费播放 | 最新色站 | 国产美女在线观看 | 日韩av在线看| 81精品国产乱码久久久久久 | 在线 成人 | 亚州av成人 | 国产人免费人成免费视频 | 美女免费黄视频网站 | 久久手机在线视频 | 九色在线 | 狠狠操狠狠操 | 欧美一二在线 | 麻豆久久精品 | 九九视频这里只有精品 | 久久免费的精品国产v∧ | 亚洲mv大片欧洲mv大片免费 | 欧美日韩精品在线播放 | 午夜美女wwww | 91麻豆文化传媒在线观看 | 91精品国产一区二区在线观看 | 99性视频 | 精品视频在线观看 | 亚洲精品三级 | 国产福利资源 | 国产精品对白一区二区三区 | 草久在线视频 | 日本性生活一级片 | 亚洲视频大全 | 欧美日韩精品影院 | 偷拍精品一区二区三区 | 五月天天色 | 久久久久久久福利 | 成人av动漫在线 | 人人爽人人爽av | 九九久久久久99精品 | 亚洲综合在线五月天 | 日韩精品一区在线观看 | 欧美激情第一页xxx 午夜性福利 | 日韩在线国产精品 | 久久99热精品这里久久精品 | 亚洲毛片在线观看. | 中国一级片在线观看 | 国产高清在线免费 | 国产福利小视频在线 | 亚洲一区美女视频在线观看免费 | 亚洲成人av一区二区 | 欧美成人精品欧美一级乱 | 欧美一区在线观看视频 | 99福利片 | 超碰97国产精品人人cao | 97超碰在线视 | 亚洲精品乱码久久久久久9色 | 丁香花在线观看免费完整版视频 | 精品国产一区在线观看 | 狠狠色丁香婷婷综合最新地址 | 国内丰满少妇猛烈精品播放 | 91亚洲精品国偷拍自产在线观看 | 青青河边草免费视频 | 日韩欧美国产免费播放 | 亚洲欧美综合精品久久成人 | 日批网站在线观看 | 欧美色图另类 | 欧美va天堂va视频va在线 | av黄色一级片 | 欧美日韩一区二区三区在线观看视频 | 日韩欧美一区二区三区在线 | 久久99视频免费观看 | 成人香蕉视频 | 国产精品黄 | 亚洲精品www久久久久久 | 天天操人人要 | 91在线小视频 | 国产91精品久久久久 | 欧美日韩中文国产 | 国产精品情侣视频 | 国产精品av久久久久久无 | 亚洲一区二区高潮无套美女 | 久久精品屋 | 国产在线观看你懂得 | 亚洲国产中文在线观看 | av福利免费 | 成人黄色在线 | 日韩成人在线免费观看 | 午夜精品一区二区三区在线视频 | 久久综合久久久 | 亚洲成人黄色av | 亚洲精品视频网站在线观看 | av久久久|