一篇教会你泛型与自定义泛型

108 阅读7分钟

Java - 泛型 - generic

前言 : 泛型的好处

  • 编译时,检查添加元素的类型,提高安全性
  • 减少了类型转换的次数,提高效率

一、泛型介绍

  • 泛型又称为参数化类型,JDK5.0新增特性,解决数据类型的安全性问题
  • 在类声明或实例化时只要指定好需要的具体的类型即可
  • Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。同时代码更加简洁、健壮。
  • 泛型的作用是:可以在类声明时通过一个标识表示类中某个属性的类型,或者是某个方法的返回值类型,或者是参数类型。

二、泛型类的结构

下面是泛型类的一个简单结构示例:

// Person.java中
/**
 * 一个泛型类示例,声明的时候传入的类型
 */
public class Person<E> {
    /**
     * 可以用作变量的类型
     */
    private E s;
    /**
     * 可以用作方法的返回值
     * @return 当前的泛型对象
     */
    public E getS() {
        return s;
    }
    /**
     * 可以用作方法的形参
     * @param e 当前的泛型对象
     */
    public void setS(E e){
        this.s = e;
    }
}

验证是否是传入的类型:

// main.java中
Person<String> stringPerson = new Person<>();
stringPerson.setS("123"); // 可以传入
stringPerson.setS(123); // 报错
System.out.println(stringPerson.getS() instanceof  String);

// 控制台输出
// true

控制台输出:true

说明是对应的String类型

三、泛型的语法

1、泛型的声明
  • 泛型接口
    • interface 接口名 <T>{}
  • 泛型类
    • class 类名 <K,V>{}
  • 说明:
    • 其中,T,K,V不代表值,而是表示类型
    • 任意字母都可以。常用T表示,是Type的缩写
2、泛型的实例化
  • 在尖括号中写入你想要的类型
Person<String> stringPerson = new Person<>();

四、泛型使用注意事项

  • 泛型里面的T,E只能是引用类型

    • ArrayList<Integer> integers = new ArrayList<Integer>(); // 正确
      ArrayList<int> integers2 = new ArrayList<int>(); // 报错
      
  • 在指定泛型具体类型后,可以传入该类型或者其子类类型

    // Son 类中
    public class Son extends Person<String>{
    }
    
    // Test 类中
    public class Test {
    }
    
    // Main.java中
    ArrayList<Person<String>> people = new ArrayList<>();
    Son son = new Son();
    Test test = new Test();
    
    people.add(son); // 可以传入
    people.add(test); // 报错
    
  • 如果我们不往泛型的<>中写入值,那么默认值就是object

五、泛型题目案例

