JavaRush /Java Blog /Random-TL /Gabay sa Java 8. 1 bahagi.
ramhead
Antas

Gabay sa Java 8. 1 bahagi.

Nai-publish sa grupo

"Buhay pa ang Java - at nagsisimula na itong maunawaan ng mga tao."

Maligayang pagdating sa aking pagpapakilala sa Java 8. Dadalhin ka ng gabay na ito nang sunud-sunod sa lahat ng mga bagong tampok ng wika. Sa pamamagitan ng maikli, simpleng mga halimbawa ng code, matututunan mo kung paano gumamit ng mga default na paraan ng interface , mga expression ng lambda , mga paraan ng sanggunian , at mga nauulit na anotasyon . Sa pagtatapos ng artikulo, magiging pamilyar ka sa mga pinakabagong pagbabago sa mga API gaya ng mga stream, mga interface ng function, mga extension ng asosasyon, at ang bagong API ng Petsa. Walang pader ng boring na text - isang grupo lang ng mga nagkomento na snippet ng code. Enjoy!

Mga default na pamamaraan para sa mga interface

Binibigyang-daan kami ng Java 8 na magdagdag ng mga di-abstract na pamamaraan na ipinatupad sa interface sa pamamagitan ng paggamit ng default na keyword . Ang tampok na ito ay kilala rin bilang mga paraan ng extension . Narito ang aming unang halimbawa: interface Formula { double calculate(int a); default double sqrt(int a) { return Math.sqrt(a); } } Bilang karagdagan sa abstract na pamamaraan na kalkulahin , ang interface ng Formula ay tumutukoy din ng isang default na paraan sqrt . Ang mga klase na nagpapatupad ng interface ng Formula ay nagpapatupad lamang ng abstract na paraan ng pagkalkula . Ang default na paraan ng sqrt ay maaaring gamitin nang diretso sa labas ng kahon. Ang formula Formula formula = new Formula() { @Override public double calculate(int a) { return sqrt(a * 100); } }; formula.calculate(100); // 100.0 formula.sqrt(16); // 4.0 object ay ipinatupad bilang isang anonymous na bagay. Ang code ay lubos na kahanga-hanga: 6 na linya ng code upang simpleng kalkulahin sqrt(a * 100) . Tulad ng makikita natin sa susunod na seksyon, mayroong isang mas kaakit-akit na paraan upang ipatupad ang mga bagay na nag-iisang pamamaraan sa Java 8.

Mga expression ng Lambda

Magsimula tayo sa isang simpleng halimbawa kung paano mag-uri-uriin ang isang hanay ng mga string sa mga unang bersyon ng Java: Ang pamamaraan ng statistical helper na Collections.sort ay kumukuha ng isang listahan at isang Comparator upang pagbukud-bukurin ang mga elemento ng ibinigay na listahan. Ang madalas na nangyayari ay gumawa ka ng mga hindi kilalang comparator at ipapasa ang mga ito upang pag-uri-uriin ang mga pamamaraan. Sa halip na lumikha ng mga hindi kilalang bagay sa lahat ng oras, binibigyan ka ng Java 8 ng kakayahang gumamit ng mas kaunting syntax, mga lambda expression : Gaya ng nakikita mo, ang code ay mas maikli at mas madaling basahin. Ngunit dito ito ay nagiging mas maikli: Para sa isang linyang paraan, maaari mong alisin ang {} curly braces at ang return keyword . Ngunit narito kung saan ang code ay nagiging mas maikli: ang Java compiler ay may kamalayan sa mga uri ng mga parameter, kaya maaari mo ring iwanan ang mga ito. Ngayon ay sumisid tayo nang mas malalim sa kung paano magagamit ang mga expression ng lambda sa totoong buhay. List names = Arrays.asList("peter", "anna", "mike", "xenia"); Collections.sort(names, new Comparator () { @Override public int compare(String a, String b) { return b.compareTo(a); } }); Collections.sort(names, (String a, String b) -> { return b.compareTo(a); }); Collections.sort(names, (String a, String b) -> b.compareTo(a)); Collections.sort(names, (a, b) -> b.compareTo(a));

Mga Functional na Interface

