Вывод локального типа против экземпляра

Я попытался просмотреть JEP-286 о локальном выводе типов. Я вижу, что это работает только для локальных переменных — понял. Так что это действительно работает:

public class TestClass {
    public static void main(String [] args){
        var list = new ArrayList<>();
        list.add("1");
        System.out.println(list.get(0)); // 1
    }  
}

Я вижу, что это, с другой стороны, не компилируется:

public class TestClass {
    public var list = new ArrayList<>();
    public static void main(String [] args){

    }
}

Очевидно, что это не так, поскольку JEP так говорит. Теперь мой вопрос:

Логично, что член public/protected, объявленный как var, потерпит неудачу, по крайней мере, IMO. Но почему он не компилируется, даже если это private? Я могу только предположить, что вы все еще можете получить эту переменную с помощью отражения (и я не могу получить локальные поля, подобные этому)... И для получения этой переменной потребуется приведение, ну, возможно, очень запутанное приведение.


person Eugene    schedule 02.03.2018    source источник
comment
Модификаторы доступа и область действия, наконец, смешиваются и подвергаются сомнению для вывода типа var. Еще один хороший вопрос исходит от вас. :)   -  person Naman    schedule 05.03.2018


Ответы (5)


Причина запрета вывода типов для полей и возвращаемых методов заключается в том, что API должны быть стабильными; доступ к полю и вызов метода связаны дескриптором во время выполнения, поэтому вещи, которые вызывают тонкие изменения в выводимых типах, могут привести к ужасным поломкам существующих скомпилированных клиентов, если изменение реализации приведет к изменению выведенного типа (стирание по модулю). Таким образом, использование это для реализации, но не для API, является разумным руководящим принципом.

Резонно спросить: «А как насчет приватных полей и методов?» И действительно, мы вполне могли бы сделать это. Как и все дизайнерские решения, это компромисс; это позволило бы использовать вывод в большем количестве мест в обмен на большую сложность пользовательской модели. (Меня не волнует сложность спецификации или компилятора; это наша проблема.) Легче рассуждать о «выводе для локальных переменных — да, полях и методах — нет», чем добавлять различные эпициклические рассуждения вроде «но поля и методы в порядке, если они закрыты». Проведение линии там, где мы это сделали, также означает, что последствия для совместимости изменения поля или метода с частного на нечастный не имеют случайных взаимодействий с логическим выводом.

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

person Brian Goetz    schedule 02.03.2018

Различные причины:

  1. Видимость и тип ортогональны — одно не должно влиять на другое. Если бы частные переменные можно было инициализировать с помощью var, вам пришлось бы изменить это, делая их защищенными или общедоступными.

  2. Поскольку var использует правую часть для определения типа, такие закрытые поля всегда необходимо сразу инициализировать. Если вы перемещаете инициализацию в конструктор, вам нужно сделать тип явным.

  3. С помощью var компилятор может вывести типы, которые вы в настоящее время не можете выразить в Java (например, типы пересечения, такие как Comparable & Serializable). Конечно, вы можете в конечном итоге полагаться на эти конкретные типы, и когда вам в какой-то момент по какой-либо причине придется прекратить использование var, вам может потребоваться довольно много рефакторинга, чтобы ваш код работал.

person Nicolai Parlog    schedule 02.03.2018
comment
Вы подразумеваете, что IFF частная переменная экземпляра будет объявлена ​​на месте, она может использоваться как var? В таком случае кажется разумным предположить (также), что поля final могут быть объявлены таким образом - они либо инициализируются внутри конструктора, либо на месте. - person Eugene; 02.03.2018
comment
и кстати, если это так... внутри метода (локального) var x; не будет компилироваться, так почему бы не сделать то же самое для переменных экземпляра? Я думаю, что мы, вероятно, что-то упускаем здесь, думая, что на мгновение игнорируем видимость переменных. - person Eugene; 02.03.2018
comment
Я не совсем понимаю ваши вопросы. Чтобы уточнить, поля var ни при каких обстоятельствах не возможны в Java 10. Во 2. Я хочу сказать, что если бы это было возможно (и вывод типов работал бы так, как сейчас), эти поля имели бы быть инициализирован сразу, потому что var x; не работает (сейчас не работает в локальных переменных). - person Nicolai Parlog; 02.03.2018

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

