Java에서 지역 변수가 초기화되지 않는 이유는 무엇입니까?


103

Java 설계자가 지역 변수에 기본값을 부여해서는 안된다고 생각한 이유가 있었습니까? 진지하게, 인스턴스 변수에 기본값이 주어질 수 있다면 왜 우리는 지역 변수에 대해 똑같이 할 수 없습니까?

또한 블로그 게시물에 대한이 댓글 에서 설명한대로 문제가 발생합니다 .

이 규칙은 finally 블록에서 리소스를 닫으려고 할 때 가장 실망 스럽습니다. try 내에서 리소스를 인스턴스화하지만 finally 내에서 닫으려고하면이 오류가 발생합니다. 인스턴스화를 시도 외부로 이동하면 시도 내에 있어야한다는 또 다른 오류가 발생합니다.

매우 실망 스럽습니다.



1
죄송합니다 ... 질문을 입력 할 때이 질문이 나타나지 않았습니다. 그러나 두 질문 사이에 차이가있는 것 같습니다 ... Java 디자이너가 왜 이렇게 디자인 했는지 알고 싶습니다. 당신이 가리키는 문제는 ... 그 요구하지 않습니다
Shivasubramanian

관련 C # 질문도 참조하십시오 : stackoverflow.com/questions/1542824/…
Raedwald

간단히-컴파일러가 초기화되지 않은 지역 변수를 추적하기 쉽기 때문입니다. 다른 변수와 동일하게 할 수 있다면 그렇게 될 것입니다. 컴파일러는 당신을 돕기 위해 노력하고 있습니다.
rustyx

답변:


62

지역 변수는 대부분 일부 계산을 수행하기 위해 선언됩니다. 따라서 변수의 값을 설정하는 프로그래머의 결정이며 기본값을 사용해서는 안됩니다. 프로그래머가 실수로 로컬 변수를 초기화하지 않았고 기본값을 사용하면 출력이 예상치 못한 값이 될 수 있습니다. 따라서 지역 변수의 경우 컴파일러는 정의되지 않은 값의 사용을 피하기 위해 프로그래머가 변수에 액세스하기 전에 일부 값으로 초기화하도록 요청합니다.


23

링크 한 "문제" 가이 상황을 설명하는 것 같습니다.

SomeObject so;
try {
  // Do some work here ...
  so = new SomeObject();
  so.DoUsefulThings();
} finally {
  so.CleanUp(); // Compiler error here
}

댓글 작성자의 불만은 컴파일러가 finally섹션 의 줄에서 so초기화되지 않았을 수 있다고 주장하면서 멍청하다는 것입니다. 주석은 다음과 같이 코드를 작성하는 다른 방법을 언급합니다.

// Do some work here ...
SomeObject so = new SomeObject();
try {
  so.DoUsefulThings();
} finally {
  so.CleanUp();
}

댓글 작성자는 컴파일러가 코드가 "시도 내에 있어야합니다"라고 말하기 때문에 해당 솔루션에 만족하지 않습니다. 이는 일부 코드가 더 이상 처리되지 않는 예외를 발생시킬 수 있음을 의미합니다. 잘 모르겠습니다. 내 코드의 두 버전 모두 예외를 처리하지 않으므로 첫 번째 버전에서 예외와 관련된 모든 것이 두 번째 버전에서 동일하게 작동해야합니다.

어쨌든,이 두 번째 버전의 코드는 그것을 작성 하는 올바른 방법입니다. 첫 번째 버전에서는 컴파일러의 오류 메시지가 정확했습니다. so변수는 초기화되지 않은 될 수 있습니다. 특히 SomeObject생성자가 실패하면 so초기화되지 않으므로을 호출하려고하면 오류가 발생합니다 so.CleanUp. 섹션이 마무리 하는 리소스를 획득 한 후에 는 항상 try섹션에 들어가십시오 .finally

