<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>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                ## Chapter 8. Methods(方法) ### Item 52: Use overloading judiciously(明智地使用重載) The following program is a well-intentioned attempt to classify collections according to whether they are sets, lists, or some other kind of collection: 下面的程序是一個善意的嘗試,根據一個 Collection 是 Set、List 還是其他的集合類型來進行分類: ``` // Broken! - What does this program print? public class CollectionClassifier { public static String classify(Set<?> s) { return "Set"; } public static String classify(List<?> lst) { return "List"; } public static String classify(Collection<?> c) { return "Unknown Collection"; } public static void main(String[] args) { Collection<?>[] collections = { new HashSet<String>(),new ArrayList<BigInteger>(),new HashMap<String, String>().values() }; for (Collection<?> c : collections) System.out.println(classify(c)); } } ``` You might expect this program to print Set, followed by List and Unknown Collection, but it doesn’t. It prints Unknown Collection three times. Why does this happen? Because the classify method is overloaded, and **the choice of which overloading to invoke is made at compile time.** For all three iterations of the loop, the compile-time type of the parameter is the same: `Collection<?>`. The runtime type is different in each iteration, but this does not affect the choice of overloading. Because the compile-time type of the parameter is `Collection<?>`, the only applicable overloading is the third one, `classify(Collection<?>)`, and this overloading is invoked in each iteration of the loop. 你可能期望這個程序打印 Set,然后是 List 和 Unknown Collection,但是它沒有這樣做。它打印 Unknown Collection 三次。為什么會這樣?因為 classify 方法被重載,并且 **在編譯時就決定了要調用哪個重載。** 對于循環的三個迭代過程,參數的編譯時類型是相同的:`Collection<?>`。運行時類型在每個迭代中是不同的,但這并不影響重載的選擇。因為參數的編譯時類型是 `Collection<?>`,唯一適用的重載是第三個,`classify(Collection<?>)`,這個重載在循環的每個迭代過程中都會調用。 The behavior of this program is counterintuitive because **selection among overloaded methods is static, while selection among overridden methods is dynamic.** The correct version of an overridden method is chosen at runtime, based on the runtime type of the object on which the method is invoked. As a reminder, a method is overridden when a subclass contains a method declaration with the same signature as a method declaration in an ancestor. If an instance method is overridden in a subclass and this method is invoked on an instance of the subclass, the subclass’s overriding method executes, regardless of the compile-time type of the subclass instance. To make this concrete, consider the following program: 這個程序的行為違反常規,因為 **重載方法的選擇是靜態的,而覆蓋法的選擇是動態的。** 在運行時根據調用方法的對象的運行時類型選擇覆蓋方法的正確版本。提醒一下,當子類包含與祖先中的方法聲明具有相同簽名的方法聲明時,方法將被覆蓋。如果在子類中覆蓋實例方法,并且在子類的實例上調用此方法,則執行子類的覆蓋方法,而不管子類實例的編譯時類型如何。為了更具體的說明,考慮以下程序: ``` class Wine { String name() { return "wine"; } } class SparklingWine extends Wine { @Override String name() { return "sparkling wine"; } } class Champagne extends SparklingWine { @Override String name() { return "champagne"; } } public class Overriding { public static void main(String[] args) { List<Wine> wineList = List.of(new Wine(), new SparklingWine(), new Champagne()); for (Wine wine : wineList) System.out.println(wine.name()); } } ``` The name method is declared in class Wine and overridden in subclasses SparklingWine and Champagne. As you would expect, this program prints out wine, sparkling wine, and champagne, even though the compiletime type of the instance is Wine in each iteration of the loop. The compile-time type of an object has no effect on which method is executed when an overridden method is invoked; the “most specific” overriding method always gets executed. Compare this to overloading, where the runtime type of an object has no effect on which overloading is executed; the selection is made at compile time, based entirely on the compile-time types of the parameters. name 方法在 Wine 類中聲明,并在 SparklingWine 和 Champagne 子類中重寫。正如你所期望的,這個程序打印出 wine、sparkling 和 champagne,即使實例的編譯時類型是循環每次迭代中的 wine。對象的編譯時類型對調用覆蓋方法時執行的方法沒有影響;「最特定的」覆蓋方法總是被執行。將此與重載進行比較,在重載中,對象的運行時類型對執行重載沒有影響;選擇是在編譯時進行的,完全基于參數的編譯時類型。 In the CollectionClassifier example, the intent of the program was to discern the type of the parameter by dispatching automatically to the appropriate method overloading based on the runtime type of the parameter, just as the name method did in the Wine example. Method overloading simply does not provide this functionality. Assuming a static method is required, the best way to fix the CollectionClassifier program is to replace all three overloadings of classify with a single method that does explicit instanceof tests: 在 CollectionClassifier 示例中,程序的目的是通過根據參數的運行時類型自動分派到適當的方法重載來識別參數的類型,就像 Wine 示例中的 name 方法所做的那樣。方法重載不提供此功能。假設需要一個靜態方法,修復 CollectionClassifier 程序的最佳方法是用一個執行顯式 instanceof 測試的方法替換 classification 的所有三個重載: ``` public static String classify(Collection<?> c) { return c instanceof Set ? "Set" :c instanceof List ? "List" : "Unknown Collection"; } ``` Because overriding is the norm and overloading is the exception, overriding sets people’s expectations for the behavior of method invocation. As demonstrated by the CollectionClassifier example, overloading can easily confound these expectations. It is bad practice to write code whose behavior is likely to confuse programmers. This is especially true for APIs. If the typical user of an API does not know which of several method overloadings will get invoked for a given set of parameters, use of the API is likely to result in errors. These errors will likely manifest themselves as erratic behavior at runtime, and many programmers will have a hard time diagnosing them. Therefore you should **avoid confusing uses of overloading.** 因為覆蓋是規范,而重載是例外,所以覆蓋滿足了人們對方法調用行為的期望。正如 CollectionClassifier 示例所示,重載很容易混淆這些期望。編寫可能使程序員感到困惑的代碼是不好的行為。對于 API 尤其如此。如果 API 的用戶不知道一組參數應該調用哪一種方法重載,那么使用 API 時很可能會導致錯誤。這些錯誤很可能在運行時表現為不穩定的行為,許多程序員將很難診斷它們。因此,**應該避免混淆重載的用法。** Exactly what constitutes a confusing use of overloading is open to some debate. **A safe, conservative policy is never to export two overloadings with the same number of parameters.** If a method uses varargs, a conservative policy is not to overload it at all, except as described in Item 53. If you adhere to these restrictions, programmers will never be in doubt as to which overloading applies to any set of actual parameters. These restrictions are not terribly onerous because **you can always give methods different names instead of overloading them.** 究竟是什么構成了混淆重載的用法還有待商榷。**安全、保守的策略是永遠不導出具有相同數量參數的兩個重載。** 如果一個方法使用了可變參數,保守策略是根本不重載它,除非如 [Item-53](/Chapter-8/Chapter-8-Item-53-Use-varargs-judiciously.md) 所述。如果遵守這些限制,程序員就不會懷疑一組參數應該調用哪一種方法重載。這些限制并不十分繁瑣,因為 **你總是可以為方法提供不同的名稱,而不是重載它們。** For example, consider the ObjectOutputStream class. It has a variant of its write method for every primitive type and for several reference types. Rather than overloading the write method, these variants all have different names, such as writeBoolean(boolean), writeInt(int), and writeLong(long). An added benefit of this naming pattern, when compared to overloading, is that it is possible to provide read methods with corresponding names, for example, readBoolean(), readInt(), and readLong(). The ObjectInputStream class does, in fact, provide such read methods. 例如,考慮 ObjectOutputStream 類。對于每個基本類型和幾種引用類型,其 write 方法都有變體。這些變體都有不同的名稱,而不是重載 write 方法,例如 `writeBoolean(boolean)`、`writeInt(int)` 和 `writeLong(long)`。與重載相比,這種命名模式的另一個好處是,可以為 read 方法提供相應的名稱,例如 `readBoolean()`、`readInt()` 和 `readLong()`。ObjectInputStream 類實際上也提供了這樣的讀方法。 For constructors, you don’t have the option of using different names: multiple constructors for a class are always overloaded. You do, in many cases, have the option of exporting static factories instead of constructors (Item 1). Also, with constructors you don’t have to worry about interactions between overloading and overriding, because constructors can’t be overridden. You will probably have occasion to export multiple constructors with the same number of parameters, so it pays to know how to do it safely. 對于構造函數,你沒有使用不同名稱的機會:一個類的多個構造函數只能重載。在很多情況下,你可以選擇導出靜態工廠而不是構造函數([Item-1](/Chapter-2/Chapter-2-Item-1-Consider-static-factory-methods-instead-of-constructors.md))。你可能會有機會導出具有相同數量參數的多個構造函數,因此知道如何安全地執行是有必要的。 Exporting multiple overloadings with the same number of parameters is unlikely to confuse programmers if it is always clear which overloading will apply to any given set of actual parameters. This is the case when at least one corresponding formal parameter in each pair of overloadings has a “radically different” type in the two overloadings. Two types are radically different if it is clearly impossible to cast any non-null expression to both types. Under these circumstances, which overloading applies to a given set of actual parameters is fully determined by the runtime types of the parameters and cannot be affected by their compile-time types, so a major source of confusion goes away. For example, ArrayList has one constructor that takes an int and a second constructor that takes a Collection. It is hard to imagine any confusion over which of these two constructors will be invoked under any circumstances. 如果總是清楚一組參數應該調用哪一種方法重載,那么用相同數量的參數導出多個重載不太可能讓程序員感到困惑。在這種情況下,每對重載中至少有一個對應的形式參數在這兩個重載中具有「完全不同的」類型。如果不可能將任何非空表達式強制轉換為這兩種類型,那么這兩種類型是完全不同的。在這些情況下,應用于給定實際參數集的重載完全由參數的運行時類型決定,且不受其編譯時類型的影響,因此消除了一個主要的混淆源。例如,ArrayList 有一個接受 int 的構造函數和第二個接受 Collection 的構造函數。很難想象在什么情況下會不知道這兩個構造函數中哪個會被調用。 Prior to Java 5, all primitive types were radically different from all reference types, but this is not true in the presence of autoboxing, and it has caused real trouble. Consider the following program: 在 Java 5 之前,所有原始類型都與所有引用類型完全不同,但在自動裝箱時并非如此,這造成了真正的麻煩。考慮以下方案: ``` public class SetList { public static void main(String[] args) { Set<Integer> set = new TreeSet<>(); List<Integer> list = new ArrayList<>(); for (int i = -3; i < 3; i++) { set.add(i); list.add(i); } for (int i = 0; i < 3; i++) { set.remove(i); list.remove(i); } System.out.println(set +""+list); } } ``` First, the program adds the integers from ?3 to 2, inclusive, to a sorted set and a list. Then, it makes three identical calls to remove on the set and the list. If you’re like most people, you’d expect the program to remove the non-negative values (0, 1, and 2) from the set and the list and to print [-3, -2, -1] [-3, -2, -1]. In fact, the program removes the non-negative values from the set and the odd values from the list and prints [-3, -2, -1] [-2, 0, 2]. It is an understatement to call this behavior confusing. 首先,程序將從 -3 到 2 的整數(包括)添加到已排序的 Set 和 List 中。然后,它執行三個相同的調用來刪除集合和列表。如果你和大多數人一樣,你希望程序從集合和列表中刪除非負值(0、1 和 2),并打印 `[-3,2,1][-3,2,1]`。實際上,程序從 Set 中刪除非負值,從 List 中刪除奇數值,并輸出 `[-3,2,1][-2,0,2]`。把這種行為稱為混亂,只是一種保守的說法。 Here’s what’s happening: The call to set.remove(i) selects the overloading remove(E), where E is the element type of the set (Integer), and autoboxes i from int to Integer. This is the behavior you’d expect, so the program ends up removing the positive values from the set. The call to list.remove(i), on the other hand, selects the overloading remove(int i), which removes the element at the specified position in the list. If you start with the list [-3, -2, -1, 0, 1, 2] and remove the zeroth element, then the first, and then the second, you’re left with [-2, 0, 2], and the mystery is solved. To fix the problem, cast list.remove’s argument to Integer, forcing the correct overloading to be selected. Alternatively, you could invoke Integer.valueOf on i and pass the result to list.remove. Either way, the program prints [-3, -2, -1] [-3, -2, -1], as expected: 實際情況如下:調用 `set.remove(i)` 選擇重載 `remove(E)`,其中 E 是 set (Integer)的元素類型,而將從 int 自動裝箱到 Integer 中。這是你期望的行為,因此程序最終會從 Set 中刪除正值。另一方面,對 `list.remove(i)` 的調用選擇重載 `remove(int i)`,它將刪除 List 中指定位置的元素。如果從 List `[-3,-2,-1,0,1,2]` 開始,移除第 0 個元素,然后是第 1 個,然后是第 2 個,就只剩下 `[-2,0,2]`,謎底就解開了。若要修復此問題,要將 `list.remove` 的參數轉換成 Integer,強制選擇正確的重載。或者,你可以調用 `Integer.valuef()`,然后將結果傳遞給 `list.remove`。無論哪種方式,程序都會按預期打印 `[-3, -2, -1] [-3, -2, -1]`: ``` for (int i = 0; i < 3; i++) { set.remove(i); list.remove((Integer) i); // or remove(Integer.valueOf(i)) } ``` The confusing behavior demonstrated by the previous example came about because the List<E> interface has two overloadings of the remove method: remove(E) and remove(int). Prior to Java 5 when the List interface was “generified,” it had a remove(Object) method in place of remove(E), and the corresponding parameter types, Object and int, were radically different. But in the presence of generics and autoboxing, the two parameter types are no longer radically different. In other words, adding generics and autoboxing to the language damaged the List interface. Luckily, few if any other APIs in the Java libraries were similarly damaged, but this tale makes it clear that autoboxing and generics increased the importance of caution when overloading. The addition of lambdas and method references in Java 8 further increased the potential for confusion in overloading. For example, consider these two snippets: 前一個示例所演示的令人困惑的行為是由于 List<E> 接口對 remove 方法有兩個重載:`remove(E)` 和 `remove(int)`。在 Java 5 之前,當 List 接口被「泛化」時,它有一個 `remove(Object)` 方法代替 `remove(E)`,而相應的參數類型 Object 和 int 則完全不同。但是,在泛型和自動裝箱的存在下,這兩種參數類型不再完全不同。換句話說,在語言中添加泛型和自動裝箱破壞了 List 接口。幸運的是,Java 庫中的其他 API 幾乎沒有受到類似的破壞,但是這個故事清楚地表明,自動裝箱和泛型出現后,在重載時就應更加謹慎。Java 8 中添加的 lambda 表達式和方法引用進一步增加了重載中混淆的可能性。例如,考慮以下兩個片段: ``` new Thread(System.out::println).start(); ExecutorService exec = Executors.newCachedThreadPool(); exec.submit(System.out::println); ``` While the Thread constructor invocation and the submit method invocation look similar, the former compiles while the latter does not. The arguments are identical (System.out::println), and both the constructor and the method have an overloading that takes a Runnable. What’s going on here? The surprising answer is that the submit method has an overloading that takes a `Callable<T>`, while the Thread constructor does not. You might think that this shouldn’t make any difference because all overloadings of println return void, so the method reference couldn’t possibly be a Callable. This makes perfect sense, but it’s not the way the overload resolution algorithm works. Perhaps equally surprising is that the submit method invocation would be legal if the println method weren’t also overloaded. It is the combination of the overloading of the referenced method (println) and the invoked method (submit) that prevents the overload resolution algorithm from behaving as you’d expect. 雖然 Thread 構造函數調用和 submit 方法調用看起來很相似,但是前者編譯而后者不編譯。參數是相同的 `System.out::println`,構造函數和方法都有一個重載,該重載接受 Runnable。這是怎么回事?令人驚訝的答案是,submit 方法有一個重載,它接受一個 `Callable<T>`,而線程構造函數沒有。你可能認為這不會有什么區別,因為 println 的所有重載都會返回 void,所以方法引用不可能是 Callable。這很有道理,但重載解析算法不是這樣工作的。也許同樣令人驚訝的是,如果 println 方法沒有被重載,那么 submit 方法調用將是合法的。正是被引用的方法 println 和被調用的方法 submit 的重載相結合,阻止了重載解析算法按照你所期望的那樣運行。 Technically speaking, the problem is that System.out::println is an inexact method reference [JLS, 15.13.1] and that “certain argument expressions that contain implicitly typed lambda expressions or inexact method references are ignored by the applicability tests, because their meaning cannot be determined until a target type is selected [JLS, 15.12.2].” Don’t worry if you don’t understand this passage; it is aimed at compiler writers. The key point is that overloading methods or constructors with different functional interfaces in the same argument position causes confusion. Therefore, **do not overload methods to take different functional interfaces in the same argument position.** In the parlance of this item, different functional interfaces are not radically different. The Java compiler will warn you about this sort of problematic overload if you pass the command line switch - Xlint:overloads. 從技術上講,問題出在 System.out::println 上,它是一個不準確的方法引用 [JLS, 15.13.1],并且「某些包含隱式類型化 lambda 表達式或不準確的方法引用的參數表達式會被適用性測試忽略,因為它們的含義在選擇目標類型之前無法確定 [JLS, 15.12.2]。」如果你不明白這段話,不要擔心;它的目標是編譯器編寫器。關鍵是在相同的參數位置上重載具有不同功能接口的方法或構造函數會導致混淆。因此,**不要重載方法來在相同的參數位置上使用不同的函數式接口。** 用本項目的話說,不同的函數式接口并沒有根本的不同。如果你通過命令行開關 `Xlint:overloads`, Java 編譯器將對這種有問題的重載發出警告。 Array types and class types other than Object are radically different. Also, array types and interface types other than Serializable and Cloneable are radically different. Two distinct classes are said to be unrelated if neither class is a descendant of the other [JLS, 5.5]. For example, String and Throwable are unrelated. It is impossible for any object to be an instance of two unrelated classes, so unrelated classes are radically different, too. 數組類型和 Object 以外的類類型是完全不同的。此外,數組類型和 Serializable 和 Cloneable 之外的接口類型也完全不同。如果兩個不同的類都不是另一個類的后代 [JLS, 5.5],則稱它們是不相關的。例如,String 和 Throwable 是不相關的。任何對象都不可能是兩個不相關類的實例,所以不相關的類也是完全不同的。 There are other pairs of types that can’t be converted in either direction [JLS, 5.1.12], but once you go beyond the simple cases described above, it becomes very difficult for most programmers to discern which, if any, overloading applies to a set of actual parameters. The rules that determine which overloading is selected are extremely complex and grow more complex with every release. Few programmers understand all of their subtleties. 還有其他成對的類不能在任何方向相互轉換 [JLS, 5.1.12],但是一旦超出上面描述的簡單情況,大多數程序員就很難辨別一組參數應該調用哪一種方法重載。決定選擇哪個重載的規則非常復雜,并且隨著每個版本的發布而變得越來越復雜。很少有程序員能理解它們所有的微妙之處。 There may be times when you feel the need to violate the guidelines in this item, especially when evolving existing classes. For example, consider String, which has had a contentEquals(StringBuffer) method since Java 4. In Java 5, CharSequence was added to provide a common interface for StringBuffer, StringBuilder, String, CharBuffer, and other similar types. At the same time that CharSequence was added, String was outfitted with an overloading of the contentEquals method that takes a CharSequence. 有時候,你可能覺得會被迫違反本條目中的指導原則,特別是在更新現有類時。例如,考慮 String,它從 Java 4 開始就有一個 `contentEquals(StringBuffer)` 方法。在 Java 5 中,添加了 CharSequence 來為 StringBuffer、StringBuilder、String、CharBuffer 和其他類似類型提供公共接口。在添加 CharSequence 的同時,String 還配備了一個重載的 contentEquals 方法,該方法接受 CharSequence。 While the resulting overloading clearly violates the guidelines in this item, it causes no harm because both overloaded methods do exactly the same thing when they are invoked on the same object reference. The programmer may not know which overloading will be invoked, but it is of no consequence so long as they behave identically. The standard way to ensure this behavior is to have the more specific overloading forward to the more general: 雖然這樣的重載明顯違反了此項中的指導原則,但它不會造成任何危害,因為當在同一個對象引用上調用這兩個重載方法時,它們做的是完全相同的事情。程序員可能不知道將調用哪個重載,但只要它們的行為相同,就沒有什么不良后果。確保這種行為的標準方法是將更具體的重載轉發給更一般的重載: ``` // Ensuring that 2 methods have identical behavior by forwarding public boolean contentEquals(StringBuffer sb) { return contentEquals((CharSequence) sb); } ``` While the Java libraries largely adhere to the spirit of the advice in this item, there are a number of classes that violate it. For example, String exports two overloaded static factory methods, valueOf(char[]) and valueOf(Object), that do completely different things when passed the same object reference. There is no real justification for this, and it should be regarded as an anomaly with the potential for real confusion. 雖然 Java 庫在很大程度上遵循了本條目中的主旨精神,但是有一些類違反了它。例如,String 導出兩個重載的靜態工廠方法 `valueOf(char[])` 和 `valueOf(Object)`,它們在傳遞相同的對象引用時執行完全不同的操作。這樣做沒有真正的理由,它應該被視為一種異常行為,有可能造成真正的混亂。 To summarize, just because you can overload methods doesn’t mean you should. It is generally best to refrain from overloading methods with multiple signatures that have the same number of parameters. In some cases, especially where constructors are involved, it may be impossible to follow this advice. In these cases, you should at least avoid situations where the same set of parameters can be passed to different overloadings by the addition of casts. If this cannot be avoided, for example, because you are retrofitting an existing class to implement a new interface, you should ensure that all overloadings behave identically when passed the same parameters. If you fail to do this, programmers will be hard pressed to make effective use of the overloaded method or constructor, and they won’t understand why it doesn’t work. 總而言之,方法可以重載,但并不意味著就應該這樣做。通常,最好避免重載具有相同數量參數的多個簽名的方法。在某些情況下,特別是涉及構造函數的情況下,可能難以遵循這個建議。在這些情況下,你至少應該避免同一組參數只需經過類型轉換就可以被傳遞給不同的重載方法。如果這是無法避免的,例如,因為要對現有類進行改造以實現新接口,那么應該確保在傳遞相同的參數時,所有重載的行為都是相同的。如果你做不到這一點,程序員將很難有效地使用重載方法或構造函數,他們將無法理解為什么它不能工作。 --- **[Back to contents of the chapter(返回章節目錄)](/Chapter-8/Chapter-8-Introduction.md)** - **Previous Item(上一條目):[Item 51: Design method signatures carefully(仔細設計方法簽名)](/Chapter-8/Chapter-8-Item-51-Design-method-signatures-carefully.md)** - **Next Item(下一條目):[Item 53: Use varargs judiciously(明智地使用可變參數)](/Chapter-8/Chapter-8-Item-53-Use-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>

                              哎呀哎呀视频在线观看