수색…


소개

이 항목에서는 Java 초보자가 범하는 일반적인 실수 중 일부를 설명합니다.

여기에는 Java 언어의 일반적인 실수 나 런타임 환경에 대한 이해가 포함됩니다.

특정 API와 관련된 실수는 해당 API와 관련된 주제로 설명 될 수 있습니다. 문자열은 특별한 경우입니다. Java 언어 사양에서 다룹니다. 일반적인 실수 이외의 자세한 내용 은이 항목의 문자열에 설명 되어 있습니다.

Pitfall : ==를 사용하여 Integer와 같은 프리미티브 래퍼 객체 비교

(이 함정은 모든 기본 래퍼 유형에 동일하게 적용되지만 Integerint 대해 설명 할 것입니다.)

Integer 객체를 사용하여 작업 할 때 == 를 사용하여 값을 비교하는 것이 유혹 스러운데, 그 이유는 int 값으로 처리하기 때문입니다. 그리고 어떤 경우에는 이것이 효과가있는 것처럼 보일 것입니다 :

Integer int1_1 = Integer.valueOf("1");
Integer int1_2 = Integer.valueOf(1);

System.out.println("int1_1 == int1_2: " + (int1_1 == int1_2));          // true
System.out.println("int1_1 equals int1_2: " + int1_1.equals(int1_2));   // true

여기서 우리는 값이 1 두 개의 Integer 객체를 생성하고 비교합니다 (이 경우 Stringint 리터럴 중 하나를 만들었습니다.) 다른 대안이 있습니다. 또한 두 가지 비교 방법 ( ==equals )이 모두 true 냅니다.

이 동작은 다른 값을 선택하면 변경됩니다.

Integer int2_1 = Integer.valueOf("1000");
Integer int2_2 = Integer.valueOf(1000);

System.out.println("int2_1 == int2_2: " + (int2_1 == int2_2));          // false
System.out.println("int2_1 equals int2_2: " + int2_1.equals(int2_2));   // true

이 경우 equals 비교 만 올바른 결과를 산출합니다.

이러한 동작의 차이는 JVM이 -128에서 127 범위의 Integer 객체 캐시를 유지한다는 것입니다. (상위 값은 시스템 속성 "java.lang.Integer.IntegerCache.high"로 오버라이드되거나 JVM 인수 "-XX : AutoBoxCacheMax = size"). 이 범위의 값의 경우 Integer.valueOf() 는 새 값을 만드는 대신 캐시 된 값을 반환합니다.

따라서 첫 번째 예제에서 Integer.valueOf(1)Integer.valueOf("1") 호출은 동일한 캐시 된 Integer 인스턴스를 반환했습니다. 대조적으로 두 번째 예제에서 Integer.valueOf(1000)Integer.valueOf("1000") 는 새 Integer 객체를 만들고 반환했습니다.

참조 유형에 대한 == 연산자는 참조 동일성 (즉, 동일한 객체)을 테스트합니다. 따라서 첫 번째 예제에서는 참조가 동일하므로 int1_1 == int1_2true 입니다. 두 번째 예제에서 참조가 다르므로 int2_1 == int2_2 는 false입니다.

찌찌 : 자원을 잊는 것을 잊어 버림

프로그램이 파일이나 네트워크 연결과 같은 리소스를 열 때마다 리소스를 사용한 후에 리소스를 해제하는 것이 중요합니다. 그러한 자원에 대한 조작 중에 예외가 발생하는 경우에도 유사한주의를 기울여야합니다. FileInputStream 에는 가비지 수집 이벤트에서 close() 메서드를 호출하는 finalizer 가 있다고 주장 할 수 있습니다. 그러나 가비지 수집주기가 시작될시기를 확신 할 수 없기 때문에 입력 스트림은 컴퓨터 자원을 무기한 소모 할 수 있습니다. try-catch 블록의 finally 섹션에서 리소스를 닫아야합니다.

Java SE 7
private static void printFileJava6() throws IOException {
    FileInputStream input;
    try {
        input = new FileInputStream("file.txt");
        int data = input.read();
        while (data != -1){
            System.out.print((char) data);
            data = input.read();
        }
    } finally {
        if (input != null) {
            input.close();
        }
    }
}

Java 7부터는 Java 7에서 특히이 경우에 대해 try-with-resources라고하는 정말 유용하고 깔끔한 진술이 소개되었습니다.

Java SE 7
private static void printFileJava7() throws IOException {
    try (FileInputStream input = new FileInputStream("file.txt")) {
        int data = input.read();
        while (data != -1){
            System.out.print((char) data);
            data = input.read();
        }
    }
}

리소스와 try-with 문은 Closeable 또는 AutoCloseable 인터페이스를 구현하는 모든 개체와 함께 사용할 수 있습니다. 이는 각 자원이 명령.의 끝까지 닫히게합니다. 두 인터페이스의 차이점은 Closeableclose() 메소드가 어떤 식 으로든 처리 Closeable IOException 을 던졌습니다.

리소스가 이미 열려 있지만 사용 후 안전하게 닫아야하는 경우 try-with-resources 내부의 로컬 변수에 리소스를 할당 할 수 있습니다

Java SE 7
private static void printFileJava7(InputStream extResource) throws IOException {
    try (InputStream input = extResource) {
        ... //access resource
    }
}

try-with-resources 생성자에서 생성 된 로컬 리소스 변수는 실질적으로 최종적입니다.

틈 : 메모리 누출

Java는 메모리를 자동으로 관리합니다. 메모리를 수동으로 해제 할 필요는 없습니다. 힙에있는 객체의 메모리는 실제 스레드가 객체에 더 이상 접근 할 수 없을 때 가비지 수집기에 의해 해제 될 수 있습니다.

그러나 더 이상 필요없는 개체에 연결할 수있게하여 메모리가 해제되는 것을 방지 할 수 있습니다. 이것을 메모리 누수 또는 메모리 팩팅이라 부르더라도 할당 된 메모리가 불필요하게 증가합니다.

Java에서의 메모리 누수는 다양한 방법으로 발생할 수 있지만 가장 일반적인 이유는 가비지 수집기가 여전히 참조가있는 동안 힙에서 객체를 제거 할 수 없으므로 영원한 객체 참조입니다.

정적 필드

객체의 콜렉션을 포함하는 static 필드를 사용하여 클래스를 정의하고 콜렉션이 더 이상 필요하지 않으면 static 필드를 null 로 설정하는 것을 잊어서 그러한 참조를 작성할 수 있습니다. static 필드는 GC 루트로 간주되어 수집되지 않습니다. 또 다른 문제는 JNI 가 사용될 때 힙이 아닌 메모리의 누수입니다.

클래스 로더 누출

그러나 가장 교활한 유형의 메모리 누출은 클래스 로더 유출 입니다. 클래스 로더는로드 한 모든 클래스에 대한 참조를 보유하고 있으며 모든 클래스는 클래스 로더에 대한 참조를 보유합니다. 모든 객체는 클래스에 대한 참조도 보유합니다. 따라서 클래스 로더가로드 한 클래스의 단일 객체라도 가비지가 아니라면 해당 클래스 로더가로드 한 단일 클래스를 수집 할 수 없습니다. 각 클래스는 정적 필드를 참조하기 때문에 수집 할 수도 없습니다.

누적 누설 누적 누설 예제는 다음과 같을 수 있습니다.

final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
final Deque<BigDecimal> numbers = new LinkedBlockingDeque<>();
final BigDecimal divisor = new BigDecimal(51);

scheduledExecutorService.scheduleAtFixedRate(() -> {
    BigDecimal number = numbers.peekLast();
    if (number != null && number.remainder(divisor).byteValue() == 0) {
        System.out.println("Number: " + number);
        System.out.println("Deque size: " + numbers.size());
    }
}, 10, 10, TimeUnit.MILLISECONDS);

scheduledExecutorService.scheduleAtFixedRate(() -> {
    numbers.add(new BigDecimal(System.currentTimeMillis()));
}, 10, 10, TimeUnit.MILLISECONDS);

try {
    scheduledExecutorService.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
    e.printStackTrace();
}

이 예에서는 두 개의 예약 된 작업을 만듭니다. 첫 번째 작업은 numbers 라고 불리는 deque에서 마지막 숫자를 취합니다. 숫자가 51로 나눌 수 있으면 숫자와 양단 큐의 크기를 출력합니다. 두 번째 작업은 숫자를 양키에 넣습니다. 두 작업 모두 고정 속도로 예약되며 10ms마다 실행됩니다.

코드가 실행되면 deque의 크기가 계속 증가하는 것을 볼 수 있습니다. 그러면 결국 deque이 사용 가능한 모든 힙 메모리를 사용하는 객체로 채워집니다.

이 프로그램의 의미를 보존이 동안을 방지하기 위해, 우리는 양단 큐에서 숫자를 복용에 대해 다른 방법을 사용할 수 있습니다 pollLast . 메소드는 달리 peekLast , pollLast 요소를 반환하면서 양단 큐에서 제거 peekLast 마지막 요소를 반환합니다.

함정 : ==를 사용하여 문자열 비교

Java 초보자가 자주 범하는 실수는 == 연산자를 사용하여 두 문자열이 같은지 테스트하는 것입니다. 예 :

public class Hello {
    public static void main(String[] args) {
        if (args.length > 0) {
            if (args[0] == "hello") {
                System.out.println("Hello back to you");
            } else {
                System.out.println("Are you feeling grumpy today?");
            }
        }
    }
}

위의 프로그램은 첫 번째 명령 줄 인수를 테스트하고 그 메시지가 "hello"가 아닌 다른 메시지를 인쇄합니다. 그러나 문제는 그것이 작동하지 않을 것이라는 것입니다. 이 프로그램은 "오늘 심술 궂은가요?"라고 출력 할 것입니다. 첫 번째 명령 줄 인수가 무엇이든 상관 없습니다.

이 특별한 경우 String "hello"는 문자열 풀에 저장되고 String args [0]은 힙에 상주합니다. 즉, 동일한 리터럴을 나타내는 두 개의 객체가 있으며 각각은 참조를 갖습니다. == 실제 평등이 아닌 참조를 테스트하기 때문에 비교는 대부분의 경우 거짓을 산출합니다. 이것은 항상 그렇게 할 것이라는 의미는 아닙니다.

== 를 사용하여 문자열을 테스트 할 때 실제로 테스트하는 것은 두 개의 String 객체가 동일한 Java 객체 인 경우입니다. 불행히도, 그것은 자바에서 문자열 평등이 의미하는 것이 아닙니다. 실제로 문자열을 테스트하는 올바른 방법은 equals(Object) 메서드를 사용하는 것입니다. 한 쌍의 문자열의 경우 동일한 순서로 동일한 문자로 구성되어 있는지 테스트하려고합니다.

public class Hello2 {
    public static void main(String[] args) {
        if (args.length > 0) {
            if (args[0].equals("hello")) {
                System.out.println("Hello back to you");
            } else {
                System.out.println("Are you feeling grumpy today?");
            }
        }
    }
}

그러나 실제로 악화됩니다. 문제는 == 일부 상황에서 예상되는 답을 줄 입니다. 예를 들어

public class Test1 {
    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "hello";
        if (s1 == s2) {
            System.out.println("same");
        } else {
            System.out.println("different");
        }
    }
}

흥미롭게도, 문자열을 잘못 테스트하더라도 "same"이 인쇄됩니다. 왜 그런가요? 자바 언어 명세 (3.10.5 절 : 문자열 리터럴) 에서는 동일한 문자로 구성된 두 개의 문자열 >> literal <<이 실제로 같은 Java 객체에 의해 표현된다는 것을 규정하고 있기 때문에. 따라서 == test는 동일한 리터럴에 대해 true를 제공합니다. (문자열 리터럴은 "interned"되어 코드가로드 될 때 공유 "문자열 풀"에 추가되지만 실제로 구현 세부 사항입니다.)

혼란에 더해 Java 언어 스펙에서는, 2 개의 string 리터럴을 연결하는 컴파일시의 상수 표현식이있을 때, 그것은 단일 리터럴과 동일하다는 것을 규정합니다. 그러므로:

    public class Test1 {
    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "hel" + "lo";
        String s3 = " mum";
        if (s1 == s2) {
            System.out.println("1. same");
        } else {
            System.out.println("1. different");
        }
        if (s1 + s3 == "hello mum") {
            System.out.println("2. same");
        } else {
            System.out.println("2. different");
        }
    }
}

그러면 "1. same"과 "2. different"가 출력됩니다. 첫 번째 경우에 + 표현식은 컴파일 타임에 평가되고 하나의 String 객체를 자체와 비교합니다. 두 번째 경우에는 런타임에 평가되고 두 ​​개의 다른 String 객체를 비교합니다

요약하면 == 를 사용하여 Java에서 문자열을 테스트하는 것은 거의 항상 부정확하지만 잘못된 대답을 제공하는 것은 아닙니다.

Pitfall : 파일을 열기 전에 파일을 테스트합니다.

어떤 사람들은 더 나은 진단을 제공하거나 예외 처리를 피하기 위해 파일을 열기 전에 파일에 다양한 테스트를 적용해야한다고 권장합니다. 예를 들어,이 메소드는 path 가 읽을 수있는 파일에 해당하는지 확인하려고 시도합니다.

public static File getValidatedFile(String path) throws IOException {
    File f = new File(path);
    if (!f.exists()) throw new IOException("Error: not found: " + path);
    if (!f.isFile()) throw new IOException("Error: Is a directory: " + path);
    if (!f.canRead()) throw new IOException("Error: cannot read file: " + path);
    return f;
}

위의 방법을 다음과 같이 사용할 수 있습니다.

File f = null;
try {
    f = getValidatedFile("somefile");
} catch (IOException ex) {
    System.err.println(ex.getMessage());
    return;
}
try (InputStream is = new FileInputStream(file)) {
    // Read data etc.
}

첫 번째 문제는 FileInputStream(File) 에 대한 서명에 있습니다. 왜냐하면 컴파일러는 여기에 IOException 을 잡거나 더 많은 스택을 요구하기 때문입니다.

두 번째 문제는 getValidatedFile 의해 수행 된 검사가 FileInputStream 이 성공할 것이라고 보장하지 않는다는 것입니다.

  • 경쟁 조건 : getValidatedFile 반환 된 후 다른 스레드 또는 별도 프로세스가 파일의 이름을 바꾸거나 파일을 삭제하거나 읽기 액세스를 제거 할 수 있습니다. 그러면 사용자 지정 메시지가없는 "일반" IOException 이 발생합니다.

  • 이러한 테스트에서 다루지 않는 가장자리 사례가 있습니다. 예를 들어, SELinux가 "시행"모드 인 시스템에서 canRead() true 리턴 함에도 불구하고 파일 읽기 시도가 실패 할 수 있습니다.

세 번째 문제는 테스트가 비효율적이라는 것입니다. 예를 들어, exists , isFilecanRead 호출은 각각 만들 것 콜이 필요한 검사를 수행 할 수 있습니다. 그런 다음 다른 시스템 콜이 파일을 열도록 만들어지고, 뒤에서 동일한 체크가 반복됩니다.

즉, getValidatedFile 과 같은 getValidatedFile 가 잘못되었습니다. 단순히 파일을 열고 예외를 처리하는 것이 좋습니다.

try (InputStream is = new FileInputStream("somefile")) {
    // Read data etc.
} catch (IOException ex) {
    System.err.println("IO Error processing 'somefile': " + ex.getMessage());
    return;
}

열기와 읽기 중에 발생하는 입출력 오류를 구별하려면 중첩 된 try / catch를 사용할 수 있습니다. 열린 실패에 대해 더 나은 진단을 원할 경우 처리기에서 exists , isFilecanRead 검사를 수행 할 수 exists .

Pitfall : 변수를 객체로 생각하기

Java 변수는 오브젝트를 나타냄니다.

String foo;   // NOT AN OBJECT

또한 Java 배열에도 객체가 들어 있지 않습니다.

String bar[] = new String[100];  // No member is an object.

실수로 변수를 객체로 생각하면 Java 언어의 실제 동작이 놀라게됩니다.

  • 기본 유형 (예 : int 또는 float )을 갖는 Java 변수의 경우 변수는 값의 사본을 보유합니다. 프리미티브 값의 모든 복사본은 구별 할 수 없습니다. 즉 하나의 int 값은 하나뿐입니다. 원시 값은 객체가 아니며 객체처럼 작동하지 않습니다.

  • 참조 유형 (클래스 또는 배열 유형)이있는 Java 변수의 경우 변수는 참조를 보유합니다. 참고 문헌의 모든 사본은 구별 할 수 없습니다. 참조는 객체를 가리킬 수도 있고, 객체가 아닌 객체를 가리키는 null 일 수도 있습니다. 그러나 그들은 객체가 아니며 객체처럼 행동하지 않습니다.

변수는 두 경우 모두 객체가 아니며 두 경우 모두 객체를 포함하지 않습니다. 그것들은 객체에 대한 참조를 포함 할 수 있지만 그것은 다른 것을 말하고 있습니다.

예제 클래스

다음 예제는 2D 공간의 한 점을 나타내는이 클래스를 사용합니다.

public final class MutableLocation {
   public int x;
   public int y;

   public MutableLocation(int x, int y) {
       this.x = x;
       this.y = y;
   }

   public boolean equals(Object other) {
       if (!(other instanceof MutableLocation) {
           return false;
       }
       MutableLocation that = (MutableLocation) other;
       return this.x == that.x && this.y == that.y;
   }
}

이 클래스의 인스턴스는, int 형의 2 개의 필드 xy 를 가지는 오브젝트입니다.

우리는 MutableLocation 클래스의 인스턴스를 많이 가질 수 있습니다. 일부는 2D 공간에서 동일한 위치를 나타냅니다. 즉, xy 의 각 값이 일치합니다. 다른 것들은 다른 위치를 나타낼 것입니다.

여러 변수가 동일한 객체를 가리킬 수 있음

 MutableLocation here = new MutableLocation(1, 2);
 MutableLocation there = here;
 MutableLocation elsewhere = new MutableLocation(1, 2);

위에서 우리는 세 가지 변수를 선언 한 here , thereelsewhere 참조 보유 할 수 MutableLocation 객체.

당신이 (틀린)이 변수들을 객체라고 생각한다면, 당신은 그 문장을 다음과 같이 오독 할 것입니다 :

