JavaRush /جاوا بلاگ /Random-UR /جاوا میں فنکشنل انٹرفیس

جاوا میں فنکشنل انٹرفیس

گروپ میں شائع ہوا۔
ہیلو! جاوا سنٹیکس پرو کی تلاش میں، ہم نے لیمبڈا ایکسپریشنز کا مطالعہ کیا اور کہا کہ وہ فنکشنل انٹرفیس سے فنکشنل طریقہ کے نفاذ کے علاوہ کچھ نہیں ہیں۔ دوسرے لفظوں میں، یہ کسی گمنام (نامعلوم) طبقے کا نفاذ ہے، اس کا غیر حقیقی طریقہ۔ اور اگر کورس کے لیکچرز میں ہم نے لیمبڈا ایکسپریشنز کے ساتھ ہیرا پھیری کی تو اب ہم دوسرے پہلو پر غور کریں گے: یعنی یہ بہت ہی انٹرفیس۔ جاوا میں فنکشنل انٹرفیس - 1جاوا کے آٹھویں ورژن نے فنکشنل انٹرفیس کا تصور متعارف کرایا ۔ یہ کیا ہے؟ ایک غیر لاگو (خلاصہ) طریقہ کے ساتھ ایک انٹرفیس کو فعال سمجھا جاتا ہے۔ بہت سے آؤٹ آف دی باکس انٹرفیس اس تعریف کے تحت آتے ہیں، جیسے، مثال کے طور پر، پہلے زیر بحث انٹرفیس Comparator۔ اور انٹرفیس بھی جو ہم خود بناتے ہیں، جیسے:
@FunctionalInterface
public interface Converter<T, N> {
   N convert(T t);
}
ہمارے پاس ایک انٹرفیس ہے جس کا کام ایک قسم کی اشیاء کو دوسری قسم کی اشیاء (ایڈاپٹر کی ایک قسم) میں تبدیل کرنا ہے۔ تشریح @FunctionalInterfaceکوئی انتہائی پیچیدہ یا اہم چیز نہیں ہے، کیونکہ اس کا مقصد کمپائلر کو بتانا ہے کہ یہ انٹرفیس فعال ہے اور اس میں ایک سے زیادہ طریقہ نہیں ہونا چاہیے۔ اگر اس تشریح کے ساتھ انٹرفیس میں ایک سے زیادہ غیر لاگو (خلاصہ) طریقہ ہے، تو مرتب کرنے والا اس انٹرفیس کو نہیں چھوڑے گا، کیونکہ وہ اسے غلط کوڈ کے طور پر سمجھے گا۔ اس تشریح کے بغیر انٹرفیس کو فعال سمجھا جا سکتا ہے اور وہ کام کریں گے، لیکن @FunctionalInterfaceیہ اضافی انشورنس سے زیادہ کچھ نہیں ہے۔ چلو واپس کلاس میں چلتے ہیں Comparator۔ اگر آپ اس کے کوڈ (یا دستاویزات ) کو دیکھیں تو آپ دیکھ سکتے ہیں کہ اس میں ایک سے زیادہ طریقے ہیں۔ پھر آپ پوچھتے ہیں: پھر، اسے ایک فعال انٹرفیس کیسے سمجھا جا سکتا ہے؟ خلاصہ انٹرفیس میں ایسے طریقے ہوسکتے ہیں جو کسی ایک طریقہ کے دائرہ کار میں نہیں ہیں:
  • جامد
انٹرفیس کے تصور کا مطلب یہ ہے کہ کوڈ کی دی گئی اکائی میں کوئی طریقہ نافذ نہیں ہو سکتا۔ لیکن جاوا 8 کے ساتھ شروع کرتے ہوئے، انٹرفیس میں جامد اور پہلے سے طے شدہ طریقے استعمال کرنا ممکن ہو گیا۔ جامد طریقے براہ راست کسی طبقے سے منسلک ہوتے ہیں اور اس طبقے کے کسی مخصوص شے کو اس طرح کے طریقہ کار کو کال کرنے کی ضرورت نہیں ہوتی ہے۔ یعنی یہ طریقے انٹرفیس کے تصور میں ہم آہنگی سے فٹ ہوتے ہیں۔ مثال کے طور پر، آئیے پچھلی کلاس میں null کے لیے کسی چیز کو چیک کرنے کے لیے ایک جامد طریقہ شامل کریں:
@FunctionalInterface
public interface Converter<T, N> {

   N convert(T t);

   static <T> boolean isNotNull(T t){
       return t != null;
   }
}
اس طریقہ کو حاصل کرنے کے بعد، مرتب کرنے والے نے شکایت نہیں کی، جس کا مطلب ہے کہ ہمارا انٹرفیس اب بھی فعال ہے۔
  • پہلے سے طے شدہ طریقے
جاوا 8 سے پہلے، اگر ہمیں کسی ایسے انٹرفیس میں کوئی طریقہ بنانے کی ضرورت ہو جو دوسری کلاسوں سے وراثت میں ملی ہو، تو ہم صرف ایک تجریدی طریقہ بنا سکتے ہیں جو ہر مخصوص کلاس میں لاگو کیا گیا ہو۔ لیکن کیا ہوگا اگر یہ طریقہ تمام کلاسوں کے لیے یکساں ہو؟ اس معاملے میں ، تجریدی کلاسیں اکثر استعمال ہوتی تھیں ۔ لیکن جاوا 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;
  }
}
فرض کریں کہ ہمارے پاس ایک آبجیکٹ ہے Dog، اور ہمیں اس کی فیلڈز کی بنیاد پر ایک آبجیکٹ بنانے کی ضرورت ہے Raccoon۔ یعنی Converterیہ ایک قسم کی چیز کو دوسری قسم میں تبدیل کرتا ہے۔ یہ کیسا نظر آئے گا:
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
اور اس کا مطلب ہے کہ ہمارا طریقہ درست طریقے سے کام کرتا ہے۔جاوا میں فنکشنل انٹرفیس - 2

بنیادی جاوا 8 فنکشنل انٹرفیس

ٹھیک ہے، اب آئیے کئی فنکشنل انٹرفیسز کو دیکھتے ہیں جو جاوا 8 ہمارے لیے لائے ہیں اور جو اسٹریم 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(انگریزی سے - "صارف") - ایک فنکشنل انٹرفیس جو 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 قسم کی کسی چیز پر ڈال دیتا ہے، جس کے نتیجے میں واپس آ جاتا ہے:
@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- ایک فنکشنل انٹرفیس جو T قسم کی کسی چیز کو پیرامیٹر کے طور پر لیتا ہے، اس پر کچھ آپریشن کرتا ہے اور اسی قسم کے T کے کسی شے کی شکل میں آپریشن کا نتیجہ واپس کرتا ہے۔
@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
ہم نے پانچ فنکشنل انٹرفیس کو دیکھا۔ یہ وہ سب کچھ نہیں ہے جو جاوا 8 سے شروع ہو کر ہمارے لیے دستیاب ہے - یہ اہم انٹرفیس ہیں۔ باقی دستیاب ان کے پیچیدہ اینالاگ ہیں۔ مکمل فہرست سرکاری اوریکل دستاویزات میں مل سکتی ہے ۔

سٹریم میں فنکشنل انٹرفیس

جیسا کہ اوپر بحث کی گئی ہے، یہ فنکشنل انٹرفیس مضبوطی سے Stream API کے ساتھ جوڑے گئے ہیں۔ کیسے، آپ پوچھتے ہیں؟ جاوا میں فنکشنل انٹرفیس - 3اور اس طرح کہ بہت سے طریقے Streamخاص طور پر ان فنکشنل انٹرفیس کے ساتھ کام کرتے ہیں۔ آئیے دیکھتے ہیں کہ فنکشنل انٹرفیس کو کس طرح استعمال کیا جا سکتا ہے Stream۔

Predicate کے ساتھ طریقہ

مثال کے طور پر، کلاس کا طریقہ لیں 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ایک طریقہ ہے 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
یعنی ہمارے ہر ایک عنصر کو خود سے ضرب دیا جاتا ہے، اور اسی طرح پہلے چار نمبروں کے لیے۔ جاوا میں فنکشنل انٹرفیس - 4بس! یہ بہت اچھا ہوگا اگر اس مضمون کو پڑھنے کے بعد آپ جاوا میں اسٹریم API کو سمجھنے اور اس میں مہارت حاصل کرنے کے ایک قدم کے قریب ہوں!
تبصرے
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION