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