Java 泛型

158 阅读6分钟

1. 什么是泛型

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

1.1 在集合中使用泛型之前可能存在的问题

问题1,类型不安全。因为add()的参数是0bject类型,意味着任何类型的对象都可以添加成功
问题2,需要使用强转操作,繁琐。还有可能导致CLassCastException异常。

1.2 在集合中使用泛型的例子
//在集合中使用泛型的例子
@Test
public void test01(){
    ArrayList<Integer> list = new ArrayList<Integer>();
    list.add(14);
    list.add(634);
    list.add(73);
    list.add(42);

    Iterator<Integer> iterator = list.iterator();
    while (iterator.hasNext()){
        int score;
        score = iterator.next();
        System.out.println(score);
    }
}

//在MAP中使用泛型的例子
@Test
public void test02(){
    HashMap<String, Integer> map = new HashMap<String, Integer>();
    
    map.put("Tom",52);
    map.put("Luccy",75);
    map.put("James",123);
    map.put("Sam",77);

    Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
    Iterator<Map.Entry<String, Integer>> iterator = entrySet.iterator();
    
    while (iterator.hasNext()){
        Map.Entry<String, Integer> entry = iterator.next();
        String name = entry.getKey();
        Integer value = entry.getValue();
        System.out.println(name + "----" + value);
    }
}
1.3 在比较器中使用泛型的例子
MyDate类
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 int getMonth() {
        return month;
    }

    public int getDay() {
        return day;
    }

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

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

    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.getYear();
    }
}
Employee类
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 int getAge() {
        return age;
    }

    public MyDate getBirthday() {
        return birthday;
    }

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

    public void setAge(int age) {
        this.age = age;
    }

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

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

    @Override
    public int compareTo(Employee o) {
        if(o == this){
            return 0;
        }
            return this.name.compareTo(o.name);
    }
}
执行类
public class Employee_Test {

    //自然排序  按名字进行排序 (从小到大)
    @Test
    public void test01(){
        Employee employee1 = new Employee("Tom",18,new MyDate(2000,1,1));
        Employee employee2 = new Employee("Luccy",20,new MyDate(1999,2,2));
        Employee employee3 = new Employee("Kevin",30,new MyDate(1990,3,3));
        Employee employee4 = new Employee("James",38,new MyDate(1985,5,5));
        Employee employee5 = new Employee("Curry",32,new MyDate(1989,6,6));

        //自然排序
        TreeSet<Employee> treeSet = new TreeSet<Employee>();
        treeSet.add(employee1);
        treeSet.add(employee2);
        treeSet.add(employee3);
        treeSet.add(employee4);
        treeSet.add(employee5);

        Iterator<Employee> iterator = treeSet.iterator();
        while(iterator.hasNext()){
            Employee employee = iterator.next();
            System.out.println(employee);
        }
    }

    //定制排序  按生日大小排序(从小到大)
    @Test
    public void test02(){
        Employee employee1 = new Employee("Tom",18,new MyDate(2000,1,1));
        Employee employee2 = new Employee("Luccy",20,new MyDate(1999,2,2));
        Employee employee3 = new Employee("Kevin",30,new MyDate(1990,3,3));
        Employee employee4 = new Employee("James",38,new MyDate(1985,5,5));
        Employee employee5 = new Employee("Curry",32,new MyDate(1989,6,6));

//写法1
        /*
        Comparator<Employee> comparator = new Comparator<Employee>() {
            @Override
            public int compare(Employee o1, Employee o2) {
                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().getYear();
            }

            @Override
            public boolean equals(Object obj) {
                return false;
            }
        };
*/

//写法2  再重写comparator接口时,在接口内部调用自己定义的MyDate的compare(MyDate的自然排序)

        Comparator<Employee> comparator = new Comparator<Employee>() {
            @Override
            public int compare(Employee o1, Employee o2) {
                return o1.getBirthday().compareTo(o2.getBirthday());
            }
        };

        TreeSet<Employee> treeSet = new TreeSet<Employee>(comparator);
        treeSet.add(employee1);
        treeSet.add(employee2);
        treeSet.add(employee3);
        treeSet.add(employee4);
        treeSet.add(employee5);

        Iterator<Employee> iterator = treeSet.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

    }
1.4.使用说明

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

2. 泛型类

2.1 实例
GenericCustom 泛型类
public class GenericCustom<A,B,C> {
    private A a;
    private B b;
    private C c;

    public GenericCustom() {
    }

    public GenericCustom(A a, B b, C c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    /**
     * 静态方法不能使用类的泛型符号
     *
     * @param a
     */

    public A method1(A a) {
        return a;
    }

    public A getA() {
        return a;
    }

    public B getB() {
        return b;
    }

    public C getC() {
        return c;
    }

}
GenericCustom 的继承子类 SubGenericCustom

//1. SubGenericCustom1
public class SubGenericCustom1 extends GenericCustom{
    //SubGenericCustom1不是泛型类
}

//2. SubGenericCustom2
public class SubGenericCustom2 extends GenericCustom<String, Double, Long>{
    //SubGenericCustom2 也不是泛型类
}

//3. SubGenericCustom3 
public class SubGenericCustom3<A,B,C> extends GenericCustom<A,B,C>{
    //SubGenericCustom3 是泛型类
}

//4. SubGenericCustom4
public class SubGenericCustom4<A,B,C,D> extends GenericCustom<A,B,C>{
    //此时SubGenericCustom4也是泛型类
    D d;
    public SubGenericCustom4() {
    }

    public SubGenericCustom4(D d,A a, B b, C c) {
        super(a, b, c);
        this.d = d;
    }
    public D GetD(){
        return d;
    }
    public D show(D d){
        return d;
    }
}
在实例化时,可以指明类的泛型参数的具体类型
@Test
public void test03(){
    GenericCustom<String, Double, Long> genericCustom = new GenericCustom<String, Double, Long>();

    //泛型符号 如果不指定,统统当成Object来看
    GenericCustom genericCustom2 = new GenericCustom();

    //泛型参数在指明时,是不可以使用基本数据类型的!但是可以使用包装类替代基本数据类型。
    GenericCustom<Integer,Integer,Integer> genericCustom3 = new GenericCustom<Integer,Integer,Integer>();
}

测试GenericCustom的子类
@Test
public void test04(){

    //1. 实例化SubGenericCustom1,虽然是GenericCustom的子类,但SubGenericCustom不是泛型类
    SubGenericCustom1 subGenericCustom1 = new SubGenericCustom1();
    //子类SubGenericCustom1未指定GenericCustom<A,B,C>泛型,此A,B,C是Object
    Object a = subGenericCustom1.getA();

    //2. 实例化SubGenericCustom2,虽然是GenericCustom的子类,但SubGenericCustom不是泛型类
    //因为指明了它的类型
    SubGenericCustom2 subGenericCustom2 = new SubGenericCustom2();
    //子类SubGenericCustom2指定GenericCustom<A,B,C>,A,B,C是String, Double, Long
    String a1 = subGenericCustom2.getA();

    //3. 实例化SubGenericCustom3,SubGenericCustom3是泛型类,继承了GenericCustom的未指明类型
    SubGenericCustom3<String, Double, Long> subGenericCustom3 = new SubGenericCustom3<String, Double, Long>();
    String submethod = subGenericCustom3.method1("hello SubGenericCustom3");
    System.out.println(submethod);

    //4. 实例化SubGenericCustom4,SubGenericCustom4是泛型类,比GenericCustom3多一个泛型对象
    SubGenericCustom4<String,String,Double,Long> subGenericCustom4 = new SubGenericCustom4<String, String, Double, Long>();
    Long show1 = subGenericCustom4.show(15042L);
    System.out.println(show1);

}
2.2 使用说明

①我们在声明完自定义泛型类以后,可以在类的内部(比如,属性、方法、构造器中)使用类的泛型。
②我们在创建自定义泛型类的对象时,可以指明泛型参数类型。一旦指明,内部凡是使用类的泛型参数的位置,都具体化为指定的类的泛型类型。
③如果在创建自定义泛型类的对象时,没有指明泛型参数类型,那么泛型将被擦除,泛型对应的类型均按照bject处理,但不等价于object。
经验:泛型要使用一路都用。要不用,一路都不要用。
④泛型的指定中必须使用引用数据类型。不能使用基本数据类型,此时只能使用包装类替换。
⑤除创建泛型类对象外,子类继承泛型类时、实现类实现泛型接口时,也可以确定泛型结构中的泛型参数。
---如果我们在给泛型类提供子类时,子类也不确定泛型的类型,则可以继续使用泛型参数。
---我们还可以在现有的父类的泛型参数的基础上,新增泛型参数。

MARK

①不能使用new E[ ]。但是可以,E[ ] elements = (E[ ])new Object[capacity];
②在类/接口上声明的泛型,在本类或本接口中即代表某种类型,但不可以在静态方法中使用类的泛型。

3. 泛型方法

3.1 格式
权限修饰符 <T> 返回值类型 方法名(形参列表) //通常在形参列表或返回值类型的位置会出现泛型参数T
3.2 实例
//定义泛型方法,将E[]数组元素添加到对应类型的ArrayList中,并返回
public <E> ArrayList<E> copyArrayToList(E[] arr){
    ArrayList<E> list = new ArrayList<E>();
    for (E e:arr) {
        list.add(e);
    }
    return list;
}
//测试定义泛型方法
@Test
public void test05(){
    GenericCustom genericCustom = new GenericCustom();//此时定义对象的泛型跟泛型方法的泛型不是一个
    Integer[] arr = new Integer[]{1,2,4,56};
    System.out.println(genericCustom.copyArrayToList(arr));
}
3.3 MARK

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