Development Tip

초기화되지 않은 변수를 인쇄하려고 시도해도 항상 오류 메시지가 표시되지 않는 이유

yourdevel 2021. 1. 8. 22:29
반응형

초기화되지 않은 변수를 인쇄하려고 시도해도 항상 오류 메시지가 표시되지 않는 이유


일부는 SO 질문과 유사하다고 생각할 수 있습니다 Java Final 변수에는 기본값이 있습니까? 그러나 그 질문은 인스턴스 이니셜 라이저 블록 내에서 x 값을 직접 인쇄하지 않기 때문에이 문제를 완전히 해결하지는 못합니다.

이 문제는 인스턴스 이니셜 라이저 블록 내부에서 x를 직접 인쇄하려고 할 때 발생하며 블록 끝 전에 x에 값을 할당했습니다.

사례 1

class HelloWorld {

    final int x;

    {
        System.out.println(x);
        x = 7;
        System.out.println(x);    
    }

    HelloWorld() {
        System.out.println("hi");
    }

    public static void main(String[] args) {
        HelloWorld t = new HelloWorld();
    }
}

이는 변수 x가 초기화되지 않았을 수 있다는 컴파일 시간 오류를 제공합니다.

$ javac HelloWorld.java
HelloWorld.java:6: error: variable x might not have been initialized
        System.out.println(x);
                           ^
1 error

사례 2

직접 인쇄하는 대신 인쇄 할 함수를 호출합니다.

class HelloWorld {

    final int x;

    {
        printX();
        x = 7;
        printX();
    }

    HelloWorld() {
        System.out.println("hi");
    }

    void printX() {
        System.out.println(x);
    }

    public static void main(String[] args) {
        HelloWorld t = new HelloWorld();
    }
}

이것은 올바르게 컴파일되고 출력을 제공합니다.

0
7
hi

두 경우의 개념적 차이점은 무엇입니까?


JLS에서 §8.3.3. Field Initialization 동안 Forward References 는 다음과 같은 경우 컴파일 타임 오류가 있다고 명시했습니다.

사용 후 선언이 텍스트로 나타나는 인스턴스 변수의 사용은 이러한 인스턴스 변수가 범위 내에 있더라도 때때로 제한됩니다. 특히 다음 사항이 모두 참이면 컴파일 타임 오류입니다.

  • 클래스 또는 인터페이스 C의 인스턴스 변수 선언은 인스턴스 변수 사용 후 텍스트로 나타납니다.

  • 사용은 C의 인스턴스 변수 이니셜 라이저 또는 C의 인스턴스 이니셜 라이저에서 간단한 이름입니다.

  • 사용은 과제의 왼쪽에 있지 않습니다.

  • C는 사용을 포함하는 가장 안쪽의 클래스 또는 인터페이스입니다.

다음 규칙은 몇 가지 예와 함께 제공되며 그중 가장 가까운 규칙은 다음과 같습니다.

class Z {
    static int peek() { return j; }
    static int i = peek();
    static int j = 1;
}
class Test {
    public static void main(String[] args) {
        System.out.println(Z.i);
    }
}

Accesses [to static or instance variables] by methods are not checked in this way, so the code above produces output 0, because the variable initializer for i uses the class method peek() to access the value of the variable j before j has been initialized by its variable initializer, at which point it still has its default value (§4.12.5 Initial Values of Variables).

So, to summarize, your second example compiles and executes fine, because the compiler does not check if the x variable was already initialized when you invoke printX() and when printX() actually takes place at Runtime, the x variable will be assigned with its default value (0).


Reading the JLS, the answer appears to be in section 16.2.2:

A blank final member field V is definitely assigned (and moreover is not definitely unassigned) before the block (§14.2) that is the body of any method in the scope of V and before the declaration of any class declared within the scope of V.

This means that when a method is called, the final field is assigned to its default value 0 before invoking it, so when you reference it inside the method, it compiles successfully and prints the value 0.

However, when you access the field outside of a method, it is considered unassigned, hence the compilation error. The following code will also not compile:

public class Main {
    final int x;
    {
        method();
        System.out.println(x);
        x = 7;
    }
    void method() { }
    public static void main(String[] args) { }
}

because:

  • V is [un]assigned before any other statement S of the block iff V is [un]assigned after the statement immediately preceding S in the block.

Since the final field x is unassigned before the method invocation, it is still unassigned after it.

This note in the JLS is also relevant:

Note that there are no rules that would allow us to conclude that V is definitely unassigned before the block that is the body of any constructor, method, instance initializer, or static initializer declared in C. We can informally conclude that V is not definitely unassigned before the block that is the body of any constructor, method, instance initializer, or static initializer declared in C, but there is no need for such a rule to be stated explicitly.


The difference is that in the first case you are calling System.out.println from initializer block so the block which is invoked before constructor. In the first line

System.out.println(x);

variable x is not yet initialized so that you get compilation error.

But in the second case you call instance method which doesn't know if variable has already been initialized so you don't have compilation error and you can see the default value for x


Ok, here is my 2 cents.

We all know that final variables can be initialized only While declaring or later on in constructors. Keeping that fact in mind, let see what happened here so far.

No errors Case:

So when you use inside a method, it have already a value.

 1) If you initialize it, that value.
 2) If not, the default value of data type. 

Error case :

When you do that in an initialization block, which you are seeing errors.

If you look at the docs of initialization block

{
    // whatever code is needed for initialization goes here
}

and

The Java compiler copies initializer blocks into every constructor. Therefore, this approach can be used to share a block of code between multiple constructors.

In compiler's eye, your code is literally equals to

class HelloWorld {

    final int x;
    HelloWorld() {
        System.out.println(x);  ------------ ERROR here obviously
        x = 7;
        System.out.println(x);  
        System.out.println("hi");
    }

    public static void main(String[] args) {
        HelloWorld t = new HelloWorld();
    }
}

You are using it before even initializing it.


Case 1 :

Gives you a compile-error,

Because at System.out.println(x);

you are trying to print x which was never initialized.

Case 2:

Works because you are not directly using any literal values, instead you are calling some method, which is correct.

General Rule is,

If you are trying to access any variable which is never initialized then it will give a compilation error.


We deal here with initializer block. The Java compiler copies initializer blocks into every constructor.

The compiler error don't occure in second example, because printing x is in another Frame, please refer to spec.

ReferenceURL : https://stackoverflow.com/questions/33995384/why-attempt-to-print-uninitialized-variable-does-not-always-result-in-an-error-m

반응형