اشتباهات برنامه نویسان مبتدی جاوا قسمت 1
نویسنده: A.Grasoff™ پیوند به منبع: اشتباهات برنامه نویسان مبتدی جاوا
9. فراخوانی متدهای کلاس غیراستاتیک از متد main().
نقطه ورود هر برنامه جاوا باید یک روش ثابت باشدmain
:
public static void main(String[] args) {
...
}
از آنجایی که این متد ثابت است، نمی توانید متدهای کلاس غیراستاتیک را از آن فراخوانی کنید. دانشآموزان اغلب این موضوع را فراموش میکنند و سعی میکنند روشها را بدون ایجاد نمونهای از کلاس فراخوانی کنند. این اشتباه معمولا در همان ابتدای آموزش، زمانی که دانش آموزان برنامه های کوچک می نویسند، انجام می شود. مثال اشتباه:
public class DivTest {
boolean divisible(int x, int y) {
return (x % y == 0);
}
public static void main(String[] args) {
int v1 = 14;
int v2 = 9;
// на следующие строки компилятор выдаст ошибку
if (divisible(v1, v2)) {
System.out.println(v1 + " is a multiple of " + v2);
} else {
System.out.println(v2 + " does not divide " + v1);
}
}
}
2 راه برای رفع خطا وجود دارد: متد مورد نظر را ثابت کنید یا یک نمونه از کلاس ایجاد کنید. برای انتخاب روش مناسب، از خود بپرسید که آیا این متد از یک فیلد استفاده می کند یا از متدهای کلاس دیگر. اگر بله، پس باید یک نمونه از کلاس ایجاد کنید و متدی را روی آن فراخوانی کنید، در غیر این صورت باید متد را ثابت کنید. مثال 1 تصحیح شد:
public class DivTest {
int modulus;
public DivTest(int m) {
modulus = m;
}
boolean divisible(int x) {
return (x % modulus == 0);
}
public static void main(String[] args) {
int v1 = 14;
int v2 = 9;
DivTest tester = new DivTest(v2);
if (tester.divisible(v1) {
System.out.println(v1 + " is a multiple of " + v2);
} else {
System.out.println(v2 + " does not divide " + v1);
}
}
}
مثال 2 تصحیح شد:
public class DivTest {
static boolean divisible(int x, int y) {
return (x % y == 0);
}
public static void main(String[] args) {
int v1 = 14;
int v2 = 9;
if (divisible(v1, v2)) {
System.out.println(v1 + " is a multiple of " + v2);
} else {
System.out.println(v2 + " does not divide " + v1);
}
}
}
10. استفاده از اشیاء کلاس String به عنوان پارامترهای متد.
در جاوا، یک کلاسjava.lang.String
داده های رشته ای را ذخیره می کند. با این حال، رشته ها در جاوا
- ماندگاری دارند (یعنی قابل تغییر نیستند)
- اشیاء هستند.
public static void main(String args[]) {
String test1 = "Today is ";
appendTodaysDate(test1);
System.out.println(test1);
}
/* прим. редактора: закомментированный метод должен иметь модификатор
static (здесь автором допущена ошибка №9)
public void appendTodaysDate(String line) {
line = line + (new Date()).toString();
}
*/
public static void appendTodaysDate(String line) {
line = line + (new Date()).toString();
}
در مثال بالا، دانشآموز میخواهد مقدار یک متغیر محلی را test1
با اختصاص یک مقدار جدید به یک پارامتر line
در یک متد تغییر دهد appendTodaysDate
. طبیعتاً این کار نخواهد کرد. معنی line
تغییر می کند، اما معنی test1
ثابت می ماند. این خطا به دلیل سوء تفاهم رخ می دهد که (1) اشیاء جاوا همیشه با مرجع ارسال می شوند و (2) رشته ها در جاوا تغییر ناپذیر هستند. باید بدانید که اشیاء رشته ای هرگز مقدار خود را تغییر نمی دهند و تمام عملیات روی رشته ها یک شی جدید ایجاد می کنند. برای رفع خطای مثال بالا، باید یا یک رشته از متد برگردانید، یا StringBuffer
به جای String
. مثال 1 تصحیح شد:
public static void main(String args[]) {
String test1 = "Today is ";
test1 = appendTodaysDate(test1);
System.out.println(test1);
}
public static String appendTodaysDate(String line) {
return (line + (new Date()).toString());
}
مثال 2 تصحیح شد:
public static void main(String args[]) {
StringBuffer test1 = new StringBuffer("Today is ");
appendTodaysDate(test1);
System.out.println(test1.toString());
}
public static void appendTodaysDate(StringBuffer line) {
line.append((new Date()).toString());
}
تقریبا ترجمه |
11. اعلام سازنده به عنوان متد
سازنده های شی در جاوا از نظر ظاهری شبیه به روش های معمولی هستند. تنها تفاوت این است که سازنده نوع بازگشتی را مشخص نمی کند و نام با نام کلاس یکی است. متأسفانه جاوا اجازه می دهد تا نام متد با نام کلاس یکی باشد. در مثال زیر، دانش آموز می خواهد یک فیلد کلاس راVector list
هنگام ایجاد کلاس مقداردهی اولیه کند. این اتفاق نمی افتد زیرا یک متد 'IntList'
سازنده نیست. مثال اشتباه
public class IntList {
Vector list;
// Выглядит How конструктор, но на самом деле это метод
public void IntList() {
list = new Vector();
}
public append(int n) {
list.addElement(new Integer(n));
}
}
این کد NullPointerException
در اولین باری که به فیلد دسترسی پیدا می کند یک استثنا ایجاد می کند list
. رفع خطا آسان است: فقط باید مقدار بازگشتی را از هدر متد حذف کنید. مثال تصحیح شده:
public class IntList {
Vector list;
// Это конструктор
public IntList() {
list = new Vector();
}
public append(int n) {
list.addElement(new Integer(n));
}
}
12. فراموش کرده اید که یک شی را به نوع مورد نیاز ریخته کنید
مانند سایر زبان های شی گرا، در جاوا می توانید به یک شی به عنوان سوپرکلاس آن اشاره کنید. این نامیده می شود'upcasting'
، به طور خودکار در جاوا انجام می شود. با این حال، اگر یک متغیر، فیلد کلاس یا مقدار بازگشتی متد به عنوان یک سوپرکلاس اعلام شود، فیلدها و متدهای زیر کلاس نامرئی خواهند بود. اشاره به یک سوپرکلاس به عنوان یک زیر کلاس 'downcasting'
، باید خودتان آن را ثبت کنید (یعنی شی را به زیر کلاس مورد نظر برسانید). دانشآموزان اغلب زیر کلاس یک شی را فراموش میکنند. این اغلب هنگام استفاده از آرایههای اشیاء و مجموعهها از یک بسته java.util
(به معنای Framework مجموعه ) اتفاق میافتد. مثال زیر String
یک شی را در یک آرایه قرار می دهد و سپس آن را از آرایه حذف می کند تا آن را با رشته دیگری مقایسه کند. کامپایلر یک خطا را تشخیص می دهد و تا زمانی که یک نوع Cast به صراحت مشخص نشده باشد، کد را کامپایل نمی کند. مثال اشتباه
Object arr[] = new Object[10];
arr[0] = "m";
arr[1] = new Character('m');
String arg = args[0];
if (arr[0].compareTo(arg) < 0) {
System.out.println(arg + " comes before " + arr[0]);
}
معنای ریخته گری نوع برای برخی دشوار است. روش های پویا به ویژه اغلب باعث ایجاد مشکلات می شوند. در مثال بالا، اگر متد equals
به جای استفاده می شد compareTo
، کامپایلر خطایی ایجاد نمی کرد و کد به درستی کار می کرد، زیرا متد equals
کلاس فراخوانی می شد String
. باید درک کنید که پیوند پویا با downcasting
. مثال تصحیح شده:
Object arr[] = new Object[10];
arr[0] = "m";
arr[1] = new Character('m');
String arg = args[0];
if ( ((String) arr[0]).compareTo(arg) < 0) {
System.out.println(arg + " comes before " + arr[0]);
}
13. استفاده از رابط ها.
برای بسیاری از دانش آموزان، تفاوت بین کلاس ها و رابط ها کاملاً مشخص نیست. بنابراین، برخی از دانشآموزان سعی میکنند رابطهایی مانندObserver
یا Runnable
با استفاده از کلمه کلیدی extends به جای implements پیادهسازی کنند . برای تصحیح خطا، فقط باید کلمه کلیدی را به کلمه صحیح تصحیح کنید. مثال اشتباه:
public class SharkSim extends Runnable {
float length;
...
}
مثال تصحیح شده:
public class SharkSim implements Runnable {
float length;
...
}
خطای مرتبط: ترتیب نادرست گسترش و پیاده سازی بلوک ها . طبق مشخصات جاوا، اعلانهای پسوند کلاس باید قبل از اعلانهای پیادهسازی رابط ارائه شوند. همچنین، برای واسط ها، کلیدواژه Implements فقط یک بار باید نوشته شود؛ چندین رابط با کاما از هم جدا می شوند. چند مثال اشتباه دیگر:
// Неправильный порядок
public class SharkSim implements Swimmer extends Animal {
float length;
...
}
// ключевое слово implements встречается несколько раз
public class DiverSim implements Swimmer implements Runnable {
int airLeft;
...
}
نمونه های اصلاح شده:
// Правильный порядок
public class SharkSim extends Animal implements Swimmer {
float length;
...
}
// Несколько интерфейсов разделяются запятыми
public class DiverSim implements Swimmer, Runnable {
int airLeft;
...
}
14. فراموش کرده اید که از مقدار بازگشتی یک متد سوپرکلاس استفاده کنید
جاوا به شما این امکان را می دهد که با استفاده از کلمه کلیدی کلمه کلیدی، یک روش سوپرکلاس مشابه را از یک زیر کلاس فراخوانی کنید. گاهی اوقات دانش آموزان باید متدهای سوپرکلاس را فراخوانی کنند، اما اغلب فراموش می کنند که از مقدار بازگشتی استفاده کنند. این امر به ویژه در میان آن دسته از دانشآموزانی اتفاق میافتد که هنوز روشها و مقادیر بازگشتی آنها را درک نکردهاند. در مثال زیر، دانش آموزی می خواهد نتیجه یکtoString()
متد سوپرکلاس را در نتیجه یک toString()
متد زیر کلاس قرار دهد. با این حال، از مقدار برگشتی متد superclass استفاده نمی کند. مثال اشتباه:
public class GraphicalRectangle extends Rectangle {
Color fillColor;
boolean beveled;
...
public String toString() {
super();
return("color=" + fillColor + ", beveled=" + beveled);
}
}
برای تصحیح خطا، معمولاً کافی است مقدار بازگشتی را به یک متغیر محلی اختصاص دهید و سپس هنگام محاسبه نتیجه متد زیر کلاس، از آن متغیر استفاده کنید. مثال تصحیح شده:
public class GraphicalRectangle extends Rectangle {
Color fillColor;
boolean beveled;
...
public String toString() {
String rectStr = super();
return(rectStr + " - " +
"color=" + fillColor + ", beveled=" + beveled);
}
}
15. فراموش کرده اید که اجزای AWT را اضافه کنید
AWT از یک مدل طراحی ساده رابط کاربری گرافیکی استفاده میکند: هر جزء رابط ابتدا باید با استفاده از سازنده خودش ایجاد شود و سپس با استفاده ازadd()
روش مؤلفه والد در پنجره برنامه قرار داده شود. بنابراین، رابط در AWT یک ساختار سلسله مراتبی دریافت می کند. دانش آموزان گاهی این 2 مرحله را فراموش می کنند. آنها یک جزء ایجاد می کنند اما فراموش می کنند که آن را در پنجره بزرگنمایی قرار دهند. این باعث ایجاد خطا در مرحله کامپایل نمی شود، کامپوننت به سادگی در پنجره برنامه ظاهر نمی شود. مثال اشتباه
public class TestFrame extends Frame implements ActionListener {
public Button exit;
public TestFrame() {
super("Test Frame");
exit = new Button("Quit");
}
}
برای رفع این خطا، کافی است اجزای آن را به والدین آنها اضافه کنید. مثال زیر نحوه انجام این کار را نشان می دهد. لازم به ذکر است که اغلب دانش آموزی که فراموش می کند کامپوننتی را به پنجره برنامه اضافه کند، فراموش می کند شنونده رویداد را نیز به آن مؤلفه اختصاص دهد. مثال تصحیح شده:
public class TestFrame extends Frame implements ActionListener {
public Button exit;
public TestFrame() {
super("Test Frame");
exit = new Button("Quit");
Panel controlPanel = new Panel();
controlPanel.add(exit);
add("Center", controlPanel);
exit.addActionListener(this);
}
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
}
17. فراموش کرده اید که جریان را شروع کنید
Multithreading در جاوا با استفاده ازjava.lang.Thread
. چرخه زندگی یک thread شامل 4 مرحله است: مقداردهی اولیه، شروع، مسدود شدن و توقف. موضوع تازه ایجاد شده در حالت اولیه است. برای قرار دادن آن در حالت در حال اجرا، باید آن را فراخوانی کنید start()
. گاهی اوقات دانش آموزان رشته هایی ایجاد می کنند اما فراموش می کنند که آنها را شروع کنند. معمولاً این خطا زمانی رخ می دهد که دانش آموز در مورد برنامه نویسی موازی و چند رشته ای دانش کافی نداشته باشد. (تقریباً ترجمه: من اتصال را نمی بینم) برای رفع خطا، فقط باید موضوع را شروع کنید. در مثال زیر، دانشآموزی میخواهد با استفاده از رابط، یک انیمیشن از یک عکس بسازد Runnable
، اما فراموش کرده است که موضوع را شروع کند. مثال اشتباه
public class AnimCanvas extends Canvas implements Runnable {
protected Thread myThread;
public AnimCanvas() {
myThread = new Thread(this);
}
// метод run() не будет вызван,
// потому что поток не запущен.
public void run() {
for(int n = 0; n < 10000; n++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) { }
animateStep(n);
}
}
...
}
مثال تصحیح شده:
public class AnimCanvas extends Canvas implements Runnable {
static final int LIMIT = 10000;
protected Thread myThread;
public AnimCanvas() {
myThread = new Thread(this);
myThread.start();
}
public void run() {
for(int n = 0; n < LIMIT; n++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) { }
animateStep(n);
}
}
...
}
چرخه حیات یک thread و رابطه بین رشته ها و کلاس هایی که یک رابط را پیاده سازی می کنند، Runnable
بخش بسیار مهمی از برنامه نویسی جاوا است و تمرکز بر روی آن مفید خواهد بود.
18. استفاده از روش ممنوعه readLine() کلاس java.io.DataInputStream
در جاوا نسخه 1.0، برای خواندن یک رشته متن باید از یک متدreadLine()
کلاس استفاده میکردید java.io.DataInputStream
. جاوا نسخه 1.1 مجموعه کاملی از کلاس های ورودی/خروجی را برای ارائه عملیات ورودی/خروجی برای متن اضافه کرد: Reader
و Writer
. بنابراین، از نسخه 1.1 برای خواندن یک خط متن، باید از متد readLine()
کلاس استفاده کنید java.io.BufferedReader
. دانش آموزان ممکن است از این تغییر آگاه نباشند، به خصوص اگر از کتاب های قدیمی تر تدریس شده باشند. (تقریباً ترجمه: در واقع دیگر مرتبط نیست. بعید است که کسی اکنون از روی کتابهایی که 10 سال قدمت دارند مطالعه کند). روش قدیمی readLine()
در JDK باقی می ماند، اما غیرقانونی اعلام می شود، که اغلب دانش آموزان را سردرگم می کند. چیزی که باید درک کنید این است که استفاده از روش readLine()
کلاس java.io.DataInputStream
اشتباه نیست، فقط قدیمی است. شما باید از کلاس استفاده کنید BufferedReader
. مثال اشتباه:
public class LineReader {
private DataInputStream dis;
public LineReader(InputStream is) {
dis = new DataInputStream(is);
}
public String getLine() {
String ret = null;
try {
ret = dis.readLine(); // Неправильно! Запрещено.
} catch (IOException ie) { }
return ret;
}
}
مثال تصحیح شده:
public class LineReader {
private BufferedReader br;
public LineReader(InputStream is) {
br = new BufferedReader(new InputStreamReader(is));
}
public String getLine() {
String ret = null;
try {
ret = br.readLine();
} catch (IOException ie) { }
return ret;
}
}
روش های ممنوعه دیگری در نسخه های بالاتر از 1.0 وجود دارد، اما این یکی رایج ترین است.
19. استفاده از double به عنوان شناور
مانند بسیاری از زبان های دیگر، جاوا از عملیات اعداد ممیز شناور (اعداد کسری) پشتیبانی می کند. جاوا دو نوع اولیه برای اعداد ممیز شناور دارد:double
دقت 64 بیتی IEEE و float
دقت 32 بیتی IEEE. مشکل در استفاده از اعداد اعشاری مانند 1.75، 12.9e17 یا -0.00003 است - کامپایلر آنها را به تایپ اختصاص می دهد double
. جاوا در عملیاتی که ممکن است دقت را از دست بدهد، نوع ریخته گری را انجام نمی دهد. این نوع ریخته گری باید توسط برنامه نویس انجام شود. به عنوان مثال، جاوا به شما اجازه نمی دهد که یک مقدار نوع را بدون نوع Cast به یک int
متغیر نوع اختصاص دهید byte
، همانطور که در مثال زیر نشان داده شده است.
byte byteValue1 = 17; /* неправильно! */
byte byteValue2 = (byte)19; /* правильно */
از آنجایی که اعداد کسری با نوع نمایش داده می شوند double
و انتساب double
به یک متغیر از نوع float
می تواند منجر به کاهش دقت شود، کامپایلر از هرگونه تلاش برای استفاده از اعداد کسری به عنوان شکایت خواهد کرد float
. بنابراین استفاده از تکالیف زیر از کامپایل کلاس جلوگیری می کند.
float realValue1 = -1.7; /* неправильно! */
float realValue2 = (float)(-1.9); /* правильно */
این تخصیص در C یا C++ کار میکند، اما در جاوا بسیار سختگیرانهتر است. 3 راه برای خلاص شدن از شر این خطا وجود دارد. می توانید double
به جای float
. این ساده ترین راه حل است. در واقع، استفاده از محاسبات 32 بیتی به جای 64 بیتی، فایده چندانی ندارد؛ تفاوت در سرعت هنوز توسط JVM کم می شود (علاوه بر این، در پردازنده های مدرن همه اعداد کسری به فرمت یک پردازنده 80 بیتی تبدیل می شوند. قبل از هر عملیاتی ثبت نام کنید). تنها مزیت استفاده از آنها float
این است که حافظه کمتری را اشغال می کنند، که هنگام کار با تعداد زیادی متغیر کسری مفید است. شما می توانید از یک اصلاح کننده نوع عدد استفاده کنید تا به کامپایلر بگویید چگونه عدد را ذخیره کند. float - 'f'
اصلاح کننده برای نوع بنابراین، کامپایلر نوع 1.75 را به double
، و اختصاص می دهد 1.75f - float
. مثلا:
float realValue1 = 1.7; /* неправильно! */
float realValue2 = 1.9f; /* правильно */
می توانید از ریخته گری نوع صریح استفاده کنید. این کمترین شیک ترین راه است، اما هنگام تبدیل یک متغیر نوع double
به نوع مفید است float
. مثال:
float realValue1 = 1.7f;
double realValue2 = 1.9;
realValue1 = (float)realValue2;
در اینجا و اینجا می توانید اطلاعات بیشتری در مورد اعداد ممیز شناور بخوانید.
-- نظر مترجم -- |
GO TO FULL VERSION