2

Hopefully, after understanding generic boundaries, i'm trying to understand wildcard upper and lower bounds. my reference is: https://docs.oracle.com/javase/tutorial/java/generics/wildcards.html I found there a sentence i can understand: "The wildcard can be used in a variety of situations: as the type of a parameter, field, or local variable;" Fields and local variable? can't imagine it. Why such an important source doesn't emphasize it by a simple example?

I'm trying to understand with which reference java compiler replaces (while erasure) '?'. Maybe i have a big misunderstanding and any erasure happens (So all the following examples are not relevant). In the following examples: 1.

public static void funcA (List<? extends Number>l)

2.

public static void funcB(List<? super Integer>l)

and is there a difference between second example and the following code: 3.

public static <T extends Integer> funcC(List<? extends T>l)

And if there any different between example 2 and the following one: 4.

public static <T extends Integer> void funcC(List<T>l)
Eitanos30
  • 1,089
  • 7
  • 9
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/201499/discussion-between-slaw-and-eitanos30). – Slaw Oct 28 '19 at 14:06
  • *"Can't imagine it"* — Fields: `private List> myList;`, local variables: `void test() { List> myList; }`. – MC Emperor Oct 29 '19 at 19:41

2 Answers2

2

Preface: You've asked a number of questions about type erasure, including in the chat room. While this answer will address this specific question it may answer some of your other questions as well (maybe even some you haven't asked yet).

Warning: This answer is long and the question specifically asked in this post is only directly addressed at the end.


What is type erasure?

This has been covered quite well in other Q&As so I will simply link to them:


What are the rules of type erasure?

The rules of type erasure are specified in §4.6 Type Erasure of the Java Language Specification (JLS):

Type erasure is a mapping from types (possibly including parameterized types and type variables) to types (that are never parameterized types or type variables). We write |T| for the erasure of type T. The erasure mapping is defined as follows:

  • The erasure of a parameterized type (§4.5) G<T1,...,Tn> is |G|.

  • The erasure of a nested type T.C is |T|.C.

  • The erasure of an array type T[] is |T|[].

  • The erasure of a type variable (§4.4) is the erasure of its leftmost bound.

  • The erasure of every other type is the type itself.

Type erasure also maps the signature (§8.4.2) of a constructor or method to a signature that has no parameterized types or type variables. The erasure of a constructor or method signature s is a signature consisting of the same name as s and the erasures of all the formal parameter types given in s.

The return type of a method (§8.4.5) and the type parameters of a generic method or constructor (§8.4.4, §8.8.4) also undergo erasure if the method or constructor's signature is erased.

The erasure of the signature of a generic method has no type parameters.

For this answer we should focus on the first and fourth bullet points which say:

The erasure of a parameterized type (§4.5) G<T1,...,Tn> is |G|.

And:

The erasure of a type variable (§4.4) is the erasure of its leftmost bound.

Respectively. In particular, notice how the fourth bullet point only explains the erasure of type variables. Why does this matter? I'll come back to that.

Terminology

Understanding the terms is important to understanding how the rules are applied:

  • Generic class

  • Generic interface

  • Generic method

    • Specified in §8.4.4 Generic Methods of the JLS.
    • A generic method is a method which declares one or more type variables.
  • Generic constructor

  • Type variable

    • Specified in §4.4 Type Variables of the JLS.
    • A type variable is introduced by a type parameter declared on the generic member.
    • The syntax of a type variable is: {Annotation} TypeIdentifier
  • Type parameter

    • Specified in many sections of the JLS. Every section I linked to for the above terms mentions type parameters.
    • A type parameter is the declaration of a type variable, plus its bounds, on a generic member.
    • The syntax of a type parameter is: {TypeParameterModifier} TypeIdentifier [TypeBound] where TypeParameterModifier expands into Annotation.
  • Parameterized type

    • Specified in §4.5 Parameterized Types of the JLS.
    • A parameterized type is the actual use of a generic class or generic interface with type arguments.
  • Type argument

Note that the difference between type parameter and type argument is similar to how Java differentiates between a method parameter and argument. When you declare a method as void bar(Object obj) the Object obj is a parameter. However, when you call the method like bar(someObjInstance) the value of someObjInstance is the argument.

Code Examples of the Terminology

Seeing some examples in code can help understand what parts of the code each term applies to.

A generic class with type parameters

public class Foo<T extends CharSequence, U> {
    // class body...
}

There are two type parameters:

  1. T extends Charsequence

    • The type variable is T.
    • The type bounds are extends CharSequence
  2. U

    • The type variable is U
    • The type bounds are extends Object (implicitly defined)

The code looks similar for generic interfaces.

A generic method with a type parameter

public void <V extends Number> bar(V obj) {
   // method body...
}

This method has one type parameter:

  1. V extends Number
    • The type variable is V.
    • The type bounds are extends Number.

The code looks similar for generic constructors.

A parameterized type (no wildcard)

public <E extends Number> void bar(List<E> list) {
    // method body...
}

There is one parameterized type:

  1. List<E>
    • The type argument is E.

A parameterized type (with wildcard)

public void bar(List<? extends Number> list) {
    // method body...
}

There is one parameterized type:

  1. List<? extends Number>
    • The type argument is ? extends Number

Back to the Rules

As I mentioned, it's important to note that the rules only mention the erasure of type variables. The reason this is important is because wildcards are not allowed in the places where type variables can be defined (i.e. in type parameters). A wildcard can only be used in a type argument which is a part of a parameterized type.

