수색…


소개

몇 가지 Java 프로그래밍 언어 오용은 올바르게 컴파일되었지만 부정확 한 결과를 생성하는 프로그램을 수행 할 수 있습니다. 이 주제의 주요 목적은 일반적인 함정을 원인과 함께 나열하고 그러한 문제에 빠지지 않도록 올바른 방법을 제안하는 것입니다.

비고

이 주제는 오류가 발생하기 쉬운 Java 언어 구.의 특정 측면 또는 특정 f 식으로 사용해서는 안되는 것입니다.

함몰 - 방법의 가시성 무시

숙련 된 Java 개발자조차도 Java에는 단 3 개의 보호 수정 자만 있다고 생각하는 경향이 있습니다. 언어에는 실제로 4가있다! 패키지 비공개 (일명 기본값) 표시 수준은 종종 잊어 버립니다.

어떤 방법으로 공개하는지주의해야합니다. 애플리케이션의 공개 메소드는 애플리케이션의 가시적 인 API입니다. 특히 재사용 가능한 라이브러리를 작성하는 경우에는 작고 작아야합니다 ( SOLID 원칙 참조). 모든 메소드의 가시성을 유사하게 고려하고 적절한 경우 보호되거나 패키지 된 개인 액세스 만 사용하는 것이 중요합니다.

비공개 로 선언해야하는 메서드를 public으로 선언하면 해당 클래스의 내부 구현 정보가 노출됩니다.

이것에 대한 결과는 클래스의 public 메소드를 단위 테스트 만한다는 것입니다. 사실 public 메소드 테스트 할 수 있습니다. 이러한 메소드에 대해 단위 테스트를 실행할 수 있도록 개인 메소드의 가시성을 높이는 것은 나쁜 습관입니다. 보다 제한적인 가시성을 가진 메소드를 호출하는 public 메소드를 테스트하면 전체 API를 테스트하는 데 충분해야합니다. 단위 테스트를 허용하기 위해 더 많은 공용 메소드로 API를 확장해서는 안됩니다 .

Pitfall - '스위치'케이스에서 '휴식'이 없습니다.

이러한 Java 문제는 매우 당혹 스러울 수 있으며 때로는 프로덕션 환경에서 실행될 때까지 알려지지 않은 채로 남아 있습니다. switch 문에서의 fallthrough 동작은 종종 유용합니다. 그러나 이러한 동작이 바람직하지 않을 때 "중단"키워드가 누락되면 심각한 결과를 초래할 수 있습니다. 아래의 코드 예제에서 "case 0"에 "break"를 넣는 것을 잊어 버린 경우, 프로그램은 "Zero"다음에 "One"을 씁니다. 여기의 제어 흐름은 전체 "switch"문을 통과 할 때까지 그것은 "휴식"에 도달합니다. 예 :

public static void switchCasePrimer() {
        int caseIndex = 0;
        switch (caseIndex) {
            case 0:
                System.out.println("Zero");
            case 1:
                System.out.println("One");
                break;
            case 2:
                System.out.println("Two");
                break;
            default:
                System.out.println("Default");
        }
}

대부분의 경우 클리너 솔루션은 인터페이스를 사용하고 특정 동작이있는 코드를 별도의 구현 ( 상속을 통한 구성 )으로 이동하는 것입니다.

switch-statement가 불가피한 경우 "예상"fallthrough가 발생할 경우 문서화하는 것이 좋습니다. 그렇게하면 동료 개발자에게 누락 된 부분을 알리고 이것이 예상되는 동작임을 알 수 있습니다.

