JavaRush /Java блогу /Random-KY /Java 8 колдонмосу. 1 бөлүк.
ramhead
Деңгээл

Java 8 колдонмосу. 1 бөлүк.

Группада жарыяланган

"Java дагы эле тирүү - жана адамдар аны түшүнө башташты."

Менин Java 8 менен тааныштыруума кош келиңиз. Бул колдонмо сизди тилдин бардык жаңы мүмкүнчүлүктөрүн этап-этабы менен көрсөтөт. Кыска, жөнөкөй code мисалдары аркылуу интерфейстин демейки ыкмаларын , ламбда туюнтмаларын , маалымдама ыкмаларын жана кайталануучу annotationларды кантип колдонууну үйрөнөсүз . Макаланын аягында сиз агымдар, функция интерфейстери, ассоциация кеңейтүүлөрү жана жаңы Date API сыяктуу APIлердин акыркы өзгөртүүлөрү менен тааныш болосуз. Тажатма тексттин дубалдары жок - жөн гана комментарий берилген code үзүндүлөрүнүн бир тобу. Enjoy!

Интерфейстердин демейки ыкмалары

Java 8 демейки ачкыч сөздү колдонуу аркылуу интерфейсте ишке ашырылган абстракттуу эмес ыкмаларды кошууга мүмкүндүк берет . Бул өзгөчөлүк кеңейтүү ыкмалары катары да белгилүү . Бул жерде биздин биринчи мисал: абстракттуу ыкма эсептөөдөн interface Formula { double calculate(int a); default double sqrt(int a) { return Math.sqrt(a); } } тышкары , Формула интерфейси демейки sqrt ыкмасын да аныктайт . Формула интерфейсин ишке ашырган класстар абстракттуу эсептөө ыкмасын гана ишке ашырат . Демейки sqrt ыкмасын түз эле колдонсо болот. Формула an objectи анонимдүү an object катары ишке ашырылат. Код абдан таасирдүү: sqrt(a * 100) эсептөө үчүн 6 сап code . Кийинки бөлүмдө көрө турганыбыздай, Java 8де бирдиктүү метод an objectтерин ишке ашыруунун жагымдуураак жолу бар. 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 туюнтмалары

Келгиле, Javaнын алгачкы versionларында саптардын массивдерин кантип иреттөөнүн жөнөкөй мисалы менен баштайлы: Collections.sort статистикалык жардамчы ыкмасы берилген тизменин элементтерин сорттоо үчүн тизмени жана Салыштыргычты алат . Көбүнчө сиз анонимдүү компараторлорду түзүп, аларды сорттоо ыкмаларына өткөрүп бересиз. Ар дайым анонимдүү an objectтерди түзүүнүн ордуна, Java 8 сизге синтаксисти, ламбда туюнтмаларын азыраак колдонуу мүмкүнчүлүгүн берет : Көрүнүп тургандай, code бир топ кыскараак жана окууга оңой. Бирок бул жерде ал андан да кыскараак болот: бир саптык ыкма үчүн, сиз {} тармал кашаалардан жана return ачкыч сөзүнөн арыла аласыз . Бирок бул жерде code дагы кыскараак болот: Java компилятору параметрлердин түрлөрүн билет, ошондуктан сиз аларды да калтырсаңыз болот. Эми ламбда туюнтмаларын реалдуу жашоодо кантип колдонсо болорун тереңирээк карап көрөлү. 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));

Функционалдык интерфейстер

Ламбда туюнтмалары Java типтеги системасына кантип туура келет? Ар бир ламбда интерфейс тарабынан аныкталган берилген түргө туура келет. Ал эми функционалдык интерфейс деп аталган бир абстракттуу ыкманы камтышы керек. Берилген типтеги ар бир лямбда туюнтмасы бул абстракттуу методго туура келет. Демейки ыкмалар абстракттуу методдор болбогондуктан, сиз функционалдык интерфейсиңизге демейки ыкмаларды кошо аласыз. Интерфейс бир гана абстракттуу ыкманы камтыган шартта, ламбда туюнтмасы катары ыктыярдуу интерфейсти колдоно алабыз. Интерфейсиңиз бул шарттарга жооп бериши үчүн, @FunctionalInterface annotationсын кошушуңуз керек . Бул annotation менен компилятор интерфейсте бир гана методду камтышы керектиги жөнүндө кабарланат жана бул интерфейсте экинчи абстракттуу ыкмага туш болсо, ката кетирет. Мисал: @FunctionalInterface annotationсы жарыяланбаса да, бул code жарактуу болоорун унутпаңыз . @FunctionalInterface interface Converter { T convert(F from); } Converter converter = (from) -> Integer.valueOf(from); Integer converted = converter.convert("123"); System.out.println(converted); // 123

Методдорго жана конструкторлорго шилтемелер

Жогорудагы мисалды статистикалык ыкма шилтемесин колдонуу менен дагы жөнөкөйлөштүрсө болот: Java 8 :: ачкыч сөздөрүнүн символдорун колдонуу менен ыкмаларга жана конструкторлорго шилтемелерди өткөрүүгө мүмкүндүк берет . Жогорудагы мисал статистикалык ыкмаларды кантип колдонсо болорун көрсөтүп турат. Бирок биз an objectтердеги ыкмаларга да кайрылсак болот: Келгиле, конструкторлор үчүн :: кантип колдонуларын карап көрөлү . Биринчиден, ар кандай конструкторлор менен мисалды аныктайлы: Андан кийин, жаңы адам an objectтерин түзүү үчүн PersonFactory фабрика интерфейсин аныктайбыз : 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); } Заводду кол менен ишке ашыруунун ордуна, биз бардыгын конструктор шилтемеси аркылуу бириктиребиз: Person::new аркылуу Person классынын конструкторуна шилтеме түзөбүз . Java компилятору конструкторлордун кол тамгасын PersonFactory.create методунун кол тамгасы менен салыштырып, автоматтык түрдө тиешелүү конструкторду чакырат . PersonFactory personFactory = Person::new; Person person = personFactory.create("Peter", "Parker");

Ламбда аймагы

Ламбда туюнтмаларынан тышкы масштабдагы өзгөрмөлөргө жетүүнү уюштуруу анонимдүү an objectтен кирүүгө окшош. Сиз жергorктүү масштабдагы акыркы өзгөрмөлөргө , ошондой эле мисал талааларына жана агрегаттык өзгөрмөлөргө кире аласыз.
Жергorктүү өзгөрмөлөргө жетүү
Биз лямбда туюнтмасынын чөйрөсүнөн акыркы модификатор менен локалдык өзгөрмөнү окуй алабыз : Бирок анонимдүү an objectтерден айырмаланып, өзгөрмөлөр ламбда туюнтмасынан жеткorктүү болуу үчүн акыркы деп жарыяланышынын кереги жок . Бул code да туура: Бирок, num өзгөрмө өзгөрүлбөс бойдон калууга тийиш, б.а. codeду түзүү үчүн акыркы болуп саналат . Төмөнкү code түзүлбөйт: Ламбда туюнтмасындагы numга өзгөртүүлөр да жол берилбейт. 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;
Мисал талааларына жана статистикалык өзгөрмөлөргө жетүү
Жергorктүү өзгөрмөлөрдөн айырмаланып, биз lambda туюнтмаларынын ичиндеги мисал талааларын жана статистикалык өзгөрмөлөрдү окуп жана өзгөртө алабыз. Биз бул жүрүм-турумду анонимдүү an objectтерден билебиз. 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); }; } }
Интерфейстердин демейки ыкмаларына жетүү
Биринчи бөлүмдөгү формула инстанциясы менен мисал эсиңиздеби ? Формула интерфейси формуланын ар бир инстанциясынан, анын ичинде анонимдүү an objectтерден кирүүгө боло турган демейки sqrt ыкмасын аныктайт . Бул лямбда туюнтмалары менен иштебейт. Демейки ыкмаларга ламбда туюнтмаларынын ичинде кирүүгө болбойт. Төмөнкү code түзүлбөйт: Formula formula = (a) -> sqrt( a * 100);

Камтылган функционалдык интерфейстер

JDK 1.8 API көптөгөн камтылган функционалдык интерфейстерди камтыйт. Алардын айрымдары Javaнын мурунку versionларынан белгилүү. Мисалы, Comparator же Runnable . Бул интерфейстер @FunctionalInterface annotationсын колдонуу менен ламбда колдоосун камтуу үчүн кеңейтилген . Бирок Java 8 API жашооңузду жеңилдете турган жаңы функционалдык интерфейстерге толгон. Бул интерфейстердин айрымдары Google'дун Guava китепканасынан белгилүү . Бул китепкана менен тааныш болсоңуз да, кээ бир пайдалуу кеңейтүү ыкмалары менен бул интерфейстер кантип кеңейтилгенин жакшылап карап чыгышыңыз керек.
Предикаттар
Предикаттар бул бир аргументи бар логикалык функциялар. Интерфейс предикаттарды колдонуу менен татаал логикалык туюнтмаларды (жана, же, жокко чыгаруу) түзүү үчүн ар кандай демейки ыкмаларды камтыйт 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();
Функциялар
Функциялар бир аргумент алып, натыйжа берет. Демейки ыкмалар бир нече функцияларды бир чынжырга бириктирүү үчүн колдонулушу мүмкүн (түзүү, анан). Function toInteger = Integer::valueOf; Function backToString = toInteger.andThen(String::valueOf); backToString.apply("123"); // "123"
Жабдуучулар
Жеткирүүчүлөр тигил же бул түрдөгү натыйжаны (мисалы) кайтарышат. Функциялардан айырмаланып, провайдерлер аргументтерди кабыл алышпайт. Supplier personSupplier = Person::new; personSupplier.get(); // new Person
Керектөөчүлөр
Керектөөчүлөр интерфейс ыкмаларын бир аргумент менен көрсөтөт. Consumer greeter = (p) -> System.out.println("Hello, " + p.firstName); greeter.accept(new Person("Luke", "Skywalker"));
Салыштыргычтар
Салыштыргычтар бизге Javaнын мурунку versionларынан белгилүү. Java 8 интерфейстерге ар кандай демейки ыкмаларды кошууга мүмкүндүк берет. 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
Опциялар
Опциялар интерфейси иштебейт, бирок ал NullPointerException алдын алуу үчүн эң сонун программа . Бул кийинки бөлүм үчүн маанилүү пункт, андыктан бул интерфейстин кантип иштээрин тез карап көрөлү. Кошумча интерфейс - бул нөл же нөл эмес болушу мүмкүн болгон баалуулуктар үчүн жөнөкөй контейнер. Метод маанини же эч нерсени кайтара алbyte деп элестетиңиз. Java 8де null кайтаруунун ордуна , сиз Кошумча инстанцияны кайтарасыз . 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

Агым

java.util.Stream – бул бир же бир нече операциялар аткарылуучу элементтердин ырааттуулугу. Ар бир Stream операциясы орто же терминал болуп саналат. Терминал операциялары белгилүү бир түрдөгү натыйжаны кайтарат, ал эми аралык операциялар агым an objectинин өзүн кайтарып, метод чакырыктарынын тизмегин түзүүгө мүмкүндүк берет. Stream – тизмелер жана топтомдор үчүн java.util.Collection сыяктуу интерфейс (карталар колдоого алынbyte). Ар бир Stream операциясы ырааттуу же параллелдүү түрдө аткарылышы мүмкүн. Келгиле, агым кантип иштээрин карап көрөлү. Биринчиден, биз үлгү codeду саптардын тизмеси түрүндө түзөбүз: Java 8деги коллекциялар өркүндөтүлгөн, ошондуктан сиз Collection.stream() же Collection.parallelStream() чакырып, агымдарды түзө аласыз . Кийинки бөлүмдө эң маанилүү, жөнөкөй агым операциялары түшүндүрүлөт. 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");
Фильтр
Чыпка агымдын бардык элементтерин чыпкалоо үчүн предикаттарды кабыл алат. Бул операция ортолук болуп саналат, ал бизге башка агым операцияларын (мисалы, forEach) натыйжада (фильтрленген) натыйжа боюнча чакырууга мүмкүндүк берет. ForEach мурунтан эле чыпкаланган агымдын ар бир элементинде аткарыла турган операцияны кабыл алат. ForEach бул терминалдык операция. Андан тышкары, башка операцияларды чакыруу мүмкүн эмес. stringCollection .stream() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); // "aaa2", "aaa1"
Сортталган
Сорттолгон агымдын сорттолгон көрүнүшүн кайтарган аралык операция. Салыштыруучуңузду көрсөтпөсөңүз, элементтер туура тартипте иреттелет . stringCollection .stream() .sorted() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); // "aaa1", "aaa2" Сортталган коллекциянын өзүнө таасирин тийгизбестен агымдын иреттелген өкүлчүлүгүн түзөрүн эстен чыгарбаңыз. stringCollection элементтеринин тартиби өзгөрүлбөйт: System.out.println(stringCollection); // ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
Карта
Аралык карта операциясы пайда болгон функцияны колдонуу менен ар бир элементти башка an objectке айлантат. Төмөнкү мисал ар бир сапты чоң тамгага айлантат. Бирок ар бир an objectти башка түргө которуу үчүн картаны да колдонсоңуз болот. Натыйжадагы агым an objectтеринин түрү сиз картага өткөргөн функциянын түрүнө жараша болот. stringCollection .stream() .map(String::toUpperCase) .sorted((a, b) -> b.compareTo(a)) .forEach(System.out::println); // "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
Матч
Ар кандай дал келүү операциялары агым мамилесинде белгилүү бир предикаттын чындыгын текшерүү үчүн колдонулушу мүмкүн. Бардык дал келүү операциялары терминалдык болуп саналат жана буль натыйжасын кайтарат. 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
Сан
Count - бул агымдын элементтеринин санын long катары кайтаруучу терминалдык операция . long startsWithB = stringCollection .stream() .filter((s) -> s.startsWith("b")) .count(); System.out.println(startsWithB); // 3
Кыскартуу
Бул өткөн функцияны колдонуу менен агымдын элементтерин кыскартуучу терминалдык операция. Натыйжада кыскартылган маанини камтыган Кошумча болот. Optional reduced = stringCollection .stream() .sorted() .reduce((s1, s2) -> s1 + "#" + s2); reduced.ifPresent(System.out::println); // "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"

Параллель агымдар

Жогоруда айтылгандай, агымдар ырааттуу же параллелдүү болушу мүмкүн. Ырааттуу агым операциялары сериялык жипте, ал эми параллелдүү агым операциялары бир нече параллелдүү жиптерде аткарылат. Төмөнкү мисал параллелдүү агымдын жардамы менен майнаптуулукту кантип оңой жогорулатууну көрсөтөт. Биринчиден, уникалдуу элементтердин чоң тизмесин түзөлү: Эми биз бул коллекциянын агымын сорттоого кеткен убакытты аныктайбыз. int max = 1000000; List values = new ArrayList<>(max); for (int i = 0; i < max; i++) { UUID uuid = UUID.randomUUID(); values.add(uuid.toString()); }
Сериялык агым
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
Параллель агым
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 Көрүнүп тургандай, эки фрагмент тең дээрлик бирдей, бирок параллелдүү сорттоо 50% тезирээк. Сизге болгону stream() параметрин parallelStream() га өзгөртүү керек .

Карта

Жогоруда айтылгандай, карталар агымдарды колдоого алbyte. Анын ордуна, карта жалпы көйгөйлөрдү чечүү үчүн жаңы жана пайдалуу ыкмаларды колдой баштады. Жогорудагы code интуитивдик болушу керек: putIfAbsent бизге кошумча нөл чектерди жазуудан эскертет. forEach ар бир карта маанилери үчүн аткаруу функциясын кабыл алат. Бул мисал функциялардын жардамы менен картанын баалуулуктары боюнча операциялар кандайча аткарыларын көрсөтөт: Андан кийин, биз берилген ачкыч үчүн жазууну, эгерде ал берилген мааниге дал келсе гана алып салууну үйрөнөбүз: Дагы бир жакшы ыкма: Карта жазууларын бириктирүү абдан оңой: Бириктирүү же картага ачкычты/маани киргизет, эгерде берилген ачкыч үчүн эч кандай жазуу жок болсо, же бириктирүү функциясы чакырылат, ал учурдагы жазуунун маанисин өзгөртөт. 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
Комментарийлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION