Java 7 – nowa wersja nadchodzi

lechert

Niniejszy artykuł przybliża nowości wprowadzone do wydania 7 Javy. Nie jest możliwe dogłębne przedstawienie wszystkich zmian i nowych elementów, ponieważ jest ich dużo. Artykuł koncentruje się na zmianach dotyczących samego języka programowania, wspomina o nowościach w API i wsparciu dla wydania przez popularne na rynku IDE.

Java w wersji 7 zawiera w sobie wiele JSR (ang. Java Specification Request) zgłoszonych przez społeczność. Niektóre z nich zostały oficjalnie przeniesione do kolejnej wersji 8 – planowaną na połowę 2012 roku, a większość z nich została zrealizowana w bieżącym wydaniu. Artykuł przedstawia w dużej mierze projekt Coin, zbiór zmian w składni języka oraz nowości w API (ang. application programming interface). Ostateczna wersja nowego SDK (ang. Software Development Kit) będzie dostępna pod koniec lipca bieżącego roku, jednak już teraz można się przyjrzeć nowościom oferowanym przez oczekiwane wydanie 7.

"Małe zmiany”

W nowej wersji JDK 7 wprowadzono kilka drobnych nowości. Pierwszą z nich jest funkcjonalność umożliwiająca definiowanie literałów binarnych. Obok znanych już literałów zapisanych w systemach ósemkowych i szesnastkowych, pojawiła się możliwość np. definiowania stałych w systemie dwójkowym. Prezentowany program z listingu 1 wydrukuje na konsolę liczbę 100.

Listing 1. Literały binarne

public static void main(String[] args) {
        byte binary = 0b01100100;
        byte hex    = 0x64;
        byte octal  = 0144;
        if (binary == hex) {
            System.out.println("0b01100100 = 0x64 = 100");
        }
        if (binary == octal) {
            System.out.println("0b01100100 = 0144 = 100");
        }
 }

Przytoczony przykład nie jest niczym nadzwyczajnym, a literały binarne obok heksadecymalnych i ósemkowych można odbierać jako uzupełnienie języka Java. Kolejną zmianą, która niezaprzeczalnie ma duży walor estetyczny są podkreślenia w literałach. Funkcja ta szczególnie przydaję się przy dużych liczbach. W celu zwiększenia czytelności kodu źródłowego zasadne jest oddzielić wiodące cyfry podkreśleniami. Zastosowanie podkreśleń prezentuje listing 2 oraz rysunek 1.

Listing 2. Podkreślenia

public static void main(String[] args) {
        long value1 = _100; 
        long _value2_ = 100_;  
        long _value3  = 110___000;
        long value4_ = 25_000_000;
        double value5 = 100_.000d;

        double value5 = 100._000d;

        System.out.println("Value 1 _100 is illegal");
        System.out.println("Value 2 100_ is illegal");        
        System.out.println("Value 3 110___000 is legal = " + _value3);
        System.out.println("Value 4 25_000_000 is legal = " + value4_);
        System.out.println("Value 5 100_.000d is illegal");
        System.out.println("Value 6 100._000d is illegal");
}

llechert_jdk7_pix001.png
Rysunek 1. Podkreślenia - wyniki działania programu z listingu 1 (w załacznikach)

Na pewno warto również przybliżyć reguły, które obowiązują przy tworzeniu podkreśleń w literałach. W przeciwieństwie do konwencji nazewniczych zmiennych w Javie, nie można stosować podkreśleń na początku i na końcu (jako prefiks i sufiks) literału. Dozwolone jest jednak łączenie podkreśleń. Literał zdefiniowany jako 110_____000 będzie jak najbardziej poprawny oraz reprezentuje cyfrę 110000. Błędy kompilacji generują również podkreślenia zapisane przed i po znaku kropki w literałach zmiennoprzecinkowych. Typy generyczne wprowadzone w Javie 5.0 są często wykorzystywanymi konstrukcjami w projektach. JDK 7 dostarcza również drobne modyfikacje dla tych typów. W przypadku definiowania złożonych kolekcji we wcześniejszych wersjach należało nadmiarowo wskazywać typy w definicjach zmiennych. Sytuację obrazuje konstrukcja Map<String, List<String>> collection = new HashMap<String, List<String>>(); W nowej wersji programista jest zwolniony z ponownego wpisywania typów dla kolekcji za operatorem new. Przykład w Java 7 wygląda następująco: Map<String, List<String>> collection = new HashMap<>(); Konstrukcję można wykorzystać również w przypadku kreowania obiektów z poziomu ciała metody oraz zastosować krótszą składnię dla rozkazu zwracającego instancję obiektu: return new HashMap<>(); Nie są to wszystkie drobne zmiany języka, jednak następną ciekawą propozycją jest wsparcie dla klasy String w instrukcji wyboru.
Switch ze stringiem