Paano magkasya ang mga expression ng lambda sa sistema ng uri ng Java? Ang bawat lambda ay tumutugma sa isang ibinigay na uri na tinukoy ng isang interface. At ang tinatawag na functional interface ay dapat maglaman ng eksaktong isang ipinahayag na abstract na pamamaraan. Ang bawat lambda expression ng isang partikular na uri ay tumutugma sa abstract na pamamaraang ito. Dahil ang mga default na pamamaraan ay hindi abstract na pamamaraan, malaya kang magdagdag ng mga default na pamamaraan sa iyong functional na interface. Maaari kaming gumamit ng isang arbitrary na interface bilang isang lambda expression, sa kondisyon na ang interface ay naglalaman lamang ng isang abstract na pamamaraan. Upang matiyak na natutugunan ng iyong interface ang mga kundisyong ito, dapat mong idagdag ang @FunctionalInterface annotation . Ipapaalam sa compiler sa pamamagitan ng anotasyong ito na ang interface ay dapat maglaman lamang ng isang paraan, at kung makatagpo ito ng pangalawang abstract na paraan sa interface na ito, ito ay maglalagay ng error. Halimbawa: Tandaan na ang code na ito ay magiging wasto din kahit na ang @FunctionalInterface annotation ay hindi naideklara. @FunctionalInterface interface Converter { T convert(F from); } Converter converter = (from) -> Integer.valueOf(from); Integer converted = converter.convert("123"); System.out.println(converted); // 123

Mga sanggunian sa mga pamamaraan at constructor

Ang halimbawa sa itaas ay maaaring mas pasimplehin sa pamamagitan ng paggamit ng statistical method reference: Ang Java 8 ay nagbibigay-daan sa iyo na magpasa ng mga reference sa mga method at constructor gamit ang :: keyword symbols . Ang halimbawa sa itaas ay nagpapakita kung paano magagamit ang mga istatistikal na pamamaraan. Ngunit maaari rin tayong sumangguni sa mga pamamaraan sa mga bagay: Tingnan natin kung paano gumagana ang paggamit ng :: para sa mga konstruktor. Una, tukuyin natin ang isang halimbawa na may iba't ibang mga konstruktor: Susunod, tinukoy natin ang interface ng pabrika ng PersonFactory para sa paglikha ng mga bagay ng bagong tao : Converter converter = Integer::valueOf; Integer converted = converter.convert("123"); System.out.println(converted); // 123 class Something { String startsWith(String s) { return String.valueOf(s.charAt(0)); } } Something something = new Something(); Converter converter = something::startsWith; String converted = converter.convert("Java"); System.out.println(converted); // "J" class Person { String firstName; String lastName; Person() {} Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } } interface PersonFactory

{ P create(String firstName, String lastName); } Sa halip na manu-manong ipatupad ang pabrika, pinagsama-sama namin ang lahat gamit ang reference ng constructor: Gumagawa kami ng reference sa constructor ng klase ng Person sa pamamagitan ng Person::new . Awtomatikong tatawagin ng Java compiler ang naaangkop na constructor sa pamamagitan ng paghahambing ng signature ng constructor sa signature ng PersonFactory.create method . PersonFactory personFactory = Person::new; Person person = personFactory.create("Peter", "Parker");

Rehiyon ng Lambda

Ang pag-aayos ng pag-access sa mga variable na panlabas na saklaw mula sa mga expression ng lambda ay katulad ng pag-access mula sa isang hindi kilalang bagay. Maa-access mo ang mga huling variable mula sa lokal na saklaw, pati na rin ang mga field ng instance at pinagsama-samang variable.
Pag-access sa Mga Lokal na Variable
Mababasa natin ang isang lokal na variable na may panghuling modifier mula sa saklaw ng isang lambda expression: Ngunit hindi tulad ng mga anonymous na bagay, ang mga variable ay hindi kailangang ideklarang pinal upang ma-access mula sa isang lambda expression . Tama rin ang code na ito: Gayunpaman, dapat manatiling hindi nababago ang variable ng num , ibig sabihin. maging implicit final para sa code compilation. Ang sumusunod na code ay hindi mag-compile: Ang mga pagbabago sa num sa loob ng lambda expression ay hindi rin pinapayagan. final int num = 1; Converter stringConverter = (from) -> String.valueOf(from + num); stringConverter.convert(2); // 3 int num = 1; Converter stringConverter = (from) -> String.valueOf(from + num); stringConverter.convert(2); // 3 int num = 1; Converter stringConverter = (from) -> String.valueOf(from + num); num = 3;
Pag-access sa Mga Instance Field at Statistical Variable
Hindi tulad ng mga lokal na variable, maaari naming basahin at baguhin ang mga field ng instance at istatistikal na variable sa loob ng mga expression ng lambda. Alam namin ang pag-uugaling ito mula sa mga hindi kilalang bagay. class Lambda4 { static int outerStaticNum; int outerNum; void testScopes() { Converter stringConverter1 = (from) -> { outerNum = 23; return String.valueOf(from); }; Converter stringConverter2 = (from) -> { outerStaticNum = 72; return String.valueOf(from); }; } }
Access sa mga default na paraan ng mga interface
Tandaan ang halimbawa na may halimbawa ng formula mula sa unang seksyon? Tinutukoy ng interface ng Formula ang isang default na paraan ng sqrt na maaaring ma-access mula sa bawat halimbawa ng formula , kabilang ang mga hindi kilalang bagay. Hindi ito gumagana sa mga expression ng lambda. Hindi ma-access ang mga default na pamamaraan sa loob ng mga expression ng lambda. Ang sumusunod na code ay hindi nag-compile: Formula formula = (a) -> sqrt( a * 100);

Mga built-in na functional na interface

Ang JDK 1.8 API ay naglalaman ng maraming built-in na functional na interface. Ang ilan sa mga ito ay kilala mula sa mga nakaraang bersyon ng Java. Halimbawa Comparator o Runnable . Ang mga interface na ito ay pinalawak upang isama ang suporta sa lambda gamit ang @FunctionalInterface annotation . Ngunit ang Java 8 API ay puno rin ng mga bagong functional na interface na magpapadali sa iyong buhay. Ang ilan sa mga interface na ito ay kilala mula sa Google's Guava library . Kahit na pamilyar ka sa library na ito, dapat mong tingnang mabuti kung paano pinalawak ang mga interface na ito, na may ilang kapaki-pakinabang na paraan ng extension.
Predicates
Ang mga predicate ay mga function ng Boolean na may isang argumento. Ang interface ay naglalaman ng iba't ibang mga default na pamamaraan para sa paglikha ng mga kumplikadong lohikal na expression (at, o, negate) gamit ang mga predicates Predicate predicate = (s) -> s.length() > 0; predicate.test("foo"); // true predicate.negate().test("foo"); // false Predicate nonNull = Objects::nonNull; Predicate isNull = Objects::isNull; Predicate isEmpty = String::isEmpty; Predicate isNotEmpty = isEmpty.negate();
Mga pag-andar
Ang mga function ay tumatagal ng isang argumento at nagbubunga ng isang resulta. Maaaring gamitin ang mga default na pamamaraan upang pagsamahin ang ilang mga function sa isang chain (mag-compose, at Pagkatapos). Function toInteger = Integer::valueOf; Function backToString = toInteger.andThen(String::valueOf); backToString.apply("123"); // "123"
Mga supplier
Nagbabalik ang mga supplier ng resulta (halimbawa) ng isang uri o iba pa. Hindi tulad ng mga function, ang mga provider ay hindi kumukuha ng mga argumento. Supplier personSupplier = Person::new; personSupplier.get(); // new Person
Mga mamimili
Kinakatawan ng mga mamimili ang mga pamamaraan ng interface na may iisang argumento. Consumer greeter = (p) -> System.out.println("Hello, " + p.firstName); greeter.accept(new Person("Luke", "Skywalker"));
Mga kumpare
Ang mga comparator ay kilala sa amin mula sa mga nakaraang bersyon ng Java. Pinapayagan ka ng Java 8 na magdagdag ng iba't ibang mga default na pamamaraan sa mga interface. Comparator comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName); Person p1 = new Person("John", "Doe"); Person p2 = new Person("Alice", "Wonderland"); comparator.compare(p1, p2); // > 0 comparator.reversed().compare(p1, p2); // < 0
Opsyonal
Ang Opsyonal na interface ay hindi gumagana, ngunit ito ay isang mahusay na utility para sa pagpigil sa NullPointerException . Ito ay isang mahalagang punto para sa susunod na seksyon, kaya tingnan natin kung paano gumagana ang interface na ito. Ang Opsyonal na interface ay isang simpleng lalagyan para sa mga value na maaaring null o non-null. Isipin na ang isang paraan ay maaaring magbalik ng halaga o wala. Sa Java 8, sa halip na ibalik ang null , magbabalik ka ng Opsyonal na instance . Comparator comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName); Person p1 = new Person("John", "Doe"); Person p2 = new Person("Alice", "Wonderland"); comparator.compare(p1, p2); // > 0 comparator.reversed().compare(p1, p2); // < 0