  1. "[1, 2]"위치를 here 복사 here
  2. 위치 복사 "[1, 2]"에 there
  3. "[1, 2]"위치를 elsewhere 위치로 복사하십시오.

그로부터 세 변수에 세 개의 독립적 인 객체가 있다고 추측 할 수 있습니다. 실제로 위에서 작성한 객체두 개뿐입니다 . 변수 herethere 실제로 같은 객체를 참조하십시오.

우리는 이것을 증명할 수 있습니다. 위와 같이 변수 선언을 가정합니다.

System.out.println("BEFORE: here.x is " + here.x + ", there.x is " + there.x +
                   "elsewhere.x is " + elsewhere.x);
here.x = 42;
System.out.println("AFTER: here.x is " + here.x + ", there.x is " + there.x +
                   "elsewhere.x is " + elsewhere.x);

그러면 다음과 같이 출력됩니다.

BEFORE: here.x is 1, there.x is 1, elsewhere.x is 1
AFTER: here.x is 42, there.x is 42, elsewhere.x is 1

우리는 여기에 새로운 값을 할당 here.x , 여기를 통해 there.x 값을 변경했습니다. there.x . 그들은 같은 대상을 가리키고 있습니다. 그러나 elsewhere.x 보게되는 값은 변하지 않았습니다. 그래서 elsewhere 에서는 다른 객체를 참조해야합니다.

변수가 객체 인 경우 here.x = 42 는 할당을 변경하지 않습니다. there.x .

항등 연산자는 두 객체가 같은지 테스트하지 않습니다.

항등 ( == ) 연산자를 참조 값에 적용하면 값이 동일한 객체를 참조하는지 테스트합니다. 두 개의 (다른) 객체가 직관적 인 의미에서 "동등"한지 여부는 테스트하지 않습니다 .

 MutableLocation here = new MutableLocation(1, 2);
 MutableLocation there = here;
 MutableLocation elsewhere = new MutableLocation(1, 2);

 if (here == there) {
     System.out.println("here is there");
 }
 if (here == elsewhere) {
     System.out.println("here is elsewhere");
 }

"here is there"라는 문구가 인쇄되지만 "here is other is"라는 문구는 인쇄되지 않습니다. ( hereelsewhere 의 참조는 두 개의 별개의 객체에 대한 것입니다.)

반대로 위에서 구현 한 equals(Object) 메소드를 호출하면 두 개의 MutableLocation 인스턴스의 위치가 같은지 테스트 할 예정입니다.

 if (here.equals(there)) {
     System.out.println("here equals there");
 }
 if (here.equals(elsewhere)) {
     System.out.println("here equals elsewhere");
 }

그러면 두 메시지가 모두 인쇄됩니다. 특히, here.equals(elsewhere) 는 두 개의 MutableLocation 객체가 MutableLocation 대해 우리가 선택한 의미 론적 기준이 만족 되었기 때문에 true 반환 true .

메소드 호출은 객체를 전혀 전달하지 않습니다.

Java 메소드 호출 은 값 1 로 전달을 사용하여 인수를 전달하고 결과를 리턴합니다.

참조 값을 메서드에 전달할 때 실제로 개체 대한 참조를 값으로 전달합니다. 즉 개체 참조의 복사본을 만드는 중입니다.

두 객체 참조가 여전히 같은 객체를 가리키고있는 한, 참조에서이 객체를 수정할 수 있습니다. 이는 일부 객체에 대한 혼란을 야기합니다.

그러나 참조로 객체를 전달 하지는 않습니다 . 2 . 개체 참조 복사본이 다른 개체를 가리 키도록 수정 된 경우 원본 개체 참조는 여전히 원래 개체를 가리 킵니다.

void f(MutableLocation foo) {  
    foo = new MutableLocation(3, 4);   // Point local foo at a different object.
}

void g() {
    MutableLocation foo = MutableLocation(1, 2);
    f(foo);
    System.out.println("foo.x is " + foo.x); // Prints "foo.x is 1".
}

또한 개체 사본을 전달하지 않습니다.

void f(MutableLocation foo) {  
    foo.x = 42;
}

void g() {
    MutableLocation foo = new MutableLocation(0, 0);
    f(foo);
    System.out.println("foo.x is " + foo.x); // Prints "foo.x is 42"
}

1 - 파이썬과 루비 같은 언어에서, "공유에 의한 전달"이라는 용어는 객체 / 참조의 "가치에 의한 전달"에 우선합니다.

2 - "참조로 전달"또는 "참조로 호출"이라는 용어는 프로그래밍 언어 용어에서 매우 특정한 의미를 지닙니다. 결과적 으로 변수 또는 배열 요소 의 주소를 전달하므로 호출 된 메소드가 형식 인수에 새 값을 지정할 때 원래 변수의 값을 변경합니다. Java는 이것을 지원하지 않습니다. 매개 변수 전달을위한 다른 메커니즘에 대한 자세한 설명은 https://en.wikipedia.org/wiki/Evaluation_strategy 를 참조 하십시오 .

함정 : 부과와 부작용을 결합 함

때때로 우리는 StackOverflow Java 질문 (및 C 또는 C ++ 질문)에서 다음과 같은 내용을 묻습니다.

i += a[i++] + b[i--];

i , ab 의 알려진 초기 상태에 대해 평가합니다.

일반적으로 말하기 :

