2

I try to use a class member as a resource in the try block (Because that resource needs to call close() after try block):

class Manager {
    MyResource m_myResource;
    ...

    void doTraining() {
        m_myResource = createMyResource();
        try(m_myResource) {
            ...
        }
    }
    
}

But there will be a complain saying that m_myResource is not effective final. Why? I think m_myResourceis not changed after initialization. Anyway, not sure how to fix this issue. One way I can think of is:

class Manager {
    MyResource m_myResource;
    ...

    void doTraining() {
        MyResource newResource = createMyResource();
        m_myResource = newResource;
        try(m_myResource) {
            ...
        }
    }
}

But I am not sure if assigning the local resource to class member could cause any problem?

lightrek
  • 951
  • 3
  • 14
  • 30
  • 4
    Why do you want to store the resource in field when the validity is limited to the `try` block? – Holger Sep 07 '22 at 12:46
  • Because this try block runs a long time. I have another method in the same class may try to access the resource to do cancellation operation why the try block is running. – lightrek Sep 07 '22 at 12:49
  • When you assign and declare the variable on separate lines (or really, any assignment outside of the initial declaration), your variable is no longer "effectively final" (same goes for lambda expressions, barring being a field). So the `...` in your code example is, in fact, quite relevant. Furthermore, your check will be invalid since you will be dealing with a new resource every time you reach the try-with-resources block, unless `#createMyResource` does some finnicky check ahead of time. – Rogue Sep 07 '22 at 13:02
  • 2
    Since this method must be invoked from within the try block to make sense, the current resource could be passed as parameter. But storing the value into a field is possible if you can live without reentrancy. The field assignment is just independent from the `try` block. – Holger Sep 07 '22 at 13:02
  • [Related](https://stackoverflow.com/q/72553015/5133585) – Sweeper Sep 07 '22 at 13:12

1 Answers1

3

Maintaining a field for storing an object of limited lifetime should be avoided. But if you really need it, the code should look like

class Manager {
    MyResource m_myResource;
    ...

    void doTraining() {
        try(MyResource newResource = createMyResource()) {
            m_myResource = newResource;
            ...
        }
        finally {
            m_myResource = null;
        }
    }
}

So when there is no ongoing operation within doTraining()’s try() block, the field is always null.

It’s clear that this implementation of doTraining is not reentrant; there must be no overlapping invocations of this method, neither through multiple threads nor through recursive calls.

As said, this is not recommended. But sometimes, passing the resource through all API layers used in an operation is impractical. The existence of such scenarios has been recognized and there’s a planned general solution, ExtentLocal.

With this, the future version would look like

class Manager {
    final static ExtentLocal<MyResource> MY_RESSOURCE = new ExtentLocal<>();
    ...

    void doTraining() {        
        try(MyResource newResource = createMyResource()) {
            ExtentLocal.where(MY_RESSOURCE, newResource )
                .run(() -> {/* code indirectly invoking foo */});
            ...
        }
    }
    void foo() {
        var resource = MY_RESSOURCE.get();
        // use resource
    }
}

Note how this does not mutate the instance for passing the locally valid resource. So here, doTraining() is reentrant.

But well, this is somewhere in the future…

Holger
  • 285,553
  • 42
  • 434
  • 765