泛型

发布时间 2024-01-11 15:40:44作者: callbin

01-泛型在集合、比较器中的使用


1. 什么是泛型?
所谓泛型,就是允许在定义类、接口时通过一个`标识`表示类中某个`属性的类型`或者是某个方法的
`返回值或参数的类型`。这个类型参数将在使用时(例如,继承或实现这个接口、创建对象或调用方法时)
确定(即传入实际的类型参数,也称为类型实参)。

2. 在集合中使用泛型之前可能存在的问题
问题1:类型不安全。因为add()的参数是Object类型,意味着任何类型的对象都可以添加成功
问题2:需要使用强转操作,繁琐。还有可能导致ClassCastException异常。

3. 在集合、比较器中使用泛型 (重点)
见代码。

题目1:

题目需求

1. 定义一个Employee类。
   该类包含:private成员变量name,age,birthday,其中 birthday 为 MyDate 类的对象;
   并为每一个属性定义 getter, setter 方法;
   并重写 toString 方法输出 name, age, birthday

2. MyDate类包含:
   private成员变量year,month,day;并为每一个属性定义 getter, setter 方法;

3. 创建该类的 5 个对象,并把这些对象放入 TreeSet 集合中(TreeSet 需使用泛型来定义)

4. 分别按以下两种方式对集合中的元素进行排序,并遍历输出:

   1). 使Employee 实现 Comparable 接口,并按 name 排序
   2). 创建 TreeSet 时传入 Comparator对象,按生日日期的先后排序。






package com.atguigu01.use.exer1;

/**
 * ClassName: Employee
 * Description:
 *      定义一个Employee类。
 *          该类包含:private成员变量name,age,birthday,其中 birthday 为 MyDate 类的对象;
 *          并为每一个属性定义 getter, setter 方法;
 *          并重写 toString 方法输出 name, age, birthday
 * @Author 尚硅谷-宋红康
 * @Create 17:03
 * @Version 1.0
 */
public class Employee implements Comparable<Employee>{
    private String name;
    private int age;
    private MyDate birthday;

    public Employee() {
    }

    public Employee(String name, int age, MyDate birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    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;
    }

    public MyDate getBirthday() {
        return birthday;
    }

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

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", birthday=[" + birthday +
                "]}";
    }

    //按照name从低到高排序
    @Override
    public int compareTo(Employee o) {
        return this.name.compareTo(o.name);
    }
}
题目需求
package com.atguigu01.use.exer1;

/**
 * ClassName: MyDate
 * Description:
 * MyDate类包含:
 * private成员变量year,month,day;并为每一个属性定义 getter, setter 方法;
 *
 * @Author 尚硅谷-宋红康
 * @Create 17:04
 * @Version 1.0
 */
public class MyDate implements Comparable<MyDate>{
    private int year;
    private int month;
    private int day;

    public MyDate() {
    }

    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 year + "年" + month + "月" + day + "日";
    }

    //按照生日从小到大排列
    @Override
    public int compareTo(MyDate o) {
        int yearDistince = this.getYear() - o.getYear();
        if(yearDistince != 0){
            return yearDistince;
        }

        int monthDistince = this.getMonth() - o.getMonth();
        if(monthDistince != 0){
            return monthDistince;
        }

        return this.getDay() - o.getDay();

    }
}
MyDate
package com.atguigu01.use.exer1;

import org.junit.Test;

import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;

/**
 * ClassName: EmployeeTest
 * Description:
 *
 * @Author 尚硅谷-宋红康
 * @Create 17:06
 * @Version 1.0
 */
public class EmployeeTest {
    //需求1:使Employee 实现 Comparable 接口,并按 name 排序
    @Test
    public void test1(){

        TreeSet<Employee> set = new TreeSet<>();

        Employee e1 = new Employee("HanMeimei",18,new MyDate(1998,12,21));
        Employee e2 = new Employee("LiLei",20,new MyDate(1996,11,21));
        Employee e3 = new Employee("LiHua",21,new MyDate(2000,9,12));
        Employee e4 = new Employee("ZhangLei",19,new MyDate(1997,5,31));
        Employee e5 = new Employee("ZhangYi",23,new MyDate(2001,11,2));

        set.add(e1);
        set.add(e2);
        set.add(e3);
        set.add(e4);
        set.add(e5);

        //遍历
        Iterator<Employee> iterator = set.iterator();
        while(iterator.hasNext()){
            Employee employee = iterator.next();
            System.out.println(employee);
        }

    }

    //需求2:创建 TreeSet 时传入 Comparator对象,按生日日期的先后排序。
    @Test
    public void test2(){

        Comparator<Employee> comparator = new Comparator<Employee>(){
            @Override
            public int compare(Employee o1, Employee o2) {
                //错误的写法:
//                return o1.getBirthday().toString().compareTo(o2.getBirthday().toString());

                //正确的写法1:
//                int yearDistince = o1.getBirthday().getYear() - o2.getBirthday().getYear();
//                if(yearDistince != 0){
//                    return yearDistince;
//                }
//
//                int monthDistince = o1.getBirthday().getMonth() - o2.getBirthday().getMonth();
//                if(monthDistince != 0){
//                    return monthDistince;
//                }
//
//                return o1.getBirthday().getDay() - o2.getBirthday().getDay();
                //正确的写法2:
                return o1.getBirthday().compareTo(o2.getBirthday());
            }
        };

        TreeSet<Employee> set = new TreeSet<>(comparator);

        Employee e1 = new Employee("HanMeimei",18,new MyDate(1998,12,21));
        Employee e2 = new Employee("LiLei",20,new MyDate(1996,11,21));
        Employee e3 = new Employee("LiHua",21,new MyDate(2000,9,12));
        Employee e4 = new Employee("ZhangLei",19,new MyDate(1996,5,31));
        Employee e5 = new Employee("ZhangYi",23,new MyDate(2000,9,2));

        set.add(e1);
        set.add(e2);
        set.add(e3);
        set.add(e4);
        set.add(e5);

        //遍历
        Iterator<Employee> iterator = set.iterator();
        while(iterator.hasNext()){
            Employee employee = iterator.next();
            System.out.println(employee);
        }
    }
}
测试代码

  题目2:
(1)创建一个ArrayList集合对象,并指定泛型为<Integer>2)添加5个[0,100)以内的随机整数到集合中

(3)使用foreach遍历输出5个整数

(4)使用集合的removeIf方法删除偶数,为Predicate接口指定泛型<Ineteger>5)再使用Iterator迭代器输出剩下的元素,为Iterator接口指定泛型
题目需求
package com.atguigu01.use.exer2;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.function.Predicate;

/**
 * ClassName: Exer02
 * Description:
 *
 * @Author 尚硅谷-宋红康
 * @Create 17:23
 * @Version 1.0
 */
public class Exer02 {
    public static void main(String[] args) {
        //(1)创建一个ArrayList集合对象,并指定泛型为<Integer>
        ArrayList<Integer> list = new ArrayList<>();

        //(2)添加5个[0,100)以内的随机整数到集合中
        for (int i = 0; i < 5; i++) {
            int random = (int)(Math.random() * (99 - 0 + 1));
            list.add(random);

        }

        //(3)使用foreach遍历输出5个整数
        for(Integer value : list){
            System.out.println(value);
        }

        //(4)使用集合的removeIf方法删除偶数,为Predicate接口指定泛型<Ineteger>
        list.removeIf(new Predicate<Integer>(){
            @Override
            public boolean test(Integer value) {
                return value % 2 == 0;
            }
        });
        System.out.println();
        //(5)再使用Iterator迭代器输出剩下的元素,为Iterator接口指定泛型<Integer>
        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            Integer value = iterator.next();
            System.out.println(value);
        }


    }
}
代码
package com.atguigu01.use;

import org.junit.Test;

import java.util.*;

/**
 * @author 尚硅谷-宋红康
 * @create 11:39
 */
public class CollectionMapTest {



    //体会集合中使用泛型前的场景
    @Test
    public void test1(){
        List list = new ArrayList();
        list.add(67);
        list.add(78);
        list.add(76);
        list.add(99);
        //1.问题1:类型不安全。因为add()的参数是Object类型,意味着任何类型的对象都可以添加成功
//        list.add("AA");

        Iterator iterator = list.iterator();
        while(iterator.hasNext()){
            //2.问题2:需要使用强转操作,繁琐。还有可能导致ClassCastException异常。
            Integer i = (Integer) iterator.next();
            int score = i;

            System.out.println(score);
        }
    }



    //在集合中使用泛型的例子
    @Test
    public void test2(){
        List<Integer> list = new ArrayList<Integer>();

        list.add(78);
        list.add(76);
        list.add(66);
        list.add(99);
        //编译报错,保证类型的安全
//        list.add("AA");

        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            //因为添加的都是Integer类型,避免了强转操作
            Integer i = iterator.next();
            int score = i;

            System.out.println(score);
        }

    }


    /*
    * 泛型在Map中使用的例子
    * */
    @Test
    public void test3(){
//        HashMap<String,Integer> map = new HashMap<String,Integer>();

        //jdk7的新特性
        HashMap<String,Integer> map = new HashMap<>(); //类型推断

        map.put("Tom",67);
        map.put("Jerry",87);
        map.put("Rose",99);

//        Set<Map.Entry<String,Integer>> entrySet = map.entrySet();
//        Iterator<Map.Entry<String, Integer>> iterator = entrySet.iterator();

        var entrySet = map.entrySet();
        var iterator = entrySet.iterator();

        while(iterator.hasNext()){
            Map.Entry<String, Integer> entry = iterator.next();
            String key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println(key + "--->" + value);
        }
    }



}
测试代码


4.使用说明
> 集合框架在声明接口和其实现类时,使用了泛型(jdk5.0),在实例化集合对象时,
如果没有使用泛型,则认为操作的是Object类型的数据。
如果使用了泛型,则需要指明泛型的具体类型。一旦指明了泛型的具体类型,则在集合的相关的方法中,凡是使用类的泛型的位置,都替换为具体的泛型类型。



02-自定义泛型


1. 自定义泛型类\接口
1.1 格式
class A<T>{

}

interface B<T1,T2>{
}

1.2 使用说明
① 我们在声明完自定义泛型类以后,可以在类的内部(比如:属性、方法、构造器中)使用类的泛型。
② 我们在创建自定义泛型类的对象时,可以指明泛型参数类型。一旦指明,内部凡是使用类的泛型参数的位置,都具体化为指定的类的泛型类型。
③ 如果在创建自定义泛型类的对象时,没有指明泛型参数类型,那么泛型将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。
- 经验:泛型要使用一路都用。要不用,一路都不要用。
④ 泛型的指定中必须使用引用数据类型。不能使用基本数据类型,此时只能使用包装类替换。

除创建泛型类对象外,子类继承泛型类时、实现类实现泛型接口时,也可以确定泛型结构中的泛型参数。比如:SubOrder2
如果我们在给泛型类提供子类时,子类也不确定泛型的类型,则可以继续使用泛型参数。比如:SubOrder3
我们还可以在现有的父类的泛型参数的基础上,新增泛型参数。比如:比如:SubOrder4,SubOrder5

1.3 注意点
① 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>
② JDK7.0 开始,泛型的简化操作:ArrayList<Fruit> flist = new ArrayList<>();
③ 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
④ 不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity];
参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组。
⑤ 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,但不可以在静态方法中使用类的泛型。
⑥ 异常类不能是带泛型的。


2. 自定义泛型方法
2.1 问题:在泛型类的方法中,使用了类的泛型参数。那么此方法是泛型方法吗?

2.2 格式
权限修饰符 <T> 返回值类型 方法名(形参列表){ //通常在形参列表或返回值类型的位置会出现泛型参数T

}

2.3 举例
public <E> E method(E e){
}

2.4 说明
> 声明泛型方法时,一定要添加泛型参数<T>
> 泛型参数在方法调用时,指明其具体的类型
> 泛型方法可以根据需要声明为static的
> 泛型方法所属的类是否是一个泛型类,都可以。


03-泛型在继承上的体现


1. 类SuperA是类A的父类,则G<SuperA> 与 G<A>的关系:G<SuperA> 和 G<A>是并列的两个类,没有任何子父类的关系。

比如:ArrayList<Object> 、ArrayList<String>没有关系

2. 类SuperA是类A的父类或接口,SuperA<G> 与 A<G>的关系:SuperA<G> 与A<G> 有继承或实现的关系。
即A<G>的实例可以赋值给SuperA<G>类型的引用(或变量)

比如:List<String> 与 ArrayList<String>

04-通配符的使用



1. 通配符: ?

2. 使用说明:
> 举例:ArrayList<?>

> G<?> 可以看做是G<A>类型的父类,即可以将G<A>的对象赋值给G<?>类型的引用(或变量)

3. 读写数据的特点(以集合ArrayList<?>为例说明)
> 读取数据:允许的,读取的值的类型为Object类型
> 写入数据:不允许的。特例:写入null值。

4. 有限制条件的通配符

List<? extends A> : 可以将List<A>或List<B>赋值给List<? extends A>。其中B类是A类的子类。
List <? super A> :可以将List<A>或List<B>赋值给List<? extends A>。其中B类是A类的父类。

5. 有限制条件的统配符的读写操作(难、了解)
技巧:开发中,遇到了带限制条件的通配符,在赋值时,如果没报错,那就正常使用。
如果报错了,知道不能这样写。改改!