Java十分钟精通泛型的使用与原理

什么是泛型?

简而言之:<>泛型就是用来约束类、方法、属性上的数据类型,比如

List<Integer> list = new ArrayList<Integer>();

new ArrayList这个集合的元素只能添加Integer类型。

为什么需要泛型?

Java推出泛型之前,程序员可以构建一个Object类型的集合,该集合能够存储任何的数据类型,而在使用该 集合的时候,需要程序员明确知道每个元素的具体的类型并向下转型,否则容易引发ClassCastException 类转换异常。现在我们通过泛型就能解决这个问题,通过泛型<>我们就能够约束这个集合插入的类型,就不需要再从Object类型转换成子类类型。

泛型有什么好处?

  • 类型安全,不会插入指定类型以外的数据
  • 消除了强制类型转换

泛型的类型:

泛型可以定义在类上、父类上、接口上、子类上、方法上、参数上、属性上, 泛型类型是可以用任意字母来代替你需要传递的数据,一般为了可读性,我们约定一般会写:

  • E -element 代码集合中存放的元素
  • T -Type表示类型Java类
  • K -key 表示键
  • V -V 表示value
  • N -Number 表示数值类型
  • ? 表示不确定的类型

那么这么多字母有什么用呢?我们来看一段代码:

这是一个普通的Student类:

package Test;

public class Student {

    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

我们升级一下,把这个类定为String的泛型,意思是这个类只能接收String的类型:

public class Student<Stirng> {   //重复的代码就不重写了,和上面的是一样
}

ok,这样没问题,但是我们想一想,如果直接就把这个类的泛型定义死了,如果后面需要在Student传入Integer类型,我们就得重写一个Student类,这样代码的复用性不高,不符合编程思维,所以我们可以这样:

public class Student<T> {

        private T t;
        public T getT() {return t;}
        public void setT(T t) {this.t = t;}
    }

然后当需要用什么泛型的时候直接传入即可:

Student<String> stu1 = new Student<>();
Student<Integer> stu2 = new Student<>();
Student<Double> stu3 = new Student<>();

测试:(接上面的代码)

public class Test {
    public static void main(String[] args) {
        //先使用Integer泛型,传入100
        Student<Integer> stu1 = new Student<>(100);
        Integer in = stu1.getT();
        System.out.println(in);

    }
}

输出:100

(没问题)

但是如果在定义为Integer下输入String类型,会怎么样?

很明显,直接就报错了

这样就更好的利用了代码的复用性。上面部分的知识点也就是泛型类。

泛型父类和子类:

我们先定义一个泛型父类:()

public class Student<T> {

    private T t;
    public T getT() {return t;}
    public void setT(T t) {this.t = t;}
}

然后定义一个子类,继承该Student父类:

public class Child<T> extends Student<T>{

    @Override
    public T getT(){
        return super.getT();
    }
    @Override
    public void setT(T t){
        super.setT(t);
    }
}

特别需要注意:

  • 泛型子类的参数一定要和父类的参数类型一致
  • 如果子类没有添加泛型,那么父类的参数类型必须明确 也就是这样:
public class Child extends Student<Integer>{
}

测试:

下面代码的解释:在父类Student内定义泛型为String,然后创建子类Child的对象child,因为是继承关系,所有child的泛型也是String,

public class Test {
    public static void main(String[] args) {

        Student<String> child = new Child<>();  //多态
        child.setT("abc");
        String value = child.getT();
        System.out.println(value);
    }
}

当子类传入非String类型值时:

泛型接口:

泛型接口其实也很简单,定义如下:

public interface USB<T> {
}

泛型子类实现泛型接口,除了处理标识父类的泛型标识外,还可以继续扩展泛型:

public class phone<T,V> implements USB<T>{
//这里的意思是子类除了有USB接口的泛型T类,也可以再扩展一个泛型

    private T color; //手机颜色
    private V phoneName;  //手机名称

    public phone(T color,V phoneName) {
        this.color = color;
        this.phoneName = phoneName;
    }

    public T getColor() {
        return color;
    }

    public void setColor(T color) {
        this.color = color;
    }

    public V getPhoneName() {
        return phoneName;
    }

    public void setPhoneName(V phoneName) {
        this.phoneName = phoneName;
    }

}

注意: 泛型接口也和泛型父子类一样,如果子类没有添加泛型参数,那么父类一定要明确的指定类型!

public class phone implements USB<String>{
}

测试: 分别把T和V的泛型都定义为String:

public class Test {
    public static void main(String[] args) {
        phone<String,String> ph = new phone<>("黑色","华为");
        String str1 = ph.getColor();
        String str2 = ph.getPhoneName();
        System.out.println(str1+str2);
    }
}

泛型方法:

定义泛型方法:在public和返回值之间定义<>的才是泛型方法:

    public<E> void printType(){

    }

然而这个方法的返回值类型就是取决于E的类型,如果E是Integer,那么这个方法的返回值类型就是整数。用法基本和上述的一致,需要什么类型的返回值,在调用的时候去传递泛型类型即可。

通配符:

通配符一般是使用 ? 代替具体的类型实参(此处是类型实参,而不是类型形参)。当操作类型时不需要使用类型的具体功能时,只使用Object类中的功能,那么可以用 ? 通配符来表未知类型。例如 List<?> 在逻辑上是List、List 、List等所有List<具体类型实参>的父类。

有界的类型参数:

有的时候需要限制那些被允许传递到一个类型参数的类型种类范围,例如一个操作数字的方法可能只希望接受Number或者Number子类的实例。这时就需要为泛型添加上边界,即传入的类型实参必须是指定类型的子类型。要声明一个有界的类型参数,首先列出类型参数的名称,后跟extends或super关键字,最后紧跟它的上界或下界。由此可以知道泛型的上下边界的添加必须与泛型的声明在一起 。

上限: <? extends T>:

表示该通配符所代表的类型是T类型的子类。 例如往集合中添加元素时,既可以添加T类型对象,又可以添加T的子类型对象。

下限:<? super T>:

表示该通配符所代表的类型是T类型的父类,就是只能获取到T类及以上的泛型,任何继承T类的泛型将得不到。

举例说明:

建一个动物的父类:

public class Animal {
}

建一个猫类,继承父类:

public class Cat extends Animal{
}

建一个小猫类,继承猫类:

public class MiniCat extends Cat{
}

现在的关系是Animal>Cat>MiniCat

通配符上限的测试类:

public class test {
    public static void main(String[] args) {
        ArrayList<Animal> animals = new ArrayList<>();
        ArrayList<Cat> cats = new ArrayList<>();
        ArrayList<MiniCat> minicats = new ArrayList<>();

        showAnimals(cats);
        showAnimals(minicats);
        //这样会报错,因为在showAnimals方法内的通配符最高继承自Cat
        showAnimals(animals);
    }

    //使用通配符,把上线限制到Cat,意思是Animal这个类不能被获取
    private static void showAnimals(ArrayList<? extends Cat> cats) {
        for (Cat cat:cats
             ) {
            System.out.println(cat);
        }
    }
}

可以看到使用通配符限制到Cat类后,Animals这个类就已经获取不到了。这就是上限。 如果把上限设置成Animal的时候:

通配符下限的测试类:(其他代码与上面的一致)

public class test2 {
        public static void main(String[] args) {
            ArrayList<Animal> animals = new ArrayList<>();
            ArrayList<Cat> cats = new ArrayList<>();
            ArrayList<MiniCat> minicats = new ArrayList<>();
            showAnimals(animals);
            showAnimals(cats);
            //设置下限:ArrayList<?  super Cat>
            showAnimals(minicats);
        }

        //使用通配符设置下限,ArrayList<? super Cat,意思是只能获取Cat及以上的类型
        private static void showAnimals(ArrayList<? super Cat> cats) {
            for (Object cat:cats
            ) {
                System.out.println(cat);
        }
    }
}

因为通配符设置了下限到Cat类,所以MiniCat是获取不到的:

以上就是关于泛型和通配符的介绍,泛型可以在类、接口、方法中使用,分别简称之泛型类、泛型接口、泛型方法,并且通过泛型实现数据类型的任意化,即灵活、又有安全性,易于维护。在编译过中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。

总而言之,泛型是在编译的时候检查类型安全,所有的强制转换都是自动和隐式的,提高代码的重用率。并且消除强制类型转换,在传入参数的时候就已经限制的类型。

到此这篇关于Java十分钟精通泛型的使用与原理的文章就介绍到这了,更多相关Java 泛型内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 史上最全图文讲解Java泛型

    目录 前言 一:泛型本质 二:为什么使用泛型 三:如何使用泛型 1.泛型类 2.泛型接口 3.泛型方法 四:泛型通配符 五:泛型中KTVE的含义 六:泛型的实现原理 七:关于泛型数组要提一下 八:最后 前言 泛型在java中有很重要的地位,无论是开源框架还是JDK源码都能看到它. 毫不夸张的说,泛型是通用设计上必不可少的元素,所以真正理解与正确使用泛型,是一门必修课. 一:泛型本质 Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允

  • 这个Java泛型不太正经

    目录 一.前言 二.泛型 三.泛型定义的格式: 什么是引用类型? 泛型的好处是: 四.泛型类 五.泛型方法 六.泛型接口 七.类型通配符 总结 一.前言 泛型在java中有很重要的地位,在实际开发中用处也很大. 二.泛型 泛型:是jdk5中引入的特性,他提供编译是类型是类型的安全检测机制,这个机制允许在编译时检测到非法的类型,它的本质是参数化类型,也就是所操作的数据类型不变指定为一个参数 将原来具体的类型参数化,然后再使用/调用的时候传入具体的参数 泛型的类型: ①:泛型类 ②:泛型方法 ③:泛

  • 详细全面解析Java泛型

    1.概述 作为一个面向对象的编程语言,Java可以通过实现一些类,作为我们各种需求的一个模板,方便我们的使用.但有时候,这个类的范围可能比我们想要的范围要大,我们只想限定于满足类的某些对象,那这样的情况下,泛型的概念就被提出来了(非官方解释,方便理解). 举个例子:比如我们我们生活中的车,它可以作为一个类,但是车其实又有很多种,包括货车,轿车,大巴车等等,而其中的轿车外观差不多,但是又属于不同的品牌,这些品牌有很多不一样的地方,这里我们可以把轿车的品牌看作是泛型(类似于标签) 通过上面的解释,泛

  • Java中泛型学习之细节篇

    目录 简介 正文 什么是类型参数 为啥要有泛型 泛型的演变史 类型擦除 泛型的应用场景 通配符限定 动态类型安全检查 总结 简介 泛型的作用就是把类型参数化,也就是我们常说的类型参数 平时我们接触的普通方法的参数,比如public void fun(String s):参数的类型是String,是固定的 现在泛型的作用就是再将String定义为可变的参数,即定义一个类型参数T,比如public static <T> void fun(T t);这时参数的类型就是T的类型,是不固定的 从上面的S

  • Java泛型之类型擦除实例详解

    目录 前言 泛型是什么? 泛型的定义和使用 泛型类 泛型方法 泛型类与泛型方法的共存现象 泛型接口 通配符 ? 无限定通配符 <?> <? extends T> 类型擦除 类型擦除带来的局限性 泛型中值得注意的地方 Java 不能创建具体类型的泛型数组 泛型,并不神奇 总结 前言 泛型,一个孤独的守门者. 大家可能会有疑问,我为什么叫做泛型是一个守门者.这其实是我个人的看法而已,我的意思是说泛型没有其看起来那么深不可测,它并不神秘与神奇.泛型是 Java 中一个很小巧的概念,但同时

  • Java 泛型详解与范例

    目录 一.泛型的使用 二.泛型类的定义-类型边界 三.类型擦除 四.泛型类的使用-通配符 五.泛型方法 六.泛型的限制 一.泛型的使用 前面我们学集合的时候,简单的说过泛型的使用.如下: ArrayList<Integer> list = new ArrayList<>(); Queue<Integer> queue = new LinkedList<>(); 那么使用是这样的简单,该注意什么? 尖括号里的类型,只能写引用类型 基础数据类型的话,就需要写相应

  • java 方法泛型入参T和String的重载关系详解

    目录 方法泛型入参T和String的重载关系 重载的基本知识不在这里讨论了 重载遇到泛型的问题 反复求证,得出以下结论 方法泛型入参T和String的重载关系 重载的基本知识不在这里讨论了 重载的一个关键理论,如果方法名相同,参数个数.父类型.位置也相同,则调用更加特殊化一个方法. 多余的没写,大家可以运行一下下面的代码,然后理解一下就ok了. public class TestMain {      public static void main(String[] args) {       

  • 带大家认识Java语法之泛型与通配符

    目录 ️前面的话️ 1.泛型 1.1泛型的用法 1.1.1泛型的概念 1.1.2泛型类 1.1.3类型推导 1.2裸类型 1.3擦除机制 1.3.1关于泛型数组 1.3.2泛型的编译与擦除 1.4泛型的上界 1.4.1泛型的上界 1.4.2特殊的泛型上界 1.4.3泛型方法 1.4.4类型推导 2.通配符 2.1通配符的概念 2.2通配符的上界 2.3通配符的下界 总结 ️前面的话️ 本篇文章带大家认识Java语法——泛型与通配符,泛型和通配符是一个非常抽象的概念,简单来说,两者都可以将类型作为

  • Java十分钟精通泛型的使用与原理

    什么是泛型? 简而言之:<>泛型就是用来约束类.方法.属性上的数据类型,比如 List<Integer> list = new ArrayList<Integer>(); new ArrayList这个集合的元素只能添加Integer类型. 为什么需要泛型? Java推出泛型之前,程序员可以构建一个Object类型的集合,该集合能够存储任何的数据类型,而在使用该 集合的时候,需要程序员明确知道每个元素的具体的类型并向下转型,否则容易引发ClassCastExceptio

  • Java十分钟精通集合的使用与原理上篇

    目录 什么是集合? 集合分为Collection和Map两种体系 一.Collection接口: 二.Map接口下分为HashMap和TreeMap: 集合总结: Collections工具类: 什么是集合? 比如我们去买超市买很多东西,我们不可能拿一样就去收银台,我们可能是先放到购物车内,然后再统一处理,所以购物车相当于一个容器,可以装很多东西,在Java中的集合也是相当于一个容器,可以装很多数据. 集合继承关系图: 但是这张图太复杂了,我们看一张简便的: 集合分为Collection和Map

  • Java十分钟精通集合的使用与原理下篇

    List集合: ArrayList: 底层是数组结构,储存有序并且可以重复的对象 package SetTest; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class ArrayListTest { public static void main(String[] args) { //创建ArrayList的对象 List<Integer> list = ne

  • Java十分钟精通接口的使用与原理

    何为接口? 官方解释: Java内的接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能). 个人理解解释: 其实接口可以理解为一种特殊的类,这个类里面全部是由全局常量和**公共的抽象方法(需要重写)所组成.接口是解决Java无法使用多继承的一种手段.我们可以把接口理解为是一个100%的抽象类,既接口中的方法必须全部是抽象方法.反正接口里面都是方法,但是没有方法体,其他类实现这个接口后重

  • Java十分钟精通反射机制原理

    什么是反射? 反射机制是在运行状态中,它为Java提供一种"操作对象"的能力,在运行状态下,通过Class文件对象,可以调用到任何类里面的属性.方法.以及构造方法,包括私有的,所有的类在反射机制面前都是透明的 自己的概括:通过Class文件对象可以看到这个类里面的所有东西,并且可以使用和修改 反射的前提是获取Class文件对象((字节码对象),那么一共有三种方式获取: Class.forName("全类名") ----通过Class类的静态方法(最常用) 类名.cl

  • Java十分钟精通Log4j日志的使用

    目录 为什么要用日志? 下载: 详细步骤: 一.打开IDEA 二.创建日志对象 为什么要用日志? 我们知道,程序在运行过程中会产生很多很多信息,比如在几点几分的时候运行了,运行的结果是怎么样的?为了我们更加了解程序运行的情况,可以通过日志来查看,日志可以在控制台输出,也可以输出至指定的文件内,在下面的文章中具体的给大家介绍. 下载: Log4J是Apache公司的开源项目,用于日志处理.下载地址: https://logging.apache.org/log4j/2.x/download.htm

  • Java十分钟精通异常处理机制

    目录 异常处理机制的底层原理 异常的继承关系图 异常的处理 一.try-catch-finally结构 二.多catch处理不同的异常: 三.throws声明异常/throw抛出异常: 四.自定义异常: 五.常见的异常 异常处理机制的底层原理 抛出异常,在执行一个方法时,如果发送了异常,则这个方法生成代表该异常的一个对象,停止当前执行的 路径,并把异常提交给jre. 捕获异常:jre得到该异常后,虚招相应的代码来处理该异常.jre在方法的调用栈中查找,从生成异常的 方法开始回溯,直到找到相应的异

  • Java十分钟精通包装类的使用与操作

    包装类 何为包装类? 基本类型包装类的概述: 将基本数据类型封装成对象的好处在于可以在对象中定义更多的功能方法操作该数据 包装类是将基本数据类型的值包装为Java中的对象,Java语言为8种基本数据类型分别提供了包装类 首先看一下八大数据类型的包装类: 包装类型出现的原因: 因为Java是一个面向对象的语言,基本类型并不具有对象的性质,为了与其他对象"接轨"就出现了包装类型,它相当于将基本类型"包装起来",使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本

  • Java十分钟精通内部类的使用

    内部类: 其实内部类顾名思义,就是类中类,一个类里面还有一个类. 内部类分为四种: 普通内部类 静态内部类 方法内部类 匿名内部类 我们一一去了解一下~~ A.普通内部类: 我们先通过代码去了解一下: package InternalClass; /** * 内部类 */ public class Car { public int a = 10; public int b = 20; //外部类的方法 public void method() { System.out.println("我是外部

  • Java十分钟精通String类的各种使用方法

    String String类: 代表字符串,提供了开发中常用的字符串处理的方法,如:求字符串的长度.截取字符串.替换字符串等方法,符串是常量,它的值创建之后就不可以再修改了. 首先我们先查一下官方文档,看看官方给String类定了什么方法: String也是属于java.lang包,所以不需要导入,这里就部分展示,全部的内容可以参考: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.htm

随机推荐