Combining pattern usage in methods and as a receiver in Java

I am trying to use a combination of wildcards in the type of the recipient and as an argument for a method in Java. Context is a container definition. Now the type Container should not allow insertions at all, since this type does not indicate the type of contained objects. However, if the underlying data structure allows this, there must be a way to search for an object of type T or any other type that extends T.

Here is a piece of code that demonstrates the problem. Any ideas on how I can achieve this design goal in Java?

public class Main { public static class Container<T extends Item> { public void insert(T t) { System.out.println("Inserting " + t); } public <R extends T> int find(R r) { return r.hashCode(); } } public static class Item { // Nothing here } public static class ExtendedItem extends Item { // And nothing here... } public static class Client { public static void main(String[] args) { useContainerOfItem(); useContainerWildCardOfItem(new Container<Item>()); Container<? extends Item> c; c = new Container<Item>(); // OK. type of c, is a super type of Container<Item> c = new Container<ExtendedItem>(); // OK. type of c, is a super type of Container<ExtendedItem> useContainerWildCardOfItem(c); } private static void useContainerOfItem() { Container<Item> c = new Container<Item>(); c.insert(new Item()); // OK. We can insert items c.insert(new ExtendedItem()); // OK. We can insert items c.find(new Item()); // OK. We can find items in here. c.find(new ExtendedItem()); // OK. We can also find derived items. } private static void useContainerWildCardOfItem(Container<? extends Item> c) { c.insert(new Item()); // Error: expected. We should not be able to insert items. c.insert(new ExtendedItem()); // Error: expected. We should not be able to insert anything! c.find(new Item()); // Error. Why??? We should be able to find items in here. c.find(new ExtendedItem()); // Error. Why??? We should be able to find items in here. } } 

}

+4
source share
5 answers

The error message tells you exactly what the problem is. Your find method uses the generic R extends T , and T in this case ? therefore the compiler is not able to check your provided R (a Item ) to check if it extends "capture # 6-of?".

+1
source

I think your search method is parameterized incorrectly. In particular, you almost certainly don't want to use extensions in the R extends T declaration.

You seem to agree that with the wildcard common parameter <? extends Item> <? extends Item> you cannot insert anything because the compiler cannot claim that any particular object you pass matches borders (with the exception of a null literal). Remember that this is not due to any special semantics of the insert-type method, but solely because of the interface.

Your find method cannot be called for the same reason. Keep in mind that declaring <R extends T> , and then declaring a parameter of type R , exactly matches the declaration of a parameter of type T (Think about it, valid values ​​in both cases). And, as you saw above, no nonzero objects can be accepted as an instance of T in your substitution case.

I think you might have intended to write your search method as <R super T> . In this case, the compiler can know for sure that regardless of the actual type of T , it is Item or a subtype - and therefore Item or any of its superclasses (including Object) will always be valid for R and, therefore, can be passed. However, in this case, since Object is a valid replacement for borders, and all objects can be accepted for the Object parameter, this method is then equivalent

 public int find(Object r) { return r.hashCode(); } 

This is a fact - this is the full semantics that you are trying to capture - you do not need general limitation, because they do not provide any boundaries. Usually it is always worth using super within common bounds when it is a nested common parameter, for example. you take the collection as the parameter you want to add objects of type T to (in this case, you need Collection<? super T> ).

Alternatively, after reading your own answer to the question, my assessment in the above paragraph may be a little wrong. There are three different restrictions that you might try to apply to the argument type of the find method:

  • Everything is valid (i.e. Object ).
  • The argument must be an instance of the maximum possible boundary for the contained types (therefore, if the Container is defined as Container<T extends Item> , you declare a method to accept a parameter of type Item ).
  • Types must match exactly (i.e. T ).

In general, I would recommend going as general as possible, if you need to call the methods that are defined in the Item class to check compliance, then your hands are tied and you have to go with the second option. However, if you do not need to do this, then accept arguments of type Object to provide subscribers with the greatest flexibility.

Along these lines there is practically no argument for accepting option 3 - you will not get any additional functions in your method (since you cannot call more specific methods than you could in the second case), and you simply limit clients. Consider the following:

 MyItem a = new MyItem(); Container<MyItem> c = new Container<MyItem>(); c.insert(a); // Much later, possibly passing through various layers of the stack/maps/etc. Item i = a; c.find(i); // Will not compile if the find method takes an argument of type T 

There is no benefit in forcing callers to reset the Item link to T , in particular when, by definition, you can make the required method calls inside the find object of the Item object and you can return the corresponding answer based on the actual state of the object, and not on the link that he is currently holding.

+1
source

The key point is that the type Container <? extends Item> means a container is something that extends an Item, not something that extends an Item. Therefore, it is possible that an object of type Item (the one that is passed to the find method) may not be a compatible subclass of what extends Item. The compiler cannot verify the correctness of the code, so it throws an error. The best you can do is allow a much wider parameter to find and limit the type of return value:

 public class Container<T> { public void insert(T item) { // insert... } public T find(Object o) { // Lookup using a map or something return null if not found } } 
0
source

I could understand the error message, but still this does not answer my question, which was:

Any ideas on how I can achieve this design goal in Java?

My best solution is to use not one, but two common parameters for the Collection class. The first thing you can find, and the other that you can insert. If this is cumbersome, you use the superclass to understand that you don't care about the type that expects to "find."

However, I am not happy with this, and I hope there is a simpler solution.

 public class Main { public static class Searchable<R extends Item> { public int find(R r) { return r.hashCode(); } } public static class Container<T extends Item> extends Searchable<Item>{ public void insert(T t) { System.out.println("Inserting " + t); } } public static class Item { // Nothing here } public static class ExtendedItem extends Item { // And nothing here... } public static class Client { public static void main(String[] args) { useContainerOfItem(); useContainerWildCardOfItem(new Container<Item>()); Container<? extends Item> c; c = new Container<Item>(); // OK. the type of c is a super type of Container<Item> c = new Container<ExtendedItem>(); // OK. the type of c is a super type of Container<ExtendedItem> useContainerWildCardOfItem(c); } private static void useContainerOfItem() { Container<Item> c = new Container<Item>(); c.insert(new Item()); // OK. We can insert items c.insert(new ExtendedItem()); // OK. We can insert items c.find(new Item()); // OK. We can find items in here. c.find(new ExtendedItem()); // OK. We can also find derived items. } private static void useContainerWildCardOfItem(Container<? extends Item> c) { c.insert(new Item()); // Error: expected. We should not be able to insert an item! c.insert(new ExtendedItem()); // Error: expected. We should not be able to insert anything! c.find(new Item()); // No error, we should be able to find an Item c.find(new ExtendedItem()); // No error, we should be able to find an ExtendedItem } } 
0
source

I think the goal here is wrong. You acknowledge that your Container#insert() method cannot work with a reference to the Container wildcard, but you expect your Container#find() method to work. This is the same problem in both cases; you are trying to use covariance in both cases, which cannot be applied in Java against a wildcard like this.

The original signature for Container#find() was fine. It matched your requirements. The latter involving Searchable#find() too relaxed; it allows you to search by any type of Item , and not just by the type equivalent to or obtained from the lower type T container. If the specification states that you only need to look for records that may be in the container, and we do not know the specific type of records in the container, we cannot ensure that this contract is executed from the call site, for example Container#find() .

Instead, try to avoid such wildcards:

 private static <C extends Item, U extends C> void useContainerOfSpecificItem(Container<C> c, C key1, U key2) { c.find(key1); c.find(key2); } 

There you can see that find() accepts covariant key types, although in this limited use you don't really need to distinguish between type U and type C

0
source

All Articles