دانستنی‌ها

ده اشتباه رایج توسعه‌دهندگان جاوا

در این مطلب لیستی از ده اشتباهی که مکررا توسعه‌دهندگان جاوا مرتکب می‌شوند را مرور می‌کنیم:

۱. تبدیل آرایه به ArrayList

برای تبدیل آرایه به ArrayList توسعه دهندگان اغلب رویکرد زیر را دنبال می‌کنند.

List<String> list = Arrays.asList(arr);

Arrays.asList() یک ‌ArrayList برمی‌گرداند که یک کلاس استاتیک private درون Arrays است و java.util.ArrayList نیست. java.util.Arrays.ArrayList توابع set() و get() و contains() دارد اما هیچ متدی برای اضافه کردن المان ندارد و سایز آن ثابت است. برای ساختن یک ArrayList واقعی باید این کار را کرد:

ArrayList<String> arrayList = new ArrayList<String>(Arrays.asList(arr));

۲. بررسی اینکه آیا یک آرایه یک مقدار دارد

توسعه‌دهندگان اغلب این چنین می‌کنند:

Set<String> set = new HashSet<String>(Arrays.asList(arr));
return set.contains(targetValue);

این کد کار می‌کند اما نیازی نیست که لیست را اول به set تبدیل کنید. تبدیل لیست به set زمان اضافی نیاز دارد. می‌تواند به سادگی زیر باشد:

Arrays.asList(arr).contains(targetValue);

یا

for(String s: arr){
	if(s.equals(targetValue))
		return true;
}
return false;

اولی از دومی خواناتر است.

۳. حذف یک المان از یک لیست درون حلقه

کد زیر را در نظر بگیرید که در تکرار حلقه المان‌های آن را حذف می‌کند.

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d"));
for (int i = 0; i < list.size(); i++) {
	list.remove(i);
}
System.out.println(list);

خروجی به شکل زیر است:

[b, d]

یک مشکل جدی در این تابع وجود دارد. وقتی یک المان حذف می‌شود، سایز لیست کوچک می‌شود و اندیس تغییر می‌کند. بنابراین اگر بخواهید چند المان را با استفاده از اندیس درون حلقه حذف کنید، به درستی کار نخواهد کرد.

ممکن است  بدانید که استفاده از iterator برای حذف کار درستی است و می‌دانید که حلقه foreach در جاوا مثل iterator کار می‌کند اما این طور نیست کد زیر را در نظر بگیرید:

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d"));
 
for (String s : list) {
	if (s.equals("a"))
		list.remove(s);
}

که خطای ConcurrentModificationException برمی‌گرداند.

اما کد زیر درست است:

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d"));
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
	String s = iter.next();
 
	if (s.equals("a")) {
		iter.remove();
	}
}

.next باید قبل از .remove صدا زده شود. در حلقه foreach کامپایلر .next را بعد از حذف صدا می‌زند و منجر به خطا می‌شود. می‌توانید نگاهی به کد ArrayList.iterator بیندازید.

۴. Hashtable در مقابل HashMap

در واقع در تئوری الگوریتم‌ها، Hashtable نام یک داده ساختار است. اما در جاوا HashMap یک داده‌ساختار است. یکی از تفاوت‌های اصلی بین Hashtable و HashMap این است که Hashtable سنکرون است. به همین دلیل خیلی وقت‌ها نیازی به آن ندارید و HashMap باید استفاده شود.

۵. استفاده از نوع خام مجموعه‌ها

در جاوا نوع raw type و unbound wildcard type خیلی مواقع اشتباه گرفته می‌شوند. به عنوان مثال Set را در نظر بگیرید، Set یک داده خام است در حالیکه Set<?> یک داده unbound wildcard است.

کد زیر که از داده خام List به عنوان پارامتر استفاده می‌کند را در نظر بگیرید:

public static void add(List list, Object o){
	list.add(o);
}
public static void main(String[] args){
	List<String> list = new ArrayList<String>();
	add(list, 10);
	String s = list.get(0);
}

این کد خطای زیر را پرتاب می‌کند.

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
	at ...

استفاده از مجموعه داده خام خطرناک است چرا که چک کردن عام نوع داده را انجام نداده و امن نیست. تفاوت‌های بسیاری بین Set، Set<?> و Set<Object> وجود دارد که در اینجا می‌توانید بررسی کنید.

۶. سطح دسترسی

توسعه‌دهندگان در بسیاری از موارد از public برای فیلد کلاس استفاده می‌کنند. دریافت مقدار فیلد با ارجاع مستقیم به آن خیلی ساده است اما یک طراحی بسیار بد است. یک قانون سرانگشتی این است که برای اعضا مینیمم سطح دسترسی را قائل شوید.

۷. ArrayList در مقابل LinkedList

وقتی توسعه‌دهندگان تفاوت بین ArrayList و LinkedList را نمی‌دانند، اغلب از ArrayList استفاده می‌کنند چرا که آشناتر به نظر می‌رسد. هرچند تفاوت کارایی چشمگیری بین آن‌ها وجود دارد. به طور خلاصه، LinkedList باید در صورتی که تعداد عملگرهای حذف/اضافه زیاد است و تعداد عملگرهای دسترسی تصادفی کم است، ترجیح داده شود. می‌توانید اینجا را برای مقایسه کارایی آنان نگاه کنید.

