0

I have an interface:

public interface ITransformer<S,T>{
    
    public void transform(S source,T target);
    
    default String getTransformerName(){
        Class<S> s;
        Class<T> t;
        
        return s.getName() + t.getName();  //*********
    }
    
}

the error message the starred line:

The local variable s may not have been initialized

The local variable t may not have been initialized

I would like to use this method to return a string with [S.classname][T.classname] . Please let me know how to achieve this or is this impossible to do at interface ?

Update: Jan 12

My purpose of doing this is due to the fact that this class will be in framework and I want to reduce the human error as much as possible.. I am changing the code as follows:

public interface ITransformer<S,T>{
    
    public void transform(S source,T target);
    
    public FieldEntry<S, T> getTransformerName();
    
}


public class FieldEntry<S,T> implements Comparable<FieldEntry> {
    
    private Class<S> s;
    private Class<T> t;

    public FieldEntry(Class<S> s,Class<T> t){
        this.s = s;
        this.t = t;
    }
    
    public String getEntryName(){
        return s.getName() + t.getName();
        
    }

    @Override
    public int compareTo(FieldEntry entry) {
        if(entry == null) throw new IllegalArgumentException("The argument to compare cannot be null!");
        
        return entry.getEntryName().compareTo(this.getEntryName());
    }
    
}
Community
  • 1
  • 1
Seng Zhe
  • 613
  • 1
  • 11
  • 21

3 Answers3

2

In order to demonstrate why this can’t work, you may change your class to

public interface ITransformer<S,T>{

    public void transform(S source,T target);

    static <In,Out> ITransformer<In,Out> noOp() {
        return (source,target) -> {};
    }
    static void main(String... arg) {
        ITransformer<String,Integer> t1 = noOp();
        ITransformer<Long,Thread> t2    = noOp();

        System.out.println(t1 == (Object)t2);
    }
}

Running this will print true. In other words, both functions are represented by the same instances, so there can’t be and property allowing to recognize their different type.

Generally, when two functions (lambda expressions or method references) exhibit the same behavior, a JVM may represent them by the same implementation type or even the same instance.

Even for non-interface classes, this doesn’t work due to Type Erasure. It only works when you have a reifiable (i.e. non-generic) type extending or implementing a generic type.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • regarding your last sentence: the other answer is showing exactly that via ParameterizedType. – Eugene Jan 11 '17 at 11:14
1

In Java it is impossible to get a Class<S>, unless you already know which class S is, or something else that knows which class S is gives you one.

user253751
  • 57,427
  • 7
  • 48
  • 90
1

It's a little bit dangerous and I wouldn't used this in production (because you should cover in your code all possible use cases of your interface), but you can use reflection for it:

public interface ITransformer<S, T> {

    public void transform(S source, T target);

    default String getTransformerName() {
        Type[] genericInterfaces = this.getClass().getGenericInterfaces();

        ParameterizedType parameterizedType = null;

        for (Type genericInterface : genericInterfaces) {
            if (genericInterface instanceof ParameterizedType) {
                ParameterizedType paramInterface = (ParameterizedType) genericInterface;
                if (paramInterface.getRawType().equals(ITransformer.class)) {
                    parameterizedType = paramInterface;
                    break;
                }
            }
        }

        if (parameterizedType == null) {
            throw new IllegalStateException("!");
        }

        return parameterizedType.getActualTypeArguments()[0].getTypeName() + parameterizedType.getActualTypeArguments()[1].getTypeName();  

    }
}

public class StringToIntegerTransfomer implements ITransformer<String, Integer> {

    @Override
    public void transform(String source, Integer target) {

    }
}

public interface StringToNumberTransfomer<T extends Number> extends ITransformer<String, T> {

}

public class StringToLongTransfomer implements StringToNumberTransfomer<Long>, ITransformer<String, Long> {
    @Override
    public void transform(String source, Long target) {

    }
}

@Test
public void test() {
    ITransformer<String, Integer> intTransformer = new StringToIntegerTransfomer();
    ITransformer<String, Long> longTransformer = new StringToLongTransfomer();
    ITransformer<String, String> stringTransformer = new ITransformer<String, String>() {

        @Override
        public void transform(String source, String target) {

        }
    };
    ITransformer<String, Double> doubleTransformer = new StringToNumberTransfomer<Double>() {

        @Override
        public void transform(String source, Double target) {

        }
    };

    System.out.println(String.format("intTransformer: %s", intTransformer.getTransformerName()));
    System.out.println(String.format("longTransformer: %s", longTransformer.getTransformerName()));
    System.out.println(String.format("stringTransformer: %s", stringTransformer.getTransformerName()));
    System.out.println(String.format("doubleTransformer: %s", doubleTransformer.getTransformerName()));
}

Output for this snippet:

intTransformer: java.lang.Stringjava.lang.Integer
longTransformer: java.lang.Stringjava.lang.Long
stringTransformer: java.lang.Stringjava.lang.String


java.lang.IllegalStateException: !

This code has one restriction, you should say implements ITransformer<S, T> for all implementations of ITransformer. That why I have got IllegalStateException for this line ITransformer<String, Double> doubleTransformer = new StringToNumberTransfomer<Double>(). But you can improve this code.

Better option is to use some base implementation of interface and pass source and target classes into constructor:

public interface ITransformer<S, T> {

    void transform(S source, T target);

    String getTransformerName();
}

public abstract class BaseITransformer<S, T> implements ITransformer<S, T> {

    private final Class<S> sourceClass;
    private final Class<T> targetClass;

    public BaseITransformer(Class<S> sourceClass, Class<T> targetClass) {
        this.sourceClass = sourceClass;
        this.targetClass = targetClass;
    }

    public String getTransformerName() {
        return sourceClass.getName() + targetClass.getName();
    }
}
Vlad Bochenin
  • 3,007
  • 1
  • 20
  • 33
  • I wanted to post about the same thing... +1 – Eugene Jan 11 '17 at 11:10
  • 1
    I bet, the OP wants to implement this interface via lambda expressions. Then, this doesn’t work. – Holger Jan 11 '17 at 11:14
  • 1
    Not only does the reflective solution impose limitations on the implementation class, the assumption that the type parameters are always `Class`es adds limitations to the type parameters itself. What about `ITransformer,List>`? – Holger Jan 11 '17 at 11:19
  • Updated for `ITransformer,List>`. For lambda fix looks more complicated, because java returns `Class` as generic interface and this class returns `Object` as type in `getTypeParameters()[0].getBounds()` . Meanwhile, I've said in answer `you should cover in your code all possible use cases of your interface` and recommended to use the second option – Vlad Bochenin Jan 11 '17 at 11:42
  • This is part of a framework where the source or target is not going to be a Collection. I want to simplify the implementer side as much as possible to reduce the human error. To my surprise my code does not work as I expected – Seng Zhe Jan 12 '17 at 01:35