Это касается jOOQ, где вы часто хотите использовать одно и то же имя метода для различных средств взаимодействия с библиотекой.
Пример: условия jOOQ
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
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); // [...] } |
Все эти методы связывают два условия друг с другом с помощью оператора «И». В идеале реализации зависят друг от друга, создавая единую точку отказа. Это делает вещи сухими :
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
package org.jooq.impl; abstract class AbstractCondition implements Condition { // The single point of failure @Override public final Condition and(Condition other) { return new CombinedCondition( Operator.AND, Arrays.asList( this , other)); } // "Convenience methods" delegating to the other one @Override public final Condition and(String sql) { return and(condition(sql)); } @Override public final Condition and(String sql, Object... bindings) { return and(condition(sql, bindings)); } } |
Беда с дженериками и перегрузками
При разработке с Eclipse мир Java 5 кажется более блестящим, чем он есть на самом деле. Varargs и дженерики были представлены как синтаксический сахар в Java 5. На самом деле они не существуют в JVM таким образом. Это означает, что компилятор должен правильно связывать вызовы методов, выводить типы при необходимости и создавать синтетические методы в некоторых случаях. Согласно JLS ( Спецификация языка Java ), существует много двусмысленности, когда varargs / generics используются в перегруженных методах.
Давайте рассмотрим дженерики:
Хорошая вещь, которую нужно сделать в jOOQ — это обрабатывать значения констант так же, как поля. Во многих местах аргументы поля перегружаются следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
// 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 следующим образом:
1
2
3
4
5
6
7
8
9
|
Field<Integer> field1 = //... Field<String> field2 = //... Field<Integer> result1 = myFunction(field1, 1 ); Field<String> result2 = myFunction(field2, "abc" ); |
Но проблема возникает, когда <T> связан с Object!
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
// 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> привязан к объекту, внезапно применяются оба метода, и, согласно JLS, ни один из них не является более конкретным! Хотя компилятор Eclipse обычно немного мягче (и в этом случае интуитивно связывает второй метод), компилятор javac не знает, что делать с этим вызовом. И нет никакого способа обойти это. Вы не можете привести field4 к Field или Field <Object>, чтобы заставить компоновщик ссылаться на второй метод. Это довольно плохие новости для дизайнера API.
Для получения более подробной информации об этом особом случае рассмотрим следующий вопрос переполнения стека, о котором я сообщил как об ошибке в Oracle и Eclipse. Давайте посмотрим, какая реализация компилятора верна:
http://stackoverflow.com/questions/5361513/reference-is-ambiguous-with-generics
Беда со статическим импортом, varargs
Varargs — еще одна замечательная функция, представленная в Java 5. Будучи просто синтаксическим сахаром, вы можете сохранить довольно много строк кода при передаче массивов в методы:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
// Method declarations with or without varargs public 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, conveniently String s3 = concat2( 1 , 2 , 3 ); |
Это хорошо известно. Он работает так же с массивами примитивного типа, как и с Object []. Это также работает с T [], где T является универсальным типом!
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
// 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 всегда может быть выведен как «Объект…». И вот как это вызывает проблемы, когда вы перегружаете такой API.
1
2
3
4
5
6
7
|
// Overloaded for "convenience". Let's ignore the compiler warning // caused when calling the second method public static <T> Field<T> myFunction(T... params); public static <T> Field<T> myFunction(Field<T>... params); |
Сначала это может выглядеть как хорошая идея. Список аргументов может быть либо постоянными значениями (T…), либо динамическими полями (Field…). В принципе, вы можете делать такие вещи:
01
02
03
04
05
06
07
08
09
10
11
|
// 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 )); |
Внутренние функции будут выводить Integer и Double для <T>. При несовместимых типах возвращаемых данных Field <Integer> и Field <Double> метод «предназначенный» с аргументом «Field <T>…» больше не применяется. Следовательно, метод один с «T…» связан компилятором как единственный применимый метод. Но вы не собираетесь угадывать (возможно) предполагаемое ограничение на <T>. Это возможные предполагаемые типы:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
// 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 do Field<? 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 с двойным), то пользователь вашего API обречен. И они не могут легко найти свою ошибку, так как никто в здравом уме не может прочитать сообщения об ошибках компилятора как это:
1
2
3
4
5
6
7
|
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)); |
Ссылка: осторожно перегружайте методы API от нашего партнера по JCG Лукаса Эдера из блога JAVA, SQL и AND JOOQ .
Статьи по Теме :