[java] “catch”또는 “finally”범위에서 “try”로 변수가 선언되지 않은 이유는 무엇입니까?

C # 및 Java (및 다른 언어도 가능)에서 “try”블록에 선언 된 변수는 해당 “catch”또는 “finally”블록의 범위에 속하지 않습니다. 예를 들어 다음 코드는 컴파일되지 않습니다.

try {
  String s = "test";
  // (more code...)
}
catch {
  Console.Out.WriteLine(s);  //Java fans: think "System.out.println" here instead
}

이 코드에서는 catch 블록의 s에 대한 참조에서 컴파일 타임 오류가 발생합니다. s는 try 블록의 범위에만 있기 때문입니다. Java에서 컴파일 오류는 “s 확인할 수 없습니다”이며 C #에서는 “현재 컨텍스트에 ‘s’이름이 없습니다”입니다.

이 문제에 대한 일반적인 해결책은 try 블록 대신 try 블록 바로 앞에 변수를 선언하는 것 같습니다.

String s;
try {
  s = "test";
  // (more code...)
}
catch {
  Console.Out.WriteLine(s);  //Java fans: think "System.out.println" here instead
}

그러나 적어도 나에게, (1) 이것은 어리석은 해결책처럼 느껴지고 (2) 변수가 프로그래머가 의도 한 것보다 더 넓은 범위를 갖습니다 (메소드의 맥락 에서만가 아니라 메소드의 전체 나머지). 마지막으로 시도하십시오.

