जावा
का नया,
14 वां संस्करण दूर नहीं है, जिसका अर्थ है कि यह देखने का समय है कि जावा के इस संस्करण में कौन से नए वाक्यविन्यास हैं। इन सिंटैक्टिक संभावनाओं में से एक
उस प्रकार का
पैटर्न मिलान है जिसे बेहतर (विस्तारित)
instanceof
ऑपरेटर का उपयोग करके लागू किया जाएगा।
आज मैं इस नए ऑपरेटर के साथ खेलना चाहूंगा और इसके काम की विशेषताओं पर अधिक विस्तार से विचार करूंगा। चूंकि प्रकार से मिलान करने वाला पैटर्न अभी तक मुख्य JDK रिपॉजिटरी में प्रवेश नहीं किया है, इसलिए मुझे
एम्बर प्रोजेक्ट का रिपॉजिटरी डाउनलोड करना पड़ा, जो नए जावा सिंटैक्स कंस्ट्रक्शंस को विकसित कर रहा है, और इस रिपॉजिटरी से
जेडीके को
संकलित करता है।
इसलिए, पहली चीज जो हम करेंगे वह यह सुनिश्चित करने के लिए जावा संस्करण की जांच करेगा कि हम वास्तव में जेडीके 14 का उपयोग करते हैं:
> java -version openjdk version "14-internal" 2020-03-17 OpenJDK Runtime Environment (build 14-internal+0-adhoc.osboxes.amber-amber) OpenJDK 64-Bit Server VM (build 14-internal+0-adhoc.osboxes.amber-amber, mixed mode, sharing)
यह सही है।
अब हम "पुराने"
instanceof
ऑपरेटर के साथ एक छोटा सा कोड लिखेंगे और उसे चलाएंगे:
public class A { public static void main(String[] args) { new A().f("Hello, world!"); } public void f(Object obj) { if (obj instanceof String) { String str = (String) obj; System.out.println(str.toLowerCase()); } } }
> java A.java hello, world!
यह काम करता है। यह एक मानक प्रकार की जांच है जिसके बाद एक कलाकार होता है। हम हर दिन समान निर्माण लिखते हैं, कोई फर्क नहीं पड़ता कि हम जावा के किस संस्करण का उपयोग करते हैं, कम से कम 1.0, कम से कम 13।
लेकिन अब हमारे हाथ में जावा 14 है, और चलो बेहतर
instanceof
ऑपरेटर का उपयोग करके कोड को फिर से लिखते हैं (मैं भविष्य में कोड की दोहराई जाने वाली लाइनों को छोड़ दूँगा):
if (obj instanceof String str) { System.out.println(str.toLowerCase()); }
> java --enable-preview --source 14 A.java hello, world!
ठीक। कोड क्लीनर, छोटा, सुरक्षित और अधिक पठनीय है। स्ट्रिंग शब्द के तीन दोहराव थे, एक बन गया। ध्यान दें कि हम तर्कों को निर्दिष्ट करने के लिए नहीं भूल गए
--enable-preview --source 14
, as नया ऑपरेटर एक
पूर्वावलोकन सुविधा है । इसके अलावा, एक चौकस पाठक ने देखा कि हमने संकलन के बिना सीधे ए.जेवा स्रोत फ़ाइल को चलाया। यह सुविधा जावा 11 में
दिखाई दी ।
आइए कुछ और अधिक परिष्कृत लिखने की कोशिश करें और एक दूसरी शर्त जोड़ें जो कि सिर्फ घोषित चर का उपयोग करती है:
if (obj instanceof String str && str.length() > 5) { System.out.println(str.toLowerCase()); }
यह संकलन और कार्य करता है। लेकिन क्या होगा अगर आप शर्तों को स्वैप करते हैं?
if (str.length() > 5 && obj instanceof String str) { System.out.println(str.toLowerCase()); }
A.java:7: error: cannot find symbol if (str.length() > 5 && obj instanceof String str) { ^
संकलन त्रुटि। जिसकी उम्मीद की जा रही है: अभी तक चर को घोषित नहीं किया गया है, जिसका अर्थ है कि इसका उपयोग नहीं किया जा सकता है।
वैसे, परिवर्तनशीलता के बारे में क्या? चर अंतिम है या नहीं? हम कोशिश करते हैं:
if (obj instanceof String str) { str = "World, hello!"; System.out.println(str.toLowerCase()); }
A.java:8: error: pattern binding str may not be assigned str = "World, hello!"; ^
हाँ, अंतिम चर। इसका मतलब है कि "चर" शब्द पूरी तरह से यहाँ सही नहीं है। और संकलक विशेष शब्द "पैटर्न बाइंडिंग" का उपयोग करता है। इसलिए, मैं अब से "परिवर्तनशील" नहीं, बल्कि "पैटर्न बाइंडिंग" कहने का प्रस्ताव करता हूं (दुर्भाग्य से, "बाइंडिंग" शब्द रूसी में बहुत अच्छी तरह से अनुवादित नहीं है)।
परिवर्तनशीलता और शब्दावली के साथ। चलिए आगे प्रयोग करते हैं। क्या होगा अगर हम संकलक को "तोड़" दें?
यदि आप एक चर और एक पैटर्न को एक ही नाम से बांधते हैं तो क्या होगा?
if (obj instanceof String obj) { System.out.println(obj.toLowerCase()); }
A.java:7: error: variable obj is already defined in method f(Object) if (obj instanceof String obj) { ^
तार्किक है। बाहरी दायरे से एक चर को ओवरलैप करने से काम नहीं होता है। यह वैसा ही है जैसे हम चर को
obj
ही दायरे में दूसरी बार
obj
।
और यदि ऐसा है:
if (obj instanceof String str && obj instanceof String str) { System.out.println(str.toLowerCase()); }
A.java:7: error: illegal attempt to redefine an existing match binding if (obj instanceof String str && obj instanceof String str) { ^
कंपाइलर कंक्रीट जितना ठोस होता है।
आप और क्या प्रयास कर सकते हैं? चलो चारों ओर scopes के साथ खेलते हैं। यदि बंधन को
if
शाखा में परिभाषित किया गया है,
if
क्या यह
else
शाखा में परिभाषित किया जाएगा यदि स्थिति उलटी है?
if (!(obj instanceof String str)) { System.out.println("not a string"); } else { System.out.println(str.toLowerCase()); }
इसने काम किया। संकलक न केवल विश्वसनीय है, बल्कि स्मार्ट भी है।
और अगर ऐसा है तो?
if (obj instanceof String str && true) { System.out.println(str.toLowerCase()); }
इसने फिर से काम किया। कंपाइलर सही ढंग से समझता है कि स्थिति एक साधारण
obj instanceof String str
से नीचे
obj instanceof String str
।
क्या यह वास्तव में संकलक को "तोड़ना" संभव नहीं है?
शायद ऐसा है?
if (obj instanceof String str || false) { System.out.println(str.toLowerCase()); }
A.java:8: error: cannot find symbol System.out.println(str.toLowerCase()); ^
अहा! यह पहले से ही बग की तरह दिखता है। आखिरकार, सभी तीन स्थितियां बिल्कुल बराबर हैं:
obj instanceof String str
obj instanceof String str && true
obj instanceof String str || false
दूसरी ओर, फ्लो स्कूपिंग नियम, न के
बराबर हैं , और शायद इस तरह के मामले में वास्तव में काम नहीं करना चाहिए। लेकिन अगर आप विशुद्ध रूप से मानवीय दृष्टिकोण से देखें, तो मुझे लगता है कि यह एक बग है।
लेकिन चलो, कुछ और करने की कोशिश करते हैं। क्या यह काम करेगा:
if (!(obj instanceof String str)) { throw new RuntimeException(); } System.out.println(str.toLowerCase());
संकलित। यह अच्छा है, क्योंकि यह कोड निम्नलिखित के बराबर है:
if (!(obj instanceof String str)) { throw new RuntimeException(); } else { System.out.println(str.toLowerCase()); }
और चूंकि दोनों विकल्प समान हैं, प्रोग्रामर को उम्मीद है कि वे उसी तरह काम करेंगे।
अतिव्यापी खेतों के बारे में क्या?
public class A { private String str; public void f(Object obj) { if (obj instanceof String str) { System.out.println(str.toLowerCase()); } else { System.out.println(str.toLowerCase()); } } }
संकलक ने शपथ नहीं ली। यह तर्कसंगत है, क्योंकि स्थानीय चर हमेशा खेतों को ओवरलैप कर सकते हैं। जाहिरा तौर पर, उन्होंने पैटर्न बाइंडिंग के लिए अपवाद नहीं बनाने का भी फैसला किया। दूसरी ओर, इस तरह के कोड बल्कि नाजुक है। एक लापरवाह चाल, और आप नोटिस नहीं कर सकते कि आपकी शाखा कैसे टूटी:
private boolean isOK() { return false; } public void f(Object obj) { if (obj instanceof String str || isOK()) { System.out.println(str.toLowerCase()); } else { System.out.println(str.toLowerCase()); } }
दोनों शाखाएं अब
str
क्षेत्र का उपयोग करती हैं, जो एक असावधान प्रोग्रामर उम्मीद नहीं कर सकता है। जितनी जल्दी हो सके ऐसी त्रुटियों का पता लगाने के लिए, आईडीई और खेतों और चर के लिए हाइलाइटिंग अलग-अलग सिंटैक्स में निरीक्षण का उपयोग करें। मैं यह भी सलाह देता हूं कि आप हमेशा खेतों के लिए
this
क्वालीफायर का उपयोग करें। इससे और भी विश्वसनीयता बढ़ेगी।
और क्या दिलचस्प है? "पुराने"
instanceof
, नया कभी भी
null
मेल नहीं खाता है। इसका मतलब है कि आप हमेशा इस तथ्य पर भरोसा कर सकते हैं कि पैटर्न बाइंडर कभी भी
null
नहीं हो सकता है:
if (obj instanceof String str) { System.out.println(str.toLowerCase());
वैसे, इस संपत्ति का उपयोग करके, आप ऐसी श्रृंखलाओं को छोटा कर सकते हैं:
if (a != null) { B b = a.getB(); if (b != null) { C c = b.getC(); if (c != null) { System.out.println(c.getSize()); } } }
यदि आप
instanceof
उपयोग करते हैं, तो उपरोक्त कोड इस तरह से फिर से लिखा जा सकता है:
if (a != null && a.getB() instanceof B b && b.getC() instanceof C c) { System.out.println(c.getSize()); }
टिप्पणियों में लिखें कि आप इस शैली के बारे में क्या सोचते हैं। क्या आप इस मुहावरे का उपयोग करेंगे?
जेनरिक के बारे में क्या?
import java.util.List; public class A { public static void main(String[] args) { new A().f(List.of(1, 2, 3)); } public void f(Object obj) { if (obj instanceof List<Integer> list) { System.out.println(list.size()); } } }
> java --enable-preview --source 14 A.java Note: A.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. 3
बहुत दिलचस्प है। यदि "पुराना"
instanceof
केवल
instanceof List
या
instanceof List<?>
का समर्थन करता है, तो नया किसी विशेष प्रकार के साथ काम करता है। हम पहले व्यक्ति के इस तरह के जाल में पड़ने का इंतजार कर रहे हैं:
if (obj instanceof List<Integer> list) { System.out.println("Int list of size " + list.size()); } else if (obj instanceof List<String> list) { System.out.println("String list of size " + list.size()); }
यह काम क्यों नहीं कर रहा है?उत्तर: जावा में संशोधित जेनरिक की कमी।
IMHO, यह एक बहुत गंभीर समस्या है। दूसरी ओर, मुझे नहीं पता कि इसे कैसे ठीक किया जाए। ऐसा लगता है कि आपको फिर से आईडीई के निरीक्षणों पर भरोसा करना होगा।
निष्कर्ष
सामान्य तौर पर, नया पैटर्न-मिलान प्रकार बहुत अच्छा काम करता है। इंप्रूव्ड
instanceof
ऑपरेटर आपको न केवल एक प्रकार का परीक्षण करने की अनुमति देता है, बल्कि इस प्रकार के रेडी-मेड बाइंडर भी घोषित करता है, जिससे मैनुअल कास्टिंग की आवश्यकता समाप्त हो जाती है। इसका मतलब है कि कोड में कम शोर होगा, और पाठक के लिए उपयोगी तर्क को समझाना बहुत आसान होगा। उदाहरण के लिए, अधिकांश
equals()
कार्यान्वयन एक पंक्ति में लिखे जा सकते हैं:
public class Point { private final int x, y; … @Override public int hashCode() { return Objects.hash(x, y); } @Override public boolean equals(Object obj) { return obj instanceof Point p && px == this.x && py == this.y; } }
ऊपर दिए गए कोड को और भी छोटा लिखा जा सकता है। कैसे?उन
प्रविष्टियों का उपयोग करना जो जावा 14 में भी शामिल होंगे। हम अगली बार उनके बारे में बात करेंगे।
दूसरी ओर, कई विवादास्पद बिंदु छोटे सवाल खड़े करते हैं:
- पूरी तरह से पारदर्शी गुंजाइश नियम नहीं हैं (उदाहरण के साथ
instanceof || false
) instanceof || false
)। - खेतों की ओवरलैपिंग।
instanceof
और जेनरिक।
हालांकि, ये गंभीर दावों की तुलना में अधिक क्षुद्र नाइटपार्टिंग हैं। सभी के लिए, नए
instanceof
ऑपरेटर के भारी लाभ निश्चित रूप से इसकी भाषा जोड़ने लायक हैं। और अगर यह अभी भी पूर्वावलोकन स्थिति को छोड़ देता है और एक स्थिर वाक्यविन्यास बन जाता है, तो यह जावा 8 को जावा के नए संस्करण के अंत में छोड़ने के लिए एक महान प्रेरणा होगी।
PS मेरे पास
टेलीग्राम में एक
चैनल है जहां मैं जावा समाचार के बारे में लिखता हूं। मेरा आपसे आग्रह है कि आप इसकी सदस्यता लें।