Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

anonymous class as generic parameter


I want to create a class that gets an object from anonymous class definition to store. I used a generic typed class to achieve that. Then i want to define some operations using functional interfaces that gets this object as a parameter to work with.
Code says more than words. So have a look at this:

public class Test<T> {
    @FunctionalInterface
    public interface operation<T> {
        void execute(T object);
    }
    private T obj;
    public Test(T _obj){
        obj = _obj;
    }
    public void runOperation(operation<T> op){
        op.execute(obj);
    }

    public static void main(String[] args){
        Test<?> t = new Test<>(new Object(){
            public String text = "Something";
        });
        t.runOperation((o) -> {
            System.out.println(o.text);  // text cannot be resolved
        });
    }
}

My problem is that o.text in the implementation of the functional interface cannot be resolved. Is this some kind of type erasure consequence?
The interesting thing is that I can get this code working when I implement the functional interface in the constructor.
Have a look at this code:

public class Test<T> {
    @FunctionalInterface
    public interface operation<T> {
        void execute(T object);
    }
    private T obj;
    private operation<T> op;

    public Test(T _obj, operation<T> _op){
        obj = _obj;
        op = _op;
    }
    public void runOperation(){
        op.execute(obj);
    }
    public static void main(String[] args){
        Test<?> t = new Test<>(new Object(){
            public String text = "Something";
        }, (o) -> {
            System.out.println(o.text);
        });
        t.runOperation();
    }
}

This works perfect and prints out "Something". But what is wrong with my first approach? I really don't get the problem here.

like image 211
ArcticLord Avatar asked Nov 20 '15 11:11

ArcticLord


2 Answers

The problem is that your anonymous class still has to conform to (extend or implement) some type, and the type you've chosen is Object which doesn't have your text property. In order to refer to properties of some kind, you'll need an actual class or interface to work with, so the compiler can make guarantees about what properties and methods are available on the object.

This works.

public class Test<T> {

    public static class Data {
        public String text;
    }

    @FunctionalInterface
    public interface Operation<K> {
        void execute(K object);
    }

    private T obj;
    private Operation<T> op;

    public Test(T obj) {
        this.obj = obj;
    }

    public void runOperation(Operation<T> op) {
        op.execute(obj);
    }

    public static void main(String[] args) {
        Test<Data> t = new Test<>(new Data() {{
            this.text = "Something";
        }});

        t.runOperation((o) -> {
            System.out.println(o.text);
        });
    }
}
like image 176
Ian McLaird Avatar answered Sep 28 '22 16:09

Ian McLaird


In the second piece of code,

    new Test<>(new Object(){
        public String text = "Something";
    }, (o) -> {
        System.out.println(o.text);
    });

compiles because the type argument of Test for the constructor call is inferred (since the diamond operator is used), and it is inferred to the anonymous type that the first argument evaluates to (the anonymous class type), and thus the second argument's type is operation<that anonymous class type>, which works.

In the first piece of code, the expression

    t.runOperation((o) -> {
        System.out.println(o.text);  // text cannot be resolved
    })

does not compile. Here, the type of the lambda is inferred based on the type of the variable t, which is Test<?>. Thus, the argument of runOperation must be operation<some unknown type>. The only argument to runOperation that will work here is null.

like image 24
newacct Avatar answered Sep 28 '22 17:09

newacct