Как троичный оператор оценивает результирующий тип данных?

Учитывая этот кусок кода

public class Main {

    public static void main(String[] args) {
        foo(1);
        foo("1");
        foo(true?1:"1");
        foo(false?1:"1");
    }

    static void foo(int i){System.out.println("int");}
    static void foo(String s){System.out.println("String");}
    static void foo(Object o){System.out.println("Object");}
}

Это вывод, который я получаю:

ИНТ
строка
объект
объект

Я не могу понять, почему в последних двух случаях foo(Object o) вызывается, а не foo(int i) а также foo(String s), Разве тип возвращаемого значения для троичного выражения не оценивается во время выполнения?

Редактировать:

Что меня смущало, так это утверждение

System.out.println((y>5) ? 21 : "Zebra");

компилируется потому что (OCA Study Guide - Sybex):

System.out.println() не заботится о том, что операторы имеют совершенно разные типы, потому что он может конвертировать оба в String

Дело в том, что println перегружен, чтобы принимать типы объектов в качестве входных данных. Совершенно вводит в заблуждение, имхо.

3 ответа

Решение

Разве тип возвращаемого значения для троичного выражения не оценивается во время выполнения?

Нет, абсолютно нет. Это будет очень противоречить тому, как работает Java, где разрешение перегрузки и т. Д. Всегда выполняется во время компиляции. Что бы вы ожидали, если бы вы не передали результат методу, а попытались присвоить его переменной? Какую переменную вы бы объявили?

Тип выражения определяется правилами JLS 15.25. В обоих ваших случаях третий операнд имеет тип String, что приводит к тому, что это является условным условным выражением, поэтому применяется таблица 15.25-E с результатом lub(Integer,Object), lub часть ссылается на JLS 4.10.4, что довольно запутанно - тип здесь не совсем такой, как просто Object, но в большинстве случаев это можно считать таким.

Обе альтернативы тройной должны быть одного типа. Единственный общий тип для Integer и String - это Object, поэтому оба операнда приводятся к Object, а тип троицы определяется во время компиляции как Object.

Затем компилятор статически связывается с методом с параметром Object.

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

Это, я полагаю, в дополнение к другим ответам. (Или, в частности, для тех, кто хочет понять педантичный ответ.)

Результат троичного условия для ссылочных типов заканчивается lub(trueType, falseType)так что в данном конкретном случае это lub(Integer, String), lub это:

Наименьшая верхняя граница, или "lub", набора ссылочных типов является общим супертипом, который более специфичен, чем любой другой общий супертип [...].

Чтобы понять lubмы могли бы сделать своего рода "чистую" версию алгоритма для простых типов следующим образом.

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

+------------------------+------------------------+
|      Integer           |       String           |
+------------------------+---------+--------------+
| classes |  interfaces  | classes |  interfaces  |
+------------------------+---------+--------------+
| Object  | Serializable | Object  | Serializable |
| Number  | Comparable   | String  | Comparable   |
| Integer |              |         | CharSequence |
+---------+--------------+---------+--------------+

(Фактический алгоритм объединил бы классы и интерфейсы как просто "супертипы".)

Сейчас:

  • вычеркните все суперклассы, начиная снизу, пока не встретите суперкласс, который является обычным явлением.
  • вычеркните все интерфейсы, которые не являются общими.
+------------------------+------------------------+
|      Integer           |       String           |
+------------------------+---------+--------------+
| classes |  interfaces  | classes |  interfaces  |
+------------------------+---------+--------------+
| Object  | Serializable | Object  | Serializable |
| Number  | Comparable   | String  | Comparable   |
| Integer |              |         | CharSequence |
+---------+--------------+---------+--------------+

lub теперь это тип пересечения, заданный этим общим суперклассом и общими интерфейсами:

lub(Integer, String) =
    Object & Serializable & Comparable

Кроме этого lub немного сложнее здесь из-за Comparable, Технически, lub(Integer, String) дает бесконечный тип, потому что нам нужно сделать lub(Integer, String) снова (бесконечно) для аргумента типа Comparable:

lub(Integer, String) =
    Object & Serializable & Comparable<? extends lub(Integer, String)>

Тип условного (boolean ? Integer : String) затем тип, который является результатом применения преобразования захвата к lub(Integer, String) (Yikes!).

foo(Object) перегрузка выбрана, потому что это наиболее применимо к этому типу.

Несколько других интересных вещей об этом:

  • Если мы добавили перегрузку foo(Serializable), он будет выбран, потому что Serializable более конкретно, чем Object,
  • Если мы добавили две перегрузки foo(Serializable) а также foo(Comparable<?>), мы получим ошибку неоднозначности.
Другие вопросы по тегам