оператор diamond в java 7 позволяет использовать следующий код:
однако в Java 5/6, я могу просто написать:
мое понимание стирания типа заключается в том, что они точно такие же. (В любом случае generic удаляется во время выполнения).
зачем вообще возиться с бриллиантом? Какие новые функции / тип безопасности это позволяет? Если он не дает никаких новых функций, почему они упоминают его как функцию? Насколько я это понимаю? концепция ошибочна?
7 ответов
дженерики существуют для обеспечения защиты времени компиляции от неправильных действий. В приведенном выше примере использование типа raw означает, что вы не получаете эту защиту и получите ошибку во время выполнения. Вот почему вы не должны использовать сырые типы.
Я думаю, что главное понять, что сырые типы (без <> ) нельзя рассматривать так же, как и универсальные типы. Когда вы объявляете необработанный тип, вы не получаете ни одного из преимущества и проверка типа дженериков. Вы также должны иметь в виду, что дженерики являются частью общего назначения языка Java. они не просто применяются к конструкторам no-arg Collection s!
ваше понимание слегка искажено. Алмазный оператор-хорошая функция, так как вам не нужно повторяться. Имеет смысл определить тип один раз, когда вы объявляете тип, но просто не имеет смысла определять его снова с правой стороны. Сухой принцип.
теперь, чтобы объяснить все пух об определении типов. Вы правы, что тип удаляется во время выполнения, но как только вы хотите получить что-то из списка с определением типа, вы получаете его обратно как тип вы определили при объявлении списка, иначе он потеряет все конкретные функции и будет иметь только объекты, за исключением случаев, когда вы приведете извлеченный объект к исходному типу, который иногда может быть очень сложным и привести к ClassCastException.
С помощью List list = new LinkedList() получите предупреждения rawtype.
эта строка вызывает предупреждение [unchecked]:
Итак, вопрос трансформируется: почему предупреждение [unchecked] не подавляется автоматически только в случае создания новой коллекции?
Я думаю, это было бы гораздо более сложной задачей, чем добавление <> характеристика.
UPD: я также думаю, что был бы беспорядок, если бы законно использовать сырые типы «только для нескольких вещей».
теоретически оператор diamond позволяет писать более компактный (и читаемый) код, сохраняя повторяющиеся аргументы типа. На практике это просто два запутанных символа, которые больше ничего не дают. Почему?
IMHO, имея ясный и простой способ пометить источник как Java 7, было бы более полезно, чем изобретать такие странные вещи. В таком отмеченном коде необработанные типы могут быть запрещены без потери чего-либо.
кстати., Я не думаю, что это следует делать с помощью переключателя компиляции. Java-версия файла программы является атрибутом файла, без опции вообще. Используя что-то тривиальное, как
может прояснить (вы можете предпочесть что-то более сложное, включая одно или несколько причудливых ключевых слов). Это даже позволило бы без проблем компилировать источники, написанные для разных версий Java. Это позволило бы вводить новые ключевые слова (например, «модуль») или отбрасывать некоторые устаревшие функции (несколько непубличных не вложенных классов в одном файле или что-либо еще) без потери совместимости.
Итак, лучше написать код, который не генерирует дополнительных предупреждений, а diamond operator позволяет сделать это удобным способом без ненужного повторения.
все сказанное в других ответах допустимо, но варианты использования не являются полностью допустимыми IMHO. Если один проверяет гуавы и особенно связанные с коллекциями вещи, то же самое было сделано со статическими методами. Е. Г. списки.newArrayList () что позволяет писать
или со статическим импортом
Это было бы более полезно, если бы они пошли на то, чтобы сделать поведение оператора diamond по умолчанию, то есть тип выводится с левой стороны выражения или если тип левой стороны выводился с правой стороны. Последнее происходит в Scala.
пункт для оператора Диаманта просто уменьшить печатать кода объявляя родовые типы. Это не имеет никакого влияния на время выполнения вообще.
единственная разница, если вы укажете в Java 5 и 6,
— Это, что вы должны указать @SuppressWarnings(«unchecked») до list (в противном случае вы получите непроверенное предупреждение о приведении). Насколько я понимаю, алмазный оператор пытается облегчить разработку. Ему нечего делать на выполнения дженериков в все.
Пришел, увидел, обобщил: погружаемся в Java Generics
Java Generics — это одно из самых значительных изменений за всю историю языка Java. «Дженерики», доступные с Java 5, сделали использование Java Collection Framework проще, удобнее и безопаснее. Ошибки, связанные с некорректным использованием типов, теперь обнаруживаются на этапе компиляции. Да и сам язык Java стал еще безопаснее. Несмотря на кажущуюся простоту обобщенных типов, многие разработчики сталкиваются с трудностями при их использовании. В этом посте я расскажу об особенностях работы с Java Generics, чтобы этих трудностей у вас было поменьше. Пригодится, если вы не гуру в дженериках, и поможет избежать много трудностей при погружении в тему.
Работа с коллекциями
Предположим, банку нужно подсчитать сумму сбережений на счетах клиентов. До появления «дженериков» метод вычисления суммы выглядел так:
С появлением Generics необходимость в проверке и приведении типа отпала:
Во второй строчке проверки необходимость тоже отпадала. Если потребуется, приведение типов ( casting ) будет сделано на этапе компиляции.
Принцип подстановки
Тип
Подтип
Number
Integer
List
ArrayList
Collection
List
Iterable
Collection
Примеры отношения тип/подтип
Вот пример использования принципа подстановки в Java:
Ковариантность, контравариантность и инвариантность
Но если мы попытаемся изменить содержимое массива через переменную arr и запишем туда число 42, то получим ArrayStoreException на этапе выполнения программы, поскольку 42 является не строкой, а числом. В этом недостаток ковариантности массивов Java: мы не можем выполнить проверки на этапе компиляции, и что-то может сломаться уже в рантайме.
«Дженерики» инвариантны. Приведем пример:
Wildcards
Всегда ли Generics инварианты? Нет. Приведу примеры:
Это ковариантность. List — подтип List
extends B — символ подстановки с указанием верхней границы super B — символ подстановки с указанием нижней границы где B — представляет собой границу
2. Почему нельзя получить элемент из списка ниже?
The Get and Put Principle или PECS (Producer Extends Consumer Super)
Особенность wildcard с верхней и нижней границей дает дополнительные фичи, связанные с безопасным использованием типов. Из одного типа переменных можно только читать, в другой — только вписывать (исключением является возможность записать null для extends и прочитать Object для super ). Чтобы было легче запомнить, когда какой wildcard использовать, существует принцип PECS — Producer Extends Consumer Super.
и Raw типы
Если мы опустим указание типа, например, как здесь:
Если мы попытаемся вызвать параметризованный метода у Raw типа, то компилятор выдаст нам предупреждение «Unchecked call». Если мы попытаемся выполнить присваивание ссылки на параметризованный тип Raw типу, то компилятор выдаст предупреждение «Unchecked assignment». Игнорирование этих предупреждений, как мы увидим позже, может привести к ошибкам во время выполнения нашего приложения.
Wildcard Capture
Попробуем теперь реализовать метод, выполняющий перестановку элементов списка в обратном порядке.
Более подробно о Wildcard Capture можно прочитать здесь и здесь.
Вывод
Переменные типа
Вот еще пример из класса Enum:
Multiple bounds (множественные ограничения)
Вывод
Переменная типа может быть ограничена только сверху одним или несколькими типами. В случае множественного ограничения левая граница (первое ограничение) используется в процессе затирания (Type Erasure).
Type Erasure
На скриншоте ниже два примера программы:
Разница между ними в том, что слева происходит compile-time error, а справа все компилируется без ошибок. Почему?
Reifiable типы
Почему информация об одних типах доступна, а о других нет? Дело в том, что из-за процесса затирания типов компилятором информация о некоторых типах может быть потеряна. Если она потерялась, то такой тип будет уже не reifiable. То есть она во время выполнения недоступна. Если доступна – соответственно, reifiable.
Решение не делать все обобщенные типы доступными во время выполнения — это одно из наиболее важных и противоречивых проектных решений в системе типов Java. Так сделали, в первую очередь, для совместимости с существующим кодом. За миграционную совместимость пришлось платить — полная доступность системы обобщенных типов во время выполнения невозможна.
И еще одна задачка. Почему в примере ниже нельзя создать параметризованный Exception?
Каждое catch выражение в try-catch проверяет тип полученного исключения во время выполнения программы (что равносильно instanceof), соответственно, тип должен быть Reifiable. Поэтому Throwable и его подтипы не могут быть параметризованы.
Unchecked Warnings
Компиляция нашего приложения может выдать так называемый Unchecked Warning — предупреждение о том, что компилятор не смог корректно определить уровень безопасности использования наших типов. Это не ошибка, а предупреждение, так что его можно пропустить. Но желательно все-так исправить, чтобы избежать проблем в будущем.
Heap Pollution
Как мы упомянули ранее, присваивание ссылки на Raw тип переменной параметризованного типа, приводит к предупреждению «Unchecked assignment». Если мы проигнорируем его, то возможна ситуация под названием » Heap Pollution » (загрязнение кучи). Вот пример:
В строке (1) компилятор предупреждает об «Unchecked assignment».
Рассмотрим еще один пример:
Java разрешает выполнить присваивание в строке (1). Это необходимо для обеспечения обратной совместимости. Но если мы попытаемся выполнить метод add в строке (2), то получим предупреждение Unchecked call — компилятор предупреждает нас о возможной ошибке. В самом деле, мы же пытаемся в список строк добавить целое число.
Reflection
Хотя при компиляции параметризованные типы подвергаются процедуре стирания (type erasure), кое-какую информацию мы можем получить с помощью Reflection.
С появлением Generics класс java.lang.Class стал параметризованным. Рассмотрим вот этот код:
Вывод
Если информация о типе доступна во время выполнения программы, то такой тип называется Reifiable. К Reifiable типам относятся: примитивные типы, непараметризованные типы, параметризованные типы с неограниченным символом подстановки, Raw типы и массивы, элементы которых являются reifiable.
Игнорирование Unchecked Warnings может привести к «загрязнению кучи» и ошибкам во время выполнения программы.
Reflection не позволяет получить информацию о типе объекта, если он не Reifiable. Но Reflection позволяет получить информацию о типе возвращаемого методом значения, о типе аргументов метода и о типе полей класса.
Type Inference
Термин можно перевести как «Вывод типа». Это возможность компилятора определять (выводить) тип из контекста. Вот пример кода:
С появлением даймонд-оператора в Java 7 мы можем не указывать тип у ArrayList :
Предположим у нас есть вот такой класс, который описывает связный список:
Результат обобщенного метода List.nil() может быть выведен из правой части:
Механизм выбора типа компилятором показывает, что аргумент типа для вызова List.nil() действительно String — это работает в JDK 7, все хорошо.
Выглядит разумно, что компилятор также должен иметь возможность вывести тип, когда результат такого вызова обобщенного метода передается другому методу в качестве аргумента, например:
В JDK 7 мы получили бы compile-time error. А в JDK 8 скомпилируется. Это и есть первая часть JEP-101, его первая цель — вывод типа в позиции аргумента. Единственная возможность осуществить это в версиях до JDK 8 — использовать явный аргумент типа при вызове обобщенного метода:
Вторая часть JEP-101 говорит о том, что неплохо бы выводить тип в цепочке вызовов обобщенных методов, например:
Но данная задача не решена до сих пор, и вряд ли в ближайшее время появится такая функция. Возможно, в будущих версиях JDK необходимость в этом исчезнет, но пока нужно указывать аргументы вручную:
После выхода JEP 101 на StackOverflow появилось множество вопросов по теме. Программисты спрашивают, почему код, который выполнялся на 7-й версии, на 8-й выполняется иначе – или вообще не компилируется? Вот пример такого кода:
Посмотрим на байт-код после компиляции на JDK1.8:
А теперь байт-код после компиляции на JDK1.7 – то есть на Java 7:
Чтобы избежать таких проблем, Oracle выпустил руководство по переходу с JDK1.7 на JDK 1.8 в котором описаны проблемы, которые могут возникнуть при переходе на новую версию Java, и то, как эти проблемы можно решить.
Например если вы хотите, чтобы в коде выше после компиляции на Java 8 все работало так же, как и на Java 7, сделайте приведение типа вручную:
Заключение
На этом мой рассказ о Java Generics подходит к концу. Вот другие источники, которые помогут вам в освоении темы:
Релиз Java 7 должен выйти 28 Июля. В связи с этой знаменательно датой, я наконец-то решил посмотреть, что нас всех ждет. Поскольку в последнее время в основном занимаюсь Scala, то на новые языковые фичи в Java не обращал серьезного внимания (только на тусовках java-программистов, плюс поглядывал что пишут в разных блогах жависты).
Технических новшеств очень много. Среди них самое интересное место занимает так называемый «Проект Монета (Project Coin)».
Он содержит в себе небольшие (конечно с их точки зрения) изменения в языке:
— Strings in switch — Binary integral literals and underscores in numeric literals — Multi-catch and more precise rethrow — Improved type inference for generic instance creation (diamond) — try-with-resources statement — Simplified varargs method invocation
В этом посте я наверно не смогу написать про все фичи, пока хотел обратить внимание на некоторые интересные на мой взгляд моменты.
Improved type inference for generic instance creation (diamond)
Это так называемый оператор diamond (бриллиант, алмаз). Думаю называется так, потому что чем-то похож на камень: <>.
В качестве примера часто приводят такой код:
// Java 7 List a = new ArrayList<>(); // до Java 7 List b= new ArrayList ();
Это конечно очень красиво, т.к. мы смогли уменьшить размер кода. Но возникает вопрос, чем это лучше, если раньше можно сделать еще проще? Вот так:
// в Java 7 List a = new ArrayList<>(); // до Java 7 List b = new ArrayList();
Понятно, что мы здесь используем сырой тип (raw types) и поэтому некоторым программистам по «морально-этическим» соображениям такой код не нравится. Хотя один java-программист даже не поленился и сравнил полученный байт-код: jdk-7-diamond-operator. Он показал на примере, что никакой разницы от использования бриллианта в компилируемом байт-коде нет, т.к. весь этот сахар при компиляции пропадает.
Мне кажется, что более реальная польза, может быть показана в следующем примере:
List a = new ArrayList<>(); // Такой прокатит, хотя и ошибочный, что плохо. List b = new ArrayList(a); // Такой уже отрубится компилятором, что хорошо. List c = new ArrayList<>(a);
Таким образом, наш бриллиантик автоматически выведет тип. Затем поймет, что у нас должен быть конструктор от списка чисел. Потом компилятор понимает, что нам вместо этого подают список строк и создаст ошибку компиляции.
try-with-resource
Появился новый интерфейс AutoCloseable. Причем не где-то там в io (как Closeable), а в самом java.lang!
Этот интерфейс был подсунут в иерархию над Closeable (Closeable extends AutoCloseable). Таким образом, автоматически все потоки (_Stream) и читатели/писатели(Reader/Writer) становятся также AutoCloseable. В AutoCloseable есть один метод void close() throws Exception.
Идея заключается в том, что если указать AutoCloseable переменные (в терминах Java 7 они называются ресурсы) в скобочках после try, то они всегда автоматически закроются при выходе из try блока.
В качестве примера как правило приводят обычно что-то вроде:
Последнее обновление 30.03.2021, включающее изменения до JDK 16.
Когда в Java 8 были представлены Streams и Lambdas, это было большим изменением, позволившим использовать функциональный стиль программирования с гораздо меньшим количеством шаблонного кода.
С тех пор Java перешла на более быструю периодичность выпусков, благодаря чему новая версия Java будет появляться каждые шесть месяцев. Эти версии постоянно добавляют в язык новые функции.
Усовершенствования языка после Java 8
Java 16
Классы записей (Record)
Сопоставление с образцом для instanceof
Java 15
Полезные исключения NullPointerExceptions
Java 14
Java 11
Вывод типа локальной переменной
Java 9
Разрешены private методы в интерфейсах
Оператор Diamond для анонимных внутренних классов
Разрешено использовать final переменные в качестве ресурсов в операторах try-with-resources
Подчеркивание больше не является допустимым в имени идентификатора
Что дальше: Превью функции в Java 16
Запечатанные (Sealed) классы
Детальный обзор всех JEP, формирующих новую платформу, включая API, улучшения производительности и безопасности, представлен в тщательно сформированном списке новых функций языка начиная с Java 8.
Классы записей (Record)
Доступно с:JDK 16 (предварительная версия в JDK 14JDK 15 )
Классы записей вводят в язык декларации нового типа для определения неизменяемых классов данных. Вместо обычной церемонии с private полями, геттерами и конструкторами это позволяет нам использовать компактный синтаксис:
Приведенный выше класс записи очень похож на обычный класс, который определяет следующее:
два private final поля, int x и int y
конструктор, который принимает x и y в качестве параметров
Их можно использовать так же, как обычные классы:
Класс записи предназначен для прозрачного хранения своих неизменяемых данных. Для реализации этой концепции у класса записи ряд ограничений.
Заголовок определения должен определять все возможные состояния. В теле класса записи не может быть дополнительных полей. Более того, хотя можно определить дополнительные конструкторы для предоставления значений по умолчанию для некоторых полей, невозможно скрыть канонический конструктор, который принимает все поля записи в качестве аргументов.
Наконец, классы записи не могут расширять другие классы, они не могут объявлять собственные методы, они неявно являются final и не могут быть abstract.
Передача данных в запись возможна только через ее конструктор. По умолчанию класс записи имеет только неявный канонический конструктор. Если данные необходимо проверить или нормализовать, канонический конструктор также может быть определен явно:
Неявный канонический конструктор имеет такую же видимость, как и сам класс записи. Если он явно объявлен, его модификатор доступа должен быть по крайней мере таким же, как и модификатор доступа класса записи.
Также можно определить дополнительные конструкторы, но они должны делегировать другим конструкторам. В конце концов, всегда будет вызываться канонический конструктор. Эти дополнительные конструкторы могут быть полезны для предоставления значений по умолчанию:
Эти геттеры также могут быть определены явно:
Подводя итог: классы записи предназначены только для данных, которые они хранят, не предоставляя слишком много параметров настройки.
Благодаря этой особой конструкции сериализация для записей намного проще и безопаснее, чем для обычных классов. Как написано в JEP:
Экземпляры классов записей можно сериализовать и десериализовать. Однако процесс нельзя настроить, предоставив методы writeObject, readObject, readObjectNoData, writeExternal или readExternal. Компоненты класса записи управляют сериализацией, в то время как канонический конструктор класса записи управляет десериализацией.
Поскольку сериализация основана именно на состоянии поля, а десериализация всегда вызывает канонический конструктор, невозможно создать запись с недопустимым состоянием.
С точки зрения пользователя включение и использование сериализации может быть выполнено как обычно:
Подкаст Inside Java, эпизод 4: «Record Classes» с Гэвином Бирманом
Подкаст Inside Java, эпизод 14: «Records Serialization» с Джулией Боус и Крисом Хегарти
️ ️ Совет: используйте локальные записи для моделирования промежуточных преобразований.
Сложные преобразования данных требуют от нас моделирования промежуточных значений. До Java 16 типичным решением было полагаться на Pair или аналогичные библиотечные классы-хранители или определять свой собственный (возможно, внутренний статический) класс для хранения этих данных.
Проблема в том, что первый довольно часто оказывается негибким, а второй загрязняет пространство имен, вводя классы, используемые только в контексте одного метода. Также возможно определять классы внутри тела метода, но из-за их подробного характера это редко подходило.
В Java 16 это улучшено и теперь также можно определять локальные записи в теле метода:
Компактный синтаксис классов записи хорошо сочетается с компактным синтаксисом Streams API.
Помимо записей, это изменение также позволяет использовать локальные перечисления и даже интерфейсы.
️ ️Совет: проверьте свои библиотеки
Классы записи не придерживаются соглашенийJavaBeans:
У них нет конструктора по умолчанию.
У них нет set методов.
По этим причинам некоторые инструменты, которые ориентированы на JavaBeans, могут не полностью работать с записями.
Одним из таких случаев является то, что записи нельзя использовать как объекты JPA (например, Hibernate). В списке рассылки jpa-dev обсуждается согласование спецификации с записями Java, но пока я не нашел новостей о состоянии процесса разработки. Однако стоит отметить, что Records можноиспользовать для проекций без проблем.
Большинство других инструментов, которые я проверил (включая Jackson, Apache Commons Lang, JSON-P и Guava ), поддерживают записи, но, поскольку он довольно новый, есть и некоторые острые углы. Например, Jackson, популярная библиотека JSON, была одной из первых, кто начал использовать записи. Большинство его функций, включая сериализацию и десериализацию, одинаково хорошо работают для классов записи и JavaBeans, но некоторые функции для управления объектами еще предстоит адаптировать.
Я советую обновить и проверить свой инструментарий, прежде чем применять классы записи, чтобы избежать сюрпризов, но в целом справедливо предположить, что популярные инструменты уже охватывают большинство функций.
Посмотрите мои эксперименты с интеграцией инструментов для классов записи на GitHub.
Сопоставление с образцом для instanceof
Доступно с:JDK 16 (предварительная версия в JDK 14JDK 15 )
В большинстве случаев за instanceof обычно следует приведение типа:
Так было в старые времена, потому что Java 16 расширяет возможности instanceof и делает этот типичный сценарий менее многословным:
Шаблон представляет собой комбинацию теста ( obj instanceof String ) и переменной шаблона ( s ).
Переменная шаблона создается, только если тест успешен. Она работает почти как обычная не final переменная:
она может быть изменена
она может переопределять объявления полей
если есть локальная переменная с тем же именем, это приведет к ошибке компиляции
Однако к таким переменным применяются особые правила области действия: переменная шаблона находится в области действия, где она точно совпала, что определяется анализом области действия потока выполнения.
Но правило «точно совпадают» также применимо и к частям более сложных условий:
s может использоваться во второй части условия, потому что она оценивается только тогда, когда первая выполняется успешно и instanceof оператор имеет совпадение.
Чтобы привести еще менее тривиальный пример, ранние возвраты и исключения также могут гарантировать совпадения:
Анализ потока видимости работает аналогично существующему анализу потоков выполнения, например проверке на предмет определенного присвоения:
Мне очень нравится эта функция, поскольку она, вероятно, уменьшит ненужное раздувание кода, вызванное явным приведением типов в Java программе. Однако, в отличие от более современных языков, эта функция все еще кажется немного многословной.
Например, в Kotlin вам не нужно определять переменную шаблона:
В случае Java переменные шаблона добавляются для обеспечения обратной совместимости, поскольку изменение типа obj in obj instanceof String будет означать, что при obj использовании в качестве аргумента перегруженного метода вызов может разрешить другую версию метода.
️ Совет: следите за обновлениями
Функция сопоставления с образцом может показаться не такой уж большой проблемой в ее нынешнем виде, но вскоре она получит еще много интересных возможностей.
JEP 405 предлагает добавить функции декомпозиции, чтобы также соответствовать содержимому класса записи или массива:
Кроме того, JEP 406 посвящен добавлению функций сопоставления с образцом для операторов и switch выражений:
В настоящее время оба JEP находятся в статусе кандидата и не имеют конкретной целевой версии, но я надеюсь, что мы скоро увидим их предварительные версии.
Текстовые блоки
Доступно с:JDK 15 (предварительная версия в JDK 13JDK 14 )
По сравнению с другими современными языками, в Java было заведомо сложно выразить текст, состоящий из нескольких строк:
Чтобы сделать это более удобным для программистов, в Java 15 были введены многострочные строковые литералы, называемые текстовыми блоками:
Они похожи на старые строковые литералы, но могут содержать новые строки и кавычки без экранирования.
Их можно использовать везде, где можно использовать старый строковый литерал, и они оба создают похожие строковые объекты.
Текстовые блоки могут быть выровнены с соседним кодом Java, поскольку случайные отступы автоматически удаляются. Компилятор проверяет пробелы, используемые для отступа в каждой строке, чтобы найти строку с наименьшим отступом, и сдвигает каждую строку влево на этот минимальный общий отступ.
Это означает, что, если закрытие «»» находится в отдельной строке, отступ можно увеличить, сдвинув закрывающий токен влево.
Открывающий токен «»» не учитывается при удалении отступа, поэтому нет необходимости выравнивать текстовый блок с ним. Например, оба следующих примера создают одну и ту же строку с одинаковым отступом:
Класс String также предоставляет некоторые программные способы обращения с отступом. Метод indent принимает целое число и возвращает новую строку с заданными уровнями дополнительного отступа, а stripIndent возвращает содержимое исходной строки без всех несущественных отступов.
Текстовые блоки не поддерживают интерполяцию, чего мне очень не хватает. Как сказано в JEP, это может быть рассмотрено в будущем, а до тех пор мы можно использовать String::formatted или String::format :
Руководство программиста по текстовым блокам
Полное руководство по текстовым блокам в Java 13
️ Совет: сохраняйте конечные пробелы
Конечные пробелы в текстовых блоках игнорируются. Обычно это не проблема, но в некоторых случаях они имеют значение, например, в контексте модульного теста, когда результат метода сравнивается с базовым значением.
В этом случае помните о них, и если строка заканчивается пробелом, добавьте \s или \t вместо последнего пробела или табуляции в конец строки.
️ Совет: создавайте правильные символы новой строки для Windows
Однако независимо от того, какую операционную систему вы выберете или как вы кодируете новые строки в исходном коде, текстовые блоки будут использовать одну \n для каждой новой строки, что может привести к проблемам совместимости.
️ Совет: обратите внимание на однотипный отступ
Текстовые блоки хорошо работают с любым типом отступа: пробелы табуляции или даже сочетание этих двух. Однако важно использовать однотипный отступ для каждой строки в блоке, иначе несущественный отступ может быть не удален.
Большинство редакторов предлагают автоформатирование и автоматически добавляют отступ в каждой новой строке, когда вы нажимаете Enter. Обязательно используйте последнюю версию этих инструментов, чтобы убедиться, что они хорошо работают с текстовыми блоками, и не пытайтесь добавлять некорректные отступы.
Полезные исключения NullPointerExceptions
Традиционно исключение NullPointerException дает такой вывод:
Из исключения не очевидно, какой метод в данном случае вернул значение null. По этой причине многие разработчики обычно распределяли такие утверждения по нескольким строкам, чтобы убедиться, что они смогут выяснить, какой шаг привел к исключению.
( Посмотрите пример проекта на GitHub )
Подробное сообщение содержит действие, которое не удалось выполнить (невозможно вызвать getChildNodes ), и причину сбоя ( item(int) есть null ), что значительно упрощает поиск точного источника проблемы.
Таким образом, эта функция хороша для отладки, а также для удобочитаемости кода, поэтому не стоит жертвовать ею по техническим причинам.
Расширение Helpful NullPointerExceptions реализовано в JVM, поэтому вы получаете те же преимущества для кода, скомпилированного с более старыми версиями Java, и при использовании других языков JVM, таких как Scala или Kotlin.
Обратите внимание, что не все NPE получают эту дополнительную информацию, а только те, которые создаются и генерируются JVM при:
чтении или записи null в поля
доступ или присвоение элемента массива (индексы не являются частью сообщения об ошибке)
Также обратите внимание, что эта функция не поддерживает сериализацию. Например, когда NPE генерируется в удаленном коде, выполняемом через RMI, исключение не будет включать полезное сообщение.
️ Совет: проверьте свои инструменты
При обновлении до Java 15 обязательно проверьте свое приложение и инфраструктуру, чтобы убедиться:
имена конфиденциальных переменных не попадают в файлы журналов и ответы веб-сервера
инструменты анализа журнала могут обрабатывать новый формат сообщений
накладные расходы, необходимые для создания дополнительных деталей, приемлемы
Switch выражения
Доступно с:JDK 14 (предварительная версия в JDK 12JDK 13 )
Старый добрый switch получил обновленную версию в Java 14. Хотя Java продолжает поддерживать старый оператор switch, он добавляет в язык новое switchвыражение:
Самое поразительное отличие состоит в том, что эту новую форму можно использовать как выражение. Его можно использовать для заполнения переменных, как показано в приведенном выше примере, и его можно использовать везде, где принято выражение:
Однако есть и другие, более тонкие различия между switch выражениями и операторами switch.
У каждого варианта своя область видимости.
В-третьих, варианты switch выражения являются исчерпывающими. Это означает, что для String, примитивных типов и их оболочек default всегда должен быть определен регистр.
Для enums либо случай default должен присутствовать, или все варианты должны быть охвачены явно. Довольно приятно полагаться на последнее, чтобы убедиться, что все значения учтены. Добавление дополнительного значения к enum переменной приведет к ошибке компиляции для всех switch выражений, в которых она используется.
По всем этим причинам предпочтение switch выражений операторам switch может привести к созданию более удобного в сопровождении кода.
️ Совет: используйте синтаксис стрелок
Switch выражение можно использовать не только с лямбда-подобными случаями формы стрелки. Старый оператор switch с его вариантами с двоеточиями также можно использовать как выражение, используя yield :
Эта версия также может использоваться как выражение, но она больше похожа на старый оператор switch, потому что
варианты выполняются все
варианты имеют одинаковую область видимости
Мой совет? Не используйте эту форму, вместо этого используйте switch выражения с синтаксисом стрелки, чтобы получить все преимущества.
Вывод типа локальной переменной
Доступно с:JDK 11 (без поддержки лямбда в JDK 10 )
Эта функция позволяет нам упростить объявления локальной переменной, опуская явную спецификацию типа:
Хотя это похоже на ключевое слово var в Javascript, речь не идет о динамической типизации.
Прочитайте эту цитату из JEP:
Мы стремимся улучшить взаимодействие с разработчиками за счет сокращения церемоний, связанных с написанием кода Java, при сохранении приверженности Java к безопасности статических типов.
Вот еще один хороший кандидат для вывода типов:
Понятно, что во многих случаях эта функция может улучшить качество кода. Однако иногда лучше придерживаться явного объявления типа. Давайте посмотрим на несколько примеров, когда замена объявления типа на var может иметь неприятные последствия.
️ Совет: помните о удобочитаемости
В первом случае удаление явной информации о типе из исходного кода делает его менее читаемым.
Конечно, IDE могут помочь в этом отношении, но во время проверки кода или, когда вы просто быстро просматриваете код, это может ухудшить читаемость. Например, рассмотрим фабрики или строители: вам нужно найти код, отвечающий за инициализацию объекта, чтобы определить тип.
Вот небольшая загадка. Следующий фрагмент кода использует API даты и времени Java 8. Угадайте типы переменных в следующем фрагменте:
Сделали? Вот решение:
Другая потенциальная проблема заключается в том, что с var что читателю приходится больше полагаться на контекст. Учтите следующее:
️ Совет: обратите внимание на сохранение важной информации о типе
Во втором случае при использовании var удаляется вся доступная информация о типах, поэтому ее невозможно даже вывести. В большинстве случаев эти ситуации улавливаются компилятором Java. Например, var не может вывести тип для лямбда-выражений или ссылок на методы, потому что для этих функций компилятор полагается на выражение в левой части для определения типов.
Поскольку он имеет дело только с generic типами, можно удалить избыточность. Попробуем сделать его короче с var :
Конечно, эта проблема может быть легко решена удалением Diamond оператора:
Другой набор проблем может возникнуть при использовании var с примитивными типами данных:
️ Совет: обязательно прочтите официальные руководства по стилю программирования.
Поскольку с var возникает так много ошибок, хорошо если оно будет использоваться консервативно и только с локальными переменными, область действия которых обычно довольно ограничена.
First Contact With ‘var’ In Java 10
26 Items for Dissecting Java Local Variable Type Inference (Var Type)
Java 10: определение типа локальной переменной
Разрешены private методы в интерфейсах
Доступно с:JDK 9 (Milling Project Coin )
Начиная с Java 8, в интерфейсы можно добавлять методы по умолчанию. В Java 9 эти методы по умолчанию могут даже вызывать private методы для совместного использования кода в случае, если вам необходимо его повторное использование, но вы не хотите раскрывать функциональность публично.
Хотя это не так уж важно, но это логическое дополнение, которое позволяет привести код в порядок в методах по умолчанию.
Оператор Diamond для анонимных внутренних классов
Доступно с:JDK 9 (Milling Project Coin )
Java 7 представила Diamond оператор ( <> ) для уменьшения многословности, позволяя компилятору определять типы параметров для конструкторов:
Однако раньше эта функция не работала с анонимными внутренними классами. Согласно обсуждению в списке рассылки проекта, это не было добавлено как часть исходной функции Diamond оператор, потому что для этого требовалось существенное изменение JVM.
В Java 9 эта небольшая шероховатость устранена, что делает оператор более универсальным:
Разрешено использовать final переменные в качестве ресурсов в операторах try-with-resources
Доступно с:JDK 9 (Milling Project Coin)
Чтобы проиллюстрировать его возможности, сначала рассмотрим усилия, предпринятые для правильного закрытия ресурса в этом типичном примере до Java 7:
С try-with-resources ресурсы могут быть автоматически освобождены, с гораздо меньшим объемом кода:
Несмотря на свою мощь, try-with-resources в Java 9 было несколько недостатков.
Хотя эта конструкция может обрабатывать несколько ресурсов, она может легко усложнить чтение кода. Объявление таких переменных в списке после try ключевого слова немного нетрадиционно по сравнению с обычным кодом Java:
Кроме того, в версии Java 7, если у вас уже есть переменная, которую вы хотите обрабатывать с помощью этой конструкции, вам нужно было ввести фиктивную переменную. (Для примера см. JDK-8068948.)
Чтобы смягчить эту критику, try-with-resources была улучшена обработка final или фактически final локальных переменных в дополнение к вновь созданным:
Совет: следите за освобожденными ресурсами
Следует иметь в виду одно предостережение: теперь можно ссылаться на переменные, которые уже освобождены, с помощью try-with-resources, что в большинстве случаев завершается ошибкой:
Подчеркивание больше не является допустимым именем идентификатора
Доступно с:JDK 9 (Milling Project Coin)
В Java 8 компилятор выдает предупреждение, когда «_» используется в качестве идентификатора. Java 9 пошла дальше, сделав единственный символ подчеркивания недопустимым в качестве идентификатора, сохранив за этим именем особую семантику в будущем:
Улучшенные предупреждения
Доступно с:JDK 9
Наконец, позвольте сказать несколько слов об изменениях, связанных с предупреждениями компилятора в новых версиях Java.
Теперь можно аннотировать частный метод, @SafeVarargs чтобы пометить Type safety: Potential heap pollution via varargs parameter предупреждение как ложное срабатывание. (Фактически, это изменение является частью ранее обсуждавшегося JEP 213: Milling Project Coin ). Узнайте больше о Varargs, Generics и потенциальных проблемах, которые могут возникнуть при объединении этих функций в официальной документации.
Также, начиная с Java 9, компилятор не выдает предупреждения для операторов импорта при импорте устаревшего типа. Эти предупреждения были неинформативными и избыточными, поскольку при фактическом использовании устаревших элементов всегда отображается отдельное предупреждение.
Что дальше: превью функции в Java 16
Sealed (запечатанные) классы
JEP 360 улучшает добавленные в язык sealed классы и интерфейсы, которые можно использовать для ограничения того, какие другие классы или интерфейсы могут их расширять или реализовывать.
Эта функция также улучшает switch выражения. Как и в случае с enum, если возможные значения известны во время компиляции и все случаи обработаны, нет необходимости определять ветвь по умолчанию.
Резюме
В этом посте рассматриваются улучшения, связанные с языком Java, начиная с Java 8. Важно следить за платформой Java, поскольку с новой быстрой периодичностью выпуска новая версия Java выпускается каждые шесть месяцев, добавляя изменения в платформу и в язык.