10.6. Generic Methods

The general layout for a generic method in Java is below. Note: Anything enclosed in [ ] is optional. Also, omitting the visibility modifier will make the method package-private.

General layout for a generic method in Java
[visibility] [static] <PlaceholderType> ReturnType methodName([params])
  • <PlaceholderType> denotes the type parameter(s), i.e., the reference types that are replaced when the method is invoked. Regarding type parameters (placeholders):

    • Enclosed in angle brackets: <, >.

    • Multiple placeholders are allowed. In that scenario, the types should be comma-separated, e.g., <T, U>.

    • The placeholder type can, but is not required to be, the return type of the method.

    • The placeholder type can, but is not required to be, the type for any of the method’s parameters.

    • The placeholder type can, but is not required to be, the type for any of the method’s local variables.

In the examples below, don’t focus on the specifics of what each method does. Instead, focus on the data types being used and how they are compatible with the given type parameters, return types, and formal parameters.

10.6.1. Example with generic return-type only

public static <T> T foo(int a, int b)

When we call foo, we can replace T with any reference type. For example, if we assume foo is defined in a class called Utility, the following calls would be valid:

1Drone d = Utility.<Drone>foo(7, 4);
2Camera c = Utility.<Camera>foo(18, 24);

On line 1, for example, the return type will be Drone since we passed in Drone as our type parameter (to replace T).

10.6.2. Example with a generic formal parameter

public static <T> int foo(String a, T b)

We will again assume that the foo method is in the Utility class. Now, when we specify the type parameter, it will impact the method’s expectations about the datatype of b. If the type of b and the type parameter are not compatible, the code will not compile (which is better than it crashing at runtime).

Drone d = new Drone(147.5);

// The next line compiles and runs
Utility.<Drone>foo("hello", d);

// Does not compile. foo expects ``b`` to be of type Camera
Utility.<Camera>foo("hello", d);

Note

When trying to figure out if a particular line of code will compile, it sometimes helps to rewrite the method signature and plug in the value of the type parameter in place of the type argument. Taking the two calls to foo above as examples, the first has a type parameter of Drone. If we replace the type argument T in the original method signature, we see that the new signature would be:

public static int foo(String a, Drone b)

This is how Java is viewing the method for that particular call. When our driver program calls the method with Utility.<Drone>foo("hello", d), we can now see that the method is expecting a Drone as the second parameter which matches what we provided.

However, in the second call to foo above, the type parameter is Camera, so the method signature becomes:

public static int foo(String a, Camera b)

Then, we call it using: Utility.<Camera>foo("hello", d); which passes a Drone as the second parameter (when foo expects a Camera). These are incompatible and will therefore not compile!

Test Yourself

Will the following lines compile? Asuume that Shape is an abstract parent class for both Ellipse and Rectangle and that Ellipse is a parent of Circle and Rectangle is a parent of Square. You should also assume we have access to all of those classes.

1Utility.<String>foo("hello", "world");
2Utility.<Integer>foo("hello", 7);
3Utility.<String>foo("hello", 7.4);
4Utility.<Shape>foo("hello", new Circle(2.4));
5Utility.<Circle>foo("hello", new Shape());
6Utility.<Circle>foo("hello", new Rectangle(3.89));
Test Yourself Solutions (open after attempting the questions above)

Here are the answers for each line in the code above:

  1. Yes. The method expects b to be a String which is what we provide.

  2. Yes. The method expects b to be an Integer which is what we provide.

  3. No. The method expects b to be a a String but we provide a Double.

  4. Yes. The method expects a Shape and we provide a Circle. Those are compatible.

  5. No. The Shape class cannot be instantiated. Also, the method expects a Circle and we attempt to give it a Shape. Those types are not compatible.

  6. No. The method expects a Circle but we provide a Rectangle. Those are not compatible types.

10.6.3. Example with a generic return-type and generic parameter

public static <T> T foo(String a, T b)

We will again assume that the foo method is in the Utility class. Now, when we specify the type parameter, it will impact the method’s expectations about the datatype of b and also serve as the return type of the method.

// The next line compiles and runs
Drone d = new Drone(147.5);

Drone d2 = Utility.<Drone>foo("hello", d);

// Does not compile. foo expects b to be of type Camera
Camera c = Utility.<Camera>foo("hello", d);

Test Yourself

