JavaRush /وبلاگ جاوا /Random-FA /اشتباهات برنامه نویسان مبتدی جاوا قسمت 2
articles
مرحله

اشتباهات برنامه نویسان مبتدی جاوا قسمت 2

در گروه منتشر شد
اشتباهات برنامه نویسان مبتدی جاوا قسمت 1

9. فراخوانی متدهای کلاس غیراستاتیک از متد main().

نقطه ورود هر برنامه جاوا باید یک روش ثابت باشد main:
اشتباهات برنامه نویسان مبتدی جاوا  قسمت 2 - 1
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داده های رشته ای را ذخیره می کند. با این حال، رشته ها در جاوا
  1. ماندگاری دارند (یعنی قابل تغییر نیستند)
  2. اشیاء هستند.
بنابراین، آنها را نمی توان فقط به عنوان یک بافر کاراکتر در نظر گرفت؛ آنها اشیایی تغییر ناپذیر هستند. گاهی اوقات دانش‌آموزان رشته‌ها را با این انتظار اشتباه ارسال می‌کنند که شی رشته به‌عنوان یک آرایه کاراکتری با مرجع ارسال می‌شود (مانند C یا C++). کامپایلر معمولاً این را یک خطا در نظر نمی گیرد. مثال اشتباه
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());
}

تقریبا ترجمه
در واقع، درک این که خطا چیست چندان آسان نیست. از آنجایی که اشیا با مرجع ارسال می شوند، به این معنی است lineکه به همان مکان اشاره دارد test1. این بدان معنی است که با ایجاد یک جدید line، یک مورد جدید ایجاد می کنیم test1. در مثال اشتباه، همه چیز به نظر می رسد که انتقال Stringبر اساس مقدار است، نه با مرجع.

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;
در اینجا و اینجا می توانید اطلاعات بیشتری در مورد اعداد ممیز شناور بخوانید.

-- نظر مترجم --
همین است.
در مثال 10، خطای 9 در واقع ایجاد شد، من بلافاصله متوجه آن شدم، اما فراموش کردم یک یادداشت بنویسم. اما اصلاح نکرد تا مغایرتی با منبع اصلی نداشته باشد.

نویسنده: A.Grasoff™ پیوند به منبع: اشتباهات برنامه نویسان مبتدی جاوا
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION