Java-haastatteluun valmistautuminen

Java-työhaastattelukysymykset ja vastaukset 2026

30 Java-haastattelukysymyksen kokoelma backend-kehittäjille, aina kielen perusteista ja JVM:n toiminnasta kokoelmiin, Stream API -rajapintaan ja samanaikaisuuteen.

Aloita Java-harjoitushaastatteluEi vaadi luottokorttia. 1 ilmainen sessio saatavilla.
Teknisten haastattelujen harjoittelu englanniksiTila, jossa ei-äidinkieliset puhujat voivat harjoitella teknisen haastattelun läpäisemistä.

Java ja JVM

1Mikä on Java ja miksi sitä pidetään alustariippumattomana?

Java on staattisesti tyypitetty yleiskäyttöinen ohjelmointikieli. Lähdekoodi käännetään tavukoodiksi, ja tavukoodi suoritetaan JVM (Java Virtual Machine) -ympäristössä. Sama `.class`- tai `.jar`-tiedosto voi yleensä toimia eri käyttöjärjestelmissä ilman uudelleenkääntämistä, jos yhteensopiva JVM on saatavilla. Tämä riippumattomuus ei ole ehdotonta: natiivikirjastot, alustakohtaiset polut, järjestelmäkomennot ja käyttöjärjestelmän toiminta voivat silti tehdä sovelluksesta alustariippuvaisen.

Java source -> javac -> bytecode -> JVM -> machine code
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

2Mitä eroa on JVM:llä, JRE:llä ja JDK:lla?

JVM (Java Virtual Machine) lataa ja suorittaa tavukoodia sekä hallitsee muistia, säikeitä ja roskienkeruuta (Garbage Collection). JRE (Java Runtime Environment) tarkoitti historiallisesti JVM:ää sekä kirjastoja, joita sovellusten ajamiseen vaaditaan. JDK (Java Development Kit) sisältää ajoympäristön lisäksi kehitystyökaluja, kuten `javac`, `java`, debuggerin, `javadoc`, `jar` ja diagnostiikkatyökaluja. Nykyaikaisissa jakeluissa erillinen JRE on harvinaisempi, koska sovellukset toimitetaan usein kustomoidun ajoympäristövedoksen (custom runtime image) kanssa.

JDK = development tools + runtime
Runtime = JVM + standard libraries
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

3Miten JVM (Java Virtual Machine) suorittaa Java-koodia ja miksi JIT (Just-In-Time) -kääntämistä tarvitaan?

`javac` kääntää lähdekoodin tavukoodiksi. JVM lataa luokat ja alkaa tulkita tai suorittaa tavukoodikäskyjä. Suorituskyvyn kannalta kriittiset koodin osat tunnistetaan ajonaikaisesti, ja JIT-kääntäjä kääntää ne optimoiduksi konekieleksi. JIT voi sisällyttää metodeja suoraan kutsukohtaan (inline), optimoida silmukoita, soveltaa pakoanalyysiä ja poistaa joitakin muistinvarauksia. Tästä syystä Java-sovellukset voivat nopeutua lämpenemisvaiheen jälkeen, ja siksi ilman JMH-työkalua tehdyt mikrotason suorituskykymittaukset ovat usein harhaanjohtavia.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

4Kuinka `ClassLoader` toimii?

`ClassLoader` lataa luokkamääritykset JVM (Java Virtual Machine) -ympäristöön. Yleisimmät lataajat ovat Bootstrap, Platform, Application ja mukautetut lataajat. Luokkaa ei tunnisteta pelkästään sen täysin määritellyn nimen (fully qualified name) perusteella, vaan myös sen ladanneen `ClassLoader`:in mukaan, joten kaksi samannimistä luokkaa eri lataajista voivat olla eri tyyppejä. Java käyttää yleensä vanhemmalle delegointia (parent delegation): lataaja kysyy ensin vanhemmaltaan vähentääkseen alustaluokkien korvautumisen riskiä.

