Салам! Java Syntax Pro квестинде биз ламбда туюнтмаларын изилдедик жана алар функционалдык интерфейстен функционалдык ыкманы ишке ашыруудан башка эч нерсе эмес деп айттык. Башкача айтканда, бул кандайдыр бир анонимдүү (белгисиз) класстын ишке ашырылышы, анын ишке ашпаган ыкмасы. Ал эми курстун лекцияларында биз лямбда туюнтмалары менен манипуляцияларды изилдеп көргөн болсок, азыр биз, мындайча айтканда, экинчи жагын карайбыз: тактап айтканда, дал ушул интерфейстер. Javaнын сегизинчи versionсы функционалдык интерфейстер түшүнүгүн киргизди . Бул эмне? Бир ишке ашырылбаган (абстрактуу) методу бар интерфейс функционалдуу деп эсептелет. Көптөгөн кутудан тышкаркы интерфейстер бул аныктамага кирет, мисалы, мурда талкууланган интерфейс . Ошондой эле биз өзүбүз жараткан интерфейстер, мисалы: Предикат
Керектөөчү
Жабдуучу
Функция
UnaryOperator
Comparator
@FunctionalInterface
public interface Converter<T, N> {
N convert(T t);
}
Бизде интерфейс бар, анын милдети бир түрдөгү an objectтерди башка an objectтерге (адаптердин бир түрү) айландыруу. Аннотация @FunctionalInterface
өтө татаал же маанилүү нерсе эмес, анткени анын максаты компиляторго бул интерфейс функционалдуу экенин жана бирден ашык методду камтыбашы керектигин айтуу. Эгерде бул annotation менен интерфейсте бирден ашык аткарылбаган (абстрактуу) методдор болсо, компилятор бул интерфейсти өткөрүп жибербейт, анткени ал аны ката code катары кабыл алат. Бул annotationсыз интерфейстерди функционалдык деп эсептесе болот жана иштейт, бирок @FunctionalInterface
бул кошумча камсыздандыруудан башка эч нерсе эмес. Класска кайталы Comparator
. Эгер анын codeун (же documentациясын ) карасаңыз , анда бир нече ыкмалар бар экенин көрө аласыз. Анда сиз суроо бересиз: анда аны кантип функционалдык интерфейс деп кароого болот? Абстракттуу интерфейстерде бир методдун чөйрөсүнө кирбеген ыкмалар болушу мүмкүн:
- статикалык
@FunctionalInterface
public interface Converter<T, N> {
N convert(T t);
static <T> boolean isNotNull(T t){
return t != null;
}
}
Бул ыкманы алгандан кийин, компилятор даттанган жок, демек биздин интерфейс дагы эле иштейт.
- демейки ыкмалар
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());
}
}
Дагы бир жолу, компилятор даттанууну баштабаганын жана функционалдык интерфейстин чектөөлөрүнүн чегинен чыккан жокпуз.
- Объект классынын методдору
Object
. Бул интерфейстерге тиешелүү эмес. Бирок интерфейсте класстын кандайдыр бир ыкмасы менен кол тамгага дал келген абстракттуу метод болсо Object
, мындай ыкма (же методдор) биздин функционалдык интерфейс чектөөбүздү бузbyte:
@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);
}
Жана дагы, биздин компилятор нааразы эмес, ошондуктан интерфейс Converter
дагы эле функционалдык деп эсептелет. Эми суроо туулат: эмне үчүн биз функционалдык интерфейсте бир гана ишке ашырылбаган ыкма менен чектешибиз керек? Анан ламбдаларды колдонуу менен аны ишке ашыра алабыз. Муну бир мисал менен карап көрөлү Converter
. Бул үчүн, келгиле, класс түзөлү 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;
}
}
Жана ушуга окшош 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;
}
}
Бизде an object бар дейли Dog
жана анын талааларынын негизинде an object түзүшүбүз керек Raccoon
. Башкача айтканда, Converter
бир түрдөгү an objectти экинчи түргө которот. Ал кандай болот:
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);
}
Аны иштеткенде, биз консолго төмөнкүдөй жыйынтыкты алабыз:
Raccoon has parameters: name - Bobbbie, age - 5, weight - 3
Бул биздин ыкма туура иштеген дегенди билдирет.
Негизги Java 8 Функционалдык интерфейстери
Келгиле, азыр Java 8 бизге алып келген жана Stream API менен бирге активдүү колдонулган бир нече функционалдык интерфейстерди карап көрөлү.Предикат
Predicate
— белгилүү бир шарттын аткарылышын текшерүү үчүн функционалдык интерфейс. Эгерде шарт аткарылса, кайтарат true
, антпесе - false
:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
Мисал катары, Predicate
бир катар түрдөгү паритетти текшере турган түзүүнү карап көрөлү 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));
}
Консолдук чыгаруу:
true
false
Керектөөчү
Consumer
(англис тorнен - "керектөөчү") - киргизүү аргументи катары T түрүндөгү an objectти кабыл алган, кээ бир аракеттерди аткарган, бирок эч нерсе кайтарбаган функционалдык интерфейс:
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
Мисал катары карап көрөлү , анын милдети өтүп кеткен сап аргументи менен консолго саламдашууну чыгаруу: Consumer
public static void main(String[] args) {
Consumer<String> greetings = x -> System.out.println("Hello " + x + " !!!");
greetings.accept("Elena");
}
Консолдук чыгаруу:
Hello Elena !!!
Жабдуучу
Supplier
(англис тorнен - провайдер) - функционалдык интерфейс, ал эч кандай аргументтерди кабыл алbyte, бирок T түрүндөгү an objectти кайтарат:
@FunctionalInterface
public interface Supplier<T> {
T get();
}
Мисал катары, Supplier
тизмеден кокус аттарды чыгара турганды карап көрөлү:
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());
}
Эгер биз муну иштетсек, консолдогу ысымдардын тизмесинен туш келди жыйынтыктарды көрөбүз.
Функция
Function
— бул функционалдык интерфейс T аргументин алат жана аны R түрүндөгү an objectке чыгарат, ал натыйжада кайтарылат:
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
Мисал катары, сандарды сап форматынан ( ) сан форматына ( ) которуучуну алалы : Function
String
Integer
public static void main(String[] args) {
Function<String, Integer> valueConverter = x -> Integer.valueOf(x);
System.out.println(valueConverter.apply("678"));
}
Аны иштеткенде, биз консолго төмөнкүдөй жыйынтыкты алабыз:
678
PS: эгерде биз сандарды гана эмес, башка символдорду да сапка өткөрсөк, анда өзгөчө жагдай пайда болот - NumberFormatException
.
UnaryOperator
UnaryOperator
— параметр катары Т типтеги an objectти кабыл алган, ал боюнча кээ бир операцияларды аткарган жана ошол эле Т типтеги an object түрүндөгү операциялардын натыйжасын кайтаруучу функционалдык интерфейс:
@FunctionalInterface
public interface UnaryOperator<T> {
T apply(T t);
}
UnaryOperator
apply
санды квадраттоо үчүн өзүнүн ыкмасын колдонот :
public static void main(String[] args) {
UnaryOperator<Integer> squareValue = x -> x * x;
System.out.println(squareValue.apply(9));
}
Консолдук чыгаруу:
81
Биз беш функционалдык интерфейсти карадык. Бул Java 8ден баштап бизге жеткorктүү болгон нерсе эмес - булар негизги интерфейстер. Калгандары алардын татаал аналогдору болуп саналат. Толук тизмени расмий Oracle documentтеринен тапса болот .
Агымдагы функционалдык интерфейстер
Жогоруда айтылгандай, бул функционалдык интерфейстер Stream API менен тыгыз айкалышкан. Кантип, сурайсыңбы? Ошентип, көптөгөн ыкмаларStream
ушул функционалдык интерфейстер менен иштешет. Функционалдык интерфейстерди кантип колдонсо болорун карап көрөлү Stream
.
Предикат менен метод
Мисалы, класс ыкмасын алалыStream
- filter
ал аргумент катары кабыл алынат Predicate
жана Stream
шартты канааттандырган элементтерди гана кайтарат Predicate
. -a контекстинде , бул интерфейс методунда колдонулганда Stream
кайтарылган элементтерден гана өтөт дегенди билдирет . Бул биздин мисалга окшош болот , бирок элементтердин чыпкасы үчүн : true
test
Predicate
Predicate
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());
}
Натыйжада, тизме evenNumbers
{2, 4, 6, 8} элементтерден турат. Жана, биз эстегендей, collect
ал бардык элементтерди белгилүү бир коллекцияга чогултат: биздин учурда, List
.
Керектөөчү менен ыкмасы
Stream
Функционалдык интерфейсти колдонгон ыкмалардын бири Consumer
болуп саналат peek
. Биздин мисалыбыз ушундай Consumer
болот 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());
}
Консолдук чыгаруу:
Hello Elena !!!
Hello John !!!
Hello Alex !!!
Hello Jim !!!
Hello Sara !!!
Бирок метод peek
менен иштегендиктен Consumer
, саптардын модификациясы Stream
болбойт, бирок баштапкы элементтер менен peek
кайтып келет Stream
: алар ага келгендей эле. Ошондуктан, тизме peopleGreetings
"Елена", "Джон", "Алекс", "Джим", "Сара" элементтеринен турат. foreach
Методго окшош көп колдонулган ыкма дагы бар peek
, бирок айырмасы бул акыркы - терминалдык.
Жабдуучу менен ыкма
Stream
Функционалдык интерфейсти колдонгон методдун мисалы Supplier
, generate
ага берилген функционалдык интерфейстин негизинде чексиз ырааттуулукту жаратат. Келгиле, Supplier
консолго беш кокус атын басып чыгаруу үчүн биздин мисалды колдонолу:
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);
}
Жана бул биз консолдо алган жыйынтык:
John
Elena
Elena
Elena
Jim
limit(5)
Бул жерде биз методго чектөө коюу үчүн методду колдондук generate
, антпесе программа консолго чексиз түрдө туш келди аттарды басып чыгарат.
Функция менен метод
Stream
Аргументтүү методдун типтүү мисалы бир типтеги элементтерди алып, алар менен бир нерсе жасап, аларды өткөрүп берүүчү Function
метод , бирок булар башка типтеги элементтер болушу мүмкүн. in map
менен мисал кандай болушу мүмкүн : Function
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());
}
Натыйжада, биз сандардын тизмесин алабыз, бирок ичинде Integer
.
UnaryOperator менен ыкма
Аргумент катары колдонулган ыкма катарыUnaryOperator
класс ыкмасын алалы Stream
- iterate
. Бул ыкма методго окшош generate
: ал ошондой эле чексиз ырааттуулукту жаратат, бирок эки аргументи бар:
- биринчи кезектеги генерация башталган элемент;
- экинчиси
UnaryOperator
- биринчи элементтен жаңы элементтерди түзүү принцибинин маанисин көрсөтөт.
UnaryOperator
, бирок методдо iterate
:
public static void main(String[] args) {
Stream.iterate(9, x -> x * x)
.limit(4)
.forEach(System.out::println);
}
Аны иштеткенде, биз консолго төмөнкүдөй жыйынтыкты алабыз:
9
81
6561
43046721
Башкача айтканда, биздин элементтердин ар бири өзүнө көбөйтүлөт жана биринчи төрт сан үчүн. Баары болду! Бул макаланы окугандан кийин Java'дагы Stream APIди түшүнүүгө жана өздөштүрүүгө бир кадам жакындасаңыз жакшы болмок!
GO TO FULL VERSION