對于jOOQ就是這種情況,在這種情況下,您經常想使用與完全相同的方法名稱來與庫進行各種交互。
示例:jOOQ條件
package org.jooq;public interface Condition {// Various overloaded forms of the "AND" operation:Condition and(Condition other);Condition and(String sql);Condition and(String sql, Object... bindings);// [...]}
所有這些方法都使用“ AND”運算符將兩個條件相互關聯。 理想情況下,實現相互依賴,從而造成單點故障。 這會使事情變干 :
package org.jooq.impl;abstract class AbstractCondition implements Condition {// The single point of failure@Overridepublic final Condition and(Condition other) {return new CombinedCondition(Operator.AND, Arrays.asList(this, other));}// "Convenience methods" delegating to the other one@Overridepublic final Condition and(String sql) {return and(condition(sql));}@Overridepublic final Condition and(String sql, Object... bindings) {return and(condition(sql, bindings));}}
泛型和重載的麻煩
當使用Eclipse開發時,Java 5世界似乎比實際情況更加光彩照人。 Varargs和泛型作為Java 5中的語法糖引入。它們在JVM中并不是真的存在。 這意味著編譯器必須正確鏈接方法調用,在需要時推斷類型,并在某些情況下創建綜合方法。 根據JLS( Java語言規范 ),當在重載方法中使用varargs / generics時,存在很多歧義。
讓我們詳細介紹一下泛型:
在jOOQ中要做的一件好事是將常量值與字段一樣對待。 在許多地方,字段參數像這樣重載:
// This is a convenience method:public static <T> Field<T> myFunction(Field<T> field, T value) {return myFunction(field, val(value));}// It's equivalent to this one.public static <T> Field<T> myFunction(Field<T> field, Field<T> value) {return MyFunction<T>(field, value);}
在大多數情況下,上面的方法效果很好。 您可以像這樣使用上述API:
Field<Integer> field1 = //...Field<String> field2 = //...Field<Integer> result1 = myFunction(field1, 1);Field<String> result2 = myFunction(field2, "abc");
但是,當<T>綁定到對象時,就會出現麻煩!
// While this works...Field<Object> field3 = //...Field<Object> result3 = myFunction(field3, new Object());// ... this doesn't!Field<Object> field4 = //...Field<Object> result4 = myFunction(field4, field4);Field<Object> result4 = myFunction(field4, (Field) field4);Field<Object> result4 = myFunction(field4, (Field<Object>) field4);
當<T>綁定到Object時,兩種方法突然都適用,并且根據JLS,它們都不是更具體的! 盡管Eclipse編譯器通常比較寬容(并且在這種情況下直觀地鏈接了第二個方法),但是javac編譯器不知道該調用要做什么。 而且沒有辦法解決。 您不能將field4強制轉換為Field或Field <Object>強制鏈接器鏈接至第二種方法。 對于API設計人員來說,這是個壞消息。
有關此特殊情況的更多詳細信息,請考慮以下堆棧溢出問題,我將此問題報告給Oracle和Eclipse。 讓我們看看哪種編譯器實現是正確的:
http://stackoverflow.com/questions/5361513/reference-is-ambiguous-with-generics
靜態導入的麻煩,varargs
Varargs是Java 5中引入的另一個重要功能。盡管它只是語法糖,但在將數組傳遞給方法時可以節省很多代碼:
// Method declarations with or without varargspublic static String concat1(int[] values);public static String concat2(int... values);// The above methods are actually the same.String s1 = concat1(new int[] { 1, 2, 3 });String s2 = concat2(new int[] { 1, 2, 3 });// Only, concat2 can also be called like this, convenientlyString s3 = concat2(1, 2, 3);
那是眾所周知的。 它與原始類型數組的工作方式與與Object []相同。 它也可以與T []一起使用,其中T是泛型類型!
// You can now have a generic type in your varargs parameter:public static <T> T[] array(T... values);// The above can be called "type-safely" (with auto-boxing):Integer[] ints = array(1, 2, 3);String[] strings = array("1", "2", "3");// Since Object could also be inferred for T, you can even do this:Object[] applesAndOranges = array(1, "2", 3.0);
最后一個例子實際上已經暗示了這個問題。 如果T沒有任何上限,則類型安全性完全消失。 這是一種錯覺,因為最后,總是可以將varargs參數推斷為“ Object…”。 這就是當您重載此類API時這會引起麻煩的方式。
// Overloaded for "convenience". Let's ignore the compiler warning// caused when calling the second methodpublic static <T> Field<T> myFunction(T... params);public static <T> Field<T> myFunction(Field<T>... params);
起初,這看起來像個好主意。 參數列表可以是常量值(T…)或動態字段(Field…)。 因此,原則上,您可以執行以下操作:
// The outer function can infer Integer for <T> from the inner// functions, which can infer Integer for <T> from T...Field<Integer> f1 = myFunction(myFunction(1), myFunction(2, 3));// But beware, this will compile too!Field<?> f2 = myFunction(myFunction(1), myFunction(2.0, 3.0));
內部函數將推斷<T>的Integer和Double。 對于不兼容的返回類型Field <Integer>和Field <Double>,帶有“ Field <T> ...”參數的“打算”方法不再適用。 因此,編譯器將帶有“ T…”的方法一鏈接為唯一適用的方法。 但是您不會猜測<T>的(可能)推斷范圍。 這些是可能的推斷類型:
// This one, you can always do:Field<?> f2 = myFunction(myFunction(1), myFunction(2.0, 3.0));// But these ones show what you're actually about to doField<? extends Field<?>> f3 = // ...Field<? extends Field<? extends Number>> f4 = // ...Field<? extends Field<? extends Comparable<?>>> f5 = // ...Field<? extends Field<? extends Serializable>> f6 = // ...
編譯器可以推斷出Field <? 將Number&Comparable <?>和Serializable>擴展為<T>的有效上限。 但是,<T>沒有有效的精確界限。 因此,必要的<? 擴展[上限]>。
結論
將varargs參數與泛型結合使用時要特別小心,尤其是在重載方法中。 如果用戶將通用類型參數正確綁定到您想要的目標,則一切正常。 但是,如果有一個拼寫錯誤(例如,將Integer與Double混淆),那么您的API用戶就注定了。 而且他們不會輕易發現自己的錯誤,因為沒有人能讀懂這樣的編譯器錯誤消息:
Test.java:58: incompatible types
found : Test.Field<Test.Field<? extends java.lang.Number&java.lang.Comparable<? extends java.lang.Number&java.lang.Comparable<?>>>>
required: Test.Field<java.lang.Integer>Field<Integer> f2 = myFunction(myFunction(1),myFunction(2.0, 3.0));
參考:在JAVA,SQL和JOOQ博客上,我們的JCG合作伙伴 Lukas Eder 謹慎使用了重載API方法 。
相關文章 :
- Java中的數據庫架構導航
- ORM問題
- Java泛型快速教程
- 使用Spring和Java泛型簡化數據訪問層
翻譯自: https://www.javacodegeeks.com/2011/12/overload-api-methods-with-care.html