21.泛型设计

本章目标

  1. 泛型简介
  2. 泛型类及方法
  3. 泛型接口
  4. 泛型进阶

本章内容

一、泛型简介

Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。

从Java 1.0版本发布以来,变化最大的部分就是泛型,致使JDK 1.5增加泛型机制的主要原因是为了满足在1999年制定的最早的Java规范需求(JSR 14)

Generics: provides compile-time (static) type safety for collections and eliminates the need for most typecasts (type conversion) (specified by JSR 14)

为集合提供编译时(静态)类型安全,并消除了大多数类型转换的需要

1、泛型的特点

  • 泛型可以将类型作为参数进行传递,即类型可以像参数一样实现参数化。
  • 在编译的时候检查类型安全。
  • 所有的强制转换都是自动和隐式的。
  • Java引入泛型是安全简单的,并能提高代码的重用。

2、泛型引入前

在没有泛型的时候,基于通用性的考虑,早期的集合类存储的都是Object类型,一个集合便可以存储任意类型的对象,因为所有对象都是Object的子类型

那么这样做会存在什么样的问题呢,我们先看一个不使用泛型的例子

image-20230715085003849

例如这个程序代码段里就有一个list集合,假设在这个集合里放的是整形对象,而我们很可能不小心把字符串对象放到里面,这时编译器却无法发现其中的错误,直到程序运行时才会发现错误,但为时已晚,这种情况甚至在应用系统已经部署之后才会发生。而利用泛型就可以提前发现这些错误

3、泛型引入后

在编译期就已经报错了,程序员可以提前解决这问题

二、泛型类

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

1、什么是泛型类

泛型类就是一个具有多种类型变量的类,泛型类可以拥有一个类型,也可以拥有多个类型

[访问修饰符] class 类名<T,U,...> {
    T 泛型成员1 ;
    U 泛型成员2;
    ...
}

类型参数使用大写形式,且比较短,这是最常使用的

2、常用泛型标识符

Java泛型类中常用泛型通配符有:

通配符 说明
T (type) 表示具体的一个java类
K V (key value) 分别代表java键值中的Key Value
E (element) 一般在集合中使用,表示集合中的元素类型
表示不确定的 java 类型,经常出现在集合类中

本质上这些个都是通配符,没啥区别,只不过是编码时的一种约定俗成的东西。比如上述代码中的 T ,我们可以换成 A-Z 之间的任何一个 字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替 T ,在可读性上可能会弱一些

3、示例

package com.woniuxy;

public class Test<T> {
    private T value;

    public Test(T value) {
        super();
        this.value = value;
    }

    public static void main(String[] args) {
        Test<String> t1 = new Test<String>("tom");
        System.out.println(t1.value);

        Test<Integer> t2 = new Test<Integer>(22);
        System.out.println(t2.value);

    }

}

4、示例

此时的Key即可以是整形还可以是字符串类型,也或以是其它类型

public class Student<K, V> {
    private K id;
    private V name;

    public Student(K id, V name) {
        super();
        this.id = id;
        this.name = name;
    }

    public static void main(String[] args) {
        Student<Integer, String> student = new Student<Integer, String>(1001, "tom");
        System.out.println(student.id);
        System.out.println(student.name);
        Student<String, String> student2 = new Student<String, String>("s1001", "tom");
        System.out.println(student2.id);
        System.out.println(student2.name);
    }

}

5、泛型方法

泛型方法,是在调用方法的时候指明泛型的具体类型

泛型方法的声明格式:

修饰符 返回类型 方法名(参数列表){ //方法体 }。例如:
public T/void/... hello([T t]){
    return t;
}

示例

package com.woniuxy;

public class Student<K, V> {
    private K id;
    private V name;

    public Student(K id, V name) {
        super();
        this.id = id;
        this.name = name;
    }

    public K getId() {
        return id;
    }

    public void setId(K id) {
        this.id = id;
    }

    public V getName() {
        return name;
    }

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

    public static void main(String[] args) {
        Student<Integer, String> student = new Student<Integer, String>(1001, "tom");
        student.setName("jerry");
        System.out.println(student.getName());
    }

}

三、泛型接口

用于接口中,在接口名末尾对泛型进行声明;

关系型数据库是由表组成的,一张数据表一般保存一类信息,那么张数据表在Java中就对应一个简单Java类(vo类),而且我们会定义一个接口来规范操作这张数据表的实现类开发。

对于实体的操作的只有接口的名称以及每个方法的参数类型不一样,方法的名称以及形式都是一样的,如果有一百张数据表就意味着要定义一百个这样的接口。此时就出现了代码重复的现象,最好的做法是使用一个接口实现多张数据表的数据操作。要实现这样的操作需要使用泛型接口。之前每个接口只能操作一种类型的数据,现在使用泛型接口之后,把要操作的数据类型使用占位符标记,具体使用接口的时候再根据需求指定泛型的类型

1、定义泛型接口

package com.woniuxy.generate;

import java.util.List;

public interface IBaseDao<K, T> {
    /**
     * 查询全部
     * @return
     */
    public List<T> getAll();
    /**
     * 根据id查询
     * @param k
     * @return
     */
    public T queryById(K k);
    /**
     * 添加
     * @param t
     * @return
     */
    public int add(T t);
    /**
     * 修改
     * @param t
     * @return
     */
    public int update(T t);
    /**
     * 删除
     * @param k
     * @return
     */
    public int delete(K k);

}

2、定义员工接口

public interface EmployeeDao extends IBaseDao<Integer,Employee> {

}

3、定义员工的实现类

package com.woniuxy.generate.impl;

import java.util.List;

import com.woniuxy.entity.Employee;
import com.woniuxy.generate.IBaseDao;

public class EmployDaoImpl implements EmployeeDao{

    @Override
    public List<Employee> getAll() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Employee queryById(Integer k) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public int add(Employee t) {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public int update(Employee t) {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public int delete(Integer k) {
        // TODO Auto-generated method stub
        return 0;
    }

}

4、定义部门接口

public interface DeptDao extends IBaseDao<Integer, Dept> {
    long getCount() throws SQLException;
}

5、定义部门的实现类

package com.woniuxy.generate.impl;

import java.util.List;

import com.woniuxy.entity.Dept;
import com.woniuxy.generate.IBaseDao;

public class DeptDaoImpl implements DeptDao {

    @Override
    public List<Dept> getAll() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Dept queryById(Integer k) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public int add(Dept t) {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public int update(Dept t) {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public int delete(Integer k) {
        // TODO Auto-generated method stub
        return 0;
    }

}

四、泛型进阶(了解)

除了用 表示泛型外,还有 这种形式。 被称作无限定的通配符

1、<?>无界通配符

T 是一个确定的类型,通常用于泛型类和泛型方法的定义。

?是一个不确定的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法

可以查看API,看到Class类中有大量的应用

返回类型 方法
static Class<?> forName(String className) 返回与给定字符串名称的类或接口相关联的 类对象。

先看以下示例:

public class MainApp {

    public static void main(String[] args) {
        List<Integer> intlist = new ArrayList<Integer>();
        intlist.add(11);
        intlist.add(22);
        intlist.add(33);
        List<String> strlist = new ArrayList<String>();
        strlist.add("aa");
        strlist.add("bb");
        strlist.add("cc");
        printList(intlist);

    }

    private static void printList(List<Integer> list) {
        for (Integer integer : list) {
            System.out.println(integer);
        }

    }

}

此时,printList方法只能打印List,那如果我们还想打印List strlist,这时该怎么办?

这时我们就需要通过<?>通配符来实现

public class MainApp {

    public static void main(String[] args) {
        List<Integer> intlist = new ArrayList<Integer>();
        intlist.add(11);
        intlist.add(22);
        intlist.add(33);
        List<String> strList = new ArrayList<String>();
        strList.add("aa");
        strList.add("bb");
        strList.add("cc");
        printList(strList);

    }
   /**
     *  定义一个方法,接收所有的集合元素
     * @param  list
     */
    private static void printList(List<?> list) {
        //list.add("bb");报错,因为?是不能确定类型的
        for (Object obj : list) {
            System.out.println(obj);
        }

    }

}

List是一个未知类型的List,不能向List中添加元素,但可以把List,List赋值给List<?>

很多人认为List<?>List<Object >是一样的,其实这是不对的,<Object>表示任意类型,<?>表示未知类型,可以向List<Object>中添加元素,但是不能把List<String>赋值给List<Object> 参考

2、上界通配符 < ? extends E>

上界(上限):用 extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。

返回类型 方法
<U> Class<? extends U> asSubclass(类<U> clazz) 这个 对象来表示由指定的类对象表示的类的子类。

在类型参数中使用 extends 表示这个泛型中的参数必须是 E 或者 E 的子类,这样有两个好处:

  • 如果传入的类型不是 E 或者 E 的子类,编译不成功
  • 泛型中可以使用 E 的方法,要不然还得强转成 E 才能使用
package com.woniuxy.generate;

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

public class MainApp {

    public static void main(String[] args) {
        List<Apple> applelist = new ArrayList<Apple>();
        applelist.add(new Apple());
        applelist.add(new Apple());
        applelist.add(new Apple());
        List<String> strList = new ArrayList<String>();
        strList.add("aa");
        strList.add("bb");
        strList.add("cc");
        printList(applelist);

    }
   /**
     *  定义一个方法,可以接收Fruit,以及Fruit所有的子类的集合元素
     * @param  list
     */
    private static void printList(List<? extends Fruit> list) {
        for (Object obj : list) {
            System.out.println(obj);
        }

    }

    class Fruit {
        public void eat() {
            System.out.println("eat fruit");
        }

        @Override
        public String toString() {
            return "Fruit []";
        }

  }
    class Apple extends Fruit {
        private String name;

        @Override
        public String toString() {
            return "Apple []";
        }

    }

}

3、下界通配符 < ? super E>

下界: 用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object

在类型参数中使用 super 表示这个泛型中的参数必须是 E 或者 E 的父类。

返回类型 方法
Class<? super T> getSuperclass() 返回 类表示此所表示的实体(类,接口,基本类型或void)的超类 类 。
package com.woniuxy.generate;

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

public class MainApp {

    public static void main(String[] args) {
        List<Fruit> applelist = new ArrayList<Fruit>();
        applelist.add(new Fruit());
        applelist.add(new Fruit());
        applelist.add(new Fruit());
        List<String> strList = new ArrayList<String>();
        strList.add("aa");
        strList.add("bb");
        strList.add("cc");
        printList(applelist);

    }
    /**
     *  定义一个方法,可以接收Apple,以及Apple所有的父类集合
     * @param list
     */
    private static void printList(List<? super Apple> list) {
        for (Object obj : list) {
            System.out.println(obj);
        }

    }

    class Fruit {
        public void eat() {
            System.out.println("eat fruit");
        }

        @Override
        public String toString() {
            return "Fruit []";
        }

  }
    class Apple extends Fruit {
        private String name;

        @Override
        public String toString() {
            return "Apple []";
        }

    }

}

4、泛型擦除

Java语言的泛型实现方式是擦拭法(Type Erasure)。

所谓擦拭法是指,虚拟机对泛型其实一无所知,所有的工作都是编译器做的。

public class MainApp2 {
    public static void main(String[] args) throws Exception{
        //Java泛型只在编译阶段起作用,实际运行中不起作用
        List<String> strList = new ArrayList<>();
        List<Integer> intList = new ArrayList<>();
        intList.add(11);
        intList.add(22);
        //intList.add("hello");
        System.out.println(strList.getClass());// 输出:class java.util.ArrayList
        System.out.println(intList.getClass());// 输出:class java.util.ArrayList
        System.out.println(strList.getClass() == intList.getClass());// 输出:true
        //通过java反射的方式可以绕过泛型,
        Class<? extends List> clazz = intList.getClass();
        Method method = clazz.getMethod("add", Object.class);
        method.invoke(intList, "hello");
        System.out.println(intList);
    }

}

如上述代码所示,最终系统返回结果是true,说明List 和List只是在编译期间有效果,当代码在实际运行时,会自动转化成class java.util.ArrayList

posted @ 2025-04-09 14:33  icui4cu  阅读(8)  评论(0)    收藏  举报