Tej popularnej instrukcji używamy jeśli chcemy w zależności o od pewnego wyrażenia wykonać jeden z kilku fragmentów - bloków kodu. Dotychczas do wersji 6.0 Javy i wcześniejszych switch mógł operować na wartościach typu char, byte, short, int, ich typach opakowujących i dalej typach wyliczeniowych. W nowej wersji specyfikacja języka została rozszerzona o typ String. Przykład z listingu 3 przedstawia użycie typu łańcuchowego w instrukcji wyboru.

Listing 3. Przykład użycia instrukcji switch z klasą String

public void printSchedule(String day) {

        switch (day) {
            case "Sunday":
                System.out.println("Sunday – Free time");
                break;
            case "Monday":
                System.out.println("Monday – Training");
                break;
            case "Tuesday":
                System.out.println("Tuesday – Training");
                break;
            case "Wednesday":
                System.out.println("Wednesday – Training");
                break;
            case "Thursday":
                System.out.println("Thursday – Time for customers");
                break;
            case "Friday":
                System.out.println("Friday – Reports");
                break;
            case "Saturday":
                System.out.println("Saturday – Free time");
                break;
            default:
                System.out.println("Error – unknown parameter");
        }
}

Dla wielu programistów „String w switchu” wydawał się naturalny, jednak taka konstrukcja nie była dostępna do wersji 7. Prezentowana metoda z zastosowaniem typu String z pewnością poprawia czytelność kodu źródłowego, jak również pozwala unikać stosowania wielopoziomowych instrukcji if, sprawdzających wartość zmiennych typu łańcuchowego. Jest to małe usprawnienie, jednak zmiana ta ułatwia implementację i upraszcza rozwiązywanie różnych problemów.

IDE i support dla JDK 7

Każdy z programistów w projektach wykorzystuje wskazane lub ulubiony edytor lub IDE (ang. Integrated Development Environment) programując w języku Java. Na rynku dostępnych jest wiele produktów, które z powodzeniem stosowane są w projektach komercyjnych. Do najpopularniejszych należą: Eclipse, Netbeans, IntelliJ. W czasie pisania artykułu wsparcie dla JDK 7 oferują Netbeans i IntelliJ. W przypadku Eclipsa wsparcie planowane jest w kolejnych wydaniach. Informacje o wersjach IDE i wsparciu dla Javy w wersji 7 przedstawia poniższa lista.

  • Eclipse - planowane na wrzesień 2011,
  • Netbeans - w wersji 7.0,
  • IntelliJ - w wersji 1.5.

Obsługa wyjątków

Obsługa sytuacji wyjątkowych w języku Java jest ważnym elementem dobrych programów. Opisywana wersja oferuje trzy nowe elementy. Są nimi: multi-catch, ulepszone ponowne dystrybuowanie wyjątków oraz instrukcja try-with-resources. Multi-catch polega na definiowaniu kilku przechwytywanych wyjątków w jednej klauzuli catch. Z pewnością jest to element, którego poprawne użycie można opanować w kilka minut. Do odseparowania poszczególnych obiektów różnych klas wyjątków kontrolowanych należy zastosować znak potoku z systemu Unix. Rozwiązanie to z pewnością eliminuje duplikaty w kodzie źródłowym oraz sprawia, że staje się on o wiele bardziej czytelniejszy. Listing 4 prezentuje wycinek bloku try-catch z zastosowaniem mechanizmu multi-catch.

Listing 4. Przykład zastosowania mechanizmu multi-catch

try {
   // Wywolania mogace wygenerowac wyjatek
} catch (IOException | InterruptedException ex) {
            System.err.println(ex.toString());
}

