Search code examples
javagenericsinterfacejava-8default-method

How to initializing Class<T> at interface default method?


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());
    }
    
}

Solution

  • 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();
        }
    }