내 질문은이 언어 디자인 결정의 근거가 무엇입니까 (Java, C # 및 / 또는 기타 적용 가능한 언어)?



답변

두가지:

  1. 일반적으로 Java는 전역 및 기능의 두 가지 범위 만 있습니다. 그러나 try / catch는 예외입니다. 예외가 발생하고 예외 개체에 변수가 할당되면 해당 개체 변수는 “캐치”섹션 내에서만 사용할 수 있으며 캐치가 완료 되 자마자 소멸됩니다.

  2. (그리고 더 중요하게). try 블록에서 예외가 발생한 위치를 알 수 없습니다. 변수가 선언되기 전에있을 수 있습니다. 따라서 catch / finally 절에 사용할 수있는 변수를 말하는 것은 불가능합니다. 제안한대로 범위를 지정하는 다음 경우를 고려하십시오.

    
    try
    {
        throw new ArgumentException("some operation that throws an exception");
        string s = "blah";
    }
    catch (e as ArgumentException)
    {
        Console.Out.WriteLine(s);
    }

이것은 분명히 문제입니다. 예외 처리기에 도달하면 s가 선언되지 않습니다. 어획량은 예외적 인 상황을 처리하고 최종적으로 실행 되어야 함을 감안할 때 안전하고 컴파일 타임에 문제를 선언하는 것이 런타임보다 훨씬 낫습니다.


답변

캐치 블록의 선언 부분에 도달했다는 것을 어떻게 확신 할 수 있습니까? 인스턴스화에서 예외가 발생하면 어떻게됩니까?


답변

전통적으로 C 스타일 언어에서 중괄호 안에서 발생하는 일은 중괄호 안에 유지됩니다. 그런 범위에서 변수의 수명을 연장하는 것은 대부분의 프로그래머에게는 직관적이지 않을 것이라고 생각합니다. try / catch / finally 블록을 다른 버팀대 안에 넣어서 원하는 것을 얻을 수 있습니다. 예 :

... code ...
{
    string s = "test";
    try
    {
        // more code
    }
    catch(...)
    {
        Console.Out.WriteLine(s);
    }
}

편집 : 나는 모든 규칙 에 예외 있다고 생각합니다 . 유효한 C ++는 다음과 같습니다.

int f() { return 0; }

void main()
{
    int y = 0;

    if (int x = f())
    {
        cout << x;
    }
    else
    {
        cout << x;
    }
}

x의 범위는 조건부, then 절 및 else 절입니다.


답변

다른 모든 사람들은 기본 사항을 가져 왔습니다. 블록에서 발생하는 일이 블록에 남아 있습니다. 그러나 .NET의 경우 컴파일러의 생각을 조사하는 것이 도움이 될 수 있습니다. 예를 들어 다음 try / catch 코드를 사용하십시오 (StreamReader가 블록 외부에 올바르게 선언되어 있음에 유의하십시오).

static void TryCatchFinally()
{
    StreamReader sr = null;
    try
    {
        sr = new StreamReader(path);
        Console.WriteLine(sr.ReadToEnd());
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
    finally
    {
        if (sr != null)
        {
            sr.Close();
        }
    }
}

이것은 MSIL에서 다음과 유사한 것으로 컴파일됩니다.

.method private hidebysig static void  TryCatchFinallyDispose() cil managed
{
  // Code size       53 (0x35)    
  .maxstack  2
  .locals init ([0] class [mscorlib]System.IO.StreamReader sr,
           [1] class [mscorlib]System.Exception ex)
  IL_0000:  ldnull
  IL_0001:  stloc.0
  .try
  {
    .try
    {
      IL_0002:  ldsfld     string UsingTest.Class1::path
      IL_0007:  newobj     instance void [mscorlib]System.IO.StreamReader::.ctor(string)
      IL_000c:  stloc.0
      IL_000d:  ldloc.0
      IL_000e:  callvirt   instance string [mscorlib]System.IO.TextReader::ReadToEnd()
      IL_0013:  call       void [mscorlib]System.Console::WriteLine(string)
      IL_0018:  leave.s    IL_0028
    }  // end .try
    catch [mscorlib]System.Exception
    {
      IL_001a:  stloc.1
      IL_001b:  ldloc.1
      IL_001c:  callvirt   instance string [mscorlib]System.Exception::ToString()
      IL_0021:  call       void [mscorlib]System.Console::WriteLine(string)
      IL_0026:  leave.s    IL_0028
    }  // end handler    
    IL_0028:  leave.s    IL_0034
  }  // end .try    
  finally
  {
    IL_002a:  ldloc.0
    IL_002b:  brfalse.s  IL_0033
    IL_002d:  ldloc.0
    IL_002e:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
    IL_0033:  endfinally
  }  // end handler    
  IL_0034:  ret
} // end of method Class1::TryCatchFinallyDispose

우리는 무엇을 볼 수 있습니까? MSIL은 블록을 존중합니다. C #을 컴파일 할 때 생성 된 기본 코드의 일부입니다. 범위는 C # 사양에 딱 맞게 설정되어있는 것이 아니라 CLR 및 CLS 사양에도 적용됩니다.

범위는 당신을 보호하지만 때로는 해결해야합니다. 시간이 지남에 따라 익숙해 져 자연스럽게 느껴지기 시작합니다. 다른 사람들이 말했듯이 블록에서 발생하는 일이 해당 블록에 유지됩니다. 무언가를 공유하고 싶습니까? 당신은 블록 밖으로 나가야합니다 …


답변

C ++에서 자동 변수의 범위는이를 둘러싸는 중괄호로 제한됩니다. 왜 중괄호 밖에서 try 키워드를 내려서 이것이 다른 것이기를 기대할까요?


답변

ravenspoint가 지적한 것처럼, 모든 사람들은 변수가 정의 된 블록에 국한 될 것으로 기대합니다. try블록을 소개합니다 .catch .

try및 에 모두 지역 변수가 필요한 경우 catch두 블록을 모두 블록으로 묶어보십시오.

// here is some code
{
    string s;
    try
    {

        throw new Exception(":(")
    }
    catch (Exception e)
    {
        Debug.WriteLine(s);
    }
}


답변

간단한 대답은 C와 구문을 상속받은 대부분의 언어는 블록 범위입니다. 즉, 변수가 하나의 블록, 즉 {} 내부에 정의되어 있으면 해당 범위가됩니다.

그런데 예외는 비슷한 구문을 가지고 있지만 기능 범위가있는 JavaScript입니다. JavaScript에서 try 블록에 선언 된 변수는 catch 블록과 그 밖의 다른 곳에서 포함 된 함수의 범위에 있습니다.