编程知识 cdmana.com

Java advanced feature - generics: the basic usage of generics, how to write 10000 lines of code less

Generics are Java An advanced feature of . stay Mybatis、Hibernate This persistence framework , Generics are everywhere .

However , Generics, after all, are advanced features , Hidden in the underlying code of the framework . We usually write business code , Maybe I've never seen generics before , Not to mention how to use it .

In that case , Let's learn generics step by step .

What are generics

Generics are a special type . You don't have to specify the specific type of parameter at the beginning , Instead, define a type variable first , Determine the specific type of parameter when using it .

It still seems hard to understand . No problem , Let's take a look first , In the absence of generics , How do we do it .

such as , In e-commerce system , There are two types of users , They are ordinary users 、 Merchant users . When the user clicks for details , The system should set some sensitive information to be empty first , Like password Fields like that , And then back to the user .

You can write a general method , Set these sensitive fields to blank ?

You may have thought of , stay Java in , All classes inherit Object. therefore , You wrote the first version .

public class ApplicationV1 {


    //  Leave sensitive fields blank 
    public static Object removeField(Object obj) throws Exception {
        //  Sensitive fields that need to be filtered 
        Set<String> fieldSet = new HashSet<String>();
        fieldSet.add("password");

        //  Get all fields : Then get all the fields of this class 
        Field[] fields = obj.getClass().getDeclaredFields();

        //  Sensitive field set to null 
        for (Field field : fields) {
            if (fieldSet.contains(field.getName())) {
                //  Open field operation permission 
                field.setAccessible(true);
                //  Set empty 
                field.set(obj, null);
            }
        }

        //  Returns the object 
        return obj;
    }

}

In this method , You put Object As an input parameter , Then use reflection to manipulate fields , hold password Set to null . Code in one go , So you write the following test code .

public class ApplicationV1 {

    // ... Omitted code 
    
    public static void main(String[] args) throws Exception {
        //  initialization 
        ShopUser shopUser = new ShopUser(0L, "shopUser", "123456");
        ClientUser clientUser = new ClientUser(0L, "clientUser", "123456");

        //  Output the original information 
        System.out.println(" Before filtration :");
        System.out.println("          " + shopUser);
        System.out.println("          " + clientUser);

        //  filtering 
        shopUser = (ShopUser) removeField(shopUser);
        clientUser = (ClientUser) removeField(clientUser);

        //  Output filtered information 
        System.out.println(" After filtration :");
        System.out.println("          " + shopUser);
        System.out.println("          " + clientUser);
    }
}

 Running results 
 Before filtration :
          ShopUser{id=0, username='shopUser', password='123456'}
          ClientUser{id=0, username='clientUser', password='123456'}
 After filtration :
          ShopUser{id=null, username='shopUser', password='null'}
          ClientUser{id=null, username='clientUser', password='null'}

The results look OK , But it's a pity. , This method doesn't work . The most obvious problem is , Not enough simplicity . This method casts objects , Look at these two lines of test code :

    //  filtering 
    shopUser = (ShopUser) removeField(shopUser);
    clientUser = (ClientUser) removeField(clientUser);

It's the same object , After you filter out sensitive fields , You have to convert the object again . Think about it. , It's at least a universal method , It's going to be used in a lot of places , Of course, the simpler the better .

You think of it again ,Java There is a method overload mechanism , You wrote the second version .

public class ApplicationV2 {
    /**********************  Business methods  ************************/
    public static ShopUser removeField(ShopUser user) throws Exception {
        //  Strong go , And return the object 
        return (ShopUser) remove(user);
    }
    public static ClientUser removeField(ClientUser user) throws Exception {
        //  Strong go , And return the object 
        return (ClientUser) remove(user);
    }

    /**********************  The core approach  ************************/
    //  Leave sensitive fields blank 
    public static Object remove(Object obj) throws Exception {
        //  Sensitive fields that need to be filtered 
        Set<String> fieldSet = new HashSet<String>();
        fieldSet.add("password");

        //  Get all fields : Then get all the fields of this class 
        Field[] fields = obj.getClass().getDeclaredFields();

        //  Sensitive field set to null 
        for (Field field : fields) {
            if (fieldSet.contains(field.getName())) {
                //  Open field operation permission 
                field.setAccessible(true);
                //  Set empty 
                field.set(obj, null);
            }
        }

        //  Returns the object 
        return obj;
    }
}

thus , The problem seems to have been solved again . But here comes the new question , There's a lot of repetition , And if you add a supplier user , Do I have to write another method ? This is a general method , Change the source code all the time , It's not the way .

In the absence of generics , Duplicate code doesn't work , You have to do something meaningless . If you don't force the object , Or write a few more methods .

However ,Java Of 1.5 Version introduces a generic mechanism , Code can be made simpler . Using generics , You wrote a third version .

public class ApplicationV3 {
    //  Leave sensitive fields blank 
    public static <T> T removeField(T obj) throws Exception {
        //  Sensitive fields that need to be filtered 
        Set<String> fieldSet = new HashSet<String>();
        fieldSet.add("password");

        //  Get all fields : Then get all the fields of this class 
        Field[] fields = obj.getClass().getDeclaredFields();

        //  Sensitive field set to null 
        for (Field field : fields) {
            if (fieldSet.contains(field.getName())) {
                //  Open field operation permission 
                field.setAccessible(true);
                //  Set empty 
                field.set(obj, null);
            }
        }

        //  Returns the object 
        return obj;
    }
}

In the third version , You use generics , Don't force objects when calling methods , You don't have to write so many repetitive methods in the source code , The code becomes simpler .

You can go through the code above , You can find , How to use generics : Define type variables <T>、 Using type variables T obj、 Determine the type variable removeField(new ShopUser(0L, "shopUser", "123456")) This is very important , Here, press no table first .

This is generics , You don't have to write the type of parameter in the code , But in use , Then determine the specific type . Using generics , Your code can be simpler 、 Security .

Of course , There are many uses of generics , Namely : Generic classes and interfaces 、 Generic methods 、 wildcard . Next , Let's unlock them one by one ~

Generic classes

When generics are used in classes and interfaces , It's called a generic class 、 Generic interface . The most typical use of this is a variety of collection classes and interfaces , such as ,List、ArrayList wait .

that , How can we use generics on classes ?

First , Define a generic class .

public class IdGen<T> {
    protected T id;

    public Generic(T id) {
        this.id = id;
    }
}

IdGen It's a id Generating classes . In the first line of code ,<T> It's a generic identity , Represents that you have defined a type variable T. Second line of code , I use this type variable , hold id Defined as a generic .

then , In instantiation 、 When we inherit , Specify the specific type .

public class IdGen<T> {
    // .. Omitted code 

    //  By inheritance , Identify generic variables 
    static class User extends IdGen<Integer> {
        public User(Integer id) {
            super(id);
        }
    }

    public static void main(String[] args) {
        //  By instantiating , Identify generic variables 
        IdGen idGen = new IdGen<String>("1");
        System.out.println(idGen);

        User user = new User(1);
        System.out.println(user);
    }
}

The user class inherits IdGen, In the code extends IdGen<Integer> in , It specifies Integer As id Specific types of ; and IdGen When instantiating , In the code new IdGen<String>("1") in , Specify the String As id Specific types of .

Generic methods

Generics can be used not only on classes and interfaces , It can also be used in methods .

such as , How to convert the member variables of a class to Map What about the assembly ?

Now , We can write a generic method .

public class Generic {
    public static <T> Map obj2Map(T obj) throws Exception {
        Map map = new HashMap<>();

        //  Get all fields : adopt  getClass()  Method to get  Class  object , Then get all the fields of this class 
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            //  Open field operation permission 
            field.setAccessible(true);
            //  Set the value 
            map.put(field.getName(), field.get(obj));
        }

        return map;
    }
}

alike ,<T> It's a generic identity , Represents that you have defined a type variable T, In this way .T obj Using type variables T, Define a obj Parameters . Last , When the method is called , Then determine the specific type .

Generic wildcard

Generic wildcards use ? Express , Represents the type of uncertainty , Is an important component of generics .

There is one point that many articles don't mention , You have to remember that !!!

There are three steps to using generics : Define type variables 、 Using type variables 、 Determine the type variable . In the third step , When determining type variables , If you can't define the type variable , In this case, you can use generic wildcards .

In general , We don't need to use generic wildcards , Because you know exactly what type variables are , Look at the following code .

public class Application {

    public static Integer count(List<Integer> list) {
        int total = 0;
        for (Integer number : list) {
            total += number;
        }
        list.add(total);
        return total;
    }

    public static void main(String[] args) {
        //  Do not transfer the specified data , Compiler error 
        List<String> strList = Arrays.asList("0", "1", "2");
        int totalNum = count(strList);

        //  Bypassing compilation , Operation error reporting 
        List strList1 = Arrays.asList("0", "1", "2");
        totalNum = count(strList1);
    }
}

You know very well count() What is the method , So when you write code , It's a direct indication that this is a Integer aggregate . thus , When the method is called , If you don't send the specified data in , You can't compile . Ten thousand steps back , Even if you bypass compilation , The program probably won't work .

therefore , If you know exactly what you're going to do , It's clear that type variables , There's no need to use generic wildcards .

However , In some general methods , Any kind of data can come in , You can't confirm type variables , What to do at this time ?

You can use generic wildcards , So you don't have to confirm type variables , So as to realize some general algorithms .

such as , You have to write a general method , Bring in List Set output to console , So you can do this .

public class Application {
    public static void print(List<?> list) {
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }

    public static void main(String[] args) {
        // Integer  aggregate , Can run 
        List<Integer> intList = Arrays.asList(0, 1, 2);
        print(intList);

        // String  aggregate , Can run 
        List<String> strList = Arrays.asList("0", "1", "2");
        print(strList);
    }
}

List<?> list That means I'm not sure List What kind of collection is it , It could be Integer, It could be String, It could be something else . But I don't care , You just pass one List Gather in , This method will work properly .

This is the generic wildcard . Besides , Some algorithms are also universal , But the scope of application is not so large . such as , Users are divided into : Ordinary users 、 Business users , But users have some special features , None of the other characters . Now , What to do ?

You can set boundaries for generic wildcards , This limits the scope of type variables .

The upper boundary of the generic wildcard

Upper boundary , The range of representative type variables is limited , Only one type can be passed in , Or its subclass . You can see this picture .

 Generic wildcard - Upper boundary

utilize <? extends Class name > The way , You can set the upper boundary of a generic wildcard . You can see this example .

public class TopLine {

    public static void print(List<? extends Number> list) {
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }

    public static void main(String[] args) {
        // Integer  yes  Number  Subclasses of , You can call  print  Method 
        print(new ArrayList<Integer>());

        // String  No  Number  Subclasses of , Can't call  print  Method 
        print(new ArrayList<String>());
    }

}

You want to call print() In the method , Then you can pass in Integer aggregate , because Integer yes Number Subclasses of . but String No Number Subclasses of , So you can't pass in String aggregate .

The lower boundary of generic wildcards

Lower boundary , The range of representative type variables is limited , Only one type can be passed in , Or its parent class . You can see this picture .

 Generic wildcard - Lower boundary

utilize <? super Class name > The way , You can set the upper boundary of a generic wildcard . You can see this example .

public class LowLine {

    public static void print(List<? super Integer> list) {
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }

    public static void main(String[] args) {
        // Number  yes  Integer  Parent class of , You can call  print  Method 
        print(new ArrayList<Number>());

        // Long  No  Integer  Parent class of , Can't call  print  Method 
        // print(new ArrayList<String>());
    }
}

You want to call print() In the method , Then you can pass in Number aggregate , because Number yes Integer Parent class of . but Long No Integer Parent class of , So you can't pass in Long aggregate .

At the end

Generics are a special type , You can use generics in classes 、 Interface 、 On the way , So as to realize some general algorithms .

Besides , There are three steps to using generics : Define type variables 、 Using type variables 、 Determine the type variable .

In the step of determining the type variable , You can use generic wildcards to limit the scope of generics , So as to realize some special algorithms .

版权声明
本文为[JerryWu]所创,转载请带上原文链接,感谢
https://cdmana.com/2020/12/20201224140109306R.html

Scroll to Top