Java——泛型设计

一、泛型简介

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

(由于泛型是在编译时起作用,所以在运行时动态生成的反射可以绕过泛型限制)

1、泛型的特点
  • 泛型可以将类型作为参数进行传递,即类型可以像参数一样实现参数化。

  • 在编译的时候检查类型安全。

  • 所有的强制转换都是自动和隐式的。

  • Java引入泛型是安全简单的,并能提高代码的重用。

2、泛型引入前

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

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

image-20230715085003849

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

3、泛型引入后

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

image-20230715085211410

二、泛型类

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

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、定义员工的实现类
package com.woniuxy.generate.impl;
​
import java.util.List;
​
import com.woniuxy.entity.Employee;
import com.woniuxy.generate.IBaseDao;
​
public class EmployDaoImpl implements IBaseDao<Integer, Employee> {
​
    @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;
    }   
​
}
​
3、定义部门的实现类
package com.woniuxy.generate.impl;
​
import java.util.List;
​
import com.woniuxy.entity.Dept;
import com.woniuxy.generate.IBaseDao;
​
public class DeptDaoImpl implements IBaseDao<Integer, Dept> {
​
    @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;
    }
    
​
}
​

四、泛型进阶(了解)

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

1、<?>无界通配符

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

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

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

static 类<?>forName(String className) 返回与给定字符串名称的类或接口相关联的 类对象。
static 类<?>forName(String name, boolean initialize, ClassLoader loader) 使用给定的类加载器返回与给定字符串名称的类或接口相关联的 对象。

先看以下示例:

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<Integer>,那如果我们还想打印List<String> 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) {
        for (Object obj : list) {
            System.out.println(obj);
        }
        
    }
​
}
List<?>`是一个未知类型的`List`,不能向`List<?>`中添加元素,但可以把`List<String>`,`List<Integer>`赋值给`List<?>

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

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

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

<U> 类<? extends U>asSubclass(类<U> clazz) 这个 对象来表示由指定的类对象表示的类的子类。
<A extends Annotation>AgetAnnotation(类<A> annotationClass) 返回该元素的,如果这样的注释 否则返回null指定类型的注释

在类型参数中使用 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 的父类。

类<? 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<Integer> 和List<String>只是在编译期间有效果,当代码在实际运行时,会自动转化成class java.util.ArrayList

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值