Java-alustan uusin versio, Java 8 , julkaistiin yli vuosi sitten. Monet yritykset ja kehittäjät työskentelevät edelleen aiempien versioiden kanssa, mikä on ymmärrettävää, koska siirtymisestä alustan versiosta toiseen on paljon ongelmia. Silti monet kehittäjät ovat vasta aloittamassa Uusi sovellukset, joissa on vanhoja Java-versioita. Tähän on hyvin vähän hyviä syitä, koska Java 8 on tuonut joitain tärkeitä parannuksia kielelle.
On paljon uudet ominaisuudet Java 8. Näytän sinulle kourallisen hyödyllisiä ja mielenkiintoisia:
CompletableFuture
: n kanssaTO lambda on koodilohko, johon voidaan viitata ja siirtää toiseen koodinpalaan tulevaa suoritusta varten yksi tai useampi kerta. Esimerkiksi nimettömät toiminnot muilla kielillä ovat lambdas. Kuten funktiot, lambdoille voidaan välittää argumentteja niiden suorittamisen yhteydessä, mikä muuttaa niiden tuloksia. Java 8 esiteltiin lambda-ilmaisut , jotka tarjoavat yksinkertaisen syntaksin lambdojen luomiseen ja käyttämiseen.
Katsotaanpa esimerkki siitä, miten se voi parantaa koodiamme. Tässä meillä on yksinkertainen vertailija, joka vertaa kahta Integer
arvot moduulilla 2:
class BinaryComparator implements Comparator{ @Override public int compare(Integer i1, Integer i2) { return i1 % 2 - i2 % 2; } }
Tämän luokan esiintymää voidaan tulevaisuudessa kutsua koodiksi, jossa tätä vertailua tarvitaan:
... List list = ...; Comparator comparator = new BinaryComparator(); Collections.sort(list, comparator); ...
Uuden lambda-syntaksin avulla voimme tehdä tämän yksinkertaisemmin. Tässä on yksinkertainen lambda-lauseke, joka tekee saman asian kuin compare
menetelmä alkaen BinaryComparator
:
(Integer i1, Integer i2) -> i1 % 2 - i2 % 2;
Rakenteella on monia yhtäläisyyksiä funktioon. Suluissa asetamme luettelon argumenteista. Syntaksi ->
osoittaa, että tämä on lambda. Ja tämän ilmaisun oikeassa reunassa asetamme lambdamme käyttäytymisen.
Nyt voimme parantaa edellistä esimerkkiä:
... List list = ...; Collections.sort(list, (Integer i1, Integer i2) -> i1 % 2 - i2 % 2); ...
Voimme määritellä muuttujan tällä objektilla. Katsotaanpa, miltä se näyttää:
Comparator comparator = (Integer i1, Integer i2) -> i1 % 2 - i2 % 2;
Nyt voimme käyttää tätä toimintoa uudestaan:
... List list1 = ...; List list2 = ...; Collections.sort(list1, comparator); Collections.sort(list2, comparator); ...
Huomaa, että näissä esimerkeissä lambda siirretään sort()
: lle menetelmä samalla tavalla kuin BinaryComparator
hyväksytään aikaisemmassa esimerkissä. Mistä JVM tietää tulkitsemaan lambdan oikein?
Jotta funktiot voivat ottaa lambdas-argumentteja, Java 8 esittelee uuden käsitteen: toiminnallinen käyttöliittymä . Toiminnallinen rajapinta on rajapinta, jolla on vain yksi abstrakti menetelmä. Itse asiassa Java 8 kohtelee lambda-lausekkeita toiminnallisen käyttöliittymän erityisenä toteutuksena. Tämä tarkoittaa, että jotta lambda voidaan vastaanottaa menetelmäargumenttina, kyseisen argumentin ilmoitetun tyypin on oltava vain toiminnallinen käyttöliittymä.
Kun ilmoitamme toimivasta rajapinnasta, voimme lisätä @FunctionalInterface
merkintä näyttää kehittäjille mikä se on:
@FunctionalInterface private interface DTOSender { void send(String accountId, DTO dto); } void sendDTO(BisnessModel object, DTOSender dtoSender) { //some logic for sending... ... dtoSender.send(id, dto); ... }
Nyt voimme kutsua menetelmää sendDTO
, välittämällä eri lambdoja erilaisen käyttäytymisen saavuttamiseksi, kuten tämä:
sendDTO(object, ((accountId, dto) -> sendToAndroid(accountId, dto))); sendDTO(object, ((accountId, dto) -> sendToIos(accountId, dto)));
Lambda-argumenttien avulla voimme muokata funktion tai menetelmän käyttäytymistä. Kuten viimeisestä esimerkistä voidaan nähdä, joskus lambda palvelee vain toisen menetelmän kutsumista (sendToAndroid
tai sendToIos
). Tätä erityistapausta varten Java 8 esittelee kätevän lyhenteen: menetelmäviitteet . Tämä lyhennetty syntakse edustaa lambdaa, joka kutsuu menetelmää ja jonka muoto on objectName::methodName
Tämän avulla voimme tehdä edellisestä esimerkistä vielä suppeamman ja luettavamman:
sendDTO(object, this::sendToAndroid); sendDTO(object, this::sendToIos);
Tässä tapauksessa menetelmät sendToAndroid
ja sendToIos
toteutetaan this
luokassa. Voimme myös viitata toisen objektin tai luokan menetelmiin.
Java 8 tuo uusia kykyjä työskentelemään Collections
kanssa upouuden Stream-sovellusliittymän muodossa. Tämän uuden toiminnon tarjoaa java.util.stream
paketti, ja sen tarkoituksena on mahdollistaa enemmän toimiva lähestymistapa ohjelmointiin kokoelmien kanssa. Kuten näemme, tämä on mahdollista pitkälti juuri keskustelemamme uuden lambda-syntaksin ansiosta.
Stream-sovellusliittymä tarjoaa helpon kokoelmien suodattamisen, laskemisen ja kartoittamisen sekä erilaisia tapoja saada osia ja osajoukkoja niistä pois. Toiminnallisen tyylin syntaksin ansiosta Stream-sovellusliittymä sallii lyhyemmän ja tyylikkäämmän koodin kokoelmien käsittelyyn.
Aloitetaan lyhyellä esimerkillä. Käytämme tätä tietomallia kaikissa esimerkeissä:
class Author { String name; int countOfBooks; } class Book { String name; int year; Author author; }
Kuvitellaan, että meidän on tulostettava kaikki kirjoittajat a books
kokoelman, joka kirjoitti kirjan vuoden 2005 jälkeen. Kuinka tekisimme sen Java 7: ssä?
for (Book book : books) { if (book.author != null && book.year > 2005){ System.out.println(book.author.name); } }
Ja miten tekisimme sen Java 8: ssa?
books.stream() .filter(book -> book.year > 2005) // filter out books published in or before 2005 .map(Book::getAuthor) // get the list of authors for the remaining books .filter(Objects::nonNull) // remove null authors from the list .map(Author::getName) // get the list of names for the remaining authors .forEach(System.out::println); // print the value of each remaining element
Se on vain yksi ilmaisu! Menetelmän kutsuminen stream()
missä tahansa Collection
palauttaa Stream
esine, joka kapseloi kokoelman kaikki elementit. Tätä voidaan manipuloida Stream API: n eri muokkaajilla, kuten filter()
ja map()
. Jokainen muokkaaja palauttaa uuden Stream
objekti muokkauksen tulosten kanssa, joita voidaan edelleen manipuloida. .forEach()
-menetelmän avulla voimme suorittaa jonkin toiminnon tuloksena olevan virran jokaiselle esiintymälle.
Tämä esimerkki osoittaa myös toiminnallisen ohjelmoinnin ja lambda-lausekkeiden läheisen suhteen. Huomaa, että virran kullekin menetelmälle välitetty argumentti on joko mukautettu lambda tai menetelmän viite. Teknisesti kukin muunnin voi vastaanottaa minkä tahansa toiminnallisen rajapinnan, kuten edellisessä osassa on kuvattu.
Stream-sovellusliittymä auttaa kehittäjiä tarkastelemaan Java-kokoelmia uudesta näkökulmasta. Kuvittele nyt, että meidän on saatava Map
käytettävissä olevista kielistä kussakin maassa. Kuinka tämä toteutettaisiin Java 7: ssä?
Map countryToSetOfLanguages = new HashMap(); for (Locale locale : Locale.getAvailableLocales()){ String country = locale.getDisplayCountry(); if (!countryToSetOfLanguages.containsKey(country)){ countryToSetOfLanguages.put(country, new HashSet()); } countryToSetOfLanguages.get(country).add(locale.getDisplayLanguage()); }
Java 8: ssa asiat ovat hieman siistimpiä:
import java.util.stream.*; import static java.util.stream.Collectors.*; ... Map countryToSetOfLanguages = Stream.of(Locale.getAvailableLocales()) .collect(groupingBy(Locale::getDisplayCountry, mapping(Locale::getDisplayLanguage, toSet())));
Menetelmä collect()
avulla voimme kerätä virran tuloksia eri tavoin. Täällä voimme nähdä, että se ensin ryhmitellään maan mukaan ja sitten kartoittaa kunkin ryhmän kielen mukaan. (groupingBy()
ja toSet()
ovat molemmat staattisia menetelmiä Collectors
-luokasta.)
Stream API: lla on paljon muita kykyjä. Täydellinen dokumentaatio löytyy tässä . Suosittelen lukemaan lisää saadaksesi syvemmän käsityksen kaikista tämän paketin tarjoamista tehokkaista työkaluista.
CompletableFuture
: llaJava 7: ssä java.util.concurrent
paketissa on käyttöliittymä Future
, jonka avulla voimme saada jonkin asynkronisen tehtävän tilan tai tuloksen tulevaisuudessa. Tämän toiminnon käyttämiseksi meidän on:
ExecutorService
, joka hallitsee asynkronisten tehtävien suorittamista ja voi luoda Future
esineitä seuraamaan niiden edistymistä.Runnable
tehtävä.ExecutorService
, joka antaa Future
tilan tai tulosten käyttöoikeuden antaminen.Asynkronisen tehtävän tulosten hyödyntämiseksi on tarpeen seurata sen etenemistä ulkopuolelta Future
käyttöliittymän, ja kun se on valmis, hae tulokset nimenomaisesti ja tee niiden kanssa lisätoimia. Tämän toteuttaminen voi olla melko monimutkaista ilman virheitä, varsinkin sovelluksissa, joissa on paljon samanaikaisia tehtäviä.
Java 8: ssa kuitenkin Future
käsite viedään pidemmälle CompletableFuture
käyttöliittymä, joka mahdollistaa asynkronisten tehtävien ketjujen luomisen ja suorittamisen. Se on tehokas mekanismi asynkronisten sovellusten luomiseen Java 8: een, koska sen avulla voimme käsitellä jokaisen tehtävän tulokset automaattisesti suorituksen jälkeen.
Katsotaanpa esimerkki:
import java.util.concurrent.CompletableFuture; ... CompletableFuture voidCompletableFuture = CompletableFuture.supplyAsync(() -> blockingReadPage()) .thenApply(this::getLinks) .thenAccept(System.out::println);
Menetelmä CompletableFuture.supplyAsync
luo uuden asynkronisen tehtävän oletusarvoon Executor
(tyypillisesti ForkJoinPool
). Kun tehtävä on valmis, sen tulokset toimitetaan automaattisesti argumentteina funktiolle this::getLinks
, joka suoritetaan myös uudessa asynkronisessa tehtävässä. Lopuksi tämän toisen vaiheen tulokset tulostetaan automaattisesti kansioon System.out
thenApply()
ja thenAccept()
ovat vain kaksi monista hyödyllisiä menetelmiä käytettävissä, jotta voit luoda samanaikaisia tehtäviä manuaalisesti käyttämättä Executors
.
CompletableFuture
helpottaa monimutkaisten asynkronisten operaatioiden sekvensoinnin hallintaa. Oletetaan, että meidän on luotava monivaiheinen matemaattinen operaatio, jossa on kolme tehtävää. Tehtävä 1 ja tehtävä 2 käytä erilaisia algoritmeja saadaksesi tuloksen ensimmäiselle vaiheelle, ja tiedämme, että vain yksi niistä toimii, kun toinen epäonnistuu. Kumpi toimii, riippuu kuitenkin syötetiedoista, joita emme tiedä etukäteen. Näiden tehtävien tulos on laskettava yhteen tuloksen kanssa tehtävä 3 . Siksi meidän on löydettävä jommankumman tulos tehtävä 1 tai tehtävä 2 , ja tulos tehtävä 3 . Tämän saavuttamiseksi voimme kirjoittaa jotain tällaista:
import static java.util.concurrent.CompletableFuture.*; ... Supplier task1 = (...) -> { ... // some complex calculation return 1; // example result }; Supplier task2 = (...) -> { ... // some complex calculation throw new RuntimeException(); // example exception }; Supplier task3 = (...) -> { ... // some complex calculation return 3; // example result }; supplyAsync(task1) // run task1 .applyToEither( // use whichever result is ready first, result of task1 or supplyAsync(task2), // result of task2 (Integer i) -> i) // return result as-is .thenCombine( // combine result supplyAsync(task3), // with result of task3 Integer::sum) // using summation .thenAccept(System.out::println); // print final result after execution
Jos tutkimme, miten Java 8 hoitaa tämän, näemme, että kaikki kolme tehtävää suoritetaan samanaikaisesti, asynkronisesti. Huolimatta tehtävä 2 jos epäonnistuu lukuun ottamatta, lopputulos lasketaan ja tulostetaan onnistuneesti.
CompletableFuture
tekee paljon helpommaksi asynkronisten tehtävien luomisen useilla vaiheilla ja antaa meille helpon käyttöliittymän, jonka avulla voimme määritellä tarkalleen, mitä toimia tulisi suorittaa jokaisen vaiheen lopussa.
Kuten Java's totesi oma pääsy :
Ennen Java SE 8 --julkaisua Java tarjosi Java - päivämäärä - ja aikamekanismin
java.util.Date
,java.util.Calendar
jajava.util.TimeZone
luokat sekä niiden alaluokat, kutenjava.util.GregorianCalendar
Näillä luokilla oli useita haittoja, mukaan lukien
- Kalenteri-luokka ei ollut tyypiltään turvallinen.
- Koska luokat olivat muutettavissa, niitä ei voitu käyttää monisäikeisissä sovelluksissa.
- Virheet sovelluskoodissa olivat yleisiä kuukausien epätavallisen numeroinnin ja tyyppiturvallisuuden puutteen vuoksi. '
Java 8 ratkaisee viimein nämä pitkäaikaiset ongelmat uudella java.time
paketti, joka sisältää kursseja päivämäärän ja kellonajan käsittelemiseksi. Kaikki ne ovat muuttumattomia ja niillä on samanlaiset sovellusliittymät kuin suosittu kehys Joda-aika , jota melkein kaikki Java-kehittäjät käyttävät sovelluksissa alkuperäisen Date
, Calendar
ja TimeZone
.
Tässä on joitain tämän paketin hyödyllisiä luokkia:
Clock
- Kello, joka kertoo nykyisen ajan, mukaan lukien nykyisen hetken, päivämäärän ja kellonajan aikavyöhykkeellä.Duration
ja Period
- Paljon aikaa. Duration
käyttää aikaperusteisia arvoja, kuten '76,8 sekuntia ja Period
, päivämääräperusteista, kuten' 4 vuotta, 6 kuukautta ja 12 päivää '.Instant
- Hetkellinen ajankohta, eri muodoissa.LocalDate
, LocalDateTime
, LocalTime
, Year
, YearMonth
- Päivämäärä, kellonaika, vuosi, kuukausi tai jokin näiden yhdistelmä ilman aikavyöhykettä ISO-8601-kalenterijärjestelmässä.OffsetDateTime
, OffsetTime
- Päivämäärä ja aika, jonka siirtymä on UTC / Greenwich ISO-8601-kalenterijärjestelmässä, kuten '2015-08-29T14: 15: 30 + 01: 00'.ZonedDateTime
- Päivämäärä-aika ja siihen liittyvä aikavyöhyke ISO-8601-kalenterijärjestelmässä, kuten '1986-08-29T10: 15: 30 + 01: 00 Eurooppa / Pariisi'.
Joskus meidän on löydettävä suhteellinen päivämäärä, kuten 'kuukauden ensimmäinen tiistai'. Näissä tapauksissa java.time
tarjoaa erityisen luokan TemporalAdjuster
. TemporalAdjuster
luokka sisältää vakiosarjan säätimiä, jotka ovat saatavana staattisina menetelminä. Näiden avulla voimme:
Tässä on lyhyt esimerkki kuukauden ensimmäisen tiistain hankkimisesta:
LocalDate getFirstTuesday(int year, int month) { return LocalDate.of(year, month, 1) .with(TemporalAdjusters.nextOrSame(DayOfWeek.TUESDAY)); }
Käytätkö edelleen Java 7: ää? Hanki ohjelma! # Java8 Tweet Kuten näemme, Java 8 on Java-alustan aikakausijulkaisu. Kielimuutoksia on paljon, etenkin lambdojen käyttöönoton myötä, mikä merkitsee siirtämistä toimivampien ohjelmointikykyjen tuomiseen Java-ohjelmaan. Stream-sovellusliittymä on hyvä esimerkki siitä, kuinka lambdas voi muuttaa tapaa, jolla työskentelemme tavallisten Java-työkalujen kanssa, joihin olemme jo tottuneet.
Java 8 tuo myös uusia ominaisuuksia työskentelyyn asynkroninen ohjelmointi ja kaivatun päivityksen ja ajan työkalujen uudistaminen.
Yhdessä nämä muutokset ovat iso askel eteenpäin Java-kielelle Java-kehitys mielenkiintoisempi ja tehokkaampi.