学妹不懂Java泛型,非让我写一篇给她看看(有图为证)

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

前言



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


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


20210518124531442.png


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


什么是泛型:


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


没有泛型的时候



泛型,从字面的意思来看,广泛的意思,能够广泛的代表或者处理某一类事务的类型(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;
    }
}


重写?你去重写吧。


20200811171032848.png


使用Object类表示泛型



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

向上转型:将子类对象赋值给父类类型的变量,这种技术称为向上转型。可以在父类中定义方法,然后根据子类中具体实现这样也正是多态机制的基本思想。


20200811174254771.png


因为很多时候我们不一定使用类越细越好,比如狗的共同点已经很相似了,一群狗我们可能使用dog来表示操作它们,但是在实例化可能根据哈士奇、藏獒、金毛等不同种类具体实例化。重写部分特殊函数,在使用的时候直接使用dog的api即可满足要求。


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


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


前面的向上转型是更具体的类转为较为抽象的类。谈完向上转型,当然还有个向下转型啦,向下转型就是将较抽象的类转换为较具体的类。当然向下转型需要强制类型转换(显示转换告诉编译器)


20200811205006533.png


如果使用代码来看:


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代码变得不太安全。


20200811213434911.png


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就可以在类中自由使用,这个类我们暂时不知道是什么,但是初始化的时候编译器就知道它是什么类型:


2020081200004377.png


泛型接口


既然类可以使用泛型,接口当然也可以,不过接口使用泛型和普通类的略有区别,子类在继承泛型接口的时候需要接口处声明泛型类型,否则编译器报错。例如下面的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中有上边界限定和下边界限定用来限制泛型的可用类型。

限定通配符包括两种:


类型的上界,格式为:<? extends T>,即类型必须为T类型或者T子类


20200812152814733.png


类型的下界,格式为:<? super T>,即类型必须为T类型或者T的父类


20200812153811117.png


举个例子在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函数就用了泛型的下边界限定,当你错误运用时候编译器就可以提示出来。


20200812154654702.png


尾声



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


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


20200812205724767.png


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


给学妹发了之后:


20210518125014995.png


累哉!

目录
相关文章
|
3月前
|
安全 Java 编译器
揭秘JAVA深渊:那些让你头大的最晦涩知识点,从泛型迷思到并发陷阱,你敢挑战吗?
【8月更文挑战第22天】Java中的难点常隐藏在其高级特性中,如泛型与类型擦除、并发编程中的内存可见性及指令重排,以及反射与动态代理等。这些特性虽强大却也晦涩,要求开发者深入理解JVM运作机制及计算机底层细节。例如,泛型在编译时检查类型以增强安全性,但在运行时因类型擦除而丢失类型信息,可能导致类型安全问题。并发编程中,内存可见性和指令重排对同步机制提出更高要求,不当处理会导致数据不一致。反射与动态代理虽提供运行时行为定制能力,但也增加了复杂度和性能开销。掌握这些知识需深厚的技术底蕴和实践经验。
77 2
|
22天前
|
Java API
[Java]泛型
本文详细介绍了Java泛型的相关概念和使用方法,包括类型判断、继承泛型类或实现泛型接口、泛型通配符、泛型方法、泛型上下边界、静态方法中使用泛型等内容。作者通过多个示例和测试代码,深入浅出地解释了泛型的原理和应用场景,帮助读者更好地理解和掌握Java泛型的使用技巧。文章还探讨了一些常见的疑惑和误区,如泛型擦除和基本数据类型数组的使用限制。最后,作者强调了泛型在实际开发中的重要性和应用价值。
18 0
[Java]泛型
|
1月前
|
存储 安全 Java
🌱Java零基础 - 泛型详解
【10月更文挑战第7天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
11 1
|
1月前
|
Java 语音技术 容器
java数据结构泛型
java数据结构泛型
27 5
|
1月前
|
存储 Java 编译器
Java集合定义其泛型
Java集合定义其泛型
18 1
|
2月前
|
Java 编译器 容器
Java——包装类和泛型
包装类是Java中一种特殊类,用于将基本数据类型(如 `int`、`double`、`char` 等)封装成对象。这样做可以利用对象的特性和方法。Java 提供了八种基本数据类型的包装类:`Integer` (`int`)、`Double` (`double`)、`Byte` (`byte`)、`Short` (`short`)、`Long` (`long`)、`Float` (`float`)、`Character` (`char`) 和 `Boolean` (`boolean`)。包装类可以通过 `valueOf()` 方法或自动装箱/拆箱机制创建。
37 9
Java——包装类和泛型
|
1月前
|
存储 Java 编译器
【用Java学习数据结构系列】初识泛型
【用Java学习数据结构系列】初识泛型
20 2
|
2月前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
1月前
|
安全 Java 编译器
Java基础-泛型机制
Java基础-泛型机制
16 0
|
2月前
|
存储 安全 搜索推荐
Java中的泛型
【9月更文挑战第15天】在 Java 中,泛型是一种编译时类型检查机制,通过使用类型参数提升代码的安全性和重用性。其主要作用包括类型安全,避免运行时类型转换错误,以及代码重用,允许编写通用逻辑。泛型通过尖括号 `&lt;&gt;` 定义类型参数,并支持上界和下界限定,以及无界和有界通配符。使用泛型需注意类型擦除、无法创建泛型数组及基本数据类型的限制。泛型显著提高了代码的安全性和灵活性。