JavaRush /Java блогу /Random-KY /Java тилиндеги функционалдык интерфейстер

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

Группада жарыяланган
Салам! Java Syntax Pro квестинде биз ламбда туюнтмаларын изилдедик жана алар функционалдык интерфейстен функционалдык ыкманы ишке ашыруудан башка эч нерсе эмес деп айттык. Башкача айтканда, бул кандайдыр бир анонимдүү (белгисиз) класстын ишке ашырылышы, анын ишке ашпаган ыкмасы. Ал эми курстун лекцияларында биз лямбда туюнтмалары менен манипуляцияларды изилдеп көргөн болсок, азыр биз, мындайча айтканда, экинчи жагын карайбыз: тактап айтканда, дал ушул интерфейстер. Javaнын сегизинчи versionсы функционалдык интерфейстерJava тorндеги функционалдык интерфейстер - 1 түшүнүгүн киргизди . Бул эмне? Бир ишке ашырылбаган (абстрактуу) методу бар интерфейс функционалдуу деп эсептелет. Көптөгөн кутудан тышкаркы интерфейстер бул аныктамага кирет, мисалы, мурда талкууланган интерфейс . Ошондой эле биз өзүбүз жараткан интерфейстер, мисалы: Comparator
@FunctionalInterface
public interface Converter<T, N> {
   N convert(T t);
}
Бизде интерфейс бар, анын милдети бир түрдөгү an objectтерди башка an objectтерге (адаптердин бир түрү) айландыруу. Аннотация @FunctionalInterfaceөтө татаал же маанилүү нерсе эмес, анткени анын максаты компиляторго бул интерфейс функционалдуу экенин жана бирден ашык методду камтыбашы керектигин айтуу. Эгерде бул annotation менен интерфейсте бирден ашык аткарылбаган (абстрактуу) методдор болсо, компилятор бул интерфейсти өткөрүп жибербейт, анткени ал аны ката code катары кабыл алат. Бул annotationсыз интерфейстерди функционалдык деп эсептесе болот жана иштейт, бирок @FunctionalInterfaceбул кошумча камсыздандыруудан башка эч нерсе эмес. Класска кайталы Comparator. Эгер анын codeун (же documentациясын ) карасаңыз , анда бир нече ыкмалар бар экенин көрө аласыз. Анда сиз суроо бересиз: анда аны кантип функционалдык интерфейс деп кароого болот? Абстракттуу интерфейстерде бир методдун чөйрөсүнө кирбеген ыкмалар болушу мүмкүн:
  • статикалык
Интерфейстердин концепциясы codeдун берилген бирдигинде эч кандай методдор ишке ашырыла албасын билдирет. Бирок Java 8ден баштап интерфейстерде статикалык жана демейки ыкмаларды колдонуу мүмкүн болду. Статикалык методдор класска түздөн-түз байланышкан жана мындай методду чакыруу үчүн ошол класстын белгилүү бир an objectисин талап кылbyte. Башкача айтканда, бул ыкмалар интерфейстердин түшүнүгүнө шайкеш келет. Мисал катары, мурунку класска an objectти нөлгө текшерүү үчүн статикалык ыкманы кошолу:
@FunctionalInterface
public interface Converter<T, N> {

   N convert(T t);

   static <T> boolean isNotNull(T t){
       return t != null;
   }
}
Бул ыкманы алгандан кийин, компилятор даттанган жок, демек биздин интерфейс дагы эле иштейт.
  • демейки ыкмалар
Java 8ге чейин, эгерде башка класстар тарабынан мурасталган интерфейсте метод түзүү керек болсо, биз ар бир конкреттүү класста ишке ашырылган абстракттуу методду гана түзө алмакпыз. Бирок бул ыкма бардык класстар үчүн бирдей болсочу? Бул учурда көбүнчө абстракттуу класстар колдонулган . Бирок Java 8ден баштап, ишке ашырылган ыкмалар менен интерфейстерди колдонуу мүмкүнчүлүгү бар - демейки ыкмалар. Интерфейсти мурастап жатканда, сиз бул ыкмаларды жокко чыгарсаңыз же баарын ошол бойдон калтырсаңыз болот (демейки логиканы калтырыңыз). Демейки ыкманы түзүүдө биз ачкыч сөздү кошушубуз керек - 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 тorндеги функционалдык интерфейстер - 2

Негизги 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);
}
Мисал катары, сандарды сап форматынан ( ) сан форматына ( ) которуучуну алалы : FunctionStringInteger
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);
}
UnaryOperatorapplyсанды квадраттоо үчүн өзүнүн ыкмасын колдонот :
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 менен тыгыз айкалышкан. Кантип, сурайсыңбы? Java тorндеги функционалдык интерфейстер - 3Ошентип, көптөгөн ыкмалар Streamушул функционалдык интерфейстер менен иштешет. Функционалдык интерфейстерди кантип колдонсо болорун карап көрөлү Stream.

Предикат менен метод

Мисалы, класс ыкмасын алалы Stream- filterал аргумент катары кабыл алынат Predicateжана Streamшартты канааттандырган элементтерди гана кайтарат Predicate. -a контекстинде , бул интерфейс методунда колдонулганда Streamкайтарылган элементтерден гана өтөт дегенди билдирет . Бул биздин мисалга окшош болот , бирок элементтердин чыпкасы үчүн : truetestPredicatePredicateStream
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менен мисал кандай болушу мүмкүн : FunctionStream
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 тorндеги функционалдык интерфейстер - 4Баары болду! Бул макаланы окугандан кийин Java'дагы Stream APIди түшүнүүгө жана өздөштүрүүгө бир кадам жакындасаңыз жакшы болмок!
Комментарийлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION