Generics in Java, Java generics 的使用

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.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值