C, PHP, VB, .NET

Дневникът на Филип Петров


* Автоматично определяне на тип в Java

Публикувано на 18 октомври 2018 в раздел ПИК3 Java.

С идването на Java 10 се въведе едно вероятно дълго чакано нововъведение. Става въпрос за „local type inference“, което аз си позволявам да преведа като „автоматично определяне на тип за локални променливи“. Надявам се да намеря по-къс термин за това определение.

Една тенденция при развитието на повечето езици за програмиране винаги е била да се опростява синтаксиса така, че програмистите да пишат по-изчистен и лесночетим код. Java от самото си начало и до ден днешен не е била сред водещите езици по този критерий – едно от честите оплаквания на програмистите са сложните езикови конструкции.

Преди години с Java 7 се направи първата крачка към съкращаване на кода чрез автоматично типизиране при генерични типове. Нека погледнем следния код, с който дефинираме речник, в който на всяка дума отговаря списък от други думи:

Map<String, ArrayList<String>> sinonimi = new HashMap<String, ArrayList<String>>();

Дори тази инак проста конструкция изглежда доста неприятна за четене. С Java 7 се предприе първата стъпка към опростяването на такива конструкции. Прие се, че след като веднъж генеричните типове са дефинирани в ъгловите скоби в лявата част на израза, няма смисъл те да се повтарят и в дясната част – компилаторът може сам да определи това. Така горният израз се опростява по следния начин:

Map<String, ArrayList<String>> sinonimi = new HashMap<>();

При Java 10 се погледна и от другата страна на знака за присвояване – лявата част на изразите. Въведе се ключова дума „var“, с която да може да се определя автоматично типа на променливата спрямо израза в дясната част. Например вместо:

String str = "Hello World";

вече можем да пишем:

var str = "Hello World";

Идеята тук е проста – очевидно е, че в дясната част стои обект от тип String, следователно е възможно типа на променливата „str“ да бъде определен автоматично чрез него. Така например изразът за дефиниране на речник от по-горе би могъл да бъде записан като:

var sinonimi3 = new HashMap<String, ArrayList<String>>();

Имайте предвид, че „var“ не е точно ключова дума – заради обратната съвместимост с по-стари версии на Java е възможно да пишете изрази като например:

var var = 5;

Единствено от Java 10 нататък няма да е възможно да дефинирате клас с име „var“ и това е единствената липса на обратна съвместимост. Все пак хората са счели, че подобно име на клас едва ли се използва в практиката.

Чрез тези две нововъведения в Java вече става наистина ненужно остарялото и тромаво задължение програмиста да пише типа на променливата два пъти. Например дефинирането на масив вече може да се прави не чрез класическото:

int[][] twoDimArr = new int[5][3];

а просто с

var twoDimArr = new int[5][3];

Използването на тази техника с „var“ може да е полезно в няколко аспекта. На първо място то е много удобно за подобряване на четимостта на кода от гледна точка на „подравняване на променливите“. Ето един пример за това. Ако разгледаме следния код:

ServerSocket servSock = new ServerSocket(2000);
Socket sock = servSock.accept();
DataInputStream in = new DataInputStream(sock.getInputStream());
DataOutputStream out = new DataOutputStream(sock.getOutputStream());

ще видим, че всеки от типовете в лявата част е с различна дължина (брой букви). Преправянето на кода с използване на ключова дума „var“ ще подравни имената на променливите едно под друго:

var servSock = new ServerSocket(2000);
var sock = servSock.accept();
var in = new DataInputStream(sock.getInputStream());
var out = new DataOutputStream(sock.getOutputStream());

Използването на тази техника обаче крие и опасности. Ако разгледаме следния пример:

var cphpvb = new URL("https://www.cphpvb.net");
var connection = cphpvb.openConnection();

ще се наложи да се замислим – от какъв тип е променливата „connection“? За човек, който преглежда чужд код, това не е очевидно – трябва първо да се провери какъв е типа на връщаните стойности на метод „openConnection“ от клас URL. Затова аз лично не бих препоръчал техниката с „var“ да се използва за подобни декларации, когато се пише с учебни цели. За такива случаи би било по-подходящо да използвате стария синтаксис:

URL cphpvb = new URL("https://www.cphpvb.net");
URLConnection connection = cphpvb.openConnection();

На обратния полюс е препоръката ми при особено дълги декларации, които се срещат често при корпоративните приложения, т.е. в практиката. Например вместо ужасно изглеждащото:

UserOrderProcessor<RegisteredUser, MakeOrder<TeddyBear>> orderProcessor = createUserOrderProcessor(user, order);

вероятно би било по-добре да се използва:

var orderProcessor = createUserOrderProcessor(user, order);

Да, по този начин не става очевидно за проверяващия кода какъв е типа на променливата orderProcessor, но за сметка на това кодът стана много по-чист и лесен за четене. Ползата тук е значително по-голяма от вредата.

Имайте предвид, че въвеждането на ключовата дума „var“ не променя по никакъв начин факта, че Java е строго типизиран език за програмиране. Автоматичното типизиране се извършва от компилатора и в байткода на компилираните класове се записва реалния тип данни. Тоест автоматичното типизиране не се прави по време на изпълнение, а по време на компилация. Тоест няма да може да променяте типа на променливите динамично. Следното няма да работи:

var x = 5;
x = "Hello";

Грешката при този пример ще е очакваната „String cannot be converted to int“. Това е сериозна разлика от езици като например JavaScript и PHP.

Други удобни места, където може да използвате автоматичното типизиране с var е при цикли for:

int[] arr = IntStream.range(1, 101).toArray();
for(var i: arr) System.out.println(i);

Както и в блокове try:

try(var s = new Scanner(new FileReader("C:\\authors.txt"))){
   ...
}

Важно ограничение за var е, че важи само за локални променливи. Не можете да дефинирате член-променлива с var. Това е направено не защото е технически невъзможно в Java, а защото се счита за лоша практика. Също така не може да използвате var за дефиниране на входен или изходен параметър на метод. Имайте също предвид, че компилатора на Java не използва полиморфизъм при определянето на типа. Например следния код не може да се компилира:

var arr = new ArrayList<String>(10);
arr = new LinkedList<String>();

Не е възможно, защото типа на променливата arr не се определя от компилатора като List, а като ArrayList.

Заключение

Остава за си отговорим на въпроса къде да използваме var и къде не. Очевидно не би било полезно да дефинирате всичките си променливи с var от тук насетне – това няма да подобри нещата. Използвайте var само в случаите, когато това очевидно подобрява четимостта на кода.

Колкото до автоматичната типизация при генеричните типове, за която споменах в началото на статията, тя е винаги препоръчителна.

 



Добави коментар

Адресът на електронната поща няма да се публикува


*