۸. Mutable در مقابل Immutable

اشیا Immutable مزایای زیادی مثل سادگی، امنیت و … دارند. اما برای هر مقدار مختلف آنان به یک شئ مجزا نیاز است و تعداد زیاد اشیاء می‌تواند هزینه زیادی برای زباله روبی ایجاد کند. به همین دلیل باید توازنی در انتخاب بین mutable و immutable وجود داشته باشد.

در حالت کلی اشیا mutable از تولید اشیا میانی اجتناب می‌کنند. یک مثال کلاسیک آن ترکیب تعداد زیادی رشته است. اگر شما از یک رشته immutable استفاده کنید، اشیا زیادی تولید می‌کنید که ممکن است فورا توسط زباله روب حذف شوند. این کار زمان و انرژی زیادی از CPU می‌گیرد، راه حل درست استفاده از mutable است مثل StringBuilder

String result="";
for(String s: arr){
	result = result + s;
}

حالت های دیگری هم هست که اشیا mutable ترجیح داده می‌شوند. برای مثال پاس دادن اشیا mutable به متدها اجازه می‌دهد نتایج زیادی را بدون پریدن به حلقه‌های ساختگی جمع‌آوری کنید. مثال دیگر مرتب کردن و فیلتر کردن است. هرچند می‌توانید متدی درست کنید که مجموعه‌های اصلی را گرفته و مرتب شده را تحویل دهد اما برای مجموعه‌های بزرگ اتلاف زیادی دارد.

۹. Constructor of Super and Sub

Implicit-super-constructor-is-undefined-for-default-constructor

در این جا خطای کامپایل رخ می‌دهد چرا که constructor پیش فرض super تعریف نشده است. در جاوا، اگر یک کلاس constructorای تعریف نکرده باشد، کامپایلر  به طور پیش‌فرض یک constructor بدون آرگومان وارد می‌کند. اگر constructor در super class تعریف شده باشد، در اینجا Super(String s) کامپایلر constructor پیش‌فرض بدون آرگومان را وارد نمی‌کند. این همان شرایط کلاس Super بالاست.

 constructor کلاس Sub چه با آرگومان و چه بدون آن، constructor بدون آرگومان کلاس Super را صدا می‌زند. از آن‌جایی که کامپایلر تلاش دارد super() را به دو constructor در کلاس Sub اضافه کند اما constructor پیش فرض کلاس Super تعریف نشده است،‌ با خطا روبرو می‌شود.

برای حل مشکل

۱) یک Super() constructor به کلاس Super به این شکل اضافه کنید:

public Super(){
    System.out.println("Super");
}

۲) Super() constructor که خود تعریف کرده اید را حذف کنید

۳)‌ super(value) را به constructor کلاس sub اضافه کنید.

۱۰)  “” یا Constructor؟

رشته‌ها به دو طریق ساخته می‌شوند:

//1. use double quotes
String x = "abc";
//2. use constructor
String y = new String("abc");

تفاوت آن‌ها چیست؟

مثال زیر یک جواب کوتاه ارائه می‌دهد:

String a = "abcd";
String b = "abcd";
System.out.println(a == b);  // True
System.out.println(a.equals(b)); // True
 
String c = new String("abcd");
String d = new String("abcd");
System.out.println(c == d);  // False
System.out.println(c.equals(d)); // True

برای اطلاعات بیشتر به اینجا مراجعه کنید.

این اطلاعات از تحلیل تعداد زیادی پروژه متن‌باز در گیت‌هاب، استک اور فلو و جستجو های گوگل به دست آمده است. ارزیابی دقیقی مبنی بر اینکه آیا دقیقا ده‌تای اول هستند انجام نشده است اما قطعا بسیار رایج هستند. شما هم با این مسائل روبرو شده‌اید؟

منبع:

http://www.programcreek.com/

نوشته های مشابه

‫3 دیدگاه ها

  1. با سلام و خسته نباشید
    خواهشآ در مورد تلفیق فریم ورک اسپرینگ و هایبرنیت سوال داشتم
    هایبرنیت کانفیگ خاص خودش را دارد و میشود در فایل hibernate.cfg.xml کانفیگش کرد ، و هم چنین اسپرینگ نیز ساپورت های خاص خود را از هایبرنیت دارد و میشود هایبرنیت را معمولا در فایل applicationContext.xml اسپرینگ نیز کانفیگ کرد ، میخواستم بدانم که به نظر شما کدام راه کاربرد بیشتری دارد و در پروژه های واقعی که هایبرنیت و اسپرینگ با هم استفاده میشوند ، هایبرنیت را چگونه کانفیگ میکنند .
    با تشکر

  2. از تکه کد زیر در قسمت آخر تعجب کردم.
    String a = “abcd”
    String b = “abcd”
    System.out.println(a == b); // True

  3. با سلام تشکر از مقاله خوبتون
    اگر ممکنه مقاله های مرتبط با فریمورک Spring را بگذارید.
    با تشکر.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا