JavaRush /Java Blog /Random-KO /초보 자바 프로그래머의 실수. 2 부
articles
레벨 15

초보 자바 프로그래머의 실수. 2 부

Random-KO 그룹에 게시되었습니다
초보 자바 프로그래머의 실수. 1 부

9. main() 메소드에서 비정적 클래스 메소드 호출

모든 Java 프로그램의 진입점은 정적 메소드여야 합니다 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에서 클래스는 java.lang.String문자열 데이터를 저장합니다. 그러나 Java의 문자열
  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매개변수에 새 값을 할당하여 지역 변수의 값을 변경하려고 합니다 . 당연히 이것은 작동하지 않습니다. 의미는 변하지만 의미는 동일하게 유지됩니다. 이 오류는 (1) Java 개체는 항상 참조로 전달되고 (2) Java의 문자열은 변경할 수 없다는 오해로 인해 발생합니다. 문자열 개체는 절대 값을 변경하지 않으며 문자열에 대한 모든 작업은 새 개체를 생성한다는 점을 이해해야 합니다. 위 예제의 오류를 수정하려면 메서드에서 문자열을 반환하거나 . 대신 객체를 매개변수로 메서드에 전달해야 합니다 . 수정된 예 1:lineappendTodaysDatelinetest1StringBufferString
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. 생성자를 메소드로 선언하기

Java의 객체 생성자는 모양이 일반 메소드와 유사합니다. 유일한 차이점은 생성자가 반환 값의 유형을 지정하지 않으며 이름이 클래스 이름과 동일하다는 것입니다. 불행하게도 Java에서는 메서드 이름이 클래스 이름과 동일하도록 허용합니다. 아래 예에서 학생은 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. 객체를 필요한 유형으로 캐스팅하는 것을 잊었습니다.

다른 모든 객체 지향 언어와 마찬가지로 Java에서는 객체를 슈퍼클래스로 참조할 수 있습니다. 이를 이라고 하며 'upcasting'Java에서 자동으로 수행됩니다. 그러나 변수, 클래스 필드 또는 메서드 반환 값이 슈퍼클래스로 선언되면 하위 클래스의 필드와 메서드는 표시되지 않습니다. 하위 클래스를 호출할 때 상위 클래스를 참조하려면 'downcasting'직접 등록해야 합니다(즉, 객체를 원하는 하위 클래스로 가져와야 합니다). 학생들은 객체를 하위 클래스로 분류하는 것을 종종 잊어버립니다. 이는 패키지의 객체 및 컬렉션 배열( 컬렉션 프레임워크를java.util 의미 ) 을 사용할 때 가장 자주 발생합니다 . 아래 예제에서는 객체를 배열에 넣은 다음 배열에서 제거하여 다른 문자열과 비교합니다. 컴파일러는 오류를 감지하고 유형 캐스트가 명시적으로 지정될 때까지 코드를 컴파일하지 않습니다. 잘못된 예입니다.String
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메서드가 호출되었기 때문에 컴파일러에서 오류가 발생하지 않고 코드가 올바르게 작동했을 것입니다 . 동적 연결은 . 수정된 예:equalsStringdowncasting
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. 인터페이스 사용.

많은 학생들에게 클래스와 인터페이스의 차이가 완전히 명확하지 않습니다. 따라서 일부 학생들은 Implements 대신 확장 키워드를 사용 Observer하거나 와 같은 인터페이스를 구현하려고 합니다 . 오류를 수정하려면 키워드를 올바른 키워드로 수정하면 됩니다. 잘못된 예:Runnable
public class SharkSim extends Runnable {
    float length;
    ...
}
수정된 예:
public class SharkSim implements Runnable {
    float length;
    ...
}
관련 오류: 블록 확장구현 순서가 잘못되었습니다 . Java 사양에 따르면 클래스 확장 선언은 인터페이스 구현 선언 앞에 와야 합니다. 또한 인터페이스의 경우 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. 슈퍼클래스 메소드의 반환값을 사용하는 것을 잊었습니다.

Java에서는 키워드 키워드를 사용하여 하위 클래스에서 유사한 슈퍼클래스 메서드를 호출할 수 있습니다. 때때로 학생들은 슈퍼클래스 메서드를 호출해야 하지만 반환 값을 사용하는 것을 잊어버리는 경우가 많습니다. 이는 메소드와 해당 반환 값을 아직 이해하지 못한 학생들 사이에서 특히 자주 발생합니다. 아래 예에서 학생은 toString()슈퍼클래스 메서드의 결과를 하위 클래스 메서드의 결과에 삽입하려고 합니다 toString(). 그러나 슈퍼클래스 메서드의 반환값은 사용하지 않습니다. 잘못된 예:
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는 간단한 GUI 디자인 모델을 사용합니다. 각 인터페이스 구성 요소는 먼저 자체 생성자를 사용하여 생성한 다음 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. 스트림을 시작하는 것을 잊었습니다.

Java의 멀티스레딩은 java.lang.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);
                }
        }
        ...
}
스레드의 수명주기와 인터페이스를 구현하는 스레드와 클래스 간의 관계는 RunnableJava 프로그래밍에서 매우 중요한 부분이므로 이에 집중하는 것이 유용할 것입니다.

18. java.io.DataInputStream 클래스 의 금지된 readLine() 메소드 사용

Java 버전 1.0에서는 텍스트 문자열을 읽으려면 readLine()클래스 메서드를 사용해야 했습니다 java.io.DataInputStream. Java 버전 1.1에는 텍스트에 대한 I/O 작업을 제공하기 위해 전체 I/O 클래스 세트인 Reader및 가 추가되었습니다 Writer. readLine()따라서 버전 1.1부터는 한 줄의 텍스트를 읽으려면 클래스 메소드 를 사용해야 합니다 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을 float로 사용하기

대부분의 다른 언어와 마찬가지로 Java는 부동 소수점 수(분수)에 대한 연산을 지원합니다. Java에는 부동 소수점 숫자에 대한 두 가지 기본 유형인 doubleIEEE 64비트 정밀도와 floatIEEE 32비트 정밀도가 있습니다. 어려운 점은 1.75, 12.9e17 또는 -0.00003과 같은 십진수를 사용할 때입니다. 컴파일러는 이를 유형에 할당합니다 double. Java는 정밀도 손실이 발생할 수 있는 작업에서 유형 캐스트를 수행하지 않습니다. 이 유형 캐스팅은 프로그래머가 수행해야 합니다. 예를 들어, 아래 예와 같이 Java에서는 유형 캐스트 ​​없이 유형 값을 유형 int변수 에 할당하는 것을 허용하지 않습니다 .byte
byte byteValue1 = 17; /* неправильно! */
byte byteValue2 = (byte)19; /* правильно */
분수는 유형으로 표시되고 유형의 변수에 double할당하면 정밀도가 손실될 수 있으므로 컴파일러는 분수를 유형으로 사용하려는 모든 시도에 대해 불만을 표시합니다 . 따라서 아래 할당을 사용하면 클래스가 컴파일되지 않습니다. doublefloatfloat
float realValue1 = -1.7;          /* неправильно! */
float realValue2 = (float)(-1.9); /* правильно */
이 할당은 C 또는 C++에서 작동하지만 Java에서는 훨씬 더 엄격합니다. 이 오류를 제거하는 방법에는 3가지가 있습니다. double대신 유형을 사용할 수 있습니다 float. 이것이 가장 간단한 해결책입니다. 실제로 64비트 대신 32비트 연산을 사용하는 것은 거의 의미가 없습니다. 속도 차이는 여전히 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™ 출처 링크: 초보 Java 프로그래머의 실수
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION