一起来学习Java的泛型

目录
  • 泛型:
    • 泛型父类和子类:
    • 泛型接口:
    • 泛型方法:
    • 通配符:
      • 举例说明:
  • 总结

泛型:

什么是泛型?

泛型是在Java SE 1.5引入的的新特性,本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

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

List<Integer> list = new ArrayList<Integer>();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);
    }
}

特别需要注意:

1.泛型子类的参数一定要和父类的参数类型一致

2.如果子类没有添加泛型,那么父类的参数类型必须明确

也就是这样:

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 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类,所以MiniCat是获取不到的:

总结

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

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

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

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

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

  • java基础开发泛型类的详解

    目录 前言 泛型概念 泛型类 结论 前言 在软件开发中,有许多执行过程很类似,许多人使用复制粘贴完成功能,这种做法虽然编译器不会报错,但会使用波浪线给出提示,给以后的维护带来了很大的隐患.这种情况开发人员通常根据需要成员抽取公用方法.公用类或使用继承完成,提高了代码的复用.但是,在一些特殊情况(如执行过程中会使用到对象,这些对象操作相同,但具体的模块有有所区别),此时只能使用泛型完成代码的复用. 泛型概念 所谓泛型就是将类型由原来的具体类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式

  • 详细全面解析Java泛型

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

  • Java 泛型详解与范例

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

  • 这个Java泛型不太正经

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

  • Java中泛型学习之细节篇

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

  • 一起来学习Java的泛型

    目录 泛型: 泛型父类和子类: 泛型接口: 泛型方法: 通配符: 举例说明: 总结 泛型: 什么是泛型? 泛型是在Java SE 1.5引入的的新特性,本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.这种参数类型可以用在类.接口和方法的创建中,分别称为泛型类.泛型接口.泛型方法. 简而言之:<>泛型就是用来约束类.方法.属性上的数据类型,比如 List<Integer> list = new ArrayList<Integer>();List<Inte

  • java基础学习笔记之泛型

    泛型 将集合中的元素限定为一个特定的类型. 术语 ArrayList<E> -- 泛型类型 ArrayList -- 原始类型 E -- 类型参数 <> -- 读作"typeof" ArrayList<Integer> -- 参数化的类型 Integer -- 实际类型参数 几点注意: 参数化类型和原始类型相互兼容 ArrayList collection1 = new ArrayList<Integer>();//通过,无warning

  • java伪泛型知识点详解

    说明 1.Java中的泛型是伪泛型.这种泛型实现方法称为类型擦除 ,基于这种方法实现的泛型称为伪泛型. 2.由于Java的泛型只在编译阶段发挥作用,因此在写代码时,起到了检查的作用,当代码运行时,它的内部并没有泛型. 实例 List<String> l1 = new ArrayList<String>(); List<Integer> l2 = new ArrayList<Integer>(); System.out.println(l1.getClass(

  • 半小时通透Java的泛型

    目录 前言 学习目标 1. 什么是泛型 2. 为什么需要泛型 3. 如何使用泛型 3.1 泛型使用 3.2 自定义泛型类 3.2.1 Java 源码中泛型的定义 3.2.2 自定义泛型类实例1 3.2.3 自定义泛型类实例2 3.3 自定义泛型方法 4. 泛型类的子类 4.1 明确类型参数变量 4.2 不明确类型参数变量 5. 类型通配符 5.1 无限定通配符 5.2 extends 通配符 5.3 super 通配符 6. 小结 Java 泛型 前言 编程不能停止,每天发一篇~ 不要捉急往后学

  • 学习Java设计模式之观察者模式

    观察者模式:对象间的一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象(被观察). 以便一个对象的状态发生变化时,所有依赖于它的对象都得到通知并发生相应的变化. 观察者模式有很多实现方式:该模式必须包含观察者和被观察对象两种角色.观察者和被观察者之间存在"观察"的逻辑关系,当被观察者发生改变的时候,观察者就会观察到这样的变化,发出相应的改变. /** * 观察者接口:观察者,需要用到观察者模式的类需实现此接口 */ public interface Observer { pu

  • JAVA利用泛型返回类型不同的对象方法

    有时需要在方法末尾返回类型不同的对象,而return 语句只能返回一个或一组类型一样的对象.此时就需要用到泛型. 首先先解释个概念, 元组:它是将一组对象直接打包存储于其中的一个单一对象,这个容器对象允许读取其中元素,但不能修改. 利用泛型创建元组 public class ReturnTwo<A,B> { public final A first; public final B second; public ReturnTwo(A a,B b) { first = a; second = b

  • 计算机二级考试java软件操作教程 教大家如何学习java

    Java并不难,只是包含的内容比较多.语言本身相当精练,但功能非常强大.为了能够更加有效地使用Java编制程序,你需要学习该语言所包含的库,其中的内容十分丰富.在本书中.你将依次了解语言如何运行以及如何应用它.学习这些内容的顺序经过精心地安排,通过一些相对简单.明了的过程,你就可以获得一定的专业知识,建立运用Java进行程序设计的信心.每一章都尽量避免使用你还没有学习到的东西.这样一来,你不能马上编写嵌入Weh网页的Java程序,但是这确实是一种诱人的想法,有点像冒险跳入深水学习游泳.一般说来,

  • Java 获取泛型的类型实例详解

    Java 获取泛型的类型实例详解 Java 泛型实际上有很多缺陷,比如不能直接获取泛型的类型,不能获取带泛型类等. 以下方式是不正确的: ①.获取带泛型的类的类型 Class lstUClazz = List<User>.class ②获取局部变量泛型的类型 List<User> listUser = new ArrayList<User>(); Type genType = listUser.getClass().getClass().getGenericSuperc

  • 学习Java多线程之同步

    如果你的java基础较弱,或者不大了解java多线程请先看这篇文章<学习Java多线程之线程定义.状态和属性> 同步一直是java多线程的难点,在我们做android开发时也很少应用,但这并不是我们不熟悉同步的理由.希望这篇文章能使更多的人能够了解并且应用java的同步. 在多线程的应用中,两个或者两个以上的线程需要共享对同一个数据的存取.如果两个线程存取相同的对象,并且每一个线程都调用了修改该对象的方法,这种情况通常成为竞争条件. 竞争条件最容易理解的例子就是:比如火车卖票,火车票是一定的,

随机推荐