Generics in Java, Java generics 的使用
1 Introduction to generics
Generics are a way to tell a compiler what type of object a collection can contain. The idea is to allow type (Integer, String, … etc and user defined types) to be a parameter to methods, classes and interfaces. For example, classes like HashSet, ArrayList, HashMap, etc use generics very well.
Advantages of Generics:
Programs that uses Generics has got many benefits over non-generic code.
-
Code Reuse 代码复用: We can write a method/class/interface once and use for any type we want.
-
Type Safety 类型安全 : Generics make errors to appear compile time than at run time (It’s always better to know problems in your code at compile time rather than making your code fail at run time). Suppose you want to create an ArrayList that store name of students and if by mistake programmer adds an integer object instead of string, compiler allows it. But, when we retrieve this data from ArrayList, it causes problems at runtime.
// A Simple Java program to demonstrate that NOT using
// generics can cause run time exceptions
import java.util.*;
class Test
{
public static void main(String[] args)
{
// Creatinga an ArrayList without any type specified
ArrayList al = new ArrayList();
al.add("Sachin");
al.add("Rahul");
al.add(10); // Compiler allows this
String s1 = (String)al.get(0);
String s2 = (String)al.get(1);
// Causes Runtime Exception
String s3 = (String)al.get(2);
}
}
// Using generics converts run time exceptions into
// compile time exception.
import java.util.*;
class Test
{
public static void main(String[] args)
{
// Creating a an ArrayList with String specified
ArrayList <String> al = new ArrayList<String> ();
al.add("Sachin");
al.add("Rahul");
// Now Compiler doesn't allow this
al.add(10);
String s1 = (String)al.get(0);
String s2 = (String)al.get(1);
String s3 = (String)al.get(2);
}
}
- Individual Type Casting is not needed : If we do not use generics, then, in the above example every-time we retrieve data from ArrayList, we have to typecast it. (即String s1 = (String)al.get(0); ) Typecasting at every retrieval operation is a big headache. If we already know that our list only holds string data then we need not to typecast it every time.
// We don't need to typecast individual members of ArrayList
import java.util.*;
class Test
{
public static void main(String[] args)
{
// Creating a an ArrayList with String specified
ArrayList <String> al = new ArrayList<String> ();
al.add("Sachin");
al.add("Rahul");
// Typecasting is not needed
String s1 = al.get(0);
String s2 = al.get(1);
}
}
- Implementing generic algorithms: By using generics, we can implement algorithms that work on different types of objects and at the same they are type safe too.
2 Generic methods in Java
Generic methods in Java are methods that allow you to create a new type parameter just for that method. This is useful if you are writing a method but want to be flexible about the type of objects you can pass in.
I have an array of characters called charArray. I also have an array of booleans called boolArray, and an array of integers called intArray. Then, I have created a method called arrayToList that iterates through all of the objects in an array and adds them to a list. I want to be able to pass any of these arrays into the method, even though they are of different types. So the first thing I have tried is making the arguments of the array to list method object types.
public class GenericMethods {
static Character[] charArray = {'h', 'e', 'l', 'l', 'o'};
static Integer[] intArray = {1, 2, 3, 4, 5};
static Boolean[] boolArray = {true, false, true};
public static List arrayToList(Object[] array, List<Object> list) {
for (Object object : array) {
list.add(object);
}
return list;
}
public static void main(String[] args) {
List<Character> charList = arrayToList(charArray, new ArrayList<>());
List<Boolean> boolList = arrayToList(boolArray, new ArrayList<>());
List<Integer> intList = arrayToList(intArray, new ArrayList<>());
}
}
Using objects means that I lose type safety. For example, in the main method, if I change the type of intList to string and compile this file, I don’t get any errors.
The solution is to make the array to list method a generic method The first thing I need to do is create a new type variable, which I will call T. This type variable only has local scope.
public class GenericMethods {
static Character[] charArray = {'h', 'e', 'l', 'l', 'o'};
static Integer[] intArray = {1, 2, 3, 4, 5};
static Boolean[] boolArray = {true, false, true};
public static <T> List<T> arrayToList(T[] array, List<T> list) {
for (T object : array) {
list.add(object);
}
return list;
}
public static void main(String[] args) {
List<Character> charList = arrayToList(charArray, new ArrayList<>());
List<Boolean> boolList = arrayToList(boolArray, new ArrayList<>());
List<Integer> intList = arrayToList(intArray, new ArrayList<>());
System.out.println(intList.get(0));
}
}
Using this approach, I can keep the method flexible, but I can discover errors in the code much earlier on.
3 Using vargs in Java
Sometimes you might want to write a method that takes a variable number of arguments. In Java there is a feature called variable-length arguments, or varargs for short, which allows you to do this.
在写printShoppingList() method 的时候,不用让输入必须是列表,比如 printShoppingList(String[] items) , Instead,我们可以用三个点代替 [ ], 这样这个method接受多个variable输入,自动成为一个list.
public class Varargs {
public static void main(String[] args) {
String imem1 = "Apples";
String item2 = "Oranges";
String item3 = "Pears";
printShoppingList(imem1, item2, item3);
printShoppingList("Bread", "Milk", "Eggs", "Bananas");
}
private static void printShoppingList(String... items) {
System.out.println("SHOPPING LIST");
for (int i = 0; i < items.length; i++) {
System.out.println(i + 1 + ": " + items[i]);
}
System.out.println();
}
}
Using a vararg method has made my code shorter and simpler, but it is still easy to read and understand.
4 The substitution principle in Java
he substitution principle, which is also known as the Liskov Substitution Principle, is an important concept in object-oriented programming because it allows you to write maintainable and reusable code.
It just means that if you have a variable of a given type you can assign it to a value that is a subtype of that type.
Because office is a subtype of building, I can pass it into this method. This is the substitution principle.
class Building {
@Override
public String toString() {
return("building");
}
}
class Office extends Building {
@Override
public String toString() {
return ("office");
}
}
public class Main {
public static void main(String[] args) {
Building building = new Building();
Office office = new Office();
build(building);
build(office);
List<Building> buildings = new ArrayList();
buildings.add(new Building());
buildings.add(new Office());
printBuildings(buildings);
List<Office> offices = new ArrayList();
offices.add(new Office());
offices.add(new Office());
printBuildings(offices);
}
static void build(Building building) {
System.out.println("Constructing a new " + building.toString());
}
static void printBuildings(List<Building> buildings) {
for(int i = 0; i < buildings.size(); i++) {
System.out.println(i + 1 + ": " + buildings.get(i).toString());
}
}
}
It is also important to know when the substitution principle does not apply. You might think that because office is a subtype of building that a list of type office is a subtype of a list of type building. However, this isn’t actually the case.
5 Using wildcards in generic programming
A wildcard is essentially an unknown type, and can give you more flexibility when writing methods.
To use a wildcard, I put a question mark and then write extends. (typing) Before the word building. This means that I can now pass in lists of any type that extends the building class.
Wildcards can also be used to specify that super types can be used when a subtype is specified. In this main class, I also have a method called addHouseToList, which takes a list of houses as an argument and adds a new house to it. Currently, I can only pass in a list of houses to this method. But if I wanted to be able to pass in a list of Buildings, I can use wildcards again. This time, I’ve put a question mark and the word super in front of house, where I specify the type of list. Now, I can pass in a list of buildings to this method.
class Building {
public int numberOfRooms = 7;
@Override
public String toString() {
return ("building");
}
public int getNumberOfRooms() {
return numberOfRooms;
}
public void setNumberOfRooms(int numberOfRooms) {
this.numberOfRooms = numberOfRooms;
}
}
/*****************************************/
public class House extends Building {
public int numberOfRooms = 10;
@Override
public String toString() {
return ("house");
}
public int getNumberOfRooms() {
return numberOfRooms;
}
public void setNumberOfRooms(int numberOfRooms) {
this.numberOfRooms = numberOfRooms;
}
}
class Office extends Building {
@Override
public String toString() {
return ("office");
}
}
public class Main {
public static void main(String[] args) {
// List of buildings
List<Building> buildings = new ArrayList();
buildings.add(new Building());
buildings.add(new Building());
printBuildings(buildings);
// List of offices
List<Office> offices = new ArrayList();
offices.add(new Office());
offices.add(new Office());
printBuildings(offices);
// List of houses
List<House> houses = new ArrayList();
houses.add(new House());
houses.add(new House());
printBuildings(houses);
addHouseToList(houses);
addHouseToList(buildings);
}
static void printBuildings(List<? extends Building> buildings) {
for(int i = 0; i < buildings.size(); i++) {
System.out.println(buildings.get(i).toString() + " " + (i + 1));
}
System.out.println();
}
static void addHouseToList(List<? super House> buildings) {
buildings.add(new House());
System.out.println();
}
}
If you are unsure about when to use extends and when to use super with wildcards, you can think about invariables and outvariables. In the printBuildings method, I am passing in a list which provides data which I use inside the method, so this in an invariable. With invariables, it makes sense to use extends. In the addHouseToList method, I am using the method to give additional data by adding to the list. So this is an outvariable. And in this case it makes sense to use super.
Also note that it is generally a bad idea to use wildcards as return types for a method.