简体   繁体   中英

Writing a class in a type-safe way

I'd like to write a type-safe code. Here's what I've tried:

public interface ResultTronsformer<T>{
    public T tranform(T t);
}

public class BigDecimalTransformer implements ResultTRansformer<BigDecimal>{

    public BigDecimal transform(BigDecimal t){
        return t.setScale(0);
    }
}

Now I define the Column interface which looks like

public interface Column{
    public ResultTransformer<?> getTransformer();
}

and would like to use it in the method

public class Report{
    private Map<Column, Object> columnValuePairs;

    public void putIntoACollection(Column c, Object columnsValue){
         ResultTransformer<?> rt = c.getTransformer();
         columnValuePairs.put(c, rt.transform(o)); //Error: Couldn't convert Object 
                                                   //to the capture of wildcard
    }
}

How can I rearrange the design to reach the desirable type-safety? Maybe I should do the type-checking at runtime instead (throwing an exception)?

You can think about the Column just like it is some kind of container that holds specific type. In that way, you can introduce generic type in Column declaration.

public interface Column<T>{
    public ResultTransformer<T> getTransformer();
}

Then, you can change Report method as follows:

public <T> void putIntoACollection(Column<T> c, T columnsValue){
        ResultTransformer<T> rt = c.getTransformer();
        columnValuePairs.put(c, rt.transform(columnsValue)); 
}

You can change the Column class and make it parametrized:

public interface Column<T> {
    public ResultTransformer<T> getTransformer();
}

Then you have to parametrize the putIntoACollection method (no need to parametrize Report ):

public class Report {
    private Map<Column, Object> columnValuePairs;

    public <T> void putIntoACollection(Column<T> c, T columnsValue) {
        final ResultTransformer<T> rt = c.getTransformer();
        columnValuePairs.put(c, rt.transform(columnsValue));
    }
}

This way, you never need to use a capture type.

Here is an example of how you would use it:

private class BigDecimalColumn implements Column<BigDecimal> {
    @Override
    public ResultTransformer<BigDecimal> getTransformer() {
        return new BigDecimalTransformer();
    }
}

public static void main(String[] args) {
    final Report report = new Report();
    report.putIntoACollection(new BigDecimalColumn(), new BigDecimal("3.14"));
}

When you get the transformer, you need to specify the type because the compiler won't know it at that time.

A possible solution is to add the class as a parameter to the getTransformer of the Column and return a specialized ResultTransformer.

public interface ResultTransformer<T> {
    public T transform(T t);
}

public interface Column{
    public <T> ResultTransformer<T> getTransformer(Class<T> theClass);
}

public class Report{
   private Map<Column, Object> columnValuePairs;

   public void putIntoACollection(Column c, Object o){
       ResultTransformer<Object> rt = c.getTransformer(Object.class);
       columnValuePairs.put(c, rt.transform(o));
   }

public interface ResultTransformer<T> {
    public T transform(T t);
}

Another way would be to generalize the interface Column.

We could give up all pretenses, just use the god damned raw type

     ResultTransformer rt = c.getTransformer();

A more pretentious solution -

    static <T> T transform (ResultTransformer<T> rt, Object obj)
    {
        T t = (T)obj; // unchecked cast
        return rt.transform(t);
    }


public void putIntoACollection(Column c, Object obj){
     ResultTransformer<?> rt = c.getTransformer();
     columnValuePairs.put(c, transform(rt, obj) );

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM