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

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

Топта жарияланған
Сәлеметсіз бе! Java Syntax Pro квестінде біз лямбда өрнектерін зерттедік және олар функционалды интерфейстен функционалды әдісті жүзеге асырудан басқа ештеңе емес екенін айттық. Басқаша айтқанда, бұл қандай да бір анонимді (белгісіз) классты жүзеге асыру, оның іске асырылмаған әдісі. Егер курстың лекцияларында біз лямбда өрнектерімен манипуляцияларды қарастыратын болсақ, енді біз, былайша айтқанда, екінші жағын қарастырамыз: дәл осы интерфейстер. Java сегізінші нұсқасы функционалдық интерфейстерJava тіліндегі функционалдық интерфейстер – 1 тұжырымдамасын енгізді . Бұл не? Бір орындалмаған (абстрактілі) әдіспен интерфейс функционалды болып саналады. Көптеген дайын интерфейстер осы анықтамаға жатады, мысалы, бұрын талқыланған интерфейс . Сондай-ақ өзіміз жасайтын интерфейстер, мысалы: Comparator
@FunctionalInterface
public interface Converter<T, N> {
   N convert(T t);
}
Бізде интерфейс бар, оның міндеті бір түрдегі нысандарды басқа нысандарға (адаптер түрі) түрлендіру болып табылады. Аннотация @FunctionalInterfaceөте күрделі немесе маңызды нәрсе емес, өйткені оның мақсаты компиляторға бұл интерфейстің функционалды екенін және бірнеше әдісті қамтуы керектігін айту. Егер осы annotationмен интерфейсте бірнеше орындалмаған (дерексіз) әдістер болса, компилятор бұл интерфейсті өткізіп жібермейді, өйткені ол оны қате code ретінде қабылдайды. Бұл annotationсыз интерфейстерді функционалды деп санауға болады және жұмыс істейді, бірақ @FunctionalInterfaceбұл қосымша сақтандырудан басқа ештеңе емес. Сабаққа қайтайық Comparator. Егер сіз оның codeын (немесе құжаттамасын ) қарасаңыз , оның бірнеше әдісі бар екенін көре аласыз. Сонда сіз сұрайсыз: қалай, оны функционалды интерфейс деп санауға болады? Абстрактілі интерфейстерде бір әдістің ауқымына кірмейтін әдістер болуы мүмкін:
  • статикалық
Интерфейс концепциясы codeтың берілген бірлігінде іске асырылған әдістердің болмауын білдіреді. Бірақ Java 8-ден бастап интерфейстерде статикалық және әдепкі әдістерді қолдану мүмкін болды. Статикалық әдістер сыныпқа тікелей байланысты және мұндай әдісті шақыру үшін сол сыныптың белгілі бір 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, мұндай әдіс (немесе әдістер) біздің функционалды интерфейс шектеуімізді бұзбайды:
@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 тіліндегі функционалдық интерфейстер – 2

Негізгі Java 8 функционалдық интерфейстері

Енді Java 8 бізге әкелген және Stream API-мен бірге белсенді қолданылатын бірнеше функционалды интерфейстерді қарастырайық.

Предикат

Predicate— белгілі бір шарттың орындалғанын тексеруге арналған функционалды интерфейс. Шарт орындалса, қайтарады true, әйтпесе - false:
@FunctionalInterface
public interface Predicate<T> {
   boolean test(T t);
}
Мысал ретінде, Predicateсан түрінің теңдігін тексеретін a жасауды қарастырыңыз 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(ағылшын тілінен – «тұтынушы») – кіріс аргументі ретінде T түріндегі нысанды қабылдайтын, кейбір әрекеттерді орындайтын, бірақ ештеңені қайтармайтын функционалды интерфейс:
@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(ағылшын тілінен - ​​провайдер) - ешқандай аргумент қабылдамайтын, бірақ T түріндегі нысанды қайтаратын функционалды интерфейс:
@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іні қабылдайтын, оған кейбір операцияларды орындайтын және сол T типті 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-ден бастап бізге қол жетімді нәрсе емес - бұл негізгі интерфейстер. Қалғандары олардың күрделі аналогтары болып табылады. Толық тізімді Oracle ресми құжаттамасынан табуға болады .

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

Жоғарыда талқыланғандай, бұл функционалды интерфейстер Stream API интерфейсімен тығыз байланысты. Қалай, сұрайсың ба? Java тіліндегі функционалдық интерфейстер – 3Және көптеген әдістер Streamосы функционалды интерфейстермен арнайы жұмыс істейді. Функционалдық интерфейстерді қалай қолдануға болатынын қарастырайық Stream.

Предикат әдісі

Мысалы, аргумент ретінде қабылданатын және шартты қанағаттандыратын элементтерді ғана қайтаратын класс Streamәдісін алайық . -a контекстінде бұл интерфейс әдісінде пайдаланылған кезде қайтарылатын элементтер арқылы ғана өтетінін білдіреді . Бұл біздің мысалға ұқсайды , бірақ элементтер сүзгісі үшін : filterPredicateStreamPredicateStreamtruetestPredicatePredicateStream
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 тіліндегі функционалдық интерфейстер – 4Бар болғаны! Осы мақаланы оқығаннан кейін Java тіліндегі Stream API интерфейсін түсінуге және меңгеруге бір қадам жақындасаңыз, тамаша болар еді!
Пікірлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION