2016.11.17 Java Generics - GlenKPeterson/One-off_Examples GitHub Wiki

If I could learn about Generics all over again, I'd want to start by defining the limits then proceed to what you can do with them. This post attempts to do that. Mathematically, a basic understanding of sets and Venn diagrams are all you need for this article. This document is extremely incomplete and will grow over time. Constructive criticism greatly appreciated!

Acknowledgements

Angelika Langar has probably the most understandable introduction in her GenericsFAQ. I referred to that more than any other source while learning about Generics. Bloch's "Effective Java" has amazing advice regarding generics, but it's far from a complete resource. Wadler/Naftalin's "Java Generics and Collections" covers some concepts and applications very well. I think everything you want to know is in there, but it's not particularly well organized for the beginner.

Note: I need to read http://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.4

Conventions used in this post

In these examples I'm generally going to use a capital 'T' as the type parameter. This can be any valid Java Identifier ('MyDarnType' is perfectly valid), but single capital letters are most commonly used because types are extremely general, like variables in general mathematical equations. They do not represent one thing, the way variables in computer programs do.

Common names for types:

  • 'T' for Type
  • 'U' for when T is the input, U is the output.
  • 'E' for Element (in a collection). Really, this and 'T' are used interchangably, but 'T' is more popular/general.
  • 'K' for Key (in a map)
  • 'V' for Value (in a map)
  • 'X', 'Y' for types that remind people of Cartesian coordinates
  • 'A', 'B', 'C' for when there are lots of parameters or just lots of types
  • 'R' or 'Z' sometimes for return type of generic functional interfaces.

Type Parameter Declaration Syntax

Type declarations put angle-brackets around the type. This syntax for general type usage is identical to the declaration syntax. Only the context in which the angle brackets are used tells you whether you're looking at a declaration or a usage.

Type declaration can occur in only two, very specific places, so learn to recognize them! Ellipses in the following examples mean "content truncated", not Java varargs.

1. In the definition of a class or interface:

class MyClass<T> ...
//           ^^^- Type declaration

interface MyInterface<T,U> ...
//                   ^^^^^- Declaring 2 types 

2. Before a method

Before any static method, or before an instance method that does not override a method in a super-class or interface. This includes constructors (not sure there is a good reason to ever declare a new type on a constructor, but it can be done).

// The declared type must then be used as a parameter of that method...
static <T> void foo(T t...
//     ^^^

// or as the return type:
public <T> T foo(...
//     ^^^

That's it. Types declared elsewhere can be used in all kinds of places, but the initial declaration spots are extremely limited.

Types do NOT work the same way with Arrays or Enums (or Exceptions). Varargs are arrays, so generics are extra tricky there. Those radical exceptions to the rules and will be covered later.

Thanks to the folks at SoftwareEngineering.StackExchange.com for their help with this secton.

Scope

There are separate type scopes for Instances/Objects and for Static/Classes. They do not ever mix. Yes, you declare static and non-static everything right next to each other, but types see them as entirely separate.

Instance/Object

If you declare a type variable on a class or interface, it is visible to all non-static inner classes and interfaces, but not to any static classes, functions, or fields. You can override it with another parameter of the same name on an inner class or interface, or just leave it off. While leaving it off is briefer, and can enable type inference in some cases that would be awkward otherwise, it's one of those syntax choices that doesn't work the same way as the rest of the language. The compiler secretly puts the type back in which can can lead to truly bizarre error messages if you don't realize what's going on. My advice: Redefine types from outer classes/interfaces on the inner classes/interfaces. That way the rules don't change.

Static/Class

Declared on a static class, interface, or field is not visible to any non-static classes, functions, or fields. In fact, type variables on statics are even shared with other statics in the same file! Well, the only exception is if you nest static classes/interfaces, in which the inner ones can see the outer generics.

#Type Erasure The generic types described here are available only to the compiler. For backward compatibility, the decision was made not to save that information in any way that's usable at run-time. This is called "Type Erasure" and is a feature of Java that's hotly debated.

I've been able to work around type erasure, but I wish the designers had given each generic object an array of types that is created automatically by the compiler and available at runtime. Class<T> tType = this.types[T]; would be sweet! Aside from the lone pointer (to the type array) on each object, there would be very little overhead. At runtime, the JVM could store all type arrays in something like a hash set which eliminates duplicates.

#Wildcards bounds and variance

⚠️ **GitHub.com Fallback** ⚠️