Triki w Javie, których prawdopodobnie nie znasz

Java jest rozbudowanym językiem, który kryje wiele ciekawych funkcji, z których nie zdają sobie sprawy nawet programiści z kilkuletnim doświadczeniem. W tym wpisie przedstawimy 3 ciekawe elementy języka, które pozwolą wam zabłysnąć i prawdopodobnie zagiąć informatyczne towarzystwo :)

wth-is-this

1. Etykiety

Pierwsza funkcjonalność to możliwość ustawienia etykiet przed pętlami w celu wygodniejszego ich przerywania. Standardowo mając tablicę dwuwymiarową i chcąc sprawdzić, czy istnieje w niej podana wartość moglibyśmy zapisać:

public class Loop {
    public static void main(String[] args) {
        int[][] tab = {{1, 2, 3},
                       {4, 5, 6},
                       {7, 8, 9}};
        boolean found = false;
        int searchValue = 5;
        for(int i=0; i<tab.length && !found; i++) {
            for(int j=0; j<tab[i].length && !found; j++) {
                if(tab[i][j] == searchValue)
                    found = true;
            }
        }
        
        if(found) {
            System.out.println("Odnaleziono " + searchValue);
        } else {
            System.out.println("Nie odnaleziono " + searchValue);
        }
    }
}

Konieczne jest ustawienie dodatkowej flagi, którą wykorzystujemy w warunkach pętli, aby przerwać ich działanie. W końcu jeżeli znajdziemy wartość, to nie ma sensu szukać dalej i tracić zasoby prawda? Da się to jednak to samo zapisać bez złożonych warunków w pętlach, ale zamiast tego stosując etykietę.

public class Loop {
    public static void main(String[] args) {
        int[][] tab = {{1, 2, 3},
                       {4, 5, 6},
                       {7, 8, 9}};
        boolean found = false;
        int searchValue = 5;
        searchLoop: for(int i=0; i<tab.length; i++) {
            for(int j=0; j<tab[i].length; j++) {
                if(tab[i][j] == searchValue) {
                    found = true;
                    break searchLoop;
                }
            }
        }
        
        if(found) {
            System.out.println("Odnaleziono " + searchValue);
        } else {
            System.out.println("Nie odnaleziono " + searchValue);
        }
    }
}

Oznaczyliśmy zewnętrzną pętlę etykietą "searchLoop" i od teraz możemy się do niej odwoływać zarówno z instrukcjach break jak i continue. W naszym przypadku instrukcja break przerwie działanie zewnętrznej pętli, a tym samym spowoduje natychmiastowe zakończenie wyszukiwania. Etykiety nazywane są czasami także pętlami nazwanymi (named loops).

2. VarArgs

Czasami definiując metodę chcielibyśmy, aby mogła ona przyjąć różną liczbę argumentów. Stosując standardowe podejście musielibyśmy zdefiniować argument typu tablicowego lub kolekcję i ją przekazać jako argument, jednak zamiast tego ostatnim parametrem metody może być tzw. varargs, który docelowo i tak zostanie przekształcony na tablicę.

public class VarArgs {
    public static void main(String[] args) {
        print("Ania", "Kasia", "Basia");
    }
    
    private static void print(String... values) {
        for (String val : values) {
            System.out.println(val);
        }
    }
}

Metoda print() przyjmuje zmienną liczbę argumentów typu String. W zależności od tego, ile wartości do niej przekażemy, Java zadba o umieszczenie wszystkich wartości do tablicy i argument values należy traktować jako tablicę właśnie. Przewaga takiego rozwiązania nad parametrem typu tablicowego lub kolekcji jest taka, że możemy wywołać metodę print() bez żadnych argumentów:

public class VarArgs {
    public static void main(String[] args) {
        print("Ania", "Kasia", "Basia");
        print();
    }
    
    private static void print(String... values) {
        System.out.println(values.length);
        for (String val : values) {
            System.out.println(val);
        }
    }
}

Java przypisze w takiej sytuacji do zmiennej values pustą tablicę.

varargs

3. Inicjalizacja przez podwójne nawiasy klamrowe

Jedną z metod, która wykorzystuje parametry typu VarArgs jest Arrays.asList(), która pozwala w prosty sposób utworzyć listę na podstawie wartości, które od razu znamy. Zapis:

List<Integer> asList = Arrays.asList(1, 2, 3, 4, 5);

jest w końcu dużo wygodniejszy od:

List<Integer> list = new ArrayList<>();
for(int i=1; i<=5; i++) {
    list.add(i);
}

Zamiast tego możemy jednak wykorzystać metodę inicjalizacji poprzez podwójne nawiasy klamrowe:

List<Integer> list2 = new ArrayList<Integer>(){{
    add(1); 
    add(2);
}};

Na pierwszy rzut oka wygląda to na jakieś czary. Przede wszystkim z jakiegoś powodu nie możemy przy ArrayList użyć w takiej sytuacji operatora diamentu <> dodatkowo pojawiają się tajemnicze podwójne nawiasy. Jeżeli spojrzymy jakie pliki zostaną wygenerowane po kompilacji klasy, w której występuje taki zapis zauważymy dwa pliki .class np.: Lists.class Lists$1.class Co więcej jeśli wydrukujemy na ekranie typ obiektu przypisanego do list2 zobaczymy, że wcale nie jest to ArrayList:

import java.util.ArrayList;
import java.util.List;

public class Lists {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<Integer>(){{
            add(1); 
            add(2);
        }};
        System.out.println(list.getClass());
    }
}

na ekranie wyświetla się "class Lists$1". Powinno nas to nakierować na odpowiedź co się stało. W rzeczywistości nie tworzymy obiektu ArrayList , tylko obiekt klasy anonimowej dziedziczącej po ArrayList , w której wykorzystujemy blok inicjujący. W podobny sposób możemy więc stworzyć obiekt każdej niefinalnej klasy. Ze względu na dodatkowy narzut pamięciowy warto jednak zachować ten sposób tworzenia obiektów jako ciekawostkę. Chcecie poznać więcej podobnych trików? Obserwujcie naszego bloga i facebooka.

Dyskusja i komentarze

Masz pytania do tego wpisu? Może chcesz się podzielić spostrzeżeniami? Zapraszamy dyskusji na naszej grupie na Facebooku.