Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Java泛型入门介绍--自定义泛型类\方法\通配符的问题

Java泛型入门介绍--自定义泛型类\方法\通配符的问题

作者头像
阑梦清川
发布于 2025-02-24 07:05:33
发布于 2025-02-24 07:05:33
17600
代码可运行
举报
文章被收录于专栏:学习成长指南学习成长指南
运行总次数:0
代码可运行

1.基于问题引入泛型

下面的这个只展示了我们的main方法里面的内容,实际上这个java文件里面还进行了这个cat,dog类的定义,我们想要对于这个狗的名字进行打印,然后加上这个狗的年龄;

我们的一贯做法就是arrayList调用这个里面的list方法实现这个数据的插入,但是当我们的这个里面无意间错写了这个new Cat的时候,这个编译器也是不会检查出来的,我们的cat类和这个dog类之间没有任何的区别,但是这个检查又检查不出来,因为在这个增强for里面使用的是这个Object类进行接受的,因为这个向上转型的原因,因此无论我们的这个arraylist里面是什么样的数据类型,这个都是可以转换成功的;

image-20241020154837730
image-20241020154837730

这个就是我们的普通写法的弊端,如果我们不小心写错了,这个时候编译器又检查不出来,因此这个时候就会报错:

  • 列表里面插入内容的时候没有限制,我们可以随意插入任意类型的数据;
  • 增强for进行遍历的时候需要先进行向上转型,转换为这个object类型的,然后进行向下转型(转换为这个Dog类),因为我们需要对于这个dog里面的getname和getage方法进行调用;但是这个时候转型的时候cat这个不小心加进来的这个对象是无法转换成功的;

上面的两个情况就是我们的普通写法的弊端,基于这个弊端,我们设计了泛型这个语法去解决这个问题和缺陷

image-20241020155759143
image-20241020155759143

2.泛型入门介绍

基于上面出现的问题,我们可以使用泛型进行解决,泛型就是多了个尖括号,和上面的相比: 在这个尖括号里面的就是我们想要在这个数据列表里面存放的内容的类型,我们写成这个Dog>之后就不可以存放其他类型的数据,即使我们不小重新写了一个cat进去,这个时候编译器也是可以自动检查出来的;

并且我们在进行遍历的时候,节省了很多的时间,我们饿可以使用这个for(Dog)直接接收对象,直接打印,如果是之前的需要使用这个object进行这个数据的接受,然后需要进行这个强制类型转换为这个dog类型的这样先进行向上转型,然后是向下转型的操作很浪费时间,我们的泛型不需要进行转型的操作;

image-20241020160736038
image-20241020160736038

3.泛型的使用实例

下面的这个就是使用泛型:往这个hashset和hashmap里面分别进行这个数据的插入,然后就去遍历这个里面的数据内容;

其实只要我们指定了这个泛型的类型(在定义的时候),后面我们使用的时候,这个就会很容易被填充(自动填充)这个就是因为我们在定义的时候已经对于这个数据类型进行了指定;

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class test {
    public static void main(String[] args) {
        //1.-------------对于HashSet进行泛型--------------------
        //使用泛型的方式往这个里面放进去三个学生对象
        
        //下面的这一行就是我们指定这个数据就是studeng对象
        HashSet<Student> students = new HashSet<Student>();
        students.add(new Student("jack",12));
        students.add(new Student("tom",15));
        students.add(new Student("lucy",18));
        //遍历的方式
        for (Student student : students) {
            System.out.println(student);
        }

        //2.------------对于HashMap使用泛型--------------------
        //和上面的集合结构不相同就是我们的这个地方的里面的数据都是k-v类型的,我们的这个泛型不仅需要指定这个key,也是需要指定这个value,当我们在定义的时候对于这个进行指定之后,后面就会自动填充,尤其是使用这个迭代器进行遍历的时候,这个类型就是根据我们定义的这个类型确定的;
        HashMap<String,Student> map = new HashMap<String,Student>();
        map.put("jack1",new Student("tom",25));
        map.put("jack2",new Student("tom",26));
        map.put("jack3",new Student("tom",27));
        Set<Map.Entry<String, Student>> entries = map.entrySet();
        Iterator<Map.Entry<String, Student>> iterator = entries.iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Student> next =  iterator.next();
            System.out.println(next.getKey()+"-"+next.getValue());
        }
    }
}

//下面的这个就是对于类的定义,以及自动生成的这个构造器,tostring方法以及这个javabean写法内容
class Student{
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = 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;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

4.泛型的使用细节

  • 传入数据的类型需要是常用类,不可以是基本的数据类型;
  • 传递进去的这个不仅可以是我们的这个泛型类,也可以是这个类的子类;
  • 泛型的写法:编译器可以进行这个类型的推断
image-20241020172138659
image-20241020172138659

之前我们没有学习这个泛型的时候,这个尖括号里面的默认的类型就是Object类的,下面的这个就是使用的默认的Object,看似我们没有使用泛型,但是这个是有默认的泛型类型的;

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ArrayList objects = new ArrayList();

5.泛型的典型案例

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Test {
    public static void main(String[] args) {
        ArrayList<employee> employees = new ArrayList<>();
        employees.add(new employee("tom",20000,new MyDate(1000,1,1)));
        employees.add(new employee("jack",30000,new MyDate(2000,3,5)));
        employees.add(new employee("lucy",60000,new MyDate(2000,6,5)));

        System.out.println(employees);

        //下面的这个就是一个匿名内部类,依据此进行排序
        employees.sort(new Comparator<employee>() {
            @Override
            public int compare(employee o1, employee o2) {
                //先对于这个参数传入的实参进行验证
                if(!(o1 instanceof employee & o2 instanceof employee))
                {
                    System.out.println("类型不匹配~~");
                    return 0;
                }

                int i=o1.getName().compareTo(o2.getName());
                if(i!=0){
                    return i;
                }

                int yearMinus=o1.getBirthday().getYear()-o2.getBirthday().getYear();
                if(yearMinus!=0){
                    return yearMinus;
                }

                int monthMinus=o1.getBirthday().getMonth()-o2.getBirthday().getMonth();
                if(monthMinus!=0){
                    return monthMinus;
                }

                return o1.getBirthday().getDay()-o2.getBirthday().getDay();

            }
        });

        System.out.println("排序之后的结果");
        System.out.println(employees);
    }
}

class MyDate{
    private int year;
    private int month;
    private int day;

    public MyDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public int getMonth() {
        return month;
    }

    public void setMonth(int month) {
        this.month = month;
    }

    public int getDay() {
        return day;
    }

    public void setDay(int day) {
        this.day = day;
    }

    @Override
    public String toString() {
        return "MyDate{" +
                "year=" + year +
                ", month=" + month +
                ", day=" + day +
                '}';
    }
}


class employee{
    private String name;
    private double salary;
    private MyDate birthday;

    @Override
    public String toString() {
        return "\nemployee{" +
                "name='" + name + '\'' +
                ", birthday=" + birthday +
                ", salary=" + salary +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public MyDate getBirthday() {
        return birthday;
    }

    public void setBirthday(MyDate birthday) {
        this.birthday = birthday;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public employee(String name, double salary, MyDate birthday) {
        this.name = name;
        this.salary = salary;
        this.birthday = birthday;
    }
}

上面的这个是在名字相同的时候,分别对于这个生日里面的年月日进行比较,但是这个在main方法里面写的话,就不够高效,不符合我们的封装性的特点,我们想要实现的效果是一次定义,永久使用,这样的话,我们每一次使用都需要把这个写一遍:

我们可以按照下面的这个方式进行优化调整,我们把这个比较的内容放到我们的这个MyDate里面去,然后再这个主方法里面就可以直接进行调用;

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Test {
    public static void main(String[] args) {
        ArrayList<employee> employees = new ArrayList<>();
        employees.add(new employee("tom",20000,new MyDate(1000,1,1)));
        employees.add(new employee("jack",30000,new MyDate(2000,3,5)));
        employees.add(new employee("jack",30000,new MyDate(2000,3,8)));


        System.out.println(employees);

        //下面的这个就是一个匿名内部类,依据此进行排序
        employees.sort(new Comparator<employee>() {
            @Override
            public int compare(employee o1, employee o2) {
                //先对于这个参数传入的实参进行验证
                if(!(o1 instanceof employee & o2 instanceof employee))
                {
                    System.out.println("类型不匹配~~");
                    return 0;
                }

                int i=o1.getName().compareTo(o2.getName());
                if(i!=0){
                    return i;
                }
				//具体的比较的方法在这个主方法里面,我们的这个只是进行调用即可
                //getBirthday()这个方法的返回值就是mydate
                return o1.getBirthday().compareTo(o2.getBirthday());
            }
        });

        System.out.println("排序之后的结果");
        System.out.println(employees);
    }
}

//实现这个comparable接口
class MyDate implements Comparable<MyDate>{
    private int year;
    private int month;
    private int day;

    //对于这个compare方法进行重写
    @Override
    public int compareTo(MyDate o) {
        //这个其实是this.year-o.year的过程,谁调用这个方法,谁就是this指针
        int yearMinus=year-o.getYear();
        if(yearMinus!=0){
            return yearMinus;
        }

        int monthMinus=month-o.getMonth();
        if(monthMinus!=0){
            return monthMinus;
        }

        return day-o.getDay();
    }

    public MyDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public int getMonth() {
        return month;
    }

    public void setMonth(int month) {
        this.month = month;
    }

    public int getDay() {
        return day;
    }

    public void setDay(int day) {
        this.day = day;
    }

    @Override
    public String toString() {
        return "MyDate{" +
                "year=" + year +
                ", month=" + month +
                ", day=" + day +
                '}';
    }
}


class employee{
    private String name;
    private double salary;
    private MyDate birthday;

    @Override
    public String toString() {
        return "\nemployee{" +
                "name='" + name + '\'' +
                ", birthday=" + birthday +
                ", salary=" + salary +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public MyDate getBirthday() {
        return birthday;
    }

    public void setBirthday(MyDate birthday) {
        this.birthday = birthday;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public employee(String name, double salary, MyDate birthday) {
        this.name = name;
        this.salary = salary;
        this.birthday = birthday;
    }
}

6.自定义泛型类的使用

  • 泛型里面其实可以有多个类型,不只是只有一个;
  • 静态的方法和静态的属性不可以使用泛型语法;
  • 泛型的数组不可以进行初始化,就是不可以进行new操作开辟空间;
image-20241020185638198
image-20241020185638198

7.自定义泛型方法的案例

下面的这个练习主要就是想要说明一个问题:

我们下面的这个Apple里面的这个fly方法用的是自己的泛型E,虽然这个在我们的类这个定义的时候没有出现,但是这个是可以使用的,符合语法的要求;

eat和run两个自定义的方法,都是没有自己的泛型,只是这个参数里面涉及到了泛型,这个和fly方法有本质的区别,在这个访问权限修饰符后面加上这个泛型之后,我们的自定义方法才可以使用自己的这个泛型,但是eat调用的这个在我们的类定义的三个里面没有出现,就会报错,这个run使用的是m在这个类定义的时候出现了,因此这个是可以的;

image-20241020192042812
image-20241020192042812

8.泛型通配符的问题

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package demo9;

import java.util.ArrayList;
import java.util.List;

public class Test {
    public static void main(String[] args) {
        Object o = new String("xx");
		//泛型没有继承性:因此下面的这个无法成功编译
		//List<Object> list = new ArrayList<String>();
		//举例说明下面三个方法的使用
        
        List<Object> list1 = new ArrayList<>();
        List<String> list2 = new ArrayList<>();
        List<AA> list3 = new ArrayList<>();
        List<BB> list4 = new ArrayList<>();
        List<CC> list5 = new ArrayList<>();
        
        
		//如果是 List<?> c , 可以接受任意的泛型类型,因此下面的这几个都是正确的
        printCollection1(list1);
        printCollection1(list2);
        printCollection1(list3);
        printCollection1(list4);
        printCollection1(list5);
        
        
		//List<? extends AA> c: 表示 上限, 可以接受 AA 或者 AA 子类
		// printCollection2(list1);//×   object肯定不是子类
		// printCollection2(list2);//×    string和这个AA也没有任何的关系
        printCollection2(list3);//√      下面的这三个实现了继承,因此是可以的
        printCollection2(list4);//√
        printCollection2(list5);//√
        
        
		//List<? super AA> c: 支持 AA 类以及 AA 类的父类, 不限于直接父类
        printCollection3(list1);//√----------object是所有类的父类,因此这个地方是可以的
		//printCollection3(list2);//×--------string既不是这个类的父类,也不是本身,因此是不可以的
        printCollection3(list3);//√ ------AA类自己因此是可以的
		//printCollection3(list4);//×-----下面的这两个都是子类,因此是不可以的
		//printCollection3(list5);//×

    } 
    
    //? extends AA 表示 上限, 可以接受 AA 或者 AA 子类
    public static void printCollection2(List<? extends AA> c) {
        for (Object object : c) {
            System.out.println(object);
        }
    } 
    
    //说明: List<?> 表示 任意的泛型类型都可以接受
    public static void printCollection1(List<?> c) {
        for (Object object : c) { // 通配符, 取出时, 就是 Object
            System.out.println(object);
        }
    } 
    
    
    //? super 子类类名 AA:支持 AA 类以及 AA 类的父类, 不限于直接父类,
    //规定了泛型的下限
    public static void printCollection3(List<? super AA> c) {
        for (Object object : c) {
            System.out.println(object);
        }
    }
}

class AA { }
class BB extends AA {}
class CC extends BB {}

表示 任意的泛型类型都可以接受 public static void printCollection1(List<?> c) { for (Object object : c) { // 通配符, 取出时, 就是 Object System.out.println(object); } }

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//? super 子类类名 AA:支持 AA 类以及 AA 类的父类, 不限于直接父类,
//规定了泛型的下限
public static void printCollection3(List<? super AA> c) {
    for (Object object : c) {
        System.out.println(object);
    }
}

}

class AA { } class BB extends AA {} class CC extends BB {}

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-02-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Java泛型详解
Dog对象含有name 和 age, 并输出name 和 age (要求使用getXxx())
timerring
2023/05/07
5120
Java泛型详解
JavaSE-汇总
前言 JavaSE 完结,撒花🌸🌸🌸,Java-基础的学习就将告一段落,今天我将之前发布的《Java-XXX》系列学习笔记进行汇总一下,此系列是我做的一个 “Java 从 0 到 1” 实验,给自己一年左右时间,按照我自己总结的 Java-学习路线,从 0 开始学 Java 知识,并不定期更新所学笔记,期待一年后的蜕变吧!<有同样想法的小伙伴,可以联系我一起交流学习哦!> 笔记汇总 环境配置:见本文下方 IDEA 插件:见本文下方 面向对象:Java-面向对象 | 简简 枚举:Java-枚举 |
小简
2023/01/04
1.5K0
JavaSE-汇总
Set集合
  Set接口也是Collection的子接口,Set接口没有提供额外的方法。Set集合支持的遍历方式也和Collection集合一样,使用foreach和Iterator遍历。
别团等shy哥发育
2023/02/25
6530
Set集合
Java-集合
哈喽!大家好,我是小简。今天开始学习《Java-集合》,此系列是我做的一个 “Java 从 0 到 1 ” 实验,给自己一年左右时间,按照我自己总结的 Java-学习路线,从 0 开始学 Java 知识,并不定期更新所学笔记,期待一年后的蜕变吧!<有同样想法的小伙伴,可以联系我一起交流学习哦!>
小简
2023/01/04
1.2K0
Java-集合
java-继承和多态
继承(inheritance) 继承满足“is-a”规则,即Manager is a Employee
卢衍飞
2023/02/16
4050
零基础学Java(11)自定义类[通俗易懂]
  之前的例子中,我们已经编写了一些简单的类。但是,那些类都只包含一个简单的main方法。现在来学习如何编写复杂应用程序所需要的那种主力类。通常这些类没有main方法,却有自己的实例字段和实例方法。要想构建一个完整的程序,会结合使用多个类,其中只有一个类有main方法。
全栈程序员站长
2022/09/19
3720
透过源码学习设计模式6—策略模式与Comparator
定义算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户端。它首先定义不同的算法策略,然后客户端把算法策略作为它的一个参数。使用这种模式的一个不错的例子是Collection.sort()方法了,它使用Comparator对象作为参数。根据Comparator接口不同实现,对象会被不同的方法排序。
java达人
2019/09/24
1.1K0
透过源码学习设计模式6—策略模式与Comparator
Java 中使用 Collections 的最佳实践
Collections 是 Java 中操作集合类数据结构的工具类。它提供了一系列可以操作 List、Set 和 Map 的静态方法,可以辅助开发人员进行集合的常用操作,如排序、搜索、遍历等。
用户1289394
2023/09/11
4160
Java 中使用 Collections 的最佳实践
从数据库中查询马上过生日的人并统计各年龄段及性别所占的人数
业务需求: 从员工表中查询5天之内过生日的人,以及五天之内合同到期的人,返回一个 Map 集合,封装了员工的姓名及还有几天过生日; Dao 层如下: @Repository public interface EmpMapper extends BaseMapper<Employee> { @Select("select * from employee\n" + "where DATE_FORMAT(birthday,'%m-%d') >= DATE_FORMAT(now(
wsuo
2020/07/31
8130
Java基础系列(六):对象与类(上)
封装从形式上看,封装是将数据和行为组合在一个包中,并对对象的使用者隐藏了数据的实现方式。对象中的数据称为实例域,而操纵数据的过程称之为方法。对于每个特定的类实例(对象)都有一组特定的实例域值。这些值的集合就是这个对象的当前状态。而实现封装的关键就是绝对不能让类中的方法直接访问其他类的实例域,仅仅可以通过对象的方法来与对象的数据进行交互。
山禾说
2019/01/21
3310
【Java8新特性】Java8为什么要引入Lambda表达式?原来如此!!
作者个人研发的在高并发场景下,提供的简单、稳定、可扩展的延迟消息队列框架,具有精准的定时任务和延迟队列处理功能。自开源半年多以来,已成功为十几家中小型企业提供了精准定时调度方案,经受住了生产环境的考验。为使更多童鞋受益,现给出开源框架地址:
冰河
2020/10/29
3890
【Java8新特性】Java8为什么要引入Lambda表达式?原来如此!!
一文打通java泛型
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection,List,ArrayList 这个就是类型参数,即泛型
一个风轻云淡
2023/10/15
2150
一文打通java泛型
Java基础知识总结--ArrayList
​ 创建一个存储字符串的集合,存储3个字符串元素,使用程序实现在控制台遍历该集合
小炜同学
2022/09/23
3180
如何使用 Java 泛型来避免 ClassCastException
泛型是相关语言特性的集合,它允许类或方法对各种类型的对象进行操作,同时提供编译时类型安全性检查
Java宝典
2021/01/28
2.3K0
Java进阶:【泛型】认识泛型,泛型方法,泛型类,泛型接口和通配符
例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。
冷环渊
2021/11/29
3.9K0
Java进阶:【泛型】认识泛型,泛型方法,泛型类,泛型接口和通配符
Java知识点总结之Java泛型
作者:苏生 链接: https://segmentfault.com/a/1190000014824002 泛型 泛型就是参数化类型 适用于多种数据类型执行相同的代码 泛型中的类型在使用时指定 泛型归根到底就是“模版” 优点:使用泛型时,在实际使用之前类型就已经确定了,不需要强制类型转换。 泛型主要使用在集合中 import java.util.ArrayList; import java.util.List; public class Demo01 { // 不使用泛型,存取数据麻烦
用户1257393
2018/07/30
5810
8万字总结的Java8新特性!!
作者个人研发的在高并发场景下,提供的简单、稳定、可扩展的延迟消息队列框架,具有精准的定时任务和延迟队列处理功能。自开源半年多以来,已成功为十几家中小型企业提供了精准定时调度方案,经受住了生产环境的考验。为使更多童鞋受益,现给出开源框架地址:
冰河
2022/06/15
1.2K0
8万字总结的Java8新特性!!
Java泛型总结
集合容器类“设计阶段/声明阶段”不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为 Object,JDK1.5 之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection<E>,List<E>,ArrayList<E>这个 <E> 就是类型参数,即泛型。
乐心湖
2021/01/18
8930
Java泛型总结
第十八天 集合-泛型&list接口&set接口【面试+工作】
泛型的使用:一般在创建对象时,将未知的类型确定具体的类型。当没有指定泛型时,默认类型为Object类型。
Java帮帮
2018/07/26
8180
第十八天 集合-泛型&list接口&set接口【面试+工作】
4.3 用户自定义类
4.3.1 Employee类 package class_; import java.util.Date; import java.util.GregorianCalendar; public class EmployeeTest { public static void main(String args[]) { Employee[] staff = new Employee[3]; staff[0] = new Employee("Carl Cracker", 75000, 1987,
Mister24
2018/05/14
8510
相关推荐
Java泛型详解
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验