میتوانید از Wildcard با حد بالا برای شل کردن محدودیتهای یک متغیر استفاده کنید. مثلاً فرض کنید میخواهید متدی بنویسید که روی List<Integer>، List<Double> و List<Number> کار کند؛ این کار را میتوانید با Wildcard با حد بالا انجام دهید.
برای اعلان Wildcard با حد بالا، از کاراکتر Wildcard ('?')، بعد از آن کلمهکلیدی extends و سپس حد بالای آن استفاده کنید. توجه کنید که در این بافت، extends به معنای عام به کار میرود و هم extends (برای کلاسها) و هم implements (برای Interfaceها) را شامل میشود.
برای نوشتن متدی که روی لیستهای Number و زیرنوعهای Number مثل Integer، Double و Float کار کند، باید List<? extends Number> مشخص کنید. عبارت List<Number> محدودکنندهتر از List<? extends Number> است چون اولی فقط لیستی از نوع Number را تطبیق میدهد، در حالی که دومی لیستی از نوع Number یا هر زیرکلاس آن را تطبیق میدهد.
متد process زیر را در نظر بگیرید:
public static void process(List<? extends Foo> list) { /* ... */ }
Wildcard با حد بالا، <? extends Foo>، که Foo هر نوعی است، Foo و هر زیرنوعی از Foo را تطبیق میدهد. متد process میتواند عناصر لیست را به عنوان نوع Foo بخواند:
public static void process(List<? extends Foo> list) {
for (Foo elem : list) {
// ...
}
}
در عبارت foreach، متغیر elem روی هر عنصر در لیست تکرار میشود. هر متدی که در کلاس Foo تعریف شده اکنون میتواند روی elem اعمال شود.
متد sumOfList() مجموع اعداد در لیست را برمیگرداند:
public static double sumOfList(List<? extends Number> list) {
double s = 0.0;
for (Number n : list)
s += n.doubleValue();
return s;
}
کد زیر با استفاده از لیستی از اشیای Integer، مقدار sum = 6.0 را چاپ میکند:
List<Integer> li = Arrays.asList(1, 2, 3);
IO.println("sum = " + sumOfList(li));
لیستی از مقادیر Double هم میتواند از همان متد sumOfList() استفاده کند. کد زیر مقدار sum = 7.0 را چاپ میکند:
List<Double> ld = Arrays.asList(1.2, 2.3, 3.5);
IO.println("sum = " + sumOfList(ld));
---
نوع Wildcard بدون حد با کاراکتر Wildcard (?) مشخص میشود، مثلاً List<?>. به این لیست نوع ناشناخته گفته میشود. دو سناریو وجود دارد که Wildcard بدون حد رویکرد مفیدی است:
Object پیادهسازی شود.List.size() یا List.clear(). در واقع Class<?> به این دلیل زیاد استفاده میشود که بیشتر متدهای Class<T> به T وابسته نیستند.متد printList() زیر را در نظر بگیرید:
public static void printList(List<Object> list) {
for (Object elem : list)
IO.println(elem + " ");
IO.println();
}
هدف printList() چاپ لیستی از هر نوعی است، اما این هدف را محقق نمیکند — فقط لیستی از نمونههای Object را چاپ میکند؛ نمیتواند List<Integer>، List<String>، List<Double> و غیره را چاپ کند، چون زیرنوع List<Object> نیستند. برای نوشتن متد Generic printList()، از List<?> استفاده کنید:
public static void printList(List<?> list) {
for (Object elem: list)
IO.print(elem + " ");
IO.println();
}
چون برای هر نوع مشخص A، List<A> زیرنوع List<?> است، میتوانید از printList() برای چاپ لیستی از هر نوعی استفاده کنید:
List<Integer> li = Arrays.asList(1, 2, 3);
List<String> ls = Arrays.asList("one", "two", "three");
printList(li);
printList(ls);
نکته: متد
Arrays.asList()در مثالهای سراسر این بخش استفاده میشود. این متد factory ثابت آرایهی مشخصشده را تبدیل و لیستی با اندازه ثابت برمیگرداند.
مهم است توجه کنید List<Object> و List<?> یکی نیستند. میتوانید Object یا هر زیرنوعی از Object را در List<Object> درج کنید. اما فقط null میتوانید در List<?> درج کنید. بخش راهنمای استفاده از Wildcard در انتهای این بخش اطلاعات بیشتری درباره نحوهی تعیین نوع Wildcard مناسب دارد.
بخش Wildcards با حد بالا نشان داد که Wildcard با حد بالا نوع ناشناخته را به نوع مشخص یا زیرنوع آن محدود میکند و با کلمهکلیدی extends نشان داده میشود. به همین شکل، Wildcard با حد پایین نوع ناشناخته را به نوع مشخص یا فوقنوع آن محدود میکند.
Wildcard با حد پایین با کاراکتر Wildcard ('?')، بعد از آن کلمهکلیدی super و سپس حد پایین آن بیان میشود: <? super A>.
نکته: میتوانید حد بالا یا حد پایین برای Wildcard مشخص کنید، اما نمیتوانید هر دو را همزمان مشخص کنید.
فرض کنید میخواهید متدی بنویسید که اشیای Integer را در لیستی قرار دهد. برای حداکثر انعطافپذیری، میخواهید متد روی List<Integer>، List<Number> و List<Object> کار کند — هر چیزی که بتواند مقادیر Integer را نگه دارد.
برای نوشتن متدی که روی لیستهای Integer و فوقنوعهای Integer مثل Integer، Number و Object کار کند، باید List<? super Integer> مشخص کنید. عبارت List<Integer> محدودکنندهتر از List<? super Integer> است چون اولی فقط لیستی از نوع Integer را تطبیق میدهد، در حالی که دومی لیستی از هر نوعی که فوقنوع Integer باشد را تطبیق میدهد.
کد زیر اعداد ۱ تا ۱۰ را به انتهای لیست اضافه میکند:
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
بخش راهنمای استفاده از Wildcard در انتهای این بخش راهنمایی دربارهی زمان استفاده از Wildcards با حد بالا و زمان استفاده از Wildcards با حد پایین ارائه میدهد.
---همانطور که در بخشهای قبلی توصیف شد، کلاسها یا Interfaceهای Generic فقط به خاطر وجود رابطه بین نوعهایشان مرتبط نیستند. اما میتوانید از Wildcards برای ایجاد رابطه بین کلاسها یا Interfaceهای Generic استفاده کنید.
دو کلاس معمولی (غیرGeneric) زیر را در نظر بگیرید:
class A { /* ... */ }
class B extends A { /* ... */ }
نوشتن کد زیر منطقی است:
B b = new B();
A a = b;
این مثال نشان میدهد ارثبری کلاسهای معمولی از این قاعدهی زیرنوعسازی پیروی میکند: کلاس B زیرنوع کلاس A است اگر B از A ارث ببرد. این قاعده برای انواع Generic اعمال نمیشود:
List<B> lb = new ArrayList<>();
List<A> la = lb; // compile-time error
با فرض اینکه Integer زیرنوع Number است، رابطه بین List<Integer> و List<Number> چیست؟
اگرچه Integer زیرنوع Number است، List<Integer> زیرنوع List<Number> نیست و در واقع این دو نوع مرتبط نیستند. والد مشترک List<Number> و List<Integer> برابر List<?> است.
برای ایجاد رابطه بین این کلاسها تا کد بتواند از متدهای Number از طریق عناصر List<Integer> استفاده کند، از Wildcard با حد بالا استفاده کنید:
List<? extends Integer> intList = new ArrayList<>();
// This is OK because List<? extends Integer> is a subtype of List<? extends Number>
List<? extends Number> numList = intList;
چون Integer زیرنوع Number است و numList لیستی از اشیای Number است، اکنون رابطهای بین intList (لیستی از اشیای Integer) و numList وجود دارد.
طبق همان قواعد، List<? extends Number> میتواند توسط لیستی از هر نوعی که امتداد Number است گسترش یابد، از جمله خود Number.
و همین موضوع درباره رابطه بین List<? super Integer> و List<Integer> صادق است.
بخش راهنمای استفاده از Wildcard در انتهای این بخش اطلاعات بیشتری درباره پیامدهای استفاده از Wildcards با حد بالا و حد پایین دارد.
---در برخی موارد، کامپایلر نوع Wildcard را استنباط میکند. مثلاً لیست ممکن است List<?> تعریف شده باشد اما هنگام ارزیابی عبارت، کامپایلر نوع خاصی را از کد استنباط میکند. این سناریو به عنوان Wildcard Capture شناخته میشود.
در بیشتر موارد، نیازی نیست نگران Wildcard Capture باشید، مگر اینکه پیام خطایی حاوی عبارت «capture of» ببینید.
مثال WildcardError هنگام کامپایل خطای Capture تولید میکند:
import java.util.List;
public class WildcardError {
void foo(List<?> i) {
i.set(0, i.get(0));
}
}
در این مثال، کامپایلر پارامتر ورودی i را از نوع Object پردازش میکند. وقتی متد foo متد List.set(int, E) را فراخوانی میکند، کامپایلر قادر به تأیید نوع شیای که در لیست درج میشود نیست و خطا تولید میشود. وقتی این نوع خطا رخ میدهد، معمولاً به این معناست که کامپایلر معتقد است نوع اشتباهی به متغیر اختصاص میدهید. Generics به دلیل همین به زبان جاوا اضافه شدند — برای تحمیل ایمنی نوع در زمان کامپایل.
مثال WildcardError با پیادهسازی Oracle's JDK 7 javac خطای زیر را هنگام کامپایل تولید میکند:
WildcardError.java:6: error: method set in interface List<E> cannot be applied to given types;
i.set(0, i.get(0));
^
required: int,CAP#1
found: int,Object
reason: actual argument Object cannot be converted to CAP#1 by method invocation conversion
where E is a type-variable:
E extends Object declared in interface List
where CAP#1 is a fresh type-variable:
CAP#1 extends Object from capture of ?
1 error
در این مثال، کد سعی در انجام عملیات ایمن دارد. پس چگونه میتوانید این خطای کامپایل را دور بزنید؟ میتوانید با نوشتن متد کمکی خصوصی که Wildcard را Capture میکند، آن را رفع کنید. روش حل مشکل با ایجاد متد کمکی خصوصی fooHelper() همانطور که در WildcardFixed نشان داده شده:
public class WildcardFixed {
void foo(List<?> i) {
fooHelper(i);
}
// Helper method created so that the wildcard can be captured
// through type inference.
private <T> void fooHelper(List<T> l) {
l.set(0, l.get(0));
}
}
به لطف متد کمکی، کامپایلر از inference استفاده میکند و تعیین میکند T برابر CAP#1، متغیر Capture، در فراخوانی است. مثال اکنون با موفقیت کامپایل میشود.
طبق قرارداد، متدهای کمکی معمولاً originalMethodNameHelper() نامگذاری میشوند.
اکنون مثال پیچیدهتری به نام WildcardErrorBad را در نظر بگیرید:
import java.util.List;
public class WildcardErrorBad {
void swapFirst(List<? extends Number> l1, List<? extends Number> l2) {
Number temp = l1.get(0);
l1.set(0, l2.get(0)); // expected a CAP#1 extends Number,
// got a CAP#2 extends Number;
// same bound, but different types
l2.set(0, temp); // expected a CAP#1 extends Number,
// got a Number
}
}
در این مثال، کد سعی در انجام عملیات ناامن دارد. فراخوانی زیر از متد swapFirst() را در نظر بگیرید:
List<Integer> li = Arrays.asList(1, 2, 3);
List<Double> ld = Arrays.asList(10.10, 20.20, 30.30);
swapFirst(li, ld);
اگرچه List<Integer> و List<Double> هر دو معیارهای List<? extends Number> را برآورده میکنند، مشخصاً اشتباه است که آیتمی از لیست مقادیر Integer برداشته و سعی کنید آن را در لیست مقادیر Double قرار دهید.
کامپایل کد با کامپایلر Oracle's JDK javac خطای زیر را تولید میکند:
WildcardErrorBad.java:7: error: method set in interface List<E> cannot be applied to given types;
l1.set(0, l2.get(0)); // expected a CAP#1 extends Number,
^
required: int,CAP#1
found: int,Number
reason: actual argument Number cannot be converted to CAP#1 by method invocation conversion
where E is a type-variable:
E extends Object declared in interface List
where CAP#1 is a fresh type-variable:
CAP#1 extends Number from capture of ? extends Number
WildcardErrorBad.java:10: error: method set in interface List<E> cannot be applied to given types;
l2.set(0, temp); // expected a CAP#1 extends Number,
^
required: int,CAP#1
found: int,Number
reason: actual argument Number cannot be converted to CAP#1 by method invocation conversion
where E is a type-variable:
E extends Object declared in interface List
where CAP#1 is a fresh type-variable:
CAP#1 extends Number from capture of ? extends Number
WildcardErrorBad.java:15: error: method set in interface List<E> cannot be applied to given types;
i.set(0, i.get(0));
^
required: int,CAP#1
found: int,Object
reason: actual argument Object cannot be converted to CAP#1 by method invocation conversion
where E is a type-variable:
E extends Object declared in interface List
where CAP#1 is a fresh type-variable:
CAP#1 extends Object from capture of ?
3 errors
هیچ متد کمکی برای دور زدن مشکل وجود ندارد، چون کد اساساً اشتباه است: مشخصاً اشتباه است که آیتمی از لیست مقادیر Integer برداشته و سعی کنید آن را در لیست مقادیر Double قرار دهید.
یکی از گیجکنندهترین جنبهها هنگام یادگیری برنامهنویسی با Generics، تعیین زمان استفاده از Wildcard با حد بالا و زمان استفاده از Wildcard با حد پایین است. این صفحه راهنماییهایی برای طراحی کد ارائه میدهد.
برای بحث حاضر، مفید است متغیرها را به عنوان ارائهدهندهی یکی از دو عملکرد در نظر بگیرید:
copy(src, dest). آرگومان src دادهی مورد نظر برای کپی را فراهم میکند، پس پارامتر «ورودی» است.copy(src, dest)، آرگومان dest داده را میپذیرد، پس پارامتر «خروجی» است.البته برخی متغیرها هم برای اهداف «ورودی» و هم «خروجی» استفاده میشوند — این سناریو هم در راهنما پوشش داده شده.
هنگام تصمیمگیری برای استفاده از Wildcard و نوع مناسب Wildcard، میتوانید از اصل «ورودی» و «خروجی» استفاده کنید:
extends تعریف میشود.super تعریف میشود.Object قابل دسترسی باشد، از Wildcard بدون حد استفاده کنید.این راهنماها برای نوع بازگشتی متد اعمال نمیشوند. استفاده از Wildcard به عنوان نوع بازگشتی باید اجتناب شود چون برنامهنویسان استفادهکنندهی کد را مجبور میکند با Wildcards سر و کار داشته باشند.
لیستی که با List<? extends ...> تعریف شده میتواند بهطور غیررسمی فقط-خواندنی (read-only) در نظر گرفته شود، اما تضمین سختگیرانهای نیست. کلاسهای زیر را در نظر بگیرید:
class NaturalNumber {
private int i;
public NaturalNumber(int i) { this.i = i; }
// ...
}
class EvenNumber extends NaturalNumber {
public EvenNumber(int i) { super(i); }
// ...
}
کد زیر را در نظر بگیرید:
List<EvenNumber> le = new ArrayList<>();
List<? extends NaturalNumber> ln = le;
ln.add(new NaturalNumber(35)); // compile-time error
چون List<EvenNumber> زیرنوع List<? extends NaturalNumber> است، میتوانید le را به ln اختصاص دهید. اما نمیتوانید از ln برای اضافه کردن عدد طبیعی به لیست اعداد زوج استفاده کنید. عملیات زیر روی لیست ممکن هستند:
null اضافه کنید.clear() را فراخوانی کنید.remove() را فراخوانی کنید.میتوانید ببینید لیستی که با List<? extends NaturalNumber> تعریف شده در معنای سختگیرانه فقط-خواندنی نیست، اما ممکن است اینطور فکر کنید چون نمیتوانید عنصر جدیدی در لیست ذخیره یا عنصر موجود را تغییر دهید.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://dev.java/learn/