Generics in Java
Source: Wikipedia

Generics in Java

Introduction

Generics is the most important feature and was first introduced in Java 5. It enable types (classes and interfaces) to be parameters when defining classes, interfaces and methods.

Generics helps us to write algorithm independent of any specific type of data. For example, It would be difficult to write sorting algorithm to all types (Integer, Double, String, and User-defined types)

Generics also provides type safety. A Java compiler applies strong type checking to generic code and issues errors if the code violates type safety. Fixing compile-time errors is easier than fixing runtime errors, which can be difficult to find. Thus helps the developer to reuse the code safely and easily.

'<>' is used to specify the type parameters

Syntax:

CLASS_NAME<TYPE> reference = new CLASS_NAME<TYPE>();

Example:

List<String> users = new ArrayList<>();


Map<String, Employee> employees = new HashMap<>();

Generic Types

A generic type is a generic class or interface that is parameterized over types.

Type Parameter Naming Conventions

By convention, type parameter names are single, uppercase letters. This stands in sharp contrast to the variable naming conventions that you already know about, and with good reason: Without this convention, it would be difficult to tell the difference between a type variable and an ordinary class or interface name.

The most commonly used type parameter names are:

E - Element (used extensively by the Java Collections Framework)

K - Key

N - Number

T - Type

V - Value

S,U,V etc. - 2nd, 3rd, 4th types

You'll see these names used throughout the Java SE API

Generic class

Generic class are just like normal class but it contains type parameter section. 

public class GenericClass<T> {


    private T t;


    public void set(T t) {
        this.t = t;
    }


    public T get() {
        return t;
    }


}


public class Main {


    public static void main(String[] args) {
        GenericClass<String> ref1 = new GenericClass<>();
        ref1.set("Hello World!");
        System.out.println("GenericClass<String> : " + ref1.get());


        GenericClass<Integer> ref2 = new GenericClass<>();
        ref2.set(114);
        System.out.println("GenericClass<Integer> : " + ref2.get());


        GenericClass<Double> ref3 = new GenericClass<>();
        ref3.set(8.78);
        System.out.println("GenericClass<Double> : " + ref3.get());


        GenericClass<Character> ref4 = new GenericClass<>();
        ref4.set('A');
        System.out.println("GenericClass<Character> : " + ref4.get());
    }


}




Output:
GenericClass<String> : Hello World!
GenericClass<Integer> : 114
GenericClass<Double> : 8.78
GenericClass<Character> : A

A generic class can have multiple type parameters. In the following example, class specifies 3 type parameters

public class GenericClass<T, S, U> {


    private T key;
    private S value;
    private U code;


    public GenericClass(T key, S value, U code) {
        this.key = key;
        this.value = value;
        this.code = code;
    }


    @Override
    public String toString() {
        return "GenericClass{" +
                "key=" + key +
                ", value=" + value +
                ", code=" + code +
                '}';
    }
}


public class Main {


    public static void main(String[] args) {
        GenericClass<String, Double, Integer> ref1 =
                new GenericClass<>("Ami", 87.17, 114);
        System.out.println(ref1);


        GenericClass<Integer, String, Character> ref2 =
                new GenericClass<>(11, "Naruto", 'N');
        System.out.println(ref2);


        GenericClass<String, String, String> ref3 =
                new GenericClass<>("Edward", "Alchemist", "SA01");
        System.out.println(ref3);
    }


}


Output:
GenericClass{key=Ami, value=87.17, code=114}
GenericClass{key=11, value=Naruto, code=N}
GenericClass{key=Edward, value=Alchemist, code=SA01}

Generic Methods

Generic methods can be called with arguments of different types. The scope of the arguments is limited to method where it is declared.

public class GenericMethod {


    public static <T> void print(T value) {
        System.out.println(value);
    }


    public static void main(String[] args) {
        print(114);
        print(87.8);
        print("Ami");
        print('A');
        print(true);
    }


}


Output:
114
87.8
Ami
A
true

Wildcards

In generic code, the question mark (?), called the wildcard, represents an unknown type. The wildcard can be used in a variety of situations: as the type of a parameter, field, or local variable; sometimes as a return type (though it is better programming practice to be more specific). The wildcard is never used as a type argument for a generic method invocation, a generic class instance creation, or a supertype.

Unbounded Wildcards

The unbounded wildcard type is specified using the wildcard character (?), for example, List<?>. This is called a list of unknown type.

There are two scenarios where an unbounded wildcard is a useful approach:

  1. If you are writing a method that can be implemented using functionality provided in the Object class.
  2. When the code is using methods in the generic class that don't depend on the type parameter.
public class UnboundedWildcard {


    public static void printList(List<?> list) {
        for (Object el : list) {
            System.out.print(el + "\t");
        }
        System.out.println();
    }


    public static void main(String[] args) {
        List<Integer> integerList = List.of(1, 2, 3, 4, 5);
        printList(integerList);


        List<Double> doubleList = List.of(1.1, 2.2, 3.3, 4.4, 5.5);
        printList(doubleList);


        List<String> strList = List.of("Ami", "Luffy", "Nami", "Sanji", "Zoro");
        printList(strList);


        List<Character> charList = List.of('K', 'I', 'N', 'G');
        printList(charList);
    }


}


Output:
1	2	3	4	5	
1.1	2.2	3.3	4.4	5.5	
Ami	Luffy	Nami	Sanji	Zoro	
K	I	N	G

Bounded Wildcards

Consider a simple drawing application that can draw shapes such as rectangles and circles. 

To represent these shapes within the program, you could define a class hierarchy such as this:

public abstract class Shape {
    public abstract void draw(Canvas c);
}


public class Circle extends Shape {
    private int x, y, radius;
    public void draw(Canvas c) {
        ...
    }
}


public class Rectangle extends Shape {
    private int x, y, width, height;
    public void draw(Canvas c) {
        ...
    }
}

These classes can be drawn on a canvas:

public class Canvas {
    public void draw(Shape s) {
        s.draw(this);
   }
}

Any drawing will typically contain a number of shapes. Assuming that they are represented as a list, it would be convenient to have a method in Canvas that draws them all:

public void drawAll(List<Shape> shapes) {
    for (Shape s: shapes) {
        s.draw(this);
   }
}

Now, the type rules say that drawAll() can only be called on lists of exactly Shape: it cannot, for instance, be called on a List<Circle>. That is unfortunate, since all the method does is read shapes from the list, so it could just as well be called on a List<Circle>. What we really want is for the method to accept a list of any kind of shape:

public void drawAll(List<? extends Shape> shapes) {
    ...
}

There is a small but very important difference here: we have replaced the type List<Shape> with List<? extends Shape>. Now drawAll() will accept lists of any subclass of Shape, so we can now call it on a List<Circle> if we want.

List<? extends Shape> is an example of a bounded wildcard. The ? stands for an unknown type, just like the wildcards we saw earlier. However, in this case, we know that this unknown type is in fact a subtype of Shape. (Note: It could be Shape itself, or some subclass; it need not literally extend Shape.) We say that Shape is the upper bound of the wildcard.

Upper Bounded Wildcards

You can use an upper bounded wildcard to relax the restrictions on a variable. 

For example, say you want to write a method that works on List<Integer>, List<Double>, and List<Number>; you can achieve this by using an upper bounded wildcard.

To declare an upper-bounded wildcard, use the wildcard character ('?'), followed by the extends keyword, followed by its upper bound. 

Note that, in this context, extends is used in a general sense to mean either "extends" (as in classes) or "implements" (as in interfaces).

To write the method that works on lists of Number and the subtypes of Number, such as Integer, Double, and Float, you would specify List<? extends Number>.

The term List<Number> is more restrictive because it matches a list of type Number only, whereas the List<? extends Number> matches a list of type Number or any of its subclasses (i.e., Integer, Double, Float, Byte, Long, Short, etc)

public class UpperBoundedWildcard {


    public static void print(List<? extends Number> list) {
        for (Number n : list) {
            System.out.print(n + "\t");
        }
        System.out.println();
    }


    public static void main(String[] args) {
        List<Integer> integerList = List.of(1, 2, 3, 4, 5);
        print(integerList);


        List<Double> doubleList = List.of(1.1, 2.2, 3.3, 4.4, 5.5);
        print(doubleList);
    }


}


Output:
1	2	3	4	5
1.1	2.2	3.3	4.4	5.5

Lower Bounded Wildcards

A lower bounded wildcard restricts the unknown type to be a specific type or a super type of that type. It is expressed using the wildcard character ('?'), following by the super keyword, followed by its lower bound: <? super A>.

Say you want to write a method that puts Integer objects into a list. To maximize flexibility, you would like the method to work on List<Integer>, List<Number>, and List<Object> — anything that can hold Integer values.

To write the method that works on lists of Integer and the supertypes of Integer, such as Integer, Number, and Object, you would specify List<? super Integer>. 

The term List<Integer> is more restrictive than List<? super Integer> because the former matches a list of type Integer only, whereas the latter matches a list of any type that is a supertype of Integer.

Following example shows the usage of lower bounded wildcard where printList accepts Integer type and its supertypes (i.e., Number, Object)

public class LowerBoundedWildcard {


    public static void printList(List<? super Integer> list) {
        for (Object el : list) {
            System.out.print(el + "\t");
        }
        System.out.println();
    }


    public static void main(String[] args) {
        List<Integer> integerList = List.of(1, 2, 3, 4, 5);
        printList(integerList);


        List<Number> numberList = List.of(11, 12, 13, 14, 15);
        printList(numberList);
    }


}


Output:
1	2	3	4	5	
11	12	13	14	15
You can specify an upper bound for a wildcard, or you can specify a lower bound, but you cannot specify both.


References

Java Generics

More Links

Bitsnqubits blog

Thanks for reading. Please like & share the article.

要查看或添加评论,请登录

社区洞察

其他会员也浏览了