JavaRush /Blog Java /Random-PL /Przerwa kawowa #143. Klasy zapieczętowane w Javie 17. 4 s...

Przerwa kawowa #143. Klasy zapieczętowane w Javie 17. 4 sposoby implementacji Singletona

Opublikowano w grupie Random-PL

Zapieczętowane klasy w Javie 17

Źródło: Codippa W tym poście przyjrzymy się zapieczętowanym klasom, nowej funkcji wprowadzonej w Javie 17, oraz sposobom ich deklarowania i używania na przykładach. Przerwa kawowa #143.  Klasy zapieczętowane w Javie 17. 4 sposoby implementacji Singletona - 1Klasy zapieczętowane pojawiły się po raz pierwszy w Javie 15 jako funkcja w wersji zapoznawczej, a później w Javie 16 z tym samym statusem. Ta funkcja stała się w pełni funkcjonalna wraz z wydaniem Java 17 ( JEP 409 ).

Czym są zajęcia zamknięte?

Zapieczętowana klasa umożliwia ograniczenie lub wybranie podklas. Klasa nie może rozszerzać klasy prywatnej, jeśli nie znajduje się ona na liście dozwolonych klas podrzędnych klasy nadrzędnej. Klasa jest zapieczętowana przy użyciu słowa kluczowego seal . Po klasie zapieczętowanej musi następować słowo kluczowe zezwolenies wraz z listą klas, które mogą ją rozszerzać. Oto przykład:
public sealed class Device permits Computer, Mobile {
}
Deklaracja ta oznacza, że ​​Urządzenie może być rozbudowywane jedynie o klasy Komputer i Mobilność . Jeśli jakakolwiek inna klasa spróbuje go rozszerzyć, zostanie zgłoszony błąd kompilatora. Klasa rozszerzająca klasę zapieczętowaną musi mieć w swojej deklaracji słowo kluczowe final , seale lub non-sealed . Mamy zatem ustaloną hierarchię klas. Jak to się ma do tworzenia klasy podrzędnej?
  1. final oznacza, że ​​nie można go dalej klasyfikować.

  2. seal oznacza, że ​​musimy zadeklarować klasy podrzędne z pozwoleniami .

  3. niezapieczętowane oznacza, że ​​w tym miejscu kończymy hierarchię rodzic-dziecko .

Na przykład Computer dopuszcza klasy Laptop i Desktop , o ile sam Laptop nie jest zapieczętowany . Oznacza to, że Laptopy można rozbudowywać o klasy takie jak Apple , Dell , HP i tak dalej.

Główne cele wprowadzenia klas zamkniętych to:

  1. Do tej pory można było ograniczyć rozszerzenie klasy jedynie za pomocą słowa kluczowego final . Zapieczętowana klasa kontroluje, które klasy mogą ją rozszerzać, umieszczając je na liście dozwolonych.

  2. Pozwala także klasie kontrolować, które z nich będą jej klasami podrzędnymi.

Zasady

Kilka zasad, o których należy pamiętać korzystając z klas zapieczętowanych:
  1. Zapieczętowana klasa musi definiować klasy, które mogą ją rozszerzać za pomocą zezwoleń . Nie jest to wymagane, jeśli klasy podrzędne są zdefiniowane w klasie nadrzędnej jako klasa wewnętrzna.

  2. Klasa podrzędna musi być ostateczna , zapieczętowana lub nie zapieczętowana .

  3. Dozwolona klasa podrzędna musi rozszerzyć swoją nadrzędną klasę zapieczętowaną.

    Oznacza to, że jeśli zapieczętowana klasa A pozwala na klasę B, wówczas B musi rozszerzyć A.

  4. Jeśli zapieczętowana klasa znajduje się w module, wówczas klasy podrzędne również muszą znajdować się w tym samym module lub w tym samym pakiecie, jeśli nadrzędna klasa zapieczętowana znajduje się w nienazwanym module.

  5. Tylko klasy bezpośrednio dozwolone mogą rozszerzyć klasę zapieczętowaną. Oznacza to, że jeśli A jest klasą zapieczętowaną, która pozwala B ją rozszerzyć, to B jest także klasą zapieczętowaną, która pozwala C.

    Wtedy C może jedynie rozszerzyć B, ale nie może bezpośrednio rozszerzyć A.

Uszczelnione interfejsy

Podobnie jak klasy zapieczętowane, interfejsy również mogą być zapieczętowane. Taki interfejs może umożliwiać wybór swoich interfejsów lub klas podrzędnych, które mogą go rozszerzać za pomocą zezwoleń . Oto dobry przykład:
public sealed interface Device permits Electronic, Physical,
DeviceImpl {
}
Tutaj interfejs urządzenia umożliwia interfejsom elektronicznym i fizycznym rozszerzenie go oraz klasę DeviceImpl w celu późniejszej implementacji.

Zapieczętowane zapisy

Klasy zapieczętowane mogą być używane z wpisami wprowadzonymi w Javie 16. Wpis nie może rozszerzać zwykłej klasy, więc może implementować jedynie prywatny interfejs. Ponadto zapis oznacza final . Dlatego też we wpisie nie można używać słowa kluczowego zezwolenia , ponieważ nie można go podklasować. Oznacza to, że istnieje tylko jednopoziomowa hierarchia z rekordami. Oto przykład:
public sealed interface Device permits Laptop {
}
public record Laptop(String brand) implement Device {
}

Wsparcie refleksji

Java Reflection zapewnia obsługę zapieczętowanych klas. Do java.lang.Class dodano następujące dwie metody :

1. getPermittedSubclasses()

Zwraca to tablicę java.lang.Class zawierającą wszystkie klasy dozwolone przez ten obiekt klasy. Przykład:
Device c = new Device();
Class<? extends Device> cz = c.getClass();
Class<?>[] permittedSubclasses = cz.getPermittedSubclasses();
for (Class<?> sc : permittedSubclasses){
  System.out.println(sc.getName());
}
Wniosek:
Komputer mobilny

2.jest zapieczętowany()

Zwraca wartość true , jeśli klasa lub interfejs, w którym jest wywoływana, jest zapieczętowana. To wszystko na razie o zapieczętowanych klasach dodanych w Javie 17. Mam nadzieję, że ten artykuł był pouczający.

4 sposoby wdrożenia Singletona

Źródło: Medium Dziś poznasz kilka sposobów implementacji wzorca projektowego Singleton. Wzorzec projektowy Singleton jest szeroko stosowany w projektach Java. Zapewnia kontrolę dostępu do zasobów, takich jak gniazdo lub połączenie z bazą danych. Kiedyś podczas rozmowy kwalifikacyjnej na stanowisko programisty internetowego w dużej firmie produkującej chipy poproszono mnie o wdrożenie singletona. To była moja pierwsza rozmowa kwalifikacyjna na stanowisko w Internecie i nie przygotowywałem się zbytnio, więc wybrałem najtrudniejsze rozwiązanie: leniwą instancję. Mój kod był poprawny tylko w 90% i niewystarczająco wydajny, w ostatniej rundzie przegrałem... Mam więc nadzieję, że mój artykuł będzie dla Was przydatny.

Wczesna instancja

class Singleton {
    private Singleton() {}
    private static Singleton instance = new Singleton();

    public static Singleton getInstance() {
        return instance;
    }
}
Ponieważ obiekt jest już utworzony podczas inicjalizacji, nie ma tutaj problemu z bezpieczeństwem wątku, ale powoduje to marnowanie zasobów pamięci, jeśli nikt go nie używa.

Leniwa realizacja

class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
W przypadku korzystania z leniwego wzorca inicjalizacji obiekt jest tworzony na żądanie. Jednakże metoda ta wiąże się z problemem związanym z bezpieczeństwem wątków: jeśli w linii 5 zostaną uruchomione jednocześnie dwa wątki, utworzą one dwie instancje Singletona. Aby tego uniknąć musimy dodać blokadę:
class Singleton {
    private Singleton() {}
    private static Singleton instance = null;

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized(Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
Podwójnie sprawdzane blokowanie (DCL): W linii 6 nie ma blokowania, więc ta linia będzie działać bardzo szybko, jeśli obiekt został już utworzony. Dlaczego musimy dwukrotnie sprawdzić instancję == null ? Bo być może w linii 7 wprowadzone zostały dwa wątki: pierwszy zainicjował obiekt, drugi czeka na blokadę Singleton.class . Jeśli nie ma sprawdzenia, drugi wątek ponownie utworzy obiekt singleton. Jednak ta metoda jest nadal niebezpieczna dla wątków. Linię 9 można podzielić na trzy linie kodu bajtowego:
  1. Przydziel pamięć.
  2. Obiekt inicjujący.
  3. Przypisz obiekt do odniesienia do instancji.
Ponieważ maszyna JVM może nie działać prawidłowo, maszyna wirtualna może przed inicjalizacją przypisać obiekt do odwołania do instancji. Inny wątek widzi już instancję != null , zacznie jej używać i spowoduje problem. Musimy więc dodać volatile do instancji, wtedy kod będzie wyglądał następująco:
class Singleton {
    private Singleton() {}
    private volatile static Singleton instance = null;

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized(Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

Używanie statycznej klasy wewnętrznej

public class Singleton {
  private Singleton() {}
  private static class SingletonHolder {
    private static final Singleton INSTANCE = new Singleton();
  }
  public static final Singleton getInstance() {
    return SingletonHolder.INSTANCE;
  }
}
SingletonHolder jest statyczną klasą wewnętrzną, jest inicjowana tylko wtedy, gdy wywoływana jest metoda getInstance . Klasa init w maszynie JVM uruchomi <clinit> cmd , następnie sama maszyna JVM upewni się, że tylko jeden wątek może wywołać <clinit> w klasie docelowej, inne wątki będą czekać.

Enum jako singleton

public enum EnumSingleton {
    INSTANCE;
    int value;
    public int getValue() {
        return value;
    }
    public int setValue(int v) {
        this.value = v;
    }
}
Domyślnie instancja wyliczeniowa jest bezpieczna dla wątków, więc nie trzeba się martwić o podwójne sprawdzanie blokowania, a pisanie jest dość łatwe.
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION