JavaRush /Java блогы /Random-KK /Java 8 нұсқаулығы. 1 бөлім.
ramhead
Деңгей

Java 8 нұсқаулығы. 1 бөлім.

Топта жарияланған

«Java әлі тірі және адамдар оны түсіне бастады».

Менің Java 8-мен таныстыруыма қош келдіңіз. Бұл нұсқаулық сізге тілдің барлық жаңа мүмкіндіктерін кезең-кезеңімен көрсетеді. Қысқа, қарапайым code мысалдары арқылы интерфейстің әдепкі әдістерін , лямбда өрнектерін , сілтеме әдістерін және қайталанатын annotationларды қалай пайдалану керектігін үйренесіз . Мақаланың соңына қарай сіз ағындар, функция интерфейстері, ассоциация кеңейтімдері және жаңа Date API сияқты API интерфейстеріне жасалған соңғы өзгерістермен таныс боласыз. Жалықтырғыш мәтіннің қабырғалары жоқ - түсініктеме берілген code үзінділері ғана. Ләззат алыңыз!

Интерфейстердің әдепкі әдістері

Java 8 әдепкі кілт сөзін пайдалану арқылы интерфейсте іске асырылған дерексіз емес әдістерді қосуға мүмкіндік береді . Бұл мүмкіндік кеңейту әдістері ретінде де белгілі . Міне, бірінші мысал: Есептеу interface Formula { double calculate(int a); default double sqrt(int a) { return Math.sqrt(a); } } дерексіз әдісіне қосымша Формула интерфейсі әдепкі sqrt әдісін де анықтайды . Формула интерфейсін жүзеге асыратын сыныптар тек абстрактілі есептеу әдісін орындайды . Әдепкі sqrt әдісін тікелей қораптан тыс пайдалануға болады. Формула нысаны анонимді нысан ретінде жүзеге асырылады. Код өте әсерлі: sqrt(a * 100) есептеу үшін codeтың 6 жолы . Келесі бөлімде көретініміздей, Java 8-де жалғыз әдіс нысандарын енгізудің неғұрлым тартымды жолы бар. Formula formula = new Formula() { @Override public double calculate(int a) { return sqrt(a * 100); } }; formula.calculate(100); // 100.0 formula.sqrt(16); // 4.0

Ламбда өрнектері

Java-ның алғашқы нұсқаларында жолдар массивін сұрыптау жолының қарапайым мысалынан бастайық: Collections.sort статистикалық көмекші әдісі берілген тізімнің элементтерін сұрыптау үшін тізім мен Comparator алады . Көбінесе сіз анонимді компараторларды жасап, оларды сұрыптау әдістеріне жібересіз. Әрдайым анонимді нысандарды жасаудың орнына, 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));

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

Lambda өрнектері 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 :: кілт сөз таңбаларын пайдаланып әдістер мен конструкторларға сілтемелерді беруге мүмкіндік береді . Жоғарыда келтірілген мысал статистикалық әдістерді қалай қолдануға болатынын көрсетеді. Бірақ біз нысандардағы әдістерге де сілтеме жасай аламыз: :: конструкторлар үшін қалай жұмыс істейтінін қарастырайық . Алдымен әртүрлі конструкторлармен мысалды анықтайық: Әрі қарай, жаңа тұлға нысандарын жасау үшін 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");

Ламбда аймағы

Ламбда өрнектерінен сыртқы аумақ айнымалыларына қатынасты ұйымдастыру анонимді нысаннан қатынасуға ұқсас. Соңғы айнымалы мәндерге жергілікті аумақтан, сонымен қатар дана өрістері мен жиынтық айнымалы мәндерден қол жеткізе аласыз.
Жергілікті айнымалыларға қол жеткізу
Біз соңғы модификаторы бар жергілікті айнымалыны lambda өрнегі ауқымынан оқи аламыз: Бірақ анонимді нысандардан айырмашылығы, айнымалы мәндерді lambda өрнегі арқылы қол жетімді болу үшін соңғы деп жариялау қажет емес . Бұл 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;
Дана өрістеріне және статистикалық айнымалыларға қатынасу
Жергілікті айнымалылардан айырмашылығы, біз лямбда өрнектерінің ішіндегі даналық өрістер мен статистикалық айнымалы мәндерді оқып, өзгерте аламыз. Біз бұл әрекетті анонимді нысандардан білеміз. 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); }; } }
Интерфейстердің әдепкі әдістеріне қол жеткізу
Бірінші бөлімдегі формула данасы бар мысалды есте сақтаңыз ба ? Формула интерфейсі анонимді нысандарды қоса, формуланың әрбір данасынан қатынасуға болатын әдепкі sqrt әдісін анықтайды . Бұл лямбда өрнектерімен жұмыс істемейді. Әдепкі әдістерге лямбда өрнектерінің ішінде қол жеткізу мүмкін емес. Келесі code құрастырылмайды: Formula formula = (a) -> sqrt( a * 100);

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

JDK 1.8 API көптеген кірістірілген функционалды интерфейстерді қамтиды. Олардың кейбіреулері Java-ның алдыңғы нұсқаларынан жақсы белгілі. Мысалы , 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-ның алдыңғы нұсқаларынан белгілі. 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 алдын алу үшін тамаша утorта болып табылады . Бұл келесі бөлім үшін маңызды мәселе, сондықтан осы интерфейстің қалай жұмыс істейтінін жылдам қарастырайық. Қосымша интерфейс - бұл нөл немесе нөл емес мәндер үшін қарапайым контейнер. Әдіс мәнді немесе ештеңені қайтара алмайтынын елестетіңіз. 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 операциясы аралық немесе терминал болып табылады. Терминал операциялары белгілі бір түрдегі нәтижені қайтарады, ал аралық операциялар ағын нысанының өзін қайтарады, бұл әдіс шақыруларының тізбегін жасауға мүмкіндік береді. Stream – тізімдер мен жиындарға арналған java.util.Collection сияқты интерфейс (карталарға қолдау көрсетілмейді).Әр ағын әрекетін дәйекті немесе параллель орындауға болады. Ағынның қалай жұмыс істейтінін қарастырайық. Алдымен жолдар тізімі түрінде үлгі 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
Карта
Аралық карта операциясы алынған функцияны пайдаланып әрбір элементті басқа нысанға түрлендіреді. Келесі мысал әрбір жолды бас әріп жолына түрлендіреді. Бірақ картаны әр нысанды басқа түрге түрлендіру үшін де пайдалануға болады. Нәтижедегі ағын нысандарының түрі картаға беретін функция түріне байланысты. 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
Санау
Санау - ағынның элементтерінің санын 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() өзгерту қажет .

Карта

Жоғарыда айтылғандай, карталар ағындарды қолдамайды. Оның орнына карта жалпы мәселелерді шешудің жаңа және пайдалы әдістерін қолдай бастады. Жоғарыдағы 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