Type Inference توانایی کامپایلر جاوا برای نگاه کردن به هر فراخوانی متد و اعلان متناظر و تعیین Type Argument (یا Type Argumentها) است که فراخوانی را ممکن میسازد. الگوریتم استنباط نوعهای آرگومانها و اگر در دسترس باشد، نوعی که نتیجه به آن اختصاص داده شده یا برگردانده شده را تعیین میکند. در نهایت، الگوریتم استنباط سعی میکند مشخصترین نوعی را پیدا کند که با تمام آرگومانها کار کند.
برای نشان دادن این نکتهی آخر، در مثال زیر، استنباط تعیین میکند که آرگومان دومی که به متد pick ارسال شده از نوع Serializable است:
static <T> T pick(T a1, T a2) { return a2; }
Serializable s = pick("d", new ArrayList<String>());
بخش متدهای Generic شما را با Type Inference آشنا کرد که به شما امکان میدهد متد Generic را مثل یک متد معمولی فراخوانی کنید، بدون اینکه نوعی بین زاویهدارها مشخص کنید. مثال BoxDemo زیر که کلاس Box را نیاز دارد را در نظر بگیرید:
public class BoxDemo {
public static <U> void addBox(U u,
java.util.List<Box<U>> boxes) {
Box<U> box = new Box<>();
box.set(u);
boxes.add(box);
}
public static <U> void outputBoxes(java.util.List<Box<U>> boxes) {
int counter = 0;
for (Box<U> box: boxes) {
U boxContents = box.get();
IO.println("Box #" + counter + " contains [" +
boxContents.toString() + "]");
counter++;
}
}
public static void main(String[] args) {
java.util.ArrayList<Box<Integer>> listOfIntegerBoxes =
new java.util.ArrayList<>();
BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);
BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);
BoxDemo.addBox(Integer.valueOf(30), listOfIntegerBoxes);
BoxDemo.outputBoxes(listOfIntegerBoxes);
}
}
خروجی این مثال به این صورت است:
Box #0 contains [10]
Box #1 contains [20]
Box #2 contains [30]
متد Generic addBox() یک Type Parameter به نام T تعریف میکند. معمولاً کامپایلر جاوا میتواند Type Parameterهای فراخوانی متد Generic را استنباط کند. در نتیجه، در بیشتر موارد مجبور نیستید آنها را مشخص کنید. مثلاً برای فراخوانی متد Generic addBox()، میتوانید Type Parameter را با Type Witness مشخص کنید:
BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);
جایگزین این است که اگر Type Witness را حذف کنید، کامپایلر جاwa بهطور خودکار از آرگومانهای متد استنباط میکند که Type Parameter Integer است:
BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);
---
میتوانید Type Argumentهای لازم برای فراخوانی Constructor یک کلاس Generic را با مجموعهی خالی Type Parameter (<>) جایگزین کنید، به شرطی که کامپایلر بتواند Type Argumentها را از بافت استنباط کند. این جفت زاویهدار بهطور غیررسمی Diamond نامیده میشود.
مثال، اعلان متغیر زیر:
Map<String, List<String>> myMap = new HashMap<String, List<String>>();
میتوانید نوع پارامتریشدهی Constructor را با مجموعهی خالی Type Parameter (<>) جایگزین کنید:
Map<String, List<String>> myMap = new HashMap<>();
توجه کنید که برای بهرهبرداری از Type Inference هنگام نمونهسازی کلاس Generic، باید از Diamond استفاده کنید. در مثال زیر، کامپایلر هشدار تبدیل Unchecked تولید میکند چون Constructor HashMap() به نوع Raw HashMap ارجاع میدهد نه نوع Map<String, List<String>>:
Map<String, List<String>> myMap = new HashMap(); // unchecked conversion warning
---
توجه کنید که Constructorها میتوانند هم در کلاسهای Generic و هم در کلاسهای غیرGeneric، Generic باشند (به عبارت دیگر، Type Parameter رسمی خودشان را اعلان کنند). مثال زیر را در نظر بگیرید:
class MyClass<X> {
<T> MyClass(T t) {
// ...
}
}
نمونهسازی زیر از کلاس MyClass را در نظر بگیرید:
new MyClass<Integer>("")
این عبارت نمونهای از نوع پارامتریشده MyClass<Integer> ایجاد میکند؛ عبارت نوع Integer را صریحاً برای Type Parameter رسمی X کلاس Generic MyClass<X> مشخص میکند. توجه کنید Constructor این کلاس Generic شامل Type Parameter رسمی T است. کامپایلر نوع String را برای Type Parameter رسمی T Constructor این کلاس Generic استنباط میکند (چون پارامتر واقعی این Constructor یک شی String است).
کامپایلرهای نسخههای قبل از جاوا SE 7 قادر به استنباط Type Parameterهای واقعی Constructorهای Generic هستند، مشابه متدهای Generic. اما کامپایلرهای جاوا SE 7 و بعد میتوانند Type Parameterهای واقعی کلاس Generic در حال نمونهسازی را استنباط کنند اگر از Diamond (<>) استفاده کنید. مثال زیر را در نظر بگیرید:
MyClass<Integer> myObject = new MyClass<>("");
در این مثال، کامپایلر نوع Integer را برای Type Parameter رسمی X کلاس Generic MyClass<X> استنباط میکند. نوع String را برای Type Parameter رسمی T Constructor این کلاس Generic استنباط میکند.
---نکته: مهم است بدانید الگوریتم استنباط فقط از آرگومانهای فراخوانی، نوعهای هدف و احتمالاً نوع بازگشتی آشکار مورد انتظار برای استنباط نوعها استفاده میکند. الگوریتم استنباط از نتایج بعدی در برنامه استفاده نمیکند.
کامپایلر جاوا از Target Typing برای استنباط Type Parameterهای فراخوانی متد Generic استفاده میکند. نوع هدف یک عبارت، نوع دادهای است که کامپایلر جاوا بسته به جایی که عبارت ظاهر میشود انتظار دارد. متد Collections.emptyList() که به این صورت اعلان شده را در نظر بگیرید:
static <T> List<T> emptyList();
عبارت اختصاص زیر را در نظر بگیرید:
List<String> listOne = Collections.emptyList();
این عبارت انتظار نمونهای از List<String> را دارد — این نوع دادهای، نوع هدف است. چون متد emptyList() مقداری از نوع List<T> برمیگرداند، کامپایلر استنباط میکند Type Argument T باید مقدار String باشد. این در جاوا SE 7 و 8 کار میکند. جایگزین این است که میتوانید Type Witness استفاده کنید و مقدار T را به این صورت مشخص کنید:
List<String> listOne = Collections.<String>emptyList();
اما این در این بافت ضروری نیست. اما در بافتهای دیگر ضروری بود. متد زیر را در نظر بگیرید:
void processStringList(List<String> stringList) {
// process stringList
}
فرض کنید میخواهید متد processStringList() را با لیست خالی فراخوانی کنید. در جاوا SE 7، عبارت زیر کامپایل نمیشود:
processStringList(Collections.emptyList());
کامپایلر جاوا SE 7 پیام خطای مشابه زیر را تولید میکند:
List<Object> cannot be converted to List<String>
کامپایلر مقداری برای Type Argument T نیاز دارد پس با مقدار Object شروع میکند. در نتیجه، فراخوانی Collections.emptyList() مقداری از نوع List<Object> برمیگرداند که با متد processStringList() ناسازگار است. بنابراین در جاوا SE 7، باید مقدار Type Argument را به این صورت مشخص کنید:
processStringList(Collections.<String>emptyList());
این دیگر در جاوا SE 8 ضروری نیست. مفهوم نوع هدف گسترش یافته و آرگومانهای متد مثل آرگومان متد processStringList() را هم شامل میشود. در این حالت، processStringList() آرگومانی از نوع List<String> نیاز دارد. متد Collections.emptyList() مقداری از List<T> برمیگرداند، پس با استفاده از نوع هدف List<String>، کامپایلر استنباط میکند Type Argument T مقدار String دارد. بنابراین در جاوا SE 8، عبارت زیر کامپایل میشود:
processStringList(Collections.emptyList());
---
فرض کنید متدهای زیر را دارید:
public static void printPersons(List<Person> roster, CheckPerson tester)
و
public void printPersonsWithPredicate(List<Person> roster, Predicate<Person> tester)
سپس کد زیر را برای فراخوانی این متدها مینویسید:
printPersons(
people,
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25);
و
printPersonsWithPredicate(
people,
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25);)
نوع Lambda Expression را در این موارد چگونه تعیین میکنید؟
وقتی زمان اجرای جاوا متد printPersons() را فراخوانی میکند، نوع دادهی CheckPerson را انتظار دارد، پس Lambda Expression از این نوع است. اما وقتی متد printPersonsWithPredicate() فراخوانی میشود، نوع دادهی Predicate<Person> را انتظار دارد، پس Lambda Expression از این نوع است. نوع دادهای که این متدها انتظار دارند نوع هدف نامیده میشود. برای تعیین نوع یک Lambda Expression، کامپایلر جاوا از نوع هدف بافت یا موقعیتی که Lambda Expression در آن یافت شده استفاده میکند. نتیجه اینکه فقط میتوانید در موقعیتهایی از Lambda Expression استفاده کنید که کامپایلر جاوا بتواند نوع هدف را تعیین کند:
?:برای آرگومانهای متد، کامپایلر جاوا نوع هدف را با دو ویژگی دیگر زبان تعیین میکند: حل Overload و استنباط Type Argument.
دو Functional Interface زیر (java.lang.Runnable و java.util.concurrent.Callable<V>) را در نظر بگیرید:
public interface Runnable {
void run();
}
public interface Callable<V> {
V call();
}
متد Runnable.run() مقداری برنمیگرداند، در حالی که Callable<V>.call() برمیگرداند.
فرض کنید متد invoke را Overload کردهاید (اطلاعات بیشتر درباره Overload کردن متدها در بخش تعریف متدها):
void invoke(Runnable r) {
r.run();
}
<T> T invoke(Callable<T> c) {
return c.call();
}
کدام متد در عبارت زیر فراخوانی خواهد شد؟
String s = invoke(() -> "done");
متد invoke(Callable<T>) فراخوانی خواهد شد چون آن متد مقداری برمیگرداند؛ متد invoke(Runnable) چنین نمیکند. در این حالت، نوع Lambda Expression () -> "done" برابر Callable<T> است.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://dev.java/learn/