JavaRush /Java blogi /Random-UZ /Java 8 uchun qo'llanma. 1 qism.
ramhead
Daraja

Java 8 uchun qo'llanma. 1 qism.

Guruhda nashr etilgan

"Java hali ham tirik - va odamlar buni tushunishni boshladilar."

Java 8 bilan tanishuvimga xush kelibsiz. Ushbu qo'llanma sizni tilning barcha yangi funksiyalari bo'yicha bosqichma-bosqich yo'lga qo'yadi. Qisqa, oddiy kod misollari orqali siz interfeysning standart usullaridan , lambda ifodalaridan , mos yozuvlar usullaridan va takrorlanadigan izohlardan qanday foydalanishni o'rganasiz . Maqolaning oxiriga kelib, siz oqimlar, funksiya interfeyslari, assotsiatsiya kengaytmalari va yangi Date API kabi API lardagi so‘nggi o‘zgarishlar bilan tanishasiz. Zerikarli matn devorlari yo'q - shunchaki sharhlangan kod parchalari. Rohatlaning!

Interfeyslar uchun standart usullar

Java 8 standart kalit so'zdan foydalanish orqali interfeysda amalga oshirilgan mavhum bo'lmagan usullarni qo'shish imkonini beradi . Bu xususiyat kengaytma usullari sifatida ham tanilgan . Mana bizning birinchi misolimiz: Hisoblash interface Formula { double calculate(int a); default double sqrt(int a) { return Math.sqrt(a); } } mavhum usulidan tashqari , Formula interfeysi standart sqrt usulini ham belgilaydi . Formula interfeysini amalga oshiradigan sinflar faqat mavhum hisoblash usulini qo'llaydi . Standart sqrt usuli to'g'ridan-to'g'ri qutidan tashqarida ishlatilishi mumkin. Formula ob'ekti anonim ob'ekt sifatida amalga oshiriladi. Kod juda ta'sirli: sqrt (a * 100) ni hisoblash uchun 6 qator kod . Keyingi bo'limda ko'rib chiqamiz, Java 8 da yagona usul ob'ektlarini amalga oshirishning yanada jozibali usuli mavjud. Formula formula = new Formula() { @Override public double calculate(int a) { return sqrt(a * 100); } }; formula.calculate(100); // 100.0 formula.sqrt(16); // 4.0

Lambda ifodalari

Java ning dastlabki versiyalarida qatorlar massivini qanday saralashning oddiy misolidan boshlaylik: Collections.sort statistik yordamchi usuli berilgan ro'yxat elementlarini saralash uchun ro'yxat va Comparatorni oladi . Tez-tez sodir bo'ladigan narsa shundaki, siz anonim taqqoslashlarni yaratasiz va ularni saralash usullariga o'tkazasiz. Har doim anonim ob'ektlarni yaratish o'rniga, Java 8 sizga kamroq sintaksis, lambda ifodalarini ishlatish imkoniyatini beradi : Ko'rib turganingizdek, kod ancha qisqaroq va o'qish osonroq. Ammo bu erda u yanada qisqaroq bo'ladi: bir qatorli usul uchun siz {} jingalak qavslar va return kalit so'zidan xalos bo'lishingiz mumkin . Ammo bu erda kod yanada qisqaroq bo'ladi: Java kompilyatori parametr turlaridan xabardor, shuning uchun siz ularni ham qoldirishingiz mumkin. Endi keling, lambda iboralarini real hayotda qanday ishlatish mumkinligini chuqurroq ko'rib chiqaylik. 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));

Funktsional interfeyslar

Lambda ifodalari Java tipidagi tizimga qanday mos keladi? Har bir lambda interfeys tomonidan belgilangan ma'lum bir turga mos keladi. Va funktsional interfeys deb ataladigan narsa aniq bir e'lon qilingan mavhum usulni o'z ichiga olishi kerak. Berilgan turdagi har bir lambda ifodasi ushbu mavhum usulga mos keladi.Standart usullar mavhum usullar emasligi sababli, siz o'zingizning funktsional interfeysingizga standart usullarni qo'shishingiz mumkin. Biz lambda ifodasi sifatida o'zboshimchalik bilan interfeysdan foydalanishimiz mumkin, agar interfeys faqat bitta mavhum usulni o'z ichiga oladi. Sizning interfeysingiz ushbu shartlarga javob berishini ta'minlash uchun @FunctionalInterface izohini qo'shishingiz kerak . Kompilyatorga ushbu izoh orqali interfeys faqat bitta usulni o'z ichiga olishi kerakligi haqida xabar beradi va agar u ushbu interfeysda ikkinchi mavhum usulga duch kelsa, u xatoga yo'l qo'yadi. Misol: Shuni yodda tutingki, agar @FunctionalInterface izohi e'lon qilinmagan bo'lsa ham, ushbu kod amal qiladi . @FunctionalInterface interface Converter { T convert(F from); } Converter converter = (from) -> Integer.valueOf(from); Integer converted = converter.convert("123"); System.out.println(converted); // 123

