2

In Chapter 8 of Generic Types from Core Java Volume I Edition 10,

NOTE: Another common use for supertype bounds is an argument type of a functional interface. For example, the Collection interface has a method

default boolean removeIf(Predicate<? super E> filter)

The method removes all elements that fulfill the given predicate. For example, if you hate employees with odd hash codes, you can remove them like this:

ArrayList<Employee> staff = . . .; Predicate<Object> oddHashCode = obj -> obj.hashCode() %2 != 0; staff.removeIf(oddHashCode);

You want to be able to pass a Predicate<Object>, not just a Predicate<Employee>. The super wildcard makes that possible.

I met some problems when trying to understand this, so <? super E> means that filter could point to any predicate type that can be superclass of Employee or Employee itself.

The text above mentioned we could pass a Predicate<Object> to Predicate<? super E>.

But what if Predicate<? super E> points to Predicate<Employee>, can Predicate<Object> be passed to Predicate<Employee>?

Did I misunderstand something?

Holmes Queen
  • 292
  • 1
  • 4
  • 16

1 Answers1

2

Your understanding is correct. For example, a function that takes Predicate<? super Employee> (as in your ArrayList<Employee> example) can also accept a Predicate<Object> like Objects::nonNull. Conceptually, this makes sense for the following reason: "We (the class) take a Predicate that operates on us, or on any of the (transitive) superclasses of ourself, because we have an is-a relationship with those superclasses." That is to say, a function that takes any Object and returns a boolean is equivalently applicable to Employee because Employee is-a Object, and so it is valid to apply this function to Employee. The derived class is not the exact same as the base class, but the predicate (logical test) still applies to the derived class, because it makes sense to talk about the derived class as-a base class.

Let's go through an example: Employees could be derived from Person. If Person can be tested with a Predicate<Person> called hasJob, then it is logically sound to be able to test Employees for hasJob as well. The ability of that function to take Predicate<? super Employee> instead of just Predicate<Employee> is required to maintain the ability of the function to take a logically sound predicate. On the other hand, if you only expect for Employees to be tested for some property, you might accept only a Predicate<Employee> instead, because that corresponds to the logical soundness of only Employee and its derived classes possessing the ability to be tested for that property.

To be 100% clear about what is going on here:

  • Predicate<? super Employee> accepts Predicate that tests Employee and any superclass of Employee, including Object
  • Predicate<Employee> accepts Predicate that tests Employee and any subclass of Employee, which excludes Object

Given this class hierarchy: SalariedEmployee is-a Employee is-a Person, here's what happens (P is shorthand for Predicate):

╔══════════════════╦═══════════╦═══════════════════╦═════════════╦═════════════════════╦═════════════════════╦═════════════════════════════╗
║       Type       ║ P<Person> ║ P<? super Person> ║ P<Employee> ║ P<? super Employee> ║ P<SalariedEmployee> ║ P<? super SalariedEmployee> ║
╠══════════════════╬═══════════╬═══════════════════╬═════════════╬═════════════════════╬═════════════════════╬═════════════════════════════╣
║ Person           ║ Accept    ║ Accept            ║ Reject      ║ Accept              ║ Reject              ║ Accept                      ║
║ Employee         ║ Accept    ║ Reject            ║ Accept      ║ Accept              ║ Reject              ║ Accept                      ║
║ SalariedEmployee ║ Accept    ║ Reject            ║ Accept      ║ Reject              ║ Accept              ║ Accept                      ║
║ Object           ║ Reject    ║ Accept            ║ Reject      ║ Accept              ║ Reject              ║ Accept                      ║
╚══════════════════╩═══════════╩═══════════════════╩═════════════╩═════════════════════╩═════════════════════╩═════════════════════════════╝

Note that Accept/Reject denote the types that can be fed into the Predicate, not the actual result of the Predicate.

Avi
  • 2,359
  • 1
  • 9
  • 21
  • From https://stackoverflow.com/a/4343547/10661805 , in "Consumer Super" situation, it said we can only add `Employee` or subclass of `Employee` to the writing method (I supposed writing method is any method that consumes argument) of a ` super T>` type. So I am quite confused when in fact we can add supertype of T into the method. I think I have messed up the idea. – Holmes Queen Oct 03 '19 at 14:27
  • @HolmesQueen Remember, in `removeIf`, you're consuming a `Predicate`, not producing anything. Therefore, you use `? super E`. You don't know what type the `Predicate` will take, except that it must take `E`. This is equivalent to saying that the `Predicate` will take `E` or any superclass of `E`, which is exactly what `Predicate super E>` means. – Avi Oct 03 '19 at 14:36
  • Writing is **NOT** consuming!!! Writing is *producing* values, reading/processing is *consuming* values! – Avi Oct 03 '19 at 14:38
  • 1
    Take a look at https://stackoverflow.com/a/52468735/10661805, so we are actually just assigning the values without even writing them? If we have an adding method, does this statement (`Predicate super Employee>` accepts `Predicate` that `test`s `Employee` and *any superclass* of `Employee`, **including** `Object`) still work? – Holmes Queen Oct 03 '19 at 15:03
  • Well, we're not really assigning, except in the sense that the value of `Predicate super Employee>` provided to the `removeIf` method is assigned to some inner variable within the method. The "assigning" within the answer you've linked indicates which types of `List` can be set to the wildcard `List`, whereas the get/add columns refer to the types that can be used as parameters/return types from those methods. Do recall that all lists are parameterized with an `E` (`List`), so the `get` method returns an `E` and `add` method takes an `E`. – Avi Oct 03 '19 at 15:11
  • Talking about `add`/`get` doesn't really make sense in the context of the generic parameter for `removeIf`, because `removeIf` takes a predicate on `E` and its superclasses, whereas `get` returns `E` (that can be assigned to any superclass of `E` as per normal reference assignment rules) and `add` takes `E` (which is any subclass of `E` as per the same reference assignment rules`). – Avi Oct 03 '19 at 15:14
  • Refer to the link again, so if we have `List super A>`, we could just add A and its subclasses but not its superclass like `Object`. I still don't understand why we can add an `Object` type into ` super T>`. Does the `add` method in the link reflect the problems I met or actually these two things are completely different stuff? After viewing your answer, so I just assume it's like an assignment operation (which is not really assigning according to you) and it's definitely not a `get/add` method. – Holmes Queen Oct 03 '19 at 15:21
  • 1
    If you check the top answer in that question, you'll see that you can't add an `Object` type into ` super T>`. The reason given is: `You can't add any object to List extends T> because you can't guarantee what kind of List it is really pointing to, so you can't guarantee that the object is allowed in that List. The only "guarantee" is that you can only read from it and you'll get a T or subclass of T.` And yes, `removeIf` is nothing like an `add/get` method. With regards to inserting `Object` into ` super T>`, you can add instances of `T` or its subclasses, but NOT its superclasses. – Avi Oct 03 '19 at 15:39
  • That is to say, inserting `Object` into a `List super T>` only works if you have `T` as a superclass/same class of `Object` (inserting `Object` into `List super Object>` will work, inserting `Object` into `List super Employee>` will not work). – Avi Oct 03 '19 at 15:46
  • Yeah, but I should never mix the `Predicate` scenario with inserting/wrtiting method, right? It's merely an assignment but not an inserting/writing process. If so, that really clears my doubt. – Holmes Queen Oct 03 '19 at 15:52
  • Well, it can get complicated. What if you have a `List>`? Then `removeIf` would be `boolean removeIf(Predicate super Predicate> filter)` as its method signature, `add` would be `boolean add(Predicate e)`, and `get` would be `Predicate get(int index)`. You can see how the `Predicate` is nested in `removeIf`, because it's a predicate on the element type of the `List`. In any case, you shouldn't be worrying about complicated generics most of the time, since the standard library handles a *lot* of the complexities out of the box. – Avi Oct 03 '19 at 15:57