Loading
Linking: verification, preparation, resolution
Initialization
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

5Miten JVM:n (Java Virtual Machine) muisti on organisoitu?

JVM:n keskeisiä muistialueita ovat keko (heap) olioille ja taulukoille, Java-pinot (stacks), jotka sisältävät kutsukehykset (call frames) kullekin säikeelle, Metaspace-alue luokkien metatiedoille, koodivälimuisti JIT (Just-In-Time) -käännetylle koodille sekä ohjelmalaskurirekisteri (PC register) ja natiivipinot. Vältä yliyksinkertaistusta, jonka mukaan primitiivityypit sijaitsevat aina pinossa ja oliot aina keossa: primitiivityyppinen kenttä tallennetaan osaksi oliotaan, ja JIT-kääntäjän pakoanalyysi (escape analysis) voi poistaa muistinvarauksia tai muuttaa olion esitystapaa.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

6Miten roskienkerääjä (Garbage Collector) toimii?

Roskienkerääjä (GC) vapauttaa muistia, jota varaavat oliot eivät ole enää saavutettavissa GC-juurista (GC Roots). Juuriin kuuluvat aktiivisten metodien paikalliset muuttujat, staattiset kentät, aktiiviset säikeet ja JNI-viittaukset. Monet roskienkerääjät perustuvat havaintoon, että useimmat uudet oliot ovat lyhytikäisiä, joten keko (heap) on usein jaettu nuoreen ja vanhaan sukupolveen. GC ei sulje tiedostoja tai yhteyksiä deterministisesti; ulkoiset resurssit tulisi sulkea `try-with-resources`-rakenteella.

try (var stream = Files.newInputStream(path)) {
    // use stream
}
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

Olio-ohjelmointi (OOP) ja tyypit

7Mitä neljää OOP (Object-Oriented Programming) -periaatetta Java tukee?

Java tukee kapselointia, abstraktiota, periytymistä ja polymorfismia. Kapselointi piilottaa sisäisen tilan julkisen rajapinnan taakse. Abstraktio keskittyy olennaiseen toiminnallisuuteen. Periytyminen luo uuden tyypin olemassa olevan tyypin pohjalta. Polymorfismi sallii koodin toimia eri toteutusten kanssa jaetun sopimuksen kautta. Käytännössä kompositio on usein periytymistä parempi vaihtoehto, koska se johtaa vähäisempään kytkeytymiseen.

interface PaymentService {
    void pay(BigDecimal amount);
}

class CardPaymentService implements PaymentService {
    @Override
    public void pay(BigDecimal amount) {
        // payment implementation
    }
}
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

8Mitä eroa on metodien ylikuormittamisella (overloading) ja ylikirjoittamisella (overriding)?

Ylikuormitus tarkoittaa, että usealla metodilla on sama nimi mutta eri parametrit; kääntäjä valitsee metodin argumenttien staattisten tyyppien perusteella. Ylikirjoitus tarkoittaa, että aliluokka korvaa yläluokan instanssimetodin toteutuksen; ajonaikainen sidonta valitsee metodin olion todellisen tyypin perusteella. Staattiset metodit piilotetaan, ei ylikirjoiteta, eikä yksityisiä metodeja ylikirjoiteta, koska ne eivät periydy saavutettavina jäseninä.

void send(String message) {}
void send(String message, int priority) {}

class Animal {
    void speak() { System.out.println("Animal"); }
}

class Dog extends Animal {
    @Override
    void speak() { System.out.println("Dog"); }
}
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

9Mitä eroa on rajapinnalla (interface) ja abstraktilla luokalla?

Rajapinta määrittelee sopimuksen ja voi sisältää abstrakteja metodeja, `default`-metodeja, staattisia metodeja, vakioita ja yksityisiä apumetodeja. Abstraktilla luokalla voi näiden lisäksi olla ilmentymän tila, konstruktoreita, suojattuja (protected) kenttiä, jaettua toteutusta sekä abstrakteja tai konkreettisia metodeja. Luokka voi periä vain yhden luokan, mutta se voi toteuttaa useita rajapintoja. Käytä rajapintaa riippumattomiin sopimuksiin ja abstraktia luokkaa läheisesti toisiinsa liittyviin tyyppeihin, joilla on jaettua toteutusta ja tilaa.

interface Repository<T> {
    Optional<T> findById(long id);
}
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

10Mitä final-avainsana tarkoittaa?

`final`-muuttujalle voidaan asettaa arvo vain kerran, mutta `final`-viittaus ei tee viitatusta oliosta muuttumatonta. `final`-metodia ei voida ylikirjoittaa. `final`-luokasta ei voida periä. `final` viestii tarkoituksesta ja rajoittaa viittausten muutoksia tai periytymistä, mutta yksinään se ei tarjoa täydellistä muuttumatonta mallia tai säieturvallisuutta.

final int maxAttempts = 3;

final List<String> names = new ArrayList<>();
names.add("Alice"); // allowed

public final void validate() {}

public final class Token {}
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

11Mitä Java-kielen `record`-tyypit ovat?

`record` on tiivis syntaksi muuttumattomille tietojen kantajille. Kääntäjä generoi automaattisesti yksityiset `final`-kentät, konstruktorin, aksessorit sekä `equals`-, `hashCode`- ja `toString`-metodit. `record`-tyypit ovat hyödyllisiä DTO-olioille (Data Transfer Object), arvo-olioille ja metodien paluuarvoille. Ne eivät kuitenkaan korvaa kaikkia toimialueen entiteettejä: validointia voidaan lisätä tiiviin konstruktorin avulla, mutta monimutkainen muuttuva tila, laiska lataus ja periytyvyyteen perustuvat mallit sopivat niille yleensä huonosti.

public record User(long id, String name) {}

public record Order(List<String> items) {
    public Order {
        items = List.copyOf(items);
    }
}
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

12Miten Javassa luodaan muuttumaton olio?

Tee luokasta `final` tai rajoita periytymistä huolellisesti, tee kentistä `private final`, vältä asetusmetodeja ja suorita validointi konstruktorissa. Jos syötteet ovat muuttuvia, luo puolustavat kopiot ja palauta kopioita tai muokkaamattomia näkymiä. Syvä muuttumattomuus vaatii, että myös kaikki sisäkkäinen tila on muuttumatonta. `final`-kentät edistävät turvallista julkaisua, mutta olio voi silti olla loogisesti muuttuva, jos se paljastaa muuttuvan kokoelman tai muuttuvan sisäkkäisen olion.

public final class User {
    private final String name;
    private final List<String> roles;

    public User(String name, List<String> roles) {
        this.name = name;
        this.roles = List.copyOf(roles);
    }

    public List<String> getRoles() {
        return roles;
    }
}
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

Viittaukset, merkkijonot ja vertailu

13Välittääkö Java parametrit arvon vai viitteen perusteella?

Java välittää argumentit aina arvon perusteella. Primitiivityyppien kohdalla itse arvo kopioidaan. Olioiden kohdalla viitteen arvo kopioidaan. Metodi voi muuttaa oliota kopioidun viitteen kautta, mutta se ei voi korvata kutsujan viitettä toisella oliolla. Tämä ero selittää, miksi kentän muuttaminen voi näkyä kutsujalle, kun taas parametrin sijoittaminen uuteen olioon ei näy.

void change(int value) {
    value = 100;
}

void rename(User user) {
    user.setName("Kate");
}

void replace(User user) {
    user = new User("New");
}
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

14Mitä eroa on `==`-operaattorilla ja `equals()`-metodilla?

