Java Language
लैंबडा एक्सप्रेशन
खोज…
परिचय
लैंबडा एक्सप्रेशन एक अभिव्यक्ति का उपयोग करके एकल-विधि इंटरफ़ेस को लागू करने का एक स्पष्ट और संक्षिप्त तरीका प्रदान करते हैं। वे आपको कोड बनाने और बनाए रखने की मात्रा को कम करने की अनुमति देते हैं। जबकि अनाम कक्षाओं के समान, उनके पास स्वयं के द्वारा कोई प्रकार की जानकारी नहीं है। टाइप इंट्रेंस होने की जरूरत है।
विधि संदर्भ अभिव्यक्ति के बजाय मौजूदा तरीकों का उपयोग करके कार्यात्मक इंटरफेस को लागू करते हैं। वे लंबोदर परिवार के भी हैं।
वाक्य - विन्यास
- () -> {वापसी अभिव्यक्ति; } // मान वापस करने के लिए फ़ंक्शन बॉडी के साथ शून्य-एरिटी।
- () -> अभिव्यक्ति // उपरोक्त घोषणा के लिए आशुलिपि; अभिव्यक्ति के लिए कोई अर्धविराम नहीं है।
- () -> {फंक्शन-बॉडी} // ऑपरेशन करने के लिए लैम्ब्डा एक्सप्रेशन में साइड-इफेक्ट।
- पैरामीटरनाम -> अभिव्यक्ति // वन-एरिटी लैम्ब्डा अभिव्यक्ति। केवल एक तर्क के साथ लैम्ब्डा अभिव्यक्तियों में, कोष्ठक हटाया जा सकता है।
- (टाइप पैरामीटरनाम, टाइप सेकंडपैरनाम, ...) -> एक्सप्रेशन // लैम्ब्डा बाईं ओर सूचीबद्ध मापदंडों के साथ एक अभिव्यक्ति का मूल्यांकन
- (पैरामीटरनाम, सेकंडपैरमीटरनाम, ...) -> एक्सप्रेशन // शॉर्टहैंड जो पैरामीटर नामों के लिए पैरामीटर प्रकार को हटा देता है। केवल उन संदर्भों में उपयोग किया जा सकता है जो कंपाइलर द्वारा अनुमान लगाया जा सकता है जहां दिए गए पैरामीटर सूची का आकार अपेक्षित कार्यात्मक इंटरफेस के आकार के एक (और केवल एक) से मेल खाता है।
एक संग्रह को सॉर्ट करने के लिए लैम्ब्डा एक्सप्रेशंस का उपयोग करना
सूचियों को क्रमबद्ध करना
Java 8 से पहले, java.util.Comparator
इंटरफ़ेस को एक अनाम (या नामित) वर्ग के साथ लागू करना आवश्यक था जब एक सूची को क्रमबद्ध किया जा रहा हो 1 :
List<Person> people = ...
Collections.sort(
people,
new Comparator<Person>() {
public int compare(Person p1, Person p2){
return p1.getFirstName().compareTo(p2.getFirstName());
}
}
);
जावा 8 से शुरू होकर अनाम वर्ग को एक लंबोदर अभिव्यक्ति के साथ बदल दिया जा सकता है। ध्यान दें कि पैरामीटर्स p1
और p2
के प्रकारों को छोड़ा जा सकता है, क्योंकि कंपाइलर उन्हें स्वचालित रूप से अनुमान लगाएगा:
Collections.sort(
people,
(p1, p2) -> p1.getFirstName().compareTo(p2.getFirstName())
);
उदाहरण का उपयोग करके सरल किया जा सकता Comparator.comparing
और विधि संदर्भ का उपयोग करते हुए व्यक्त ::
(डबल कोलन) प्रतीक।
Collections.sort(
people,
Comparator.comparing(Person::getFirstName)
);
एक स्थैतिक आयात हमें इसे अधिक स्पष्ट रूप से व्यक्त करने की अनुमति देता है, लेकिन यह बहस योग्य है कि क्या यह समग्र पठनीयता में सुधार करता है:
import static java.util.Collections.sort;
import static java.util.Comparator.comparing;
//...
sort(people, comparing(Person::getFirstName));
इस तरह से बनाए गए कंपैटिवर्स को एक साथ जंजीर भी बनाया जा सकता है। उदाहरण के लिए, लोगों की उनके पहले नाम से तुलना करने के बाद, यदि उसी पहले नाम वाले लोग हैं, तो अंतिम नाम के साथ thenComparing
विधि
sort(people, comparing(Person::getFirstName).thenComparing(Person::getLastName));
1 - ध्यान दें कि Collection.sort (...) केवल उन संग्रहों पर काम करता है जो List
उपप्रकार हैं। Set
और Collection
एपीआई तत्वों के किसी भी आदेश का मतलब नहीं है।
नक्शे की छंटाई
आप समान तरीके से HashMap
की प्रविष्टियों को मूल्य के आधार पर क्रमित कर सकते हैं। (ध्यान दें कि एक LinkedHashMap
को लक्ष्य के रूप में उपयोग किया जाना चाहिए। एक साधारण HashMap
में चाबियाँ LinkedHashMap
हैं।)
Map<String, Integer> map = new HashMap(); // ... or any other Map class
// populate the map
map = map.entrySet()
.stream()
.sorted(Map.Entry.<String, Integer>comparingByValue())
.collect(Collectors.toMap(k -> k.getKey(), v -> v.getValue(),
(k, v) -> k, LinkedHashMap::new));
जावा लैम्ब्डा का परिचय
कार्यात्मक इंटरफेस
लैम्ब्डा केवल एक कार्यात्मक इंटरफ़ेस पर काम कर सकता है, जो कि केवल एक सार पद्धति वाला इंटरफ़ेस है। कार्यात्मक इंटरफ़ेस में default
या static
विधियों की संख्या हो सकती है। (इस कारण से, उन्हें कभी-कभी एकल सार विधि इंटरफेस या एसएएम इंटरफेस के रूप में संदर्भित किया जाता है)।
interface Foo1 {
void bar();
}
interface Foo2 {
int bar(boolean baz);
}
interface Foo3 {
String bar(Object baz, int mink);
}
interface Foo4 {
default String bar() { // default so not counted
return "baz";
}
void quux();
}
एक कार्यात्मक इंटरफ़ेस की घोषणा करते समय @FunctionalInterface
एनोटेशन जोड़ा जा सकता है। इसका कोई विशेष प्रभाव नहीं है, लेकिन एक संकलक त्रुटि उत्पन्न होगी यदि यह एनोटेशन एक इंटरफ़ेस पर लागू होता है जो कार्यात्मक नहीं है, इस प्रकार एक अनुस्मारक के रूप में कार्य करता है कि इंटरफ़ेस को बदला नहीं जाना चाहिए।
@FunctionalInterface
interface Foo5 {
void bar();
}
@FunctionalInterface
interface BlankFoo1 extends Foo3 { // inherits abstract method from Foo3
}
@FunctionalInterface
interface Foo6 {
void bar();
boolean equals(Object obj); // overrides one of Object's method so not counted
}
इसके विपरीत, यह एक कार्यात्मक इंटरफ़ेस नहीं है, क्योंकि इसमें एक से अधिक सार विधि हैं:
interface BadFoo {
void bar();
void quux(); // <-- Second method prevents lambda: which one should
// be considered as lambda?
}
यह भी एक कार्यात्मक इंटरफ़ेस नहीं है, क्योंकि इसमें कोई विधियाँ नहीं हैं:
interface BlankFoo2 { }
निम्नलिखित पर ध्यान दें। मान लो तुम्हारे पास है
interface Parent { public int parentMethod(); }
तथा
interface Child extends Parent { public int ChildMethod(); }
तब Child
एक कार्यात्मक इंटरफ़ेस नहीं हो सकता क्योंकि इसमें दो निर्दिष्ट विधियाँ हैं।
Java 8 पैकेज java.util.function
में कई सामान्य टेम्पर्ड फंक्शनल इंटरफेस भी प्रदान करता है। उदाहरण के लिए, अंतर्निहित इंटरफ़ेस Predicate<T>
एक एकल विधि लपेटता है जो टाइप T
मान को इनपुट करता है और एक boolean
देता है।
लैंबडा एक्सप्रेशन
एक लैम्ब्डा अभिव्यक्ति की मूल संरचना है:
fi
तब एक अनाम वर्ग के समान एक वर्ग का एक एकल उदाहरण प्रस्तुत करेगा, जो FunctionalInterface
लागू होता है और जहां एक विधि की परिभाषा { System.out.println("Hello"); }
। दूसरे शब्दों में, ऊपर ज्यादातर समान है:
FunctionalInterface fi = new FunctionalInterface() {
@Override
public void theOneMethod() {
System.out.println("Hello");
}
};
लैम्ब्डा केवल "ज्यादातर समतुल्य" है अनाम वर्ग के लिए क्योंकि एक लैम्ब्डा में, this
तरह के अभिव्यक्तियों का अर्थ, super
या toString()
उस वर्ग को संदर्भित करता है जिसके भीतर असाइनमेंट होता है, न कि नई बनाई गई वस्तु।
मेमने का उपयोग करते समय आप विधि का नाम निर्दिष्ट नहीं कर सकते - लेकिन आपको इसकी आवश्यकता नहीं होनी चाहिए, क्योंकि एक कार्यात्मक इंटरफ़ेस में केवल एक सार विधि होनी चाहिए, इसलिए जावा उस एक को ओवरराइड करता है।
ऐसे मामलों में जहां लैम्ब्डा का प्रकार निश्चित नहीं है, (जैसे अतिभारित तरीके) आप कंपाइलर को यह बताने के लिए एक कास्ट जोड़ सकते हैं कि उसका टाइप क्या होना चाहिए, जैसे:
Object fooHolder = (Foo1) () -> System.out.println("Hello");
System.out.println(fooHolder instanceof Foo1); // returns true
यदि कार्यात्मक इंटरफ़ेस की एकल विधि पैरामीटर लेती है, तो इन के स्थानीय औपचारिक नाम लंबो के कोष्ठक के बीच दिखाई देने चाहिए। पैरामीटर के प्रकार को घोषित करने या लौटने की कोई आवश्यकता नहीं है क्योंकि ये इंटरफ़ेस से लिए गए हैं (हालांकि अगर आप चाहते हैं तो पैरामीटर प्रकार घोषित करना कोई त्रुटि नहीं है)। इस प्रकार, ये दो उदाहरण समतुल्य हैं:
Foo2 longFoo = new Foo2() {
@Override
public int bar(boolean baz) {
return baz ? 1 : 0;
}
};
Foo2 shortFoo = (x) -> { return x ? 1 : 0; };
यदि फ़ंक्शन में केवल एक तर्क है, तो तर्क के आसपास के कोष्ठकों को छोड़ा जा सकता है:
Foo2 np = x -> { return x ? 1 : 0; }; // okay
Foo3 np2 = x, y -> x.toString() + y // not okay
निहित प्रतिफल
यदि एक लैम्बडा के अंदर रखा गया कोड एक स्टेटमेंट के बजाय एक जावा एक्सप्रेशन है , तो इसे एक ऐसे तरीके के रूप में माना जाता है, जो एक्सप्रेशन का मान लौटाता है। इस प्रकार, निम्नलिखित दो समतुल्य हैं:
IntUnaryOperator addOneShort = (x) -> (x + 1);
IntUnaryOperator addOneLong = (x) -> { return (x + 1); };
स्थानीय चर पर पहुँच (मूल्य बंद)
चूँकि लंबोदा अनाम वर्गों के लिए वाक्य-विन्यास शॉर्टहैंड हैं, इसलिए वे एन्कोलिंग क्षेत्र में स्थानीय चर तक पहुँचने के लिए समान नियमों का पालन करते हैं; चर को final
माना जाना चाहिए और लैम्ब्डा के अंदर संशोधित नहीं किया जाना चाहिए।
IntUnaryOperator makeAdder(int amount) {
return (x) -> (x + amount); // Legal even though amount will go out of scope
// because amount is not modified
}
IntUnaryOperator makeAccumulator(int value) {
return (x) -> { value += x; return value; }; // Will not compile
}
यदि इस तरह से बदलते चर को लपेटना आवश्यक है, तो चर की एक प्रति रखने वाली एक नियमित वस्तु का उपयोग किया जाना चाहिए। लंबोदर भाव के साथ जावा क्लोजर में और पढ़ें ।
लंबोदर को स्वीकार करना
क्योंकि एक लैम्ब्डा एक इंटरफ़ेस का कार्यान्वयन है, एक लैम्बडा को स्वीकार करने के लिए एक विशेष विधि को करने के लिए कुछ विशेष करने की आवश्यकता नहीं है: कोई भी फ़ंक्शन जो एक कार्यात्मक इंटरफ़ेस लेता है, लैम्बडा को भी स्वीकार कर सकता है।
public void passMeALambda(Foo1 f) {
f.bar();
}
passMeALambda(() -> System.out.println("Lambda called"));
एक लैम्ब्डा अभिव्यक्ति का प्रकार
एक लंबोदर अभिव्यक्ति, अपने आप में, एक विशिष्ट प्रकार नहीं है। हालांकि यह सही है कि रिटर्न मान के प्रकार के साथ मापदंडों के प्रकार और संख्या, कुछ प्रकार की जानकारी दे सकते हैं, ऐसी जानकारी केवल यह बताएगी कि इसे किस प्रकार के लिए सौंपा जा सकता है। लैम्ब्डा एक प्रकार प्राप्त करता है जब इसे निम्नलिखित तरीकों में से एक में कार्यात्मक इंटरफ़ेस प्रकार को सौंपा जाता है:
- एक कार्यात्मक प्रकार के लिए प्रत्यक्ष असाइनमेंट, जैसे
myPredicate = s -> s.isEmpty()
- इसे एक पैरामीटर के रूप में पास करना, जिसमें एक कार्यात्मक प्रकार है, जैसे
stream.filter(s -> s.isEmpty())
- कार्यात्मक प्रकार लौटाने वाले फ़ंक्शन से इसे वापस करना, जैसे
return s -> s.isEmpty()
- इसे एक कार्यात्मक प्रकार के लिए कास्टिंग, उदाहरण के लिए
(Predicate<String>) s -> s.isEmpty()
जब तक एक कार्यात्मक प्रकार के लिए कोई असाइनमेंट नहीं किया जाता है, लैम्बडा का एक निश्चित प्रकार नहीं होता है। वर्णन करने के लिए, लैम्ब्डा अभिव्यक्ति o -> o.isEmpty()
। एक ही लैम्ब्डा अभिव्यक्ति को कई अलग-अलग कार्यात्मक प्रकारों को सौंपा जा सकता है:
Predicate<String> javaStringPred = o -> o.isEmpty();
Function<String, Boolean> javaFunc = o -> o.isEmpty();
Predicate<List> javaListPred = o -> o.isEmpty();
Consumer<String> javaStringConsumer = o -> o.isEmpty(); // return value is ignored!
com.google.common.base.Predicate<String> guavaPredicate = o -> o.isEmpty();
अब जब उन्हें सौंपा गया है, तो दिखाए गए उदाहरण पूरी तरह से अलग-अलग प्रकार के हैं, भले ही लैम्ब्डा के भाव समान दिखते हों, और उन्हें एक-दूसरे को नहीं सौंपा जा सकता है।
विधि संदर्भ
विधि संदर्भ पूर्वनिर्धारित स्थिर या उदाहरण के तरीके की अनुमति देते हैं जो एक संगत कार्यात्मक इंटरफ़ेस का पालन करते हैं जो एक अनाम लंबोदर अभिव्यक्ति के बजाय तर्क के रूप में पारित किया जाता है।
मान लें कि हमारे पास एक मॉडल है:
class Person {
private final String name;
private final String surname;
public Person(String name, String surname){
this.name = name;
this.surname = surname;
}
public String getName(){ return name; }
public String getSurname(){ return surname; }
}
List<Person> people = getSomePeople();
उदाहरण विधि संदर्भ (एक मनमाने उदाहरण के लिए)
people.stream().map(Person::getName)
समतुल्य मेमना:
people.stream().map(person -> person.getName())
इस उदाहरण में, प्रकार Person
के उदाहरण विधि getName()
का एक संदर्भ दिया जा रहा है। चूंकि यह संग्रह प्रकार के रूप में जाना जाता है, उदाहरण पर विधि (बाद में ज्ञात) को लागू किया जाएगा।
इंस्टेंस विधि संदर्भ (एक विशिष्ट उदाहरण के लिए)
people.forEach(System.out::println);
चूंकि System.out
PrintStream
का एक उदाहरण है, इस विशिष्ट उदाहरण के लिए एक विधि संदर्भ को एक तर्क के रूप में पारित किया जा रहा है।
समतुल्य मेमना:
people.forEach(person -> System.out.println(person));
स्टेटिक विधि संदर्भ
धाराओं को बदलने के लिए भी हम स्थैतिक विधियों के संदर्भ लागू कर सकते हैं:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
numbers.stream().map(String::valueOf)
यह उदाहरण String
प्रकार पर स्थैतिक valueOf()
संदर्भ valueOf()
विधि का संदर्भ देता है। इसलिए, संग्रह में उदाहरण ऑब्जेक्ट को valueOf()
रूप में पारित किया जाता है।
समतुल्य मेमना:
numbers.stream().map(num -> String.valueOf(num))
एक निर्माणकर्ता का संदर्भ
List<String> strings = Arrays.asList("1", "2", "3");
strings.stream().map(Integer::new)
संग्रह में तत्वों के संग्रह को देखने के लिए संग्रह में एक स्ट्रीम के तत्वों को पढ़ें।
Integer
स्ट्रिंग स्ट्रिंग तर्क निर्माता का उपयोग यहाँ किया जा रहा है, एक पूर्णांक बनाने के लिए दिए गए स्ट्रिंग को तर्क के रूप में दिया गया है। इस स्थिति में, जब तक स्ट्रिंग एक संख्या का प्रतिनिधित्व करती है, तब तक स्ट्रीम को इंटेगर में मैप किया जाएगा। समतुल्य मेमना:
strings.stream().map(s -> new Integer(s));
प्रवंचक पत्रक
विधि संदर्भ प्रारूप | कोड | समतुल्य लंबोदर |
---|---|---|
स्थैतिक विधि | TypeName::method | (args) -> TypeName.method(args) |
गैर-स्थैतिक विधि (उदाहरण के लिए * ) | instance::method | (args) -> instance.method(args) |
गैर-स्थैतिक विधि (कोई उदाहरण नहीं) | TypeName::method | (instance, args) -> instance.method(args) |
निर्माता ** | TypeName::new | (args) -> new TypeName(args) |
अर्रे कंस्ट्रक्टर | TypeName[]::new | (int size) -> new TypeName[size] |
* instance
कोई भी instance
हो सकता है जो उदाहरण के संदर्भ में मूल्यांकन करता है, जैसे getInstance()::method
, this::method
** यदि TypeName
एक गैर-स्थिर आंतरिक वर्ग है, तो निर्माणकर्ता संदर्भ केवल बाहरी वर्ग के उदाहरण के दायरे में मान्य है
कई इंटरफेस को लागू करना
कभी-कभी आप एक से अधिक इंटरफ़ेस को लागू करने के लिए एक लंबोदर अभिव्यक्ति चाहते हो सकते हैं। यह ज्यादातर मार्कर इंटरफेस (जैसे java.io.Serializable ) के साथ उपयोगी है क्योंकि वे सार तरीके नहीं जोड़ते हैं।
उदाहरण के लिए, यदि आप एक बनाना चाहते हैं TreeSet
एक कस्टम के साथ Comparator
और फिर इसे क्रमानुसार और नेटवर्क पर भेज सकते हैं। तुच्छ दृष्टिकोण:
TreeSet<Long> ts = new TreeSet<>((x, y) -> Long.compare(y, x));
काम नहीं करता है क्योंकि तुलनित्र के लिए लैम्ब्डा Serializable
लागू नहीं करता है। आप इसे चौराहे के प्रकारों का उपयोग करके ठीक कर सकते हैं और स्पष्ट रूप से निर्दिष्ट कर सकते हैं कि इस लंबो को क्रमबद्ध होने की आवश्यकता है:
TreeSet<Long> ts = new TreeSet<>(
(Comparator<Long> & Serializable) (x, y) -> Long.compare(y, x));
यदि आप अक्सर चौराहे के प्रकारों का उपयोग कर रहे हैं (उदाहरण के लिए, यदि आप एक रूपरेखा का उपयोग कर रहे हैं जैसे कि अपाचे स्पार्क जहां लगभग सब कुछ धारावाहिक होना है), तो आप खाली इंटरफेस बना सकते हैं और उन्हें अपने कोड में उपयोग कर सकते हैं:
public interface SerializableComparator extends Comparator<Long>, Serializable {}
public class CustomTreeSet {
public CustomTreeSet(SerializableComparator comparator) {}
}
इस तरह आप गारंटी देते हैं कि उत्तीर्ण तुलनित्र क्रमबद्ध होगा।
लैम्ब्डा और एक्सेक्यूट-अराउंड पैटर्न
साधारण परिदृश्यों में कार्यात्मक के रूप में लैम्ब्डा का उपयोग करने के कई अच्छे उदाहरण हैं। एक काफी सामान्य उपयोग का मामला जिसे लैम्ब्डा द्वारा सुधारा जा सकता है, जिसे एक्स्यूट-अराउंड पैटर्न कहा जाता है। इस पैटर्न में, आपके पास मानक सेटअप / अश्रु कोड का एक सेट है जो उपयोग किए जाने वाले केस विशिष्ट कोड के आसपास के कई परिदृश्यों के लिए आवश्यक है। इसका कुछ सामान्य उदाहरण फ़ाइल io, डेटाबेस io, try / catch ब्लॉक हैं।
interface DataProcessor {
void process( Connection connection ) throws SQLException;;
}
public void doProcessing( DataProcessor processor ) throws SQLException{
try (Connection connection = DBUtil.getDatabaseConnection();) {
processor.process(connection);
connection.commit();
}
}
फिर इस विधि को एक लंबोदर के साथ कॉल करने के लिए ऐसा लग सकता है:
public static void updateMyDAO(MyVO vo) throws DatabaseException {
doProcessing((Connection conn) -> MyDAO.update(conn, ObjectMapper.map(vo)));
}
यह I / O संचालन तक सीमित नहीं है। यह किसी भी परिदृश्य पर लागू हो सकता है जहाँ समान सेटअप / कार्य आंसू कार्य मामूली बदलाव के साथ लागू होते हैं। इस पैटर्न का मुख्य लाभ कोड री-यूज़ और एनफोर्सिंग डीआरवाई (डोंट रिपीट योरसेल्फ) है।
अपने स्वयं के कार्यात्मक इंटरफ़ेस के साथ लैम्ब्डा अभिव्यक्ति का उपयोग करना
लैंबडास एकल विधि इंटरफेस के लिए इनलाइन कार्यान्वयन कोड प्रदान करने के लिए है और उन्हें पास करने की क्षमता है जैसा कि हम सामान्य चर के साथ कर रहे हैं। हम उन्हें कार्यात्मक इंटरफ़ेस कहते हैं।
उदाहरण के लिए, अनाम कक्षा में एक रननेबल लिखना और थ्रेड शुरू करना इस तरह दिखता है:
//Old way
new Thread(
new Runnable(){
public void run(){
System.out.println("run logic...");
}
}
).start();
//lambdas, from Java 8
new Thread(
()-> System.out.println("run logic...")
).start();
अब, ऊपर की पंक्ति के अनुसार, आप कुछ कस्टम इंटरफ़ेस कहते हैं:
interface TwoArgInterface {
int operate(int a, int b);
}
अपने कोड में इस इंटरफ़ेस के कार्यान्वयन को देने के लिए आप लैम्ब्डा का उपयोग कैसे करते हैं? ऊपर दिखाए गए के रूप में एक ही चलने योग्य उदाहरण है। नीचे ड्राइवर प्रोग्राम देखें:
public class CustomLambda {
public static void main(String[] args) {
TwoArgInterface plusOperation = (a, b) -> a + b;
TwoArgInterface divideOperation = (a,b)->{
if (b==0) throw new IllegalArgumentException("Divisor can not be 0");
return a/b;
};
System.out.println("Plus operation of 3 and 5 is: " + plusOperation.operate(3, 5));
System.out.println("Divide operation 50 by 25 is: " + divideOperation.operate(50, 25));
}
}
`वापसी` केवल लंबोदर से लौटती है, बाहरी विधि से नहीं
return
विधि केवल लंबोदर से लौटती है, बाहरी विधि से नहीं।
खबरदार कि यह स्काला और कोटलिन से अलग है!
void threeTimes(IntConsumer r) {
for (int i = 0; i < 3; i++) {
r.accept(i);
}
}
void demo() {
threeTimes(i -> {
System.out.println(i);
return; // Return from lambda to threeTimes only!
});
}
जब भाषा निर्माण स्वयं लिखने का प्रयास करते हैं, तो यह अप्रत्याशित व्यवहार का कारण बन सकता है, क्योंकि बिलियन कंस्ट्रक्शन जैसे कि लूप्स return
for
अलग व्यवहार होता है:
void demo2() {
for (int i = 0; i < 3; i++) {
System.out.println(i);
return; // Return from 'demo2' entirely
}
}
स्काला और demo2
, demo
और demo
दोनों केवल 0
प्रिंट करेंगे। लेकिन यह अधिक सुसंगत नहीं है । जावा दृष्टिकोण रीफैक्टरिंग और कक्षाओं के उपयोग के अनुरूप है - शीर्ष पर कोड में return
, और नीचे दिए गए कोड को व्यवहार करने के लिए निम्न हैं:
void demo3() {
threeTimes(new MyIntConsumer());
}
class MyIntConsumer implements IntConsumer {
public void accept(int i) {
System.out.println(i);
return;
}
}
इसलिए, जावा return
वर्ग के तरीकों और पुनर्रचना के साथ और अधिक सुसंगत है, लेकिन कम से for
और while
builtins, इन विशेष रहते हैं।
इस वजह से, निम्नलिखित दो जावा में बराबर हैं:
IntStream.range(1, 4)
.map(x -> x * x)
.forEach(System.out::println);
IntStream.range(1, 4)
.map(x -> { return x * x; })
.forEach(System.out::println);
इसके अलावा, संसाधनों के साथ-साथ कोशिश का उपयोग जावा में सुरक्षित है:
class Resource implements AutoCloseable {
public void close() { System.out.println("close()"); }
}
void executeAround(Consumer<Resource> f) {
try (Resource r = new Resource()) {
System.out.print("before ");
f.accept(r);
System.out.print("after ");
}
}
void demo4() {
executeAround(r -> {
System.out.print("accept() ");
return; // Does not return from demo4, but frees the resource.
});
}
before accept() after close()
करने before accept() after close()
प्रिंट before accept() after close()
। स्काला और कोटलिन शब्दार्थ में, कोशिश के साथ-संसाधनों को बंद नहीं किया जाएगा, लेकिन यह केवल before accept()
प्रिंट होगा before accept()
।
लैम्ब्डा एक्सप्रेशन के साथ जावा क्लोजर।
एक लैम्ब्डा क्लोजर तब बनता है जब एक लैम्ब्डा अभिव्यक्ति एक एनक्लोजिंग स्कोप (वैश्विक या स्थानीय) के चर का संदर्भ देता है। ऐसा करने के नियम इनलाइन विधियों और अनाम कक्षाओं के लिए समान हैं।
एक लैम्बडा के भीतर उपयोग किए जाने वाले एनक्लोज़िंग स्कोप से स्थानीय चर को final
होना चाहिए। जावा 8 के साथ (जल्द से जल्द संस्करण जो लैम्ब्डा का समर्थन करता है), उन्हें बाहरी संदर्भ में final
घोषित किए जाने की आवश्यकता नहीं है, लेकिन इस तरह से व्यवहार किया जाना चाहिए। उदाहरण के लिए:
int n = 0; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
int i = n;
// do something
};
यह तब तक कानूनी है जब तक n
चर का मान नहीं बदला जाता है। यदि आप लैम्बडा के अंदर या बाहर परिवर्तनशील को बदलने का प्रयास करते हैं, तो आपको निम्नलिखित संकलन त्रुटि मिलेगी:
"लंबोदर अभिव्यक्ति से संदर्भित स्थानीय चर अंतिम या प्रभावी रूप से अंतिम होने चाहिए"।
उदाहरण के लिए:
int n = 0;
Runnable r = () -> { // Using lambda
int i = n;
// do something
};
n++; // Will generate an error.
यदि लंबोदा के भीतर बदलते चर का उपयोग करना आवश्यक है, तो सामान्य दृष्टिकोण चर की final
प्रति घोषित करना और प्रतिलिपि का उपयोग करना है। उदाहरण के लिए
int n = 0;
final int k = n; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
int i = k;
// do something
};
n++; // Now will not generate an error
r.run(); // Will run with i = 0 because k was 0 when the lambda was created
स्वाभाविक रूप से, लैम्ब्डा का शरीर मूल चर में परिवर्तन नहीं देखता है।
ध्यान दें कि जावा सही क्लोजर का समर्थन नहीं करता है। एक जावा लैम्ब्डा को इस तरह से नहीं बनाया जा सकता है जो इसे उस वातावरण में परिवर्तन देखने की अनुमति देता है जिसमें यह त्वरित था। यदि आप किसी ऐसे क्लोजर को लागू करना चाहते हैं जो अपने वातावरण को देखता है या उसमें बदलाव करता है, तो आपको इसे नियमित कक्षा का उपयोग करके अनुकरण करना चाहिए। उदाहरण के लिए:
// Does not compile ...
public IntUnaryOperator createAccumulator() {
int value = 0;
IntUnaryOperator accumulate = (x) -> { value += x; return value; };
return accumulate;
}
उपरोक्त उदाहरण पहले चर्चा किए गए कारणों के लिए संकलन नहीं करेगा। हम संकलन त्रुटि के आसपास काम कर सकते हैं:
// Compiles, but is incorrect ...
public class AccumulatorGenerator {
private int value = 0;
public IntUnaryOperator createAccumulator() {
IntUnaryOperator accumulate = (x) -> { value += x; return value; };
return accumulate;
}
}
समस्या यह है कि यह IntUnaryOperator
इंटरफ़ेस के लिए डिज़ाइन अनुबंध को IntUnaryOperator
है जो बताता है कि उदाहरण कार्यात्मक और स्टेटलेस होना चाहिए। यदि इस तरह के एक बंद को अंतर्निहित कार्यों के लिए पारित किया जाता है जो कार्यात्मक वस्तुओं को स्वीकार करते हैं, तो क्रैश या गलत व्यवहार का कारण होता है। उत्परिवर्ती राज्य को घेरने वाले क्लोजर को नियमित कक्षाओं के रूप में लागू किया जाना चाहिए। उदाहरण के लिए।
// Correct ...
public class Accumulator {
private int value = 0;
public int accumulate(int x) {
value += x;
return value;
}
}
लैम्ब्डा - श्रोता उदाहरण
अनाम वर्ग श्रोता
जावा 8 से पहले, यह बहुत सामान्य है कि एक अनाम वर्ग JButton के क्लिक इवेंट को संभालने के लिए उपयोग किया जाता है, जैसा कि निम्नलिखित कोड में दिखाया गया है। यह उदाहरण दिखाता है कि कैसे एक गुमनाम श्रोता को btn.addActionListener
के दायरे में btn.addActionListener
।
JButton btn = new JButton("My Button");
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Button was pressed");
}
});
लंबोदर श्रोता
क्योंकि ActionListener
इंटरफ़ेस केवल एक विधि actionPerformed()
को परिभाषित करता है, यह एक कार्यात्मक इंटरफ़ेस है जिसका अर्थ है कि बॉयलरप्लेट कोड को बदलने के लिए लैम्ब्डा अभिव्यक्ति का उपयोग करने के लिए एक जगह है। उपरोक्त उदाहरण लंबोदर भावों का उपयोग करते हुए फिर से लिखा जा सकता है:
JButton btn = new JButton("My Button");
btn.addActionListener(e -> {
System.out.println("Button was pressed");
});
पारंपरिक शैली में लैम्बडा शैली
पारंपरिक तरीका
interface MathOperation{
boolean unaryOperation(int num);
}
public class LambdaTry {
public static void main(String[] args) {
MathOperation isEven = new MathOperation() {
@Override
public boolean unaryOperation(int num) {
return num%2 == 0;
}
};
System.out.println(isEven.unaryOperation(25));
System.out.println(isEven.unaryOperation(20));
}
}
लैंबडा स्टाइल
- वर्ग नाम और कार्यात्मक इंटरफ़ेस निकाय निकालें।
public class LambdaTry {
public static void main(String[] args) {
MathOperation isEven = (int num) -> {
return num%2 == 0;
};
System.out.println(isEven.unaryOperation(25));
System.out.println(isEven.unaryOperation(20));
}
}
- वैकल्पिक प्रकार की घोषणा
MathOperation isEven = (num) -> {
return num%2 == 0;
};
- पैरामीटर के आसपास वैकल्पिक कोष्ठक, अगर यह एकल पैरामीटर है
MathOperation isEven = num -> {
return num%2 == 0;
};
- वैकल्पिक घुंघराले ब्रेसिज़, यदि फ़ंक्शन बॉडी में केवल एक पंक्ति है
- वैकल्पिक रिटर्न कीवर्ड, यदि फ़ंक्शन बॉडी में केवल एक पंक्ति है
MathOperation isEven = num -> num%2 == 0;
लम्बदा और स्मृति उपयोग
चूंकि जावा लैम्ब्डा बंद हैं, वे एन्कोडिंग लेक्सिकल स्कोप में चर के मूल्यों को "कैप्चर" कर सकते हैं। जबकि सभी लैम्ब्डा किसी भी चीज पर कब्जा नहीं करते हैं - सरल लैम्ब्डा जैसे s -> s.length()
कुछ भी कैप्चर करते हैं और स्टेटलेस कहलाते हैं - लैम्ब्डा कैप्चर करने के लिए कैप्चर किए गए वेरिएबल को रखने के लिए एक अस्थायी ऑब्जेक्ट की आवश्यकता होती है। इस कोड स्निपेट में, लैम्ब्डा () -> j
एक कैप्चरिंग लैम्ब्डा है, और इसका मूल्यांकन होने पर किसी ऑब्जेक्ट को आवंटित किया जा सकता है:
public static void main(String[] args) throws Exception {
for (int i = 0; i < 1000000000; i++) {
int j = i;
doSomethingWithLambda(() -> j);
}
}
हालाँकि यह तुरंत स्पष्ट नहीं हो सकता है क्योंकि new
कीवर्ड स्निपेट में कहीं भी दिखाई नहीं देता है, यह कोड () -> j
lambda अभिव्यक्ति के उदाहरणों का प्रतिनिधित्व करने के लिए 1,000,000,000 अलग-अलग ऑब्जेक्ट बनाने के लिए उत्तरदायी है। हालांकि, यह भी ध्यान दिया जाना चाहिए कि जावा 1 के भविष्य के संस्करणों को यह अनुकूलन करने में सक्षम हो सकता है ताकि रनटाइम पर लैम्ब्डा उदाहरणों का पुन: उपयोग किया गया, या किसी अन्य तरीके से प्रतिनिधित्व किया गया।
1 - उदाहरण के लिए, जावा 9 जावा निर्माण अनुक्रम के लिए एक वैकल्पिक "लिंक" चरण का परिचय देता है जो इस तरह से वैश्विक अनुकूलन करने का अवसर प्रदान करेगा।
एक सूची से एक निश्चित मूल्य (ओं) को प्राप्त करने के लिए लैम्ब्डा के भावों और भविष्यवाणी का उपयोग करना
Java 8 से शुरू होकर, आप लैम्ब्डा एक्सप्रेशन और विधेय का उपयोग कर सकते हैं।
उदाहरण: एक सूची से एक निश्चित मूल्य प्राप्त करने के लिए एक लैम्ब्डा अभिव्यक्तियों और एक विधेय का उपयोग करें। इस उदाहरण में हर व्यक्ति को इस तथ्य के साथ मुद्रित किया जाएगा कि वे 18 वर्ष के हैं या नहीं।
व्यक्ति वर्ग:
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() { return age; }
public String getName() { return name; }
}
अंतर्निहित इंटरफ़ेस java.util.function.Predicate पैकेज से Predicate है जो boolean test(T t)
विधि के साथ एक कार्यात्मक इंटरफ़ेस है।
उदाहरण उपयोग:
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class LambdaExample {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Jeroen", 20));
personList.add(new Person("Jack", 5));
personList.add(new Person("Lisa", 19));
print(personList, p -> p.getAge() >= 18);
}
private static void print(List<Person> personList, Predicate<Person> checker) {
for (Person person : personList) {
if (checker.test(person)) {
System.out.print(person + " matches your expression.");
} else {
System.out.println(person + " doesn't match your expression.");
}
}
}
}
print(personList, p -> p.getAge() >= 18);
विधि एक लैम्ब्डा अभिव्यक्ति लेती है (क्योंकि प्रेडिकेट एक पैरामीटर का उपयोग किया जाता है) जहां आप उस अभिव्यक्ति को परिभाषित कर सकते हैं जिसकी आवश्यकता है। चेकर की परीक्षा विधि जाँचती है कि यह अभिव्यक्ति सही है या नहीं: checker.test(person)
।
आप इसे आसानी से किसी और चीज़ में बदल सकते हैं, उदाहरण के लिए print(personList, p -> p.getName().startsWith("J"));
। यह जाँच करेगा कि क्या व्यक्ति का नाम "J" से शुरू होता है।