Stream

Ang java.util.Stream ay isang pagkakasunud-sunod ng mga elemento kung saan ang isa o maraming mga operasyon ay isinasagawa. Ang bawat operasyon ng Stream ay maaaring intermediate o terminal. Ang mga pagpapatakbo ng terminal ay nagbabalik ng resulta ng isang partikular na uri, habang ang mga intermediate na operasyon ay nagbabalik ng stream object mismo, na nagpapahintulot sa isang hanay ng mga tawag sa pamamaraan na malikha. Ang stream ay isang interface, tulad ng java.util.Collection para sa mga listahan at set (hindi suportado ang mga mapa). Ang bawat operasyon ng Stream ay maaaring isagawa nang sunud-sunod o kahanay. Tingnan natin kung paano gumagana ang stream. Una, gagawa kami ng sample na code sa anyo ng isang listahan ng mga string: Ang mga koleksyon sa Java 8 ay pinahusay upang makagawa ka ng mga stream nang simple sa pamamagitan ng pagtawag sa Collection.stream() o Collection.parallelStream() . Ipapaliwanag ng susunod na seksyon ang pinakamahalaga, simpleng mga operasyon ng stream. List stringCollection = new ArrayList<>(); stringCollection.add("ddd2"); stringCollection.add("aaa2"); stringCollection.add("bbb1"); stringCollection.add("aaa1"); stringCollection.add("bbb3"); stringCollection.add("ccc"); stringCollection.add("bbb2"); stringCollection.add("ddd1");
Salain
Tumatanggap ang filter ng mga predicate para i-filter ang lahat ng elemento ng stream. Ang operasyong ito ay intermediate, na nagbibigay-daan sa amin na tumawag sa iba pang mga operasyon ng stream (halimbawa para sa Bawat) sa resultang (na-filter) na resulta. Tumatanggap ang ForEach ng operasyon na isasagawa sa bawat elemento ng na-filter na stream. Ang ForEach ay isang terminal operation. Dagdag pa, imposible ang pagtawag sa iba pang mga operasyon. stringCollection .stream() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); // "aaa2", "aaa1"
Inayos
Ang Sorted ay isang intermediate operation na nagbabalik ng pinagsunod-sunod na representasyon ng stream. Ang mga elemento ay pinagsunod-sunod sa tamang pagkakasunod-sunod maliban kung tinukoy mo ang iyong Comparator . stringCollection .stream() .sorted() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); // "aaa1", "aaa2" Tandaan na ang pinagsunod-sunod ay lumilikha ng pinagsunod-sunod na representasyon ng stream nang hindi naaapektuhan ang mismong koleksyon. Ang pagkakasunud-sunod ng mga elemento ng stringCollection ay nananatiling hindi nagalaw: System.out.println(stringCollection); // ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
Mapa
Ang intermediate na operasyon ng mapa ay nagko-convert sa bawat elemento sa isa pang bagay gamit ang resultang function. Kino-convert ng sumusunod na halimbawa ang bawat string sa isang uppercase na string. Ngunit maaari mo ring gamitin ang mapa upang i-convert ang bawat bagay sa ibang uri. Ang uri ng mga resultang stream object ay depende sa uri ng function na ipapasa mo sa mapa. stringCollection .stream() .map(String::toUpperCase) .sorted((a, b) -> b.compareTo(a)) .forEach(System.out::println); // "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
tugma
Maaaring gamitin ang iba't ibang pagpapatakbo ng pagtutugma upang subukan ang katotohanan ng isang partikular na panaguri sa kaugnayan ng stream. Ang lahat ng pagpapatakbo ng pagtutugma ay terminal at nagbabalik ng Boolean na resulta. boolean anyStartsWithA = stringCollection .stream() .anyMatch((s) -> s.startsWith("a")); System.out.println(anyStartsWithA); // true boolean allStartsWithA = stringCollection .stream() .allMatch((s) -> s.startsWith("a")); System.out.println(allStartsWithA); // false boolean noneStartsWithZ = stringCollection .stream() .noneMatch((s) -> s.startsWith("z")); System.out.println(noneStartsWithZ); // true
Bilangin
Ang count ay isang terminal operation na nagbabalik ng bilang ng mga elemento ng stream bilang isang mahabang . long startsWithB = stringCollection .stream() .filter((s) -> s.startsWith("b")) .count(); System.out.println(startsWithB); // 3
Bawasan
Ito ay isang terminal operation na nagpapaikli sa mga elemento ng stream gamit ang naipasa na function. Ang resulta ay magiging Opsyonal na naglalaman ng pinaikling halaga. Optional reduced = stringCollection .stream() .sorted() .reduce((s1, s2) -> s1 + "#" + s2); reduced.ifPresent(System.out::println); // "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"

