- 19 सितंबर 2023 को रिलीज़ हुए Java 21 ने record patterns और switch pattern matching के ज़रिए Kotlin, Rust और C# के करीब functional pattern expressions को Java के भीतर ला दिया
- Java 14 के switch expressions, Java 16 के records और
instanceof pattern matching, और Java 17 की sealed classes के जुड़ते जाने से algebraic data types को संभालने की बुनियाद Java 21 में एक साथ फिट होती है
- records final, immutable references और standardized getters जैसी सीमाओं के साथ data को भरोसेमंद ढंग से destructure करने देते हैं, और record pattern nested data को switch में सीधे निकालने देता है
- sealed classes/interfaces केवल अनुमत subtypes को खोलकर sum type के करीब model बनाते हैं, और sealed interface व record को साथ इस्तेमाल करने पर RGB, CMYK, YUV, HSL जैसे variants को सीमित किया जा सकता है
- Java 21 का switch
null case और when guard तक support करता है, लेकिन गलत record accessor या guard execution के दौरान exception java.lang.MatchException तक ले जा सकता है
Java 21 में stable हुआ pattern matching
- Java 21 19 सितंबर 2023 को रिलीज़ हुआ था, और switch blocks व switch expressions में record patterns support करता है
- इस syntax को Java में भी Kotlin, Rust और C# जैसे तरीकों से functional programming patterns व्यक्त करने का turning point माना जाता है
- हाल के Java versions में हुए मुख्य syntax बदलाव Java 21 के pattern matching तक पहुंचे
- Java 14: switch expressions stable हुए
- Java 16: records,
instanceof pattern matching stable हुए
- Java 17: sealed classes stable हुईं
- Java 21: record patterns, switch pattern matching stable हुए
- इन बदलावों के bundle से Java अब पहले express करना मुश्किल रहे algebraic data types और उनके idiomatic usage patterns को संभाल सकता है
Type theory के लिए ज़रूरी न्यूनतम concepts
- Java 21 की features समझने के लिए type theory के कुछ concepts ज़रूरी हैं
- bottom/empty type उन values के set को दिखाता है जिन्हें compute नहीं किया जा सकता, और आम programming languages में यह आम तौर पर empty set होता है
- Kotlin का
Nothing private constructor वाला है, इसलिए उसका instance मौजूद नहीं हो सकता
- Java का
Void private constructor वाला है, लेकिन उसमें null रखा जा सकता है, इसलिए उसे असली bottom type मानना मुश्किल है
- Java का primitive
void variable type के रूप में इस्तेमाल नहीं किया जा सकता, इसलिए इस लिहाज़ से वह ज्यादा करीब व्यवहार करता है
- top type सभी types की सभी values को दिखाने वाला universal set है
- Kotlin में
Any यह भूमिका निभाता है
- Java का
Object primitive types के object model से अलग होने के कारण दूसरी languages के top type जैसा अर्थ रखना मुश्किल है
- unit type वह type है जिसमें केवल एक value होती है
- Java का
void method return में unit type की तरह संभाला जा सकता है, लेकिन parameter type के रूप में pass नहीं किया जा सकता
- Kotlin का
Unit object के रूप में define होता है और method parameter के रूप में भी इस्तेमाल किया जा सकता है
- boolean type में
true और false दो values होती हैं, और इसे nullable unit type के रूप में भी express किया जा सकता है, लेकिन यह practical नहीं है
Product type और Java records
- product type वह type है जो दो या अधिक component types को बांधता है, और component types की संख्या arity या degree बनती है
- C का
struct product type का उदाहरण है
int, char *, double, int जैसे component types repeat हो सकते हैं
- repeated types को field names के साथ ordered pair मानकर अलग पहचाना जा सकता है
- Python या Rust के tuple को भी product type माना जा सकता है, जहां components के नाम की भूमिका index निभाता है
- Java 16 में stable हुई record class product type का अच्छा उदाहरण है
- record के fields final होते हैं, और record को inherit नहीं किया जा सकता
- record की state creation के समय set होती है और उसके बाद बनी रहती है
- हालांकि, record के भीतर mutable data type डालने पर उसके contents की immutability तक guarantee नहीं होती
- सामान्य Java class में public/private state, inheritance से बनी hidden state, mutable/static field और non-standard getter मिल सकते हैं, इसलिए components को generalize करना मुश्किल होता है
- records नीचे दी गई सीमाओं से ऐसी structure guarantee करते हैं जिसमें pattern matching जैसी language features भरोसेमंद ढंग से काम करती हैं
- record implicitly final class होता है और inherit नहीं किया जा सकता
java.lang.Record के अलावा किसी class को extend नहीं कर सकता
- record component पर visibility modifier नहीं लगाया जा सकता
- component reference हमेशा final होता है और immutable माना जाता है
- default getter field name को ज्यों का त्यों इस्तेमाल करता है, और
a field का getter a() बनता है
- backing field implicitly private होता है और getter के ज़रिए access होता है
record pattern से nested data destructuring
- Java 21 का switch pattern
instanceof checks और explicit casts को दोहराए बिना nested record data को destructure करता है
- उदाहरण में
record A(Record inner), record B(char b), record SomeOtherRecord() इस्तेमाल होते हैं
- पुराने तरीके में
if (r instanceof A) के बाद cast करना पड़ता है, और internal value पर फिर से instanceof और cast दोहराना पड़ता है
- switch pattern
case A(B(char a)) -> String.valueOf(a) की तरह nested value को सीधे extract करता है
- switch block की structure if-else ladder से ज्यादा clear होती है, और यह deeply nested data को तेजी से निकालने के लिए उपयुक्त है
- Java 21 में सीधे चलाना हो तो code को
main.java में डालकर नीचे की command इस्तेमाल कर सकते हैं
java --enable-preview --source 21 main.java
Sum type और sealed classes/interfaces
- सीमित choices express करने के लिए Java enum इस्तेमाल किया जा सकता है, लेकिन RGB, HSL, YUV, CMYK जैसे अलग-अलग data structures वाले color representations को केवल enum से संभालना cumbersome है
- inheritance-based polymorphism से
Color abstract class और RGB, CMYK, YUV, HSL classes बनाई जा सकती हैं, लेकिन सामान्य class hierarchy open होती है
- library user
RYB जैसी नई class बनाकर Color inherit कर सकता है
- अगर API ने extension का इरादा नहीं रखा था, तो नया variant crash या दूर के code में subtle bugs पैदा कर सकता है
- sealed classes Java में sum type concept express करने के लिए इस्तेमाल होती हैं
- sum type ऐसा type है जो एक समय में अपने components में से एक हो सकता है
- इसे tagged union type भी कहा जाता है
sealed modifier और permits clause इस्तेमाल करने पर केवल खास classes को inheritance allow किया जा सकता है
public sealed class Color permits RGB, CMYK, YUV, HSL {
}
- sealed class hierarchy में direct या indirect inheritor के पास
sealed, non-sealed, final में से एक होना चाहिए; नहीं होने पर compile error आता है
sealed: केवल permits में नामित types inherit कर सकते हैं
non-sealed: सामान्य class की तरह inherit किया जा सकता है
final: inheritance tree का leaf, आगे extend नहीं किया जा सकता
sealed interface और record को साथ इस्तेमाल करने का तरीका
- switch pattern matching की destructuring records पर काम करती है, लेकिन records
Record के अलावा किसी class को inherit नहीं कर सकते
- समाधान sealed interface इस्तेमाल करना है
- sealed interface, sealed class की तरह ही काम करता है
- records और enums भी sealed interface को implement कर सकते हैं
- उदाहरण में
Color को sealed interface बनाया गया और RGB, CMYK, YUV, HSL को record के रूप में implement किया गया
public sealed interface Color permits RGB, CMYK, YUV, HSL {
String getDescription();
}
record RGB(int red, int green, int blue) implements Color {
public String getDescription() {
return "RGB Color: (" + red + ", " + green + ", " + blue + ")";
}
}
- इसके बाद switch में हर record की values सीधे extract की जा सकती हैं
switch (color) {
case RGB(int red, int green, int blue) -> {
}
case CMYK(double cyan, double magenta, double yellow, double black) -> {
}
case YUV(int y, int u, int v) -> {
}
case HSL hsl -> {
System.out.println(hsl.getDescription());
}
case null -> {
System.out.println("How did color become null?!");
}
}
- Java 21 switch blocks और expressions में
null case handle कर सकता है, इसलिए switch से पहले अलग null check करने की ज़रूरत नहीं होती
- अगर
Color sealed type है, तो Java जान सकता है कि सभी cases handle हुए हैं या नहीं, इसलिए default case के बिना भी exhaustive switch संभव है
Guard clause और when
- Java 21 switch arm में extra condition जोड़ने वाली guard clause support करता है
- guard clause
when keyword से case label में condition integrate करती है
switch (color) {
case RGB(int red, int green, int blue) when red > 200 -> {
System.out.println("Very red.");
}
case RGB rgb when rgb.green > 100 -> {
System.out.println("Sort of green...");
}
case RGB rgb -> {
System.out.println("Not that red...");
}
}
- पहले
case RGB(...) body के भीतर फिर से if (red > 200) डालना पड़ता था
- Java true evaluate होने वाले पहले case को eagerly match करता है, इसलिए ज्यादा specific case पहले और कम specific case बाद में रखने चाहिए
- guard वाले RGB case के बाद exhaustive बनाए रखने के लिए सामान्य
case RGB rgb ज़रूरी है
MatchException कब आती है
- Java 21 के pattern matching से
java.lang.MatchException अतिरिक्त रूप से जुड़ा है
- अगर record accessor exception throw करे, तो switch pattern fail हो सकता है और
MatchException आ सकती है
record R(int i) {
public int i() {
return i / 0;
}
}
static void exampleAnR(R r) {
switch(r) {
case R(var i): System.out.println(i);
}
}
- ऊपर के उदाहरण में
i() accessor ArithmeticException throw करता है, इसलिए switch block MatchException throw करता है
- JEP 441 के अनुसार हमेशा exception throw करने वाला record accessor बेहद असामान्य है, और exhaustive pattern switch का
MatchException throw करना भी बेहद दुर्लभ है
- exhaustive switch में भी अगर selector के लिए specified variants में से कोई match न हो, तो exception आ सकता है
- JEP 441 enum पर exhaustive switch के matching में fail होने की स्थिति को switch compile होने के बाद enum class बदल जाने की स्थिति के रूप में समझाता है
- guard clause execution के दौरान exception आने पर भी
MatchException आ सकती है
static void example(Object obj) {
switch (obj) {
case R r when (r.i / 0 == 1): System.out.println("It's an R!");
default: break;
}
}
बाकी दायरा
- Java 21 के records, sealed types, switch pattern matching और guard clause को मिलाकर functional programming के building blocks Java code में लागू किए जा सकते हैं
- generics का switch patterns के साथ interaction जैसे कुछ topics यहां cover नहीं किए गए हैं
- अगले article में Java code लिखने के तरीके को बेहतर बनाने में इस्तेमाल हो सकने वाले quirks और practical examples cover किए जाएंगे
1 टिप्पणियां
Hacker News की रायें
Java 21 की सबसे बड़ी सुविधा virtual threads का रिलीज़ होना है: https://openjdk.org/jeps/444
पता नहीं क्यों, लेख में यह छूट गया है। अगर कोई ऐसी सुविधा है जो मौजूदा Go developers को Java की ओर खींच सकती है, तो शायद यही हो सकती है, और यह उन लोगों को भी मना सकती है जिन्हें reactive-style concurrency patterns पसंद नहीं थे
Java applications और libraries को inheritance, packaging, object-oriented design, build tools वगैरह की वजह से Go की तुलना में reason करना और समझना बहुत मुश्किल है
Go सरल है और समझने, पढ़ने व maintain करने में आसान है। Packaging वैसी ही लगती है जैसे कंप्यूटर में files को एक ही folder में व्यवस्थित करना, और tools भी language में built-in हैं। ऐसा भी नहीं लगता कि IntelliJ जैसे IDE के बिना यह बस मुश्किल से इस्तेमाल लायक बनता है
हो सकता है अब चीज़ें बदल गई हों, लेकिन आजकल जो ज्यादातर Java libraries दिखती हैं, वे अब भी ऐसी ही लगती हैं
JVM और उसके tooling ecosystem में मुझे बहुत सी चीज़ें पसंद हैं, लेकिन अब Java code लिखना खास पसंद नहीं है। JRuby दोनों पक्षों की खूबियाँ कुछ हद तक साथ दे देता है
presentation यहाँ है, और virtual threads demo करीब 45वें मिनट से शुरू होता है
https://youtu.be/pzm6I4liJlg?si=GtxQ4MThEaNDfC67
sync.Mapतक Java के general-purposeConcurrentMapजैसा नहीं है, बल्कि दो खास use cases के लिए specialized है। Java में concurrent sets, queues, barriers, phasers, fork-join pools वगैरह हैं। Go routines होने के बावजूद ऐसे containers काफी उपयोगी हो सकते हैं; कम-से-कम fork-join को implement करना इतना मामूली नहीं है। हर जगह mutex इस्तेमाल करना बहुत low-level लगता हैमुझे पता है कि third-party implementations मौजूद हैं, लेकिन concurrency को सही तरीके से बनाना इतना मुश्किल है कि जब तक कोई third-party package Java के JCTools या Google Guava जितना mature न हो और उसके पीछे बहुत सारे users व developers न हों, उसे अपनाने में हिचक होती है
Executor.newVirtualThreadPerTaskExecutorऔरgoका contrast अच्छी तरह दिखाता है कि Go developers Java पर नहीं जाएंगे, ऐसा क्यों लगता हैसुधार करूँ तो असल में यह
goकी जगहtry (var executor = Executors.newVirtualThreadPerTaskExecutor()) { executor.submit(...) }के ज्यादा करीब हैमुझे लगता है blog post का title अच्छा चुनाव नहीं था। छिपा हुआ subtitle "Algebraic data types in Java" है, और वही content को कहीं बेहतर समझाता है। बेहतर title Algebraic data types in Java 21 होता
शायद title की वजह से यहाँ की काफी comments topic से भटक गईं। मैं algebraic data types, Java implementation के pros-cons, और दूसरी languages से technical comparison के बारे में ज्यादा देखना चाहता था
मौजूदा Java code गायब तो नहीं होगा, इसलिए अगर ऐसा code randomly mix होने लगे तो क्या वाकई चीज़ें बेहतर होंगी, इस पर शक है
यहाँ समझाई गई sealed classes सुविधा कुछ पूरी तरह गलत-सी लगती है
तर्क यह है कि अगर कोई सामान्य interface है, तो कोई भी उसे implement करने वाली नई class बना सकता है, और
if (x instanceof Foo) { ... } else if (x instanceof Bar) { ... }जैसा code तब runtime पर टूट जाएगा जब कोई नई class जोड़ देगा। इसलिए नई sealed interface सुविधा से अगर किसी को भी उस interface को implement करने वाली नई class बनाने से रोक दिया जाए, तोifstatement नहीं टूटेगालेकिन क्या object-oriented programming ने पहले ही इस तरह की समस्या के बारे में सोचकर उसे हल नहीं कर दिया था? मुझे पता है कि object-oriented अब चलन से बाहर हो गया है, लेकिन Java एक object-oriented language है
समाधान है interface में method जोड़ना और सभी classes से उसे implement कराना। तब सभी विकल्पों को एक विशाल if/switch statement में गिनाने के बजाय method call किया जा सकता है
यह तरीका code को extend करने से रोकने की तुलना में बेहतर है, और उलटे उसे extend करने देता है। नए implementer को बस वह method implement करना होगा, और compiler इसे enforce करता है, इसलिए गलती से उसे छोड़ा भी नहीं जा सकता
लेख का color space वाला उदाहरण (RGB, CMYK आदि) बहुत अच्छा counterexample है। अगर मैंने color spaces का उपयोग करने वाला code लिखा है, तो user या customer को कोई ऐसा अजीब और दुर्लभ color space इस्तेमाल करना पड़ सकता है जिसके बारे में मैंने सोचा ही न हो। मैं ऐसा code नहीं बनाना चाहूँगा जो केवल विशाल if/switch statement में सूचीबद्ध color spaces को support करे और उसी structure की वजह से extend न किया जा सके
sealed classes इस समस्या को हल करती हैं। लेकिन बदले में एक नई समस्या पैदा होती है: “अगर और extension classes की ज़रूरत हो और उन्हें सबको पहले से जानना संभव न हो तो?” आखिर सवाल यह बनता है कि क्या दोनों चीज़ें हासिल करने का कोई तरीका है
इस समस्या को expression problem कहा जाता है [1]
कुछ statically typed languages हैं जो expression problem को हल कर सकती हैं, और Java भी उनमें से एक है [2]। हालांकि Java में ऐसा करने का तरीका अब भी बहुत जटिल और असुविधाजनक है, इसलिए इसका इस्तेमाल लगभग नहीं होता। अगर आप Haskell या JVM की दुनिया में रहना चाहते हैं, तो Scala इस मामले में कहीं बेहतर है
[1] https://en.wikipedia.org/wiki/Expression_problem
[2] https://koerbitz.me/posts/Solving-the-Expression-Problem-in-...
interface methods और virtual calls वाला तरीका तब बहुत flexible नहीं होता जब आप नई classes जोड़ने के बजाय नए operations जोड़ना चाहते हों। सिर्फ एक नया operation जोड़ना हो, तब भी सभी implementations में जाकर नया method डालना पड़ता है, और जिन implementations तक आपकी access नहीं है उन्हें तोड़ भी सकता है। आपस में असंबंधित methods भी एक ही class में define करने पड़ते हैं, जिससे code readability काफी खराब होती है, और virtual call भी मुफ्त नहीं है—performance पर भी असर पड़ता है
इस मामले में sealed class कहीं बेहतर तरीके से scale होती है। एक जगह नया switch जोड़िए और काम खत्म, backward compatibility भी नहीं टूटती
यही मशहूर expression problem है
https://pkolaczk.github.io/in-defense-of-switch/
instanceofके बजाय polymorphic dispatch इस्तेमाल करने की सलाह मैं समझता हूँ, और Bob Martin को इस पर लंबी बात करते भी देखा है, लेकिन मैं सहमत नहीं हूँइस तरह का polymorphic dispatch करने के लिए object को कई concerns खुद संभालने पड़ते हैं
वीडियो गेम में
Carके पास.render(),.collide(),.playSound()हो सकते हैं। बाद मेंDogजोड़ें तो बस ये तीन methods implement करने होंगे, औरRenderer,PhysicsEngine,SoundEngineको बदलने या फिर से compile करने की ज़रूरत नहीं पड़ेगी। दूसरे programmer भी मेरे कीमती code में bug डाले बिना entities जोड़ सकते हैं। सुनने में अच्छा लगता हैलेकिन अब
CarऔरDogको graphics, physics और sound—तीनों के बारे में जानना होगा। और entities अलग-थलग मौजूद नहीं होतीं। कार और कुत्ते को सही क्रम में render होना चाहिए और वे एक-दूसरे को ढक भी सकते हैं। collisions भी आपस में check करने होंगे। असली game jam में जैसा अनुभव हुआ, sound वाला व्यक्ति sound behavior जोड़ने के लिए हर object के अंदर जाने पर मजबूर हो सकता हैबेहतर यह है कि physics के बारे में सोचते समय
Physics.collideAll()के अंदर काम किया जाए और ज़रूरत हो तोinstanceofसे special handling की जाए; और graphics के बारे में सोचते समयGraphics.renderAll()के अंदर काम किया जाएरोज़मर्रा के backend Java web development में भी यही बात लागू होती है। REST controller में Java object को HTTP response में बदलने का तरीका तय करते समय, एक ही method में सब कुछ देखना और
{instanceof Forbidden}को 403,{instanceof NotFound}को 404 पर map करना बेहतर है। मैं Java class के अंदर हीgetCode()या REST-specific चीज़ें नहीं डालना चाहूँगाStringकेfinalहोने की वजह है, और यहाँ तक कहा जा सकता है किfinalको default रखना चाहिए और जिन classes को subclassing की अनुमति देनी हो उन्हें ही साफ तौर परopenmark करना चाहिएfunctional programming में sum type का typical उदाहरण list है। इसमें केवल
Element(T head, List tail)औरNil()होते हैं। इसे extend करने की कोई वजह नहीं है, और सच कहें तो extend करने पर list को handle करने वाले सभी functions के साथ मिलकर गलत code बन सकता हैसाथ ही pattern matching जैसा visitor pattern बहुत verbose है और Java के सामान्य method dispatch semantics का उपयोग करने वाले hack पर निर्भर करता है। यहाँ मुझे pattern matching कई गुना ज्यादा readable लगती है
उदाहरण के लिए, security token validate करने वाला security interface सोचा जा सकता है
अगर वह सामान्य interface हो, तो उसे implement करके token को ignore करना (सबको allow करना), token चुराना, या backdoor डालना आसान है। अगर ऐसी class उस जगह inject हो जाए जहाँ security check होता है, तो security टूट सकती है
sealed interface के साथ unauthorized नया implementation मौजूद नहीं हो सकता। अगर आपको कोई object मिलता है जो उस interface को implement करने का दावा करता है, तो यह guarantee होती है कि वह वास्तविक security check करने वाले verified implementations में से एक है। यानी security bugs और exploits की एक पूरी category ही खत्म हो गई
sum type के बारे में पता है, लेकिन Java के sum type के बारे में ज्यादा नहीं जानने वाले नज़रिए से यह अच्छा लेख है
हालांकि मुझे नहीं पता कि सिर्फ sum type की वजह से Java फिर से पसंद आने लगेगा या नहीं। व्यापक nullability अब भी बनी हुई है, और इस लेख में भी यह कई बार सिर उठाती है
Google, Meta, Microsoft आदि द्वारा
@Nullableसे शुरू करके annotations को standardize करने की कोशिश https://jspecify.dev/ से मुझे वाकई बहुत उम्मीद है"इसे product type क्यों कहते हैं?" के लिए लेख का जवाब गलत नहीं है, लेकिन ज्यादा सहज और संक्षिप्त रूप से कहें तो product type में possible values की कुल संख्या उसके component types की possible values की संख्याओं का गुणनफल होती है
product को sum से बदल दें तो भी यही बात वैसी ही लागू होती है
दिलचस्प बात यह है कि
a -> bरूप वाले unique functions की कुल संख्या, सिर्फ input और output को देखें तो exponent से निकाली जा सकती है। यानी(b की possible values की संख्या) ^ (a की possible values की संख्या)गणितीय रूप में लिखें तो, Bool की list में बाईं ओर elements की संख्या है और दाईं ओर कुल possibilities की संख्या है
0 : 1
1 : 2
2 : 4
3 : 8
4 : 16
5 : 32
इसी तरह आगे चलता है
Project Valhalla पूरा होकर Java में आखिरकार value types आने का इंतज़ार कर रहा हूँ। तब sum types, value types और coroutines तक मिलकर यह काफी अच्छी languages में से एक बन सकती है
Java मूल रूप से सचमुच खराब language नहीं थी
समस्या लोग थे। विशाल over-engineering, codebase समझना मुश्किल बनाने वाले बहुत ज्यादा abstract concepts, reverse GOTO statements जैसे annotation-form code magic, और DI frameworks समस्या थे
जिसे ठीक करने की जरूरत है वह language नहीं, बल्कि ecosystem है। Java ecosystem के अंदर एक तरह के "Reformation" movement की जरूरत है
सिर्फ Kotlin, Clojure, Scala पर चले जाना पर्याप्त नहीं है
HammerFactoryबनाने वालीHammerFactoryFactoryबनाई जा सकती है। लेकिन Java ecosystem इस तरह की problem-solving approach को बढ़ावा देता और encourage करता है। C# भी मुझे कुछ वैसा ही लगता हैJava को सच में जिस एक चीज़ की जरूरत है, वह independent functions, या namespaced functions हैं। कभी-कभी class की जरूरत नहीं होती और module या namespace के अंदर function ही काफी होता है, लेकिन यह क्यों नहीं हो सकता, समझ नहीं आता
लेखक Records की जरूरत समझाते हुए बताते हैं कि ज्यादातर Java objects सभी fields को private रखते हैं और सिर्फ read/write accessor methods से access कराते हैं
लेकिन accessors define करने के लिए language-level enforced convention नहीं है, इसलिए
fooके getter का नामgetBarरख दें तो वह काम तो करेगा, लेकिनbaraccess करने की कोशिश करने वाले व्यक्ति को confuse कर सकता हैScala
unapplymethod implement करने वाले objects पर pattern matching support करता है। क्या इस तरीके को harmful माना जाता है? Java ने यह रास्ता क्यों नहीं अपनाया?unapplyजैसा कुछ तैयार हो सकता है, इसलिए उम्मीद अभी पूरी तरह खत्म नहीं हुई हैJava हमेशा से शानदार language रही है। उल्टी जैसा महसूस कराने वाली चीज़ enterprise-style ecosystem है। मैंने एक लाइन की logic implement करने के लिए दर्जनों classes और interfaces इस्तेमाल होते देखे हैं