java泛型
泛型
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
public class GenericsDemo00 {
public static void main(String[] args) {
List arrayList = new ArrayList();
arrayList.add("String类型");
arrayList.add(2);
arrayList.add(5);
arrayList.add(3);
Integer count = 0;
for (Object o : arrayList) {
if (o instanceof String){
System.out.println((String) o);
}
if (o instanceof Integer){
count += (Integer) o;
}
}
System.out.println(count);
}
}
没有泛型之前集合中可能会装有多个类型的元素,需要进行类型的检查和类型的强转,一旦犯错就是一个运行时崩溃
List<String> list = new ArrayList<>();
list.add(2);
有了泛型之后,编译器在编译时期即可完成 类型检查 工作,并提出错误(其实IDE在代码编辑过程中已经报红了);
泛型擦除
泛型擦除:Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除。Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。
例如:List<String>
和 List<Integer>
在编译后都变成 List
。
public class GenericsDemo01 {
public static void main(String[] args) {
ArrayList<Integer> list1 = new ArrayList<>();
ArrayList<String> list2 = new ArrayList<>();
list1.add(5);
list2.add("证明泛型擦除");
System.out.println(list1.getClass()==list2.getClass());//true
}
}
还可用反射向List<Strig>
中添加其他类型数据来更进一步证实泛型擦除
public class GenericsDemo02 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
List<String> list = new ArrayList<>();
Class c1 = list.getClass();
list.add("证明泛型除");
// list.add(5);//这一步ide会爆红,但是可以通过反射的方式加入到list
Method method = c1.getMethod("add", Object.class);
method.invoke(list,5);
System.out.println(list);
}
}
泛型通配符
常用的 T,E,K,V,?
本质上这些个都是通配符,没啥区别,只不过是编码时的一种约定俗成的东西。比如上述代码中的 T ,我们可以换成 A-Z 之间的任何一个 字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替 T ,在可读性上可能会弱一些。通常情况下,T,E,K,V,? 是这样约定的:
- ? 表示不确定的 java 类型
- T (type) 表示具体的一个java类型
- K V (key value) 分别代表java键值中的Key Value
- E (element) 代表Element
泛型的使用方式
泛型类
泛型一般有三种使用方式:泛型类、泛型接口、泛型方法。
类的泛型就是把泛型定义在类上,用户在使用该类时才确定下来类的类型;
public class GenericsTest03 {
public static void main(String[] args) {
Test<String> stringTest = new Test<>();
stringTest.setT("类的泛型");
System.out.println(stringTest.getT());
}
}
class Test<T>{
private int id;
private int age;
private T t;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
public Test(int id, int age, T t) {
this.id = id;
this.age = age;
this.t = t;
}
public Test() {
}
public Test(T t) {
this.t = t;
}
}
泛型接口
public interface Generator<T> {
public T method();
}
实现泛型接口,不指定类型:
class GeneratorImpl<T> implements Generator<T>{
@Override
public T method() {
return null;
}
}
实现泛型接口,指定类型:
class GeneratorImpl2<T> implements Generator<String>{
@Override
public String method() {
return "null";
}
}
泛型方法
判断泛型方法:判断一个方法是否是泛型方法关键看方法返回值前面有没有使用 <>
标记的类型,有就是,没有就不是。
public class GenericsDemo03 {
public <T> String getString(T t){
return t.toString();
}
public static <T> void printString(T t){
System.out.println(t);
}
public static void main(String[] args) {
String string = new GenericsDemo03().getString(555);
System.out.println(string);
GenericsDemo03.printString("dasd");
}
}
// 泛型类中的泛型方法
class GenericsDemo04<T> {
// 成员泛型方法
public <E> String getString(E e) {
return e.toString();
}
// 静态泛型方法
public static <V> void printString(V v) {
System.out.println(v.toString());
}
}
- 方法返回值前面的
T
,声明此方法持有一个类型T
,也可以理解为声明此方法为泛型方法 - 后面的
T
作用是指明泛型的类型
通配符 ?
假入有三个类A
、B
、C
,B
、C
继承A
public class A{
public int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
class B extends A{
public int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
class C extends A {
public String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
无限定通配符?
List<?>
的意思是这个集合是一个可以持有任意类型的集合,它可以是List<A>
,也可以是List<B>
,或者List<C>
等等。
因为你不知道集合是哪种类型,所以你只能够对集合进行读操作。并且你只能把读取到的元素当成 Object
实例来对待。
public class GenericsDemo05 {
//可以读取 并且是以object类型来读取
public static void forEach(List<?> list){
for (Object o : list) {
System.out.println(o);
}
//不能添加 报错
// list.add(new A());
// list.add(new B());
// list.add(new C());
}
}
不知道集合是哪种类型,那集合所持有的元素类型也就不确定,所以不可以随便往集合里写入东西,不然会出现List<B>
存在了C
,这明显违背了泛型的本意。
上界通配符(? extends)
List<? extends A>
代表的是一个可以持有 A及其子类(如B和C)的实例的List集合。
当集合所持有的实例是A或者A的子类的时候,此时从集合里读出元素并把它强制转换为A是安全的(也可以转成object类型读取,它们是object的子类)。
public static void forEach2(List<? extends A> list){
for (A a : list) {
System.out.println(a);
}
for(Object o :list){
System.out.println(o);
}
//不能添加 报错
// list.add(new A());
// list.add(new B());
// list.add(new C());
}
forEach()
方法仍然是不能给传入的list插入元素的(比如进行*list.add()*操作),因为你不知道list集合里面的元素是什么类型(A、B还是C等等)。比如你传进来的list是List,那插入C或者A就不行。
这里涉及到了一个多态里向上转型的题,这里有一篇向上、向下转型的文章推荐向上转型和向下转型,如果有不理解的可以去参考一下。
下界通配符(? super)
List<? super A>
的意思是List集合 list,它可以持有 A 及其父类的实例。
当你知道集合里所持有的元素类型都是A及其父类的时候,此时往list集合里面插入A及其子类(B或C)是安全的。
public static void forEach3(List<? super A> list){
//list读取只能时object类型读取 因为你不知到集合里的类型是什么,所以你不能够把他们读出来并转换为某一特定类型
for (Object o : list) {
System.out.println(o);
}
//可以添加
list.add(new A());
list.add(new B());
list.add(new C());
}
传入的List集合里的元素要么是A的实例,要么是A父类的实例,因为B和C都继承于A,如果A有一个父类,那么这个父类同时也是B和C的父类。
因为你不知到集合里的类型是什么,所以你不能够把他们读出来并转换为某一特定类型(除非你可以找出集合里元素的共同父类,比如这里的Object类)。
list<? extends A>
可以转换为A的原因是他知道集合里的元素的类型要么是A要么是A的子类,他们都可以转换为A。这个和这里的都可以转换为Object的道理是一样的。