Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to serialize anonymous class without outer class?

I made a small research on web and reviewed related topics on this site, but the answers were contradictory: some people said it is not possible, others said it is possible, but dangerous.

The goal is to pass an object of the anonymous class as a parameter of the RMI method. Due to RMI requirements, this class must be serializable. Here's no problem, it is easy to make class Serializable.

But we know that instances of inner classes hold a reference to an outer class (and anonymous classes are inner classes). Because of this, when we serialize instance of inner class, instance of outer class is serialized as well as a field. Here's the place where problems come: outer class is not serializable, and what's more important - I do not want to serialize it. What I want to do is just to send instance of the anonymous class.

Easy example - this is an RMI service with a method that accepts Runnable:

public interface RPCService {    
    Object call(SerializableRunnable runnable);
}

And here is how I'd like to call the method

void call() {
     myRpcService.call(new SerializableRunnable() {             
         @Override
         public Object run {
             System.out.println("It worked!");
         }
     }        
}

As you can see, what I want to do is to send an "action" to the other side - system A describes the code, that should be run on system B. It is like sending a script in Java.

I can easily see some dangerous consequences, if this was possible: for example if we access a field or captured final variable of outer class from Runnable - we'll get into a trouble, because caller instance is not present. On the other hand, if I use safe code in my Runnable (compiler can check it), then I don't see reasons to forbid this action.

So if someone knows, how writeObject() and readObject() methods should be properly overriden in anonymous class OR how to make reference to outer class transient OR explain why it is impossible in java, it will be very helpful.

UPD Yet another important thing to consider: outer class is not present in the environment that will execute the method (system B), that's why information about it should be fully excluded to avoid NoClassDefFoundError.

like image 400
AdamSkywalker Avatar asked Oct 17 '14 10:10

AdamSkywalker


1 Answers

You can't do exactly what you want, which is to serialize an anonymous inner class, without also making its enclosing instance serializable and serializing it too. The same applies to local classes. These unavoidably have hidden fields referencing their enclosing instances, so serializing an instance will also attempt to serialize their enclosing instances.

There are a couple different approaches you can try.

If you're using Java 8, you can use a lambda expression instead of an anonymous inner class. A serializable lambda expression does not (necessarily) have a reference to its enclosing instance. You just need to make sure that your lambda expression doesn't reference this explicitly or implicitly, such as by using fields or instance methods of the enclosing class. The code for this would look like this:

public class Caller {
    void call() {
        getRpcService().call(() -> {
            System.out.println("It worked!");
            return null;
        });
}

(The return null is there because RPCService.Runnable.run() is declared to return Object.)

Also note that any values captured by this lambda (e.g., local variables, or static fields of the enclosing class) must also be serializable.

If you're not using Java 8, your next best alternative is to use a static, nested class.

public class Caller {
    static class StaticNested implements RPCService.Runnable {
        @Override
        public Object run() {
            System.out.println("StaticNested worked!");
            return null;
        }
    }

    void call() {
        getRpcService().call(new StaticNested());
    }
}

The main difference here is that this lacks the ability to capture instance fields of Caller or local variables from the call() method. If necessary, these could be passed as constructor arguments. Of course, everything passed this way must be serializable.

A variation on this, if you really want to use an anonymous class, is to instantiate it in a static context. (See JLS 15.9.2.) In this case the anonymous class won't have an enclosing instance. The code would look like this:

public class Caller {
    static RPCService.Runnable staticAnonymous = new RPCService.Runnable() {
        @Override
        public Object run() {
            System.out.println("staticAnonymous worked!");
            return null;
        }
    };

    void call() {
        getRpcService().call(staticAnonymous);
    }
}

This hardly buys you anything vs. a static nested class, though. You still have to name the field it's stored in, and you still can't capture anything, and you can't even pass values to the constructor. But it does satisfy your the letter of your initial question, which is how to serialize an instance of an anonymous class without serializing an enclosing instance.

like image 161
Stuart Marks Avatar answered Sep 28 '22 06:09

Stuart Marks