开发者

Splitting a constructor method into parts - the problem with final values

开发者 https://www.devze.com 2023-02-18 21:08 出处:网络
I wanted to split the constructor of my class into parts. But I have a problem... Is it possible to initialize a final value in a method called inside constructor? It has to be initialized directly i

I wanted to split the constructor of my class into parts. But I have a problem...

Is it possible to initialize a final value in a method called inside constructor? It has to be initialized directly in constructor?

This...

import java.util.Scanner;

public final class A
{
    private final int L;
    private final int D;
    privat开发者_JS百科e final int N;

    public A()
    {
        Scanner scanner = new Scanner(System.in);
        this.getFirstLine(scanner);

        /* the rest of the constructor method */
    }

    private void getFirstLine(Scanner scanner)
    {
        this.L = scanner.nextInt();
        this.D = scanner.nextInt();
        this.N = scanner.nextInt();
    }
}

gives me errors similar to The final field A.L cannot be assigned.

So it is treated as an assignment? Yeah?

Is there a way of splitting constructor in order to achieve what I wanted?

Thanks in advance.


You cannot do this according to the Java Language Spec:

A blank final instance variable must be definitely assigned (§16.9) at the end of every constructor (§8.8) of the class in which it is declared; otherwise a compile-time error occurs.

You can do it in the constructor because the compiler can guarantee that the constructor will only be called once per instance. The code below compiles. I just verified it:

import java.util.Scanner;

public final class A
{
    private final int L;
    private final int D;
    private final int N;

    public A()
    {
        Scanner scanner = new Scanner(System.in);
        this.L = scanner.nextInt();
        this.D = scanner.nextInt();
        this.N = scanner.nextInt();

        /* the rest of the constructor method */
    }
}

You cannot assign the final variables in your non-constructor method because it cannot be guaranteed to be called only once per instance.


How about this.

import java.util.Scanner;

public final class A {

    private final int l;
    private final int d;
    private final int n;

    public A() {
        Scanner scanner = new Scanner(System.in);
        int[] list = this.getFirstLine(scanner);
        l = list[0];
        d = list[1];
        n = list[2];

        /* the rest of the constructor method */
    }

    private int[] getFirstLine(Scanner scanner) {
        return new int[]{scanner.nextInt(), scanner.nextInt(), scanner.nextInt()};
    }
}


final members can only be set during initialisation of the instance. So you can initialize final fields when you declare it, in an initialization block or in a constructor. Why don't you make getFirstLine a private constructor:

private A(Scanner scanner)
{
    this.L = scanner.nextInt();
    this.D = scanner.nextInt();
    this.N = scanner.nextInt();
}


Is it possible to initialize a final value in a method called inside constructor?

No it is not possible.

It has to be initialized directly in constructor?

It has to be initialized by the end of each and every constructor. The initialization can be done in the declaration or an instance initializer ... that gets executed as part of all constructors.

So it is treated as an assignment? Yeah?

Yeah!

The problem is that the method could be called at any point:

  • it could be called twice by a constructor,
  • it could be called after the object has been constructed, or
  • it could throw an exception before / after assignment, leaving it unclear whether the assignment occurred. (And the constructor could catch the exception ...)

Rather than requiring the compiler to do a complicated analysis to ensure the assignment happens once and only once during construction (and not after construction), the JLS simply forbids the assignment in that context.

Is there a way of splitting constructor in order to achieve what I wanted?

You can calculate the values in a separate method, but the actual initialization of the final must be performed in the constructors, the declaration initializer or an initializer block.

(If there were subclasses in the frame, there are other tricks that could possibly be used.)


OK, that's what I've done after being inspired by answers:

import java.util.Scanner;

public final class A
{
    private static final Scanner scanner = new Scanner(System.in);

    private static class FirstLine
    {
        public static int[] get()
        {
            return new int[]
            {
                A.scanner.nextInt(), 
                A.scanner.nextInt(), 
                A.scanner.nextInt()
            };
        }
    }

    private final int L;
    private final int D;
    private final int N;

    public A()
    {
        int[] valuesScanned = FirstLine.get();
        this.L = valuesScanned[0];
        this.D = valuesScanned[1];
        this.N = valuesScanned[2];
    }

    /*JUST TO TEST*/
    public static void main(String[] args)
    {
        A test = new A();
        System.out.println(test.L);
        System.out.println(test.D);
        System.out.println(test.N);
    }
}

I've made private static class that is responsible for reading input. Now it should be more logically divided into separate parts.

0

精彩评论

暂无评论...
验证码 换一张
取 消