How to avoid generics in this case?

class Attribute<T>{
   private T attr;
   public Attribute(T attr){
      this.attr = attr;
   } 
}

Class Matrix<T>{
   private String name;
   List<Attribute<T>> list;

   public Matrix(String name, T t){
     this.name = name;
     list = new ArrayList<>();
     list.add(new Attribute<T>(t)); 
  }
}

 interface Extractor<T> {
    public List<Matrix<T>> extract();
}

InfoExtractor implements Extractor<String>{

    public List<Matrix<String>> extract(){ 
       List<Matrix<String>> matrixList = new ArrayList<>();

        // The problem is here!!!!
        matrixList.add(new Matrix<String>("abc")); 
    }
 }

 Document<T>{
      Map<String, List<Matrix<T>>> matrixMap;

        public void process(){
        ...
         Extractor<T> extractor = (Extractor<T>) new StringExtractor(sent);
        List<Matrix<T>> matrix = extractor.extract(...);
   }

      

My question is, is there a way to avoid defining a matrix as a generic type? The reason I want to avoid is because " List<Attribute<T>>

" is used in several other classes, either as private member variables or method return types. Because of the attribute, it seems to me that I have to define some other related classes as generic types, which is causing my problem.

In this case, is there a way to define the Matrix as generic, but keep the "list" variable as generic?

+3


source to share


2 answers


Your problem is not with the general implementation, but with its usage:

class InfoExtractor implements Extractor{

    // The problem is actually here
    public <T> List<Matrix<T>> extract(){ 
       List<Matrix<T>> matrixList = new ArrayList<>(); //and here

        // "The problem is here!!!!"
        matrixList.add(new Matrix<String>("abc")); 
    }
}

      

<T>

indicates that you are binding a new generic type relative to a method call. In short, the new type is T

just to execute this method. You create as well List<Matrix<T>>

, but then try to add a new one Matrix<String>

back. If you know that the list will be of type Matrix<String>

, you can specify this in InfoExtractor

:

//If you cannot generify the interface for some reason
interface Extractor {
    public List<? extends Matrix<?>> extract(); //explained at bottom
}

//IDEALLY, then implement Extractor<String> instead
interface Extractor<T> {
    public List<Matrix<T>> extract();
}

class InfoExtractor implements Extractor { //or Extractor<String>

    public List<Matrix<String>> extract() {
        List<Matrix<String>> matrixList = new ArrayList<>();
        matrixList.add(new Matrix<>("abc"));
        return matrixList;
    }
}

      



Of course, you can see how the method signature has changed for Extractor

. Because of the use of a nested generic in the return type, things get a little messy for compile-time type matching. <?>

for the Matrix is ​​pretty self-explanatory, we are returning several and possibly unknown types within the matrix.

By pointing ? extends Matrix

to Extractor

, we indicate the correct variance. Specific generics are invariant, so a is List<Toyota>

not List<Car>

even if it Toyota

is a subclass Car

. If we want to Matrix

be covariant, then unfortunately we also need a bounded matrix. This means that, in essence, by ignoring the basic Liskov substitution principle, we will essentially be referring to concrete subclasses for extractors ( InfoExtractor ex

vs Extractor ex

), especially from a usability standpoint (since concrete classes can return proper type safety, whereas an interface does not can).

This is of course handled much more sanely / cleanly when you specify <T>

for matrices in a list as a generic class type.

+2


source


It depends on what you would like to do.

If you intend to use Matrix to store one type of attribute, i.e. T, then the best approach seems to be the one you posted when you get compile-time type checking.

If you remove the generic type T from the Matrix, you cannot know the type of each attribute. You can remove T completely from the Matrix declaration and use a wildcard for the Attribute variable. While this will allow you to store attributes of different T types in the same Matrix object, it will remove compilation type checking. In other words, classes using Matrix would need to know which class to expect and possibly assign each attribute to the expected class.

Class Matrix{
   private String name;
   List<Attribute<?>> list;

   public Matrix(String name, T t){
     this.name = name;
     list = new ArrayList<>();
     list.add(new Attribute<>(t)); 
  }
}

      



Adding a class variable and storing the class object inside the attribute can help you preserve some information at run time to identify the class of objects inside the attribute.

class Attribute<T>{
   private T attr;
   private final Class<T> clazz;

   public Attribute(T attr, Class<T> clazz){
      this.attr = attr;
      this.clazz=clazz;
   } 

    public Attribute(T attr){
      this.attr = attr;
      Objects.requireNonNull(attr);
      this.clazz= (Class<T>) attr.getClass();
   } 
}

      

Using this approach, the original constructor can only be used for non-null values, otherwise the class object must be provided by the programmer.

0


source







All Articles