A brief introduction of Java generics

1. What is Generic

"Generics", as the name implies, "refers to the type." We offer refers to the concept, but the execution of specific time but can have specific rules to restrain, for example, we use a lot of ArrayList is a generic class, ArrayList can be stored as a collection of various elements, such as Integer, String, since the definition of other types, but when we use to restrain by specific rules, constraints as we can store only the set of elements of type Integer, such as List<Integer> iniData = new ArrayList<>().

II. What are the benefits of using generics

Exemplified in sets, the benefits of using generics is because we do not add different types of elements defined collection of different types, such as integers collections, collections float, string collections, we can define a set of storage integer, floating point, string data, which is not the most important, because as long as we set up the underlying storage Object can add all the data can be upcast to Object. More important is that we can control the type of data stored by the rules in accordance with their own ideas.

We ArrayList, for example, if we have to month as of today's date into the ArrayList, if we do not use generics, then we define an ArrayList.

List monthDays = new ArrayList();

To this was added to our 4 No. 1 Date

public static List addMonthDays(){
    List monthDays = new ArrayList();
    monthDays.add(LocalDate.now().withDayOfMonth(1));
    monthDays.add(LocalDate.now().withDayOfMonth(2));
    monthDays.add(LocalDate.now().withDayOfMonth(3));
    monthDays.add(new Date());
    return monthDays;
}

So there is no problem? We all saw it, of course, although the date can be represented, but with the Date, LocalDate, we call the method directly printed, that's it

public static void main(String[] args) {
    List monthDays = addMonthDays();
    for(Object day : monthDays){
        System.out.println(day);
    }
}
2019-08-01
2019-08-02
2019-08-03
Sun Aug 04 10:27:10 CST 2019

We certainly do not want such an outcome, we want to be

2019-08-01
2019-08-02
2019-08-03
2019-08-04

If the storage element types only two (if we know), this time we have to manually determine what

public static void main(String[] args) {
    List monthDays = addMonthDays();
    for(Object day : monthDays){
        if (day instanceof Date){
            Date date = (Date) day;
            System.out.println(LocalDate.of(date.getYear(), date.getMonth(), date.getDay()));
        }else {
            System.out.println(day);
        }
    }
}

This time we can achieve the above purpose, but we also know that this great question wording problems

  1. We can not control the elements stored in the end whether the relevant date and, as we store a "1", 65536 and other non-date, method definition also does not complain, but when calling the type of conversion is bound to error;
  2. We do not know the collection is stored in the end what types of elements, such as there are "2019/08/04" The date string, java.sql.Date do this type, it is difficult to guarantee exhaustive;
  3. 代码过于复杂,太难维护;

这时泛型就提供了很好的解决方案,从源头上就制定好规则,只能添加LocalDate类型,那么上述的问题就都得以解决了。

public static List<LocalDate> addFormatMonthDays(){
    List<LocalDate> monthDays = new ArrayList<>();
    monthDays.add(LocalDate.now().withDayOfMonth(1));
    monthDays.add(LocalDate.now().withDayOfMonth(2));
    monthDays.add(LocalDate.now().withDayOfMonth(3));
    monthDays.add(new Date());//这个代码在编译期间就无法通过了
    return monthDays;
}

不仅提高了代码的可读性,一眼即可看出我们存储的是LocalDate类型。同时编译器也可很好的利用该信息,编译期间就可进行类型检查,保证了安全性,在get的时候,无需进行强制类型转换。

三. 泛型类

为使类适应更多的情况,具备更好的扩展性,我们可以将其设置为泛型类,即具有一个或多个类型变量的类,写法如下:

public class ClassName<泛型标识,可以是字母、中文字符等,不过一般用大写英文字符> {
    private 泛型标识 property;
}

对于泛型标识符,一般有一些约定俗称的写法,如果是表示集合的元素类型,一般用字母E,如我们常用的ArrayList,public class ArrayList<E>,我们自定义如下:

//车库
public class Garage<E> {
    //向车库中添加车
    public void add(E car){
        //...
    }
}

我们还可以用字符K和V表示关键字和值的类型,如我们常用的HashMap,public class HashMap<K,V>,我们也可以自定义如下:

//映射关系
//K,V: 蔬菜,是否有机; 水果,产地; 服装,类型; 汽车,品牌
public class Mapping<K, V> {
    
    private K key;
    
    private V value;
}

我们还经常用一个字符T表示类型

public class Person<T> {
    private T t;
    public Person(T t){
        this.t = t;
    }
    public void run(){
        System.out.println(t);
    }
}

如何使用泛型类,类型如何实例化,我们只需要保证传入的实参类型和泛型参数类型相同即可。

//正常用法
Person<String> s1 = new Person<String>("张三");
//jdk7.0后可以省略后面的参数类型
Person<String> s2 = new Person<>("张三");
s2.run();

//当然泛型的定义是可以帮助我们按照某种规则去做事,如果不做限制,也不会编译错误,但泛型就毫无意义了
Person t = new Person(111);
t.run();

//泛型的类型不能是八大基本类型,下面会编译出错
Person<int> s = new Person<>(1);

四. 泛型接口

泛型接口和泛型类的定义基本一致,定义如下:

public interface Person<T> {
    public T parent();
    public String eat();
}

当我们定义了一个类要实现该接口时,那么该类的泛型类型必须和接口类的泛型类型一致,未传递实参的情况下,继续使用泛型类型T,传递了实参的情况下,泛型类型必须使用实参类型

public class Teacher<T> implements Person<T> {
    @Override
    public T parent() {
        return null;
    }

    @Override
    public String eat() {
        return null;
    }
}
//Teacher不必再定义类型了,因为泛型类型在Person处已经定义好了
public class Teacher implements Person<Integer> {
    //这里的返回类型必须为Integer,否则必须出错
    @Override
    public Integer parent() {
        return null;
    }

    @Override
    public String eat() {
        return null;
    }
}

五. 泛型方法

泛型方法可以定义在普通类和泛型类中,比如泛型类更为常用,一般能用泛型方法解决的问题优先使用泛型方法而不使用泛型类,类型变量放在修饰符的后面,如public static ,public final等的后面。

public class Teacher {
    public static <T> T println(T t){
        System.out.println(t);
        return t;
    }
}

调用很简单,很一般方法调用是一样的,更方便的是类型不像一般方法做了限定。

String s = Teancher.println("str");

另外需要说明的是,定义在泛型类中的泛型方法的泛型变量之间是没有关系的,如这样的代码

public class Teacher<T> {
    T teacher;
    public Teacher(T t){
        this.teacher = t;
    }
    public <T> T println(T t){
        System.out.println(t);
        return t;
    }
}
Teacher<String> teacher = new Teacher<>("张三");
Integer in = teacher.println(123456);

类泛型类型为String,方法的泛型类型为Integer,虽然都是用T来表示的。

同时关于泛型方法需要说明的是:

在修饰符public xx与方法名之间非常重要,有< T >这样的才算是泛型方法;仅仅使用了泛型变量并不算是泛型方法。

六. 限定类型变量

不论是泛型类还是泛型方法,目前来说其实都是没有做类型限定,无论我们传递什么样类型的变量进去都可以,因为我们在处理逻辑中并没有使用到该类型特有的东西(成员变量、方法等)。假如我们想传递的参数类型仅仅是某个大类(父类)下面的一些小类(子类),那么怎么做呢?

public class ArrayFlag {
    public static <T> T getMax(T[] array){
        if(array == null || array.length == 0){
            return null;
        }
        T maxValue = array[0];
        for(int i = 0; i < array.length; i++){
            if(array[i].compareTo(maxValue) > 0){
                maxValue = array[i];
            }
        }
        return maxValue;
    }
}

大家也看到了我们在getMax方法中使用了compareTo方法进行比较,但如果我们传入的类型T没有compareTo方法呢,岂不是要报错,因此我们需要做限定,只要限定了是Comparable接口的必然具备compareTo方法,那么改造后就成了这样

public class ArrayFlag {
    public static <T extends Comparable> T getMax(T[] array){
        if(array == null || array.length == 0){
            return null;
        }
        T maxValue = array[0];
        for(int i = 0; i < array.length; i++){
            if(array[i].compareTo(maxValue) > 0){
                maxValue = array[i];
            }
        }
        return maxValue;
    }
}

同时需要说明的是,此处用的是extends关键字,extends在这里是表示的是绑定了Comparable接口及其子类型,是“绑定、限定”的意思,非“继承”的意思,后面也可以是接口或者类,如果有多个限制,可以使用&分隔,如:

public static <T extends Comparable & Serializable> T getMax(T[] array)

七. 泛型通配符

举个例子,定义了一个书籍类和一个小说类

//定义了一个书籍类
public class Book {}
//定义了一个小说书籍类继承书籍类
public class Novel extends Book {}

我们再定义一个书柜类用来装书籍以及小说

//定义了一个书柜类用来装书
public class Bookcase<T> {
    T b; 
    public Bookcase(T t){
        this.b = t;
    }
    public void set(T t) {
        b=t;
    } 
    public T get() {
        System.out.println(b.getClass());
        return b;
    }
}

下面我们就用书柜来装小说

//以前的写法,无法编译通过,提示Incompatible types, Required Book Found Novel
Bookcase<Book> bc = new Bookcase<Novel>(new Novel());

但在jdk7.0后,new Bookcase的时候是可以不用给出泛型类型的,省略的类型可以从变量的类型推断得出,因此如果下面这种写法

Bookcase<Book> bc = new Bookcase<>(new Novel());
System.out.println(bc.getClass());
bc.get();

此时可以编译通过,我们执行后得出的结果是:

class generic.Bookcase
class generic.Novel

当然我们还可以通过通配符来解决该问题,通配符包括以下几种:

上界通配符、下界通配符、无限定通配符

7.1 上界通配符

上界通配符定义方式如下:用extends 关键字,含义是该书柜只能放置小说类书籍(如什么都市小说、爱情小说、玄幻小说都可以),但不能放置父类书籍、其他类如史书、职场类书籍、财经类书籍等,是在使用的时候进行限定,如:

Bookcase<? extends Book> bc = new Bookcase<Novel>(new Novel());

这种定义方式就不会编译错误了。另外关于上界通配符的特点,对上有限制,根据java多态向上造型的原则,不适合频繁插入数据,适合频繁读取数据的场景。

7.2 下界通配符

下界通配符定义方式如下:用super关键字,含义就是书柜放置设置了下限,我们只能放置Book书籍以及Novel书籍,却无法再将细分的都市小说、爱情小说类书籍放进去

Bookcase<? super Novel> bc = new Bookcase<Novel>(new Novel());

另外关于下界通配符的特点,和上界通配符正好相反,不适合频繁读取数据,适合频繁插入数据的场景。

7.3 无限定通配符

无限定通配符意味着可以使用任何对象,因此使用它类似于使用原生类型。但它是有作用的,原生类型可以持有任何类型,而无限定通配符修饰的容器持有的是某种具体的类型。

举个例子:

List<?> list = new ArrayList<>();
//无法编译通过
list.add(new Object());

//下面这样的却可以添加任何类型
List<Object> list = new ArrayList<>();
list.add(new Object());

再说一下< T > 和< ? >之间的区别,初看好像他们都可以表示泛型变量,都可以extends,但它们确实有不同的使用场景

  • 类型参数< T >声明一个泛型类或泛型方法
  • 无限定通配符< ? >使用泛型类或泛型方法

八. 总结

泛型在java中可以说很常用,我们前面提到的集合类,如ArrayList,HashSet,以及Map都使用到了泛型,泛型也是也是我们再进行一些组件封装经常用到的,本文主要介绍了泛型基本概念,使用泛型的好处,泛型类、接口、方法、通配符的简单介绍以及使用方法,最后泛型一般和反射集合使用,通过泛型可以进行类型的灵活传递,通过反射可获取到实体以及类的数据信息,从而实现一些框架、组件的封装,若有不对之处,请批评指正,望共同进步,谢谢!

Guess you like

Origin www.cnblogs.com/LiaHon/p/11332355.html