var l = new ArrayList<String>();
l.add("text");
System.out.println(l);
System.out.println(
  new Object(){ { var x = l; } }.getClass().getDeclaredFields()[0].getGenericType()
);

В текущей версии он просто печатает ArrayList, поэтому фактический универсальный тип не был сохранен в файле класса анонимного внутреннего класса, и маловероятно, что это изменится, поскольку поддержка этого самоанализа не является фактической целью. Это также особый случай, когда тип обозначается как ArrayList<String>. Чтобы проиллюстрировать другой случай:

var acs = true? new StringBuilder(): CharBuffer.allocate(10);
acs.append("text");
acs.subSequence(1, 2);
System.out.println(
  new Object(){ { var x = acs; } }.getClass().getDeclaredFields()[0].getGenericType()
);

Тип acs является типом пересечения Appendable и CharSequence, что демонстрируется вызовом метода любого интерфейса для него, но поскольку не указано, выводит ли компилятор #1 extends Appendable&CharSequence или #1 extends CharSequence&Appendable, не указано, будет ли код печатать java.lang.Appendable или java.lang.CharSequence .

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

Однако я сомневаюсь, что экспертная группа подробно рассматривала такие воздействия. Вместо этого решение не поддерживать объявления полей (и, следовательно, пропускать длительные размышления о последствиях) было принято с самого начала, поскольку локальные переменные всегда были предполагаемой целью для этой функции. Количество локальных переменных намного больше, чем количество объявлений полей, поэтому сокращение шаблона для объявлений локальных переменных имеет наибольшее значение.

person Holger    schedule 02.03.2018
comment
Мне действительно это нравится, это отвечает на мой вопрос, но трудно превзойти архитектора. Спасибо - person Eugene; 03.03.2018

Развивая ответ Николая (в частности, его причину № 2), в предлагаемом проекте JLS 10 говорится, что и var e;, и var g = null; являются незаконными. для локальных переменных и не зря; неясно из правой части (или ее отсутствия), какой тип вывести для var.

В настоящее время неконечные переменные экземпляра автоматически инициализируются в зависимости от их типа (примитивы к 0 и false и ссылки к null, как я уверен, вы уже знаете). Выведенный тип переменной экземпляра останется неясным, если только он не будет инициализирован при объявлении или в конструкторе(ах) соответствующего класса.

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

person Jacob G.    schedule 02.03.2018

Было бы разумным решением разрешить var для закрытых полей (IMO). Но его отсутствие делает функцию проще.

Кроме того, его можно будет добавить в каком-нибудь будущем выпуске, когда будет больше опыта с выводом только локального типа, а удалить функцию будет намного сложнее.

person Alexey Romanov    schedule 02.03.2018
comment
как насчет отражения в этом случае? предположим, что это возможно, и вы получили этот var, будет ли это все еще Object, который вам нужно разыграть? Я не знаю... - person Eugene; 02.03.2018
comment
Поле будет иметь тип, заданный инициализатором, поэтому var x = new Object() будет Object, а var x = "" будет String, как и в случае с локальными переменными. Отражение всегда дает вам Object, там ничего не изменится. field.getType() вернет предполагаемый тип. - person Alexey Romanov; 02.03.2018
comment
+1, я все еще не думаю, что это то, что я искал, к сожалению. внутри метода var x; не будет компилироваться по понятным причинам, а var x = "" будет. Поэтому, если вы определяете тип (правая сторона) как нечто, о котором можно сделать вывод, я не вижу причин, по которым это не работает для переменных экземпляра. Я действительно думаю, что здесь что-то глубже - person Eugene; 02.03.2018