try- finally애프터 블록 so초기화가 입력 금지에 SomeObject상관없이 다른 일이 무엇인지는 청소 도착하지 않습니다하는 예입니다. 이 경우 다른 필요가 실행할 수있는 것들,하지만 그들은하지 여부와 관련된 SomeObject인스턴스 속성이 할당 된 후, 그들은에 가야 다른 try - finally아마, 블록 내가 보여준 하나의 랩을.

사용하기 전에 수동으로 변수를 할당하도록 요구하는 것은 실제 문제로 이어지지 않습니다. 그것은 사소한 번거 로움으로 이어지지 만 코드가 더 좋을 것입니다. 당신은 더 제한 범위에 변수가 있고,거야 try- finally너무 많이 보호하기 위해 노력하지 않는 블록을.

지역 변수에 기본값이 so있는 경우 첫 번째 예에서는 null. 그것은 실제로 아무것도 해결하지 못했을 것입니다. 대신에 컴파일 타임 오류를 얻는 finally블록, 당신은이 것 NullPointerException이 숨어 그 힘의 숨기기 다른 예외 코드의 "여기에 몇 가지 작업을 수행"절에서 발생할 수있는 무엇을. (또는 finally섹션의 예외가 이전 예외에 자동으로 연결됩니까? 기억 나지 않습니다. 그렇더라도 실제 예외와는 다른 예외가있을 것입니다.)


2
finally 블록에 if (so! = null) ...를 사용하지 않는 이유는 무엇입니까?
izb

그래도 컴파일러 경고 / 오류가 발생합니다. 컴파일러가 확인하면 그것을 이해한다고 생각하지 않습니다 (하지만 테스트되지 않은 메모리에서 이것을 수행합니다).
Chii

6
나는 시도하기 전에 SomeObject so = null을 넣고 finally 절에 null 검사를 넣었습니다. 이렇게하면 컴파일러 경고가 없습니다.
Juha Syrjälä

복잡한 이유는 무엇입니까? 이런 식으로 try-finally 블록을 작성하면 변수에 유효한 값이 있음을 알 수 있습니다. 널 검사가 필요하지 않습니다.
Rob Kennedy

1
Rob, 귀하의 "new SomeObject ()"예제는 간단하고 거기에서 예외가 생성되지 않아야합니다. 그러나 호출이 예외를 생성 할 수 있다면 처리 할 수 ​​있도록 try-block 내부에서 발생하도록하는 것이 좋습니다.
Sarel Botha

12

또한 아래 예제에서 SomeObject 생성 내부에서 예외가 발생했을 수 있습니다.이 경우 'so'변수는 null이되고 CleanUp에 대한 호출은 NullPointerException을 발생시킵니다.

SomeObject so;
try {
  // Do some work here ...
  so = new SomeObject();
  so.DoUsefulThings();
} finally {
  so.CleanUp(); // Compiler error here
}

내가하는 경향은 다음과 같습니다.

SomeObject so = null;
try {
  // Do some work here ...
  so = new SomeObject();
  so.DoUsefulThings();
} finally {
  if (so != null) {
     so.CleanUp(); // safe
  }
}

12
차라리 무엇을 하시겠습니까?
Electric Monk

2
그래, 못 생겼다. 네, 나도 그렇게합니다.
SMBiggs 2013 년

@ElectricMonk 어떤 형태가 더 낫다고 생각하는지, 보여준 것 또는 여기 getContents (..) 메소드에 표시된 것 : javapractices.com/topic/TopicAction.do?Id=126
Atom

11

최종 인스턴스 / 멤버 변수는 기본적으로 초기화되지 않습니다. 그것들은 최종이며 나중에 프로그램에서 변경할 수 없기 때문입니다. 이것이 Java가 기본값을 제공하지 않고 프로그래머가 초기화하도록하는 이유입니다.

반면에 최종 멤버가 아닌 변수는 나중에 변경할 수 있습니다. 따라서 컴파일러는 나중에 변경할 수 있기 때문에 정확하게 초기화되지 않은 상태로 두지 않습니다. 지역 변수와 관련하여 지역 변수의 범위는 훨씬 좁습니다. 컴파일러는 언제 사용되는지 알고 있습니다. 따라서 프로그래머가 변수를 초기화하도록하는 것은 의미가 있습니다.


9

귀하의 질문에 대한 실제 대답은 단순히 스택 포인터에 숫자를 추가하여 메서드 변수가 인스턴스화되기 때문입니다. 제로화는 추가 단계입니다. 클래스 변수의 경우 힙의 초기화 된 메모리에 저장됩니다.

추가 조치를 취하지 않으시겠습니까? 한 걸음 물러서십시오. 아무도이 경우 "경고"가 매우 좋은 일이라고 언급하지 않았습니다.

첫 번째 패스 (처음 코딩 할 때)에서 변수를 0 또는 null로 초기화해서는 안됩니다. 실제 값에 할당하거나 전혀 할당하지 마십시오. 그렇지 않으면 java가 당신이 정말로 실수 할 때 알려줄 수 있기 때문입니다. Electric Monk의 답변을 좋은 예로 들어 보겠습니다. 첫 번째 경우에는 SomeObject의 생성자가 예외를 던 졌기 때문에 try ()가 실패하면 결국에는 NPE로 끝날 것이라고 말하는 것이 실제로 놀랍게도 유용합니다. 생성자가 예외를 throw 할 수없는 경우 try에 있지 않아야합니다.

이 경고는 모든 경로를 확인하고 어떤 경로에서 변수를 사용한 경우 해당 경로로 이어지는 모든 경로에서 초기화해야했기 때문에 어리석은 일을하지 않도록 도와주는 멋진 다중 경로 나쁜 프로그래머 검사기입니다. . 이제 올바른 작업인지 결정할 때까지 변수를 명시 적으로 초기화하지 않습니다.

게다가 "int size"보다는 "int size = 0"이라고 명시 적으로 말하고 다음 프로그래머가 0이 될 의도가 있다는 것을 알아 내도록하는 것이 낫지 않습니까?

반대로 컴파일러가 모든 초기화되지 않은 변수를 0으로 초기화하도록하는 하나의 유효한 이유를 찾을 수 없습니다.


1
예, 코드가 흐르는 방식 때문에 null로 초기화해야하는 다른 경우도 있습니다.이 내용을 반영하기 위해 답변을 업데이트 한 적이 없다고 말해서는 안됩니다.
Bill K

4

주된 목적은 C / C ++와의 유사성을 유지하는 것이라고 생각합니다. 그러나 컴파일러는 문제를 최소한으로 줄일 수있는 초기화되지 않은 변수 사용에 대해 감지하고 경고합니다. 성능 관점에서 보면 컴파일러가 할당 문을 작성할 필요가 없기 때문에 다음 문에서 변수 값을 덮어 쓰더라도 초기화되지 않은 변수를 선언하는 것이 조금 더 빠릅니다.


1
틀림없이 컴파일러는 변수를 사용하기 전에 항상 변수에 할당할지 여부를 결정하고 이러한 경우 자동 기본값 할당을 억제 할 수 있습니다. 컴파일러가 액세스 전에 할당이 발생하는지 여부를 확인할 수없는 경우 기본 할당이 생성됩니다.
Greg Hewgill

2
예,하지만 프로그래머가 실수로 초기화되지 않은 변수를 남겨 두 었는지 여부를 프로그래머에게 알려 준다고 주장 할 수 있습니다.
Mehrdad Afshari

1
컴파일러는 두 경우 모두 그렇게 할 수 있습니다. :) 개인적으로 컴파일러가 초기화되지 않은 변수를 오류로 처리하는 것을 선호합니다. 어딘가에서 실수를했을 수도 있다는 뜻입니다.
Greg Hewgill

나는 자바 사람은 아니지만 그것을 처리하는 C # 방식을 좋아합니다. 차이점은이 경우 컴파일러가 경고를 발행해야했기 때문에 올바른 프로그램에 대해 수백 개의 경고를받을 수 있습니다.)
Mehrdad Afshari

멤버 변수에 대해서도 경고합니까?
Adeel Ansari

4

(질문 이후 너무 오래 새 답변을 게시하는 것이 이상하게 보일 수 있지만 중복 이 나왔습니다.)

저에게 이유 는 이것 때문 입니다. 지역 변수의 목적은 인스턴스 변수의 목적과 다릅니다. 지역 변수는 계산의 일부로 사용됩니다. 인스턴스 변수는 상태를 포함합니다. 값을 할당하지 않고 지역 변수를 사용하는 경우 거의 확실히 논리 오류입니다.

즉, 인스턴스 변수가 항상 명시 적으로 초기화되도록 요구하는 데 완전히 뒤처 질 수 있습니다. 결과가 초기화되지 않은 인스턴스 변수를 허용하는 모든 생성자에서 오류가 발생합니다 (예 : 선언시 초기화되지 않고 생성자에서 초기화되지 않음). 그러나 그것은 Gosling 등의 결정이 아닙니다. al., 90 년대 초반에 들어 왔으니 여기 있습니다. (그리고 나는 그들이 잘못된 전화를 걸었다 고 말하는 것이 아닙니다.)

그래도 지역 변수를 기본값으로 설정할 수는 없습니다 . 예, 우리는 로직을 재확인하기 위해 컴파일러에 의존해서는 안되며, 하나는 그렇지 않습니다.하지만 컴파일러가 하나를 잡을 때 여전히 편리합니다. :-)


"즉, 인스턴스 변수가 항상 명시 적으로 초기화되도록 요구하는 것을 완전히 뒤처 질 수있었습니다 ..." FWIW가 TypeScript에서 취한 방향입니다.
TJ Crowder

3

변수를 초기화하지 않는 것이 더 효율적이며, 지역 변수의 경우 컴파일러가 초기화를 추적 할 수 있으므로 그렇게하는 것이 안전합니다.

초기화 할 변수가 필요한 경우 언제든지 직접 할 수 있으므로 문제가되지 않습니다.


3

지역 변수 뒤에있는 아이디어는 그들이 필요한 제한된 범위 내에서만 존재한다는 것입니다. 따라서 그 가치 또는 적어도 그 가치가 어디에서 오는지에 대한 불확실성에 대한 이유가 거의 없어야합니다. 지역 변수에 대한 기본값을 가지면서 발생하는 많은 오류를 상상할 수 있습니다.

예를 들어, 다음과 같은 간단한 코드를 고려하십시오 ... ( NB는 명시 적으로 초기화되지 않은 경우 지정된대로 로컬 변수에 기본값이 할당되는 데모 목적으로 가정하겠습니다. )

System.out.println("Enter grade");
int grade = new Scanner(System.in).nextInt(); //I won't bother with exception handling here, to cut down on lines.
char letterGrade; //let us assume the default value for a char is '\0'
if (grade >= 90)
    letterGrade = 'A';
else if (grade >= 80)
    letterGrade = 'B';
else if (grade >= 70)
    letterGrade = 'C';
else if (grade >= 60)
    letterGrade = 'D';
else
    letterGrade = 'F';
System.out.println("Your grade is " + letterGrade);

모든 것을 말하고 완료 하면 컴파일러가 letterGrade에 기본값 '\ 0'을 할당했다고 가정하면 이 코드는 작성된대로 제대로 작동합니다. 그러나 else 문을 잊어 버린 경우에는 어떻게합니까?

코드를 테스트하면 다음과 같은 결과가 나올 수 있습니다.

Enter grade
43
Your grade is

이 결과는 예상 할 수 있지만 분명히 코더의 의도는 아닙니다. 실제로 대부분의 경우 (또는 적어도 상당수의 경우) 기본값이 원하는 값이 아니므로 대부분의 경우 기본값으로 인해 오류가 발생합니다. = 1in 을 잊음으로 인한 디버깅 슬픔이 in for(int i = 1; i < 10; i++)을 포함하지 않아도되는 편리함보다 중요하기 때문에 사용하기 전에 코더가 로컬 변수에 초기 값을 할당하도록하는 것이 더 합리적 = 0입니다 for(int i; i < 10; i++).

예를 들어 객체가 생성자에서 확인 된 예외를 던질 때 try-catch-finally 블록이 약간 지저분해질 수 있다는 것은 사실입니다 (하지만 실제로는 catch-22가 아닙니다). 이유 또는 다른, 최종적으로 블록의 끝에서이 객체에 대해 뭔가 수행 해야합니다 . 이에 대한 완벽한 예는 폐쇄되어야하는 자원을 다룰 때입니다.

과거에 이것을 처리하는 한 가지 방법은 다음과 같을 수 있습니다.

Scanner s = null; //declared and initialized to null outside the block. This gives us the needed scope, and an initial value.
try {
    s = new Scanner(new FileInputStream(new File("filename.txt")));
    int someInt = s.nextInt();
} catch (InputMismatchException e) {
    System.out.println("Some error message");
} catch (IOException e) {
    System.out.println("different error message"); 
} finally {
    if (s != null) //in case exception during initialization prevents assignment of new non-null value to s.
        s.close();
}

그러나 Java 7에서이 finally 블록은 더 이상 try-with-resources를 사용하여 필요하지 않습니다.

try (Scanner s = new Scanner(new FileInputStream(new File("filename.txt")))) {
...
...
} catch(IOException e) {
    System.out.println("different error message");
}

즉, (이름에서 알 수 있듯이) 이것은 리소스에서만 작동합니다.

그리고 앞의 예제는 약간 엉뚱하지만, 이것은 아마도 지역 변수와 그것들이 구현되는 방법에 대해 말하는 것보다 try-catch-finally 또는 이러한 클래스가 구현되는 방식에 더 많은 것을 말합니다.

필드가 기본값으로 초기화되는 것은 사실이지만 약간 다릅니다. 예를 들어라고 말하면 int[] arr = new int[10];이 배열을 초기화하자마자 객체가 지정된 위치의 메모리에 존재합니다. 잠시 동안 기본값이 없다고 가정 해 봅시다. 대신 초기 값은 그 순간에 해당 메모리 위치에있는 일련의 1과 0입니다. 이로 인해 많은 경우 비 결정적 동작이 발생할 수 있습니다.

우리가 ...

int[] arr = new int[10];
if(arr[0] == 0)
    System.out.println("Same.");
else
    System.out.println("Not same.");

이 완벽하게 가능한 것 Same.하나 실행에 표시 될 수 있습니다 및 Not same.다른 표시 될 수 있습니다. 참조 변수에 대해 이야기하기 시작하면 문제가 더욱 심각해질 수 있습니다.

String[] s = new String[5];

정의에 따르면 s의 각 요소는 String (또는 null)을 가리켜 야합니다. 그러나 초기 값이이 메모리 위치에서 발생하는 일련의 0과 1이면 매번 동일한 결과를 얻을 수있을뿐만 아니라 객체 s [0]이 가리키고 있다는 보장도 없습니다. 에도 (그것이 어떤 의미를 가리키는 가정) 이다 문자열은 (아마도 그것은 토끼,이다 : P )! 유형에 대한 이러한 관심 부족은 Java Java를 만드는 거의 모든 것에 직면하게 될 것입니다. 따라서 지역 변수에 대한 기본값을 갖는 것은 기껏해야 선택 사항으로 보일 수 있지만 인스턴스 변수에 대한 기본값을 갖는 것은 필요성에 더 가깝습니다 .


1

내가 틀리지 않았다면 또 다른 이유는