Will the following lines compile? Asuume that Shape is an abstract parent class for both Ellipse and Rectangle and that Ellipse is a parent of Circle and Rectangle is a parent of Square. You should also assume we have access to all of those classes.

1String s = Utility.<String>foo("hello", "world");
2String s2 = Utility.<String>foo("hello", 7.4);
3
4Shape sh = Utility.<Shape>foo("hello", new Circle(4.29));
5Circle c = Utility.<Shape>foo("hello", new Circle(2.4));
6Rectangle r = Utility.<Rectangle>foo("hello", new Square(3.4));
Test Yourself Solutions (open after attempting the questions above)

Here are the answers for each line in the code above:

  1. Yes. The method expects b to be a String and returns a String. Everything is compatible.

  2. No. The method expects b to be a String but we provide a floating point number.

  3. Yes. The method expects b to be a Shape and we provide a compatible Circle. Also, the return type is Shape and we store that value in a Shape variable.

  4. No. The method expects a Shape and we provide a Circle. Those are compatible. However, the method returns a reference to a Shape which cannot be assigned to a Circle variable (remember, not all shapes are circles).

  5. Yes. The method expects a Rectangle and we provide a Square. Those are compatible. The return type also matches the type returned.

10.6.4. Example of a non-static generic method

public <T> T foo(String a, T b)

If we still assume that the foo method is in the Utility class, we now need an object of type Utility to call the method. The other aspects of the method remain the same. A valid call would look like this:

Utility util = new Utility();
String s = util.<String>foo("Hello", "World");

The examples where foo was static hold here as well. The only change is in how the method is called.

10.6.5. Example with a non-static generic method in a generic class

public class SomeClass<T> {

   public <R> T foo(T a, R b) {
      ...
   } // foo

} // someClass

This is likely the most complicated combination of generic classes and generic methods you would see in this course. In the example above, the class has its own placeholder type T, which can be used throughout the class in any non-static method. The foo method is non-static and has its own placeholder type R. You might see something like this:

SomeClass<Integer> sc = new SomeClass<Integer>();
Integer int1 = sc.<String>foo(12, "help");
Integer int2 = sc.foo(12, "help"); // R = String
Rapid Fire Review
  1. Which symbol is used to denote the type parameter(s) in a generic method?

    1. Parentheses ()

    2. Square brackets []

    3. Curly braces {}

    4. Angle brackets <>

  2. Where is the type parameter declared in a generic method?

    1. After the method name

    2. Before the method return type

    3. Inside the method body

    4. After the method parameters

  3. What happens if the type of a parameter is incompatible with the type argument in a generic method?

    1. The program will crash at runtime.

    2. The program will run with a warning.

    3. The program will fail to compile.

    4. Both (a) and (b)

  4. In the following method signature, what is T?

    public static <T> T foo(int a, int b)
    
    1. A placeholder for a reference type

    2. A return type fixed to int

    3. A parameter name for a formal parameter

    4. A local variable declared in the method

Review - Compatibility Questions

For this set of questions, you can safely assume that foo is in the Utility class and that you have access to the reference types used in the code.

  1. Which of the following calls to the foo method will successfully compile given this method signature?

    public static <T> T foo(int a, T b)
    
    1. Camera c = Utility.<Camera>foo(7, new Drone(175.0));

    2. Integer i = Utility.<Integer>foo(7, "hello");

    3. Drone d = Utility.<Drone>foo(7, new Drone(175.0));

    4. Shape s = Utility.<Shape>foo(7, "hello");

  2. Which of the following calls to the foo method will successfully compile given this method signature?

    public static <T> T foo(T a, T b)
    
    1. Camera c = Utility.<Camera>foo(new Camera(128.0), new Drone(175.0));

    2. String s = Utility.<String>foo("hello", "world");

    3. Integer i = Utility.<Integer>foo("hello", 7);

    4. Circle c = Utility.<Shape>foo("hello", new Circle(2.4));

  3. Which of the following calls to the foo method will successfully compile given this method signature?

    public <T> int foo(String a, T b)
    
    1. int x = util.<Drone>foo("Amazeon", new Drone(175.0));

    2. Circle c = util.<Circle>foo("hello", new Rectangle(4.0));

    3. Integer i = util.<Integer>foo("hi", 5.6);

    4. Camera z = util.<Camera>foo("world", "camera");

Compatibility Question Solutions (Do not open until attempting questions above)