Статьи

Эта общая методика API на самом деле является анти-паттерном

Признаюсь, нас тоже заманили в эту технику. Это так удобно, так как позволяет избежать, казалось бы, ненужного броска. Это следующая техника здесь:

1
2
3
interface SomeWrapper {
  <T> T get();
}

Теперь вы можете безопасно набирать что-либо из оболочки для любого типа:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
SomeWrapper wrapper = ...
 
// Obviously
Object a = wrapper.get();
 
// Well...
Number b = wrapper.get();
 
// Risky
String[][] c = wrapper.get();
 
// Unprobable
javax.persistence.SqlResultSetMapping d =
    wrapper.get();

На самом деле это API, который вы можете использовать, когда используете jOOR, нашу библиотеку отражений, которую мы написали и открыли для улучшения наших интеграционных тестов. С помощью jOOR вы можете писать такие вещи, как:

01
02
03
04
05
06
07
08
09
10
Employee[] employees = on(department)
    .call("getEmployees").get();
  
for (Employee employee : employees) {
    Street street = on(employee)
        .call("getAddress")
        .call("getStreet")
        .get();
    System.out.println(street);
}

API довольно прост. Метод on() оборачивает Object или Class . Затем методы call() вызывают метод для этого объекта, используя отражение (но не требуя точных подписей, не требуя, чтобы метод был общедоступным, и не вызывая никаких проверенных исключений). И без необходимости приведения вы можете затем вызвать get() чтобы присвоить результат любому произвольному ссылочному типу.

Это, вероятно, нормально с библиотекой отражений, такой как jOOR, потому что вся библиотека не безопасна для типов Этого не может быть, потому что это отражение.

Но «грязное» чувство остается. Чувство предоставления сайту вызова обещания относительно результирующего типа, обещание, которое не может быть выполнено, и которое приведет к ClassCastExceptionClassCastException прошлого, которое мало знают младшие разработчики, которые начали после Java 5 и дженериков.

Но библиотеки JDK также делают это …

Да, они делают. Но очень редко и только в том случае, если параметр общего типа действительно не имеет значения. Например, при получении Collection.emptyList() , реализация которого выглядит следующим образом:

1
2
3
4
@SuppressWarnings("unchecked")
public static final <T> List<T> emptyList() {
    return (List<T>) EMPTY_LIST;
}

Это правда, что EMPTY_LIST приведен из List в List<T> , но с семантической точки зрения это безопасное приведение. Вы не можете изменить эту ссылку List , и поскольку она пуста, в List<T> нет метода, который бы когда-либо давал вам экземпляр T или T[] , который не соответствует вашему целевому типу. Итак, все они действительны:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
// perfectly fine
List<?> a = emptyList();
 
// yep
List<Object> b = emptyList();
 
// alright
List<Number> c = emptyList();
 
// no problem
List<String[][]> d = emptyList();
 
// if you must
List<javax.persistence.SqlResultSetMapping> e
    = emptyList();

Таким образом, как всегда ( или в основном ), разработчики библиотеки JDK позаботились о том, чтобы не давать ложных обещаний относительно общего типа, который вы можете получить. Это означает, что вы часто будете получать тип объекта, если вы знаете, что другой тип будет более подходящим.

Но даже если вы знаете это, компилятор не будет. Erasure поставляется по цене, и цена выплачивается, когда ваша обертка или коллекция пуста. Нет способа узнать тип такого выражения, поэтому не притворяйтесь, что вы это делаете. Другими словами:

Не используйте общий шаблон анти-паттерна «просто для избежания приведения»