  • Java의 경우 대답은 항상 1로 지정되어 있지만 명확하지 않으며 종종 파악하기가 어렵습니다.
  • C와 C ++에 대한 대답은 종종 지정되지 않습니다.

이러한 예는 종종 시험이나 면접에서 학생이나 면접자가 표현 평가가 Java 프로그래밍 언어에서 실제로 어떻게 작동 하는지를 이해하기위한 시도로 사용됩니다. 이것은 논증의 여지가 있지만 "지식 테스트"로서 합법적이지만 실제 프로그램에서이 작업을 수행해야한다는 의미는 아닙니다.

설명하기 위해, 다음과 같은 단순 해 보이는 예는 (같은 StackOverflow의 질문에 몇 번 출연 이 하나 ). 경우에 따라 다른 사람의 코드에서 실수로 나타납니다.

int a = 1;
a = a++;
System.out.println(a);    // What does this print.

그 진술을 빨리 읽는 대부분의 프로그래머 (Java 전문가 포함)는 2 출력한다고 말합니다. 사실 1 을 출력합니다. 그 이유에 대한 자세한 설명은 이 답변을 읽어보십시오.

그러나이 유사한 예에서 실제 테이크 아웃은 자바모두에 할당 부작용이 같은 변수가 기껏 열심히 이해하고, 최악의 솔직한 오해의 소지가 될 것입니다 것입니다. 이런 식으로 코드를 작성하면 안됩니다.


1 - 다른 스레드에서 변수 또는 객체를 볼 수있는 경우 Java 메모리 모델 의 잠재적 인 문제를 모듈화합니다.

함정 : String이 불변의 클래스라는 것을 이해하지 못한다.

새로운 Java 프로그래머는 종종 Java String 클래스가 변경되지 않는다는 것을 잊거나 완전히 이해하지 못합니다. 이 경우 다음 예제와 같은 문제가 발생합니다.

public class Shout {
    public static void main(String[] args) {
        for (String s : args) {
            s.toUpperCase();
            System.out.print(s);
            System.out.print(" ");
        }
        System.out.println();
    }
}

위의 코드는 명령 행 인수를 대문자로 인쇄합니다. 불행히도, 그것은 작동하지 않습니다, 인수의 경우 변경되지 않습니다. 문제는 다음과 같습니다.

s.toUpperCase();

toUpperCase() 를 호출하면 s 가 대문자 문자열로 변경됩니다. 그렇지 않습니다. 그것은 할 수 없다! String 객체는 변경할 수 없습니다. 그들은 변경할 수 없습니다.

실제로 toUpperCase() 메서드 호출하는 String 의 대문자 버전 인 String 객체를 반환 합니다. 이것은 아마도 새로운 String 객체 일 것입니다. 그러나 s 가 이미 모두 대문자이면 결과는 기존 문자열이 될 수 있습니다.

따라서이 메서드를 효과적으로 사용하려면 메서드 호출에서 반환 한 개체를 사용해야합니다. 예 :

s = s.toUpperCase();

실제로 "문자열은 변하지 않습니다"규칙은 모든 String 메서드에 적용됩니다. 이것을 기억하면 초급의 실수 전체를 피할 수 있습니다.



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