泛型

从 Java 5 开始,增加了泛型技术

什么是 泛型

  • 类型变为参数,提高代码复用率

建议的类型参数名称:

T :Type
• E :Element
• K :Key
• N :Number
• V :Value
• S、U、V :2nd, 3rd, 4th types


文章目录

  • 泛型
  • 泛型类型
  • 多个类型参数
  • 泛型类型的继承
  • 原始类型
  • 泛型方法(Generic Method)
  • 泛型方法 - 类型推断
  • 泛型方法 – 构造方法
  • 限制类型参数
  • 限制类型参数 — 接收泛型数组返元素最大值方法
  • 限制类型参数 — 要求传入的类型必须可比较
  • 通配符(Wildcards)
  • 通配符 — 上界(`extends`)
  • 通配符 — 下界(`super`)
  • 通配符 — 无限制
  • 通配符 – 继承
  • 通配符 — 注意点
  • 泛型的使用限制
  • 常用函数式接口
  • Supplier:优化不一定执行的代码
  • Consumer:接收一个值决定要做什么
  • Predicate:让过滤条件更灵活
  • Function:类型转换
  • 综合实例(Iterable、Predicate、Function、Consumer、Stream)


泛型类型

什么是泛型类型?

  • 使用了泛型的类或者接口
    比如:java.util.Comparatorjava.util.Comparable
public class Student <T> {
	private T score;
	public T getScore() {
		return score;
	}
	public void setScore() {
		this.score = score;
	}
}
// Java 7 以前的写法
Student<String>	stu = new Student<String>();

// Java 7开始, 可以省略右边<>中的类型(钻石语法)
Student<String> stu1 = new Student<>();
stu1.setScore("A");
String score1 = stu1.getScore();

Student<Double> stu2 = new Student<>();
stu2.setScore(98.5);
Double score2 = stu2.getScore();

多个类型参数

public class Student <N, S {
	private N no;
	private S score;
	public Student(N no, S score) {
		this.no = no;
		this.score = score;
	}
}
Student<String, String> s1 = new Student<>("E9527", "A++");
Student<Integer, Double> s2 = new Student<>(17210224, 96.5);

泛型类型的继承

如果没有泛型类型的类之间有继承关系加了泛型类型不一定会保持继承关系
例如 Integer 类继承自 Number,但是 Box<Integer> 不继承 Box<Number>

java的集合在最前面加元素_类型参数

只有泛型类型相同时,原本有继承关系的类会继续保持继承关系

java的集合在最前面加元素_类型参数_02

如果拥有多个泛型类型第一个泛型类型父类的泛型类型相同,即可保持继承关系
例如,下图中 MyList<String, Object>List<String> 保持了继承关系

java的集合在最前面加元素_后端_03

原始类型

java的集合在最前面加元素_java的集合在最前面加元素_04

泛型方法(Generic Method)

什么是泛型方法?

  • 使用了泛型的方法(实例方法、静态方法、构造方法),比如 Arrays.sort(T[], Comparator<T>)
public class Main {
	public static void main(String[] args) {
		Student<String, String> s1 = new Student<>();
		// 可以像下面这么写, 但一般不会写的这么麻烦
		Main.<String, String>set(s1, "K99", "C++");
		
		Student<Integer, Double> s2 = new Student<>();
		// 编译器可以自动推断出类型参数的具体类型
		set(s2, 25, 99.5);
	}
	
	// 泛型方法(静态方法)
	static <T1, T2> void set(Student<T1, T2> stu, T1 no, T2 score) {
		stu.setNo(no);
		stu.setScore(score);
	}
}

示例

public class Box<E> {
	private E element;
	public Box() {}
	public Box(E element) {
		this.element = element;
	}
}
public class Main {
	public static void main(String[] args) {
		List<Box<Integer>> boxes = new ArrayList<>();
		addBox(11, boxes);
		addBox(22, boxes);
		addBox(33, boxes);
	}
	// 泛型方法(静态方法)
	static <T> void addBox(T element, List<Box<T>> boxes) {
		Box<T> box = new Box<>(element);
		boxes.add(box);
	}
}

泛型方法 - 类型推断

java的集合在最前面加元素_java的集合在最前面加元素_05

上图是 jdk 中 Collections 类的源码,可以看出 emptyList() 是泛型方法,可以自动推断类型

// 根据接收的类是 List<String> 推断出了泛型是 String 类型
List<String> list1 = Collections.emptyList();
// 根据接收的类是 List<Integer> 推断出了泛型是 Integer 类型
List<Integer> list2 = Collections.emptyList();

泛型方法 – 构造方法

public class Person<T> {
    private T age;
    public <E> Person(E name, T age){
    }
}
Person<Integer> p1 = new Person<>("jack",20);
Person<Double> p2 = new Person<>(666,20.6);
Person<String> p3 = new Person<String>(12.34,"00后");

限制类型参数

可以通过 extends 对类型参数增加一些限制条件,比如 <T extends A>

  • extends 后面可以跟上类名接口名,代表 T 必须是 A 类型,或者继承、实现 A
// <T extends Number> 表示传入的类型参数必须是Numbe及其子类
public class Person<T extends Number> {
	private T age;
	public <E> Person(T age) {
		this.age = age;
	}
	public int getAge() {
		// 传入null则视为0, 否则转为int
		return (age == null) ? 0 : age.intValue();
	}
}
public static void main(String[] args) {
	Person<Double> p1 = new Person<>(18.7);
	System.out.println(p1.getAge()); // 18
	Person<Integer> p2; // 正常运行, Integer是Number的子类
	// Person<String> p3; // 会报错, 因为String不是Number的子类
}
  • 可以同时添加多个限制,比如 <T extends A & B & C>,代表 T 必须同时满足 A、B、C,只能有一个类,但是可以有多个接口类必须放在前面

限制类型参数 — 接收泛型数组返元素最大值方法

public class Main {
	public static void main(String[] args) {
		Double[] ds = {5.6, 3.4, 8.8, 4.6};
		System.out.println(getMax(ds)); // 8.8
		
		Integer[] is = {4, 19, 3, 28, 56};
		System.out.println(getMax(is)); // 56
		}
	
	static <T extends Comparable<T>> T getMax(T[] array) {
		if (array == null || array.length == 0) return null;
		T max = array[0];
		for (int i = 0; i < array.length; i++) {
			if (array[i] == null) continue;
			if (array[i].compareTo(max) <= 0) continue;
			max = array[i];
		}
		return max;
	}
}

限制类型参数 — 要求传入的类型必须可比较

public class Student <T extends Comparable<T>> implements Comparable<Student<T>> {
	private T score; // 传入的 T 继承了 Comparable<T>, 表示是可比较的
	public Student(T score) {
		this.score = score;
	}
	@Override
	public int compareTo(Student<T> s) {
		// 传入若为null, 必然比本身小
		if (s == null) return 1;
		// 若自身的score不为null, 则调用compareTo与传入的进行比较
		if (score != null) return score.compareTo(s.score); 
		// 此时, 自身的score为null, 若传入的score也为null则视为相同, 否则就是传入的大
		return s.score == null ? 0 : -1;
	}
	@Override
	public String toString() {
		return "Student [score=" + score + "]";
	}
}
public class Main {
	public static void main(String[] args) {
		Student<Integer>[] stus = new Student[3];
		stus[0] = new Student<>(18);
		stus[1] = new Student<>(38);
		stus[2] = new Student<>(28);
		// Student [score=38]
		System.out.println(getMax(stus));
 	}
	static <T extends Comparable<T>> T getMax(T[] array) {
		if (array == null || array.length == 0) return null;
		T max = array[0];
		for (int i = 0; i < array.length; i++) {
			if (array[i] == null) continue;
			if (array[i].compareTo(max) <= 0) continue;
			max = array[i];
		}
		return max;
	}
}

通配符(Wildcards)

  • 在泛型中,问号 ? 被称为是通配符
  • 通常用作变量类型返回值类型类型参数
  • 不能用作泛型方法调用、泛型类型实例化、泛型类型定义的类型参数

通配符 — 上界(extends

  • 可以通过 extends 设置类型参数的上界

基本使用:

// 类型参数必须是Number类型或者是Number的子类型
void testUpper(Box<? extends Number> box) {}
// Integer是Number的子类
Box<Integer> p1 = null; 
testUpper(p1); // 可以

// Number类型可以作为参数
Box<Number> p2 = null;
testUpper(p2); // 可以

// ? extends Number 表示参数是Number的子类
Box<? extends Number> p3 = null; // 可以
testUpper(p3);

// ? extends Integer 表示参数是Integer的子类, Integer是Number的子类
Box<? extends Integer> p4 = null; // 可以
testUpper(p4);

// String不是Number的子类, 传入testUpeer会报错
Box<String> p5 = null;
// testUpper(p5); // 报错

示例:对泛型数组的元素求和方法(传入的必须是Number的子类型)

public class Main {
	public static void main(String[] args) {
		List<Integer> is = Arrays.asList(1, 2, 3);
		System.out.println(sum(is)); // 6.0
		
		List<Double> ds = Arrays.asList(1.2, 2.3, 3.5);
		System.out.println(sum(ds)); // 7.0
 	}

	static double sum(List<? extends Number> list) {
		double s= 0.0;
		for (Number n : list) {
			s += n.doubleValue();
		}
		return s;
	}	
}

通配符 — 下界(super

  • 可以通过 super 设置类型参数的下界

基本使用:

// 类型参数必须是Integer类型或者是Integer的父类型
void testLower(Box<? super Integer> box) {
Box<Integer> p1 = null;
testLower(p1);

Box<Number> p2 = null;
testLower(p2);

Box<? super Integer> p3 = null;
testLower(p3);

Box<? super Number> p4 = null;
testLower(p4);

示例:往泛型数组中添加元素(传入的必须是Integer的父类型)

public class Main {
	public static void main(String[] args) {
		List<Integer> is = new ArrayList<>();
		addNumbers(is); 
		System.out.println(is); // [1, 2, 3, 4, 5]
		
		List<Number> ns = new ArrayList<>();
		addNumbers(ns);
		System.out.println(ns); // [1, 2, 3, 4, 5]
 	}
	
	static void addNumbers(List<? super Integer> list) {
		for (int i = 1; i <= 5; i++) {
			list.add(i);
		}
	} 	
}

通配符 — 无限制

// 类型参数是什么类型都可以
void test(Box<?> box) {}
Box<Integer> p1 = null;
Box<Integer> p2 = null;
Box<Integer> p3 = null;
Box<? extends Number> p4 = null;
Box<? super String> p5 = null;
Box<?> p6 = null;

test(p1);
test(p2);
test(p3);
test(p4);
test(p5);
test(p6);

示例:打印泛型数组的元素(无限制,任何类型都可以)

public class Main {
	public static void main(String[] args) {
		List<Integer> is = Arrays.asList(1, 2, 3);
		printList(is); // 1 2 3
		
		List<Double> ds = Arrays.asList(1.2, 2.3, 3.5);
		printList(ds); // 1.2 2.3 3.5
		
 	}
	
	static void printList(List<?> list) {
		for (Object object : list) {
			System.out.print(object + " ");
		}
		System.out.println();
	}
}

通配符 – 继承

无限制的绝对是最顶层的,其余的按正常继承关系理解即可

java的集合在最前面加元素_后端_06

通配符 — 注意点

编译器在解析 List.set(int index, E element) 时,无法确定E 的真实类型,所以报错

java的集合在最前面加元素_java_07

泛型的使用限制

这里列出一些常用的误区,不必死记,过一遍理解即可

  • 基本类型不能作为类型参数
//error
Map<int, char> map1 = new HashMap<>(); 
//ok
Map<Integer, Character> map2 = new HashMap<>();
  • 不能创建类型参数的实例
public class Box<E> {
    public void add(Class<E> cls) throws Exception{
        //error
        E e1 = new E();
        //ok
        E e2 = cls.newInstance();
    }
}
  • 不能用类型参数定义静态变量
public class Box<E> {
    //error
    private static E value;
}
Box<Integer> box1 = new Box<>();
Box<String> box2 = new Box<>();
//请问静态变量value是什么类型?Integer还是String?
  • 泛型类型的类型参数不能用在静态方法上
public class Box<E> {
    //error
    public static void show(E value){}
}
  • 类型参数不能跟instanceof一起使用
ArrayList<Integer> list = new ArrayList<>();
    //error
    if (list instanceof ArrayList<Integer>){
}
  • 不能创建带有类型参数的数组
//error
Box<Integer>[] boxes1 = new Box<Integer>[4];
//ok
Box<Integer>[] boxes2 = new Box[4];
  • 下面的方法不属于重载
//error
void test(Box<Integer> box) {

}
void test(Box<String> box){

}
//error
void foo(Box<? extends Number> box) {

}
void foo(Box<String> box){

}
  • 不能定义泛型的异常类
//error
public class MyException<T> extends Exception {

}
  • catch的异常类型不能用类型参数
public static <T> extends Exception {
    try {

    //error
    } catch (T e){}
}
  • 下面代码是正确的
class Parser<T extends Exception> {
    //OK
    public void parse(File file) throws T {
        
    }
}

常用函数式接口

java.util.function 包中提供了很多常用的函数式接口:

  • Supplier
  • Consumer
  • Predicate
  • Function

Supplier:优化不一定执行的代码

有时使用 Supplier 传参,可以避免代码的浪费执行(有必要的时候再执行)

@FunctionalInterface
public interface Supplier<T> {
    /**
     * Gets a result
     *
     * @return a result
     */
    T get();
}

首先看这段例子,getFirstNotEmptyString 返回传入的第一个不为空的字符串,但是有时候会浪费性能,比如第二个字符串是由某个函数构造的,虽然第一个字符串已经确定不为空,但依旧执行了构造第二个字符串的函数

public static void main(String[] args) {
    String s1 = "Jack";
    String s2 = "Rose";
    System.out.println(getFirstNotEmptyString(s1, makeString()));
}

static String makeString() {
    System.out.println("makeString");
    return String.format("%d %d %d", 1, 2, 3);
}

// 获取第一个不为空的字符串, 正常写法
static String getFirstNotEmptyString(String s1, String s2) {
    if (s1 != null || s1.length() > 0) return s1;
    if (s2 != null || s2.length() > 0) return s2;
    return null;
}
输出结果:makeString
		Jack

这时候可以用函数式接口 Supplier 来优化

public static void main(String[] args) {
    String s1 = "Jack";
    // 使用了函数式接口, 不会执行 makeString()
    System.out.println(getFirstNotEmptyString(s1, () -> makeString()));
}

static String makeString() {
    System.out.println("makeString");
    return String.format("%d %d %d", 1, 2, 3); // 1 2 3
}

// 获取第一个不为空的字符串, 函数式接口
static String getFirstNotEmptyString(String s1, Supplier<String> supplier) {
    if (s1 != null || s1.length() > 0) return s1;
    String s2 = supplier.get();
    if (s2 != null || s2.length() > 0) return s2;
    return null;
}
输出结果:jack

Supplier 并不神秘,我们自己也可以写一个接口达到类似效果,这里只是演示一下,实际中类似需求用 Supplier 即可,不需要自己写

public static void main(String[] args) {
    String s1 = "Jack";
    String s2 = "Rose";
    // 使用了函数式接口, 不会执行s1 + s2
    System.out.println(getFirstNotEmptyString3(s1, () -> s1 + s2));
}

@FunctionalInterface
public interface GetString {
    String get();
}

static String getFirstNotEmptyString3(String s1, GetString supplier) {
    if (s1 != null || s1.length() > 0) return s1;
    String s2 = supplier.get();
    if (s2 != null || s2.length() > 0) return s2;
    return null;
}

Consumer:接收一个值决定要做什么

@FunctionalInterface
public interface Consumer<T> {
	void accept(T t);
    default Consumer<T> andThen(Consumer<? super T> after);
}

Consumer 应用:接收一个值,来决定要做什么

public static void main(String[] args) {
    int[] nums = { 11, 33, 44, 88, 77, 66 };
    forEach(nums, (n) -> {
        String result = ((n & 1) == 0) ? "偶数" : "奇数";
        System.out.println(n + "是" + result);
    });	
}

static void forEach(int[] nums, Consumer<Integer> c) {
    if (nums == null || c == null) return;
    for (int n : nums) {
        c.accept(n);
    }
}
11是奇数
33是奇数
44是偶数
88是偶数
77是奇数
66是偶数

andThen 应用

public static void main(String[] args) {
    int[] nums = { 11, 33, 44, 88, 77, 66 };
    forEach(nums, (n) -> {
        String result = ((n & 1) == 0) ? "偶数" : "奇数";
        System.out.println(n + "是" + result);
    }, (n) -> {
        String result = ((n % 3) == 0) ? "能" : "不能";
        System.out.println(n + result + "被3整除");
    });
}

static void forEach(int[] nums, Consumer<Integer> c1, Consumer<Integer> c2) {
    if (nums == null || c1 == null || c2 == null) return;
    for (int n : nums) {
        // 相当于先执行c1的任务, 然后执行c2的任务, 执行完后进入下一轮循环
        c1.andThen(c2).accept(n);
    }
}
11是奇数
11不能被3整除
33是奇数
33能被3整除
44是偶数
44不能被3整除
88是偶数
88不能被3整除
77是奇数
77不能被3整除
66是偶数
66能被3整除

Predicate:让过滤条件更灵活

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
    default Predicate<T> and(Predicate<? super T> other);
    default Predicate<T> negate();
    default Predicate<T> or(Predicate<? super T> other);
    static <T> Predicate<T> isEqual(Object targetRef);
}

Predicate应用:可以不将过滤条件写死,由外面传入更加灵活

public static void main(String[] args) {
    int[] nums = { 11, 33, 44, 88, 77, 66 };
    // 过滤条件: 偶数
    String str = join(nums, (n) -> (n & 1) == 0);
    // 44_88_66
    System.out.println(str);
}

// 将数组元素用_拼接成字符串, 并且满足外面传入的过滤条件
static String join(int[] nums, Predicate<Integer> p) {
    if (nums == null || p == null) return null;
    StringBuilder sb = new StringBuilder();
    for (int n : nums) {
        if (p.test(n)) {
            sb.append(n).append("_");
        }
    }
    sb.deleteCharAt(sb.length() - 1);
    return sb.toString();
}

andornegate 应用

public static void main(String[] args) {
int[] nums = { 11, 33, 44, 88, 77, 66 };
    // 过滤条件: 偶数且能被3整除
    String str1 = join1(nums, (n) -> (n & 1) == 0, (n) -> (n % 3) == 0);
    // 过滤条件: 偶数或能被3整除
    String str2 = join2(nums, (n) -> (n & 1) == 0, (n) -> (n % 3) == 0);
    // 过滤条件: 偶数条件取反, 即奇数
    String str3 = join3(nums, (n) -> (n & 1) == 0);

    System.out.println(str1); // 66
    System.out.println(str2); // 33_44_88_66
    System.out.println(str3); // 11_33_77
}
// and
static String join1(int[] nums, Predicate<Integer> p1, Predicate<Integer> p2) {
    if (nums == null || p1 == null || p2 == null) return null;
    StringBuilder sb = new StringBuilder();
    for (int n : nums) {
        //p1.test(n) && p2.test(n) -> p1.and(p2).test(n)
        if (p1.and(p2).test(n)) {
            sb.append(n).append("_");
        }
    }
    sb.deleteCharAt(sb.length() - 1);
    return sb.toString();
}
// or
static String join2(int[] nums, Predicate<Integer> p1, Predicate<Integer> p2) {
    if (nums == null || p1 == null || p2 == null) return null;
    StringBuilder sb = new StringBuilder();
    for (int n : nums) {
        //p1.test(n) || p2.test(n) -> p1.or(p2).test(n)
        if (p1.or(p2).test(n)) {
            sb.append(n).append("_");
        }
    }
    sb.deleteCharAt(sb.length() - 1);
    return sb.toString();
}
// negate
static String join3(int[] nums, Predicate<Integer> p) {
    if (nums == null || p == null) return null;
    StringBuilder sb = new StringBuilder();
    for (int n : nums) {
        //!p.test(n) -> p.negate().test(n)
        if (p.negate().test(n)) {
            sb.append(n).append("_");
        }
    }
    sb.deleteCharAt(sb.length() - 1);
    return sb.toString();
}

Function:类型转换

@FunctionalInterface
public interface Function<T, R> {
	R apply(T t);
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before);
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after);
    static <T> Function<T, T> identity();
}

