۵ نکته مهم در مورد انواع دادهعام جاوا
انواع داده عام در جاوا از بحث برانگیزترین ویژگیهای زبان جاواست. در این مقاله ۵ نکتهای که هر توسعهدهنده جاوا لازم است در مورد انواع داده عام بداند را مطرح میکنیم.
انواع داده عام اجازه میدهد بدون هیچ خطایی در زمان کامپایل یک نوع داده یا تابع روی اشیائی از انواع مختلف عمل کند و اینچنین زبان جاوا را یک زبان کاملا statically typed نماید. در ادامه پنج نکته مهم در مورد این قابلیت بحثبرانگیز جاوا را مطرح میکنیم.
۱- انواع داده عام با استفاده از Type Eraser پیادهسازی میشوند.
در جاوا به منظور تعریف کردن نوع یک یا چند پارامتر، یک کلاس یا واسط تعریف میشود که این نوع پارامترها لازم است در زمان ساخت اشیا مشخص باشند. برای مثال:
List<Long> list = new ArrayList<Long>(); list.add(Long.valueOf(1)); list.add(Long.valueOf(2));
در این مثال یک لیست ساخته میشود که تنها میتواند دادههایی از نوع Long داشته باشد و هر دادهی دیگری بخواهید به این لیست اضافه کنید با خطای زمان کامپایل روبرو میشوید.
این کار کمک میکند تا خطاها را در زمان کامپایل تشخیص دهید و کد خود را امن کنید. به محض اینکه این کد کامپایل شود، اطلاعات نوع داده آن پاک شده و در نتیجه به بایت کدی منجر میشود که با نوشتن کدی مشابه با جاوا ۱.۴ یا نسخههای قبل آن به دست میآوردیم. همین مساله سازگاری بین نسخههای مختلف جاوا را به وجود میآورد. پس یک List یا List<> هر دو در زمان اجرا یک تایپ دارند و آن هم List است.
۲- انواع داده عام از sub-typing پشتیبانی نمیکند.
انواع دادهعام از sub-typing پشتیبانی نمیکند به این معنی که اگر که S زیرتایپ T است، <S>List به عنوان زیر تایپ <T>List در نظر گرفته نمیشود. به عنوان مثال:
List<Number> numbers = new ArrayList<Integer>(); // will not compile
کدی که در بالا نشان داده شده است کامپایل نمیشود چرا که اگر کامپایل شود امنیت نوع داده به دست نمیآید. برای روشنتر کردن این مساله، تکه کدی که در زیر آمده است را در نظر بگیرید. در خط ۴ یک لیست از long به لیست از number انتساب میشود. این تکه کد کامپایل نمیشود چرا که اگر کامپایل میشد میتوانستیم یک داده double را به لیستی از اعداد long اضافه کنیم. این مساله میتوانست در زمان اجرا با خطای ClassCastException روبرو شده و امنیت نوعداده را به خطر بیندازد.
List<Long> list = new ArrayList<Long>(); list.add(Long.valueOf(1)); list.add(Long.valueOf(2)); List<Number> numbers = list; // this will not compile numbers.add(Double.valueOf(3.14));
۳- شما نمیتوانید آرایههای Generic بسازید.
شما نمیتوانید آرایههای Generic بسازید به این دلیل که آرایهها اطلاعات نوعدادهها را در زمان اجرا نگهمیدارند و از این اطلاع برای چک کردن نوع المانهای خود استفاده کرده و در صورتی که تایپ دادهها سازگار نباشد خطای ArrayStoreException پرتاب میکند. اما با وجود این مساله که اطلاعات نوع داده عام پاک میشوند، در صورتی که میتوانستیم آرایه Generic تعریف کنیم، بررسی تایپ المانهای آرایه در جایی که باید با شکست روبرو شود، موفقیت آمیز میشد.
T[] arr = new T[10];// this code will not compile
شما حتی نمیتوانید آرایهای از نوع کلاسهای Generic تعریف کنید. برای مثال کد زیر کامپایل نمیشود.
List<Integer>[] array = new List<Integer>[10]; // does not compile
آرایهها متفاوت از collectionها عمل میکنند چرا که آرایهها به طور پیشفرض covariant هستند به این معنی که اگر S زیرنوعی از T باشد، S[] زیر نوعی از T[] است . نوع داده عام از covariance پشتیبانی نمیکند. بنابراین اگر کد زیر کامپایل میشد در بررسی نوع المانهای آرایه اشکال وارد مینمود.برای مثال:
List<Integer>[] ints = new List<Integer>[10]; // does not compile Object[] objs = ints; List<Double> doubles = new ArrayList<Double>(); doubles.add(Double.valueOf(12.4)); objs[0] = doubles; // this check should fail but it succeed
اگر آرایه generic اجازه داده میشد، میتوانستیم آرایه int را به آرایهای از object انتساب دهیم چرا که آرایهها covariant هستند. پس میتوانستیم یک لیست از double را به آرایهای از obj اضافه کنیم. انتظار داریم که با ArrayStoreException روبرو شویم اما JVM نمیتواند ناسازگاری تایپ را تشخیص دهد چرا که اطلاعات نوعداده پاک میشود.
۴- از wildcardها به همراه extend یا super استفاده کنید تا انعطافپذیری API را افزایش دهید.
زمانهایی هست که نیاز دارید نه تنها با T بلکه با زیرنوعهای T هم کار کنید. برای مثال متد addAll در واسط Collection که همهی المانها را در Collection اضافه میکند امضای زیر را دارد:
boolean addAll(Collection<? extends E> c)
عبارت ? extends E اطمینان حاصل میکند که شما نه تنها میتوانید از نوع E اضافه کنید، از زیرنوعهای E نیز میتوانید اضافه کنید. ? یک widecard است و به این معنی است که پارامتر محدود به E باشد.پس اگر یک لیست از number بسازیم، نه تنها میتوانیم number اضافه کنیم بلکه میتوانیم Integer یا هر زیرنوعی از number نیز اضافه نماییم.
List<Number> numbers = new ArrayList<Number>(); ArrayList<Integer> integers = new ArrayList<Integer>(); ArrayList<Long> longs = new ArrayList<Long>(); ArrayList<Float> floats = new ArrayList<Float>(); numbers.addAll(integers); numbers.addAll(longs); numbers.addAll(floats);
تاکنون استفاده از widecard با استفاده از extends را دیدیم که انعطافپذیری به API اضافه میکند. اما چه زمانی از super استفاده میکنیم؟ کلاس Collections متدی به اسم addAll دارد که امضای زیر را دارد:
public static <T> boolean addAll(Collection<? super T> c, T... elements) ;
در این تابع المانهایی از نوع T به مجموعه c اضافه میکند. در اینجا super به جای extends استفاده میشود چرا که المانها به Collection اضافه میشود در حالی که در مثال قبلی المانهای متد addAll از collection خوانده میشدند. در کتاب Effective Java Book این را PECS مینامند که PECS مخفف producer Extends, Consumer super است. با استفاده از این دیگر در استفاه از extends و super گیج نخواهید شد.
۵- از boundهای چندگانه استفاده کنید.
قابلیت تعریف چندین bound یکی از ویژگیهای generic است که بیشتر توسعهدهندگان از آن بیاطلاع هستند. این اجازه میدهد یک نوع متغیر یا widecard چندین bound داشته باشد. برای مثال میتوانید محدودیتی تعریف کنید مثل اینکه تایپ باید Number باشد و همینطور باید واسط Comparable را پیاده سازی کنید.
public static <T extends Number & Comparable<? super T>> int compareNumbers(T t1, T t2){return t1.compareTo(t2);}
این اطمینان حاصل میکند که شما میتوانید دو عدد را مقایسه کنید که Comparable را پیادهسازی میکند. boundچندگانه نیز محدودیتهای کلاس و واسط در زبان جاوا تبعیت میکند. به عنوان مثال T نمیتواند از دو کلاس ارث بری کند اما هر تعداد واسط را میتواند پیادهسازی نماید. به علاوه لازم است ابتدا کلاس را معرفی کنید و سپس هر تعداد واسط که نیاز دارید را اضافه نمایید.
public static <T extends String & Number > int compareNumbers(T t1, T t2) // does not work..can't have two classes public static <T extends Comparable<? super T> & Number > int compareNumbers(T t1, T t2) // does not work.. public static <T extends CharSequence & Comparable<T>> int compareNumbers(T t1, T t2)// works..multiple interfaces
منبع: