Java Language
जेनेरिक्स
खोज…
परिचय
जेनरिक जेनेरिक प्रोग्रामिंग की एक सुविधा है जो संकलित समय प्रकार की सुरक्षा प्रदान करते हुए विभिन्न प्रकार की वस्तुओं पर काम करने के लिए एक प्रकार या विधि को अनुमति देने के लिए जावा के प्रकार प्रणाली का विस्तार करती है। विशेष रूप से, जावा संग्रह फ्रेमवर्क संग्रह उदाहरण में संग्रहीत वस्तुओं के प्रकार को निर्दिष्ट करने के लिए जेनरिक का समर्थन करता है।
वाक्य - विन्यास
- वर्ग ArrayList <E> {} // एक सामान्य वर्ग है जिसमें टाइप पैरामीटर E है
- वर्ग HashMap <K, V> {} // दो प्रकार के मापदंडों K और V के साथ एक सामान्य वर्ग
- <E> शून्य प्रिंट (E तत्व) {} // एक सामान्य विधि है जिसमें टाइप पैरामीटर E है
- ArrayList <स्ट्रिंग> नाम; // एक सामान्य वर्ग की घोषणा
- ArrayList <?> ऑब्जेक्ट; // एक अज्ञात प्रकार के पैरामीटर के साथ एक सामान्य वर्ग की घोषणा
- नया ArrayList <String> () // एक सामान्य वर्ग की तात्कालिकता
- नया ArrayList <> () // प्रकार के आविष्कार के साथ तात्कालिकता "हीरा" (जावा 7 या बाद का)
टिप्पणियों
जावा को टाइप इरेज़र के माध्यम से जेनरिक कार्यान्वित किया जाता है, जिसका अर्थ है कि रनटाइम के दौरान जेनेरिक क्लास के तात्कालिकता में निर्दिष्ट प्रकार की जानकारी उपलब्ध नहीं है। उदाहरण के लिए, विवरण List<String> names = new ArrayList<>();
एक सूची ऑब्जेक्ट का निर्माण करता है जिससे तत्व प्रकार String
को रनटाइम पर पुनर्प्राप्त नहीं किया जा सकता है। हालाँकि, यदि सूची को टाइप List<String>
एक क्षेत्र में संग्रहीत किया जाता है, या इस प्रकार के एक विधि / कंस्ट्रक्टर पैरामीटर को पास किया जाता है, या उस रिटर्न प्रकार की विधि से लौटा दिया जाता है, तो रनटाइम पर पूर्ण प्रकार की जानकारी को पुनर्प्राप्त किया जा सकता है। जावा परावर्तन एपीआई के माध्यम से।
इसका अर्थ यह भी है कि जब जेनेरिक प्रकार (जैसे: (List<String>) list
) में डाली जाती है, तो कलाकार एक अनियंत्रित कलाकार होता है । क्योंकि पैरामीटर <String>
मिटा दिया गया है, JVM यह जांच नहीं कर सकता है कि List<?>
से एक डाली List<?>
से List<String>
सही है; JVM केवल रनटाइम पर List
लिए List
लिए एक कलाकारों को देखता है।
एक सामान्य वर्ग बनाना
जेनरिक कक्षा, इंटरफेस और तरीकों को अन्य वर्गों और इंटरफेस को पैरामीटर के रूप में लेने में सक्षम बनाता है।
यह उदाहरण जेनेरिक क्लास Param
का उपयोग करता है, जो एक प्रकार के पैरामीटर T
को लेने के लिए, एंगल ब्रैकेट्स द्वारा सीमांकित ( <>
):
public class Param<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
इस वर्ग को तुरंत करने के लिए, T
स्थान पर एक प्रकार का तर्क प्रदान करें। उदाहरण के लिए, Integer
:
Param<Integer> integerParam = new Param<Integer>();
प्रकार तर्क किसी भी संदर्भ प्रकार हो सकता है, जिसमें सरणियाँ और अन्य सामान्य प्रकार शामिल हैं:
Param<String[]> stringArrayParam;
Param<int[][]> int2dArrayParam;
Param<Param<Object>> objectNestedParam;
जावा एसई 7 और बाद में, प्रकार तर्क को हीरे के खाली प्रकार के तर्कों ( <>
) से बदला जा सकता है:
Param<Integer> integerParam = new Param<>();
अन्य पहचानकर्ताओं के विपरीत, प्रकार के मापदंडों में कोई नामकरण बाधा नहीं है। हालांकि उनके नाम आमतौर पर ऊपरी मामले में उनके उद्देश्य का पहला अक्षर हैं। (यह आधिकारिक JavaDocs में भी सच है।)
उदाहरणों में शामिल हैं T
"प्रकार" के लिए , E
"तत्व" के लिए और K
/ V
"कुंजी" के लिए / "मूल्य" ।
एक सामान्य वर्ग का विस्तार
public abstract class AbstractParam<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
AbstractParam
एक सार वर्ग है जो T
एक प्रकार के पैरामीटर के साथ घोषित किया गया है। इस वर्ग का विस्तार करते समय, उस प्रकार के पैरामीटर को <>
अंदर लिखे गए प्रकार के तर्क द्वारा प्रतिस्थापित किया जा सकता है, या टाइप पैरामीटर अपरिवर्तित रह सकता है। नीचे दिए गए पहले और दूसरे उदाहरण में String
और Integer
टाइप पैरामीटर की जगह लेते हैं। तीसरे उदाहरण में, टाइप पैरामीटर अपरिवर्तित रहता है। चौथा उदाहरण जेनेरिक का बिल्कुल भी उपयोग नहीं करता है, इसलिए यह वर्ग के Object
के समान है। कंपाइलर AbstractParam
कच्चे प्रकार होने के बारे में चेतावनी देगा, लेकिन यह ObjectParam
वर्ग को संकलित करेगा। पांचवें उदाहरण में 2 प्रकार के पैरामीटर हैं (नीचे "कई प्रकार के पैरामीटर देखें"), दूसरे पैरामीटर का चयन करते हुए टाइप पैरामीटर को सुपरक्लास को पास किया गया।
public class Email extends AbstractParam<String> {
// ...
}
public class Age extends AbstractParam<Integer> {
// ...
}
public class Height<T> extends AbstractParam<T> {
// ...
}
public class ObjectParam extends AbstractParam {
// ...
}
public class MultiParam<T, E> extends AbstractParam<E> {
// ...
}
निम्नलिखित उपयोग है:
Email email = new Email();
email.setValue("[email protected]");
String retrievedEmail = email.getValue();
Age age = new Age();
age.setValue(25);
Integer retrievedAge = age.getValue();
int autounboxedAge = age.getValue();
Height<Integer> heightInInt = new Height<>();
heightInInt.setValue(125);
Height<Float> heightInFloat = new Height<>();
heightInFloat.setValue(120.3f);
MultiParam<String, Double> multiParam = new MultiParam<>();
multiParam.setValue(3.3);
ध्यान दें कि Email
वर्ग में, T getValue()
विधि कार्य करती है जैसे कि इसमें String getValue()
void setValue(T)
String getValue()
का हस्ताक्षर था, और void setValue(T)
विधि कार्य करती है मानो इसे void setValue(String)
घोषित किया गया हो।
एक खाली घुंघराले ब्रेसिज़ ( {}
) के साथ अनाम आंतरिक वर्ग के साथ त्वरित करना भी संभव है:
AbstractParam<Double> height = new AbstractParam<Double>(){};
height.setValue(198.6);
ध्यान दें कि अनाम आंतरिक वर्गों के साथ हीरे का उपयोग करने की अनुमति नहीं है।
कई प्रकार के पैरामीटर
जावा सामान्य वर्ग या इंटरफ़ेस में एक से अधिक प्रकार के पैरामीटर का उपयोग करने की क्षमता प्रदान करता है। कोण कोष्ठक के बीच के प्रकारों की अल्पविराम से अलग सूची रखकर कई प्रकार के मापदंडों को एक वर्ग या इंटरफ़ेस में उपयोग किया जा सकता है। उदाहरण:
public class MultiGenericParam<T, S> {
private T firstParam;
private S secondParam;
public MultiGenericParam(T firstParam, S secondParam) {
this.firstParam = firstParam;
this.secondParam = secondParam;
}
public T getFirstParam() {
return firstParam;
}
public void setFirstParam(T firstParam) {
this.firstParam = firstParam;
}
public S getSecondParam() {
return secondParam;
}
public void setSecondParam(S secondParam) {
this.secondParam = secondParam;
}
}
उपयोग नीचे दिया जा सकता है:
MultiGenericParam<String, String> aParam = new MultiGenericParam<String, String>("value1", "value2");
MultiGenericParam<Integer, Double> dayOfWeekDegrees = new MultiGenericParam<Integer, Double>(1, 2.6);
एक सामान्य विधि की घोषणा
विधियों में सामान्य प्रकार के पैरामीटर भी हो सकते हैं।
public class Example {
// The type parameter T is scoped to the method
// and is independent of type parameters of other methods.
public <T> List<T> makeList(T t1, T t2) {
List<T> result = new ArrayList<T>();
result.add(t1);
result.add(t2);
return result;
}
public void usage() {
List<String> listString = makeList("Jeff", "Atwood");
List<Integer> listInteger = makeList(1, 2);
}
}
ध्यान दें कि हमें वास्तविक प्रकार के तर्क को जेनेरिक पद्धति से पारित नहीं करना है। संकलक लक्ष्य प्रकार (उदाहरण के लिए हम जो परिणाम प्रदान करते हैं, उदाहरण के लिए), या वास्तविक तर्कों के प्रकार के आधार पर, हमारे लिए टाइप तर्क को संक्रमित करता है। यह आमतौर पर सबसे विशिष्ट प्रकार के तर्क का अनुमान लगाएगा जो कॉल प्रकार को सही करेगा।
कभी-कभी, शायद ही कभी, स्पष्ट प्रकार के तर्कों के साथ इस प्रकार के निष्कर्ष को ओवरराइड करना आवश्यक हो सकता है:
void usage() {
consumeObjects(this.<Object>makeList("Jeff", "Atwood").stream());
}
void consumeObjects(Stream<Object> stream) { ... }
यह इस उदाहरण में आवश्यक है क्योंकि कंपाइलर stream()
को कॉल करने के बाद उस Object
को T
लिए वांछित है यह देखने के लिए "आगे नहीं देख सकता है" और यह अन्यथा makeList
तर्कों के आधार पर String
अनुमान makeList
। ध्यान दें कि जावा भाषा उस कक्षा या ऑब्जेक्ट को छोड़ने का समर्थन नहीं करती है जिस पर विधि कहा जाता है ( this
ऊपर के उदाहरण में) जब प्रकार तर्क स्पष्ट रूप से प्रदान किए जाते हैं।
हीरा
जावा 7 ने जेनेरिक श्रेणी के तात्कालिकता के आसपास कुछ बॉयलर-प्लेट को हटाने के लिए डायमंड 1 को पेश किया। जावा 7+ के साथ आप लिख सकते हैं:
List<String> list = new LinkedList<>();
जहां आपको पिछले संस्करणों में लिखना था, यह:
List<String> list = new LinkedList<String>();
एक सीमा बेनामी वर्गों के लिए है , जहाँ आपको अभी भी तात्कालिकता में टाइप पैरामीटर प्रदान करना होगा:
// This will compile:
Comparator<String> caseInsensitiveComparator = new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareToIgnoreCase(s2);
}
};
// But this will not:
Comparator<String> caseInsensitiveComparator = new Comparator<>() {
@Override
public int compare(String s1, String s2) {
return s1.compareToIgnoreCase(s2);
}
};
हालांकि बेनामी इनर क्लासेस के साथ हीरे का उपयोग जावा 7 और 8 में समर्थित नहीं है, इसे जावा 9 में एक नई सुविधा के रूप में शामिल किया जाएगा ।
पाद लेख:
1 - कुछ लोग "डायमंड ऑपरेटर " का उपयोग <>
कहते हैं। यह गलत है। हीरा ऑपरेटर के रूप में व्यवहार नहीं करता है, और जेएलएस या आधिकारिक (आधिकारिक) जावा ट्यूटोरियल में ऑपरेटर के रूप में कहीं भी वर्णित या सूचीबद्ध नहीं है। वास्तव में, <>
एक अलग जावा टोकन भी नहीं है। बल्कि यह एक है <
टोकन एक के बाद >
टोकन, और यह कानूनी है (हालांकि बुरा शैली) है खाली स्थान या दोनों के बीच टिप्पणी करने के लिए। जेएलएस और ट्यूटोरियल लगातार <>
को "हीरा" के रूप में संदर्भित करते हैं, और इसलिए इसके लिए सही शब्द है।
कई ऊपरी सीमाएं आवश्यक हैं ("ए और बी का विस्तार करता है")
आप कई ऊपरी सीमा का विस्तार करने के लिए एक सामान्य प्रकार की आवश्यकता कर सकते हैं।
उदाहरण: हम संख्याओं की सूची को क्रमबद्ध करना चाहते हैं लेकिन Number
Comparable
लागू नहीं होती है।
public <T extends Number & Comparable<T>> void sortNumbers( List<T> n ) {
Collections.sort( n );
}
इस उदाहरण में T
का विस्तार करना चाहिए Number
और लागू Comparable<T>
जो सभी "सामान्य" में निर्मित की तरह नंबर कार्यान्वयन के अनुरूप होनी चाहिए Integer
या BigDecimal
अधिक विदेशी की तरह लोगों लेकिन फिट नहीं करता है Striped64
।
चूंकि एकाधिक वंशानुक्रम की अनुमति नहीं है, आप एक सीमा के रूप में अधिकांश एक वर्ग में उपयोग कर सकते हैं और इसे पहले सूचीबद्ध होना चाहिए। उदाहरण के लिए, <T extends Comparable<T> & Number>
की अनुमति नहीं है क्योंकि तुलनीय एक इंटरफ़ेस है, और एक वर्ग नहीं है।
एक बंधी हुई जेनरिक क्लास बनाना
आप उस प्रकार को वर्ग परिभाषा में बांधकर एक सामान्य वर्ग में उपयोग किए जाने वाले मान्य प्रकारों को प्रतिबंधित कर सकते हैं। निम्नलिखित सरल प्रकार पदानुक्रम को देखते हुए:
public abstract class Animal {
public abstract String getSound();
}
public class Cat extends Animal {
public String getSound() {
return "Meow";
}
}
public class Dog extends Animal {
public String getSound() {
return "Woof";
}
}
बंधे हुए जेनरिक के बिना, हम एक कंटेनर क्लास नहीं बना सकते हैं जो दोनों सामान्य हो और जानता है कि प्रत्येक तत्व एक जानवर है:
public class AnimalContainer<T> {
private Collection<T> col;
public AnimalContainer() {
col = new ArrayList<T>();
}
public void add(T t) {
col.add(t);
}
public void printAllSounds() {
for (T t : col) {
// Illegal, type T doesn't have makeSound()
// it is used as an java.lang.Object here
System.out.println(t.makeSound());
}
}
}
क्लास की परिभाषा में सामान्य बाध्यता के साथ, यह अब संभव है।
public class BoundedAnimalContainer<T extends Animal> { // Note bound here.
private Collection<T> col;
public BoundedAnimalContainer() {
col = new ArrayList<T>();
}
public void add(T t) {
col.add(t);
}
public void printAllSounds() {
for (T t : col) {
// Now works because T is extending Animal
System.out.println(t.makeSound());
}
}
}
यह जेनेरिक प्रकार के मान्य इंस्टेंटिएशन को भी प्रतिबंधित करता है:
// Legal
AnimalContainer<Cat> a = new AnimalContainer<Cat>();
// Legal
AnimalContainer<String> a = new AnimalContainer<String>();
// Legal because Cat extends Animal
BoundedAnimalContainer<Cat> b = new BoundedAnimalContainer<Cat>();
// Illegal because String doesn't extends Animal
BoundedAnimalContainer<String> b = new BoundedAnimalContainer<String>();
`T`,` के बीच निर्णय लेना? सुपर टी`, और `? टी का विस्तार करता है
जावा जेनरिक के लिए सिंटैक्स, वाइल्डकार्ड से बंधे हैं, अज्ञात प्रकार का प्रतिनिधित्व करते हैं ?
है:
? extends T
एक ऊपरी बंधे वाइल्डकार्ड का प्रतिनिधित्व करती है। अज्ञात प्रकार एक प्रकार का प्रतिनिधित्व करता है जो T का एक उपप्रकार होना चाहिए, या स्वयं T टाइप करना चाहिए।? super T
एक कम बाउंडेड वाइल्डकार्ड का प्रतिनिधित्व करता है। अज्ञात प्रकार एक प्रकार का प्रतिनिधित्व करता है जो टी का एक सुपरटेप होना चाहिए, या स्वयं टी टाइप करना चाहिए।
अंगूठे के एक नियम के रूप में, आपको उपयोग करना चाहिए
-
? extends T
यदि आप केवल "पहुंच" ("इनपुट") पढ़ते हैं, तो? extends T
-
? super T
अगर आपको "लिखने" का उपयोग ("आउटपुट") चाहिए -
T
तुम दोनों ( "संशोधित") की जरूरत है
extends
या super
का उपयोग करना आमतौर पर बेहतर होता है क्योंकि यह आपके कोड को अधिक लचीला बनाता है (जैसा कि: उपप्रकारों और सुपरपेप्स के उपयोग की अनुमति देता है), जैसा कि आप नीचे देखेंगे।
class Shoe {}
class IPhone {}
interface Fruit {}
class Apple implements Fruit {}
class Banana implements Fruit {}
class GrannySmith extends Apple {}
public class FruitHelper {
public void eatAll(Collection<? extends Fruit> fruits) {}
public void addApple(Collection<? super Apple> apples) {}
}
कंपाइलर अब कुछ ख़राब उपयोग का पता लगा सकेगा:
public class GenericsTest {
public static void main(String[] args){
FruitHelper fruitHelper = new FruitHelper() ;
List<Fruit> fruits = new ArrayList<Fruit>();
fruits.add(new Apple()); // Allowed, as Apple is a Fruit
fruits.add(new Banana()); // Allowed, as Banana is a Fruit
fruitHelper.addApple(fruits); // Allowed, as "Fruit super Apple"
fruitHelper.eatAll(fruits); // Allowed
Collection<Banana> bananas = new ArrayList<>();
bananas.add(new Banana()); // Allowed
//fruitHelper.addApple(bananas); // Compile error: may only contain Bananas!
fruitHelper.eatAll(bananas); // Allowed, as all Bananas are Fruits
Collection<Apple> apples = new ArrayList<>();
fruitHelper.addApple(apples); // Allowed
apples.add(new GrannySmith()); // Allowed, as this is an Apple
fruitHelper.eatAll(apples); // Allowed, as all Apples are Fruits.
Collection<GrannySmith> grannySmithApples = new ArrayList<>();
fruitHelper.addApple(grannySmithApples); //Compile error: Not allowed.
// GrannySmith is not a supertype of Apple
apples.add(new GrannySmith()); //Still allowed, GrannySmith is an Apple
fruitHelper.eatAll(grannySmithApples);//Still allowed, GrannySmith is a Fruit
Collection<Object> objects = new ArrayList<>();
fruitHelper.addApple(objects); // Allowed, as Object super Apple
objects.add(new Shoe()); // Not a fruit
objects.add(new IPhone()); // Not a fruit
//fruitHelper.eatAll(objects); // Compile error: may contain a Shoe, too!
}
सही T
चुनना ? super T
या ? extends T
को उपप्रकारों के साथ उपयोग करने की अनुमति देना आवश्यक है। कंपाइलर तब प्रकार की सुरक्षा सुनिश्चित कर सकता है; यदि आप उन्हें ठीक से उपयोग करते हैं, तो आपको कास्ट करने की आवश्यकता नहीं है (जो सुरक्षित नहीं है, और प्रोग्रामिंग त्रुटियों का कारण बन सकता है)।
यदि यह समझना आसान नहीं है, तो कृपया PECS नियम याद रखें:
P roducer " E xtends" का उपयोग करता है और C Onumer " S uper" का उपयोग करता है।
(निर्माता के पास केवल लिखने की पहुंच है, और उपभोक्ता ने केवल पहुंच का उपयोग किया है)
सामान्य वर्ग और इंटरफ़ेस के लाभ
जेनेरिक का उपयोग करने वाले कोड में गैर-जेनेरिक कोड के कई लाभ हैं। नीचे मुख्य लाभ दिए गए हैं
संकलन के समय मजबूत प्रकार की जाँच
एक जावा कंपाइलर जेनेरिक कोड के लिए मजबूत प्रकार की जाँच लागू करता है और यदि कोड प्रकार की सुरक्षा का उल्लंघन करता है तो त्रुटियों को जारी करता है। संकलन समय त्रुटियों को ठीक करना रनटाइम त्रुटियों को ठीक करने से आसान है, जिसे ढूंढना मुश्किल हो सकता है।
जातियों का उन्मूलन
जेनरिक के बिना निम्नलिखित कोड स्निपेट को कास्टिंग की आवश्यकता होती है:
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
जब जेनरिक का उपयोग करने के लिए दोबारा लिखा जाता है, तो कोड को कास्टिंग की आवश्यकता नहीं होती है:
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0); // no cast
सामान्य एल्गोरिदम को लागू करने के लिए प्रोग्रामर को सक्षम करना
जेनरिक का उपयोग करके, प्रोग्रामर जेनेरिक एल्गोरिदम को लागू कर सकते हैं जो विभिन्न प्रकारों के संग्रह पर काम करते हैं, अनुकूलित किए जा सकते हैं, और पढ़ने के लिए सुरक्षित और आसान टाइप हैं।
1 से अधिक प्रकार से सामान्य पैरामीटर को बांधना
जेनेरिक पैरामीटर भी T extends Type1 & Type2 & ...
सिंटैक्स का उपयोग करके एक से अधिक प्रकारों के लिए बाध्य हो सकते हैं।
मान लीजिए कि आपकी एक वर्ग जिसका सामान्य प्रकार दोनों को लागू करना चाहिए बनाना चाहते हैं का कहना है कि Flushable
और Closeable
, आप लिख सकते हैं
class ExampleClass<T extends Flushable & Closeable> {
}
अब, ExampleClass
केवल सामान्य मापदंडों प्रकार है जो दोनों को लागू के रूप में स्वीकार करता है Flushable
और Closeable
।
ExampleClass<BufferedWriter> arg1; // Works because BufferedWriter implements both Flushable and Closeable
ExampleClass<Console> arg4; // Does NOT work because Console only implements Flushable
ExampleClass<ZipFile> arg5; // Does NOT work because ZipFile only implements Closeable
ExampleClass<Flushable> arg2; // Does NOT work because Closeable bound is not satisfied.
ExampleClass<Closeable> arg3; // Does NOT work because Flushable bound is not satisfied.
वर्ग तरीकों या तो के रूप में सामान्य प्रकार तर्क अनुमान लगाने के लिए चुन सकते हैं Closeable
या Flushable
।
class ExampleClass<T extends Flushable & Closeable> {
/* Assign it to a valid type as you want. */
public void test (T param) {
Flushable arg1 = param; // Works
Closeable arg2 = param; // Works too.
}
/* You can even invoke the methods of any valid type directly. */
public void test2 (T param) {
param.flush(); // Method of Flushable called on T and works fine.
param.close(); // Method of Closeable called on T and works fine too.
}
}
ध्यान दें:
आप या ( |
) क्लॉज़ का उपयोग करके या तो टाइप करने के लिए जेनेरिक पैरामीटर को बाँध नहीं सकते। केवल AND ( &
) क्लॉज़ का समर्थन किया जाता है। जेनेरिक प्रकार केवल एक वर्ग और कई इंटरफेस का विस्तार कर सकता है। कक्षा को सूची की शुरुआत में रखा जाना चाहिए।
जेनेरिक प्रकार का त्वरितकरण
टाइप इरेज़र के कारण निम्नलिखित कार्य नहीं करेगा:
public <T> void genericMethod() {
T t = new T(); // Can not instantiate the type T.
}
T
टाइप मिटा दिया जाता है। चूंकि, रनटाइम के दौरान, जेवीएम को पता नहीं है कि T
मूल रूप से क्या था, यह नहीं जानता कि किस निर्माता को कॉल करना है।
समाधान
genericMethod
कहते समयT
की कक्षाgenericMethod
:public <T> void genericMethod(Class<T> cls) { try { T t = cls.newInstance(); } catch (InstantiationException | IllegalAccessException e) { System.err.println("Could not instantiate: " + cls.getName()); } }
genericMethod(String.class);
जो अपवादों को फेंकता है, क्योंकि यह जानने का कोई तरीका नहीं है कि पारित वर्ग में एक सुलभ डिफ़ॉल्ट कंस्ट्रक्टर है या नहीं।
T
के कंस्ट्रक्टर के संदर्भ में पासिंग:public <T> void genericMethod(Supplier<T> cons) { T t = cons.get(); }
genericMethod(String::new);
अपनी खुद की घोषणा के भीतर घोषित सामान्य प्रकार का जिक्र
आप जिस विधि के रूप में घोषित किए जा रहे हैं, जेनेरिक प्रकार में ही (संभवतः आगे) विरासत में दिए गए जेनेरिक प्रकार के उदाहरण का उपयोग करने के बारे में कैसे जाना जाता है? यह उन समस्याओं में से एक है जिनका सामना आप तब करेंगे जब आप जेनेरिक में थोड़ी गहराई से खुदाई करेंगे, लेकिन फिर भी यह काफी सामान्य है।
मान लें कि हमारे पास एक DataSeries<T>
प्रकार (यहाँ इंटरफ़ेस) है, जो एक सामान्य डेटा श्रृंखला को परिभाषित करता है जिसमें टाइप T
का मान होता है। इस प्रकार के साथ सीधे काम करना बोझिल है जब हम उदाहरण के लिए डबल मान के साथ बहुत सारे ऑपरेशन करना चाहते हैं, इसलिए हम परिभाषित करते हैं कि DoubleSeries extends DataSeries<Double>
। अब मान लें, मूल DataSeries<T>
प्रकार में एक विधि add(values)
जो समान लंबाई की एक और श्रृंखला जोड़ता है और एक नया रिटर्न देता है। हम अपने व्युत्पन्न वर्ग में DataSeries<Double>
बजाय values
के प्रकार और रिटर्न के प्रकार को DoubleSeries
कैसे लागू करते हैं?
एक सामान्य प्रकार के पैरामीटर को जोड़कर और घोषित किए जा रहे प्रकार को निकालने के लिए समस्या को हल किया जा सकता है (यहां एक इंटरफ़ेस पर लागू किया जाता है, लेकिन वही कक्षाओं के लिए खड़ा है):
public interface DataSeries<T, DS extends DataSeries<T, DS>> {
DS add(DS values);
List<T> data();
}
यहाँ T
उस डेटा प्रकार का प्रतिनिधित्व करता है जो श्रृंखला रखती है, उदाहरण के लिए Double
और DS
श्रृंखला। एक विरासत में मिला प्रकार (या प्रकार) अब एक समान व्युत्पन्न प्रकार द्वारा उपरोक्त पैरामीटर को प्रतिस्थापित करके आसानी से लागू किया जा सकता है, इस प्रकार, फॉर्म की एक ठोस Double
आधारित परिभाषा की उपज:
public interface DoubleSeries extends DataSeries<Double, DoubleSeries> {
static DoubleSeries instance(Collection<Double> data) {
return new DoubleSeriesImpl(data);
}
}
इस समय भी एक IDE सही प्रकार के साथ उपरोक्त इंटरफ़ेस को लागू करेगा, जो सामग्री भरने के बाद कुछ इस तरह दिख सकता है:
class DoubleSeriesImpl implements DoubleSeries {
private final List<Double> data;
DoubleSeriesImpl(Collection<Double> data) {
this.data = new ArrayList<>(data);
}
@Override
public DoubleSeries add(DoubleSeries values) {
List<Double> incoming = values != null ? values.data() : null;
if (incoming == null || incoming.size() != data.size()) {
throw new IllegalArgumentException("bad series");
}
List<Double> newdata = new ArrayList<>(data.size());
for (int i = 0; i < data.size(); i++) {
newdata.add(this.data.get(i) + incoming.get(i)); // beware autoboxing
}
return DoubleSeries.instance(newdata);
}
@Override
public List<Double> data() {
return Collections.unmodifiableList(data);
}
}
जैसा कि आप देख सकते हैं कि add
मेथड को DoubleSeries add(DoubleSeries values)
घोषित किया गया है और कंपाइलर खुश है।
आवश्यकता होने पर पैटर्न को और भी नेस्टेड किया जा सकता है।
जेनरिक के साथ इंस्टाफॉ का उपयोग
Instof में प्रकार को परिभाषित करने के लिए जेनेरिक का उपयोग करना
औपचारिक पैरामीटर के साथ घोषित निम्नलिखित सामान्य वर्ग Example
पर विचार करें <T>
:
class Example<T> {
public boolean isTypeAString(String s) {
return s instanceof T; // Compilation error, cannot use T as class type here
}
}
यह हमेशा एक संकलन त्रुटि देगा क्योंकि जैसे ही संकलक जावा स्रोत को जावा बाइटकोड में संकलित करता है, यह एक प्रक्रिया है जो टाइप इरेज़र के रूप में जाना जाता है, जो सभी जेनेरिक कोड को गैर-जेनेरिक कोड में परिवर्तित करता है, जिससे रनटाइम के प्रकारों में अंतर करना असंभव हो जाता है। प्रकार के साथ प्रयोग किया instanceof
हो गया है reifiable , जो साधन प्रकार के बारे में सभी जानकारी रनटाइम पर उपलब्ध हो गया है कि, और यह आमतौर पर सामान्य प्रकार के लिए ऐसा नहीं है।
निम्न वर्ग दर्शाता है कि Example
दो अलग-अलग वर्ग, Example<String>
और Example<Number>
, जैसे कि जेनेरिक ने टाइप इरेज़र द्वारा छीन लिया है:
class Example { // formal parameter is gone
public boolean isTypeAString(String s) {
return s instanceof Object; // Both <String> and <Number> are now Object
}
}
चूंकि प्रकार चले गए हैं, इसलिए जेवीएम के लिए यह जानना संभव नहीं है कि कौन सा प्रकार T
।
पिछले नियम से अपवाद
आप हमेशा एक प्रकार के वाइल्डकार्ड का उपयोग कर सकते हैं (?) निम्न प्रकार के instanceof
में निर्दिष्ट करने के instanceof
:
public boolean isAList(Object obj) {
return obj instanceof List<?>;
}
यह मूल्यांकन करने के लिए उपयोगी हो सकता है कि क्या एक उदाहरण obj
एक List
या नहीं:
System.out.println(isAList("foo")); // prints false
System.out.println(isAList(new ArrayList<String>()); // prints true
System.out.println(isAList(new ArrayList<Float>()); // prints true
वास्तव में, अनबाउंडेड वाइल्डकार्ड एक रिफ़ेक्टिव प्रकार माना जाता है।
Instof के साथ एक सामान्य उदाहरण का उपयोग करना
सिक्के का दूसरा पहलू यह है कि t
के साथ T
के instanceof
का उपयोग करना कानूनी है, जैसा कि निम्नलिखित उदाहरण में दिखाया गया है:
class Example<T> {
public boolean isTypeAString(T t) {
return t instanceof String; // No compilation error this time
}
}
क्योंकि प्रकार के क्षरण के बाद वर्ग निम्न की तरह दिखेगा:
class Example { // formal parameter is gone
public boolean isTypeAString(Object t) {
return t instanceof String; // No compilation error this time
}
}
चूंकि, भले ही प्रकार का क्षरण कुछ भी हो, अब JVM विभिन्न प्रकारों के बीच स्मृति में अंतर कर सकता है, भले ही वे एक ही संदर्भ प्रकार ( Object
) का उपयोग करें, जैसा कि निम्नलिखित स्निपेट दिखाता है:
Object obj1 = new String("foo"); // reference type Object, object type String
Object obj2 = new Integer(11); // reference type Object, object type Integer
System.out.println(obj1 instanceof String); // true
System.out.println(obj2 instanceof String); // false, it's an Integer, not a String
जेनेरिक इंटरफ़ेस लागू करने के विभिन्न तरीके (या जेनेरिक क्लास का विस्तार)
मान लीजिए कि निम्नलिखित सामान्य इंटरफ़ेस घोषित किया गया है:
public interface MyGenericInterface<T> {
public void foo(T t);
}
नीचे इसे लागू करने के संभावित तरीके सूचीबद्ध हैं।
एक विशिष्ट प्रकार के साथ गैर-सामान्य वर्ग कार्यान्वयन
औपचारिक प्रकार के पैरामीटर को बदलने के लिए एक विशिष्ट प्रकार चुनें MyGenericClass
का <T>
और इसे लागू करें, जैसा कि निम्नलिखित उदाहरण है:
public class NonGenericClass implements MyGenericInterface<String> {
public void foo(String t) { } // type T has been replaced by String
}
यह वर्ग केवल String
, और इसका अर्थ है कि विभिन्न मापदंडों के साथ MyGenericInterface
का उपयोग करना (जैसे Integer
, Object
आदि) संकलन नहीं करेगा, जैसा कि निम्नलिखित स्निपेट दिखाता है:
NonGenericClass myClass = new NonGenericClass();
myClass.foo("foo_string"); // OK, legal
myClass.foo(11); // NOT OK, does not compile
myClass.foo(new Object()); // NOT OK, does not compile
सामान्य वर्ग कार्यान्वयन
औपचारिक टाइप पैरामीटर <T>
साथ एक और सामान्य इंटरफ़ेस घोषित करें, जो निम्नानुसार MyGenericInterface
लागू करता है:
public class MyGenericSubclass<T> implements MyGenericInterface<T> {
public void foo(T t) { } // type T is still the same
// other methods...
}
ध्यान दें कि एक अलग औपचारिक प्रकार पैरामीटर का उपयोग किया जा सकता है, निम्नानुसार है:
public class MyGenericSubclass<U> implements MyGenericInterface<U> { // equivalent to the previous declaration
public void foo(U t) { }
// other methods...
}
कच्चा प्रकार वर्ग कार्यान्वयन
एक गैर-सामान्य वर्ग की घोषणा करें जो MyGenericInteface
को एक कच्चे प्रकार के रूप में लागू करता है (जेनेरिक का उपयोग नहीं करना), निम्नानुसार है:
public class MyGenericSubclass implements MyGenericInterface {
public void foo(Object t) { } // type T has been replaced by Object
// other possible methods
}
यह तरीका अनुशंसित नहीं है , क्योंकि यह रनटाइम के दौरान 100% सुरक्षित नहीं है क्योंकि यह जेनेरिक (इंटरफ़ेस के) के साथ कच्चे प्रकार (उपवर्ग) को मिलाता है और यह भी भ्रमित है। आधुनिक जावा कंपाइलर इस तरह के कार्यान्वयन के साथ एक चेतावनी देगा, फिर भी कोड - पुराने जेवीएम (1.4 या पहले) के साथ संगतता कारणों के लिए - संकलन करेगा।
उपरोक्त सभी तरीकों की अनुमति भी है जब एक सामान्य इंटरफ़ेस के बजाय एक जेनेरिक क्लास को सुपरपाइप के रूप में उपयोग किया जाता है।
ऑटो-कास्ट के लिए जेनरिक का उपयोग करना
जेनेरिक के साथ, कॉलर को जो भी उम्मीद है उसे वापस करना संभव है:
private Map<String, Object> data;
public <T> T get(String key) {
return (T) data.get(key);
}
विधि एक चेतावनी के साथ संकलन करेगी। कोड वास्तव में अधिक सुरक्षित है क्योंकि यह दिखता है क्योंकि जावा रनटाइम एक कास्ट करेगा जब आप इसका उपयोग करेंगे:
Bar bar = foo.get("bar");
जब आप सामान्य प्रकारों का उपयोग करते हैं तो यह कम सुरक्षित होता है:
List<Bar> bars = foo.get("bars");
यहां, जब कोई प्रकार की List
(यानी रिटर्निंग List<String>
किसी ClassCastException
ट्रिगर नहीं करेगा) कास्ट होने पर कास्ट काम करेगा (आप सूची से तत्वों को निकालते समय अंततः इसे प्राप्त करेंगे)।
इस समस्या को हल करने के लिए, आप एक API बना सकते हैं जो टाइप की गई कुंजियों का उपयोग करता है:
public final static Key<List<Bar>> BARS = new Key<>("BARS");
इस put()
के साथ put()
विधि:
public <T> T put(Key<T> key, T value);
इस दृष्टिकोण के साथ, आप गलत प्रकार को मानचित्र में नहीं डाल सकते हैं, इसलिए परिणाम हमेशा सही होगा (जब तक कि आप गलती से एक ही नाम से दो कुंजी नहीं बनाते हैं लेकिन विभिन्न प्रकार के होते हैं)।
सम्बंधित:
रनटाइम पर सामान्य पैरामीटर को संतुष्ट करने वाला वर्ग प्राप्त करें
कई अनबाउंड जेनेरिक पैरामीटर, जैसे स्थैतिक विधि में उपयोग किए जाने वाले, रनटाइम पर पुनर्प्राप्त नहीं किए जा सकते हैं ( इरेज़र पर अन्य थ्रेड्स देखें)। हालाँकि, रनटाइम के समय एक सामान्य पैरामीटर को एक वर्ग पर संतुष्ट करने के लिए नियोजित एक सामान्य रणनीति है। यह सामान्य कोड की अनुमति देता है जो हर कॉल के माध्यम से थ्रेड प्रकार की जानकारी के बिना टाइप करने के लिए उपयोग पर निर्भर करता है।
पृष्ठभूमि
अनाम आंतरिक वर्ग बनाकर एक वर्ग पर सामान्य पैरामीटर का निरीक्षण किया जा सकता है। यह वर्ग प्रकार की जानकारी कैप्चर करेगा। सामान्य तौर पर इस तंत्र को सुपर प्रकार के टोकन के रूप में जाना जाता है, जो कि नील गेर के ब्लॉग पोस्ट में विस्तृत हैं।
क्रियान्वयन
जावा में तीन सामान्य कार्यान्वयन हैं:
उदाहरण उपयोग
public class DataService<MODEL_TYPE> {
private final DataDao dataDao = new DataDao();
private final Class<MODEL_TYPE> type = (Class<MODEL_TYPE>) new TypeToken<MODEL_TYPE>
(getClass()){}.getRawType();
public List<MODEL_TYPE> getAll() {
return dataDao.getAllOfType(type);
}
}
// the subclass definitively binds the parameterization to User
// for all instances of this class, so that information can be
// recovered at runtime
public class UserService extends DataService<User> {}
public class Main {
public static void main(String[] args) {
UserService service = new UserService();
List<User> users = service.getAll();
}
}