Это касается 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 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 является универсальным типом!
|
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 methodpublic 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 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 с двойным), то пользователь вашего API обречен. И они не могут легко найти свою ошибку, так как никто в здравом уме не может прочитать сообщения об ошибках компилятора как это:
|
1
2
3
4
5
6
7
|
Test.java:58: incompatible typesfound : 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 .
Статьи по Теме :