There is a class RecipeElement . There is also in one of the classes with a functional method:

 private void updateRecipeElements(RecipeElement element) { if (!mRecipeElements.contains(element)) { element.setParentRecipeUUID(mRecipe.getId().toString()); mRecipeElements.add(element); } } 

The logic, as you can see, is simple.

After a while, I needed to expand the functionality and also add the RecipeStep class, which has processing logic that has the same field and method ( setParentRecipeUUID ). By this, I created an abstract class containing one parameter, a getter and a setter:

 public abstract class RecipeChild { public String mParentRecipeUUID; public String getParentRecipeUUID() { return mParentRecipeUUID; } public void setParentRecipeUUID(String parentRecipeUUID) { mParentRecipeUUID = parentRecipeUUID; } } 

Inherited the RecipeElement and RecipeStep from it in order to make the above method "universal" for objects of both classes, since the logic of the method is primitive. Now the method looks like this:

 private void updateArrayOfChilds(RecipeChild element, ArrayList<RecipeChild> array) { if (!array.contains(element)) { element.setParentRecipeUUID(mRecipe.getId().toString()); array.add(element); } } 

The method has practically failed to change, except for one input parameter — an array of required child elements, which should be used.

But during the change, a problem arose - during the method development in the class where it is declared - updateArrayOfChilds(step, mRecipeSteps); development environment warns about compilation errors in the future Mistake

Why does an exception occur? After all, my RecipeStep class (like RecipeElement) is inherited from the class that the “improved” method can accept and an extension of the type should occur (or not?). How to use generics in this case (if at all possible)?

UPD : I solved one problem by changing the method signature to private void updateArrayOfChilds(RecipeChild element, ArrayList<? extends RecipeChild> array) but in this case the problem occurs directly inside the method: enter image description here

  • Try <T extends RecipeChild> void updateArrayOfChilds(T element, ArrayList<T> array) if the element type is known at compile time and matches the content type of array . If not, then I don't think it will. - zRrr
  • Yes, the type is known, I did it, it helped, thanks for the advice! - abbath0767

2 answers 2

I will try to explain with examples why such errors appear. We have two types with a common ancestor:

 interface RecipeChild {} class RecipeElement implements RecipeChild {} class RecipeStep implements RecipeChild {} 

and lists of elements of these types:

 ArrayList<RecipeElement> elements = new ArrayList<>(); ArrayList<RecipeStep> steps = new ArrayList<>(); 

We need a method that will insert an element of the specified type into the list of elements of this type (and there is something else to do).

Let's try to get by with the supertype:

 void addToList_supertype( RecipeChild element, ArrayList<RecipeChild> array ) { // работает array.add( element ); } void addSome_supertype() { // The method addToList_supertype(RecipeChild, ArrayList<RecipeChild>) // in the type Generics // is not applicable for the arguments (RecipeElement, ArrayList<RecipeElement>) addToList_supertype( new RecipeElement(), elements ); } 

The code does not work, because generics are invariant (the inheritance relationship between types in a parameter does not establish an inheritance relationship between parameterized types) and ArrayList<RecipeElement> not a descendant of ArrayList<RecipeChild>

Let's try to make addToList accept lists of RecipeChild heirs:

 void addSome_wildcard_extends() { // работает, ура addToList_wildcard_extends( new RecipeElement(), elements ); } void addToList_wildcard_extends( RecipeChild element, ArrayList<? extends RecipeChild> array ) { // но нет... // The method add(capture#1-of ? extends RecipeChild) // in the type ArrayList<capture#1-of ? extends RecipeChild> // is not applicable for the arguments (RecipeChild) array.add( element ); } 

ArrayList<? extends RecipeChild> type ArrayList<? extends RecipeChild> ArrayList<? extends RecipeChild> is not a list in which any heirs of RecipeChild , but a list of elements of some type that is a heir of RecipeChild . ArrayList<RecipeElement> is suitable, so calling addToList_wildcard_extends works, but array.add( element ) could add an element of arbitrary type (eg RecipeStep ) to the inheriting RecipeChild , so the compiler denies this call.

We read about PECS, we see there that if the data is passed to the argument ( array.add( element ) ), then the argument is the consumer, and you need to use super . We do:

 void addToList_wildcard_super( RecipeChild element, ArrayList<? super RecipeChild> array ) { // работает array.add( element ); } void addSome_wildcard_super() { // The method addToList_wildcard_super(RecipeChild, ArrayList<? super RecipeChild>) // in the type Generics // is not applicable for the arguments (RecipeElement, ArrayList<RecipeElement>) addToList_wildcard_super( new RecipeElement(), elements ); } 

array.add earned, because array now a list of elements with some kind of ancestor RecipeChild . Since an heir can be used anywhere an ancestor is used, adding a child to the collection will not break anything. But the addToList call broke because RecipeElement is not an ancestor of RecicpeChild , and the RecipeChild list RecipeChild not appropriate.

Remember what we need. You need to add an object of some type to the list of objects of the same type . Those. we need to declare the type:

 // объявляем тип-параметр T, расширяющий RecipeChild <T extends RecipeChild> void addToList_type_parameter( T element, ArrayList<T> array ) { // работает, мы можем добавить элемент типа T в список элементов типа T array.add( element ); } void addSome_type_parameter() { // работает, мы добавляем элемент типа RecipeElement в список RecipeElement addToList_type_parameter( new RecipeElement(), elements ); // не работает, тип элементов списка не совпадает addToList_type_parameter( new RecipeElement(), steps ); } 

Then you can again recall PECS and use ArrayList<? super T> ArrayList<? super T> . Then the method will be able to add elements not only to the list of the same type, but also to the lists of ancestors:

 <T extends RecipeChild> void addToList_type_parameter_super( T element, ArrayList<? super T> array ) { // работает, мы можем добавить элемент типа T в список элементов типа T array.add( element ); } void addSome_type_parameter_super() { ArrayList<RecipeChild> children = new ArrayList<>(); addToList_type_parameter_super( new RecipeElement(), children ); // работает addToList_type_parameter_super( new RecipeStep(), children ); // работает addToList_type_parameter_super( new RecipeElement(), steps ); // не работает // T == RecipeElement, RecipeStep не является супертипом RecipeElement } 
  • Thank you for the detailed answer! I understand correctly that using the "? Super T" construct as an array type, and not the "T extends RecipeChild" advises PECS principles, even if they are similar in result? - abbath0767
  • @ mamba0767 you simply can not use methods of a generic type whose argument type is declared with extends . In general, PECS is a convenient (well for English-speaking) memory for covariance (PE) / contravariance (CS) ( wiki ) - zRrr

The development environment tells you to add a wildcard to the signature:

 private void updateArrayOfChilds(RecipeChild element, ArrayList<? extends RecipeChild> array) 

So everything will be fine

  • @iksay I literally did just that a few minutes before your answer, but then there is a problem inside the method. - abbath0767
  • And what is your variable array ? Are you familiar with the PECS (Producer extends consumer super) principle? I recommend: habrahabr.ru/post/207360 - iksuy
  • Thanks for the link, it was required to add <T extends RecipeChild> in the signature of the method itself, not the parameters. - abbath0767