1. Синтаксичний цукор

Програмісти люблять, коли якийсь складний код або логіку можна вмістити у кілька рядків, і водночас код буде компактним і легко читатись. А розробники мов іноді допомагають їм у цьому.

Хитрі особливості мови, які дають змогу використовувати коротший шлях (писати менше коду), називають синтаксичним цукром. Хоча, щиро кажучи, у Java його зовсім небагато.

Розробники мови зробили все, щоб усунути із Java всю можливу надлишковість. Якщо в С++ можна щось зробити десятьма способами, то в Java те саме можна зробити переважно лише одним.

Проте така уніфікація не подобається ні Java-програмістам, ні авторам Java. Тож інколи вони спрощують життя звичайним хлопцям і дівчатам, як-от ми з вами.

Ви, до речі, вже познайомилися з однією річчю, яку можна віднести до синтаксичного цукру, — це autoboxing і unboxing. Порівняйте:

Довгий код Компактний код
Integer a = new Integer(5);
int b = a.intValue();
Integer a = 5;
int b = a;
int b = 5;
Integer c = new Integer(b);
int b = 5;
Integer c = b;
Integer a = new Integer(1);
int b = 1;
if (a.intValue() == b)
{
   ...
}
Integer a = 1;
int b = 1;
if (a == b)
{
   ...
}

Замість довгого коду, як ліворуч, ви можете писати компактніший код, як праворуч. А розумний Java-компілятор на основі короткого коду сам згенерує його повну версію. Це і є синтаксичний цукор.


2. Виведення типу змінної – var

У Java 11 компілятор став ще розумнішим і тепер може визначити тип створюваної змінної за типом значення, яке їй присвоюють. Отакий вигляд у коді це має:

var ім'я = значення;

де ім'я — це ім'я нової змінної, значення — її початкове значення, а var — це ключове слово, що використовується для оголошення змінної. Тип змінної ім'я буде таким самим, як тип значення, що їй присвоюють.

Приклади:

Як цей код бачимо ми Що бачить компілятор
var i = 1;
int i = 1;
var s = "Привіт";
String s = "Привіт";
var console = new Scanner(System.in);
Scanner console = new Scanner(System.in);
var list = new ArrayList<String>();
ArrayList<String> list = new ArrayList<String>();
var data = new int[]{1, 2, 3};
int[] data = new int[]{1, 2, 3};

Компілятор сам визначає або, як ще кажуть, виводить тип змінної на основі значення, яке їй присвоюють.

Чимало списів було зламано в суперечках програмістів щодо того, чи варто додавати таку можливість у мову. Багато хто побоювався, що використанням var почнуть зловживати і прочитність коду сильно погіршиться.

Частка правди в цьому є, отож найкраще використовувати var там, де це підвищує прочитність коду. Наприклад, у цих двох випадках:

Випадок 1: значення змінної чітко вказує на її тип

Код Пояснення
var stream = url.getInputStream();
Змінна має тип InputStream
var name = person.getFullName();
Змінна має тип String

А от у цих випадках використовувати var не варто. Нумо скажіть, який тип має змінна?

Код Пояснення
var result = task.execute();
Тип змінної визначити важко
var status = person.getStatus();
Тип змінної визначити важко

Випадок 2: тип змінної не є важливим для розуміння коду

Часто в коді трапляються ситуації, коли для змінної не викликаються ніякі методи — вона просто використовується для тимчасового зберігання якихось даних. Використання var тут абсолютно не погіршує розуміння коду:

Довгий код Компактний код
var data = stream.getMetaData();
storage.save(data)
Ми отримали метадані з потоку stream і зберегли їх у сховищі storage. Який саме тип мала змінна data — неважливо.

Золота середина

Зараз наведемо три способи запису одного й того самого коду. Використання var буде оптимальним варіантом.

Код Примітка
dest.writeHeaderInfo(src.getFileMetaInfo());
Надто компактно
var headerInfo = src.getFileMetaInfo();
dest.writeHeaderInfo(headerInfo);
Ідеально
FileMetaInfo headerInfo = src.getFileMetaInfo();
dest.writeHeaderInfo(headerInfo);
Надто детально

Перейшовши від варіанту в рядку 1 до варіанту в рядку 2, ми завдяки імені змінної (headerInfo) додали коду трохи прочитності. Тепер ясно, що метод повертав не просто метаінформацію, а інформацію про заголовок.

Третій варіант був би надлишковим. Що з того, що headerInfo має тип FileMetaInfo — це й так було майже зрозуміло з методу getFileMetaInfo(). Набагато цікавішим є призначення цієї метаінформації.



3. Випущення типу — оператор diamond: <>

Ще до появи оператора var були спроби навчити компілятор виводити типи колекцій. Погодьтеся, цей запис на вигляд є трохи надлишковим:

ArrayList<String> list = new ArrayList<String>();

Починаючи із сьомої версії Java в записі типу колекції можна було випускати (не писати) тип елементів колекції, якщо він указаний під час оголошення змінної. Тобто наведений вище код можна записати в дещо скороченому вигляді:

ArrayList<String> list = new ArrayList<>();

Як бачите, другий раз писати тип String вже не потрібно. Не так круто, як з оператором var, але свого часу й це здавалося досягненням.

Порожні трикутні дужки в типі колекції отримали назву оператор diamond: дві дужки віддалено нагадували силует діаманта.

Використовувати одночасно var і оператор diamond не вийде, так писати не можна:

var list = new ArrayList<>();

Інформації про тип, який зберігає колекція, зовсім не залишається.



4. Подвійні фігурні дужки

Пам'ятаєте швидку ініціалізацію масиву?

Ми там просто перелічували значення у фігурних дужках:

Приклади
int[] data = new int[] {1, 2, 3, 4, 5, 6, 7};
int[] data = {1, 2, 3, 4, 5, 6, 7};

Авторам мови Java сподобалася ідея використовувати фігурні дужки для спрощеного запису даних у масив. Але як бути з колекціями?

Для колекцій теж вистачило фантазії: дозволили використовувати трюк із подвійними фігурними дужками.

З цукром Без цукру
var list = new ArrayList<String>()
{{
   add("Привіт");
   add("Як");
   add("Справи");
}};
var list = new ArrayList<String>();

list.add("Привіт");
list.add("Як");
list.add("Справи");

Якщо компілятор зустріне код у прикладі ліворуч, він перетворить його на код праворуч.

Код не стає набагато компактнішим. Тут радше економія на дрібницях: не потрібно щоразу писати list. Це може бути вигідно, якщо ім'я змінної дуже довге.

Тож, зустрівши в чийомусь проєкті такий код, не дивуйтеся 🙂