JavaRush /Java Blogu /Random-AZ /Java 8 bələdçi. 1 hissə.
ramhead
Səviyyə

Java 8 bələdçi. 1 hissə.

Qrupda dərc edilmişdir

"Java hələ də yaşayır - və insanlar bunu anlamağa başlayır."

Java 8-ə girişimə xoş gəlmisiniz. Bu təlimat sizə dilin bütün yeni xüsusiyyətlərini addım-addım aparacaq. Qısa, sadə kod nümunələri vasitəsilə siz standart interfeys metodlarından , lambda ifadələrindən , istinad metodlarındantəkrarlanan annotasiyalardan necə istifadə edəcəyinizi öyrənəcəksiniz . Məqalənin sonunda siz axınlar, funksiya interfeysləri, assosiasiya genişlənmələri və yeni Date API kimi API-lərə edilən ən son dəyişikliklərlə tanış olacaqsınız. Darıxdırıcı mətn divarları yoxdur - sadəcə şərh edilmiş kod parçalarından ibarət bir dəstə. Zövq alın!

İnterfeyslər üçün standart üsullar

Java 8 bizə standart açar sözdən istifadə etməklə interfeysdə həyata keçirilən qeyri-mücərrəd metodları əlavə etməyə imkan verir . Bu xüsusiyyət genişləndirmə üsulları kimi də tanınır . Budur bizim ilk nümunəmiz: Abstrakt hesablama interface Formula { double calculate(int a); default double sqrt(int a) { return Math.sqrt(a); } } metoduna əlavə olaraq Formula interfeysi sqrt standart metodunu da müəyyən edir . Formula interfeysini həyata keçirən siniflər yalnız mücərrəd hesablama metodunu tətbiq edir . Standart sqrt metodu qutudan dərhal istifadə edilə bilər. Formula obyekti anonim obyekt kimi həyata keçirilir. Kod olduqca təsir edicidir: sadəcə olaraq sqrt (a * 100) hesablamaq üçün 6 sətir kod . Növbəti bölmədə görəcəyimiz kimi, Java 8-də tək metodlu obyektlərin həyata keçirilməsi üçün daha cəlbedici bir yol var. 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 ifadələri

Java-nın ilkin versiyalarında sətirlər massivinin necə çeşidlənməsinin sadə nümunəsi ilə başlayaq: Collections.sort statistik köməkçi metodu verilmiş siyahının elementlərini çeşidləmək üçün siyahı və Comparator götürür . Tez-tez baş verən şey, anonim müqayisələr yaratmaq və onları çeşidləmə üsullarına ötürməkdir. Hər zaman anonim obyektlər yaratmaq əvəzinə, Java 8 sizə daha az sintaksisdən, lambda ifadələrindən istifadə etmək imkanı verir : Gördüyünüz kimi, kod daha qısa və oxunması daha asandır. Amma burada daha da qısalır: Bir sətirli üsul üçün siz {} əyri mötərizələrdən və return açar sözündən xilas ola bilərsiniz . Amma kodun daha da qısaldığı yer budur: Java tərtibçisi parametrlərin növlərini bilir, ona görə də siz onları kənarda qoya bilərsiniz. İndi gəlin lambda ifadələrinin real həyatda necə istifadə oluna biləcəyinə daha dərindən baxaq. 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));

Funksional interfeyslər

Lambda ifadələri Java tipli sistemə necə uyğun gəlir? Hər bir lambda interfeys tərəfindən müəyyən edilmiş verilmiş tipə uyğundur. Və sözdə funksional interfeys dəqiq bir elan edilmiş mücərrəd metodu ehtiva etməlidir. Verilmiş növün hər bir lambda ifadəsi bu mücərrəd metoda uyğun olacaq.Defolt metodlar mücərrəd üsullar olmadığı üçün siz funksional interfeysinizə defolt metodlar əlavə edə bilərsiniz. İnterfeysdə yalnız bir mücərrəd metod olması şərti ilə biz ixtiyari interfeysdən lambda ifadəsi kimi istifadə edə bilərik. İnterfeysinizin bu şərtlərə cavab verməsini təmin etmək üçün @FunctionalInterface annotasiyasını əlavə etməlisiniz . Kompilyator bu annotasiya ilə məlumatlandırılacaq ki, interfeys yalnız bir metoddan ibarət olmalıdır və bu interfeysdə ikinci mücərrəd metodla qarşılaşarsa, xəta atacaq. Nümunə: Unutmayın ki, @FunctionalInterface annotasiyası elan edilməsə belə, bu kod da etibarlı olacaq. @FunctionalInterface interface Converter { T convert(F from); } Converter converter = (from) -> Integer.valueOf(from); Integer converted = converter.convert("123"); System.out.println(converted); // 123

Metodlara və konstruktorlara istinadlar

Yuxarıdakı nümunə statistik metod istinadından istifadə etməklə daha da sadələşdirilə bilər: Java 8 sizə :: açar söz simvollarından istifadə edərək metodlara və konstruktorlara istinadlar ötürməyə imkan verir . Yuxarıdakı nümunə statistik metodların necə istifadə oluna biləcəyini göstərir. Lakin biz obyektlər üzrə metodlara da istinad edə bilərik: Gəlin :: istifadəsinin konstruktorlar üçün necə işlədiyinə nəzər salaq . Əvvəlcə müxtəlif konstruktorlarla nümunə müəyyən edək: Sonra yeni şəxs obyektləri yaratmaq üçün PersonFactory zavod interfeysini təyin edirik : 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); } Zavodu əl ilə həyata keçirmək əvəzinə, biz konstruktor istinadından istifadə edərək hər şeyi birləşdiririk: Biz Person::new vasitəsilə Person sinfinin konstruktoruna istinad yaradırıq . Java kompilyatoru konstruktorların imzasını PersonFactory.create metodunun imzası ilə müqayisə edərək avtomatik olaraq müvafiq konstruktoru çağıracaq . PersonFactory personFactory = Person::new; Person person = personFactory.create("Peter", "Parker");

Lambda bölgəsi

Lambda ifadələrindən xarici əhatə dairəsi dəyişənlərinə girişin təşkili anonim obyektdən daxil olmağa bənzəyir. Siz yerli əhatə dairəsindən yekun dəyişənlərə , həmçinin nümunə sahələrinə və ümumi dəyişənlərə daxil ola bilərsiniz.
Yerli Dəyişənlərə Giriş
Biz yerli dəyişəni lambda ifadəsinin əhatə dairəsindən son dəyişdirici ilə oxuya bilərik: Lakin anonim obyektlərdən fərqli olaraq, dəyişənlərin lambda ifadəsindən əlçatan olmaq üçün yekun elan edilməsinə ehtiyac yoxdur . Bu kod da düzgündür: Bununla belə, num dəyişəni dəyişməz qalmalıdır, yəni. kodun tərtibi üçün gizli yekun olmalıdır . Aşağıdakı kod tərtib edilməyəcək: Lambda ifadəsi daxilində num-a dəyişikliklərə də icazə verilmir. 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;
Nümunə Sahələrinə və Statistik Dəyişənlərə Giriş
Yerli dəyişənlərdən fərqli olaraq, biz lambda ifadələri daxilində misal sahələrini və statistik dəyişənləri oxuya və dəyişdirə bilərik. Biz bu davranışı anonim obyektlərdən bilirik. 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); }; } }
İnterfeyslərin standart üsullarına giriş
Birinci bölmədəki düstur nümunəsi ilə nümunəni xatırlayırsınız ? Formula interfeysi anonim obyektlər də daxil olmaqla, formulanın hər bir nümunəsindən əldə edilə bilən standart sqrt metodunu müəyyən edir . Bu lambda ifadələri ilə işləmir. Defolt metodlara lambda ifadələri daxilində daxil olmaq mümkün deyil. Aşağıdakı kod tərtib edilmir: Formula formula = (a) -> sqrt( a * 100);

Daxili funksional interfeyslər

JDK 1.8 API çoxlu daxili funksional interfeysləri ehtiva edir. Onlardan bəziləri Java-nın əvvəlki versiyalarından yaxşı məlumdur. Məsələn, Comparator və ya Runnable . Bu interfeyslər @FunctionalInterface annotasiyasından istifadə edərək lambda dəstəyini əhatə edəcək şəkildə genişləndirilir . Lakin Java 8 API həm də həyatınızı asanlaşdıracaq yeni funksional interfeyslərlə doludur. Bu interfeyslərdən bəziləri Google-un Guava kitabxanasından yaxşı məlumdur . Bu kitabxana ilə tanış olsanız belə, bəzi faydalı genişləndirmə üsulları ilə bu interfeyslərin necə genişləndirilməsinə daha yaxından nəzər salmalısınız.
Predikatlar
Predikatlar bir arqumentli Boolean funksiyalarıdır. İnterfeys predikatlardan istifadə edərək mürəkkəb məntiqi ifadələr yaratmaq (və ya, inkar etmək) üçün müxtəlif standart üsulları ehtiva edir 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
Funksiyalar bir arqument götürür və nəticə verir. Bir neçə funksiyanı bir zəncirdə birləşdirmək üçün standart metodlardan istifadə edilə bilər (tərtib et və sonra). Function toInteger = Integer::valueOf; Function backToString = toInteger.andThen(String::valueOf); backToString.apply("123"); // "123"
Təchizatçılar
Təchizatçılar bu və ya digər növ nəticəni (nümunəni) qaytarırlar. Funksiyalardan fərqli olaraq, provayderlər arqumentlər qəbul etmirlər. Supplier personSupplier = Person::new; personSupplier.get(); // new Person
İstehlakçılar
İstehlakçılar interfeys üsullarını tək arqumentlə təmsil edirlər. Consumer greeter = (p) -> System.out.println("Hello, " + p.firstName); greeter.accept(new Person("Luke", "Skywalker"));
Müqayisəçilər
Müqayisəçilər bizə Java-nın əvvəlki versiyalarından məlumdur. Java 8 interfeyslərə müxtəlif standart metodlar əlavə etməyə imkan verir. 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
Seçimlər
Opsiyonel interfeys funksional deyil, lakin NullPointerException-ın qarşısını almaq üçün əla köməkçi proqramdır . Bu, növbəti bölmə üçün vacib məqamdır, ona görə də bu interfeysin necə işlədiyinə qısaca nəzər salaq. Könüllü interfeys null və ya qeyri-null ola bilən dəyərlər üçün sadə konteynerdir . Təsəvvür edin ki, metod bir dəyər və ya heç nə qaytara bilər. Java 8-də null qaytarmaq əvəzinə , siz Könüllü nümunə qaytarırsınız . 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

Axın

java.util.Stream bir və ya bir neçə əməliyyatın yerinə yetirildiyi elementlər ardıcıllığıdır. Hər bir Stream əməliyyatı ya aralıq, ya da terminaldır. Terminal əməliyyatları müəyyən bir növün nəticəsini qaytarır, aralıq əməliyyatlar isə axın obyektinin özünü qaytarır və metod çağırışları zənciri yaratmağa imkan verir. Stream, siyahılar və dəstlər üçün java.util.Collection kimi interfeysdir (xəritələr dəstəklənmir).Hər bir Stream əməliyyatı ardıcıl və ya paralel olaraq icra edilə bilər. Gəlin axının necə işlədiyinə nəzər salaq. Birincisi, biz sətirlərin siyahısı şəklində nümunə kodu yaradacağıq: Java 8-də kolleksiyalar təkmilləşdirilmişdir ki, siz Collection.stream() və ya Collection.parallelStream() zəng etməklə axınlar yarada bilərsiniz . Növbəti bölmədə ən vacib, sadə axın əməliyyatları izah ediləcək. 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 axının bütün elementlərini süzgəcdən keçirmək üçün predikatları qəbul edir. Bu əməliyyat aralıqdır, nəticədə (süzgəcdən keçirilmiş) nəticə üzrə digər axın əməliyyatlarını (məsələn, forEach) çağırmağa imkan verir. ForEach artıq süzülmüş axının hər bir elementində yerinə yetiriləcək əməliyyatı qəbul edir. ForEach bir terminal əməliyyatıdır. Bundan əlavə, digər əməliyyatları çağırmaq mümkün deyil. stringCollection .stream() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); // "aaa2", "aaa1"
Çeşidləndi
Sorted axının çeşidlənmiş təsvirini qaytaran ara əməliyyatdır. Müqayisəçinizi təyin etməyincə elementlər düzgün qaydada sıralanır . stringCollection .stream() .sorted() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); // "aaa1", "aaa2" Nəzərə alın ki, sorted kolleksiyanın özünə təsir etmədən axının çeşidlənmiş təsvirini yaradır. stringCollection elementlərinin sırası toxunulmaz olaraq qalır: System.out.println(stringCollection); // ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
Xəritə
Aralıq xəritə əməliyyatı nəticədə yaranan funksiyadan istifadə edərək hər bir elementi başqa obyektə çevirir. Aşağıdakı nümunə hər bir sətri böyük hərf sətrinə çevirir. Lakin siz hər bir obyekti fərqli bir növə çevirmək üçün xəritədən də istifadə edə bilərsiniz. Yaranan axın obyektlərinin növü xəritəyə ötürdüyünüz funksiyanın növündən asılıdır. stringCollection .stream() .map(String::toUpperCase) .sorted((a, b) -> b.compareTo(a)) .forEach(System.out::println); // "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
Qarşılaşma
Axın münasibətində müəyyən bir predikatın həqiqətini yoxlamaq üçün müxtəlif uyğunluq əməliyyatlarından istifadə edilə bilər. Bütün uyğunluq əməliyyatları terminaldır və Boolean nəticəsini qaytarır. 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
saymaq
Count axın elementlərinin sayını long olaraq qaytaran terminal əməliyyatıdır . long startsWithB = stringCollection .stream() .filter((s) -> s.startsWith("b")) .count(); System.out.println(startsWithB); // 3
Azaltmaq
Bu, ötürülən funksiyadan istifadə edərək axın elementlərini qısaldan terminal əməliyyatıdır. Nəticə qısaldılmış dəyəri ehtiva edən Könüllü olacaq. Optional reduced = stringCollection .stream() .sorted() .reduce((s1, s2) -> s1 + "#" + s2); reduced.ifPresent(System.out::println); // "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"

Paralel axınlar

Yuxarıda qeyd edildiyi kimi, axınlar ardıcıl və ya paralel ola bilər. Ardıcıl axın əməliyyatları serial ipdə, paralel axın əməliyyatları isə çoxlu paralel yivlərdə yerinə yetirilir. Aşağıdakı nümunə paralel axın istifadə edərək performansın necə asanlıqla artırılmasını nümayiş etdirir. Əvvəlcə unikal elementlərin böyük siyahısını yaradaq: İndi bu kolleksiyanın axınının çeşidlənməsinə sərf olunan vaxtı müəyyən edəcəyik. int max = 1000000; List values = new ArrayList<>(max); for (int i = 0; i < max; i++) { UUID uuid = UUID.randomUUID(); values.add(uuid.toString()); }
Serial axını
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
Paralel axın
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 Gördüyünüz kimi, hər iki fraqment demək olar ki, eynidir, lakin paralel çeşidləmə 50% daha sürətlidir. Sizə lazım olan tək şey stream() funksiyasını parallelStream()- ə dəyişməkdir .

Xəritə

Artıq qeyd edildiyi kimi, xəritələr axınları dəstəkləmir. Bunun əvəzinə xəritə ümumi problemlərin həlli üçün yeni və faydalı üsulları dəstəkləməyə başladı. Yuxarıdakı kod intuitiv olmalıdır: putIfAbsent bizi əlavə null yoxlamaları yazmağa qarşı xəbərdar edir. forEach xəritə dəyərinin hər biri üçün icra etmək funksiyasını qəbul edir. Bu nümunə funksiyalardan istifadə edərək xəritə dəyərlərində əməliyyatların necə yerinə yetirildiyini göstərir: Sonra, verilmiş açar üçün girişi yalnız verilmiş dəyərə uyğunlaşdığı təqdirdə necə silməyi öyrənəcəyik: Başqa bir yaxşı üsul: Xəritə daxiletmələrini birləşdirmək olduqca asandır: Birləşdirmə verilmiş açar üçün heç bir giriş olmadıqda ya açarı/dəyəri xəritəyə daxil edəcək, ya da mövcud girişin dəyərini dəyişəcək birləşmə funksiyası çağırılacaq. 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
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION