<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                ## Chapter 5. Generics(泛型) ### Item 31: Use bounded wildcards to increase API flexibility(使用有界通配符增加 API 的靈活性) As noted in Item 28, parameterized types are invariant. In other words, for any two distinct types `Type1` and `Type2`, `List<Type1>` is neither a subtype nor a supertype of `List<Type2>`. Although it is counterintuitive that `List<String>` is not a subtype of `List<Object>`, it really does make sense. You can put any object into a `List<Object>`, but you can put only strings into a `List<String>`. Since a `List<String>` can’t do everything a `List<Object>` can, it isn’t a subtype (by the Liskov substitution principal, Item 10). 如 [Item-28](/Chapter-5/Chapter-5-Item-28-Prefer-lists-to-arrays.md) 所示,參數化類型是不可變的。換句話說,對于任意兩種不同類型 `Type1` 和 `Type2`,`List<Type1>` 既不是 `List<Type2>` 的子類型,也不是它的父類。雖然 `List<String>` 不是 `List<Object>` 的子類型,這和習慣的直覺不符,但它確實有意義。你可以將任何對象放入 `List<Object>`,但只能將字符串放入 `List<String>`。因為 `List<String>` 不能做 `List<Object>` 能做的所有事情,所以它不是子類型(可通過 Liskov 替換原則來理解這一點,[Item-10](/Chapter-3/Chapter-3-Item-10-Obey-the-general-contract-when-overriding-equals.md))。 **譯注:里氏替換原則(Liskov Substitution Principle,LSP)面向對象設計的基本原則之一。里氏替換原則指出:任何父類可以出現的地方,子類一定可以出現。LSP 是繼承復用的基石,只有當衍生類可以替換掉父類,軟件單位的功能不受到影響時,父類才能真正被復用,而衍生類也能夠在父類的基礎上增加新的行為。** Sometimes you need more flexibility than invariant typing can provide. Consider the Stack class from Item 29. To refresh your memory, here is its public API: 有時你需要獲得比不可變類型更多的靈活性。考慮 [Item-29](/Chapter-5/Chapter-5-Item-29-Favor-generic-types.md) 中的堆棧類。以下是它的公共 API: ``` public class Stack<E> { public Stack(); public void push(E e); public E pop(); public boolean isEmpty(); } ``` Suppose we want to add a method that takes a sequence of elements and pushes them all onto the stack. Here’s a first attempt: 假設我們想添加一個方法,該方法接受一系列元素并將它們全部推入堆棧。這是第一次嘗試: ``` // pushAll method without wildcard type - deficient! public void pushAll(Iterable<E> src) { for (E e : src) push(e); } ``` This method compiles cleanly, but it isn’t entirely satisfactory. If the element type of the `Iterable src` exactly matches that of the stack, it works fine. But suppose you have a `Stack<Number>` and you invoke `push(intVal)`, where `intVal` is of type `Integer`. This works because `Integer` is a subtype of `Number`. So logically, it seems that this should work, too: 該方法能夠正確編譯,但并不完全令人滿意。如果 `Iterable src` 的元素類型與堆棧的元素類型完全匹配,那么它正常工作。但是假設你有一個 `Stack<Number>`,并且調用 `push(intVal)`,其中 `intVal` 的類型是 `Integer`。這是可行的,因為 `Integer` 是 `Number` 的子類型。因此,從邏輯上講,這似乎也應該奏效: ``` Stack<Number> numberStack = new Stack<>(); Iterable<Integer> integers = ... ; numberStack.pushAll(integers); ``` If you try it, however, you’ll get this error message because parameterized types are invariant: 但是,如果你嘗試一下,將會得到這個錯誤消息,因為參數化類型是不可變的: ``` StackTest.java:7: error: incompatible types: Iterable<Integer> cannot be converted to Iterable<Number> numberStack.pushAll(integers); ^ ``` Luckily, there’s a way out. The language provides a special kind of parameterized type call a bounded wildcard type to deal with situations like this. The type of the input parameter to pushAll should not be “Iterable of E” but “Iterable of some subtype of E,” and there is a wildcard type that means precisely that: Iterable<? extends E>. (The use of the keyword extends is slightly misleading: recall from Item 29 that subtype is defined so that every type is a subtype of itself, even though it does not extend itself.) Let’s modify pushAll to use this type: 幸運的是,有一種解決方法。Java 提供了一種特殊的參數化類型,`有界通配符類型`來處理這種情況。pushAll 的輸入參數的類型不應該是「E 的 Iterable 接口」,而應該是「E 的某個子類型的 Iterable 接口」,并且有一個通配符類型,它的確切含義是:`Iterable<? extends E>`(關鍵字 extends 的使用稍微有些誤導:回想一下 [Item-29](/Chapter-5/Chapter-5-Item-29-Favor-generic-types.md),定義了子類型,以便每個類型都是其本身的子類型,即使它沒有擴展自己。)讓我們修改 pushAll 來使用這種類型: ``` // Wildcard type for a parameter that serves as an E producer public void pushAll(Iterable<? extends E> src) { for (E e : src) push(e); } ``` With this change, not only does Stack compile cleanly, but so does the client code that wouldn’t compile with the original pushAll declaration. Because Stack and its client compile cleanly, you know that everything is typesafe. Now suppose you want to write a popAll method to go with pushAll. The popAll method pops each element off the stack and adds the elements to the given collection. Here’s how a first attempt at writing the popAll method might look: 更改之后,不僅 Stack 可以正確編譯,而且不能用原始 pushAll 聲明編譯的客戶端代碼也可以正確編譯。因為 Stack 和它的客戶端可以正確編譯,所以你知道所有東西都是類型安全的。現在假設你想編寫一個與 pushAll 一起使用的 popAll 方法。popAll 方法將每個元素從堆棧中彈出,并將這些元素添加到給定的集合中。下面是編寫 popAll 方法的第一次嘗試: ``` // popAll method without wildcard type - deficient! public void popAll(Collection<E> dst) { while (!isEmpty()) dst.add(pop()); } ``` Again, this compiles cleanly and works fine if the element type of the destination collection exactly matches that of the stack. But again, it isn’t entirely satisfactory. Suppose you have a `Stack<Number>` and variable of type Object. If you pop an element from the stack and store it in the variable, it compiles and runs without error. So shouldn’t you be able to do this, too? 同樣,如果目標集合的元素類型與堆棧的元素類型完全匹配,那么這種方法可以很好地編譯。但這也不是完全令人滿意。假設你有一個 `Stack<Number>` 和 Object 類型的變量。如果從堆棧中取出一個元素并將其存儲在變量中,那么它將編譯并運行,不會出錯。所以你不能也這樣做嗎? ``` Stack<Number> numberStack = new Stack<Number>(); Collection<Object> objects = ... ; numberStack.popAll(objects); ``` If you try to compile this client code against the version of popAll shown earlier, you’ll get an error very similar to the one that we got with our first version of pushAll: `Collection<Object>` is not a subtype of `Collection<Number>`. Once again, wildcard types provide a way out. The type of the input parameter to popAll should not be “collection of E” but “collection of some supertype of E” (where supertype is defined such that E is a supertype of itself [JLS, 4.10]). Again, there is a wildcard type that means precisely that: Collection<? super E>. Let’s modify popAll to use it: 如果你嘗試根據前面顯示的 popAll 版本編譯此客戶端代碼,你將得到一個與第一個版本的 pushAll 非常相似的錯誤:`Collection<Object>`不是 `Collection<Number>` 的子類型。同樣,通配符類型提供解決方法。popAll 的輸入參數的類型不應該是「E 的集合」,而應該是「E 的某個超類型的集合」(其中的超類型定義為 E 本身是一個超類型[JLS, 4.10])。同樣,有一個通配符類型,它的確切含義是:`Collection<? super E>`。讓我們修改 popAll 來使用它: ``` // Wildcard type for parameter that serves as an E consumer public void popAll(Collection<? super E> dst) { while (!isEmpty()) dst.add(pop()); } ``` With this change, both Stack and the client code compile cleanly. 通過此更改,Stack 類和客戶端代碼都可以正確編譯。 The lesson is clear. **For maximum flexibility, use wildcard types on input parameters that represent producers or consumers.** If an input parameter is both a producer and a consumer, then wildcard types will do you no good: you need an exact type match, which is what you get without any wildcards. Here is a mnemonic to help you remember which wildcard type to use: 教訓是清楚的。為了獲得最大的靈活性,應在表示生產者或消費者的輸入參數上使用通配符類型。如果輸入參數既是生產者又是消費者,那么通配符類型對你沒有任何好處:你需要一個精確的類型匹配,這就是在沒有通配符的情況下得到的結果。這里有一個助記符幫助你記住使用哪種通配符類型: **PECS stands for producer-extends, consumer-super.** PECS 表示生產者應使用 extends,消費者應使用 super。 In other words, if a parameterized type represents a T producer, use `<? extends T>`; if it represents a T consumer, use `<? super T>`. In our Stack example, pushAll’s src parameter produces E instances for use by the Stack, so the appropriate type for src is `Iterable<? extends E>`; popAll’s dst parameter consumes E instances from the Stack, so the appropriate type for dst is `Collection<? super E>`. The PECS mnemonic captures the fundamental principle that guides the use of wild-card types. Naftalin and Wadler call it the Get and Put Principle [Naftalin07, 2.4]. 換句話說,如果參數化類型表示 T 生成器,則使用 `<? extends T>`;如果它表示一個 T 消費者,則使用 `<? super T>`。在我們的 Stack 示例中,pushAll 的 src 參數生成 E 的實例供 Stack 使用,因此 src 的適當類型是 `Iterable<? extends E>`;popAll 的 dst 參數使用 Stack 中的 E 實例,因此適合 dst 的類型是 `Collection<? super E>`。PECS 助記符捕獲了指導通配符類型使用的基本原則。Naftalin 和 Wadler 稱之為 Get and Put 原則[Naftalin07, 2.4]。 With this mnemonic in mind, let’s take a look at some method and constructor declarations from previous items in this chapter. The Chooser constructor in Item 28 has this declaration: 記住這個助記符后,再讓我們看一看本章前面提及的一些方法和構造函數聲明。[Item-28](/Chapter-5/Chapter-5-Item-28-Prefer-lists-to-arrays.md) 中的 Chooser 構造函數有如下聲明: ``` public Chooser(Collection<T> choices) ``` This constructor uses the collection choices only to produce values of type T (and stores them for later use), so its declaration should use a wildcard type that **extends T.** Here’s the resulting constructor declaration: 這個構造函數只使用集合選項來生成類型 T 的值(并存儲它們以供以后使用),因此它的聲明應該使用擴展 T 的通配符類型 **extends T**。下面是生成的構造函數聲明: ``` // Wildcard type for parameter that serves as an T producer public Chooser(Collection<? extends T> choices) ``` And would this change make any difference in practice? Yes, it would. Suppose you have a `List<Integer>`, and you want to pass it in to the constructor for a Chooser<Number>. This would not compile with the original declaration, but it does once you add the bounded wildcard type to the declaration. 這種改變在實踐中會有什么不同嗎?是的,它會。假設你有一個 `List<Integer>`,并且希望將其傳遞給 `Chooser<Number>` 的構造函數。這不會與原始聲明一起編譯,但是一旦你將有界通配符類型添加到聲明中,它就會編譯。 Now let’s look at the union method from Item 30. Here is the declaration: 現在讓我們看看 [Item-30](/Chapter-5/Chapter-5-Item-30-Favor-generic-methods.md) 中的 union 方法。以下是聲明: ``` public static <E> Set<E> union(Set<E> s1, Set<E> s2) ``` Both parameters, s1 and s2, are E producers, so the PECS mnemonic tells us that the declaration should be as follows: 參數 s1 和 s2 都是 E 的生產者,因此 PECS 助記符告訴我們聲明應該如下: ``` public static <E> Set<E> union(Set<? extends E> s1,Set<? extends E> s2) ``` Note that the return type is still `Set<E>`. **Do not use bounded wildcard types as return types.** Rather than providing additional flexibility for your users, it would force them to use wildcard types in client code. With the revised declaration, this code will compile cleanly: 注意,返回類型仍然設置為 `Set<E>`。**不要使用有界通配符類型作為返回類型。** 它將強制用戶在客戶端代碼中使用通配符類型,而不是為用戶提供額外的靈活性。經修訂后的聲明可正確編譯以下代碼: ``` Set<Integer> integers = Set.of(1, 3, 5); Set<Double> doubles = Set.of(2.0, 4.0, 6.0); Set<Number> numbers = union(integers, doubles); ``` Properly used, wildcard types are nearly invisible to the users of a class. They cause methods to accept the parameters they should accept and reject those they should reject. **If the user of a class has to think about wildcard types, there is probably something wrong with its API.** 如果使用得當,通配符類型對于類的用戶幾乎是不可見的。它們讓方法接受它們應該接受的參數,拒絕應該拒絕的參數。**如果類的用戶必須考慮通配符類型,那么它的 API 可能有問題。** Prior to Java 8, the type inference rules were not clever enough to handle the previous code fragment, which requires the compiler to use the contextually specified return type (or target type) to infer the type of E. The target type of the union invocation shown earlier is `Set<Number>`. If you try to compile the fragment in an earlier version of Java (with an appropriate replacement for the Set.of factory), you’ll get a long, convoluted error message like this: 在 Java 8 之前,類型推斷規則還不足以處理前面的代碼片段,這要求編譯器使用上下文指定的返回類型(或目標類型)來推斷 E 的類型。前面顯示的 union 調用的目標類型設置為 `Set<Number>` 如果你嘗試在 Java 的早期版本中編譯該片段(使用 Set.of factory 的適當替代),你將得到一條長而復雜的錯誤消息,如下所示: ``` Union.java:14: error: incompatible types Set<Number> numbers = union(integers, doubles); ^ required: Set<Number> found: Set<INT#1> where INT#1,INT#2 are intersection types: INT#1 extends Number,Comparable<? extends INT#2> INT#2 extends Number,Comparable<?> ``` Luckily there is a way to deal with this sort of error. If the compiler doesn’t infer the correct type, you can always tell it what type to use with an explicit type argument [JLS, 15.12]. Even prior to the introduction of target typing in Java 8, this isn’t something that you had to do often, which is good because explicit type arguments aren’t very pretty. With the addition of an explicit type argument, as shown here, the code fragment compiles cleanly in versions prior to Java 8: 幸運的是,有一種方法可以處理這種錯誤。如果編譯器沒有推斷出正確的類型,你總是可以告訴它使用顯式類型參數[JLS, 15.12]使用什么類型。即使在 Java 8 中引入目標類型之前,這也不是必須經常做的事情,這很好,因為顯式類型參數不是很漂亮。通過添加顯式類型參數,如下所示,代碼片段可以在 Java 8 之前的版本中正確編譯: ``` // Explicit type parameter - required prior to Java 8 Set<Number> numbers = Union.<Number>union(integers, doubles); ``` Next let’s turn our attention to the max method in Item 30. Here is the original declaration: 接下來讓我們將注意力轉到 [Item-30](/Chapter-5/Chapter-5-Item-30-Favor-generic-methods.md) 中的 max 方法。以下是原始聲明: ``` public static <T extends Comparable<T>> T max(List<T> list) ``` Here is a revised declaration that uses wildcard types: 下面是使用通配符類型的修正聲明: ``` public static <T extends Comparable<? super T>> T max(List<? extends T> list) ``` To get the revised declaration from the original, we applied the PECS heuristic twice. The straightforward application is to the parameter list. It produces T instances, so we change the type from `List<T>` to `List<? extends T>`. The tricky application is to the type parameter T. This is the first time we’ve seen a wildcard applied to a type parameter. Originally, T was specified to extend `Comparable<T>`, but a comparable of T consumes T instances (and produces integers indicating order relations). Therefore, the parameterized type `Comparable<T>` is replaced by the bounded wildcard type `Comparable<? super T>`. Comparables are always consumers, so you should generally **use `Comparable<? super T>` in preference to `Comparable<T>`.** The same is true of comparators; therefore, you should generally **use `Comparator<? super T>` in preference to `Comparator<T>`.** 為了從原始聲明中得到修改后的聲明,我們兩次應用了 PECS 啟發式。直接的應用程序是參數列表。它生成 T 的實例,所以我們將類型從 `List<T>` 更改為 `List<? extends T>`。復雜的應用是類型參數 T。這是我們第一次看到通配符應用于類型參數。最初,T 被指定為擴展 `Comparable<T>`,但是 T 的 Comparable 消費 T 實例(并生成指示順序關系的整數)。因此,將參數化類型 `Comparable<T>` 替換為有界通配符類型 `Comparable<? super T>`,Comparables 始終是消費者,所以一般應**優先使用 `Comparable<? super T>` 而不是 `Comparable<T>`**,比較器也是如此;因此,通常應該**優先使用 `Comparator<? super T>` 而不是 `Comparator<T>`。** The revised max declaration is probably the most complex method declaration in this book. Does the added complexity really buy you anything? Again, it does. Here is a simple example of a list that would be excluded by the original declaration but is permitted by the revised one: 修訂后的 max 聲明可能是本書中最復雜的方法聲明。增加的復雜性真的能給你帶來什么好處嗎?是的,它再次生效。下面是一個簡單的列表案例,它在原來的聲明中不允許使用,但經訂正的聲明允許: ``` List<ScheduledFuture<?>> scheduledFutures = ... ; ``` The reason that you can’t apply the original method declaration to this list is that ScheduledFuture does not implement `Comparable<ScheduledFuture>`. Instead, it is a subinterface of Delayed, which extends `Comparable<Delayed>`. In other words, a ScheduledFuture instance isn’t merely comparable to other ScheduledFuture instances; it is comparable to any Delayed instance, and that’s enough to cause the original declaration to reject it. More generally, the wildcard is required to support types that do not implement Comparable (or Comparator) directly but extend a type that does. 不能將原始方法聲明應用于此列表的原因是 ScheduledFuture 沒有實現 `Comparable<ScheduledFuture>`。相反,它是 Delayed 的一個子接口,擴展了 `Comparable<Delayed>`。換句話說,ScheduledFuture 的實例不僅僅可以與其他 ScheduledFuture 實例進行比較;它可以與任何 Delayed 實例相比較,這足以導致初始聲明時被拒絕。更通俗來說,通配符用于支持不直接實現 Comparable(或 Comparator)但擴展了實現 Comparable(或 Comparator)的類型的類型。 There is one more wildcard-related topic that bears discussing. There is a duality between type parameters and wildcards, and many methods can be declared using one or the other. For example, here are two possible declarations for a static method to swap two indexed items in a list. The first uses an unbounded type parameter (Item 30) and the second an unbounded wildcard: 還有一個與通配符相關的主題值得討論。類型參數和通配符之間存在對偶性,可以使用其中一種方法聲明許多方法。例如,下面是靜態方法的兩種可能聲明,用于交換列表中的兩個索引項。第一個使用無界類型參數([Item-30](/Chapter-5/Chapter-5-Item-30-Favor-generic-methods.md)),第二個使用無界通配符: ``` // Two possible declarations for the swap method public static <E> void swap(List<E> list, int i, int j); public static void swap(List<?> list, int i, int j); ``` Which of these two declarations is preferable, and why? In a public API, the second is better because it’s simpler. You pass in a list—any list—and the method swaps the indexed elements. There is no type parameter to worry about. As a rule, **if a type parameter appears only once in a method declaration, replace it with a wildcard.** If it’s an unbounded type parameter, replace it with an unbounded wildcard; if it’s a bounded type parameter, replace it with a bounded wildcard. 這兩個聲明中哪個更好,為什么?在公共 API 中第二個更好,因為它更簡單。傳入一個列表(任意列表),該方法交換索引元素。不需要擔心類型參數。通常,如果類型參數在方法聲明中只出現一次,則用通配符替換它。**如果它是一個無界類型參數,用一個無界通配符替換它;** 如果它是有界類型參數,則用有界通配符替換它。 There’s one problem with the second declaration for swap. The straightforward implementation won’t compile: 交換的第二個聲明有一個問題。這個簡單的實現無法編譯: ``` public static void swap(List<?> list, int i, int j) { list.set(i, list.set(j, list.get(i))); } ``` Trying to compile it produces this less-than-helpful error message: 試圖編譯它會產生一個不太有用的錯誤消息: ``` Swap.java:5: error: incompatible types: Object cannot be converted to CAP#1 list.set(i, list.set(j, list.get(i))); ^ where CAP#1 is a fresh type-variable: CAP#1 extends Object from capture of ? ``` It doesn’t seem right that we can’t put an element back into the list that we just took it out of. The problem is that the type of list is `List<?>`, and you can’t put any value except null into a `List<?>`. Fortunately, there is a way to implement this method without resorting to an unsafe cast or a raw type. The idea is to write a private helper method to capture the wildcard type. The helper method must be a generic method in order to capture the type. Here’s how it looks: 我們不能把一個元素放回剛剛取出的列表中,這看起來是不正確的。問題是 list 的類型是 `List<?>`,你不能在 `List<?>` 中放入除 null 以外的任何值。幸運的是,有一種方法可以實現,而無需求助于不安全的強制轉換或原始類型。其思想是編寫一個私有助手方法來捕獲通配符類型。為了捕獲類型,helper 方法必須是泛型方法。它看起來是這樣的: ``` public static void swap(List<?> list, int i, int j) { swapHelper(list, i, j); } // Private helper method for wildcard capture private static <E> void swapHelper(List<E> list, int i, int j) { list.set(i, list.set(j, list.get(i))); } ``` The swapHelper method knows that list is a `List<E>`. Therefore, it knows that any value it gets out of this list is of type E and that it’s safe to put any value of type E into the list. This slightly convoluted implementation of swap compiles cleanly. It allows us to export the nice wildcard-based declaration, while taking advantage of the more complex generic method internally. Clients of the swap method don’t have to confront the more complex swapHelper declaration, but they do benefit from it. It is worth noting that the helper method has precisely the signature that we dismissed as too complex for the public method. swapHelper 方法知道 list 是一個 `List<E>`。因此,它知道它從這個列表中得到的任何值都是 E 類型的,并且將 E 類型的任何值放入這個列表中都是安全的。這個稍微復雜的實現能夠正確編譯。它允許我們導出基于 通配符的聲明,同時在內部利用更復雜的泛型方法。swap 方法的客戶端不必面對更復雜的 swapHelper 聲明,但它們確實從中受益。值得注意的是,helper 方法具有我們認為對于公共方法過于復雜而忽略的簽名。 In summary, using wildcard types in your APIs, while tricky, makes the APIs far more flexible. If you write a library that will be widely used, the proper use of wildcard types should be considered mandatory. Remember the basic rule: producer-extends, consumer-super (PECS). Also remember that all comparables and comparators are consumers. 總之,在 API 中使用通配符類型雖然很棘手,但可以使其更加靈活。如果你編寫的庫將被廣泛使用,則必須考慮通配符類型的正確使用。記住基本規則:生產者使用 extends,消費者使用 super(PECS)。還要記住,所有的 comparable 和 comparator 都是消費者。 --- **[Back to contents of the chapter(返回章節目錄)](/Chapter-5/Chapter-5-Introduction.md)** - **Previous Item(上一條目):[Item 30: Favor generic methods(優先使用泛型方法)](/Chapter-5/Chapter-5-Item-30-Favor-generic-methods.md)** - **Next Item(下一條目):[Item 32: Combine generics and varargs judiciously(明智地合用泛型和可變參數)](/Chapter-5/Chapter-5-Item-32-Combine-generics-and-varargs-judiciously.md)**
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看