멤버 변수의 기본값을 제공하는 것은 클래스 로딩의 일부입니다.

클래스 로딩은 자바에서 실행 시간입니다. 객체를 만들 때 클래스가 클래스 로딩으로로드됩니다. 멤버 변수 만 기본값으로 초기화됩니다. JVM은 일부 메소드가 절대로 발생하지 않기 때문에 로컬 변수에 기본값을 제공하는 데 시간이 걸리지 않습니다. 메서드 호출은 조건부 일 수 있기 때문에 호출되므로 기본값을 사용하지 않을 경우 기본값을 제공하고 성능을 저하시키는 데 시간이 걸립니다.


0

Eclipse는 초기화되지 않은 변수에 대한 경고도 제공하므로 어쨌든 상당히 분명해집니다. 개인적으로 이것이 기본 동작이라는 것이 좋은 일이라고 생각합니다. 그렇지 않으면 응용 프로그램이 예상치 못한 값을 사용할 수 있으며 컴파일러가 오류를 던지는 대신 아무 작업도하지 않고 (아마도 경고를 줄 수 있음) 스크래치가 발생할 것입니다. 특정 사물이 제대로 작동하지 않는 이유에 대해 알아보세요.


0

로컬 변수는 스택에 저장되지만 인스턴스 변수는 힙에 저장되므로 힙에서 발생하는 기본값 대신 스택의 이전 값을 읽을 가능성이 있습니다. 이러한 이유로 jvm은 초기화하지 않고 지역 변수를 사용하는 것을 허용하지 않습니다.


2
완전히 잘못됨 ... 모든 자바 비 프리미티브는 생성시기와 방법에 관계없이 힙에 저장됩니다.
gshauger

Java 7 이전에는 인스턴스 변수가 힙에 저장되고 로컬 변수는 스택에 있습니다. 그러나 지역 변수가 참조하는 모든 개체는 힙에서 찾을 수 있습니다. Java 7부터 "Java Hotspot Server Compiler"는 "이스케이프 분석"을 수행하고 힙 대신 스택에 일부 개체를 할당하기로 결정할 수 있습니다.
mamills 2013

0

인스턴스 변수에는 기본값이 있지만 로컬 변수에는 기본값이있을 수 없습니다. 지역 변수는 기본적으로 메서드 / 동작에 있으므로 주요 목표는 일부 작업 또는 계산을 수행하는 것입니다. 따라서 지역 변수에 대한 기본값을 설정하는 것은 좋지 않습니다. 그렇지 않으면 예기치 않은 답변의 원인을 확인하는 것이 매우 어렵고 시간이 많이 걸립니다.


-1

대답은 인스턴스 변수가 클래스 생성자 또는 모든 클래스 메서드에서 초기화 될 수 있다는 것입니다. 그러나 지역 변수의 경우 클래스에 영원히 남아있는 메서드에서 무엇이든 정의하면 일단 정의됩니다.


-2

다음 두 가지 이유를 생각할 수 있습니다.

  1. 대부분의 답변은 지역 변수를 초기화하는 제약 조건을 두어 말했듯이 지역 변수에 프로그래머가 원하는 값이 할당되고 예상 결과가 계산되도록 보장합니다.
  2. 인스턴스 변수는 지역 변수 (동일한 이름)를 선언하여 숨길 수 있습니다. 예상되는 동작을 보장하기 위해 지역 변수는 값을 초기화해야합니다. (그래도 이것을 완전히 피할 것입니다)

필드는 재정의 할 수 없습니다. 기껏해야 숨길 수 있으며 숨김이 초기화 확인을 방해하는 방법을 알지 못합니다.
meriton dec

Right hidden. 인스턴스와 같은 이름으로 지역 변수를 생성하기로 결정하면이 제약으로 인해 지역 변수가 의도적으로 선택된 값 (인스턴스 변수 값 제외)으로 초기화됩니다
Mitra
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.