Niekiedy programista jest w sytuacji, kiedy musi przechwycić wyjątek, wykonać kilka operacji związanych z wygenerowanym błędem oraz przekazać obiekt wyjątku dalej. Mechanizm przekazywania (ang. rethrow) w wersji 7 jest wygodny i bardziej precyzyjny. Przyjrzyjmy się przykładowi z listingu 5.

Listing 5. Przykłady przekazywania wyjątków w Java 6 i 7

// Przyklad 1 - Java 6

public static void main(String[] args) throws IOException, SQLException {
        try {
        } catch (SQLException ex) {
            System.err.print(ex.getMessage());
            throw ex;
        } catch (IOException ex) {
            System.err.print(ex.getMessage());
            throw ex;
        }
}

// Przyklad 2 - Java 6

public static void main(String[] args) throws Exception {
        try {
        } catch (Exception ex) {
            System.err.print(ex.getMessage());
            throw ex;
        }
}

// Przyklad 3 - Java 7

public static void main(String[] args) throws IOException, SQLException{
        try {
        } catch (final Exception ex) {
            System.err.print(ex.getMessage());
            throw ex;
        }
}

W wszystkich przykładach przed dystrybucją wygenerowanego wyjątku wywoływana jest operacja logowania błędu. W przykładzie 1 mamy do czynienia z redundancją kodu źródłowego, ponieważ blok catch wykonuje w każdym przypadku te same operacje dwie operacje. W sytuacji konieczności dodania nowych wyjątków cały blok try-catch będzie się powiększał oraz dodawane będą prawdopodobnie te same wywołania logujące błędy i przekazujące wyjątek. Z czasem takie postępowanie wpłynie również na czytelność klasy. W przykładzie 2 konkretne wyjątki można zastąpić klasą bazową Exception. W tym przypadku dany jest tylko jeden blok catch oraz unikamy powtarzania wywołań. W Java 6 taki kod metody main bez zmiany listy generowanych wyjątków w klauzuli throws nie będzie się kompilował. Konieczna jest również korekta listy wyjątków w throws, co powoduje że staje się ona bardziej ogólna, umożliwiająca wygenerowanie dowolnego, kontrolowanego wyjątku – potomka klasy Exception. W Java 7 umożliwiono zastosowanie słowa kluczowego final w bloku catch (przykład 3). W tym przypadku modyfikacja sygnatury metody nie jest konieczna. Innym rozwiązaniem, stosownym w tym przypadku jest zastąpienie poszczególnych bloków catch mechanizmem multi-catch. Ostatnią nowością jest blok obsługujący wyjątki try-with-resources i automatyczne zwalnianie zasobów. Kod źródłowy i działanie tej nowej konstrukcji można zobaczyć na listingu 6.

Listing 6. Przykład dla bloku try-with-resources

// Przykład 1 – Java 6

InputStream is = null;
try {
     is = new FileInputStream(new File("conf.cfg"));
} catch (IOException ex) {
            System.err.println(ex.getMessage());
} finally {
     if (is != null) {
         try {
              is.close();
         } catch (IOException ex) {
              System.err.println(ex.getMessage());
         }
}

// Przykład 2 – Java 7

try (InputStream is = new FileInputStream(new File("conf.cfg"))) {
  ...
} catch (IOException ex) {
}

W nawiasie w bloku try można umieścić wiele instrukcji kreujących obiekty, które wymagają zasobów systemowych. Warunek, który muszą spełniać klasy wykorzystywane w tym bloku to zaimplementowana metoda close z interfejsu AutoCloseable. Przykład 3 pokazuje, że za pomocą bloku try-with-resources kod staje się krótszy i prostszy oraz podobnie jak w idei Garbage Collectora, programista nie musi martwić się o zamykanie plików i wyjątki związane z tą operacją. Oprócz zmian w samym języku, JDK 7 dostarcza również nowe elementy w API.

API i inne nowości

Nowe rozszerzenia w bibliotekach IO zostały już wprowadzone w wersji Javy 1.4. W tym czasie pojawił się nowy pakiet java.nio. To nowe rozszerzenie nazwano New IO (NIO). W najnowszej wersji platformy nazwa rozszerzenia została zmodyfikowana do NIO 2.0. Pakietem na który zasługuje uwagę jest java.nio.file. Zawiera on wiele nowych i użytecznych narzędzi oraz nowe klasy, które ułatwiają przetwarzanie plików. Przykładem jest klasa Files, która umożliwia testowanie i kompleksowe pozyskiwanie informacji o elementach systemu plików oraz np. tworzenie wielu katalogów jednocześnie. Kolejną klasą jest Path. Klasa ta wspiera wcześniej wymienioną klasę Files i służy do przekazywania obiektów klasy Path jako parametrów aktualnych w wielu metodach pierwszej klasy. Do użytecznych metod klasy Files można zaliczyć readAllBytes(Path path) i createSymbolicLink(Path path ...). Pierwsza z metod odczytuje wszystkie dostępne bajty z podanego w ścieżce źródła, druga tworzy dowiązanie symboliczne w systemie. Na temat nowych elementów w API i w nowej wersji biblioteki NIO można napisać bardzo dużo. Zachęcam jednak do własnych eksperymentów i poznawania jej możliwości. Na szczególną jednak uwagę zasługuje nowe API jakim jest WatchService. Pełni on rolę elementu nasłuchującego (ang. listener) dla zdarzeń, które wystąpiły w systemie plików. WatchService jest klasą abstrakcyjną oraz reaguje na typy zdarzeń, które zdefiniowane zostały w klasie StandardWatchEventKinds. Typy zdarzeń zostały zamieszczone w ramce. Klasa ta jest na tyle interesująca, że stosownym jest zaprezentowanie przykładu pokazującego jej możliwości.

StandardWatchEventKinds i typy zdarzeń

+ENTRY_CREATE – element np. plik, katalog został utworzony w systemie plików,
+ENTRY_DELETE – element został usunięty z systemu plików,
+ENTRY_MODIFY – element został zmodyfikowany w systemie plików,
+OVERFLOW – specjalna flaga umożliwiające rozpoznawanie sytuacji np. utraty powiadomienia o zdarzeniu.

Kod programu z listingu 7 prezentuje jeden ze sposobów wykorzystania klasy WatchService. W katalogu doc należy utworzyć dowolny plik. Program działa jak polecenie echa i drukuje informacje o utworzeniu pliku w podanym katalogu.

Listing 7. Listener dla operacji tworzenia plików

public static void main(String[] args) {
        System.out.println("Starting ...");
        Path path = FileSystems.getDefault().getPath("doc");
        try {
            WatchService watcher = FileSystems.getDefault().newWatchService();
            WatchKey key = path.register(watcher, StandardWatchEventKinds.ENTRY_CREATE);
            while (true) {
                System.out.println("Listening ...");
                WatchKey currentKey = watcher.take();
                if (currentKey.equals(key)) {
                    System.out.println("File was created in the directory " + path.getFileName());
                    break;
                }
            }
        } catch (IOException | InterruptedException ex) {
            System.err.println(ex.toString());
        }      
}

Zmiany odnoszą się do wielu obszarów platformy standardowej. Obejmują one opisywane, dotyczące samego języka Java i API, jak również innych bibliotek np. JDBC czy też Swing. Duża ilość nowych funkcji na pewno przyczyni się do powstania kolejnych artykułów na temat JDK 7, a ja zachęcam czytelników do własnych eksperymentów z nowym produktem.

Wybrane, nowe elementy i modyfikacje w JDK 7

  • VM – wsparcie dla języków dynamicznych,
  • JDBC – nowa wersja 4.1,
  • Coin – zmiany i nowe elementy w języku Java,
  • Intl/i18n – wsparcie dla Unicode 6.0,
  • Swing – komponent JLayer i Nimbus look and feel,
  • Core – upgrade architektury class-loadera,
  • I/O – nowe przydatne narzędzia,
  • Management – ulepszenie w MBeans,
    … i inne.

Podsumowanie

Opisane zmiany w artykule nie wyczerpują w pełni tematu. W chwili obecnej można powiedzieć, że w nowej Javie pojawiły się elementy usprawniające codzienny development. Wszystkie nowe elementy w konstrukcjach Javy są może znane z innych języków programowania, jednak są one odpowiedzią na wymagania zgłoszone przez programistów. Oprócz przedstawionych nowości położono duży nacisk na stabilność i usunięcie błędów z poprzednich wydań. W czasie pisania artykułu dostępny był Build 146 nowej platformy. Wersja finalna może się nieco różnić od stanu obecnego. Proponuję sprawdzić samemu.

Linki

0 komentarzy