The erasure of a parameterized type is simply the raw type.


When Does Type Erasure Matter?

In everyday development of generic code type erasure is virtually irrelevant. One of the only times you have to know how type erasure works in some detail is when working with raw types. In an ideal and just world you'll only work with raw types when working with legacy code (from the days before Java 5) that you cannot change. In other words, unless you're forced to work with raw types you should always appropriately use generics.

However, maybe you're forced to work with raw types or you're simply curious. In that case, you need to know how type erasure works because the erasure determines what types are used. Here's an example of a generic class:

public class Foo<T extend CharSequence, U> {

    private List<T> listField;
    private U objField;

    public void bar(List<? extends T> listParam) {
        // method body...
    }

    public U baz(T objParam) {
        // method body...
    }

    public <V extends Number> V qux(V objParam) {
        // method body...
    }

}

And following the aforementioned rules of type erasure, here's what the above class looks like afterwards:

// the raw type of Foo
public class Foo {

    private List listField;
    private Object objField;

    public void bar(List listParam) {
        // method body...
    }

    public Object baz(CharSequence objParam) {
        // method body...
    }

    public Number qux(Number objParam) {
        // method body...
    }

}

But again, you'll only need to know about the latter version when you're using raw types.


Applying This Knowledge to Your Question

Some of what we've learned so far is that wildcards can only be used in type arguments and are thus only applicable to parameterized types. The erasure of a parameterized type is simply the raw type. If we apply this knowledge to your examples you get the following:

  1. Example #1

    • Original

      public static void funcA(List<? extends Number> l)
      
    • Erased

      public static void funcA(List l)
      
  2. Example #2

    • Original

      public static void funcB(List<? super Integer> l)
      
    • Erased

      public static void funcB(List l) 
      
  3. Example #3

    • Original (forgot to specify return type in question, assuming void)

      public static <T extends Integer> void funcC(List<? extends T> l)
      
    • Erased

      public static void funcC(List l)
      
  4. Example #4

    • Original

      public static <T extends Integer> void funcC(List<T> l)
      
    • Erased

      public static void funcC(List l)
      

Reinforcing the Point

To really point out the difference between the erasure of a type variable and the erasure of a parameterized type let's look at another example.

public class Foo<T extends Number> {

    public void bar(T obj) {
        // method body...
    }

    public void baz(List<? extends T> list) {
        // method body...
    }

}

The method bar has a single parameter of type T. The parameter is using the type variable directly. The erasure of a type variable is the erasure of its leftmost bound which is Number in this case. This means after erasure the method's parameter is Number.

The method baz has a single parameter of type List<? extends T>. Here, the type variable T is being used as the upper bound in the type argument of a parameterized type. In other words, despite a type variable being used, the erasure actually being used here is that of a parameterized type. This means after erasure the method's parameter is just List. This would happen even if the type argument was a lower-bounded wildcard (e.g. List<? super T>), an unbounded wildcard (e.g. List<?>), or even a non-wildcard (e.g. List<T>).

How Wildcards are Handled by Type Erasure

To directly answer your question on how type erasure treats wildcards, the answer is effectively: It doesn't, not directly. Wildcards simply disappear (when the parameterized type is erased) and have no significance in the resulting raw type.

Wildcards enable flexibility for those using the generic API. Here are some Q&As which address that concept:

Hopefully those Q&As also help answer your auxiliary question, which is what are the differences between the second and fourth examples from your post.

Slaw
  • 25,955
  • 5
  • 33
  • 58
0

Example 2 is saying that you can pass List<? super Integer>, so a List<Integer> is ok, but also List<Number> or List<Object>.

The next example however says you can pass List<? extends T> where <T extends Integer>. So really you can only pass List<Integer> and if they existed any subclass of Integer (which don't exist because Integer is final).

As you can see they are quite the opposite of each other.

The last example is sort of similar. You are not specifically using T anywhere else, so it is resolving to the same thing. This would be more useful if you needed T as an extra parameter or part of the return value.

Finally don't get confused about the role of type erasure. The ? is a generic type wildcard, nothing related to type erasure.

Type erasure is just the fact that at runtime generic types do not exist during execution. Generics only enforce compile time checks. After compilation you will just have a plain List.

jbx
  • 18,832
  • 14
  • 73
  • 129
  • i need time to understand your response. But two days ago i asked a question and according the response their, it seems that erasure exist also in compile time (changing method signature): _https://stackoverflow.com/questions/58549511/how-erasure-of-generics-replace-multiple-bounds/58549819?noredirect=1#comment103421693_58549819_ – Eitanos30 Oct 26 '19 at 23:38
  • Well yes, after compilation generics do not exist. That is what I meant by it only occurs at runtime. Generics are only a compile time check to reduce mistakes. After compilation every `List` becomes just `List` etc. – jbx Oct 27 '19 at 21:24
  • @jbx the fact that `List` is erased to `List` _does_ not mean that generics to do exist after compilation. [here](https://stackoverflow.com/a/57102366/1059372) – Eugene Oct 31 '19 at 02:54
  • @Eugene that is precisely what i said. "after compilation generics do not exist" Read my comments! – jbx Oct 31 '19 at 08:58
  • @jbx well in my understanding _Type erasure is just the fact that at runtime generic types do not exist during execution_ means that generics completely do not exist at runtime - and they do, if you would also read my linked answer. This is just nitpicking, I guess, because everyone thinks that they don't exist, at all. – Eugene Oct 31 '19 at 12:56