Function 应用:类型转换

public static void main(String[] args) {
    String[] strs = { "10", "20", "30" };
    // 将字符串转为int
    int result = sum(strs, Integer::valueOf);
    System.out.println(result);	 // 60
}

static int sum(String[] strs, Function<String, Integer> f) {
    if (strs == null || f == null) return 0;
    int result = 0;
    for (String str : strs) {
        result += f.apply(str);
    }
    return result;
}
public static void main(String[] args) {
    String[] strs = { "10", "20", "30" };
    // 将字符串转为int并模10, 相加
    int result = sum(strs, Integer::valueOf, (i) -> i % 10);
    System.out.println(result); // 0
}

static int sum(String[] strs, Function<String, Integer> f1, Function<Integer, Integer> f2) {
    if (strs == null || f1 == null || f2 == null) return 0;
    int result = 0;
    for (String str : strs) {
        result += f1.andThen(f2).apply(str);
    }
    return result;
}

综合实例(Iterable、Predicate、Function、Consumer、Stream)

public class Person {
    private String name;
    private int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public int getAge() {
        return age;
    }
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }

}
public class Main {
	static <X, Y> void process(
			Iterable<X> eles,
			Predicate<X> tester, 
			Function<X, Y> mapper, 
			Consumer<Y> block) {
		if (eles == null || tester == null
		|| mapper == null || block == null) return;
		for (X ele : eles) {
			if (!tester.test(ele)) continue;
			Y data = mapper.apply(ele);
			block.accept(data);
		}
	}
	
	public static void main(String[] args) {
		
		List<Person> ps = new ArrayList<>();
		ps.add(new Person("Jack", 20));
		ps.add(new Person("Rose", 10));
		ps.add(new Person("Kate", 15));
		ps.add(new Person("Larry", 40));
		
		process(ps, 
				(p) -> p.getAge() >= 15 && p.getAge() <= 25, 
				Person::toString, 
				System.out::println);
		// Person [name=Jack, age=20]
		// Person [name=Kate, age=15]
		
		List<Integer> is = new ArrayList<>();
		is.add(11);
		is.add(22);
		is.add(33);
		is.add(44);
		process(is, 
				(i) -> (i & 1) == 0, 
				(i) -> "test_" + i, 
				System.out::println);
		// test_22
		// test_44
		
		// Stream初体验, 有点牛批啊
		ps.stream()
		.filter((p) -> p.getAge() >= 15 && p.getAge() <= 25)
		.map(Person::toString)
		.forEach(System.out::println);
		// Person [name=Jack, age=20]
		// Person [name=Kate, age=15]
		
		is.stream()
		.filter((i) -> (i & 1) == 0)
		.map((i) -> "test_" + i)
		.forEach(System.out::println);
		// test_22
		// test_44
	}
}