Explanation of the Get-Put Principle

Explanation of the get-put principle

Consider a bunch of bananas. This is a Collection<? extends Fruit> in that it's a collection of a particular kind of fruit - but you don't know (from that declaration) what kind of fruit it's a collection of. You can get an item from it and know it will definitely be a fruit, but you can't add to it - you might be trying to add an apple to a bunch of bananas, which would definitely be wrong. You can add null to it, as that will be a valid value for any kind of fruit.

Now consider a fruitbowl. This is a Collection<? super Banana>, in that it's a collection of some type "greater than" Banana (for instance, Collection<Fruit> or Collection<TropicalFruit>). You can definitely add a banana to this, but if you fetch an item from the bowl you don't know what you'll get - it may well not be a banana. All you know for sure is that it will be a valid (possibly null) Object reference.

(In general, for Java generics questions, the Java Generics FAQ is an excellent resource which contains the answer to almost anything generics-related you're likely to throw at it.)

Get-put principles in Java generics

Nothing unexpected is happening here. Let's run through the steps:

List<? super A> list = new ArrayList<>();

We have a List of ?, which are above A in the class hierarchy. Every ? is an Object, so we can think of it as a List<Object>. Proceeding onwards...

list.add(new A());
System.out.println((list.get(0)

All good so far - list has an A inside, and it is fetched as an Object.

                               .toString()));

We call toString, which is invoked on an Object (the A). Dynamic dispatch proceeds to invoke the A's toString method (the lowest definition from the type hierarchy). However this is perfectly legal because toString is defined for Object as well as for A. Moving on...

 A a = list.get(0); //oops!

This breaks, as expected, because we try to convert an Object from a List<Object> into an A, without a cast (e.g. A a = (A) list.get(0);).

Overcoming generics put-get rule

No, it's absolutely fine to have two different kinds of Fruit in a List<Fruit>.

The issue comes when you've actually created a List<Banana>. For example:

List<Banana> bananas = new ArrayList<>();
bananas.add(new Banana());
// This is fine!
List<? extends Fruit> fruits = bananas;
// Calling fruits.get(0) is fine, as it will return a Banana reference, which
// is compatible with a Fruit reference...

// This would *not* be fine
List<Fruit> badFruits = bananas;
badFruits.add(new Apple());
Banana banana = bananas.get(0); // Eek! It's an apple!

What is the difference between POST and PUT in HTTP?

Overall:

Both PUT and POST can be used for creating.

You have to ask, "what are you performing the action upon?", to distinguish what you should be using. Let's assume you're designing an API for asking questions. If you want to use POST, then you would do that to a list of questions. If you want to use PUT, then you would do that to a particular question.

Great, both can be used, so which one should I use in my RESTful design:

You do not need to support both PUT and POST.

Which you use is up to you. But just remember to use the right one depending on what object you are referencing in the request.

Some considerations:

  • Do you name the URL objects you create explicitly, or let the server decide? If you name them then use PUT. If you let the server decide then use POST.
  • PUT is defined to assume idempotency, so if you PUT an object twice, it should have no additional effect. This is a nice property, so I would use PUT when possible. Just make sure that the PUT-idempotency actually is implemented correctly in the server.
  • You can update or create a resource with PUT with the same object URL
  • With POST you can have 2 requests coming in at the same time making modifications to a URL, and they may update different parts of the object.

An example:

I wrote the following as part of another answer on SO regarding this:

POST:

Used to modify and update a resource

POST /questions/<existing_question> HTTP/1.1
Host: www.example.com/

Note that the following is an error:

POST /questions/<new_question> HTTP/1.1
Host: www.example.com/

If the URL is not yet created, you
should not be using POST to create it
while specifying the name. This should
result in a 'resource not found' error
because <new_question> does not exist
yet. You should PUT the <new_question>
resource on the server first.

You could though do something like
this to create a resources using POST:

POST /questions HTTP/1.1
Host: www.example.com/

Note that in this case the resource
name is not specified, the new objects
URL path would be returned to you.

PUT:

Used to create a resource, or
overwrite it. While you specify the
resources new URL.

For a new resource:

PUT /questions/<new_question> HTTP/1.1
Host: www.example.com/

To overwrite an existing resource:

PUT /questions/<existing_question> HTTP/1.1
Host: www.example.com/

Additionally, and a bit more concisely, RFC 7231 Section 4.3.4 PUT states (emphasis added),

4.3.4. PUT

The PUT method requests that the state of the target resource be
created or replaced with the state defined by the representation
enclosed in the request message payload.

What's the difference between these generic declarations?

  1. A List whose elements are lists of dogs
  2. A List whose elements are lists of a type that extends Dog
  3. A List whose elements are a single subtype of a list of dogs
  4. A List whose elements are a single subtype of a (list of a type that extends Dog)
  5. A List whose elements are a single supertype of a (list of a type that extends Dog)
  6. A List whose elements are a single subtype of a (list of a type that super Dog)

Where "subtype", "supertype", "super", and "extends" are the "generic" versions (i.e. also includes the bounding type)


Examples with Number and subtypes, because why not. Replace Dog with Number.

  1. List<List<Number>> might look like a 2D array of Number elements. Pretty simple.

  2. List<List<? extends Number>> might look like a 2D array, where each row is a different subtype of number. For example, the first row might be a List<Integer>, the second row might be a List<Double>, etc.

  3. List<? extends List<Number>> might be a List<ArrayList<Number>>, List<List<Number>>, List<LinkedList<Number>>, etc. The Number has to stay, because generics are invariant, but you can have List or any of its subtypes as the "overall" type of the elements. You can only pick one of List or its subtypes though, and the one you pick you have to stay with.

  4. List<? extends List<? extends Number>> is similar to List<? extends List<Number>>, except now you can pick Number or any of its subclasses as the elements of the "overall" 2D array. So you can have List<List<Integer>>, List<ArrayList<Integer>>, List<LinkedList<Double>>, etc. As before, you can only pick one of List's subtypes and one of Number's subtypes.

  5. (tricky!) List<? super List<? extends Number>> appears to be equivalent to List<List<? extends Number>>, List<Collection<? extends Number>>, etc. but not List<List<Number>> or anything concrete where a subtype of Number is used. I think this is because List<Number> isn't considered a supertype of List<? extends Number>, which I suppose makes sense due to generics being invariant. List<Object> as well as raw types (List<List>, List<Collection>, etc.) also works.
  6. Same thing as 4, except you get either List<Number> or List<Object> (and apparently List<Serializable>) as the inner list.

As @MadProgrammer said, due to PECS (Producer-Extends-Consumer-Super), any time you have a ? extends for your generic type you won't be able to update the list, only retrieve items from it. So no add()-ing and no set()-ing.

Fundamentals on Generics

The problem basically is that by declaring v as Vector<? extends SomeClass> you say that it's a collection of SomeClass or one of its subclasses. The compiler cannot make any assumptions about what specific type the container actually hold, even if the assignment to new Vector<SomeClass>() is done on the same line. As much as the compiler knows v might have been created as new Vector<SubClassOfSomeClass>(). That is why it refuses to put SomeClass instances (or anything else for that matter) to the collection. You can however get the SomeClass elements out of it.

If you want to be able to put elements to the collection, then declare v as Vector<? super SomeClass>, by which you say that it's a container of SomeClass or one of its superclasses. The compiler knows then that a SomeClass instance will certainly fit in such a collection.

You cannot however get a SomeClass element out of that collection, as the compiler knows nothing about the type of elements in it (except that it is some superclass of SomeClass). By the definition of v it might as well be just a collection of Objects.

This is called the put/get principle in short which is discussed in detail also in other posts like this.

What is PECS (Producer Extends Consumer Super)?

tl;dr: "PECS" is from the collection's point of view. If you are only pulling items from a generic collection, it is a producer and you should use extends; if you are only stuffing items in, it is a consumer and you should use super. If you do both with the same collection, you shouldn't use either extends or super.


Suppose you have a method that takes as its parameter a collection of things, but you want it to be more flexible than just accepting a Collection<Thing>.

Case 1: You want to go through the collection and do things with each item.

Then the list is a producer, so you should use a Collection<? extends Thing>.

The reasoning is that a Collection<? extends Thing> could hold any subtype of Thing, and thus each element will behave as a Thing when you perform your operation. (You actually cannot add anything (except null) to a Collection<? extends Thing>, because you cannot know at runtime which specific subtype of Thing the collection holds.)

Case 2: You want to add things to the collection.

Then the list is a consumer, so you should use a Collection<? super Thing>.

The reasoning here is that unlike Collection<? extends Thing>, Collection<? super Thing> can always hold a Thing no matter what the actual parameterized type is. Here you don't care what is already in the list as long as it will allow a Thing to be added; this is what ? super Thing guarantees.



Related Topics



Leave a reply



Submit