Usullar va konstruktorlarga havolalar

Yuqoridagi misolni statistik usul havolasi yordamida yanada soddalashtirish mumkin: Java 8 :: kalit so'z belgilaridan foydalangan holda usullar va konstruktorlarga havolalarni uzatish imkonini beradi . Yuqoridagi misol statistik usullardan qanday foydalanish mumkinligini ko'rsatadi. Lekin biz ob'ektlardagi usullarga ham murojaat qilishimiz mumkin: Keling, konstruktorlar uchun :: dan qanday foydalanishini ko'rib chiqaylik . Birinchidan, turli konstruktorlar bilan misolni aniqlaymiz: Keyin, yangi shaxs ob'ektlarini yaratish uchun PersonFactory zavod interfeysini aniqlaymiz : 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); } Zavodni qo'lda amalga oshirish o'rniga, biz konstruktor ma'lumotnomasi yordamida hamma narsani bir-biriga bog'laymiz: Biz Person::new orqali Person sinfining konstruktoriga havola yaratamiz . Java kompilyatori konstruktorlar imzosini PersonFactory.create usuli imzosi bilan solishtirib, avtomatik ravishda tegishli konstruktorni chaqiradi . PersonFactory personFactory = Person::new; Person person = personFactory.create("Peter", "Parker");

Lambda hududi

Lambda ifodalaridan tashqi doiradagi o'zgaruvchilarga kirishni tashkil qilish anonim ob'ektdan kirishga o'xshaydi. Yakuniy o'zgaruvchilarga mahalliy miqyosdan, shuningdek, misol maydonlari va jamlangan o'zgaruvchilardan kirishingiz mumkin.
Mahalliy o'zgaruvchilarga kirish
Biz mahalliy oʻzgaruvchini lambda ifodasi doirasidan yakuniy modifikator bilan oʻqishimiz mumkin: Lekin anonim obyektlardan farqli oʻlaroq, oʻzgaruvchilar lambda ifodasidan foydalanish mumkin boʻlishi uchun yakuniy deb eʼlon qilinishi shart emas . Bu kod ham to'g'ri: Biroq, num o'zgaruvchisi o'zgarmas bo'lib qolishi kerak, ya'ni. kodni kompilyatsiya qilish uchun aniq yakuniy bo'lishi . Quyidagi kod kompilyatsiya qilinmaydi: lambda ifodasi ichida numga o'zgartirishlar ham ruxsat etilmaydi. 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;
Namuna maydonlari va statistik o'zgaruvchilarga kirish
Mahalliy o'zgaruvchilardan farqli o'laroq, biz lambda ifodalari ichidagi misol maydonlari va statistik o'zgaruvchilarni o'qishimiz va o'zgartirishimiz mumkin. Biz bu xatti-harakatni anonim ob'ektlardan bilamiz. 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); }; } }
Interfeyslarning standart usullariga kirish
Birinchi bo'limdagi formula misoli bilan misolni eslaysizmi ? Formula interfeysi formulaning har bir nusxasidan, shu jumladan anonim ob'ektlardan kirish mumkin bo'lgan standart sqrt usulini belgilaydi . Bu lambda ifodalari bilan ishlamaydi. Standart usullarga lambda ifodalari ichida kirish mumkin emas. Quyidagi kod kompilyatsiya qilinmaydi: Formula formula = (a) -> sqrt( a * 100);

O'rnatilgan funktsional interfeyslar

JDK 1.8 API ko'plab o'rnatilgan funktsional interfeyslarni o'z ichiga oladi. Ulardan ba'zilari Java ning oldingi versiyalaridan yaxshi ma'lum. Masalan, Comparator yoki Runnable . Ushbu interfeyslar @FunctionalInterface izohidan foydalangan holda lambda qo'llab-quvvatlashi uchun kengaytirilgan . Ammo Java 8 API ham hayotingizni osonlashtiradigan yangi funktsional interfeyslarga to'la. Ushbu interfeyslarning ba'zilari Google Guava kutubxonasidan yaxshi ma'lum . Agar siz ushbu kutubxona bilan tanish bo'lsangiz ham, ba'zi foydali kengaytma usullari bilan ushbu interfeyslar qanday kengaytirilganligini diqqat bilan ko'rib chiqishingiz kerak.
Predikatlar
Predikatlar bitta argumentli mantiqiy funktsiyalardir. Interfeys predikatlar yordamida murakkab mantiqiy ifodalarni yaratish (va, yoki, inkor qilish) uchun turli standart usullarni o'z ichiga oladi 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();
Funksiyalar
Funktsiyalar bitta argumentni oladi va natija beradi. Bir nechta funksiyalarni bitta zanjirga birlashtirish uchun standart usullardan foydalanish mumkin (tuzatish va keyin). Function toInteger = Integer::valueOf; Function backToString = toInteger.andThen(String::valueOf); backToString.apply("123"); // "123"
Yetkazib beruvchilar
Yetkazib beruvchilar u yoki bu turdagi natijani (nasolni) qaytaradilar. Funktsiyalardan farqli o'laroq, provayderlar argumentlarni qabul qilmaydi. Supplier personSupplier = Person::new; personSupplier.get(); // new Person
Iste'molchilar
Iste'molchilar interfeys usullarini bitta argument bilan ifodalaydi. Consumer greeter = (p) -> System.out.println("Hello, " + p.firstName); greeter.accept(new Person("Luke", "Skywalker"));
Taqqoslovchilar
Komparatorlar bizga Java ning oldingi versiyalaridan ma'lum. Java 8 interfeyslarga turli xil standart usullarni qo'shish imkonini beradi. 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
Ixtiyoriy
Opsiyonel interfeysi funksional emas, lekin u NullPointerException ning oldini olish uchun ajoyib yordamchi dastur hisoblanadi . Bu keyingi bo'lim uchun muhim nuqta, shuning uchun keling, ushbu interfeys qanday ishlashini tez ko'rib chiqaylik. Ixtiyoriy interfeys - bu null yoki nol bo'lishi mumkin bo'lgan qiymatlar uchun oddiy konteyner. Tasavvur qiling-a, usul qiymatni yoki hech narsani qaytara olmaydi. Java 8 da null ni qaytarish o'rniga siz ixtiyoriy misolni qaytarasiz . 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

Oqim

