Class Loadery


Po co powstały

Żeby nie wiązać się ze sposobem dostarczania do JVMa kodu klasy. Można go ściągać, odszyfrowywać albo generować w locie. Umożliwiają instrumentalizację kodu (debugger). Klasy załadowane przez sąsiednie albo potomne Class Loadery są wzajemnie niewidoczne (bezpieczeństwo). Pozwala na załadowanie kilkukrotnie różnego kodu pod tą samą nazwą klasy ale nie wiem czemu ktoś miałby to robić.

Co robią

Dostarczają JVMowi bajty klasy w ściśle zdefiniowanej postaci. Same nie tworzą obiektu Class (te javowe). Robi to natywny kod który sprawdza poprawność i kompatybilność binarną.

Linkowanie

Referencje do obiektów są określane w czasie wykonania a referencje do klas w czasie linkowania. Linkowanie może być robione wcześnie (tuż po kompilacji) albo późno (w czasie wykonania). Java linkuje tak późno jak to możliwe. Typ jest określany przez kwalifikowaną nazwę klasy oraz CL który ją załadował.

Linkowanie jest robione przy wykorzystaniu Class.forName
nie jest robione przy loadClass z Class Loadera – jest przeciążona metoda z flagą loadClass(String name, boolean resolve) ale jest protected.

Klasa do załadowania swoich zależności używa CL który ją załadował.

Inicjalizacja

Klasa która jest zlinkowana ma w Constant Pool mapowania kwalifikowanych nazw klas na ich definicję (Class). Definicja klasy niekoniecznie musi być zainicjalizowana. Tzn. jej statyczne bloki inicjalizujące mogą nie być wykonane tak samo jak przypisania do statycznych pól. Jeśli w wyniku wykonania kodu JVM musi linkować kod to także go inicjalizuje.

Użycie stałej NazwaKlasy.class wykorzystuje metodę loadClass Class Loadera która nie inicjalizuje. Analogiczny kod w kotlinie inicjalizuje companion object. Przeciążone Class.forName(String name, boolean initialize, ClassLoader loader) przy przekazaniu false nie linkuje

Kompatybilność binarna

Przy kompilowaniu klas/modułów oddzielnie klasa która wykorzystuje inną klasę mogła być kompilowana z inną definicją swojej zależności niż ta aktualnie dostępna na Class Pathu. Nie wszystkie zmiany spowodują błąd. Niektóre kompatybilne zmiany [JLS]:

  • Reimplementing existing methods, constructors, and initializers to improve performance.
  • Changing methods or constructors to return values on inputs for which they previously either threw exceptions that normally should not occur or failed by going into an infinite loop or causing a deadlock.
  • Adding new fields, methods, or constructors to an existing class or interface.
  • Deleting private fields, methods, or constructors of a class.
  • When an entire package is updated, deleting package access fields, methods, or constructors of classes and interfaces in the package.
  • Reordering the fields, methods, or constructors in an existing type declaration.
  • Moving a method upward in the class hierarchy.
  • Reordering the list of direct superinterfaces of a class or interface.
  • Inserting new class or interface types in the type hierarchy.

Podobna sytuacja występuje dla serializacji.

Implementacja Class Loaderów

Obiekt Class powstaje w wyniku wywołania jednej z wersji natywnej metody defineClass w ClassLoader. Class ma referencje do Class Loadera który ją załadował. Dlatego ten sam kod klasy załadowany przez dwa różne ClassLoadery skutkuje dwoma typami (między którymi nie można rzutować).

Natywne metody ładujące są prywatne i są wywoływane przez protected defineClass które robi walidacje certyfikatów o ile są i nazwy ładowanej klasy (np czy pakiet nie zaczyna się od java żeby nie można było załadować swojej wersji Stringa). Bez Security Managera prywatne metody można zawołać refleksją. Przewidziany przez twórców sposób to rozszerzenie klasy ClassLoader i wywołanie metod z walidacją.

Hierarchia Class Loaderów

Klasa Class Loader zawiera metodę szablonową loadClass(String name, boolean resolve) która zakłada locki w przypadku ładowania wielowątkowego (tylko jeden wątek ładuje klasę z wykorzystaniem jednego CL) oraz zapewnia domyślną hierarchiczność CL. Metoda do zaimplementowania to findClass(String name) która musi zwrócić klasę więc w implementacji zawołać którąś z natywnych defineClass. Domyślna hierarchia to :

  • Bootstrap
  • Extension
  • Application/System

Loader najpierw deleguje do rodzica zanim spróbuje załadować sam. Zapewnia to że klasy core’owe będą ładowane tylko raz i to przez Bootstrapowy CL oraz że wszystkie klasy odnosząc się do Stringa dostaną tą samą klasę Stringa.

Bootstrap

Bootstrapowy CL jest napisany w C i nie można mieć referencji do niego z poziomu Javy. Ładuje klasy z JRE. Powtarza część walidacji robionych w javie i dodaje swoje (dt. spójności typów [Bracha])

Extension

Extension CL powstał na potrzeby Apletów (jak być może cały mechanizm CL) i daje alternatywny/globalny „Class Path” czyli miejsce z którego są ładowane klasy dla wszystkich JVMów na danej maszynie o ile nie zmieniały parametrów uruchomieniowych (java.ext.dirs które powinno zawierać foldery a nie pliki).

Extension CL ładuje pliki z pominięciem walidacji Security Managera (? AccessController.doPrivileged() i nie sprawdza uprawnienia createClassLoader).

System

Systemowy CL ładuje kod z Class Patha.

Problemy

Nie zawsze CL działają parent first, servlety są child first:

„The Web application class loader must load classes from the WEB-INF/ classes directory first, and then from library JARsB in the WEB-INF/lib directory.” str 109 spec 3.0

To może powodować problem jeśli klasa możliwa do załadowania przez rodzica i potomnego. Kod rodzica będzie używał innej klasy niż kod potomny i nie będą mogły przekazywać instancji tej klasy w wywołaniach między sobą.

Przy ładowaniu parent first problem pojawia się jeśli kod core’owy (rodzica) korzysta z klas na Class Path np. Xerces (xml), JNDI (service locator).

Przy każdym sposobie ładowania pojawia się problem współpracy między kodem załadowanym przez różne CL. Musi się to odbywać przez interfejs który jest wspólny dla obu (ładowany prawdopodobnie przez trzeciego CL do którego oba mają referencje).

ContextClassLoadery

Żeby zignorować hierarchie wprowadzono ContextClassLoadery. To pole w Thread do którego można wsadzić Class Loadera (żeby nie trzeba było do Thread Locala). A później w tym samym wątku ale w klasie załadowanej przez innego CL zignorować jej aktualnego CL i użyć tego z wątku.

Źródła:
„The Java® Virtual Machine Specification” Tim Lindholm, Frank Yellin, Gilad Bracha, Alex Buckley

„Inside the Java Virtual Machine” Bill Venners

Dynamic Class Loading in the Java VirtualMachine Sheng Liang, Gilad Bracha

Dokumentacja mechanizmu rozszerzeń

Specyfikacja Servletów 4.0

Ogólnie o ładowaniu klas

O Context Class Loaderach

Rożnica między Class.forName() i ClassLoader.loadClass()

JLS Kompatybilność binarna


Functional Programming in Java

Okładka Functional Programming in Java

Function

Ma domyślne metody:

funkcja.compose(wcześniejszaFunkcja)składa wywołania funkcji, parametr będzie najpierw przekazany do wcześniejszejFunkcji a wynik do funkcji

funkcja.andThen(późniejszaFunkcja) przekaże parametr do funkcji a rezultat do późniejszejFunkcji

funkcja.identity() zwraca przekazany parametr

Predicate

Ma domyślne metody and(innyPredykat), or(innyPredykat) i negate() oraz statyczną isEqual(obiekt) która zwraca predykat porównujący, z wykorzystaniem equals, z przekazanym obiektem.

Unary/Binary Operator

Rozszerzają odpowiednio Function i BiFunction ale operują tylko na jednym typie generycznym

UnaryOperator ma identity() który zwraca ten sam UnaryOperator a BinaryOperator ma minBy(comparator) oraz maxBy(comparator) które porównują dwie wartości.


Checked Exceptions

Interfejsy z java.util.function nie deklarują żadnych sprawdzanych wyjątków dlatego muszą być one łapane lub przepakowywane wewnątrz lambdy. Ewentualnie można utworzyć interfejs który deklaruje wyjątki ale i tak nie będzie możliwy do użycia w streamach:

interface UseInstance<T, X extends Throwable> {
void accept (T instance) throws X;
}

Jeśli koniecznie z lambdy musi wydostać się sprawdzany wyjątek wtedy:

  • unsafe.throwException(new IOException()) działa
  • Thread.currentThread().stop(new IOException()) już nie działa
  • ograniczony parametr typu i rzutowanie działa:
private static <T extends Throwable> void sneakyThrow(Throwable exception) throws T {
throw (T) exception;
}

Stream

Streamy są leniwe więc zmiana źródła danych z którego powstał stream przed wywołaniem metody kończącej zmienia stream. Zmiana źródła w czasie przeglądania streamu jest bezpieczna tylko dla bezpiecznych wielowątkowo źródeł (ConcurrentHashMap).

Niektóre sposoby stworzenia streama:

  • Stream.of(varargs)
  • Stream.generate(supplier)
  • Stream.iterate(poczatkowaWartość, zmianaWartości)
  • Stream.concat(jakiśStream, innyStream)
  • Stream.builder().add("element").build()

ZmianaWartości w Stream.iterate() może polegać np. na zwróceniu następnej liczby pierwszej.

Ograniczanie streama przez limit i skip

Przejście do streamów prymitywnych przez mapToInt/Double/Long, są też wersje flatMapTo

Primitive Stream

Przejście do Streamu obiektowego przez mapToObject

Przy wyliczaniu średniej zwracają prymitywne Optionale np. OptionalDouble, OptionalInteger, OptionalLong – od zwykłego Optionala różnią się tym, że opakowują typ prosty i mają metodę getTypProsty, dodatkowo orElse wymaga Suppliera tego typu prostego.

sum na typie prostym zwraca wartość bez Optionala – dla pustego streamu zwraca 0

CharSequence (String, StringBuilder…) ma domyślną metodę chars która zwraca IntStreama. To Integery nie Charactery przez znaki uzupełniające Unicode które nie mieszczą się w zakresie char a kompatybilnośc wsteczna nie pozwala go rozszerzyć.

Summary Statistics

Dają dostęp do max, min, average, count
Do statystki można dodawać kolejne wartości (accept) albo łączyć kilka statystyk ze sobą (combine)
wszystkie interfejsy dla streamów prymitywnych mają metody asTypPrymitywny – wyjątki do consumer i predicate – te mają standardowe nazwy


Collectors

stream.collect(supplier, consumer, consumer) pierwszy dostarcza pusty obiekt drugi to akumulator a trzeci combiner
stream.collect przyjmuje też obiekt Collector który implementuje 3 powyższe metody plus finisher i characteristics (concurrent, unordered, identityFinish – czyli finisher nic nie robi).

toMap

toMap(keyMapper, valueMapper) przyjmuje dwie funkcje z których jedna to często Function.identity(), jeśli klucz jest zdublowany wtedy jest rzucany IllegalStateException

toMap(keyMapper, valueMapper, mergeFunction) jak poprzednia ale w razie wystąpienia duplikatu jest on mergowany za pomocą mergeFunction

toMap(keyMapper, valueMapper, mergeFunction, mapSupplier) pozwala wybrać typ mapy, domyślnie to HashMapa

Wszystkie trzy funkcje mają swoje bezpieczne wielowątkowo odpowiedniki: toConcurrentMap

groupingBy

groupingBy zwraca mapę w której wartością jest lista, groupingBy w groupingBy zwraca mapę map.

GroupingBy ma trzy warianty:

  • groupingBy(funkcjaKlasyfikująca)
  • groupingBy(funkcjaKlasyfikująca, collectorDlaDownstream)
  • groupingBy(funkcjaKlasyfikująca, fabrykaMapy, collectorDlaDownstream)

Wszystkie trzy mają też odpowiedniki bezpieczne wielowątkowo.

collectorDlaDownstream umożliwia dalsze przetwarzanie wartości w mapie przez zastosowanie dowolnego Collectora, jeśli klucz i wartość są na innych poziomach zagnieżdżenia, lub wymagają modyfikacji, przydatny jest Collectors.mapping, który mapuje wartości. W javie 9 dodano analogiczne flatMapping i filtering.

partitioningBy

Przyjmuje predykat i zwraca mapę która ma zawszę dwie wartości True i False (niezależnie czy mają jakieś wartości).

Ma tylko dwa warianty:

  • partitioningBy(predykat)
  • partitioningBy(predykat, collectorDlaDownstream)

Nie można określić rodzaju mapy, np wykorzystać wersję bezpieczną wielowątkowo. Nie ma bezpiecznych wielowątkowo metod partitioningBy.

Redukujące

  • joining()
  • joining(delimiter)
  • joining(delimiter, prefix, sufix)

Działa analogicznie do String.join(delimiter, stringi). Pierwszy łączy Stringi, drugi łączy i dodaje między nimi delimiter (poza ostatnim). Trzeci umożliwia poprzedzenie i zakończenie całego ciągu.

  • maxBy(comparator)
  • minBy(comparator)

Zwracaja Optionale i nie mają wersji z domyślną wartością tak samo jak ich odpowiedniki dla streama.

counting() – zwraca longa

Reduce

Od Collectora różni się tym, że typ zwracany musi być taki sam jak typ elementów i w teorii każde wywołanie tworzy nowy element. W praktyce nic poza konwencją nie stoi na przeszkodzie żeby reduce mutowało przekazywane mu obiekty i je zwracało. Dodatkowo w jednym przypadku zwracany typ może być inny niż typ streama.

Zarówno Collectors.reducing() jak i stream.reduce() mają trzy wersję:

  • reducing(redukującyBinaryOperator) – zwraca Optionala
  • reducing(wartośćPoczątkowa, redukującyBinaryOperator) – dla pustego streamu zwraca wartośćPoczątkowa, dla niepustego wartośćPoczątkowa jest pierwszym argumentem redukującegoBinaryOperatora
  • reducing(wartośćPoczątkowa, mapper, redukującyBinaryOperator) – jak wyżej ale przed rozpoczęciem redukcji najpierw wartość jest mapowana, ta sygnatura metody pozwala na redukowanie do typu wartościPoczątkowej która może być inna niż typ streamu, analogiczna metoda Stream.reduce(wartośćPoczątkowa, redukującaBiFunkcja, combiner) jest wykorzystywana przy przetwarzaniu wielowątkowym a combiner łączy rezultaty uzyskane w podzadaniach.

Arytmetyczne

Wszystkie zwracają Double, 0 jeśli stream jest pusty, czyli inaczej niż dla prymitywnych streamów gdzie jest zwracany odpowiedni Optional.

averangingDouble/Int/Long(maper Object -> odpowiedni typ prosty) -dają dokładniejsze wyniki przy ciągu rosnącym.

summarizingDouble/Int/Long() – zwraca statystyki bez konieczności przechodzenia na stream dla typów prostych

summingDouble/Int/Long()– prostsza wersja powyższego z samym sumowaniem

Do kolekcji

  • toList()
  • toSet()
  • toCollection(konstruktorKolekcji)

Dwa pierwsze dają niegwarantowane implementacje (ArrayList i HashSet), trzeci wymusza podanie konstruktora samemu

Wrapper

collectingAndThen(downstreamCollector, funkcjaOpakowująca) – wynik działania downstreamCollectora zostanie przekazany funkcjiOpakowywującej i to jej rezultat zostanie zwrócony. Funkcja może np. opakowywać kolekcję w Collections::unmodifiableCollection.

Przy implementowaniu Collectora samodzielnie funkcjaOpakowująca to finisher.

Typy proste

Mają typy ObjTypPrymitywnyConsumer która przyjmuje T i typ prosty i ma metodę accept.


Lambdy

Zmienne w lambdach domyślnie nie są finalne. Jeśli mają być wtedy trzeba podać też typ.

Pole jest zawsze Effectively final: 

class EffectivelyFinal {
int value = 1;

void method(){
value = 2;
produce(() -> value);
value = 3;
}

int produce(Supplier<Integer> supplier) {
return supplier.get();
}
}

Ten kod się skompiluje bo value jest polem i odniesienie ma postać this.value. A this jest Effectively final.

Jeśli funkcja zależy od zmiennej spoza niej (domknięcie) wtedy można zrobić funkcję która przyjmuję tą wartość i zwraca funkcję która ją wykorzystuje.
Można zawęzić zasięg wewnętrznej funkcji tylko do zewnętrznej (odpowiednik where w Haskelu) przez zwracanie lambdy jako wyniku lambdy.

final Function<String, Predicate<String>> startsWithLetter = 
letter -> name -> name.startsWith(letter);

Wykorzystanie:
...
.filter(startsWithLetter.apply("N"))

Comparator

Comparator ma domyślne metody: thenComparing(innyComparator) i thenComparing(funkcjaWyciągającaRzeczDoPorównywania, comparator)

W mapie są comparatory comparingByKey i comparingByValue zarówno w naturalnym porzadku jak i z wykorzystaniem przekazanego comparatora
są też comparingInt/Long/Double(przyjmuje ToIntFunction która wyciąga z obiektu typ prosty


Rekurencja ogonowa

Java nie optymalizuje rekurencji ogonowej. Wywołanie metody można przenieść ze stosu na stertę. Kolejne wywołania można przedstawić w postaci obiektu który albo jest potrzebny do kolejnego wywołania albo posiada wynik.

Obiekt może rozróżnić te 2 sytuacje i rzucać wyjątek jeśli jest pobierany wynik z nieukończonej rekurencji lub rekurencja jest wywoływana dla obiektu posiadającego wynik.

Liczba możliwych wywołań rekurencyjnych jest niedeterministyczna. Wątek wykonujący wywołania ściga się z JITem który kompresuje ramkę. Im wcześniej JIT ją skompresuje tym więcej wywołań będzie miało miejsce przed przepełnieniem stosu. W przypadku wyłączenia JITa przez -Djava.compiler=NONE ilość wywołań jest stała i zależna od wielkości stosu.


Memoizer

Funkcja która jest rekurencyjnie wywoływana ma referencje do samej siebie owrapowanej w coś co ją cacheuje.
Memoizer tworzy swoją implementacje Function która ma cache dla parametrów wywołania, jeśli nie ma wyniku w cache to wywołuje przekazaną funkcję ale podając referencje do siebie (wrappera). Przez co funkcja może wykorzystać wrapper wywołując się rekurencyjnie.

class Memoizer {
public static <T,R> R callMemoizer(final BiFunction< Function<T,R>,T,R> function, final T input ){
Function<T,R> memoized = new Function<T,R>(){
HashMap<T,R> store = new HashMap<>();
@Override
public R apply(T input) {
return store.computeIfAbsent(input, key -> function.apply(this, key));
}
};
return memoized.apply(input);
}
}

Nieco podobne do ThreadLocal, nie ma globalnego cache w którym trzeba szukać po wątku / funkcji i argumencie. A każdy wątek/funkcja ma swój cache. Nie sprawdzi się dla rekurencji liniowej (która woła się tylko raz) bo wartości będą dodawane do cache jak już będą niepotrzebne.

Źródła:
„Functional Programming in Java: Harnessing the Power Of Java 8 Lambda Expressions ” Venkat Subramaniam

Dokumentacja Streama

Dokumentacja Collectora

Pola i Effectively final (23 minuta)

Throwing Undeclared Checked Exceptions

OCP Czas

UTC

Standard zapisu czasu, wykorzystujący uśredniony czas słoneczny zerowego południka. Nie mylić z GMT które jest strefą czasową, tym bardziej nie mylić z czasem londyńskim (po przejściu na czas letni Londyn ma przesunięcie +1). Do wyznaczania czasu w standardzie UTC wykorzystywanych jest ponad 400 zegarów atomowych które co jakiś czas są korygowane o sekundę przestępną.

Sekunda przestępna

W wyniku nieregularnego zwalniania obrotu ziemi wokół własnej osi czas UTC rozsynchronizowuje się z czasem słonecznym. By zniwelować tą różnicę ogłaszane jest wystąpienie sekundy przestępnej (z wyprzedzeniem). Dodatnia sekunda „cofa” czas przez wystąpienie 23:60. Ujemna sekunda przeskakiwałaby jedną sekundę ale jeszcze nie wystąpiła.
Sekunda przestępna przeczy założeniu, że minuta ma 60 sekund co może powodować błędy.
Istnienie sekundy przestępnej sprawia, że nie ma możliwości dokładnego obliczenia ilości sekund między dwiema datami bez informacji o sekundach przestępnych (wyjątek data Juliańska).

Java Time Scale

System zapisu czasy stosowany w Javie.

    • Założenia:
  • pokrywa się z czasem UTC w południe każdego dnia
  • ma dwie wersje: przed 1972-11-03 pokrywa się z czasem słonecznym zerowego południka, po pokrywa się z UTC-SLS

Implementacja UTC-SLS

Może być wykonana po stronie systemu na którym zainstalowany jest JVM albo w JVM.
Nie ma sekund przestępnych. Jeśli implementacja jest po stronie systemu wtedy czas systemowy jest zwracany w System.currentTimeMillis() a przez to w wyniku wywołania wszystkich now() na obiektach czasu. Jeśli tak nie jest JVM rozciąga czas trwania ostatnich 1000 sekund dnia o 1/1000 sekundy.

ZonedDateTime

Zawiera LocalDateTime i ZoneId. Wszystkie obiekty czasu są bezpieczne wielowątkowo i niezmienne.

Na podstawie ZonedDateTime można tworzyć instancje w innych strefach czasowych:
withZoneSameInstant(zone) po prostu przypisuje LocalDateTime inną strefę czasową
withZoneSameInstant(zone) mówi jaka godzina jest teraz w podanej strefie czasowej

LocalDateTime

Godzina i data bez strefy czasowej, zawiera LocalDate i LocalTime. Oba obiekty udostępniają metody do utworzenia LocalDateTime np. atTime(), atStartDay() lub atDate().
Zawierają metody until() służące do obliczania różnicy między obiektami w jednostkach podanych jako drugi parametr. Wykonanie jest delegowane do kodu jednostki ChronoUnit.
Zwracają zmodyfikowaną datę przez plusJednostka() i minusJednostka() np. minusSeconds(), plusWeeks().

ZoneId

Reprezentuje strefę czasową, która składa się z różnicy w stosunku do czasu GMT (ZoneOffset) oraz reguł zmiany czasu (ZoneRules).

ZoneOffset

Przechowuje różnice w sekundach cache’ując wielokrotności 15 minut oraz dotychczas użyte Offsety.

ZoneRules

Zawierają informację o zmianach czasu podzielone na dwie grupy:
ZoneOffsetTransitionRules algorytmiczne reguły zmiany czasu, np ostatnia niedziela marca
ZoneOffsetTransition dotychczasowe historyczne zmiany czasu oraz zmiany wynikające z zastosowania ZoneOffsetTransitionRules w danym roku, zawierają Offsety przed i po zmianie oraz czas zmiany.

Jeśli obiekt ZoneDateTime jest tworzony na podstawie LocalDateTime wtedy mogą wystąpić trzy przypadki:

  • normalny: dla tej daty i strefy czasowej występuje tylko jedno przesunięcie w stosunku do GMT
  • godzina nie istnieje: jest pomijana w czasie zmiany czasu na letni
  • godzina występuje podwójnie: zegar jest cofany przy zmianie czasu na zimowy

W drugim przypadku do „nieistniejącej” godziny dodawana jest wartość zmiany czasu.

W trzecim przypadku domyślnym zachowaniem jest ustawienie godziny ze „starym” przesunięciem. Można wpływać na to zachowanie metodami withEarlierOffsetAtOverlap (domyślne zachowanie) oraz withLaterOffsetAtOverlap. Metody zwrócą nowy obiekt z alternatywną wartością przesunięcia. Earlier znaczy „wcześniejsze: (przed zmianą czasu) nie „o mniejszej wartości”. Alternatywnie można wykorzystać parametr prefferedOffset – jeśli wśród praw przesunięć będzie podana wartość, wtedy zostanie wybrana.

Informacje o zmianie daty są dostarczane z IANA Time Zone DataBase. Nowsza wersja jest dostarczana z updatem jdk ale może być podmieniona ręczniie (jdk/jre/lib/tzdb.dat).

Przy tworzeniu ZoneDateTime na podstawie Instant nie ma niejednoznaczności.

Instant

Przechowuje ilość sekund (long) i nanosekund (int) od początku ery (1970-01-01T00:00:00Z). Ma metody plusSeconds(), plusMillis() i plusNanos() i jedną która przyjmuje ilość i jednostkę. Czas jest zapisany w strefie GMT – nie występują komplikacje związane ze zmianą czasu – przesunięcie jest stałe (zerowe). Stosowany np. jako timestamp.

TemporalAdjuster

Można stosować poprzez wywołanie metody adjustInto(temporal) na obiekcie TemporalAdjuster lub wywołując
with(thisAdjuster) na obiekcie Temporal (wywołanie jest delegowane do Adjustera).

Domyslne Adjustery umieszczone w TemporalAdjusters

  • firstDayOfMonth
  • lastDayOfMonth
  • firstDayOfNextMonth
  • lastDayOfYear
  • firstDayOfNextYear
  • firstInMonth(Dzien tygodnia)
  • lastInMonth(DzienTygodnia)
  • next(DzienTygodnia)
  • previous(DzienTygodnia)
  • previousOrSame(DzienTygodnia)
  • lastInMonth(DzienTygodnia)
  • dayOfWeekInMonth (licznik, DzienTygodnia)

Licznik w ostatniej opcji oznacza które to ma być wystąpienie dnia tygodnia w miesiącu. Może wyjść poza zakres miesiąca do następnego: np 8 wtorek stycznia da luty;
-1 to ostatnie wystąpienie tego dnia tygodnia w tym miesiącu, -2 to przedostatnie itd (można cofnąć się do poprzedniego miesiąca)
0 to ostatnie wystąpienie w poprzednim miesiącu

Period

Zawiera pola int: year, month, day. Pola mogą mieć różne znaki. Dodanie Period.of(0, -1, 16) odejmie miesiąc i doda 16 dni. Nie można dodawać go do LocalTime. Bierzę pod uwagę zmiany czasu przy dodawaniu go do ZonedDateTime.
Nie można chainować Period.of (to statyczna metoda).
Ma metody plusYears(), minusDays() itd. które już są instancyjne.
Period domyślnie nie jest normalizowany tzn 24 miesiące nie są zamieniane na 2 lata. Służy do tego metoda normalized().

Inne przydatne metody:
toTotalMonths(), multipliedBy(), negated(), between()
Period toString() wyświetla PxYyMzD ale parse() przyjmuje tez W – tygodnie i może mieć minus przy dowolnej sekcji.

Duration

Zawiera pola long seconds i int nanos.
Ma metody analogiczne do Period oraz pomocniczne:plusDays(), plusHours(). Pobranie sekund i nanosekund ma przedrostek get…(), pozostałe jednostki mają metodę to…().
Ujemne nanosekundy są odejmowane od pełnych sekund. Sekundy mają metody związane z ich znakiem isNegative(), isNegative(), abs(). Dodawanie Duration nigdy nie bierze pod uwagę zmiany czasu.
toString() wyświetla PTsekundy.nanoSekundyS
parse() może przyjmować niecałkowite sekundy

Clock

Clock pozwala zastapic czas sytemowy.
Domyślne implementacje to:

  • fixed – cały czas zwraca jedną wartość
  • offset – opakowuje inny zegar i dodaje/odejmuje od niego stała wartość
  • tick – zaokrągla o wskazanie w Duration
  • tickSeconds i tickMinutestick w którym Duration to seconda i minuta

Zegar zawiera informację o strefie czasowej.

Inne klasy

MonthDay – ma metodę isValidYear(year) która dotyczy tylko 29 lutego, czyli pokrętnie sprawdza czy rok jest przestępny
YearMonth – ma metodę do tego samego nazwaną isLeapYear() i drugą do sprawdzania czy dzień istnieje w miesiącu – isValidDay(day)
OffsetDateTime – ZonedDateTime pozbawione reguł zmiany czasu, składa się z OffsetTime i LocalDateTime.

ChronoLocalDate

Interfejs do przechowywania daty w dowolnym kalendarzu. Zalecanie jest nie używanie go, posługiwanie się LocalDate a tłumaczenie do innych kalendarzy traktować jako lokalizacje. Pisanie aplikacji posługującej się abstrakcyjnym kalendarzem będzie podatne na błędy przez nieświadome stosowanie założeń dotyczących kalendarza gregoriańskiego: stała ilość miesięcy w roku, podobna długość miesięcy, rozpoczynanie miesiąca od pierwszego dnia, mniejszy numer roku oznaczający wcześniejszy rok.

Data Juliańska

Przy stosowaniu kalendarza ciężko obliczyć liczbę dni między dwoma datami. Dodatkowo jeśli jedna z dat jest p.n.e a druga n.e. od wyniku trzeba odjąć 1 ponieważ w tym aktualnym sposobie zapisu dat nie występuje rok 0. Tzn. po 1 p.n.e występuje rok 1 n.e. W czasie powstawania koncepcji „naszej ery” nie istniała koncepcja zera.
Powoduje to problemy z liczeniem dlatego powstała data Juliańska (JD – nie mylić z kalendarzem Juliańskim). Zakłada wybranie roku zerowego (konkretnie 4713 p.n.e) a następnie zapisywanie dat jako ilości dni które minęły od tego czasu (w oryginalnym formacie dzień zaczynał się w południe). Godziny są zapisywane jako części ułamkowe dnia. Data stosowana np. w astronomii, pomysł został zaadoptowany przez programistów Unixa (z uwzględnieniem sekundy przestępnej). Stosowana jest zmodyfikowana data Juliańska (MJD) która przesuwa początek epoki 2400000,5 dni do przodu, żeby operować na mniejszych liczbach. Połówka na końcu odzwierciedla, że w MJD dzień zaczyna się o północy. Java ma implementacje obu dat w klasie JulianFields ale nie wspiera części ułamkowych – godzin.

Pomiar czasu a bezpieczeństwo

Pomiar odchyleń od prawidłowego czasu umożliwia przeprowadzanie ataków deanonimizujących. Zwiększenie obciążenia anonimowego serwera wpływa na jego temperaturę, temperatura wpływa na działanie oscylatora co tworzy odchylenia, sprawdzając czas „podejrzanych” serwerów można wnioskować który z nich działa również w anonimowej sieci.

Pomiar czasu przetwarzania danych może, w niektórych przypadkach, zdradzać informacje na temat samego przetwarzania.
Przykładowo: aplikacja porównując hasła (zamiast hashy) po jednym znaku (zamiast całych) zwróci wynik po czasie proporcjonalnym do liczby prawidłowo wpisanych znaków.

Źródła:
Dokumentacja pakietu java.time
www.timeanddate.com UTC
www.timeanddate.com Sekunda przestępna
Internet Draft UTC-SLS
Hot or Not: Revealing Hidden Services by their Clock Skew Steven J. Murdoch

JLS Finalizacja i rodzaje referencji

Finalizacja

Służy do zwalniania zasobów zainicjalizowanych poza JVMem. Realizuje to samo co sekcja finally lub try with resources ale dla obiektów o widoczności większej niż zasięg metody. Umożliwia umieszczenie kodu zwalniającego zasoby wewnątrz obiektu którego dotyczy, co utrudnia niepoprawne wykorzystanie.

Klasa Object ma metodę protected void finalize() throws Throwable { }. Obiekty mogą nadpisywać tą metodę. Kontrakt tej metody mówi, że będzie wywołana przez Garbage Collectora przed ponownym użyciem pamięci zajmowanej przez obiekt (co może nie nastąpić). Czas jej wykonania wpływa na czas działania GC.
„Finalizer” może pojawiać się w dwóch kontekstach: jako implementacja metody finalize, lub jako obiekt rozszerzający FinalReference (patrz niżej) który przechowuje referencje do obiektu przeznaczonego do finalizacji.
Enumy nie mogą mieć finalizerów.

Mechanizm finalizacji jest w większości napisany w Javie. Wyjątek to pole zawierające elementy które mogą być kiedyś finalizowane (unfinalized) uzupełniane przez JVMa oraz metody natywne (według komentarzy w kodzie) pośredniczące w wywołaniu metod opisanych poniżej. Pośredniczące metody natywne równie dobrze mogą same zaimplementować te metody.

Metody System.runFinalization(), System.gc() w teorii mogą przyśpieszyć wywołanie finalizerów.
JVM nie czeka na skończenie pracy finalizerów kiedy się wyłącza. Wyjątek to zastosowanie System.runFinalizersOnExit(true) metoda jest niezalecana i ma dwie wady: finalizacja może być przeprowadzona na „żywym” obiekcie co może skutkować niespójnymi danymi oraz nie zapewnia, że finalizer zakończy pracę w sytuacji kiedy jego wykonanie rozpocznie się przed wyłączaniem JVM.
Po załadowaniu klasy Finalizer.java uruchamiany jest wątek który kolejno wykonuje finalizację obiektów dodanych do kolejki przez JVMa.

Model pamięci gwarantuje, że metoda będzie wykonywana po wszystkich zapisach a wszystkie odczyty nie będą widziały jej skutków. Finalizacja może przebiegać wielowątkowo. Aktualna javowa implementacja obejmuje dwa wątki dla finalizacji i jeden do obsługi referencji (patrz niżej). Główny obsługujący finalizacje ma priorytet o 2 mniejszy od maksymalnego. Drugi jest uruchamiany jako wątek systemowy i wykorzystywany (według dokumentacji) tylko dla runFinalization i runFinalizersOnExit. W przeciwieństwie do pierwszego nie wchodzi w stan uśpienia.
Finalizer może być wywołany tylko raz dla konkretnego obiektu (implementacja tego jest raczej po stronie JVMa).

W przeciwieństwie do konstruktora finalizer nie wywołuje swojego odpowiednika w nadklasie – trzeba to zrobić jawnie.
Jeśli klasa może być rozszerzana wykonanie finalizacji dla instancji klas rozszerzających można wymusić można przez utworzenie klasy wewnętrznej („strażnika”) zawierającego pożądaną implementacje finalize, a następnie przechowywania referencji do instancji tej klasy wewnątrz obiektu który może być rozszerzany. Czas życia „strażnika” będzie pokrywał się z czasem życia instancji klas rozszerzających.

Podział obiektów względem dostępności

  • reachable wykorzystywany przez aplikację
  • finalizer-reachable możliwy do uzyskania tylko przez obiekt przeznaczony do finalizacji
  • unreachable nieosiągalny

Optymalizacje kompilatora mogą sprawić, że obiekt będzie możliwy do finalizacji wcześniej niż to wynika z kodu. Dotyczy tylko referencji trzymanych na stosie (wewnątrz wywołań metody). Możliwa jest finalizacja obiektu na rzecz którego jeszcze wywoływane są metody o ile stan tego obiektu nie jest później odczytywany.

Finalizer umożliwa ponowne „ożywienie” obiektu (np. przez dodanie referencji do niego do listy z użyciem this). Taki obiekt tworzy wyciek pamięci bo GC traktuje go jako usunięty (uruchomiono jego finalizer) mimo, że do samego usunięcia nie doszło.

Jeśli podczas finalizacji będzie rzucony wyjątek to jest on ignorowany a finalizacja tego obiektu się kończy.


Typy referencji

Typy rozróżniane z punktu widzenia Garbage Collectora, nie w kategoriach obiektu na który wskazują (instancja klasy, interfejsu, tablicy lub nulla). GC bierze pod uwagę „najsilniejszy” typ referencji po którym można dotrzeć do obiektu. Wszystkie typy poza pierwszym to wrappery na silną referencje.

Dodatkowe typy referencji są umieszczone w pakiecie java.lang.ref. Umożliwiają rozdzielenie czasu życia obiektu od czasu życia referencji do niego, które domyślnie są jednakowe. Częsty przypadek wykorzystania to obiekty z metadanymi dotyczącymi innych obiektów. Metadane nie zawsze mogą być umieszczone wewnątrz obiektów które opisują. Jeśli zastosowano mapę do powiązania opisujących i opisywanych obiektów lub opisujące obiekty zawierają referencje do opisywanych wtedy istnieją trzy opcje:

  • czas życia opisywanych obiektów jest rozszerzany do czasu życia metadanych
  • czas życia opisywanych obiektów może być skracany przez jawne nullowanie referencji (czyli „ręczne” zarządzanie czasem życia obiektu)
  • wykorzystanie, poza zwykłymi, także referencji nie wpływających na działanie GC.

strong

Standardowe referencje, możliwość dotarcia do obiektu (zaczynając w GC roots) przez tą referencję uniemożliwia usunięcie go przez GC.

soft

Referencja gwarantuje usunięcie obiektu zanim JVM rzuci OutOfMemoryError. Nie ma innych gwarancji co do czasu życia obiektu: może zostać usunięty od razu po utworzeniu lub nigdy (jeśli nie brakuje pamięci). Implementacje JVMa są zachęcane do nieusuwania obiektów które były niedawno wykorzystywane. Przeważnie wykorzystywane przy implementowaniu cache’a.

weak

Istnienie odwołania przez słabą referencje nie wpływa na czas życia obiektu. Stosowane np. do implementacji mapowań standaryzująych. Przykładem takiego mapowania jest cache Integera czy BigDecimala (mimo że oba używają silnych referencji – mapowane obiekty są małe). Mapa agreguje wszystkie obiekty ale nie zapobiega ich usunięciu. Dopiero użytkownik mapy tworzy silną referencje do obiektu na którym pracuje uniemożliwiając jego usunięcie.

phantom

Jedyny typ referencji który nie umożliwia „dotarcia” do wskazywanego elementu. Używany, w połączeniu z kolejką referencji, do informowania o tym, że obiekt może zostać usunięty (finalizacja już miała miejsce). Analogicznie działanie umożliwia finalizer obiektu ale użycie tego typu referencji jest bezpieczniejsze, przez brak możliwości „wskrzeszenia” obiektu. Użycie phantom reference umożliwia kontrolowanie który wątek odpowiada za finalizacje, w przypadku finallize aplikacja nie ma na to wpływu. W przeciwieństwie do finallize kod zwalniający zasoby jest umieszczony poza obiektem którego dotyczy.

final

Wykorzystywany przez finalizery. GC pośrednio tworzy ich instancje.


Reference Queue

Referencje inne niż silne można powiązać z kolejką. Kolejka dostarcza informację, o tym że zmieniła się dostępność referenta. Obiekty referencji Soft i Weak trafiają do kolejki w momencie w którym wskazywane obiekty przestają być możliwe do otrzymania przez te typy referencji (czyli w momencie opuszczania stanu Soft Reachable i Weak Reachable). Odwrotnie dla Phantom Reference: obiekty trafiają do kolejki w momencie w którym wchodzą w stan Phantom Reachable. Final Reference są tworzone przez GC kiedy obiekt nadaje się do finalizacji.
Obiekty referencji same podlegają usuwaniu przez GC. Jeśli wskazywany obiekt został usunięty wrapper zwraca nulla.

Jeśli referencje Soft i Weak zostaną usunięte przez GC wtedy automatycznie trafiają do kolejki o ile są do jakiejś przypisane.
Obiekt do którego istnieje Phantom Reference nie zostanie usunięty do czasu aż jego wszystkie fantomowe referencje nie zostaną usunięte lub znullowane przez metodę clear().

Reference Handler

Po załadowaniu klasy dziedziczącej po Reference tworzony jest oddzielny wątek daemona ReferenceHandler, z najwyższym priorytetem, którego zadanie to dodawanie referencji do kolejek. Referencje są dodawane do statycznego pola transient private Reference discovered; w klasie Reference przez Garbage Collectora, pole jest synchronizowane i umożliwia połączenie obiektów referencji w listę. GC rozszerza tę listę a Reference Handler ją skraca.

WeakHashMap

Implementacja mapy która wykorzystuje słabe referencje do przechowywania kluczy. W momencie usunięcia klucza jest on dodawany do wewnętrznej kolejki referencji. Następna interakcja z mapą usunie wpis który zawierał usunięty klucz. Trzeba upewnić się, że wartość nie ma referencji do swojego klucza (zapobiegłoby to jego usunięciu). Wartości mogą być ręcznie opakowane przez słabą referencję przed umieszczeniem w mapie, ale w przypadku ich finalizacji, odpowiadające im wpisy nie będą automatycznie usuwane.


Cleaner

Jeśli kod finalizujący jest prosty może być umieszczony w Cleanerze. Cleaner jest elementem dwukierunkowej listy, zawiera referenta (jako fantomową referencję), referencje do następnego i poprzedniego Cleanera oraz kod wywoływany w czasie sprzątania (w formie Runnable). Sprzątanie jest wykonywane dla wszystkich elementów listy. Kod sprzątający jest wykonywany przez wątek ReferenceHandler a nie wątek/wątki finalizujące. Istnienie Cleaner nie wyłącza finalizera. Cleaner znajduje się w paczce sun.misc co sugeruje niewykorzystywanie go poza core’owym kodem javy. Jest wykorzystywany w pakiecie NIO

Java 9 wprowadziła nowszą wersje Cleanera umieszczoną w java.lang.ref razem powyższymi typami referencji.

Źródła:
„The Java® Language Specification Java SE 8 Edition” James Gosling, Bill Joy, Guy Steele, Gilad Bracha, Alex Buckley
„Inside the Java Virtual Machine” Bill Venners
„Java. Efektywne programowanie” Wydanie II Joshua Bloch
Plugging memory leaks with soft references Brian Goetz
Plugging memory leaks with weak references Brian Goetz
Dokumentacja Soft Reference
Dokumentacja Weak Reference
Dokumentacja Phantom Reference
Dokumentacja Reference – klasy bazowej dla powyższych
Dokumentacja Reference Queue
Dokumentacja WeakHashMap
Kod Cleanera

JLS Unicode

Translacja Unicode

    Kolejność operacji:

  • Zamiana ucieczek Unicode na znaki Unicode
  • Wyszczególnienie z powyższych znaków końca linii
  • Usunięcie białych znaków i komentarzy

Powyższe kroki są wykonywane przed analizą składniową w kompilacji. Można wykorzystywać ucieczki Unicode w formacie \uxxxx gdzie xxxx to wartość znaku zapisana szesnastkowo. Ma to na celu danie możliwości zapisania w programie znaków Unicode przy wykorzystywaniu tylko znaków ASCII.

\u0070\u0075\u0062\u006C\u0069\u0063\u0020\u0063\u006C\u0061\u0073\u0073\u0020\u004F\u006A\u0061\u0063\u0069\u0065\u007B\u0070\u0075\u0062\u006C\u0069\u0063\u0020\u0073\u0074\u0061\u0074\u0069\u0063\u0020\u0076\u006F\u0069\u0064\u0020\u006D\u0061\u0069\u006E\u0028\u0053\u0074\u0072\u0069\u006E\u0067\u005B\u005D\u0020\u0061\u0072\u0067\u0073\u0029\u007B\u0053\u0079\u0073\u0074\u0065\u006D\u002E\u006F\u0075\u0074\u002E\u0070\u0072\u0069\u006E\u0074\u006C\u006E\u0028\u0022\u0048\u0065\u006A\u0068\u006F\u0022\u0029\u003B\u007D\u007D zapisane w pliku Ojacie.java można skompilować i uruchomić (raczej z konsoli).

Ucieczki Unicode muszą mieć nieparzystą ilość backslashy: \u0023 to #, \\u0023 to \u0023, \\\u0023 to \# itd …

Ucieczki mogą mieć więcej niż jedno u. Ostatnie musi poprzedzać cztery znaki szesnastkowe inaczej występuje błąd kompilacji (prekompilacji) \uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu0023 jest akceptowalne.

Zamiana ucieczek na znaki odbywa się tylko raz: \u005cu0023 nie da w wyniku # (005c to kod \).

Specyfikacja dopuszcza konwersję kodu z Unicode do ASCII. Znaki niemieszczące się w ASCII są tłumaczone na ucieczki, ucieczki mają dodatkowe u np. \uu0023.


Znaki końca linii i pliku

Dopuszczalne znaki końca linii to:

  • CR (\u000d) wykorzystywany w : Commodore, Apple II, Mac OS (do wersji 9), Microware OS-9
  • LF (\u000a) wykorzystywany w: Unix, BeOS, AmigaOS, MorphOS, RISC OS, GNU/Linux, Mac OS, Multics
  • CRLF (\u000d\u000a) wykorzystywane w: DOS, OS/2, Microsoft Windows, Symbian, DEC RT-11.

CRCRLF stworzy tylko jeden znak końca linii. Kompilator może ustalać numery linii na podstawie znaków końca linii. Komentarz rozpoczynający się od // kończy się na znaku końca linii.

Jeśli plik kończy się znakiem SUB (\u001a) przeważnie wysyłanym przez control + Z, to jest on ignorowany.
Jego pełna nazwa to Substitute character. Przy wysłaniu znaku do linuksowego terminala shell wysyła do procesu sygnał SIGSTOP który pauzuje jego wykonywanie. Jest też zwyczajowo używany do oznaczania końca pliku (mimo tego że mogą po nim występować jakieś dane – przeważnie nie będą wyświetlone).

Znak jest także wykorzystywany jeśli nie ma możliwości konwersji znaku z jednej reprezentacji do innej. Pominięcie takiego znaku mogłoby skutkować lukami bezpieczeństwa.
Jeśli moduł dodawania użytkowników korzysta z innego kodowania niż moduł autoryzujący a nieznane znaki są pomijane wtedy jest możliwość utworzenia konta użytkownika o nazwie składającej się z już istniejącego użytkownika z uprawnieniami + znaki nieznane przez system kodowania. Moduł dodawania użytkowników widziałby dwóch różnych użytkowników a moduł uwierzytelniania jednego.


Komentarze i białe znaki

Zawartość komentarzy i białe znaki mogą mieć wpływ na kompilacje.
// char lf = '\u000a'; nie skompiluje się – ucieczka zostanie zmieniona na znak końca linii. Inicjalizacja zmiennej typu char mieć miejsce w jednej linii (dla Stringa nawiasy także muszą być zamknięte w tej samej linii ale można użyć znaku plusa i utworzyć nowy literał w następnej linii który będzie dodany do poprzedniego).
Nie dotyczy to komentarza utworzonego przez /* */ lub umieszczenia \n w komentarzu, które jest przetwarzane w czasie kompilacji nie przed.

Białe znaki mają znaczenie jeśli tokeny (najmniejsze fragmenty składniowe) które rozdzielają tworzą inny token gdy są nierozdzielone. += to operator przypisania ale + = daje błąd kompilacji.


Różne

Identyfikatory są takie same jeśli są reprezentowane za pomocą tych samych znaków Unicode. Czyli mogą być wyświetlane w ten sam sposób a dotyczyć różnych zmiennych.

Literał 2147483648 może wystąpić tylko z minusem inaczej ma miejsce błąd kompilacji. Integer w którym domyślnie są zapisywane literały całkowite ma zakres ujemny o jeden większy od zakresu dodatniego przez zastosowanie zapisu U2. Analogicznie dla longa i wartości 9223372036854775808.
Double i Float także mają minimalne (względem odległości od 0) i maksymalne wartości. Jeśli reprezentacja niezerowej wartości jest reprezentowana jako zero – występuje błąd kompilacji, tak samo jeśli wartością jest nieskończoność.
Przy operacjach w których występują bardzo duże i bardzo małe liczby zmiennoprzecinkowe ich reprezentacje muszą być „sprowadzone do wspólnego mianownika” a konkretnie do wspólnej mantysy co skutkuje utratą informacji na temat małej liczby.

Źródła:
„The Java® Language Specification Java SE 8 Edition” James Gosling, Bill Joy, Guy Steele, Gilad Bracha, Alex Buckley
Wikipedia znaki końca linii
Wikipedia Substitute character
Zalecenia Unicode dt. reprezentowania niemapowalnych znaków
Wikipedia Liczby zmiennoprzecinkowe w informatyce

OCA Wyjątki

Wyjątki

Stacktrace wyjątku jest zapisywany w momencie tworzenia wyjątku. Jeśli wyjątek jest utworzony i przekazany do innej metody która go rzuca wtedy wyświetlony stacktrace będzie odpowiadał miejscu w którym był tworzony nie rzucony.

toString() wyświetla nazwę wyjątku i jego tekst
getMessage() zwraca wiadomość
stacktrace jest wyświetlany przez printStackTrace().

Tylko ostatni z rzucanych wyjątków jest propagowany dalej. Rzucanie wyjątku w catch i kolejnego w finally skutkuje rzuceniem drugiego. W tej sytuacji jeśli to sprawdzany wyjątek to musi być zadeklarowany w sygnaturze.

throws w nazwie metody może być „na zapas” ale jeśli jest catch sprawdzanego wyjątku wyjątek musi być możliwy do rzucenia w try.
Jeśli wyjątek to Exception to powyższa zasada nie obowiązuje. Wtedy Exception jest traktowany jako RuntimeException bo może nim być.

Jeśli wywoływana jest nadpisana metoda której bazowa metoda rzuca wyjątek a nadpisująca nie wtedy referencja ma znaczenie – referencja do bazowej wymaga try catch; referencja do rozszerzającej nie.

Można łapać i deklarować RuntimeException.
Można łapać i deklarować Errory.

Źródła:
„OCA: Oracle Certified Associate Java SE 8 Programmer I Study Guide: Exam 1Z0-808” Jeanne Boyarsky, Scott Selikoff
„JA+ V8 for Oracle Certified Associate – Java SE8 Programmer I” http://enthuware.com

OCA Projektowanie klas

Dziedziczenie

Wywołanie innego konstruktora (this lub super) musi być pierwszą instrukcją konstruktora.

Jeśli konstruktor klasy nadrzędnej nie jest wywoływany jawnie wtedy kompilator dodaje odwołanie do bezparametrowego konstruktora klasy nadrzędnej. Jeśli klasa nadrzędna nie ma konstruktora bezparametrowego wtedy jej konstruktor parametrowy musi być wywołany przez konstruktor klasy dziedziczącej.

Konstruktor klasy nadrzędnej nie ma dostępu do metod klasy rozszerzającej (nic o niej nie wie) ale jeśli wywoła swoją metodę która jest nadpisana w klasie rozszerzającej to zostanie wywołana nadpisująca metoda. Jeśli metoda korzysta z pól, pola te będą miały wartość domyślną nawet jeśli jest ona zmieniana w konstruktorze klasy rozszerzającej (konstruktor ten będzie wywołany dopiero po utworzeniu klasy nadrzędnej).

Klasy abstrakcyjne

Klasa abstrakcyjna nie musi mieć żadnych abstrakcyjnych metod.

Metoda abstrakcyjna nie może mieć implementacji ani być prywatna.

Implementacja abstrakcyjnych metod podlega tym samym zasadom co nadpisywanie.

Nadpisywanie

Żeby nadpisanie miało miejsce muszą być spełnione warunki:

  • sygnatury metod muszą być takie same
  • metoda nadpisująca musi być co najmniej tak samo dostępna
  • metoda nadpisująca nie może rzucać ogólniejszego lub nowego sprawdzanego wyjątku (może precyzować)
  • jeśli metoda nadpisująca coś zwraca, typ zwracany musi być ten sam lub bardziej szczegółowy (kowariantnynie dotyczy typów prostych)

Prywatne metody nie są nadpisywane ale mogą być deklarowane metody o tej samej sygnaturze które nie mają związku z metodą klasy bazowej.

Przy nadpisywaniu referencja nie ma znaczenia, istotny jest tylko rzeczywisty typ obiektu (który jest sprawdzany w czasie uruchomienia).

Przesłanianie

Metody statyczne mogą być przesłaniane (poza finalnymi). Warunki dla przesłaniania są takie same jak dla nadpisywania oraz metoda przesłaniana oraz przesłaniająca muszą być statyczne (inaczej występuje błąd kompilacji).

Zmienne statyczne mogą być przesłaniane przez niestatyczne i odwrotnie.

Przesłoniętą metodę można wywołać przez super.metoda() lub rzutowanie, analogicznie dla zmiennych.

Przy przesłanianiu referencja decyduje która metoda będzie wywołana (dzieje się to w czasie kompilacji).

Przeciążanie

Nie można przeciążać po typie zwracanym.


Interfejsy

Interfejsy domyślnie mają modyfikator abstract, dozwolony dostęp publiczny lub pakietowy.

Pola domyślnie mają modyfikatory public static final.

Do pól można odwołać się przez nazwę interfejsu, nazwę klasy która go implementuje lub referencje do niej a wewnątrz klasy która go implementuje także bez żadnej referencji (przez this).

Metody domyślnie mają modyfikatory public abstract.

Metody interfejsu tak jak metody abstrakcyjny nie mogą być private, protected ani final.

Jeśli wiele implementowanych interfejsów ma metodę o tej samej sygnaturze to wystarczy ją zaimplementować raz.

Jeśli metody w dwóch interfejsach mają te same sygnatury ale różne typy zwracane – nie mogą być implementowane jednocześnie.

Metody domyślne interfejsów nie mogą być statyczne, ale można mieć metodę domyślną w interfejsie dziedziczącym o takiej samej sygnaturze jak metoda statyczna w metodzie bazowej (odwrotnie jest błąd kompilacji).

SuperInterface.super.method() wywołuje metodę domyślną z rozszerzanego interfejsu, można przejść tylko jeden poziom do góry.
Samo super się nie skompiluje ponieważ interfejs może rozszerzać kilka interfejsów.

Można nadpisywać metody domyślne (rozszerzając interfejs z metodą domyślną).

Metody domyślne umożliwiają wielokrotne dziedziczenie. Jeśli nie jest jasne która metoda powinna być wywołana występuje błąd kompilacji, chyba że klasa sama nadpisuje metodę która jest dziedziczona domyślnie z kilku interfejsów.

Interfejsy mogą mieć metody statyczne ale te metody nie są dziedziczone przez klasy implementujące interfejs (nie można się do nich odwołać też przez referencje do interfejsu). Dzięki temu nie ma wielokrotnego dziedziczenia (konieczność jawnego napisania o metodę z którego interfejsu chodzi).

Jeśli interfejs ma metodę która także jest w klasie rozszerzanej ale o mniejszym dostępie. Klasa która implementuje i rozszerza jednocześnie musi przesłonić metodę żeby zwiększyć jej poziom dostępu.

Istnienie pól lub metod o jednakowych sygnaturach w interfejsach implementowanych przez jedną klasę nie generuje błędu. Dopiero niejednoznaczne odwołanie (bez podania konkretnego interfejsu) generuje błąd kompilacji.

Źródła:
„OCA: Oracle Certified Associate Java SE 8 Programmer I Study Guide: Exam 1Z0-808” Jeanne Boyarsky, Scott Selikoff
„JA+ V8 for Oracle Certified Associate – Java SE8 Programmer I” http://enthuware.com

OCA Metody i enkapsulacja

Struktura metody

dostęp modyfikatory typZwracany nazwa parametry
pierwsze dwa nie są obowiązkowe.

Opcjonalne modyfikatory:
static, abstract, final, synchronized, native, strictfp mogą występować w dowolnej kolejności, konstruktor nie może mieć żadnego z nich.

static oraz final są dozwolone dla pól i metod
transient oraz volatile – tylko dla pól
synchronized, abstract oraz native – tylko dla metod
final – dozwolone dla zmiennych lokalnych.

Klasy finalne nie mogą być rozszerzane a metody nadpisywane (ale mogą być przeciążane).
Zmiennie finalne nie mogą zmienić wartości ale mogą być przesłonięte.

Statyczna metoda finalna nie może być przesłaniana.

Może istnieć metoda i pole o tej samej nazwie w tym samym zasięgu.


Wywołanie metody

Przy dopasowaniu metody do wywołania java używa metody z najdokładniejszymi typami parametrów, nigdy typy zmiennych nie są zawężane (w przeciwieństwie do przypisywania zmiennych całkowitych). Dotyczy także konstruktorów.

Priorytety przy dopasowaniach:

  • dokładne dopasowanie
  • bardziej pojemny typ
  • automatyczne opakowywanie
  • varargs

Przeprowadzana jest tylko jedna konwersja.
Nie będzie konwersji na bardziej pojemny typ prosty a następnie jego odpowiednik obiektowy.

Wywołanie metody protected na rzecz obiektu z innego pakietu jest możliwe tylko jeśli posługujemy się referencją podklasy.
W przypadku posługiwania się referencją w innym pakiecie to musi być referencja typu dziedziczącego.

Modyfikator private dotyczy wszystkich obiektów tego typu a nie tylko jednego obiektu (patrz equals()).

Do metod i pól statycznych można odwoływać się przez referencję instancji (nawet jeśli wskazuje na null).

Przy wywoływaniu metody z nullem wywoływana jest najbardziej szczegółowa implementacja (klasa która jest najniżej w hierarchii dziedziczenia).

Odwoływanie do pól lub metod statycznych klasy nadrzędnej jest możliwe przez referencje klasy dziedziczącej (ale klasa dziedzicząca nie jest wtedy ładowana).

Metoda statyczna nie ma dostępu do metod i pól instancyjnych.

Finalna zmienna musi być zainicjalizowany w konstruktorze lub w inicjalizacji instancyjnej, ewentualnie w sekcji statycznej jeśli jest statyczny.

Finalne zmienne typów prostych są konwertowane do literałów (są znane w czasie kompilacji).

Typ podawany w lamdzie musi być identyczny z typem w sygnaturze interfejsu funkcyjnego. Jeśli w interfejsie jest List wtedy ArrayList w lambdzie się nie skompiluje.

Źródła:
„OCA: Oracle Certified Associate Java SE 8 Programmer I Study Guide: Exam 1Z0-808” Jeanne Boyarsky, Scott Selikoff
„JA+ V8 for Oracle Certified Associate – Java SE8 Programmer I” http://enthuware.com

OCA Java core API

String

Operator + jest przeciążony: dla argumentów numerycznych to dodawanie, jeśli chociaż jeden z argumentów jest Stringiem jest to konkatentacja.

Przez niezmienność Stringów operacje na nich wykonane zwracają nowy obiekt.

Stringi utworzone przez dodawanie stałych są, w czasie kompilacji, zamieniane na stałe.

Stringi które powstały z wykorzystaniem niefinalnej zmiennej powstają w czasie uruchomienia więc nie są umieszczane w puli. Można je tam umieścić przez wywołanie intern(), metoda zwraca referencje do łańcucha o tej samej zawartości ale umieszczonego w puli.

"0123".substring(4) da w wyniku pusty String ale "0123".charAt(4) rzuci wyjątek, tak samo jak "0123".substring(5).

Operator ++ nie działa dla Stringa, mimo że "napis"+1 da w wyniku napis1.

toString() z Object zwraca String klasaZeScieżką;@hashCodeObiektu, jeśli klasa jest poprzedzona [L – referencja wskazuje tablicę obiektów tego typu [Ljava.langString;@120bc123.

indexOf przyjmuje Stringa lub znak jako int.

Metody które rzucają StringIndexOutOfBoundsExcepion delete, deleteCharAt, replace, insert, substring
Metody które rzucają bardziej ogólny IndexOutOfBoundsExcepion append, insert, setLength, charAt, codePointAt, codePointBefore, codePointCount, offsetByCodePoints, getChars, setCharAt, subSequence. W praktyce wszystkie te metody rzucają szczegółowy wyjątek, ale to może się zmienić i dalej będą zgodne z dokumentacją.


StringBuilder

Obiekt o zmiennej zawartości, domyślna pojemność to 16 znaków.

equals porównuje referencje a nie zawartość.

setLength może obciąć łańcuch lub dopełnić go za pomocą null character ’\u0000′.

indexOf przyjmuje Stringa.

append przyjmuje wszystko poza short i byte.


Tablice

W momencie dodawania elementu do tablicy sprawdzany jest typ obiektu i może zostać rzucony ArrayStoreException. Jest to spowodowane kowariancyjnością tablic.

Tablica Stringów jest podtypem tablicy Obiektów.

Tablica dwuwymiarowa to tablica kolumn (które też są tablicami) dlatego nie muszą być prostokątne.

Tablice wielowymiarowe podczas deklaracji wymagają podania tylko pierwszego wymiaru.

Tablica może być zadeklarowana przez String [] tab = {„a”, „b”} tylko w momencie inicjalizacji, w innych miejscach będzie to błąd kompilacji i trzeba to zrobić przez tab = new String[] {„a”, „b”}.

Puste tablice dwuwymiarowe można zainicjalizować przez jedną parę nawiasów.

java.util.Arrays zawiera narzędzia do sortowania, przeszukiwania i wyswietlania tablic. Arrays.binarySech zwraca pozycję elementu lub indeks miejsca w którym zostałby dodany -1.

Tablice nie przeciążają equals za to domyślnie implementują Serializable i Cloneable.


ArrayList

add() zawsze zwraca true.

ArrayList rozszerza AbstractList – szkieletową implementacje listy z dostępem do dowolnego elementu która umożliwia sekwencyjne dodawanie elementów, czyszczenie całej listy, łączenie list (addAll()), Iterator, subList(), equals(), hashCode()
Odpowiednik dla dostępu szeregowego to AbstractSequentialList.

remove(Object object) mówi czy usunięcie miało miejsce
remove(int index) zwraca usuwany element lub rzuca IndexOutOfBoundsException.

set(int index, E newElement) nadpisuje i zwraca zastąpiony element.


Typy opakowujące

Wszystkie typy są niezmienne. Wszystkie mają cache który przechowuje wartości od -128 do 127 (węższy zakres mają Character i Boolean, Integer ma konfigurowalny górny zakres). Cache jest wykorzystywany przez valueOf() który jest wywoływany przy tworzeniu literałów i podczas autoboxingu.

Integer.parseInt(String string) zwraca typ prosty (analogicznie dla innych typów opakowujących).

Autoboxing ma niższy priorytet w dopasowaniu parametrów niż bezpośrednie dopasowanie
listaIntegerow.remove(1) usunie pierwszy element
listaIntegerow.remove(new Integer(1)) usunie obiekt o wartości 1.


java.time

LocalDate przechowuje datę, LocalTimeprzechowuje czas, LocalDateTime przechowuje oba.
Wszystkie te obiekty są niezmienne, konstruowane przez statyczne metody, implementują TemporalAccessor którego zaleca się nie stosować.

LocalDate.of(int rok, int miesiąc, int dzień) miesiące są numerowane od 1 i mogą być zastąpione przez enuma Month.

LocalDateTime.of(rok, miesiąc, dzień, godzina, minuta, sekunda) lub LocalDateTime.of(data, czas).

Utworzenie daty przez podanie błędnych parametrów jest wykrywane dopiero w czasie uruchomienia (DateTimeException).

Wszystkie obiekty mają fluent API do dodawania lub odejmowania wartości (zwracają nowy obiekt).

LocalDate nie ma metod do modyfikowania czasu.

Period

Period.ofYears, Period.ofMonths, Period.ofWeeks, Period.ofDays tworzą obiekty okresu które można dodawać/odejmować od daty.

Okresy nie mają fluent API to tworzenia (tak samo jak Local*Time) of() to statyczne metody zwracające nowy obiekt. Przy próbie użycia of() jak fluent API tylko ostatnia instrukcja ma znaczenie.

Obiekt Period bierze pod uwagę zmiany strefy czasowej lub czasu na letni/zimowy. Analogiczny obiekt przechowujący bezwzględny okres to Duration.

DateTimeFormatter to obiekt opisujący formatowanie dla czasu. Formatowaniu można przekazać datę lub odwrotnie (dawne SimpleDateFormat).

Źródła:
„OCA: Oracle Certified Associate Java SE 8 Programmer I Study Guide: Exam 1Z0-808” Jeanne Boyarsky, Scott Selikoff
„JA+ V8 for Oracle Certified Associate – Java SE8 Programmer I” http://enthuware.com

OCA Operatory i instrukcje

Priorytety operatorów

– indeks tablicy, wywołanie metody i odwołanie do pola po kropce
– post-unary ++,+
– pre-unary --, -
– reszta jednoargumentowych (!, ~ – bitowa negacja)
– rzutowanie, new
– mnożenie / dzielenie / modulo
– dodawanie/ odejmowanie
– przesunięcia bitowe
– relacyjne
– równość / nierówność, instanceof
– operatory logiczne
– podwójne operatory logiczne
– operator trójargumentowy
– przypisania (compound)

Pojedyncze operatory logiczne & i | na typach logicznych mają działanie logiczne na numerycznych to operacje bitowe.

Wartość ujemna jest tworzona przez negacje binarną wartości dodatniej (kodowanie U2). Negacja binarna neguje bity i dodaje 1 więc Integer.MIN_VALUE == -Integer.MIN_VALUE.


Rzutowanie

Przy operacji na dwóch różnych typach prostych mniejszy jest promowany do większego.

Typy proste są automatycznie opakowywane, typy opakowujące są automatycznie rozpakowywane.

byte, short i char są promowane do int przy dwuargumentowych operacjach arytmetycznych
System.out.println('a') wyswietla literę
ale System.out.println('a' + 1 ) wyswietla 98.

Kompilator nie pozwala na niejawne rzutowanie w dół (jest wyjątek patrz niżej), jawne rzutowanie na typ całkowity ucina część ułamkową, w obrębie typów całkowitych bierze tylko tyle najmniej istotnych bitów ile może.

Niejawne rzutowanie w dół ma miejsce dla całkowitych zmiennych finalnych (lub literałów) mieszczących się w zakresie docelowego typu.

short i char mają różne zakresy (poza powyższym przypadkiem potrzebne jest rzutowanie).

x+=y to tak naprawdę x =(typ y) x + y ma znaczenie jeśli argumenty są różnych typów.

Można rzutować typy proste na opakowujące
int i = 1; (Integer) i.

Można rzutować nulla.

Można rzutować wszystkie niefinalne klasy na interfejs. Któraś podklasa może implementować ten interfejs, więc kompilator dopuszcza rzutowanie.

Przy wywoływaniu metod (rzutowanie parametru) występują dodatkowe warunki.


Operator trójargumentowy

Wykonuje tylko jedną z dwóch instrukcji.

Instrukcja może być kolejnym operatorem trójargumentowym.

Jeśli ma argumenty byte i short – wynik to short.

Jeśli ma argumenty byte, short lub char a drugi to literał (int) możliwy do zapisania w pierwszym – wynik to pierwszy typ.

W innych przypadkach typy muszą być możliwe do konwersji inaczej występuje błąd kompilacji.


Pętle

Nie można przesłaniać zmiennych w pętli for, wszystkie deklarowane zmienne muszą być tego samego typu. Przecinek nic nie zmienia – typ musi się pojawić tylko raz.

Zmienne zadeklarowane w bloku do nie są widoczne w warunku while.

Foreach działa tylko dla obiektów implementujących Iterable.

while nie wymaga średnika, do-while wymaga po warunku, żadne z nich nie wymaga {}.


Etykiety

Pętle, switche i warunki mogą mieć etykiety nazwa: instrukcja. Wykonanie wewnątrz break nazwa przenosi wykonanie do następnej instrukcji po bloku oznaczonym etykietą.


Switch

Pusty switch {} jest ok, zmienna musi być wystarczająco pojemna żeby pomieścić wszystkie przypadki, przypadki nie mogą się powtarzać. Long nie może być w switchu.

Switch jako wartość przed dwukropkiem dopuszcza tylko te znane w momencie kompilacji (literały, zmienne finalne zadeklarowane i zainicjalizowane w jednej instrukcji oraz enumy).

Zmienne to: całkowite typy numeryczne + char oraz ich typy opakowujące, enumy, Stringi (Java 7).

Zmienna zadeklarowana w jednym wpisie case switcha jest zadeklarowana (ale nie zainicjalizowana) także w innych. Dodanie nawiasów klamrowych po case zawęża zakres (wtedy powyższy przypadek nie ma miejsca) ale break; i tak jest wymagane.


Referencje

Przypisanie ma wartość prawej strony (z=1; x =y = ++z; wszystkie zmienne są równe dwa).

Dwa nulle są sobie równe.

null jest zamieniany na „null” jeśli wyrażenie jest traktowane jako String (na przykład przy dodawaniu Stringów).

Nie można porównywać referencji różnych typów (obiektowych).

Źródła:
„OCA: Oracle Certified Associate Java SE 8 Programmer I Study Guide: Exam 1Z0-808” Jeanne Boyarsky, Scott Selikoff
„JA+ V8 for Oracle Certified Associate – Java SE8 Programmer I” http://enthuware.com