0

Currently, I have three classes, Apple, Grape, Banana, and I created a converter class (can convert a string to list) for each of them. Then I realized all those converter classes are basically the same except they take different class types. The example of AppleConverter.java

  public class AppleConverter extends StrutsTypeConverter {

  @SuppressWarnings("rawtypes")
  @Override
  public Object convertFromString(Map context, String[] values, Class toClass) {

    final List<Apple> appleList = new ArrayList<>();
    try {
      for (String appleString : values) {
          Apple apple = Apple.valueOf(appleString);
          appleList.add(apple);
      }
    }
    catch (IllegalArgumentException e) {
      throw new TypeConversionException(e);
    }
    return appleList;
  }

  @SuppressWarnings("rawtypes")
  @Override
  public String convertToString(Map context, Object o) {
    if (o == null) {
      return null;
    }
    if (o instanceof List<?>) {
      List<Apple> list = (List<Apple>) o;
      String appleString = list
        .stream()
        .map(e -> String.valueOf(e))
        .collect(Collectors.joining(","));
      return appleString;
    }
    throw new TypeConversionException("wrong");
  }

Basically, GrapdeConverter.java and BananaConverter.java are the same as AppleConverter.java except they take different class types. So I tried to create a generic class then those converter classes can inherit it.
My generic class code:

    public class FruitConverter<T> extends StrutsTypeConverter {


  @SuppressWarnings("rawtypes")
  @Override
  public Object convertFromString(Map context, String[] values, Class toClass) {
    if (ArrayUtils.isEmpty(values)) {
      return null;
    }
    final List<T> objs = new ArrayList<>();
    try {
      for (String objStr : values) {

           Object obj = toClass.getDeclaredMethod("valueOf", String.class).invoke(null, objStr);

          objs.add(obj);
        }
      }
    }
    catch (IllegalArgumentException e) {
      throw new TypeConversionException(e);
    }
    return objs;
  }

  @SuppressWarnings("rawtypes")
  @Override
  public String convertToString(Map context, Object o) {
    if (o == null) {
      return null;
    }
    if (o instanceof List<?>) {
      List<?> list = (List<?>) o;
      String fruitString = list
        .stream()
        .map(e -> String.valueOf(e))
        .collect(Collectors.joining(","));
      return fruitString;
    }
    throw new TypeConversionException("object is not a list of objs");
  }
}

When I called T obj = T.valueOf(objStr);, it throws an error can not resolve method valueOf. May anyone tell me how to apply generics correctly in this case.

Roman C
  • 47,329
  • 33
  • 60
  • 147
user3369592
  • 1,187
  • 5
  • 16
  • 38

2 Answers2

1

You cannot use T in such expressions, I doubt that you even need generics for this task.

Quick and dirty way to generalize is to use reflection, instead of T obj = T.valueOf(objStr); use this:

Object obj = toClass.getDeclaredMethod("valueOf", String.class).invoke(null, objStr);

then you will be able to use same function for all objects like this.

Another approach is to use fabrics, but it will require much more code.

Iłya Bursov
  • 20,672
  • 4
  • 30
  • 48
1

The compilation error occurs because Java's generics are implemented with type erasure. In this case, T.valueOf is treated as the same as if you had tried to do Object.valueOf, i.e. it doesn't know which classes to search in for the valueOf method except for Object.

You can pass the method in as a function using a method reference, as in the following example:

import java.util.function.Function;

class FruitConverter<T> {
    private final Function<String, T> valueOf;

    FruitConverter(Function<String, T> valueOf) {
        this.valueOf = valueOf;
    }

    // ...
}

class AppleConverter extends FruitConverter<Apple> {
    AppleConverter() {
        super(Apple::valueOf);
    }
}

Then you can use this.valueOf.apply(...) when you want to call the method.

Also see https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html.

Radiodef
  • 35,285
  • 14
  • 78
  • 114