Java泛型深入讲解(超详细)

泛型

什么是泛型?

长什么样?

<字母><类型单词>

泛型,又被称为参数化的类型,即把类型当成参数传递。即在<>中传递类型。
在这里插入图片描述

泛型的好处

没有泛型,需要向下转型,而且有风险,可能在运行时发生ClassCastException。

有了泛型,可以在编译时就做类型检查,而不是等到运行时发生ClassCastException才发现有问题。
在这里插入图片描述

泛型的应用场景之一:泛型类或泛型接口(重点)

java.lang.Comparable:这个在接口名后面,把Comparable称为泛型接口。

java.util.Comparator:它也是泛型接口。

实现泛型接口

package com.gj.generic;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student implements Comparable<Student>{
    private int id;
    private String name;
    private int score;

    @Override
    public int compareTo(Student o) {
        return this.id-o.id;
        //在compare方法中有局部变量id与成员变量id重名吗?没有,就可以省略this.
    }
}
package com.gj.generic;

import org.junit.Test;

import java.util.Arrays;
import java.util.Comparator;

public class TestGeneric {
    @Test
    public void test1(){
        //定义一个定制比较器实现Comparator接口,用于比较两个学生对象的成绩
        Comparator<Student> c = new Comparator<Student>(){
            @Override
            public int compare(Student o1, Student o2) {
                return o1.getScore()-o2.getScore();
            }
        };//匿名内部类实现泛型接口Comparator

        Student[] arr = new Student[3];
        arr[0] = new Student(2,"熊二",89);
        arr[1] = new Student(1,"熊大",100);
        arr[2] = new Student(3,"光头强",99);

        System.out.println("按照id排序:");
        Arrays.sort(arr);
        for (Student student : arr) {
            System.out.println(student);
        }

        System.out.println("按照成绩排序:");
        Arrays.sort(arr, c);
        for (Student student : arr) {
            System.out.println(student);
        }
    }
}

泛型类创建对象

package com.gj.generic;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/*
坐标类,包含x,y坐标
x,y的类型可能是String,可能是double,可能是int类型
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Coordinate<T> {
    private T x;
    private T y;

   /* public Coordinate() {
    }

    public Coordinate(T x, T y) {
        this.x = x;
        this.y = y;
    }

    public T getX() {
        return x;
    }

    public void setX(T x) {
        this.x = x;
    }

    public T getY() {
        return y;
    }

    public void setY(T y) {
        this.y = y;
    }

    @Override
    public String toString() {
        return "Coordinate{" +
                "x=" + x +
                ", y=" + y +
                '}';
    }*/
}
package com.gj.generic;

import org.junit.Test;

public class TestCoordinate {
    @Test
    public void test1(){
        /*
        x代表维度:北纬23.5
        x代表经度:东经46.6
         */
        Coordinate<String> c = new Coordinate<>("北纬23.5","东经46.6");
        //从JDK7开始,左边<>里面写了类型,右边<>里面可以省略,但是<>本身不能省略,否则有警告
        System.out.println(c);
    }

    @Test
    public void test2(){
        /*
        数轴的坐标,x=5.2, y=3.6

         */
//        Coordinate<double> c = new Coordinate<>();
        //泛型只支持引用数据类型,不支持基本数据类型
        //对于基本数据类型,要用包装类
        Coordinate<Double> c = new Coordinate<>(5.2,3.6);
        System.out.println(c);
    }
}

继承泛型类

在子类继承时,直接明确泛型父类具体是什么类型
在这里插入图片描述

package com.gj.generic;

import lombok.Data;

@Data
public class Father<T> {
    private T t;

    public Father() {
    }

    public Father(T t) {
        this.t = t;
    }
}
package com.gj.generic;

/*
情况1:继承泛型父类时,就明确<T>是什么类型,子类恢复普通类的身份
 */
public class Son extends Father<String>{
    public Son(String s) {
        super(s);
    }

    public Son() {
    }
}

子类新定义泛型字母,来代替父类的泛型字母

这个泛型字母可以与父类的一样,也可以换一个字母。
在这里插入图片描述

package com.gj.generic;

/*public class Sub<T> extends Father<T>{
}*/
public class Sub<U> extends Father<U>{
    public Sub() {
    }

    public Sub(U u) {
        super(u);
    }
}

泛型的应用场景之二:泛型方法(重点)

在方法的修饰符与返回值类型之间定义了<泛型字母>,这种方法被称为泛型方法。

泛型方法的声明格式

【修饰符】 class 类名{
    【修饰符】 <泛型字母> 返回值类型 方法名(【形参列表】)throws 异常类型列表】{
        方法体语句;
    }
}

泛型方法的<泛型字母>在调用它时,由实参的类型“自动”确定,不需要手动指定。因为泛型方法的<泛型字母>会被用于形参类型的定义。

例如:在java.util.Arrays工具类中有很多泛型方法:

public static <T> T[] copyOf(T[] original, int newLength):复制一个数组,新数组的长度是newLength。

在这里插入图片描述

package com.gj.generic;

public class MyArrays {
    public static Object[] copyOf(Object[] oldArr, int newLength){
        //(1)创建新数组对象,长度为newLength
        Object[] arr = new Object[newLength];
        //(2)复制元素
       // for(int i=0; i<oldArr.length && i<newLength; i++){
        for(int i=0; i<Math.min(oldArr.length,newLength); i++){
            arr[i] = oldArr[i];
        }
        //(3)返回新数组
        return arr;
    }
}
package com.gj.generic;

import org.junit.Test;

import java.time.LocalDate;
import java.util.Arrays;

public class TestMyArrays {
    @Test
    public void test1(){
        String[] strings = {"hello","java","atguigu"};
        Object[] objects = MyArrays.copyOf(strings, 5);
        //使用我们自己定义的MyArrays工具类,不够完美,返回的新数组类型只能是Object[]
        /*String[] newArr = (String[]) objects;
        for (String s : newArr) {
            System.out.println(s);
        }*/
        for (Object object : objects) {
            System.out.println(object);
        }
    }

    @Test
    public void test2(){
        String[] strings = {"hello","java","atguigu"};
        //使用系统定义好的Arrays工具类
        String[] newArr = Arrays.copyOf(strings, 5);
        for (String s : newArr) {
            System.out.println(s);
        }
    }

    @Test
    public void test3(){
        LocalDate[] arr = {LocalDate.now(),LocalDate.of(2024,1,1)};
        LocalDate[] newArr = Arrays.copyOf(arr, 5);
        for (LocalDate d : newArr) {
            System.out.println(d);
        }
    }
}

对比:把泛型定义在类名后面或方法的前面

作用域范围不同
在这里插入图片描述

package com.gj.generic;

public class Demo<T>{
    public void m1(T t){
        System.out.println("Demo.m1");
    }
    public void m2(T t){
        System.out.println("Demo.m2");
    }
}
package com.gj.generic;

import org.junit.Test;

public class TestDemo {
    @Test
    public void test1(){
        Demo<String> d = new Demo<>();
        d.m1("hello");
        d.m2("java");
    }
}
package com.gj.generic;

public class Example {
    public static  <T> void m1(T t){
        System.out.println("Example.m1");
    }
    public static <T> void m2(T t){
        System.out.println("Example.m2");
    }
}
package com.gj.generic;

import org.junit.Test;

public class TestExample {
    @Test
    public void test1(){
        Example e = new Example();
        e.m1(1.0);
        e.m2("hello");
    }
}

是否适用于静态成员

在这里插入图片描述

泛型字母的声明和使用要求

1、无论是在类名后面还是在方法的返回值类型前面,如果想要定义一个<泛型字母>,建议使用单个的大写字母,不要写单词。

  • T:Type的首字母,type是类型的意思,泛指某种类型
  • E:Element的首字母,element是元素的意思
  • K,V:Key的首字母,Value的首字母,(key,value)代表键值对
    在这里插入图片描述

2、为<泛型字母>指定具体类型时,必须指定引用数据类型,不能是基本数据类型。

3、可以定义多个<泛型字母>,字母之间使用逗号分割

4、如果对类型有范围要求,还可以指定上限,而且上限可以有多个。但是上限中类名只能有1个,而且在最左边
在这里插入图片描述在这里插入图片描述

package com.gj.generic;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
//这里extends表示的是<=的关系,不是新定义一个Z类
public class Base <T,U,Z extends Number & Comparable & Cloneable >{
    private T t;
    private U u;
    private Z z;
}
package com.gj.generic;

import org.junit.Test;

public class TestBase {
    @Test
    public void test1(){
//        Base<int,double,int> b = new Base<int, double, int>();//错误,泛型不能指定基本数据类型
//        Base<String,Integer,Double> b1 = new Base<>("hello",1,1.0);
       // Base<String,Integer,String> b2 = new Base<>("hello",1,"world");
    }
}

泛型擦除

泛型擦除后,会失去类型信息,不推荐这么干。Java是严格的、强制类型的语言。
在这里插入图片描述

泛型通配符

必须配合泛型类或泛型接口使用,即<>的前面必须有类名或接口名,而且?必须在<>里面。

  • <?> :代表任意引用数据类型
  • <? extends A>:代表<=A类型
  • <? super A>:代表>=A类型
package com.gj.generic;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Couple<H,W> {//夫妻
    private H husband;
    private W wife;
}
package com.gj.generic;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Human {//人类
    private String name;

}
package com.gj.generic;

public class Chinese extends Human{//中国人
    public Chinese(String name) {
        super(name);
    }

    public Chinese() {
    }
}
package com.gj.generic;

public class American extends Human{//美国人
}
package com.gj.generic;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Robot {//机器人
    private String name;
}
package com.gj.generic;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Demon {//妖
    private String name;

}
package com.gj.generic;

import org.junit.Test;

public class TestCouple {
    //单独?表示任意引用数据类型
    public void marry1(Couple<?,?> c){
        System.out.println(c);
    }

    //? extends A 类型,代表<=A的类型
    public void marry2(Couple<? extends Human,? extends Human> c){
        System.out.println(c);
    }

    //? super A 类型,代表 >= A的类型
    public void marry3(Couple<? super Human,? super Human> c){
        System.out.println(c);
    }
    

    @Test
    public void test1(){
        Couple<Robot,Human> c = new Couple<>();
        c.setHusband(new Robot("爱德华"));
        c.setWife(new Human("朱丽叶"));

        marry1(c);
//        marry2(c);//第一个Robot不满足
    }

    @Test
    public void test2(){
        Couple<Human,Demon> c = new Couple<>();
        c.setHusband(new Human("许仙"));
        c.setWife(new Demon("白娘子"));
        marry1(c);
//        marry2(c);//第二个Demon不满足

    }

    @Test
    public void test3(){
        Couple<Human,Human> c = new Couple<>();
        c.setHusband(new Human("邓超"));
        c.setHusband(new Human("孙俪"));
        marry1(c);
        marry2(c);
    }

    @Test
    public void test4(){
        Couple<Object,Object> c = new Couple<>();
        c.setHusband(new Human("邓超"));
        c.setWife(new Human("孙俪"));
        marry1(c);
//        marry2(c);//Object不满足<=Human
        marry3(c);//Object满足>=Human类型
    }

    @Test
    public void test5(){
        //Chinese继承了Human
        Couple<Chinese,Chinese> c = new Couple<>();
        c.setHusband(new Chinese("邓超"));
        c.setHusband(new Chinese("孙俪"));
        marry1(c);
        marry2(c);//Chinese 满足 <= Human类型
//        marry3(c);//Chinese 不满足 >= Human类型

    }
}

使用通配符的地方,一般不会涉及到修改属性或元素的值。

package com.gj.generic;

import org.junit.Test;

public class TestCouple2 {
    public void marry1(Couple<?,?> c){
        //这里set设置什么类型的对象都会编译报错
        //因为编译时不清楚实参传进来的Couple对象指定的<H,W>具体是什么类型,下面的代码无法满足所有类型
        //无论设置哪种类型,都有风险
/*        c.setHusband(new Human());
        c.setHusband(new Robot());
        c.setHusband(new Demon());
        c.setHusband(new Chinese());
        c.setHusband(new Object());*/
    }
    public void marry2(Couple<? extends Human,?> c){
        //这里set设置什么类型的对象都会编译报错
        //因为编译时不清楚实参传进来的Couple对象指定的<H,W>具体是什么类型,下面的代码无法满足所有类型
        //无论设置哪种类型,都有风险
/*        c.setHusband(new Human());
        c.setHusband(new Robot());
        c.setHusband(new Demon());
        c.setHusband(new Chinese());
        c.setHusband(new Object());*/
    }
    public void marry3(Couple<? super Human,?> c){
        //这里set设置什么类型的对象都会编译报错
        //因为编译时不清楚实参传进来的Couple对象指定的<H,W>具体是什么类型,下面的代码无法满足所有类型
        //无论设置哪种类型,都有风险
        c.setHusband(new Human());
//        c.setHusband(new Robot());
//        c.setHusband(new Demon());
        c.setHusband(new Chinese());
        c.setHusband(new American());
//        c.setHusband(new Object());

    }

    @Test
    public void test3(){
        Couple<Human,Human> c = new Couple<>();
        marry3(c);
        //Chinese也是Human的子类
        Couple<Chinese,Chinese> c2 = new Couple<>();
       // marry3(c2);//marry3要求?代表>=Human类型,Chinese不满足>=Human的类型
        //American也是Human的子类
        Couple<American,American> c3 = new Couple<>();
      //  marry3(c3);//marry3要求?代表>=Human类型,American不满足>=Human的类型
    }

    @Test
    public void test2(){
        Couple<Human,Human> c = new Couple<>();
        marry2(c);
        //Chinese也是Human的子类
        Couple<Chinese,Chinese> c2 = new Couple<>();
        marry2(c2);
        //American也是Human的子类
        Couple<American,American> c3 = new Couple<>();
        marry2(c3);
    }

    @Test
    public void test1(){
        Couple<Human,Human> c = new Couple<>();
        marry1(c);
        Couple<Robot,Robot> c2 = new Couple<>();
        marry1(c2);
    }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值