Java泛型初探

前言

在学习java掉头的日子里很多青年脱坑,同时也有很多青年入坑,但入坑的时候可能没有什么好的指导或者学习方法可能头发掉的一发不可收拾……

笔者有个学弟就遇到了相同的境遇,学弟被泛型搞得头晕目眩,搞不懂泛型是个啥玩意。天天用的泛型也不知道啥玩意(他可能都不知道他有没有用泛型)。立图为证!当然,笔者深度还欠缺,如果错误还请指正!

Java泛型初探

本篇就根据笔者的理解简单的介绍一下泛型(深入还需自己),如果深度不够或者有错误还请见谅。

泛型是程序设计语言的一种特性。允许程序员在强类型程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须作出指明。各种程序设计语言和其编译器、运行环境对泛型的支持均不一样。将类型参数化以达到代码复用提高软件开发工作效率的一种数据类型。泛型类是引用类型,是堆对象,主要是引入了类型参数这个概念。
——百度百科

没有泛型的时候

泛型,从字面的意思来看,广泛的意思,能够广泛的代表或者处理某一类事务的类型(java集合类)。在没有泛型的时候,你会如何去处理?比如你写链表的时候。可能会这样:

public class node {
    public int value;//节点的结果
    node next;//下一个连接的节点
    public node(){}
    public node(int value)
    {
        this.value=value;
    }
}

这个node 节点存的是int类型,如果是存一个字符串的链表或者是一个double类型数据链表呢?难道要这样重复的写:

public class node {
    public String value;//节点的结果
    node next;//下一个连接的节点
    public node(){}
    public node(String value)
    {
        this.value=value;
    }
}
  • 重写?你去重写吧。

Java泛型初探

使用Object类表示泛型

发现在设计上存在的这个大问题之后,大大纷纷考虑到这种问题的严重和复杂性,随着面向对象的发展流行我们知道在java中有向上转型向下转型.

向上转型:将子类对象赋值给父类类型的变量,这种技术称为向上转型。可以在父类中定义方法,然后根据子类中具体实现这样也正是多态机制的基本思想。
Java泛型初探
因为很多时候我们不一定使用类越细越好,比如狗的共同点已经很相似了,一群狗我们可能使用dog来表示操作它们,但是在实例化可能根据哈士奇、藏獒、金毛等不同种类具体实例化。重写部分特殊函数,在使用的时候直接使用dog的api即可满足要求。

在java中这种多态思想也非常多,比如类似以下:

List<Integer>list1=new ArrayList<Integer>();
List<Integer>list2=new LinkedList<Integer>();

前面的向上转型是更具体的类转为较为抽象的类。谈完向上转型,当然还有个向下转型啦,向下转型就是将较抽象的类转换为较具体的类。当然向下转型需要强制类型转换(显示转换告诉编译器)
Java泛型初探
如果使用代码来看:

public class test {
    public static void main(String[] args) {
       //animal指想dog的堆地址,向上转型 从 dog——》animal 向上父类转
        animal animal=new dog("doudou",17);
        //dog 指想animal指想的地址,从 anmial——》dog为向下子类转
        dog dog=(dog)animal;
        animal.sayhello();
        dog.sayhello();
    }
}

class  animal
{
    public String name;
    public  int age;
    public animal(String name,int age){
        this.name=name;
        this.age=age;
    }
    public  void sayhello()
    {
        System.out.println("hello,我是aninal"+this.name+"今年"+this.age+"岁");
    }
}
class dog extends  animal
{
    public dog(String name, int age) {
        super(name, age);
    }
    @Override
    public void sayhello() {
        System.out.println("hello,我是狗狗"+this.name+"今年"+age+"岁");
    }
}

我们知道在java中Object类为所有类的父类(超类)。它是站在最顶端的类型,所有类(class)都是它的子子孙孙,它自己写好了toString(),equalls()等方法。

而同理我们借鉴这种思想可以将一个类先向上转型成Object类,然后再将操作完的数向下转型成我们所需要的数。达到这种使用上的效果,但是基本类型无法满足这个要求啊,所以就出现了包装类这个东西。在储存对象时候一定程度上能够满足使用需求。

public class test {
    public static void main(String[] args) {
        Integer a=10;
        node node1=new node(a);
        Integer va1=(Integer) node1.getValue();
        System.out.println(va1);

        node node2=new node("hello");
        String va2=(String) node2.getValue();
        System.out.println(va2);
    }
}
class node
{
    private Object value;
    public  node(Object value)
    {
        this.value=value;
    }
    public Object getValue() {
        return value;
    }
    public void setValue(Object value) {
        this.value = value;
    }
}

执行的结果为:

10
hello

这种虽然一定程度能够达到泛型效果,但是除了功能受限、使用麻烦之外,还有个更大的毛病。就是在第二次显示的向下转向的时候,如果人为转换错误编译器无法识别,而必须等到代码执行时候才报错,这样的机制让java代码变得不太安全。
Java泛型初探

Java泛型

在Object显示转换存在不安全行为的情况下,Java在jdk1.5以后提出了泛型机制,通过泛型就能有效避免转型时候出现的问题,泛型简单的理解就是在类、接口、方法中定义未知类型变量,只有初始化的时候才知道真正的类型。在定义的类或接口函数中可以直接使用这个未知类型进行操作。

泛型类

泛型类的语法如下:

类名<T>  其中T代表一个类型名称
类名<T1,T2> 可能有多个类型

其中T,T1,T2都叫做通配符。常用的通配符有T,E,K,V分别表示类型、元素、键、值,当然这并不是硬性规定,而是大家形成的一种通识。

你在创建node类就可以使用如下的写法了:

public class node <T>{
    private T value;
    public node(T value) {
        this.value = value;
    }
    public T getValue() {
        return value;
    }
    public void setValue(T value) {
        this.value = value;
    }
}

其中T就可以在类中*使用,这个类我们暂时不知道是什么,但是初始化的时候编译器就知道它是什么类型:
Java泛型初探

泛型接口

既然类可以使用泛型,接口当然也可以,不过接口使用泛型和普通类的略有区别,子类在继承泛型接口的时候需要接口处声明泛型类型,否则编译器报错。例如下面的pig类。

而如果你依然想在子类中使用泛型,那就需要在子类中声明一个泛型,而接口中的泛型使用子类的泛型类型。例如下面的dog类。

interface aninal <T>
{
    T getValue(T t);
}
class cat implements aninal {//用默认Object类型
    @Override
    public Object getValue(Object o) {
        return o;
    }
}
class pig implements aninal<String>{//子类不设置泛型,父类接口的泛型需要明确类型
    @Override
    public String getValue(String s) {
        return s+"哼哼";
    }
}
class dog <T>implements aninal<T> {//子类和接口均使用泛型。
    @Override
    public T getValue(T t) {
        return t;
    }
}

也就是说使用泛型接口如果进行继承依然想使用泛型就需要在继承的类中事先定义好接口部分的泛型类供接口使用。

class 类 <A,B,C>implements aninal<C> 

泛型方法

泛型函数的基本使用也很容易,和泛型类和泛型接口使用很相似,不过就是菱形需要放到函数类型前面:

public   <T1,T2> void fuc(T1 t1,T2 t2)
{
     System.out.println(t1);
     System.out.println(t2);
 }

边界限定

泛型既然这么好用,可以在使用时候直接传入具体类型,但是我们在开发很多时候某个类或者某个方法的能够传入类型是有限制的。那么在java中有上边界限定和下边界限定用来限制泛型的可用类型。
限定通配符包括两种:

  1. 类型的上界,格式为:<? extends T>,即类型必须为T类型或者T子类
    Java泛型初探
  2. 类型的下界,格式为:<? super T>,即类型必须为T类型或者T的父类
    Java泛型初探

举个例子在Java中编写这些类和方法:

class  animal {
    //dosomething
}
class dog extends  animal {
    //dosomething
}
class pig extends  animal {
    //dosomething
}
public class test {
    static void printlist1(List<? extends animal>list)//只能aninal和animal的子类
    {
        for(animal animal:list)
        {
            System.out.println(animal);
        }
    }
    static void dolist2(List<? super pig>list)
    {
        //dosomething
    }
}

这样printlist1函数就使用了上边界限定。而dolist2函数就用了泛型的下边界限定,当你错误运用时候编译器就可以提示出来。
Java泛型初探

尾声

当然本篇并不是一个完整的泛型解答和总结,泛型还有很多细致的需要对比其差别这里就先不介绍啦。

从整体来讲,泛型的主要作用还是为了解决类型转换的安全性问题,避免了Object来回转换的问题,使得编译器就能识别类型转换的错误,同时通过限定类型使得函数方法等使用的更加灵活方便。不过泛型更多的应用于框架的编写方面,在java中其实也是随处可见。尤其是集合类:

Java泛型初探
看了这篇泛型,下次设计链表二叉树别傻傻的用int 表示node节点的值了!我想你该知道正确的写法了!

笔者微信公众号:bigsai 更多学习资料、精彩内容与你分享!

上一篇:【jmespath】—4. 进阶 Flatten Projections


下一篇:MongoDB从立地到成佛(介绍、安装、增删改查)