Alkeistyypeillä `==` vertailee arvoja. Olioviittauksilla `==` tarkistaa, osoittavatko molemmat viittaukset samaan olioon. `equals()` on metodi, jonka tulisi vertailla loogista yhtäsuuruutta, jos luokka toteuttaa sen oikein. `String`, `Integer`, `LocalDate` ja monet arvotyyppiluokat ylikirjoittavat `equals`-metodin. Mukautetuissa sovellusalueen olioissa (domain-olioissa) `equals`-metodin on heijastettava haluttua identiteetti- tai arvosemantiikkaa.

int a = 10;
int b = 10;
System.out.println(a == b); // true

String first = new String("java");
String second = new String("java");

System.out.println(first == second);      // false
System.out.println(first.equals(second)); // true
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

15Mikä on equals- ja hashCode-metodien välinen sopimus?

Jos kaksi oliota ovat yhtä suuria equals-metodin mukaan, niiden on palautettava sama hashCode. Käänteinen ei ole vaatimus: eri olioilla voi olla sama hashCode. equals-metodin on oltava refleksiivinen, symmetrinen, transitiivinen ja johdonmukainen, ja sen on palautettava false arvolle null. Sopimuksen rikkominen korruptoi HashMap-, HashSet- ja muiden tiivisteeseen perustuvien kokoelmien toiminnan: oliota voi olla mahdotonta löytää, vaikka se olisi kokoelmassa.

@Override
public boolean equals(Object object) {
    if (this == object) return true;
    if (!(object instanceof User other)) return false;
    return id == other.id;
}

@Override
public int hashCode() {
    return Long.hashCode(id);
}
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

16Miksi `String` on muuttumaton?

`String` on muuttumaton turvallisuuden, ennakoitavuuden ja suorituskyvyn vuoksi. Muuttumattomuus mahdollistaa merkkijonojen sisäistämisen (string interning), turvallisen jakamisen säikeiden kesken, luotettavan `hashCode`-arvon välimuistiin tallentamisen sekä turvallisen käytön poluissa, URL-osoitteissa, luokkien nimissä ja turvallisuuskriittisissä arvoissa. Operaatiot, kuten `concat`, `replace` ja `substring`, luovat uusia merkkijonoja. Java 9:stä lähtien `String` saattaa sisäisesti käyttää `byte[]`-taulukkoa tiiviimmässä esitysmuodossa, mutta sen julkinen muuttumattomuuslupaus pysyy ennallaan.

String value = "Java";
value.toUpperCase();

System.out.println(value); // Java

String a = "java";
String b = "java";

System.out.println(a == b); // often true due to interning
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

17Mitä eroa on `String`-, `StringBuilder`- ja `StringBuffer`-luokilla?

`String` on muuttumaton, joten toistuva merkkijonojen yhdistäminen silmukoissa voi luoda paljon väliaikaisia olioita, vaikka kääntäjä osaakin optimoida yksinkertaiset tapaukset. `StringBuilder` on muuttuva ja suositeltava tapa rakentaa merkkijonoja yhdessä säikeessä. `StringBuffer` on samankaltainen mutta synkronoitu, joten se on yleensä hitaampi ja pääasiassa merkityksellinen vain vanhemmissa säieturvallisissa API-rajapinnoissa. Muotoilussa `String.format` tai mallinepohjaiset rajapinnat voivat olla selkeämpiä, mutta ne eivät aina ole nopeampia.

StringBuilder builder = new StringBuilder();

for (String item : items) {
    builder.append(item).append(',');
}

String result = builder.toString();

String message = "Hello, " + name;
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

Geneeriset tyypit ja kokoelmat

18Mitä ovat geneeriset tyypit (generics) ja mitä tarkoittaa tyyppien poisto (type erasure)?

Geneeristen tyyppien avulla koodissa voidaan ilmaista tyyppiturvallisia säiliöitä ja API-rajapintoja (Application Programming Interface), kuten `List<String>`. Käännösaikana kääntäjä tarkistaa tyypit ja lisää tarvittaessa tyyppimuunnoksia. Suoritusaikana suurin osa geneerisestä tyyppitiedosta poistetaan, joten `List<String>` ja `List<Integer>` käyttävät samaa suorituksenaikaista luokkaa. Tämän vuoksi ei voida suoraan luoda instanssia `new T()`, käyttää alkeistietotyyppejä tyyppiparametreina tai tarkistaa tyyppiä luotettavasti rakenteella `instanceof List<String>`.

List<String> names = new ArrayList<>();
names.add("Alice");

List<? extends Number> producer;
List<? super Integer> consumer;
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

19Mikä on Java Collections Framework?

Collections Framework on kokoelma rajapintoja ja toteutuksia oliojoukkojen tallentamiseen ja käsittelyyn. Ydinrajapintoihin kuuluvat `List`, `Set`, `Queue`, `Deque` ja `Map`. Toteutuksiin kuuluvat puolestaan muun muassa `ArrayList`, `LinkedList`, `HashSet`, `TreeSet`, `HashMap`, `TreeMap` ja `PriorityQueue`. Oikea kokoelma valitaan hakumallin, järjestysvaatimusten, yksilöllisyyden, hakuoperaatioiden aikavaativuuden, muistinkulutuksen ja samanaikaisuusvaatimusten perusteella.

List<String> list = new ArrayList<>();
Set<Long> ids = new HashSet<>();
Map<Long, User> usersById = new HashMap<>();
Deque<String> queue = new ArrayDeque<>();
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

20Miten `HashMap` toimii?

`HashMap` tallentaa avain-arvoparit lokeroihin (buckets), jotka valitaan avaimen tiivisteen (hash) perusteella. Hakua varten se laskee tiivisteen, etsii oikean lokeron ja vertailee avaimia `equals`-metodilla. Törmäykset käsitellään lokeron sisällä; nykyaikaisessa Javassa pitkät ketjut voidaan muuttaa tasapainotetuiksi puiksi tiettyjen ehtojen täyttyessä. `HashMap` sallii yhden `null`-avaimen ja useita `null`-arvoja, se ei ole säieturvallinen, ja sen koko kasvaa automaattisesti, kun kuormituskertoimen kynnysarvo ylittyy.

Map<Long, User> users = new HashMap<>();
users.put(user.id(), user);

User found = users.get(user.id());
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

21Mitä eroa on HashMap-, LinkedHashMap-, TreeMap- ja ConcurrentHashMap-luokilla?

`HashMap` tarjoaa nopean järjestämättömän pääsyn eikä ole säieturvallinen. `LinkedHashMap` säilyttää lisäys- tai käyttöjärjestyksen ja on hyödyllinen LRU (Least Recently Used) -tyyppisissä välimuisteissa. `TreeMap` pitää avaimet lajiteltuna luonnollisen järjestyksen tai `Comparator`-rajapinnan mukaan ja sen operaatioiden aikavaativuus on O(log n). `ConcurrentHashMap` on suunniteltu samanaikaiseen käyttöön segmentoidun tai sisäisen synkronoinnin avulla, se ei salli null-avaimia tai -arvoja, ja sitä kannattaa suosia `synchronizedMap`:n sijaan raskaassa kuormituksessa.

map.computeIfAbsent(key, ignored -> loadValue());
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

22Mitä eroa on ArrayList- ja LinkedList-luokilla?

`ArrayList` perustuu taulukkoon. Se tarjoaa nopean indeksipohjaisen haun, tiiviin muistiasettelun ja tehokkaan läpikäynnin, mutta alkioiden lisääminen keskelle vaatii muiden alkioiden siirtämistä. `LinkedList` on kaksisuuntaisesti linkitetty lista. Siihen lisääminen on kevyempää, jos kohdesolmu on jo tiedossa, mutta indeksipohjaisen haun aikavaativuus on O(n), muistin lisäkuorma on suurempi ja välimuistin paikallisuus on huonompi. Monissa todellisissa sovelluksissa `ArrayList` on nopeampi, vaikka mukana olisi jonkin verran lisäyksiä.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

23Mitä eroa on Comparable- ja Comparator-rajapinnoilla?

`Comparable` määrittää luokan sisäisen luonnollisen järjestyksen `compareTo`-metodin avulla. `Comparator` on erillinen olio tai lambda, joka määrittää ulkoisen järjestyksen. `Comparable` on hyödyllinen silloin, kun tyypillä on yksi ilmeinen oletusjärjestys. `Comparator` on parempi valinta, kun on olemassa useita eri järjestämistapoja, kuten nimen, päivämäärän tai prioriteetin mukaan. `compare`- ja `compareTo`-metodien on oltava johdonmukaisia ja mieluiten yhteensopivia `equals`-metodin kanssa, kun niitä käytetään lajitelluissa Set- tai Map-tietorakenteissa.

class User implements Comparable<User> {
    private int age;

    @Override
    public int compareTo(User other) {
        return Integer.compare(age, other.age);
    }
}

Comparator<User> byName =
    Comparator.comparing(User::getName);

Comparator<User> byAgeThenName =
    Comparator.comparingInt(User::getAge)
              .thenComparing(User::getName);
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

Poikkeukset ja resurssit

24Mitä eroa on tarkistetuilla (checked) ja tarkistamattomilla (unchecked) poikkeuksilla?

Tarkistetut poikkeukset perivät `Exception`-luokan, mutta eivät `RuntimeException`-luokkaa, ja kääntäjä vaatii kutsujaa käsittelemään ne tai esittelemään ne metodin otsikossa. Tarkistamattomat poikkeukset perivät `RuntimeException`-luokan, eikä niitä vaadita metodien esittelyissä. Tarkistetut poikkeukset sopivat API-rajapinnoissa tilanteisiin, joista voidaan vielä toipua. Tarkistamattomat poikkeukset sopivat ohjelmointivirheisiin, virheellisiin tiloihin ja sopimusrikkomuksiin. Nykyaikaisessa palvelukoodissa tiimit välttävät usein liiallisia tarkistettuja poikkeuksia, koska ne leviävät kerrosten läpi ja lisäävät koodiin turhaa kohinaa.

void load() throws IOException {
    // ...
}

throw new IllegalArgumentException("Invalid id");
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

25Mitä eroa on throw- ja throws-avainsanoilla sekä try-with-resources-rakenteella?

`throw` heittää poikkeuksen välittömästi. `throws` ilmoittaa mahdollisesta poikkeuksesta metodin esittelyssä. `try-with-resources` sulkee automaattisesti `AutoCloseable`-rajapinnan toteuttavat resurssit käänteisessä järjestyksessä. Jos sekä päälohko että resurssin sulkeminen heittävät poikkeuksen, jälkimmäinen säilytetään vaimennettuna (suppressed) poikkeuksena. Tämä on turvallisempaa ja selkeämpää kuin manuaaliset `finally`-lohkot, kun käytössä on useita resursseja.

throw new IllegalArgumentException("Invalid id");

void readFile() throws IOException {}

try (var reader = Files.newBufferedReader(path)) {
    return reader.readLine();
}
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

Stream API ja funktionaalinen tyyli

26Mikä on Stream API?

Stream API mahdollistaa sekvenssien deklaratiivisen käsittelyn. Välioperaatiot, kuten filter, map ja sorted, suoritetaan laiskasti; pääteoperaatiot, kuten toList, collect, reduce ja forEach, käynnistävät suorituksen. Stream ei ole kokoelma, sitä käytetään yleensä vain kerran, ja se toimii parhaiten puhtaiden, sivuvaikutuksettomien funktioiden kanssa. parallelStream ei takaa nopeampaa suoritusta ja se voi heikentää suorituskykyä tai ohjelman oikeellisuutta.

List<String> names = users.stream()
    .filter(User::isActive)
    .sorted(Comparator.comparing(User::getName))
    .map(User::getName)
    .toList();
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

27Mikä on Optional ja milloin sitä tulisi käyttää?

Optional<T> ilmaisee eksplisiittisesti tuloksen puuttumisen ja toimii hyvin paluuarvon tyyppinä. Hyödyllisiä metodeja ovat map, flatMap, filter, orElse, orElseGet ja orElseThrow. orElse evaluoi argumenttinsa ahneesti, kun taas orElseGet-metodin saama tuottaja suoritetaan vain, jos arvo puuttuu. Optional-luokkaa ei yleensä suositella entiteettien kenttiin, metodien parametreihin tai sarjallistettuihin DTO (Data Transfer Object) -olioihin ilman vahvaa perustetta.

Optional<User> user = repository.findById(id);

String name = user
    .map(User::getName)
    .orElse("Unknown");

optional.orElse(expensiveOperation());
optional.orElseGet(() -> expensiveOperation());
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

Samanaikaisuus

28Mitä synchronized, volatile ja atomiset luokat takaavat?

synchronized tarjoaa poissulkevuuden ja happens-before-suhteen. volatile takaa uusimpien kirjoitusten näkyvyyden ja rajoittaa käskyjen uudelleenjärjestämistä, mutta se ei tee yhdistelmäoperaatioista atomisia. Atomiset luokat tarjoavat yksittäisiä atomisia operaatioita, kuten incrementAndGet. Käytä lukkoja tai synchronized-avainsanaa useisiin toisiinsa liittyviin muutoksiin, volatile-avainsanaa yksinkertaisiin tilalippuihin ja AtomicInteger- tai LongAdder-luokkia kilpailutilanteissa oleviin laskureihin.

synchronized (lock) {
    counter++;
}

private volatile boolean running = true;

AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet();
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

29Mitä ovat kilpailutilanne, lukkiutuminen ja säieturvallisuus?

Kilpailutilanne syntyy, kun tulos riippuu säikeiden ennalta-arvaamattomasta suoritusjärjestyksestä. Lukkiutuminen tapahtuu, kun säikeet odottavat ikuisesti toistensa resursseja. Säieturvallisuuden saavuttamiseksi vähennä jaettua muuttuvaa tilaa, pidä lukitusjärjestys yhdenmukaisena, lyhennä kriittisiä osioita, vältä ulkoisen koodin kutsumista lukkojen ollessa varattuna, ja käytä muuttumattomia olioita, samanaikaisuutta tukevia kokoelmia, aikakatkaisuja tai viestinvälitystä. Säieturvallisuus on koko tilankäsittelyprotokollan ominaisuus.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

30Mitä eroa on ExecutorService-rajapinnalla, CompletableFuture-luokalla ja virtuaalisäikeillä?

ExecutorService hallitsee säiepoolia ja tehtävien suoritusta. CompletableFuture auttaa asynkronisten operaatioiden yhdistämisessä. Virtuaalisäikeet ovat kevyitä JVM (Java Virtual Machine) -säikeitä, jotka ovat käteviä suurelle määrälle estäviä I/O-tehtäviä. Ne yksinkertaistavat säie per pyyntö -mallia, mutta ne eivät nopeuta suoritinsidonnaista työtä, poista tietokannan tai ulkoisten resurssien rajoituksia, eivätkä tee muuttuvasta tilasta automaattisesti säieturvallista.

try (var executor = Executors.newFixedThreadPool(4)) {
    Future<Integer> future = executor.submit(() -> calculate());
    int result = future.get();
}

CompletableFuture<User> userFuture =
    CompletableFuture.supplyAsync(this::loadUser)
        .thenApply(this::enrichUser)
        .exceptionally(error -> fallbackUser());

try (var executor =
         Executors.newVirtualThreadPerTaskExecutor()) {
    executor.submit(() -> handleRequest());
}
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa