1

I have this scenario:

class ClassB{
    ClassB(){
        ClassA a = new ClassA(() -> a.foo());
    }
}

class ClassA{
    ClassA(Runnable onChange) {

    }

    public void foo(){
        System.out.println("Hello");
    }
}

And I get "Variable 'a' might not have been initialized". I understand why this is happening. Is there a work around or do I have to restructure my classes?

5
  • 2
    What are you actually trying to achieve? Right now your code does nothing: a is unused, and onChange is unused. Commented Dec 18, 2020 at 10:34
  • 1
    I have different JComponents in ClassA and they call onChange.run() in their actionListeners. Does that make more sense? Commented Dec 18, 2020 at 10:41
  • This looks like some kind of a design flaw. Constructors should be used to instantiate and initialise objects, not to execute some actions passed as an argument. Also why not simply call the foo() method inside of the ClassA constructor implementation? Commented Dec 18, 2020 at 10:46
  • 1
    Can you accept the runnable somewhere else, not in the constructor? For example in a setOnChange method. Anyway, you need to come up with a minimal reproducible example. Right now your code does nothing, so one way to fix it, while maintaining the behaviour (doing nothing) is to just remove the line with the error. Hopefully you see why this question is too broad. Commented Dec 18, 2020 at 10:47
  • Making foo() static would work, but presumably you don't want that. Commented Dec 18, 2020 at 10:47

2 Answers 2

1

Without changing any of your types, this should work:

class ClassB {
    ClassB() {
        AtomicReference<A> ref = new AtomicReference<>(); // holder for instance
        ClassA a = new ClassA(() -> ref.get().foo());
        ref.set(a);
    }
}

But you cannot invoke your lambda (Runnable#run) in your constructor, because a still has the value null. Only after the constructor has completed, the value is assigned.

Another possibility could be using a Consumer instead of a Runnable:

class ClassB {
    ClassB() {
        ClassA a = new ClassA(that -> that.foo()); // or maybe even: A::foo
    }
}

class ClassA {
    ClassA(Consumer<A> onChange) {

    }

    public void foo() {
        System.out.println("Hello");
    }
}

// call outside of `A`:
consumer.accept(a);
// or, inside of `A`:
consumer.accept(this);

Without seeing the rest of the code, it is difficult to give a good solution.

Sign up to request clarification or add additional context in comments.

3 Comments

What you suggested, will not compile, because the a variable is not intialised and that means it cannot be passed to the lambda.
The first ClassB example does not work. You will still get 'a' might not have been initialized. I also tried the same code but with ClassA a = null; but that will instead give you "Varible used in lambda must be final or effectively final".
You are right. I've updated the answer to use an (initialized) AtomicReference to hold the value. The same limitations wrt. to call order still apply (you cannot call the lambda before the constructor has returned).
0

I think you should consider refactoring your code and use a different approach. It's hard to tell without the full code but I suspect the design is not optimal.

With that said, here is something you could do which is similar to your approach.

Make ClassA Runnable and abstract:

abstract class ClassA implements Runnable{

    private final Runnable onChange;

    protected ClassA() {
        this.onChange = this;
    }

    public void foo(){
        System.out.println("Hello");
    }
}

In ClassB you can implement an anonymous ClassA:

class ClassB {
    ClassB() {
        ClassA a = new ClassA() {
            @Override
            public void run() {
                foo();
            }
        };
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.