定义Employee

  1. 该类包含:private 成员变量 name , sal , birthday ,其中birthdayMyDate 类的对象;

  2. 为每一个属性定义 getter , setter 方法;

  3. 重写toString 方法输出 namesalbirthday

  4. MyDate 类包含 :private 成员变量 monthdayyear;并为每一个属性定义gettersetter 方法;

  5. 创建该类的3个对象,并把这些对象放入ArrayList 集合中 (集合用泛型定义),对集合中的元素进行排序,并遍历输出

  6. 排序方式:调用ArrayListsort 方法,传入 Comparator 对象 (使用泛型) ,先按照name排序,如果name 相同,则按生日日期的先后排序。【即:定制排序】

  • MyDate

    import java.util.StringJoiner;
    /**
     * 存储出生日期的对象
     */
    public class MyDate implements Comparable<MyDate>{
        private Integer year;
        private Integer month;
        private Integer day;
    
    
        public MyDate(Integer year, Integer month, Integer day) {
            this.year = year;
            this.month = month;
            this.day = day;
        }
    
    
        public Integer getMonth() {
            return month;
        }
        
        public void setMonth(Integer month) {
            this.month = month;
        }
        
        public Integer getDay() {
            return day;
        }
        
        public void setDay(Integer day) {
            this.day = day;
        }
        
        public Integer getYear() {
            return year;
        }
        
        public void setYear(Integer year) {
            this.year = year;
        }
        
        @Override
        public String toString() {
            return new StringJoiner(", ", MyDate.class.getSimpleName() + "[", "]")
                    .add("year=" + year)
                    .add("month=" + month)
                    .add("day=" + day)
                    .toString();
        }
        
        @Override
        public int compareTo(MyDate o) {
            // 在比较生日年月日
            int yearMinus = year - o.getYear();
            if(yearMinus != 0){
                return yearMinus;
            }
            int monthMinus =  month - o.getMonth();
            if(monthMinus != 0){
                return monthMinus;
            }
            return day - o.getDay();
        }
    }
    
  • Employee

    import java.sql.Time;
    import java.time.LocalDateTime;
    import java.util.StringJoiner;
    
    /**
     * 员工对象
     */
    public class Employee {
        private String name;
        private Integer sal;
        private MyDate birthday;
    
        public Employee() {
        }
    
        public Employee(String name, Integer sal, MyDate birthday) {
            this.name = name;
            this.sal = sal;
            this.birthday = birthday;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getSal() {
            return sal;
        }
    
        public void setSal(Integer sal) {
            this.sal = sal;
        }
    
        public MyDate getBirthday() {
            return birthday;
        }
    
        public void setBirthday(MyDate birthday) {
            this.birthday = birthday;
        }
    
        @Override
        public String toString() {
            return new StringJoiner(", ", Employee.class.getSimpleName() + "[", "]")
                    .add("name='" + name + "'")
                    .add("sal=" + sal)
                    .add("birthday=" + birthday)
                    .toString();
        }
    }
    
  • Main

    import com.hgz.javaDemo.generic.model.ComparatorTest;
    import com.hgz.javaDemo.generic.model.Employee;
    import com.hgz.javaDemo.generic.model.MyDate;
      
    import javax.lang.model.element.VariableElement;
    import java.util.ArrayList;
    import java.util.Comparator;
      
    /**
     * 入口类
     */
    public class Main {
        public static void main(String[] args) {
            Employee employee1 = new Employee("小罗",2500,new MyDate(2024,11,12));
            Employee employee2 = new Employee("小李",5000,new MyDate(2024,11,14));
            Employee employee3 = new Employee("小李",4500,new MyDate(2024,11,13));
            ArrayList<Employee> employees = new ArrayList<>();
            employees.add(employee1);
            employees.add(employee2);
            employees.add(employee3);
      
            // 使用 Comparator 按年龄排序
            Comparator<Employee> ageComparator = new Comparator<Employee>() {
                @Override
                public int compare(Employee emp1, Employee emp2) {
                    if(!(emp1 instanceof Employee && emp2 instanceof Employee) ){
                        return 0;
                    }
      
                    // 先比较名字
                    int i = emp1.getName().compareTo(emp2.getName());
                    if(i != 0){
                        return i;
                    }
      
                    return emp1.getBirthday().compareTo( emp2.getBirthday());
                }
            };
      
            employees.sort(ageComparator);
            for (Employee employee : employees) {
                System.out.println(employee);
            }
        }
    }
    

六、自定义泛型类

/**
 * 1、Tiger 后面泛型,所以我们把Tiger 就称为自定义泛型类
 * 2、T,R,M 泛型的标识符,一般是单个大写字母
 * 3、泛型标识符可以有多个
 */
public class Tiger<T,R,M> {
    String name;
    R r;
    M m;
    T t;
}
自定义泛型使用细节:
  • 普通成员可以使用泛型(属性,方法)
  • 使用泛型的数组,不能初始化
    • 因为数组在new的时候,不能确定当前泛型是什么类型,所以无法在内存空间中开辟
  • 静态方法和属性中不能使用类的泛型
    • 因为static 是与类相关的,在类加载的时候,对象还没有创建,所以也无法确定类型
  • 泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)
  • 如果在创建对象时,没有指定类型,默认是Object

七、自定义泛型接口

/**
 * 泛型接口定义
 */
public interface IUsb<U,R> {
    
    U u; // 无法使用报错
    
    //普通方法中可以使用泛型接口
    R get(U u);
    void hi(R r);
    void run(R r1,R r2,U u1,U u2);

    // 在jdk8 中,可以在接口中,使用默认方法,也是可以使用泛型
    default R method(U u){
        return null;
    }
}
自定义泛型使用细节:
  • 接口中,静态成员也不能使用泛型(这个和泛型类规定一样)

    U u; // 在接口中无法使用会报错
    
  • 泛型接口的类型,在继承接口或者实现接口时确定

  • 没有指定类型,默认为Object

八、自定义泛型方法

// 普通类中定义
public class Test {
    // 泛型方法
    public <T,R> void fly(T t,R r){
        System.out.println("t : "+t);
        System.out.println("r : "+r);
    }
}

// 泛型类中定义
public class Fish<T,R> {
    public void run(){};

    public <U,M> void eat(U u,M m){ //泛型方法

    }
}


// 调用
Test test = new Test();
test.fly("你好",123);
自定义泛型使用细节:
  • 泛型方法,可以定义在普通类中,也可以定义在泛型类中
  • 当泛型方法被调用时,类型会确定
  • public void eat(E e){} , 修饰符后没有<T,R...> eat 方法不是泛型方法,而是使用了泛型
    • public <E> void eat(E e){} 正确使用
  • 在泛型类中定义的时候,标识符最好和泛型类的标识符区分

九、泛型的继承和通配

  • 泛型不具备继承性

    // 虽然String继承自Object 但是这里会直接报错
    ArrayList<Object> strings = new ArrayList<String>();
    
  • <?>:支持任意泛型类型

  • <? extends A> :支持A类以及A类的子类,规定了泛型的上限

  • <? super A>:支持A类以及A类的父类,不限于直接父类,规定了泛型的下限

    举例:

    class AA{}
    class BB extends AA{}
    class CC extends BB{}
    
    import java.security.PublicKey;
    import java.util.ArrayList;
    import java.util.List;
    
    public class GenericTest {
        public static void main(String[] args) {
            ArrayList<Object> list1 = new ArrayList<>();
            ArrayList<String> list2 = new ArrayList<>();
            ArrayList<AA> list3 = new ArrayList<>();
            ArrayList<BB> list4 = new ArrayList<>();
            ArrayList<CC> list5 = new ArrayList<>();
    
            // 如果是 List<?> c 就可以接收任意类型的List
            printCollection1(list1);
            printCollection1(list2);
            printCollection1(list3);
            printCollection1(list4);
            printCollection1(list5);
    
    //        List<? extends AA> c
    //        printCollection2(list1); // 传入不了
    //        printCollection2(list2); // 传入不了
    //        printCollection2(list3); // 传入成功
    //        printCollection2(list4); // 传入成功
    //        printCollection2(list5); // 传入成功
    
    //        List<? super AA> c
    //        printCollection3(list1); // 传入成功
    //        printCollection3(list2); // 传入不了
    //        printCollection3(list3); // 传入成功
    //        printCollection3(list4); // 传入不了
    //        printCollection3(list5); // 传入不了
        }
        public static void printCollection1(List<?> c){
            for (Object o : c) {
                System.out.println(o);
            }
        }
        // ? extends AA 表示 上限 , 可以接收 AA 或者 AA 子类
        public static void printCollection2(List<? extends AA> c){
            for (Object aa : c) {
                System.out.println(aa);
            }
        }
        // ? super 子类类名 AA : 支持AA类以及 AA 类的父类,不限于直接父类
        // 规定泛型的下限
        public static void printCollection3(List<? super AA> c){
            for (Object o : c) {
                System.out.println(o);
            }
        }
    }
    

十、总结

使用泛型可以让我们创建的接口,类,方法,具有很好的拓展性,大大节省我们开发的时间,以及代码量会相应的减少,并且维护起来也会相对更加容易。