Kamusta! Sa Java Syntax Pro quest, pinag-aralan namin ang mga expression ng lambda at sinabi na ang mga ito ay walang iba kundi isang pagpapatupad ng isang functional na paraan mula sa isang functional na interface. Sa madaling salita, ito ay ang pagpapatupad ng ilang hindi kilalang (hindi kilalang) klase, ang hindi natanto na pamamaraan nito. At kung sa mga lektura ng kurso ay hinanap natin ang mga manipulasyon na may mga ekspresyong lambda, ngayon ay isasaalang-alang natin, wika nga, ang kabilang panig: ibig sabihin, ang mga mismong interface na ito. Ipinakilala ng ikawalong bersyon ng Java ang konsepto ng mga functional na interface . Ano ito? Ang isang interface na may isang hindi naipatupad (abstract) na pamamaraan ay itinuturing na gumagana. Maraming out-of-the-box na interface ang nasa ilalim ng kahulugang ito, gaya ng, halimbawa, ang dating tinalakay na interface panaguri
Konsyumer
Supplier
Function
UnaryOperator
Comparator
. At gayundin ang mga interface na nilikha namin sa aming sarili, tulad ng:
@FunctionalInterface
public interface Converter<T, N> {
N convert(T t);
}
Mayroon kaming isang interface na ang gawain ay i-convert ang mga bagay ng isang uri sa mga bagay ng isa pa (isang uri ng adaptor). Ang anotasyon @FunctionalInterface
ay hindi isang bagay na sobrang kumplikado o mahalaga, dahil ang layunin nito ay sabihin sa compiler na ang interface na ito ay gumagana at hindi dapat maglaman ng higit sa isang paraan. Kung ang isang interface na may ganitong anotasyon ay may higit sa isang hindi naipatupad na (abstract) na pamamaraan, hindi laktawan ng compiler ang interface na ito, dahil iisipin ito bilang maling code. Ang mga interface na walang annotation na ito ay maaaring ituring na gumagana at gagana, ngunit @FunctionalInterface
ito ay walang iba kundi karagdagang insurance. Balik na tayo sa klase Comparator
. Kung titingnan mo ang code nito (o dokumentasyon ), makikita mo na mayroon itong higit sa isang paraan. Pagkatapos ay itatanong mo: paano, kung gayon, maaari itong ituring na isang functional na interface? Ang mga abstract na interface ay maaaring magkaroon ng mga pamamaraan na wala sa saklaw ng isang paraan:
- static
@FunctionalInterface
public interface Converter<T, N> {
N convert(T t);
static <T> boolean isNotNull(T t){
return t != null;
}
}
Nang matanggap ang pamamaraang ito, hindi nagreklamo ang compiler, na nangangahulugang gumagana pa rin ang aming interface.
- mga default na pamamaraan
default
:
@FunctionalInterface
public interface Converter<T, N> {
N convert(T t);
static <T> boolean isNotNull(T t){
return t != null;
}
default void writeToConsole(T t) {
System.out.println("Текущий an object - " + t.toString());
}
}
Muli, nakita namin na ang compiler ay hindi nagsimulang magreklamo, at hindi kami lumampas sa mga limitasyon ng functional interface.
- Mga pamamaraan ng klase ng object
Object
. Hindi ito nalalapat sa mga interface. Ngunit kung mayroon kaming abstract na pamamaraan sa interface na tumutugma sa lagda sa ilang paraan ng class Object
, hindi masisira ng ganoong paraan (o mga pamamaraan) ang aming functional interface restriction:
@FunctionalInterface
public interface Converter<T, N> {
N convert(T t);
static <T> boolean isNotNull(T t){
return t != null;
}
default void writeToConsole(T t) {
System.out.println("Текущий an object - " + t.toString());
}
boolean equals(Object obj);
}
At muli, ang aming compiler ay hindi nagrereklamo, kaya ang interface Converter
ay itinuturing pa rin na gumagana. Ngayon ang tanong ay: bakit kailangan nating limitahan ang ating sarili sa isang hindi naipatupad na pamamaraan sa isang functional na interface? At pagkatapos ay upang maipatupad natin ito gamit ang mga lambdas. Tingnan natin ito sa isang halimbawa Converter
. Upang gawin ito, lumikha tayo ng isang klase Dog
:
public class Dog {
String name;
int age;
int weight;
public Dog(final String name, final int age, final int weight) {
this.name = name;
this.age = age;
this.weight = weight;
}
}
At isang katulad Raccoon
(raccoon):
public class Raccoon {
String name;
int age;
int weight;
public Raccoon(final String name, final int age, final int weight) {
this.name = name;
this.age = age;
this.weight = weight;
}
}
Ipagpalagay na mayroon tayong isang bagay Dog
, at kailangan nating lumikha ng isang bagay batay sa mga patlang nito Raccoon
. Iyon ay, Converter
pinapalitan nito ang isang bagay ng isang uri sa isa pa. Paano ito magiging hitsura:
public static void main(String[] args) {
Dog dog = new Dog("Bobbie", 5, 3);
Converter<Dog, Raccoon> converter = x -> new Raccoon(x.name, x.age, x.weight);
Raccoon raccoon = converter.convert(dog);
System.out.println("Raccoon has parameters: name - " + raccoon.name + ", age - " + raccoon.age + ", weight - " + raccoon.weight);
}
Kapag pinatakbo namin ito, nakukuha namin ang sumusunod na output sa console:
Raccoon has parameters: name - Bobbbie, age - 5, weight - 3
At nangangahulugan ito na gumana nang tama ang aming pamamaraan.
Pangunahing Java 8 Mga Pang-andar na Interface
Ngayon, tingnan natin ang ilang functional na interface na dinala sa atin ng Java 8 at aktibong ginagamit kasabay ng Stream API.panaguri
Predicate
— isang functional na interface para sa pagsuri kung ang isang partikular na kundisyon ay natutugunan. Kung natugunan ang kundisyon, babalik true
, kung hindi - false
:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
Bilang isang halimbawa, isaalang-alang ang paglikha ng isang Predicate
na magsusuri ng parity ng isang bilang ng uri Integer
:
public static void main(String[] args) {
Predicate<Integer> isEvenNumber = x -> x % 2==0;
System.out.println(isEvenNumber.test(4));
System.out.println(isEvenNumber.test(3));
}
Output ng console:
true
false
Konsyumer
Consumer
(mula sa English - "consumer") - isang functional na interface na kumukuha ng isang object ng uri T bilang isang input argument, nagsasagawa ng ilang mga aksyon, ngunit walang ibinalik:
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
Bilang halimbawa, isaalang-alang ang , na ang gawain ay maglabas ng pagbati sa console na may ipinasa na argumentong string: Consumer
public static void main(String[] args) {
Consumer<String> greetings = x -> System.out.println("Hello " + x + " !!!");
greetings.accept("Elena");
}
Output ng console:
Hello Elena !!!
Supplier
Supplier
(mula sa English - provider) - isang functional na interface na hindi kumukuha ng anumang mga argumento, ngunit nagbabalik ng object ng uri T:
@FunctionalInterface
public interface Supplier<T> {
T get();
}
Bilang halimbawa, isaalang-alang ang Supplier
, na gagawa ng mga random na pangalan mula sa isang listahan:
public static void main(String[] args) {
ArrayList<String> nameList = new ArrayList<>();
nameList .add("Elena");
nameList .add("John");
nameList .add("Alex");
nameList .add("Jim");
nameList .add("Sara");
Supplier<String> randomName = () -> {
int value = (int)(Math.random() * nameList.size());
return nameList.get(value);
};
System.out.println(randomName.get());
}
At kung patakbuhin natin ito, makikita natin ang mga random na resulta mula sa isang listahan ng mga pangalan sa console.
Function
Function
— ang functional na interface na ito ay kumukuha ng argumentong T at inihagis ito sa isang bagay ng uri R, na ibinalik bilang resulta:
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
Bilang halimbawa, kunin natin ang , na nagko-convert ng mga numero mula sa format ng string ( ) sa format ng numero ( ): Function
String
Integer
public static void main(String[] args) {
Function<String, Integer> valueConverter = x -> Integer.valueOf(x);
System.out.println(valueConverter.apply("678"));
}
Kapag pinatakbo namin ito, nakukuha namin ang sumusunod na output sa console:
678
PS: kung ipapasa namin hindi lamang ang mga numero, kundi pati na rin ang iba pang mga character sa string, isang pagbubukod ang itatapon - NumberFormatException
.
UnaryOperator
UnaryOperator
— isang functional na interface na kumukuha ng isang bagay ng uri T bilang isang parameter, nagsasagawa ng ilang mga operasyon dito at ibinabalik ang resulta ng mga operasyon sa anyo ng isang bagay ng parehong uri ng T:
@FunctionalInterface
public interface UnaryOperator<T> {
T apply(T t);
}
UnaryOperator
, na gumagamit ng pamamaraan nito apply
upang i-square ang isang numero:
public static void main(String[] args) {
UnaryOperator<Integer> squareValue = x -> x * x;
System.out.println(squareValue.apply(9));
}
Output ng console:
81
Tumingin kami sa limang functional na interface. Hindi lang ito ang magagamit sa amin simula sa Java 8 - ito ang mga pangunahing interface. Ang natitirang mga magagamit ay ang kanilang mga kumplikadong analogues. Ang kumpletong listahan ay matatagpuan sa opisyal na dokumentasyon ng Oracle .
Mga functional na interface sa Stream
Gaya ng tinalakay sa itaas, ang mga functional na interface na ito ay mahigpit na pinagsama sa Stream API. Paano, tanong mo? At tulad na maraming mga pamamaraanStream
ang partikular na gumagana sa mga functional na interface na ito. Tingnan natin kung paano magagamit ang mga functional na interface sa Stream
.
Pamamaraan na may panaguri
Halimbawa, kunin natin ang paraan ng klaseStream
- filter
na tumatagal bilang argumento Predicate
at ibinabalik Stream
lamang ang mga elementong iyon na nakakatugon sa kundisyon Predicate
. Sa konteksto ng Stream
-a, nangangahulugan ito na dumadaan lamang ito sa mga elementong iyon na ibinalik true
kapag ginamit sa isang paraan test
ng interface Predicate
. Ito ang magiging hitsura ng aming halimbawa para sa Predicate
, ngunit para sa isang filter ng mga elemento sa Stream
:
public static void main(String[] args) {
List<Integer> evenNumbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8)
.filter(x -> x % 2==0)
.collect(Collectors.toList());
}
Bilang resulta, ang listahan evenNumbers
ay bubuo ng mga elemento {2, 4, 6, 8}. At, gaya ng naaalala namin, collect
kokolektahin nito ang lahat ng elemento sa isang partikular na koleksyon: sa aming kaso, sa List
.
Pamamaraan sa Consumer
Isa sa mga pamamaraan saStream
, na gumagamit ng functional interface Consumer
, ay ang peek
. Ito ang magiging hitsura ng aming halimbawa Consumer
sa Stream
:
public static void main(String[] args) {
List<String> peopleGreetings = Stream.of("Elena", "John", "Alex", "Jim", "Sara")
.peek(x -> System.out.println("Hello " + x + " !!!"))
.collect(Collectors.toList());
}
Output ng console:
Hello Elena !!!
Hello John !!!
Hello Alex !!!
Hello Jim !!!
Hello Sara !!!
Ngunit dahil gumagana ang pamamaraan peek
sa Consumer
, ang pagbabago ng mga string sa Stream
ay hindi magaganap, ngunit peek
babalik Stream
kasama ang mga orihinal na elemento: katulad ng pagdating nila dito. Samakatuwid, ang listahan peopleGreetings
ay binubuo ng mga elementong "Elena", "John", "Alex", "Jim", "Sara". Mayroon ding karaniwang ginagamit na paraan foreach
, na katulad ng pamamaraan peek
, ngunit ang pagkakaiba ay ito ay panghuling - terminal.
Paraan sa Supplier
Ang isang halimbawa ng isang paraanStream
na gumagamit ng functional na interface Supplier
ay ang generate
, na bumubuo ng isang walang katapusang sequence batay sa functional na interface na ipinasa dito. Gamitin natin ang ating halimbawa Supplier
para mag-print ng limang random na pangalan sa console:
public static void main(String[] args) {
ArrayList<String> nameList = new ArrayList<>();
nameList.add("Elena");
nameList.add("John");
nameList.add("Alex");
nameList.add("Jim");
nameList.add("Sara");
Stream.generate(() -> {
int value = (int) (Math.random() * nameList.size());
return nameList.get(value);
}).limit(5).forEach(System.out::println);
}
At ito ang output na nakukuha namin sa console:
John
Elena
Elena
Elena
Jim
Dito ginamit namin ang pamamaraan limit(5)
upang magtakda ng limitasyon sa pamamaraan generate
, kung hindi ay magpi-print ang programa ng mga random na pangalan sa console nang walang katapusan.
Paraan na may Function
Ang isang tipikal na halimbawa ng isang pamamaraan na mayStream
argumento Function
ay isang paraan map
na kumukuha ng mga elemento ng isang uri, gumagawa ng isang bagay sa kanila at ipinapasa ang mga ito, ngunit ang mga ito ay maaaring mga elemento ng ibang uri. Ano ang hitsura ng isang halimbawa na may Function
in Stream
:
public static void main(String[] args) {
List<Integer> values = Stream.of("32", "43", "74", "54", "3")
.map(x -> Integer.valueOf(x)).collect(Collectors.toList());
}
Bilang resulta, nakakakuha kami ng listahan ng mga numero, ngunit sa Integer
.
Paraan sa UnaryOperator
Bilang isang pamamaraan na ginagamitUnaryOperator
bilang isang argumento, kunin natin ang isang paraan ng klase Stream
- iterate
. Ang pamamaraang ito ay katulad ng pamamaraan generate
: bumubuo rin ito ng walang katapusang pagkakasunud-sunod ngunit may dalawang argumento:
- ang una ay ang elemento kung saan nagsisimula ang sequence generation;
- ang pangalawa ay
UnaryOperator
, na nagpapahiwatig ng prinsipyo ng pagbuo ng mga bagong elemento mula sa unang elemento.
UnaryOperator
, ngunit sa pamamaraan iterate
:
public static void main(String[] args) {
Stream.iterate(9, x -> x * x)
.limit(4)
.forEach(System.out::println);
}
Kapag pinatakbo namin ito, nakukuha namin ang sumusunod na output sa console:
9
81
6561
43046721
Iyon ay, ang bawat isa sa aming mga elemento ay pinarami ng sarili nito, at iba pa para sa unang apat na numero. Iyon lang! Magiging mahusay kung pagkatapos basahin ang artikulong ito ikaw ay isang hakbang na mas malapit sa pag-unawa at mastering ang Stream API sa Java!
GO TO FULL VERSION