Подтвердить что ты не робот

Тип возврата объекта Java против общих методов

Я видел несколько вопросов о типичном обратном типе, но никто не ответил на мой вопрос.
Если для любого из аргументов нет ограничений, таких как следующий метод в JayWay:

public static <T> T read(String json, String jsonPath, Filter... filters) {
    return new JsonReader().parse(json).read(jsonPath, filters);
}

В чем смысл использования этого как общего?
Я сказал ребятам из моей команды, что этот метод следует использовать как:

JsonPath.<Boolean>read(currentRule, "$.logged")

вместо:

(boolean) JsonPath.read(currentRule, "$.logged")

Но я действительно не могу сказать разницы...

4b9b3361

Ответ 1

Генераторы работают компилятором, вставляя невидимые роли в ваш код.

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

List list = new ArrayList();
list.add("Foo");
list.add("Bar");
String str0 = (String) list.get(0);
String str1 = (String) list.get(1);

Это было очень неприятно. Поскольку get() возвращен Object, вам нужно было использовать каждый раз, когда вы хотели получить String из List.

В настоящее время List является общим, а get() возвращает T, поэтому вы можете просто сделать это.

List<String> list = new ArrayList<>();
list.add("Foo");
list.add("Bar");
String str0 = list.get(0);
String str1 = list.get(1);

Что здесь происходит, так это то, что компилятор превращает новую версию в старую версию, добавляя к ней роли, но они все еще там.

Тем не менее, вся точка генерики заключается в том, что эти сгенерированные компилятором отливки гарантированно безопасны - то есть они не могут выбросить ClassCastException во время выполнения.

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

Является ли это универсальным методом и вы делаете

Boolean a = JsonPath.<Boolean>read(currentRule, "$.logged");

или он возвращает Object, и вы делаете

Boolean a = (Boolean) JsonPath.read(currentRule, "$.logged");

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

Я считаю неправильной практикой для возвращаемого типа общего метода для включения параметра типа T, если параметры метода отсутствуют, если только возвращаемый объект не может использоваться таким образом, чтобы скомпрометировать безопасность типа. Например,

public static <T> List<T> emptyList()

in Collections - это нормально (список пуст, поэтому он не может содержать элемент неправильного типа).

В вашем случае, я думаю, что метод read не должен быть общим и должен просто возвращать Object.

Ответ 2

Основная причина, по которой я буду держаться подальше от

JsonPath.<Boolean>read(currentRule, "$.logged")

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

JsonPath.<String>read(currentRule, "$.logged")

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

Невозможно уйти от непроверенного броска - я просто предпочел бы это прямо перед собой в коде, поэтому я знаю, что существует потенциальная опасность; это позволяет мне принять разумные меры для смягчения проблемы.

@SuppressWarnings("unchecked")  // I know something might go wrong here!
boolean value = (boolean) JsonPath.read(currentRule, "$.logged")

Ответ 3

Между ними нет ничего функционально другого. Байт-код, вероятно, будет идентичным.

Основное отличие заключается в том, что вы используете литье, в то время как otrher использует generics.

Обычно я старался избегать кастинга, если есть какой-либо альтернативный механизм, и поскольку общая форма является совершенно эффективной альтернативой, я бы пошел на это.

// The right way.
JsonPath.<Boolean>read(currentRule, "$.logged");

Ответ 4

Наличие параметра типа, который никогда не был установлен (при вызове JsonPath.read(currentRule, "$.logged")), фактически заставляет компилятор полностью игнорировать всю общую информацию в методе и заменять все параметры типа:

  • Object, если параметр типа не имеет верхней границы. (как в вашем случае)
  • U, если параметр типа ограничен как <T extends U>. Например, если у вас есть <T extends Number> как параметр типа и игнорировать его, вызывая JsonPath.read(...), тогда компилятор заменит параметр типа Number.

В случае с литой ((boolean) JsonPath.read(...)) параметр типа заменяется на Object. Затем этот тип без проблем преобразуется в boolean, сначала возвращая boolean (возможно), а затем автоматически распаковывая эту оболочку на boolean. Это совсем не безопасно. Фактически, каждый кастинг небезопасен - в значительной степени вы говорите компилятору: "Я знаю, что этот тип будет в Runtime, поэтому, пожалуйста, поверьте мне, и позвольте мне применить его к чему-то еще, что совместимо с ним". Ваш скромный слуга, компилятор, позволяет это, но это небезопасно, если вы ошибаетесь.:)

Есть и другая вещь с вашим методом. Параметр type никогда не используется внутри тела метода или параметров - это делает его довольно избыточным. Так как, выполнив приведение к boolean, вы настаиваете на том, что знаете тип возврата new JsonReader().parse(json).read(jsonPath, filters);, тогда вы должны просто сделать тип возврата boolean (или boolean):

public static Boolean read(String json, String jsonPath, Filter... filters) {
    return new JsonReader().parse(json).read(jsonPath, filters);
}