switch(caseIndex) {
    [...]
    case 2:
        System.out.println("Two");
        // fallthrough
    default:
        System.out.println("Default");

함몰 - 잘못 배치 된 세미콜론과 누락 된 중괄호

이것은 Java 초보자가 적어도 처음에는 혼란을 야기하는 실수입니다. 이것을 쓰는 대신에 :

if (feeling == HAPPY)
    System.out.println("Smile");
else
    System.out.println("Frown");

그들은 실수로 다음과 같이 씁니다 :

if (feeling == HAPPY);
    System.out.println("Smile");
else
    System.out.println("Frown");

Java 컴파일러가 else 가 잘못 배치되었다고 말할 때 당황합니다. 자바 컴파일러는 다음과 같이 해석합니다.

if (feeling == HAPPY)
    /*empty statement*/ ;
System.out.println("Smile");   // This is unconditional
else                           // This is misplaced.  A statement cannot
                               // start with 'else'
System.out.println("Frown");

다른 경우에는 컴파일 오류가 없지만 코드는 프로그래머가 의도 한대로 수행하지 않습니다. 예 :

for (int i = 0; i < 5; i++);
    System.out.println("Hello");

한 번만 "Hello"를 인쇄합니다. 다시 한번, 가짜 세미콜론은 for 루프의 본문이 빈 문장임을 의미합니다. 즉, 뒤 따르는 println 호출은 무조건 부 M됩니다.

다른 변형 :

for (int i = 0; i < 5; i++);
    System.out.println("The number is " + i);

이렇게하면 i 대해 "기호를 찾을 수 없음"오류가 발생합니다. 가짜 세미콜론이 존재한다는 것은 println 호출이 범위 밖에서 i 를 사용하려고한다는 것을 의미합니다.

이 예에서는 간단한 해결책이 있습니다. 가짜 세미콜론을 삭제하면됩니다. 그러나 다음 예제에서 더 깊이 이해할 수있는 교훈이 있습니다.

  1. Java의 세미콜론은 "통어론 적 소음"이 아닙니다. 세미콜론의 유무는 프로그램의 의미를 바꿀 수 있습니다. 모든 행의 끝에 추가하지 마십시오.

  2. 코드의 들여 쓰기를 신뢰하지 마십시오. Java 언어에서는 컴파일러가 행의 처음에있는 여분의 공백을 무시합니다.

  3. 자동 압자를 사용하십시오. 모든 IDE 및 많은 간단한 텍스트 편집기는 Java 코드를 올바르게 들여 쓰기하는 방법을 알고 있습니다.

  4. 이것이 가장 중요한 교훈입니다. 최신 Java 스타일 지침을 따르고 "then"및 "else"문과 루프 본문에 중괄호를 사용하십시오. 대괄호 ( { )는 새 줄에 있어서는 안됩니다.

프로그래머가 스타일 규칙을 따르면, 잘못 배치 된 세미콜론이있는 if 예제는 다음과 같습니다.

if (feeling == HAPPY); {
    System.out.println("Smile");
} else {
    System.out.println("Frown");
}

그것은 경험 많은 눈에 이상하게 보입니다. 해당 코드를 자동 들여 쓰기하면 다음과 같이 보일 것입니다.

if (feeling == HAPPY); {
                           System.out.println("Smile");
                       } else {
                           System.out.println("Frown");
                       }

초보자조차도 두드러지지 않아야합니다.

핏방울 - 괄호를 벗어나 : "매달려 있다면"과 "매달려있는"문제

Oracle Java Style Guide의 최신 버전에서는 if 문에있는 "then"및 "else"문을 항상 "중괄호"또는 "중괄호"로 묶어야한다고 규정하고 있습니다. 비슷한 규칙이 다양한 루프 문 본문에 적용됩니다.

if (a) {           // <- open brace
    doSomething();
    doSomeMore();
}                  // <- close brace

이것은 실제로 Java 언어 구문에서는 필요하지 않습니다. (가) "다음"는 부분이 있다면 사실, if 문이 하나의 문, 괄호를 생략 할 법적

if (a)
    doSomething();

또는

if (a) doSomething();

그러나 Java 스타일 규칙을 무시하고 중괄호를 생략하면 위험 할 수 있습니다. 특히 잘못된 들여 쓰기가있는 코드가 잘못 읽힐 위험이 크게 높아집니다.

"dangling if"문제 :

위에서 괄호없이 다시 작성된 예제 코드를 살펴보십시오.

if (a)
   doSomething();
   doSomeMore();

이 코드 doSomethingdoSomeMore 대한 호출은 atrue 경우에만 발생 true . 사실, 코드는 잘못 들여 쓰기되어 있습니다. doSomeMore() 호출이 if 문 다음에 오는 별도의 명령문 인 Java 언어 사양. 올바른 들여 쓰기는 다음과 같습니다.

if (a)
   doSomething();
doSomeMore();

"dangling else"문제

믹스에 else 를 추가 할 때 두 번째 문제가 나타납니다. 중괄호가 누락 된 다음 예제를 고려하십시오.

if (a)
   if (b)
      doX();
   else if (c)
      doY(); 
else
   doZ();

위의 코드는 말할 것 doZ 때 호출 될 a 이다 false . 사실, 들여 쓰기는 다시 한번 부정확합니다. 코드의 올바른 들여 쓰기는 다음과 같습니다.

if (a)
   if (b)
      doX();
   else if (c)
      doY(); 
   else
      doZ();

코드가 Java 스타일 규칙에 따라 작성된 경우 실제로는 다음과 같습니다.

if (a) {
   if (b) {
      doX();
   } else if (c) {
      doY(); 
   } else {
      doZ();
   }
}

왜 더 나은지 설명하기 위해 우연히 실수로 코드를 들여 쓰기했다고 가정 해보십시오. 다음과 같이 끝낼 수도 있습니다.

if (a) {                         if (a) {
   if (b) {                          if (b) {
      doX();                            doX();
   } else if (c) {                   } else if (c) {
      doY();                            doY();
} else {                         } else {
   doZ();                            doZ();
}                                    }
}                                }

그러나 두 경우 모두, 들여 쓰기가 잘못된 코드는 숙련 된 Java 프로그래머의 눈에 "틀린 것처럼 보입니다".

함정 - 오버라이드 대신 오버로딩

다음 예제를 고려하십시오.

public final class Person {
    private final String firstName;
    private final String lastName;
   
    public Person(String firstName, String lastName) {
        this.firstName = (firstName == null) ? "" : firstName;
        this.lastName = (lastName == null) ? "" : lastName;
    }

    public boolean equals(String other) {
        if (!(other instanceof Person)) {
            return false;
        }
        Person p = (Person) other;
        return firstName.equals(p.firstName) &&
                lastName.equals(p.lastName);
    }

    public int hashcode() {
        return firstName.hashCode() + 31 * lastName.hashCode();
    }
}

이 코드는 예상대로 작동하지 않습니다. 문제는 Personequalshashcode 메소드가 Object 의해 정의 된 표준 메소드를 대체하지 않는다는 것입니다.

  • equals 메소드의 서명이 잘못되었습니다. equals(Object) 아닌 equals(String) 로 선언해야합니다.
  • hashcode 메소드의 이름이 잘못되었습니다. hashCode() (대문자 C에 주의 hashCode() 이어야합니다.

이러한 실수는 우발적 인 과부하를 선언했음을 의미하며 Person 이 다형성 환경에서 사용되는 경우에는 사용되지 않습니다.

그러나이 문제를 처리 할 수있는 간단한 방법이 있습니다 (Java 5부터). 메소드를 재정의 하려는 경우 @Override 주석을 사용하십시오.

Java SE 5
public final class Person {
    ...

    @Override
    public boolean equals(String other) {
        ....
    }

    @Override
    public hashcode() {
        ....
    }
}

@Override 어노테이션을 메소드 선언에 추가하면 컴파일러는 메소드 수퍼 클래스 또는 인터페이스에 선언 된 메소드를 대체 (또는 구현)하는지 확인합니다. 따라서 위의 예에서 컴파일러는 컴파일 오류를 두 가지로 제공합니다.이 오류는 우리에게 실수를 알리는 데 충분합니다.

핏방울 - 8 진 리터럴

다음 코드 스 니펫을 고려하십시오.

// Print the sum of the numbers 1 to 10
int count = 0;
for (int i = 1; i < 010; i++) {    // Mistake here ....
    count = count + i;
}
System.out.println("The sum of 1 to 10 is " + count);

Java 초보자는 위의 프로그램이 잘못된 대답을 인쇄한다는 것을 알고 놀랄 수 있습니다. 실제로 숫자 1 - 8의 합계를 인쇄합니다.

그 이유는 영숫자 ( '0')로 시작하는 정수 리터럴은 자바 컴파일러에 의해 예상되는 십진수 리터럴이 아닌 8 진수 리터럴로 해석되기 때문입니다. 따라서 010 은 8 진수 10이며 십진수는 8입니다.

Pitfall - 표준 클래스와 같은 이름을 가진 클래스 선언

때로 Java를 처음 사용하는 프로그래머는 널리 사용되는 클래스와 같은 이름의 클래스를 정의하는 실수를 저지르는 경우가 있습니다. 예 :

package com.example;

/**
 * My string utilities
 */
public class String {
    ....
}

그런 다음 그들은 예상치 못한 오류가 왜 발생하는지 궁금해합니다. 예 :

package com.example;

public class Test {
    public static void main(String[] args) {
        System.out.println("Hello world!");
    }
}

위에서 컴파일 한 클래스를 실행하려고하면 오류가 발생합니다.

$ javac com/example/*.java
$ java com.example.Test
Error: Main method not found in class test.Test, please define the main method as:
   public static void main(String[] args)
or a JavaFX application class must extend javafx.application.Application

Test 클래스의 코드를 본 누군가는 main 이라는 선언을보고 서명을보고 java 명령이 불평하는 것이 무엇인지 궁금해 할 것이다. 그러나 실제로, java 명령은 진실을 말하고 있습니다.

Test 와 같은 패키지에 String 버전을 선언하면이 버전이 java.lang.String 의 자동 가져 오기보다 우선합니다. 따라서 Test.main 메서드의 서명은 실제로

void main(com.example.String[] args) 

대신에

void main(java.lang.String[] args)

java 명령은 엔트리 포인트 방식으로 그것을 인식하지 않을 것이다.

레슨 : java.lang 기존 클래스와 같은 이름을 가진 클래스 나 Java SE 라이브러리의 다른 일반적으로 사용되는 클래스를 정의하지 마십시오. 그렇게한다면, 모든 종류의 모호한 오류에 대해 스스로를 열어 놓을 것입니다.

Pitfall - '=='를 사용하여 부울 테스트하기

때때로 새로운 자바 프로그래머는 다음과 같은 코드를 작성합니다.

public void check(boolean ok) {
    if (ok == true) {           // Note 'ok == true'
        System.out.println("It is OK");
    }
}

숙련 된 프로그래머는 그것을 서투른 것으로 여기고 그것을 다음과 같이 다시 쓰려고합니다.

public void check(boolean ok) {
    if (ok) {
       System.out.println("It is OK");
    }
}

그러나 단순한 서투름보다 ok == true 가 더 잘못되었습니다. 다음 변형을 고려하십시오.

public void check(boolean ok) {
    if (ok = true) {           // Oooops!
        System.out.println("It is OK");
    }
}

여기서 프로그래머는 == as = ...을 잘못 입력했습니다. 코드에는 미묘한 버그가 있습니다. 식 x = true 무조건 할당 truex 다음으로 평가 true . 즉, 매개 변수가 무엇이든 관계없이 check 메소드가 "It is OK"를 인쇄합니다.

여기서 교훈은 == false== true 를 사용하는 습관을 벗어나는 것입니다. 장황함에 더하여, 그들은 당신의 코딩을 에러가 발생하기 쉽게 만든다.


참고 : 함정을 피하는 ok == true 의 가능한 대안은 Yoda 조건 을 사용하는 것입니다 . 즉, 관계 연산자의 왼쪽에 리터럴을 넣습니다 ( true == ok 처럼). 이것은 작동하지만 대부분의 프로그래머는 Yoda 조건이 이상하게 보일 수도 있음에 동의 할 것입니다. 확실히 ok (또는 !ok )는 더 간결하고 자연 스럽습니다.

핏방울 - 와일드 카드 가져 오기로 인해 코드가 깨지기 쉽습니다.

다음 부분적인 예를 고려하십시오.

import com.example.somelib.*;
import com.acme.otherlib.*;

public class Test {
    private Context x = new Context();   // from com.example.somelib
    ...
}

그것이 somelib 버전 1.0과 somelib 버전 1.0에 대한 코드를 처음 개발했을 때를 가정 otherlib . 나중에 어떤 시점에서 종속성을 이후 버전으로 업그레이드해야하며 otherlib 버전 2.0을 사용하기로 결정했습니다. 또한 1.0과 2.0 사이에서 otherlib 에 한 변경 사항 중 하나가 Context 클래스를 추가하는 것이 otherlib 가정하십시오.

이제 Test 를 다시 컴파일하면 Context 가 모호한 가져 오기라는 컴파일 오류가 발생합니다.

당신이 코드베이스에 익숙하다면, 이것은 아마도 사소한 불편 일 것이다. 그렇지 않다면, 당신은이 문제를 해결하기 위해 할 일이 있습니다. 여기서 그리고 잠재적으로 다른 곳에서 할 수 있습니다.

여기서 문제는 와일드 카드 가져 오기입니다. 반면에 와일드 카드를 사용하면 클래스를 몇 줄 더 짧게 만들 수 있습니다. 반면에 :

  • 코드베이스의 다른 부분, Java 표준 라이브러리 또는 타사 라이브러리에 대한 위쪽 호환 가능 변경은 컴파일 오류를 유발할 수 있습니다.

  • 가독성이 떨어집니다. IDE를 사용하지 않는 한 와일드 카드 가져 오기 중 이름이 지정된 클래스를 가져 오는 것이 무엇인지 파악하는 것은 어려울 수 있습니다.

교훈은 오래 살 필요가있는 코드에서 와일드 카드 가져 오기를 사용하는 것은 좋지 않다는 것입니다. 특정 (와일드 카드가 아닌) 가져 오기는 IDE를 사용하는 경우 유지하기 위해 많은 노력을 기울이지 않으며 그 노력은 가치가 있습니다.

Pitfall : 인수 또는 사용자 입력 유효성 검사에 'assert'사용

StackOverflow에서 종종 assert 를 사용하여 메서드에 제공된 인수를 유효하게하는지 또는 심지어 사용자가 제공 한 입력을 사용하는지 여부가 문제입니다.

간단한 대답은 적절하지 않다는 것입니다.

더 나은 대안은 다음과 같습니다.

  • 커스텀 코드를 사용해 IllegalArgumentException를 Throw하는
  • Google Guava 라이브러리에서 사용할 수있는 Preconditions 방법 사용
  • Apache Commons Lang3 라이브러리에서 사용할 수있는 Validate 메소드 사용.

이것이 Java 언어 사양 (JLS 14.10, Java 8) 에서이 문제에 관해 조언하는 내용입니다.

일반적으로 어설 션 검사는 프로그램 개발 및 테스트 중에 활성화되고 배포를 위해 비활성화되어 성능을 향상시킵니다.

어설 션은 사용할 수 없으므로 프로그램은 어설 션에 포함 된 표현식을 평가한다고 가정해서는 안됩니다. 따라서 이러한 부울 식은 일반적으로 부작용이 없어야합니다. 이러한 불리언 표현식을 평가해도 평가가 완료된 후 표시되는 상태에는 영향을주지 않습니다. 어설 션에 포함 된 부울식이 부작용을 갖는 것은 불법이 아니지만 어설 션을 사용할지 여부에 따라 프로그램 동작이 달라질 수 있으므로 일반적으로 부적절합니다.

이 점에 비추어, 공용 메소드의 인수 검사에 어설 션을 사용해서는 안됩니다. 인수 검사는 일반적으로 메소드 계약의 일부이며, 어설 션을 사용할지 여부에 관계없이이 계약을 유지해야합니다.

인수 검사에 어설 션을 사용하는 경우의 두 번째 문제점은 잘못된 인수가 적절한 런타임 예외 (예 : IllegalArgumentException , ArrayIndexOutOfBoundsException 또는 NullPointerException )를 야기해야한다는 것입니다. 어설 션 오류는 적절한 예외를 throw하지 않습니다. 다시 말하지만 공개 메소드에 대한 인수 검사에 어설 션을 사용하는 것은 불법이 아니지만 일반적으로 부적절합니다. AssertionError 절대로 잡힐 수 없지만 그렇게 할 수 있습니다. 따라서 try 문에 대한 규칙은 throw 문에 대한 현재 처리와 비슷하게 try 블록에 나타나는 어설 션을 처리해야합니다.

자동 언 박싱 Null 객체의 프리미티브로의 찔림

public class Foobar {
    public static void main(String[] args) {

        // example: 
        Boolean ignore = null;
        if (ignore == false) {
            System.out.println("Do not ignore!");
        }
    }
}

여기서 함정은 nullfalse 와 비교된다는 것입니다. 우리는 원시적 비교하고 있기 때문에 boolean 에 대해 Boolean , 자바하여 언 박싱을 시도 Boolean Object 비교를 위해 준비 원시 상당으로. 그러나 그 값은 null 이기 때문에 NullPointerException 이 발생합니다.

Java는 런타임시 NullPointerException 을 발생시키는 null 값과 원시 유형을 비교할 수 없습니다. 조건의 원시적 인 경우를 생각해 보자. false == null ; 이는 incomparable types: int and <null>컴파일 타임 오류를 생성합니다.



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow