FAQ по var в Java
Локальные переменные — это «рабочие лошадки» Java. Они используются для хранения промежуточных результатов вычислений. И, в отличие от полей, объявляются, инициализируются и используются в одном блоке. Для понимания кода часто более важны имя и инициализатор, чем тип локальной переменной. В имени и инициализаторе столько же информации, сколько и в типе:
Person person = new Person();
Вместо явного указания типа можно использовать var :
var person = new Person();
Компилятор определяет тип локальной переменной по инициализатору. Это особенно важно, когда тип параметризован wildcard , или упоминается в инициализаторе. Использование var может сделать код более кратким без ущерба для удобочитаемости, а в некоторых случаях может улучшить читаемость благодаря устранению избыточности.
Вопрос 2. Делает ли это Java динамически типизированным языком? Это то же самое, что и var в JavaScript?
Нет и нет. Java остается языком со статической типизацией, и появление var не изменяет этого. var используется в объявлении локальной переменной без явного указания типа. При использовании var компилятор Java определяет тип переменной во время компиляции, используя информацию о типе, полученную от инициализатора переменной. Далее выведенный тип используется как статический тип этой переменной. Как правило, будет тот же тип, который вы бы указали явно, поэтому переменная, объявленная с помощью var , ведет себя точно так же, как при явном указании типа.
В компиляторе Java выведение типов присутствует уже много лет. Например, параметры лямбда-выражения в Java 8 не нуждаются в явном указании типа, так как компилятор определяет их по тому, как используется лямбда-выражение:
List list = . list.stream().filter(p -> p.getAge() > 18) .
В приведенном выше фрагменте кода тип параметра p в лямбде выводится как Person . Если класс Person будет изменен так, что в нем больше не будет метода getAge , или в списке будет храниться что-то отличное от Person , то выведение типа завершится ошибкой при компиляции.
Вопрос 3. Переменная var неявно final?
Нет. Локальные переменные, объявленные с помощью var , по умолчанию не являются final . Однако к var можно добавить модификатор final :
final var person = new Person();
Для final var в Java нет никакого сокращения. Например, в Scala для объявления неизменяемых переменных используется val . В Scala это хорошо работает, потому что все переменные (и локальные, и поля) объявляются с помощью синтаксиса следующего вида.
val name : type
var name : type
Указывать » : type » в объявлении необязательно — это зависит от того, хотите ли вы использовать выведение типа или нет. В Scala выбор между изменяемостью и неизменяемостью ортогонален к выведению типов.
В Java var можно использовать только там, где требуется выведение типа. Его нельзя использовать, если тип объявлен явно. Если в Java добавить val , то его тоже можно было бы использовать только там, где используется выведение типов. В Java нельзя было бы использовать var или val для управления иммутабельностью при явном объявлении типа.
Кроме того, Java позволяет использовать var только для локальных переменных, но не для полей. Для полей иммутабельность более значима. Неизменяемые локальные переменные используются сравнительно редко.
Использование ключевых слов var/val для управления иммутабельностью — это то, что действительно стоит позаимствовать из Scala в Java. Однако в Java это было бы гораздо менее полезно, чем в Scala.
Вопрос 4. Не будут ли плохие разработчики злоупотреблять var, чтобы писать ужасный код?
Да, плохие разработчики будут писать ужасный код, что бы мы ни делали. Отказ от var не помешает им это сделать. Но при правильном применении выведение типов позволит писать более качественный код.
Один из моментов, благодаря которому var может побудить разработчиков писать более качественный код, заключается в том, что var снижает накладные расходы на объявление новой переменной. Разработчики часто избегают объявления новой переменной, если это требует больших затрат, и создают сложные конструкции, ухудшающие читаемость, только из-за того, чтобы избежать объявления дополнительных переменных. С var накладные расходы на извлечение части кода в именованную переменную уменьшаются, поэтому разработчики с большей вероятностью будут это делать, что приведет к более чистому коду.
Часто программисты начинают очень активно использовать новые возможности языка, возможно, злоупотребляя ими, и требуется некоторое время, чтобы сообщество выработало рекомендации по разумному использованию.
Начиная с выведения типа локальной переменной (Local Variable Type Inference, LVTI), мы публикуем материалы о назначении и рекомендации по использованию (например, данный FAQ и LVTI Style Guidelines) почти одновременно с появлением этой функциональности. Мы надеемся, что это ускорит понимание в сообществе, когда разумно использовать var , и поможет избежать злоупотребления.
Вопрос 5. Где можно использовать var?
var может использоваться при объявлении локальных переменных, включая индексные переменные цикла for и ресурсные переменные оператора try-with-resources .
var нельзя использовать для полей, параметров методов и возвращаемых типов методов. Причина заключается в том, что типы в этих местах явно присутствуют в class-файлах и в спецификациях Javadoc. При выведении типа изменение инициализатора может легко привести к изменению предполагаемого типа переменной. Для локальных переменных это не проблема, так как область видимости локальных переменных ограничена, и их типы напрямую не записываются в class-файлы. Однако для полей, параметров методов и типов, возвращаемых методом, выведение типа может легко вызвать проблему.
Например, возвращаемый тип метода был выведен из выражения в операторе return . Изменение реализации метода может привести к изменению типа выражения в return . Это, в свою очередь, может изменить тип возвращаемого значения. Что может привести к бинарной несовместимости или несовместимости в исходном коде. Такие несовместимые изменения не должны возникать из-за безобидных на вид изменений в реализации.
Предположим, что также будет выводиться тип поля. Тогда изменение инициализатора поля может привести к изменению типа поля, что может неожиданно нарушить рефлексивный код.
Выведение типа допустимо в реализации, но не в API. API-контракты должны быть объявлены явно.
Как насчет private-полей и методов, которые не являются частью API? Теоретически мы могли бы реализовать поддержку var для private-полей и возвращаемых типов для private-методов, не опасаясь, что это вызовет несовместимость из-за раздельной компиляции и динамической линковки. Но для простоты мы решили ограничить область выведения типов. Если расширить границы и включить отдельные виды полей и типы, возвращаемые некоторыми методами, то это сделает все значительно более сложным и трудным для понимания, и лишь незначительно более полезным.
Вопрос 6. Почему для var всегда нужен инициализатор в правой части?
Тип переменной вычисляется на основе инициализатора. Это означает, что var можно использовать только при наличии инициализатора. Можно было бы сделать выведение типа из присвоений переменной, но это значительно усложнило бы реализацию и потенциально могло привести к вводящим в заблуждение или трудно диагностируемым ошибкам. Для простоты мы определили var так, чтобы для выведения типа использовалась только локальная информация.
Допустим, мы разрешили выведение типа на основе присваивания в нескольких местах, отдельно от объявления переменной. Тогда рассмотрим следующий пример:
var order; . order = "first"; . order = 2;
Если тип был выбран на основе, например, первого присваивания, то это может привести к ошибке в другом операторе, весьма удаленном от местоположения настоящей ошибки. (Иногда это называют проблемой «дальнодействия».)
В качестве альтернативы можно выбрать тип, совместимый со всеми присваиваниями. И в этом случае можно ожидать, что выведенный тип будет Object , как общий суперкласс String и Integer . К сожалению, ситуация сложнее. Поскольку и String , и Integer являются Serializable и Comparable , общий супертип будет пересечением типов, что-то вроде
Serializable & Comparable>
Обратите внимание, что невозможно явно объявить переменную этого типа. Также это приводит к упаковыванию (boxing), когда order присваивается 2, что может быть неожиданным и нежелательным.
Чтобы избежать подобных проблем, проще потребовать вывод типа с использованием явного инициализатора.
Вопрос 7. Почему нельзя использовать var с null?
Посмотрите на такое объявление (оно некорректно):
var person = null; // ОШИБКА
Литерал null обозначает значение специального типа null (JLS 4.1), который является подтипом всех ссылочных типов в Java. Единственным значением типа null является сам null , поэтому единственное значение, которое может быть присвоено переменной типа null — это null . Это не очень полезно.
Можно ввести специальное правило, чтобы объявление var , инициализированное значением null , имело тип Object . Но возникает вопрос о намерениях программиста. Предположим, что переменная инициализируется значением null , чтобы позже ей присвоить какое-либо другое значение. Но в этом случае маловероятно, что определение типа переменной как Object будет правильным.
Вместо того чтобы создавать какие-то специальные правила для обработки этого случая, мы его запретили. Если вам нужна переменная типа Object , объявите ее явно.
Вопрос 8. Можно ли использовать var с diamond-оператором справа?
Да, это работает, но, скорее всего, не так, как вы ожидаете. Пример:
var list = new ArrayList<>();
Тип списка будет ArrayList . В общем, лучше указывать явный тип слева с diamond-оператором справа или использовать var слева с явным типом справа. См. особенности использования var с diamond-оператором и дженериками в LVTI Style Guidelines (Руководство по стилю LVTI).
Приглашаем всех желающих на открытое занятие «Реляционные базы данных для начинающих Java-разработчиков». Поговорим о месте реляционных баз данных в архитектуре информационных систем. Рассмотрим основные компоненты и возможности РСУБД на примере PostgreSQL. Сделаем обзор основных технологий по работе с реляционными БД в Java (JDBC, JPA/Hibernate, Spring Data и др.) Регистрация по ссылке.
- Блог компании OTUS
- Программирование
- Java
Java 10: ключевое слово «var»
В Java 10 введен вывод типа локальной переменной с ключевым словом var . Это означает, что вместо записи:
Map
for (Entry
List
Вы можете использовать var чтобы уменьшить шаблон, следующим образом:
var map = new HashMap
for (var dept : map.entrySet()) <
var employees = dept.getValue();
var устраняет необходимость написания явных типов в вашем коде. Это не только уменьшает количество повторений, но также облегчает поддержку вашего кода, потому что, если, например, вы решите изменить типы объектов, хранящихся на вашей карте в будущем, вам потребуется изменить только одну строку кода.
Полиморфизм:
Теперь давайте посмотрим, как var ведет себя с полиморфным кодом. Например, если у вас есть класс Shape с двумя подклассами, Square и Circle , какой тип будет выведен, если вы используете var v = new Circle() ? Давайте попробуем это в JShell :
Кофе-брейк #180. Переменные в Java: что это такое и как они используются. 5 вещей, которые вы должны знать о сериализации и десериализации в Java
Переменные в Java: что это такое и как они используются
Источник: Hackernoon В Java существует четыре различных типа переменных, в зависимости от того, где они объявлены в программе. Сегодня вы узнаете примеры и отличия каждого типа. 1. Переменные экземпляра (Instance variables) или поля экземпляра (Instance fields) — это переменные, объявленные внутри класса без ключевого слова static , но вне метода, конструктора или блока кода. Такие переменные могут быть объявлены в любом месте внутри класса. Вы можете объявить их с модификатором доступа или без него, например, public , private , protected или default (не ключевое слово).
public class MyClass < //instance field 1 private String instanceField1; public MyClass()<>//Constructor //instance field 2 public int anotherInstanceField2; public void setInstanceField(String parameterVariable) //instance method //instance field 3 boolean instanceField3; public static void main(String[] args) < System.out.println("field 1 value: " + instanceField1); // = null System.out.println("field 2 value: " + anotherInstanceField2); // = 0 System.out.println("field 3 value: " + instanceField3); // = 0 >>
Если полю экземпляра не присваивается значение во время объявления, то ему присваивается значение по умолчанию, равное нулю, если это примитивный тип, например ( int , boolean , long , float ), или null , если это не примитивный тип, например ( String , Integer , AnyClass ). Они называются полями или переменными экземпляра, потому что они принадлежат экземпляру любого объекта, созданного из класса, в котором они объявлены.
public Main < public static void main(String[] args) < MyClass obj1 = new MyClass(); MyClass obj2 = new MyClass(); //Now we can access every 'public' field declared in the MyClass class // from the newly created object 'obj' obj1.anotherInstanceField2 = 11; obj2.anotherInstanceField2 = 33; System.out.println(obj1.anotherInstanceField2); // prints '11' System.out.println(obj2.anotherInstanceField2); // prints '33' >>
Таким образом, каждое поле экземпляра уникально для своего объекта, как видно из приведенного выше фрагмента. В нем obj1 и obj2 имеют уникальные значения, присвоенные их соответствующим полям экземпляра. 2. Поля класса (Class fields) или статические поля (static fields) — это поля, объявленные с ключевым словом static . Они объявляются внутри класса, но вне метода, конструктора или блока кода. Они также могут быть объявлены в любой позиции внутри класса с модификатором доступа или без него, например, public , private , protected или default (не ключевое слово).
public class MyClass < //static field public static String staticField; public MyClass()<>//Constructor > class Main < public static void main(String[] args) < MyClass obj = new MyClass(); obj.staticField //will throw Not defined Error //Now we cannot access the static field declared in MyClass class from the // newly created object 'obj' because static fields are not attached to any // object. They belong solely to the class they are declared and can only be // accessed from their class. MyClass.staticField = "I am a static field"; System.out.println(MyClass.staticField); // prints 'I am a static field' >>
Доступ к статическим полям возможен только через их классы, а не из какого-либо объекта, как показано во фрагменте кода выше. 3. Параметры (Parameters) или переменные-аргументы (Argument variables) — это переменные, объявленные внутри конструкции метода между открывающимися и и закрывающимися фигурными скобками сигнатуры метода. Они используются для передачи значений или объектов в метод.
public class MyClass < //instance field public String instanceField; public MyClass()<>//Constructor //instance method with a parameter variable public void setInstanceField(String parameterVariable) < instanceField = parameterVariable; >> class Main < public static void main(String[] args) < MyClass obj = new MyClass(); obj.setInstanceField("From a parameter variable"); System.out.println(obj.instanceField); // prints 'From a parameter variable' >>
4. Локальные переменные (Local variables) — это переменные, объявленные внутри метода или любого блока кода, например, внутри блока операторов if , for loop , while loop , блока операторов switch и так далее.
public Main < public static void main(String[] args) < MyClass obj1 = new MyClass(); // 'obj1' is local reference variable int // 'name' is a local variable here. if (id >1) < String tempName = "Austin"; // 'tempName' is a local reference variable >> >
В данном коде можно заметить использование reference с некоторыми переменными, в то время как локальная переменная id не упоминалась, как ссылочная переменная. Любая непримитивная переменная является ссылочной переменной. Например, obj1 — это переменная типа MyClass , а tempName — это переменная типа String , и здесь оба типа не являются примитивными типами. При этом id — это переменная типа int , которая является примитивным типом данных. Следовательно, это нессылочная переменная.
5 вещей, которые вы должны знать о сериализации и десериализации в Java
Источник: Devgenius Благодаря этому руководству вы улучшите свои знания о работе сериализации и десериализации. Сериализация в Java помогает преобразовать существующий объект в поток байтов. И наоборот, десериализация делает поток байтов объектом. Используя сериализацию и десериализацию в Java, информацию об объектах можно переносить с одной JVM на другую.
#1 Сериализация
Прежде чем приступить к подробному описанию, давайте обратим внимание на классы SerializeUtils.java и Person.java . Здесь они нам помогут выполнить сериализацию и десериализацию на конкретных примерах.
SerializeUtils.java
package com.techteam.serialization; import java.io.*; public class SerializeUtils < public static void serialize(T input, String fileName) throws IOException < FileOutputStream file = new FileOutputStream(fileName); ObjectOutputStream out = new ObjectOutputStream(file); out.writeObject(input); out.close(); file.close(); >public static T deserialize(String fileName) throws IOException, ClassNotFoundException < FileInputStream file = new FileInputStream(fileName); ObjectInputStream in = new ObjectInputStream(file); T result = (T) in.readObject(); return result; >public static void externalSeialize(Externalizable e, String fileName) throws IOException < FileOutputStream file = new FileOutputStream(fileName); ObjectOutputStream out = new ObjectOutputStream(file); e.writeExternal(out); out.close(); file.close(); >public static void externalDeseialize(Externalizable e, String fileName) throws IOException, ClassNotFoundException < FileInputStream file = new FileInputStream (fileName); ObjectInputStream in = new ObjectInputStream (file); e.readExternal(in); in.close(); file.close(); >>
Person.java
package com.techteam.serialization; import java.io.Serializable; public class Person implements Serializable < private static final long serialVersionUID = 1L; private int id; private String name; private int age; public int getId() < return id; >public void setId(int id) < this.id = id; >public String getName() < return name; >public void setName(String name) < this.name = name; >public int getAge() < return age; >public void setAge(int age) < this.age = age; >>
Как уже упоминалось, сериализация помогает преобразовать объект в поток байтов. Это означает, что вся информация об объекте также преобразуется в поток байтов, такой как метод, свойства и данные. Вот пример, как происходит сериализация объекта:
package com.techteam.serialization; import java.io.IOException; public class SerializationMain < public static void main(String[] args) throws IOException < Person p = new Person(); p.setId(1); p.setName("Tech team members"); p.setAge(20); SerializeUtils.serialize(p, "/person.txt"); >>
После процесса сериализации у нас есть файл с таким содержимым:
#2 Десериализация
Если в предыдущем примере мы создали поток байтов посредством сериализации объекта, то теперь давайте посмотрим, как мы вернемся к объекту, используя десериализацию:
package com.techteam.serialization; import java.io.IOException; public class DeserializationMain < public static void main(String[] args) throws IOException, ClassNotFoundException < Person p = SerializeUtils.deserialize("/person.txt"); System.out.println("Person data:"); System.out.println(p.getId()); System.out.println(p.getName()); System.out.println(p.getAge()); >>
Вот данные после процесса десериализации:
#3 Serial Version UID
SerialVersionUID означает уникальный идентификационный номер для каждой версии процесса сериализации и десериализации. Этот номер используется, чтобы убедиться, что и сериализованные, и десериализованные объекты используют совместимые классы. Для Person.java я хотел бы увеличить serialVersionUID до 2. Давайте посмотрим на вывод после десериализации файла person.txt.
#4 Ключевое слово Transient
В процессе сериализации и десериализации иногда нам не нужно сериализовать всю информацию об объекте. Используя переходный (transient) процесс для переменных, мы можем игнорировать эти переменные из сериализуемого объекта. Пример ниже поможет понять это более четко:
package com.techteam.serialization; import java.io.IOException; import java.io.Serializable; public class PersonWithTransient implements Serializable < private static final long serialVersionUID = 1L; private int id; private String name; private transient int age; public int getId() < return id; >public void setId(int id) < this.id = id; >public String getName() < return name; >public void setName(String name) < this.name = name; >public int getAge() < return age; >public void setAge(int age) < this.age = age; >public static void main(String[] args) throws IOException, ClassNotFoundException < PersonWithTransient p = new PersonWithTransient(); p.setId(2); p.setName("Tech team members(transient)"); p.setAge(50); SerializeUtils.serialize(p, "/person_transient.txt"); PersonWithTransient deserializeP = SerializeUtils.deserialize("/person_transient.txt"); System.out.println("Person without transient data:"); System.out.println(deserializeP.getId()); System.out.println(deserializeP.getName()); System.out.println(deserializeP.getAge()); >>
В приведенном выше коде мы использовали ключевое слово transient для переменной age . И вот что у нас получилось после процесса сериализации и десериализации.
#5 Внешний интерфейс (Externalizable Interface)
В Java, когда мы хотим настроить процесс сериализации и десериализации, мы можем использовать переходный процесс, чтобы игнорировать переменные, которые нам не нужны для процесса сериализации и десериализации. Еще один способ упростить и повысить производительность — использовать интерфейс Externalizable вместо интерфейса Serializable . Давайте посмотрим на пример:
package com.techteam.serialization; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; public class PersonExternalizable implements Externalizable < private static final long serialVersionUID = 1L; private int id; private String name; private int age; public int getId() < return id; >public void setId(int id) < this.id = id; >public String getName() < return name; >public void setName(String name) < this.name = name; >public int getAge() < return age; >public void setAge(int age) < this.age = age; >@Override public void writeExternal(ObjectOutput out) throws IOException < out.writeUTF(this.name); out.writeInt(this.age); >@Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException < this.name = in.readUTF(); this.age = in.readInt(); >public static void main(String[] args) throws IOException, ClassNotFoundException < PersonExternalizable p = new PersonExternalizable(); p.setId(3); p.setName("Tech team members(Externalizable)"); p.setAge(30); SerializeUtils.externalSeialize(p, "/person_externalizable.txt"); PersonExternalizable deserializeP = new PersonExternalizable(); SerializeUtils.externalDeseialize(deserializeP, "/person_externalizable.txt"); System.out.println("Person data:"); System.out.println(deserializeP.getId()); System.out.println(deserializeP.getName()); System.out.println(deserializeP.getAge()); >>
Как видите, при использовании Externalizable мы можем легко написать пользовательскую логику, игнорировать переменные и получить более высокую производительность, чем при использовании Serializable . Теперь давайте посмотрим на вывод:
Заключение
Надеюсь, что благодаря этой статье вы получили ясное представление о том, как сериализация и десериализация работают в Java, а приведенные выше примеры в будущем смогут помочь вам на практике.
Var java что это
Подумаем, к примеру, над использованием класса java.nio.channels. Selector . Этот класс имеет статический метод open (), который возвращает new Selector и открывает его. Но здесь запросто можно подумать, что метод Selector . open () может возвращать тип boolean, в зависимости от успешности открытия имеющегося селектора, или даже возвращать void. Использование здесь var приведет к потери информации и путанице в коде.
Пункт 6: тип var гарантирует безопасность во время компиляции