泛型
从 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.Comparator
、java.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>
只有泛型类型也相同时,原本有继承关系的类会继续保持继承关系
如果拥有多个泛型类型,第一个泛型类型与父类的泛型类型相同,即可保持继承关系
例如,下图中 MyList<String, Object>
与 List<String>
保持了继承关系
原始类型
泛型方法(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);
}
}
泛型方法 - 类型推断
上图是 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();
}
}
通配符 – 继承
无限制的绝对是最顶层的,其余的按正常继承关系理解即可
通配符 — 注意点
编译器在解析 List.set(int index, E element)
时,无法确定E
的真实类型,所以报错
泛型的使用限制
这里列出一些常用的误区,不必死记,过一遍理解即可
- 基本类型不能作为类型参数
//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();
}
and
、or
、negate
应用
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
}
}