Mga Parallel Stream

Tulad ng nabanggit sa itaas, ang mga stream ay maaaring sunud-sunod o parallel. Ang mga sequential stream operations ay ginagawa sa isang serial thread, habang ang parallel stream operations ay ginagawa sa maramihang parallel threads. Ang sumusunod na halimbawa ay nagpapakita kung paano madaling taasan ang pagganap gamit ang isang parallel stream. Una, gumawa tayo ng malaking listahan ng mga natatanging elemento: Ngayon ay tutukuyin natin ang oras na ginugol sa pag-uuri ng stream ng koleksyong ito. int max = 1000000; List values = new ArrayList<>(max); for (int i = 0; i < max; i++) { UUID uuid = UUID.randomUUID(); values.add(uuid.toString()); }
Serial na stream
long t0 = System.nanoTime(); long count = values.stream().sorted().count(); System.out.println(count); long t1 = System.nanoTime(); long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.println(String.format("sequential sort took: %d ms", millis)); // sequential sort took: 899 ms
Parallel stream
long t0 = System.nanoTime(); long count = values.parallelStream().sorted().count(); System.out.println(count); long t1 = System.nanoTime(); long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.println(String.format("parallel sort took: %d ms", millis)); // parallel sort took: 472 ms Tulad ng nakikita mo, ang parehong mga fragment ay halos magkapareho, ngunit ang parallel na pag-uuri ay 50% na mas mabilis. Ang kailangan mo lang ay baguhin ang stream() sa parallelStream() .

Mapa

Gaya ng nabanggit na, hindi sinusuportahan ng mga mapa ang mga stream. Sa halip, nagsimulang suportahan ng mapa ang mga bago at kapaki-pakinabang na pamamaraan para sa paglutas ng mga karaniwang problema. Ang code sa itaas ay dapat na intuitive: ang putIfAbsent ay nagbabala sa amin laban sa pagsusulat ng mga karagdagang null check. forEach tumatanggap ng function na ipapatupad para sa bawat isa sa mga value ng mapa. Ipinapakita ng halimbawang ito kung paano ginaganap ang mga pagpapatakbo sa mga value ng mapa gamit ang mga function: Susunod, matututunan natin kung paano mag-alis ng entry para sa isang ibinigay na key lamang kung ito ay nagmamapa sa isang ibinigay na halaga: Isa pang magandang paraan: Ang pagsasama-sama ng mga entry sa mapa ay medyo madali: Pagsasama-sama ilalagay ang key/value sa mapa , kung walang entry para sa ibinigay na key, o tatawagin ang merge function, na magbabago sa value ng kasalukuyang entry. Map map = new HashMap<>(); for (int i = 0; i < 10; i++) { map.putIfAbsent(i, "val" + i); } map.forEach((id, val) -> System.out.println(val)); map.computeIfPresent(3, (num, val) -> val + num); map.get(3); // val33 map.computeIfPresent(9, (num, val) -> null); map.containsKey(9); // false map.computeIfAbsent(23, num -> "val" + num); map.containsKey(23); // true map.computeIfAbsent(3, num -> "bam"); map.get(3); // val33 map.remove(3, "val3"); map.get(3); // val33 map.remove(3, "val33"); map.get(3); // null map.getOrDefault(42, "not found"); // not found map.merge(9, "val9", (value, newValue) -> value.concat(newValue)); map.get(9); // val9 map.merge(9, "concat", (value, newValue) -> value.concat(newValue)); map.get(9); // val9concat
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION