Пробуждането на Тигъра<br>Новите възможности в J2SE 5.0
Категория: Интернет
вторник, 10 Февруари 2004 0:00ч
На 30 септември 2004 г. Sun Microsystems официално обяви излизането на новата версия 5.0 на Java 2 Platform Standard Edition (J2SE), позната под кодовото име Тигър.
За почти четири години работа по J2SE 5.0 повече от 150 експерти са участвали в изграждането на новия облик на платформата с акцент върху леснотата на ползване, общата производителност, разширяемостта и управлението.
Два факта около създаването и официалното обявяването на Тигър са особено любопитни. Първо, това е може би най-модифицираната до момента версия на спецификацията (над 100 мащабни нововъведения и доста по-незначителни, както и такива, засягащи самия език Java). и второ - процесът на разработка е бил сериозно подпомогнат от Java общността (над 4 000 получени писма и 25 000 реда код са добавени от ентусиасти).
Промените в J2SE засягат множество аспекти на платформата - производителността, самия език Java, вируталната машина, базовите библиотеки и тези, обслужващи интеграционните услуги, потребителския интерфейс, различните инструменти и поддържани архитектури.
Разбира се, най-основни в ежедневието на разработчиците са промените, касещи синтаксиса и семантиката на езика. Тук ще представя накратко най-интересните промени и нововъведения именно в самия език Java съгласно новата спецификация Тигър.
Параметризирани типове
Обикновено за илюстрация на типизация се използват контейнерни типове. Интерфейсът Collection например, който стои в основата на цяла йерархия от класове/интерфейси, реализиращи контейнери за данни, е имплементиран чрез подинтерфейсите си в най-популярните структури (например Vector, ArrayList и т.н.) е променен с цел поддръжка на параметризирани типове. Нека разгледаме следния пример:
1: public void fillData() {
2:
3: List data = new ArrayList();
4:
5: data.add(new String(John Keats));
6: data.add(new String(The Eve of St. Agnes));
7: data.add(new Integer(1819));
8:
9: displayData(data);
10:
11: }
Тук в ArrayList обекта data записваме данни за литературни произведения, чийто формат е автор, произведение, година на написване. Тъй като по време на изпълнение всички колекции са колекции, съдържащи елементи от тип Object, не е проблем да предадем на метода add два обекта от тип String и един от тип Integer (редове 5 и 7). Затруднението настъпва, когато обектите трябва да бъдат извлечени и записани в някаква променлива.
1: public void displayData(Collection col) {
2:
3: Iterator it = col.iterator();
4:
5: while (it.hasNext()) {
6:
7: String element = (String) it.next();
8:
9: System.out.println(element);
10:
11: }
12: }
Изпълнението на горния код след извикване на fillData() ще доведе до ClassCastException на ред 7, когато процесът достигне до обработката на Integer данната. Използвайки новата възможност за параметризация на типовете, разглеждания пример може да се напише така:
1: public void fillData() {
2:
3: List
4:
5: data.add(new String(John Keats));
6: data.add(new String(The Eve of St. Agnes));
7: data.add(new Integer(1819));
8:
9: displayData(data);
10:
11: }
12:
13: public void displayData(Collection col) {
14:
15: Iterator
16:
17: while (it.hasNext()) {
18:
19: String element = it.next();
20:
21: System.out.println(element);
22:
23: }
24: }
Приема се, че записът List
При опит за компилация на така преправения код ще се получи съобщение за грешка, информиращо, че не може да се добавят елементи от тип Integer в колекция от тип String.
Използването на показаната (ред. 3 и 15) параметризация гарантира проверката на типовете във фазата на компилация и премества откриването на такива грешки към по-ранна фаза от процеса на изпълнение. Освен възползване от параметризацията на различни класове (като ArrayList), възможно е параметризирането и на собствени такива.
За да може един клас да поддържа параметризация, то неговата декларация трябва да бъде леко изменена, както и да се вземат някои допълнителни мерки в методите му. Нека за пример погледнем извадка от кода на интерфейса List:
1: public interface List
2:
3: void add(E x);
4:
5: Iterator
6:
7: ...
8:
9: }
Тук Е е формален параметър за типа и може да се използва почти както един обичаен тип (включително и за указване на типа на аргумент на метод, както е видно). Ако се върнем към показания пример с авторите, то по време на компилация на мястото на E ще застане действителният тип на аргумента - String.
Параметризираните типове са изключително полезни - те помагат да се създава обща функционалност за множество типове данни, които могат да се проверяват за типова безопасност по време на изпълнение. От гледна точка на производителността тази нова функционалност не оказва съществено влияние, тъй като обработките, от които се нуждае, се извършват предимно във фазата на компилация.
Цикли
Основното подобрение на цикъла for е неговата възможност да обхожда колекции и масиви, без да е нужно използването на допълнителна променлива (за индекс) или итератор. Ако преправим първоначалния вариант на displayData така, че да използва for цикъл, то би се получило нещо такова:
1: public void displayData(Collection col) {
2:
3: for (Iterator it = col.iterator(); it.hasNext();) {
4:
5: String element = (String) it.next();
6:
7: System.out.println(element);
8:
9: }
10: }
С новия синтаксис горният пример може да се трансформира в по-кратък и ясен код, а именно:
1: public void displayData(Collection
2:
3: for (String str : col) {
4:
5: System.out.println(str);
6:
7: }
8:
9: }
В този случай е прието двуеточието да се чете като в. Или редът for (String str : col) се тълкува като за всеки String str в col.
Използването на това подобрение премахва и една често допускана грешка при вложен обход с итератори, а именно показаната по-долу:
1: List suits = ...;
2: List ranks = ...;
3: List sortedDeck = new ArrayList();
4:
5: for (Iterator i = suits.iterator(); i.hasNext(); )
6:
7: for (Iterator j = ranks.iterator(); j.hasNext(); )
8
9: sortedDeck.add(new Card(i.next(), j.next()));
Лесно се вижда, че този код води до NoSuchElementException на ред 9 заради прекалено многото извиквания на i.next(). В практиката избягването на този проблем често се решава така:
1: for (Iterator i = suits.iterator(); i.hasNext(); ) {
2:
3: Suit suit = (Suit) i.next();
4:
5: for (Iterator j = ranks.iterator(); j.hasNext(); )
6:
7: sortedDeck.add(new Card(suit, j.next()));
8:
9: }
Въпреки това използването на новите възможности на J2SE свеждат ситуацията до едно доста по-елегантно решение:
1: for (Suit suit : suits)
2:
3: for (Rank rank : ranks)
4:
5: sortedDeck.add(new Card(suit, rank));
Същият механизъм може да се използва и за обхождане на масиви. Например за намирането на сумата на елементите на масив от целочислен тип може да се използва следният подход:
1: public int getSum(int array[]) {
2:
3: int sum = 0;
4:
5: for(int i : array) {
6:
7: sum += i;
8:
9: }
10:
11: return sum;
12: }
Една идея, която може да ни хрумне веднага, е, че ако имаме някакъв обхождащ метод, например
1: void printCollection(Collection col) {
2:
3: Iterator it = col.iterator();
4:
5: for (int i = 0; i < col.size(); i++) {
6:
7: System.out.println(it.next());
8:
9: }
10:
11: }
можем да го трансформираме така:
1: void printCollection(Collection

