• 0824(038天 泛型01)


    0824(038天 泛型01)

    0824(038天 集合框架03 泛型)

    每日一狗(田园犬西瓜瓜

    在这里插入图片描述

    泛型

    1. 泛型

    1.1 引入

    为什么要引入泛型以ArrayList

    • 传入数据时无法进行类型检查(传入时会默认转换为Object)
    • 获取的数据都是Object,无法调用其创建类型的独有方法(要用的时候必须进行强制类型转换)
    • 人为强转数据类型经常会出错 java.lang.ClassCastException

    为了解决类型安全隐患问题,传入取出的类型转换问题,提高类型安全性,在编译阶段就会对类型进行检测。

    • 解决类型安全隐患

    • 便于程序员及时准确地发现问题

    • 提高可靠性并加快开发速度

    平替方案

    在泛型出现之前,Java的程序员可以采用一种变通的办法:将参数的类型均声明为Object类型。
    由于Object类是所有类的父类,所以它可以指向任何类对象,但这样做不能保证类型安全。

    泛型则弥补了Object做法所缺乏的类型安全,也简化了过程,不必显示地在Object与实际操作的数据类型之间进行强制转换。

    通过泛型,所有的强制类型转换都是自动和隐式的。因此,泛型扩展了重复使用代码的能力,而且既安全又简单。

    1.2 什么是泛型

    JDK1.5引入的类型机制:类型参数化。泛型:即将数据类型当作参数进行传递。泛型的本质是类型参数化,将类型转换时的类型检查从运行时提前到了编译时,这个样就可以尽可能的避免因为类型转换所导致的错误。

    public interface Comparable<T> { // 可比较接口
        public int compareTo(T o);
    }
    
    • 1
    • 2
    • 3

    这里的中的内容T就是类型参数,一般建议使用T(Type)或者E(element元素)之类的全大写。

    在声明阶段,这个T/E只是一个占位符,相当于方法中的形参,只有在真实调用的时候才会将指定的类型参数进行传递。

    在声明List阶段E是什么类型不确定,这里的E仅仅充当占位符的作用,在具体调用时类型才能确定,而E的所有位置将被指定的类型所替代。

    1.3 看一下人家是咋用的

    系统是咋用的

    // 用于实现规范类的可比较性
    public interface Comparable<T> { 
        public int compareTo(T o);
    }
    // Comparable 设定传入可比较的类型 Integer 
    public final class Integer 
        // 所有的系统预定义的数值类型包装类都继承与Number
        extends Number 
        // 实现了可比较的接口,当前类不是抽象类,所以必须提供compareTo方法的实现
        implements Comparable<Integer> {
        
        public int compareTo(Integer anotherInteger) {
            return compare(
                this.value,
                anotherInteger.value
            );
        }
        。。。
        public static int compare(int x, int y) {
            return (x < y) ? -1 : ((x == y) ? 0 : 1);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    1.4 基本使用

    规范

    • 针对泛型T使用extends是对传入的T进行类型约束
    • 一般来说再用泛型类的时候前后的泛型参数都是一至的,1.7后可以不用写后边的参数,但是<>还是要写的
    • 泛型中类型的个数没有限制,中间使用,隔开

    语法

    上限通配符,用来限制类型的上限,即传入的类型必须直接或间接的继承实现E

    下限通配符,用来限制类型的下限。即传入的类型必须是E的父类或E实现的接口

    通配符?,通配符只有在创建运行的时候能够找到类型,这种会在写入时报错,无法判定类型,就没有办法添加元素。但是对元素只进行读操作的时候无需特殊操作。

    • ?既不能用于入参也不能用于返参
    • 频繁往外读取内容的,适合用上界extends
    • 经常往里插入的,适合用下界Super

    语法糖

    JDK1.7+开始支持泛型推导/菱形语法

    List<String> list = new ArrayList<String>();
    List<String> list = new ArrayList<>(); // 泛型推导/菱形语法
    
    • 1
    • 2

    典型应用

    需求:获取两个变量之间的比较大的值

    如果需要比较的不是Integer类型,而是Double或是Float类型,那么就需要另外再写max()方法【重载】。引入泛型的目的实际上就是能够在编写max()方法时,不必确定参数a和b的数据类型,而等到调用的时候再来确定这两个参数的数据类型,那么只需要编写一个max()就可以了,这将大大降低程序员编程的工作量。

    package com.yang1;
    
    public class Test01 {
    
    	public static void main(String[] args) {
    		System.out.println(test(5, 6)); // 6
    	}
    	// 针对泛型T使用extends是对传入的T进行类型约束
    	public static <T extends Comparable<T>> T test(T t1, T t2) {
    		return (t1.compareTo(t2) > 0) ? t1 : t2;
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    1.5 几个小方向

    泛型类

    • 如果定义了泛型类,但是在创建对象的时候没有传入类型时会默认设定为Object类型

      A2 a1 = new A2(); // 如果不传入类型即默认会设定为Object类型
      a1.setName(Obj); // obj 可以传入任意的数据类型
      
      • 1
      • 2
    package com.yang1;
    
    import java.util.Date;
    
    public class Test02 {
    
    	public static void main(String[] args) {
    		A2<String, Integer, Date> a1 = new A2<>();
    		a1.setId(20);
    		a1.setName("wangwu");
    		a1.setData(new Date());
    		System.out.println(a1.getData().getDate()); // 24
    		System.out.println(a1); // A2 [name=wangwu, id=20]
    	}
    
    }
    
    class A2<T1, T2, T3> {
    	private T1 name;
    	private T2 id;
    	private T3 data;
    
    	public T3 getData() {
    		return data;
    	}
    
    	public void setData(T3 data) {
    		this.data = data;
    	}
    
    	public T1 getName() {
    		return name;
    	}
    
    	public void setName(T1 name) {
    		this.name = name;
    	}
    
    	public T2 getId() {
    		return id;
    	}
    
    	public void setId(T2 id) {
    		this.id = id;
    	}
    
    	@Override
    	public String toString() {
    		return "A2 [name=" + name + ", id=" + id + "]";
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    有界类型

    • 可以在传入参数的时候对类型进行范围限定
    • 限定使用 // 定义上界来实现对T类型的必须继承指定类或实现对应的接口
    • 上限进行指定时同一个类型只能继承一个类,可以实现多个接口,中间用&进行分隔
    class Test02<T1 extends 
        Number & Comparable<T1> & Serializable> 
    {
        // T1这个类型直接或间接的继承了Number实现了Comparable 和 Serializable两个接口
    }
    
    class MyTest<T1 extends Number, T2 extends Comparable<T2>> {
        private T1 t1;
        private T2 t21;
        private T2 t22;
    
        public T1 getT1() {
            return t1;
        }
    
        public void setT1(T1 t1) {
            this.t1 = t1;
        }
    
        public T2 getT21() {
            return t21;
        }
    
        public void setT21(T2 t21) {
            this.t21 = t21;
        }
    
        public T2 getT22() {
            return t22;
        }
    
        public void setT22(T2 t22) {
            this.t22 = t22;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    泛型方法

    泛型方法就是泛型参数在方法中使用到了,普通方法就可以使用类中泛型的类型

    但是静态方法就必须通过方法的调用的同时传入泛型

    方法和类上都传入了泛型时,采用就近原则来进行类型的定义,没有传入则使用Object类型

    package com.yang2;
    
    public class Test01 {
    
    	public static void main(String[] args) {
    
    		// 成员方法的应用
    		Test2<String, String> t2 = new Test2<>();
    		t2.name = "name";
    		t2.printName("String"); // String::class java.lang.String
    		
    		t2.pp(45); // 45::class java.lang.Integer
    		t2.pp("卧槽"); //卧槽::class java.lang.String
    
    
    		// 静态泛型类的应用
    		Test1.pp(5); // lass java.lang.Integer
    		Test1.pp("5"); // class java.lang.String
    
    	}
    
    }
    
    class Test2<T1, T2> {
    	T1 name;
    
    	void printName(T1 tt) {
    		System.out.println(tt + "::" + tt.getClass());
    
    	}
    	// 这里的泛型T2是重新传入的,调用方法的时候在重新传入泛型,方法没有传入类型则会默认采用Object来进行声明
    	<T2> void pp(T2 t) {
    		System.out.println(t + "::" + t.getClass());
    	}
    }
    
    class Test1 {
    	public static <Type extends Object> void pp(Type t) {
    		System.out.println(t.getClass());
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    1.6 Comparator和Comparable

    这里将compare和equals写在一起为了提醒开发人员这里可能会有二义性,因为排序时使用的数据量只能是compare<=equals;所以compare判定相等不一定equals判定就相等。

    @FunctionalInterface
    public interface Comparator<T> {
        int compare(T o1, T o2); 
        boolean equals(Object obj); 
    }
    public interface Comparable<T> {
    
        public int compareTo(T o);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    Comparable是排序接口;若一个类实现了Comparable接口,就意味着“该类支持排序”。而Comparator是比较器;我们若需要控制某个类的次序,可以建立一个“该类的比较器”来进行排序。
    Comparable相当于“内部比较器”,而Comparator相当于“外部比较器”。

    package com.yang1;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.List;
    import java.util.Random;
    
    public class Test04 {
    
    	public static void main(String[] args) {
    		List<Pig> ps = new ArrayList<>();
    		Random r = new Random();
    		for (int i = 0; i < 10; i++) {
    			ps.add(new Pig(r.nextDouble() * 20 + 200));
    		}
    		Collections.sort(ps);
    		System.out.println(ps);
            // [Pig [201.28745667269567], Pig [204.5633698990805], Pig [206.90634233144894], Pig [212.07127553335187], Pig [212.5907032865837]]
    
    		// 逆序 return -ress;
    		Collections.sort(ps, (Pig o1, Pig o2) -> {
    			Double res = o1.getWeight() - o2.getWeight();
    			int ress;
    			if (res > 1e-6) {
    				ress = 1;
    			} else if (res < -1e-6) {
    				ress = -1;
    			} else {
    				ress = 0;
    			}
    			return -ress;
    		}
    
    		);
    		System.out.println(ps);
            // [Pig [212.5907032865837], Pig [212.07127553335187], Pig [206.90634233144894], Pig [204.5633698990805], Pig [201.28745667269567]]
    
    		// 逆序的逆序 com.reversed()
    		Collections.sort(ps, new Comparator<Pig>() {
    
    			@Override
    			public int compare(Pig o1, Pig o2) {
    				Double res = o1.getWeight() - o2.getWeight();
    				int ress;
    				if (res > 1e-6) {
    					ress = 1;
    				} else if (res < -1e-6) {
    					ress = -1;
    				} else {
    					ress = 0;
    				}
    				return -ress;
    			}
    
    		}.reversed());
    		System.out.println(ps);
            // [Pig [201.28745667269567], Pig [204.5633698990805], Pig [206.90634233144894], Pig [212.07127553335187], Pig [212.5907032865837]]
    
    	}
    
    }
    
    class Pig implements Comparable<Pig> {
    	private Double weight;
    
    	public Pig(Double weight) {
    		super();
    		this.weight = weight;
    	}
    
    	public Double getWeight() {
    		return weight;
    	}
    
    	public void setWeight(Double weight) {
    		this.weight = weight;
    	}
    
    	@Override
    	public String toString() {
    		return "Pig [weight=" + weight + "]";
    	}
    
    	@Override
    	public int compareTo(Pig o) {
    		Double res = weight - o.getWeight();
    		if (res > 1e-6) {
    			return 1;
    		} else if (res < -1e-6) {
    			return -1;
    		} else {
    			return 0;
    		}
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97

    1.7 小案例

    创建不确定类型

    有一个接口IA,但是具体实现类不确定,具体实现类的创建类的创建交给配置文件,定义类或者方法时,不能确定所需要的参数类型。

    实现当时使模板模式用做的排序

    package com.yang2;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Random;
    
    public class Test02 {
    	private <T extends Comparable<T>> void sort(List<T> list) {
    		for (int i = 1; i < list.size(); i++) {
    			for (int j = 0; j < list.size() - i; j++) {
    				if (list.get(j).compareTo(list.get(j + 1)) > 0) {
    					T tmp = list.get(j);
    					list.set(j, list.get(j + 1));
    					list.set(j + 1, tmp);
    				}
    			}
    		}
    	}
    
    	public static void main(String[] args) {
    		Test02 t2 = new Test02();
    		List<Integer> list = new ArrayList<>();
    		Random r = new Random();
    		for (int i = 0; i < 10; i++) {
    			list.add(r.nextInt(100));
    		}
    		System.out.println(Arrays.toString(list.toArray()));
    		// [7, 31, 69, 39, 59, 71, 54, 99, 57, 17]
    		t2.sort(list);
    		System.out.println(Arrays.toString(list.toArray()));
    		// [7, 17, 31, 39, 54, 57, 59, 69, 71, 99]
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    1.8 泛型类的继承

    泛型类也是可以继承的,任何一个泛型类都可以作为父类或子类。不过泛型类与非泛型类在继承时的主要区别在于: