Java-泛型
文章出处:
参考+摘抄: https://www.yuque.com/qingkongxiaguang/javase/rk6if6#dcab57b7
泛型类
泛型其实就一个待定类型,我们可以使用一个特殊的名字表示泛型,泛型在定义时并不明确是什么类型,而是需要到使用时才会确定对应的泛型类型。
我们可以将一个类定义为一个泛型类
package cn.meowrain.generic;
public class Main {
public static void main(String[] args) {
Score<String> score01 = new Score<String>("meow",13,"优秀");
String value01 = score01.score;
System.out.println(value01); // 优秀
Score<Integer> score02 = new Score<Integer>("meowrain",18,100);
int value02 = score02.score;
System.out.println(value02); // 100
}
}
class Score<T> {//泛型类需要使用<>,我们需要在里面添加1 - N个类型
String name;
int age;
T score;//T会根据使用时提供的类型自动变成对应类型
public Score(String name, int age, T score) {
//这里T可以是任何类型,但是一旦确定,那么就不能修改了
this.name = name;
this.age = age;
this.score = score;
}
}
泛型将数据类型的确定控制在了编译阶段,在编写代码的时候就能明确泛型的类型,如果类型不符合,将无法通过编译!因为是具体使用对象时才会明确具体类型,所以说静态方法中是不能用的:
只不过这里需要注意一下,我们在方法中使用待确定类型的变量时,因为此时并不明确具体是什么类型,那么默认会认为这个变量是一个Object类型的变量,因为无论具体类型是什么,一定是Object类的子类:
我们可以对其进行强制类型转换,但是实际上没多大必要:
public void test(T t){
String str = (String)t; //向下转型,Object转型为String类型
}
因为泛型本身就是对某些待定类型的简单处理,如果都明确要使用什么类型了,那大可不必使用泛型。还有,不能通过这个不确定的类型变量就去直接创建对象和对应的数组:
注意,具体类型不同的泛型类变量,不能使用不同的变量进行接收:
?
通配符
如果要让某个变量支持引用确定了任意类型的泛型,那么可以使用?
通配符:
package cn.meowrain.generic;
public class Main {
public static void main(String[] args) {
Test<?> test = new Test<>("meowrain",18,98.5);
System.out.println(test.name + " " + test.age + " " + test.score);
}
}
class Test<T> {
T name;
T age;
T score;
public Test(T name, T age, T score) {
this.name = name;
this.age = age;
this.score = score;
}
}
定义多个泛型变量
当然,泛型变量
不止可以只有一个,如果需要使用多个的话,我们也可以定义多个:
package cn.meowrain.generic;
import cn.meowrain.Poly.poly04.Test;
public class Main {
public static void main(String[] args) {
Score<String,Integer,Double> score = new Score<>("meowrain",17,98.5);
}
}
class Score<A,B,C> {//泛型类需要使用<>,我们需要在里面添加1 - N个类型
A name;
B age;
C score;//T会根据使用时提供的类型自动变成对应类型
public Score(A name, B age, C score) {
this.name = name;
this.age = age;
this.score = score;
}
}
只要是在类中,都可以使用类型变量:
public class Test<T>{
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
注意事项
只不过,泛型只能确定为一个引用类型,基本类型是不支持的:
如果要存放基本数据类型的值,我们只能使用对应的包装类:
package cn.meowrain.generic;
public class Main {
public static void main(String[] args) {
Test<Integer> test = new Test<Integer>();
test.value = 10;
System.out.println(test.value);
}
}
class Test<T> {
T value;
}
当然,如果是基本类型的数组,因为数组本身是引用类型,所以说是可以的:
package cn.meowrain.generic;
public class Main {
public static void main(String[] args) {
Test<int[]> test = new Test<>();
test.value = new int[]{1, 2, 3, 4, 5, 6};
for(int i = 0;i<test.value.length;i++){
System.out.println(test.value[i]);
}
}
}
class Test<T> {
T value;
}
泛型与多态
不只是类,包括接口、抽象类,都是可以支持泛型的:
interface Study<T> { T learn(); }
当子类实现此接口时,我们可以选择在实现类明确泛型类型,或是继续使用此泛型让具体创建的对象来确定类型:
package cn.meowrain.generic; public class Main { public static void main(String[] args) { Student stu1 = new Student("xiaoming"); System.out.println(stu1.learn());//xiaoming is learning } } class Student implements Study<String> { //在实现接口或是继承父类时,如果子类是一个普通类,那么可以直接明确对应类型 String name; public Student(String name) { this.name = name; } @Override public String learn() { return (this.name + " is learning"); } } interface Study<T> { T learn(); }
继续使用泛型:
package cn.meowrain.generic; public class Main { public static void main(String[] args) { Student<String> stu1 = new Student<>("xiaoming"); System.out.println(stu1.learn());//null } } class Student<T> implements Study<T> { String name; public Student(String name) { this.name = name; } @Override public T learn() { ///让子类继续为一个泛型类,那么可以不用明确 return null; } } interface Study<T> { T learn(); }
继承也是同样的
package cn.meowrain.generic; public class Main { public static void main(String[] args) { Student stu1 = new Student("xiaoming",17,"game","girls",99); System.out.println(stu1); } } class Human<T> { String name; int age; String hobby; T like; public Human(String name, int age, String hobby, T like) { this.name = name; this.age = age; this.hobby = hobby; this.like = like; } } class Student extends Human<String>{ int score; public Student(String name, int age, String hobby, String like, int score) { super(name, age, hobby, like); this.score = score; } @Override public String toString() { return "Student{" + "score=" + score + ", name='" + name + '\'' + ", age=" + age + ", hobby='" + hobby + '\'' + ", like=" + like + '}'; } }
泛型方法
当然,类型变量并不是只能在泛型类中才可以使用,我们也可以定义泛型方法。
当某个方法(无论是是静态方法还是成员方法)需要接受的参数类型并不确定时,我们也可以使用泛型来表示:
package cn.meowrain.generic;
public class Main {
public static void main(String[] args) {
String str = test("mike");
System.out.println(str);
}
public static <T> T test(T t) { ////在返回值类型前添加<>并填写泛型变量表示这个是一个泛型方法
//返回类型为T ,参数类型为T
return t;
}
}
泛型方法会在使用时自动确定泛型类型,比如上我们定义的是类型T作为参数,同样的类型T作为返回值,实际传入的参数是一个字符串类型的值,那么T就会自动变成String类型,因此返回值也是String类型。
package cn.meowrain.generic;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
String[] strings = new String[1];
Main main = new Main();
main.add(strings,"hello");
System.out.println(Arrays.toString(strings));//[hello]
Integer[] arr = new Integer[1];
main.add(arr,1);
System.out.println(Arrays.toString(arr));//[1]
}
public <T> void add(T[] arr, T t) {
arr[0] = t;
}
}
泛型在工具类中的应用
实际上泛型方法在很多工具类中也有,比如说Arrays的排序方法:
package cn.meowrain.generic;
import java.util.Arrays;
import java.util.Comparator;
public class Main {
public static void main(String[] args) {
Integer[] arr = {1, 4, 5, 2, 6, 3, 0, 7, 9, 8};
//从小到大排列
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});//[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
System.out.println(Arrays.toString(arr));
//从大到小排序
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});//[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
System.out.println(Arrays.toString(arr));
}
}
因为我们前面学习了Lambda表达式,像这种只有一个方法需要实现的接口,直接安排了:
package cn.meowrain.generic;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
Integer[] arr = {1, 4, 5, 2, 6, 3, 0, 7, 9, 8};
//从小到大排列
Arrays.sort(arr, (o1, o2) -> o1 - o2);//[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
System.out.println(Arrays.toString(arr));
//从大到小排序
Arrays.sort(arr, (o1, o2) -> o2 - o1);//[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
System.out.println(Arrays.toString(arr));
}
}
包括数组复制方法:
package cn.meowrain.generic;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
String[] arr = {"aaa", "bbb", "ccc"};
String[] newArr = Arrays.copyOf(arr, arr.length);
System.out.println(Arrays.toString(newArr));//[aaa, bbb, ccc]
}
}
泛型的界限
上限
现在有一个新的需求,现在没有String类型的成绩了,但是成绩依然可能是整数,也可能是小数,这时我们不希望用户将泛型指定为除数字类型外的其他类型,我们就需要使用到泛型的上界定义:
package cn.meowrain.generic;
public class Main {
public static void main(String[] args) {
Score<Integer> stu1 = new Score<>("xiaoming",1,100);
Score<Double> stu2 = new Score<>("xiaohong",2,120.03);
System.out.println(stu1.getValue()); // 100
System.out.println(stu2.getValue()); // 120.03
}
}
class Score<T extends Number>{
////设定类型参数上界,必须是Number或是Number的子类
private final String name;
private final int id;
private final T value;
public Score(String name, int id, T value) {
this.name = name;
this.id = id;
this.value = value;
}
public T getValue() {
return value;
}
public String getName() {
return name;
}
public int getId() {
return id;
}
}
只需要在泛型变量的后面添加extends
关键字即可指定上界,使用时,具体类型只能是我们指定的上界类型或是上界类型的子类,不得是其他类型。否则一律报错:
像是这样
泛型通配符在泛型界限中的使用
同样的,当我们在使用变量时,泛型通配符也支持泛型的界限:
package cn.meowrain.generic;
public class Main {
public static void main(String[] args) {
Score<? extends Number> score = new Score<>("数据结构与算法", "EP074512", 60);
}
}
class Score<T>{
private final String name;
private final String id;
private final T value;
public Score(String name, String id, T value) {
this.name = name;
this.id = id;
this.value = value;
}
public T getValue() {
return value;
}
public String getName() {
return name;
}
public String getId() {
return id;
}
}
那么限定了上界后,我们再来使用这个对象的泛型成员,会变成什么类型呢?
package cn.meowrain.generic;
public class Main {
public static void main(String[] args) {
Score<? extends Number> score = new Score<>("数据结构与算法", "EP074512", 60.5);
Number o = score.getValue();
System.out.println(o);//60.5
}
}
class Score<T>{
private final String name;
private final String id;
private final T value;
public Score(String name, String id, T value) {
this.name = name;
this.id = id;
this.value = value;
}
public T getValue() {
return value;
}
public String getName() {
return name;
}
public String getId() {
return id;
}
}
可以看到,此时虽然使用的是通配符,但是不再是Object类型,而是对应的上界
但是我们限定下界的话,因为还是有可能是Object,所以说依然是跟之前一样:
那么既然泛型有上界,那么有没有下界呢?有
下限
package cn.meowrain.generic;
public class Main {
public static void main(String[] args) {
Score<? super Integer> score = new Score<>("数据结构与算法", "EP074512", 60.5);
Object o = score.getValue();
System.out.println(o);//60.5
}
}
class Score<T>{
private final String name;
private final String id;
private final T value;
public Score(String name, String id, T value) {
this.name = name;
this.id = id;
this.value = value;
}
public T getValue() {
return value;
}
public String getName() {
return name;
}
public String getId() {
return id;
}
}
但是我们限定下界的话,因为还是有可能是Object,所以说依然是跟之前一样:
通过给设定泛型上限,我们就可以更加灵活地控制泛型的具体类型范围。
类型擦除
前面我们已经了解如何使用泛型,那么泛型到底是如何实现的呢,程序编译之后的样子是什么样的?
public abstract class A <T>{
abstract T test(T t);
}
实际上在Java中并不是真的有泛型类型(为了兼容之前的Java版本)因为所有的对象都是属于一个普通的类型,一个泛型类型编译之后,实际上会直接使用默认的类型:
public abstract class A {
abstract Object test(Object t); //默认就是Object
}
当然,如果我们给类型变量设定了上界,那么会从默认类型变成上界定义的类型:
public abstract class A <T extends Number>{ //设定上界为Number
abstract T test(T t);
}
那么编译之后:
public abstract class A {
abstract Number test(Number t); //上界Number,因为现在只可能出现Number的子类
}
因此,泛型其实仅仅是在编译阶段进行类型检查,当程序在运行时,并不会真的去检查对应类型,所以说哪怕是我们不去指定类型也可以直接使用:
public static void main(String[] args) {
Test test = new Test(); //对于泛型类Test,不指定具体类型也是可以的,默认就是原始类型
}
只不过此时编译器会给出警告:
同样的,由于类型擦除,实际上我们在使用时,编译后的代码是进行了强制类型转换的:
package cn.meowrain.generic;
public class test {
public static void main(String[] args) {
A<String> a = new B();
String i = a.test("10");
System.out.println(i);
}
}
class A<T> {
public T test(T t) {
return t;
}
}
class B extends A<String> {
@Override
public String test(String s) {
return null;
}
}
实际上编译之后:
不过,我们思考一个问题,既然继承泛型类之后可以明确具体类型,那么为什么@Override
不会出现错误呢?我们前面说了,重写的条件是需要和父类的返回值类型和形参一致,而泛型默认的原始类型是Object类型,子类明确后变为其他类型,这显然不满足重写的条件,但是为什么依然能编译通过呢?
public class B extends A<String>{
@Override
String test(String s) {
return null;
}
}
我们来看看编译之后长啥样:
// Compiled from "B.java"
public class com.test.entity.B extends com.test.entity.A<java.lang.String> {
public com.test.entity.B();
java.lang.String test(java.lang.String);
java.lang.Object test(java.lang.Object); //桥接方法,这才是真正重写的方法,但是使用时会调用上面的方法
}
通过反编译进行观察,实际上是编译器帮助我们生成了一个桥接方法用于支持重写:
public class B extends A {
public Object test(Object obj) { //这才是重写的桥接方法
return this.test((Integer) obj); //桥接方法调用我们自己写的方法
}
public String test(String str) { //我们自己写的方法
return null;
}
}
类型擦除机制其实就是为了方便使用后面集合类(不然每次都要强制类型转换)同时为了向下兼容采取的方案。因此,泛型的使用会有一些限制:
首先,在进行类型判断时,不允许使用泛型,只能使用原始类型:
只能判断是不是原始类型,里面的具体类型是不支持的:
package cn.meowrain.generic;
public class Main {
public static void main(String[] args) {
Hello<String> hello = new Hello<>("meow");
System.out.println(hello instanceof Hello); // true
}
}
class Hello<T> {
T name;
public Hello(T name) {
this.name = name;
}
}