java.util.Stream - bir yoki bir nechta amallar bajariladigan elementlar ketma-ketligi. Har bir Stream operatsiyasi oraliq yoki terminal hisoblanadi. Terminal operatsiyalari ma'lum turdagi natijani qaytaradi, oraliq operatsiyalar esa oqim ob'ektining o'zini qaytaradi va bu usul chaqiruvlari zanjirini yaratishga imkon beradi. Stream – roʻyxatlar va toʻplamlar uchun java.util.Collection kabi interfeys (xaritalar qoʻllab-quvvatlanmaydi).Har bir Stream operatsiyasi ketma-ket yoki parallel ravishda bajarilishi mumkin. Keling, oqim qanday ishlashini ko'rib chiqaylik. Birinchidan, biz satrlar ro'yxati ko'rinishida namuna kodini yaratamiz: Java 8-dagi to'plamlar yaxshilangan, shuning uchun siz Collection.stream() yoki Collection.parallelStream() ga qo'ng'iroq qilish orqali oqimlarni yaratishingiz mumkin . Keyingi bo'limda eng muhim, oddiy oqim operatsiyalari tushuntiriladi. 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");
Filtr
Filtr oqimning barcha elementlarini filtrlash uchun predikatlarni qabul qiladi. Ushbu operatsiya oraliq bo'lib, natijada olingan (filtrlangan) natija bo'yicha boshqa oqim operatsiyalarini (masalan, forEach) chaqirish imkonini beradi. ForEach allaqachon filtrlangan oqimning har bir elementida bajariladigan operatsiyani qabul qiladi. ForEach - bu terminal operatsiyasi. Bundan tashqari, boshqa operatsiyalarni chaqirish mumkin emas. stringCollection .stream() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); // "aaa2", "aaa1"
Saralangan
Saralangan - oqimning tartiblangan ko'rinishini qaytaradigan oraliq operatsiya. Komparatoringizni ko'rsatmaguningizcha elementlar to'g'ri tartibda saralanadi . stringCollection .stream() .sorted() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); // "aaa1", "aaa2" Yodda tutingki, sorted to'plamning o'ziga ta'sir qilmasdan oqimning tartiblangan ko'rinishini yaratadi. stringCollection elementlarining tartibi o'zgarmagan bo'lib qoladi: System.out.println(stringCollection); // ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
Xarita
Oraliq xarita operatsiyasi natijaviy funksiya yordamida har bir elementni boshqa ob'ektga aylantiradi. Quyidagi misol har bir satrni bosh harfga aylantiradi. Lekin har bir ob'ektni boshqa turga aylantirish uchun xaritadan ham foydalanishingiz mumkin. Olingan oqim ob'ektlarining turi xaritaga o'tadigan funksiya turiga bog'liq. stringCollection .stream() .map(String::toUpperCase) .sorted((a, b) -> b.compareTo(a)) .forEach(System.out::println); // "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
Match
Oqim munosabatida ma'lum bir predikatning haqiqatini tekshirish uchun turli xil moslik operatsiyalaridan foydalanish mumkin. Barcha moslik operatsiyalari terminal va mantiqiy natijani qaytaradi. 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
Hisoblash
Count - bu oqim elementlari sonini long sifatida qaytaradigan terminal operatsiyasi . long startsWithB = stringCollection .stream() .filter((s) -> s.startsWith("b")) .count(); System.out.println(startsWithB); // 3
Kamaytirish
Bu uzatilgan funksiya yordamida oqim elementlarini qisqartiruvchi terminal operatsiyasi. Natijada qisqartirilgan qiymatni o'z ichiga olgan ixtiyoriy bo'ladi. Optional reduced = stringCollection .stream() .sorted() .reduce((s1, s2) -> s1 + "#" + s2); reduced.ifPresent(System.out::println); // "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"

Parallel oqimlar

Yuqorida aytib o'tilganidek, oqimlar ketma-ket yoki parallel bo'lishi mumkin. Ketma-ket oqim operatsiyalari ketma-ket oqimda, parallel oqim operatsiyalari bir nechta parallel oqimlarda amalga oshiriladi. Quyidagi misol parallel oqim yordamida unumdorlikni qanday oson oshirishni ko'rsatadi. Birinchidan, noyob elementlarning katta ro'yxatini yarataylik: Endi biz ushbu to'plam oqimini saralash uchun sarflangan vaqtni aniqlaymiz. int max = 1000000; List values = new ArrayList<>(max); for (int i = 0; i < max; i++) { UUID uuid = UUID.randomUUID(); values.add(uuid.toString()); }
Seriyali oqim
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 oqim
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 Ko'rib turganingizdek, ikkala fragment ham deyarli bir xil, ammo parallel tartiblash 50% tezroq. Sizga kerak bo'lgan yagona narsa stream() ni parallelStream() ga o'zgartirishdir .

Xarita

Yuqorida aytib o'tilganidek, xaritalar oqimlarni qo'llab-quvvatlamaydi. Buning o'rniga, xarita umumiy muammolarni hal qilish uchun yangi va foydali usullarni qo'llab-quvvatlay boshladi. Yuqoridagi kod intuitiv bo'lishi kerak: putIfAbsent bizni qo'shimcha null tekshiruvlar yozishdan ogohlantiradi. forEach har bir xarita qiymatlari uchun bajarish funksiyasini qabul qiladi. Ushbu misol funksiyalar yordamida xarita qiymatlarida qanday operatsiyalar bajarilishini ko'rsatadi: Keyinchalik, berilgan kalit uchun yozuvni faqat ma'lum bir qiymatga mos keladigan bo'lsa, qanday olib tashlashni bilib olamiz: Yana bir yaxshi usul: Xarita yozuvlarini birlashtirish juda oson: Birlashtirish kalit/qiymatni xaritaga kiritadi, agar berilgan kalit uchun hech qanday yozuv bo'lmasa yoki mavjud yozuvning qiymatini o'zgartiradigan birlashtirish funktsiyasi chaqiriladi. 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
Izohlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION