什么是泛型
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
广泛的类型,类,接口和方法代码可以应用于非常广泛的类型,代码与他们能够操作的数据类型不再绑定在一起,同一套代码,可以用于多种数据类型,这样不仅可以复用代码,降低耦合,同时可以提高代码的可读性和安全性.
使用场景:数据类型不确定
例如:现在要存储一个学生的成绩,但是成绩有可能想存为数字,小数,或者字符串(优秀,良好,好)之类的数据。这种数据都是类型不确定的。
可以使用Object来存储该成绩,但是这样存储的话会把所有类型都当做Object来对待。从而”丢失”了实际的数据类型。获取数据的时候也需要转换类型,效率低,还容易出错。
一个简单的泛型类
public class Pair<T> {
T first;
T second;
public Pair(T first, T second){
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
}
Pair类引入了类型变量T 用<>括起来,并放在类名之后.
类的属性的类型也是T
T也是类方法的中返回类型
怎么使用这个泛型类
用具体的类型代替类型变量就可以实例化泛型
Pair<Integer> minmax = new Pair<Integer>(1,100);
Integer min = minmax.getFirst();
Integer max = minmax.getSecond();
<Integer>表示传入的类型是Integer
这个类型是可以变化的
Pair<String> kv = new Pair<String>("name",”nm");
泛型类可以有多个类型变量
public class Pair<U, V> {
U first;
V second;
public Pair(U first, V second){
this.first = first;
this.second = second;
}
public U getFirst() {
return first;
}
public V getSecond() {
return second;
}
}
使用
Pair<String,Integer> pair = new Pair<String,Integer>("name",100);
简单来说,泛型类可以看作普通类的工厂
泛型类型的命名
泛型类型变量使用大写形式,并且比较短 在java中变量E表示集合的元素类型,K和V分别表示表的关键字与值的类型. T(需要时还可以使用临近的字母U和S)表示任意类型
注意:
Jdk1.7以后 构造函数中可以省略泛型类型 ArrayList<String> files = new ArrayList<>();
泛型不能使用在静态属性或者静态方法上也不能用在全局常量上
泛型的原理
Java有Java编译器和Java虚拟机,编译器将Java源代码转换为.class文件,虚拟机加载并运行.class文件。对于泛型类,Java编译器会将泛型代码转换为普通的非泛型代码,就像上面的普通Pair类代码及其使用代码一样,将类型参数T擦除,替换为Object,插入必要的强制类型转换。Java虚拟机实际执行的时候,它是不知道泛型这回事的,它只知道普通的类及代码。
泛型的好处
既然泛型是实现原理是将泛型变为了普通类和Object就可以的,而且泛型最后也变成了普通类,那么为什么还是要使用泛型呢?
栗子
只使用Object,代码写错的时候,开发环境和编译器不能帮我们发现问题,看代码:
Pair pair = new Pair("name",1);
Integer id = (Integer)pair.getFirst();
String name = (String)pair.getSecond();
写代码时,不小心,类型弄错了,不过,代码编译时是没有任何问题的,但,运行时,程序抛出了类型转换异常ClassCastException。
但是如果使用泛型,那么开发环境如Eclipse会提示你类型错误,即使没有好的开发环境,编译时,Java编译器也会提示你。
1,类型安全。 泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。
将运行时期的ClassCastException,转移到了编译时期变成了编译失败。
2,消除强制类型转换。 泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。提高了代码的可读性
泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法
泛型方法
是不是泛型方法和他在不在泛型类中没有关系
只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法
public static <T> int indexOf(T[] arr, T elm){
for(int i=0; i<arr.length; i++){
if(arr[i].equals(elm)){
return i;
}
}
return -1;
}
类型参数在方法修饰符之后,返回类型之前
调用indexOf(new Integer[]{1,3,5}, 10)
或者indexOf(new String[]{"hello","老马","编程"}, "老马")
和泛型类一样泛型方法也可以有多个参数
public static <U,V> Pair<U,V> makePair(U first, V second){
Pair<U,V> pair = new Pair<>(first, second);
return pair;
}
在大多数情况下,方法调用中可以省略<T>参数的,编译器有足够的信息推断出所调用的方法
例如makePair(1,”a”);
类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
类中的泛型方法转载https://blog.csdn.net/s10461/article/details/53941091
public class GenericFruit {
class Fruit {
@Override
public String toString() {
return "fruit";
}
}
class Apple extends Fruit {
@Override
public String toString() {
return "apple";
}
}
class Person {
@Override
public String toString() {
return "Person";
}
}
class GenerateTest<T> {
public void show_1(T t) {
System.out.println(t.toString());
}
// 在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
// 由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
public <E> void show_3(E t) {
System.out.println(t.toString());
}
// 在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
public <T> void show_2(T t) {
System.out.println(t.toString());
}
}
public static void main(String[] args) {
Apple apple = new Apple();
Person person = new Person();
GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>();
// apple是Fruit的子类,所以这里可以
generateTest.show_1(apple);
// 编译器会报错,因为泛型类型实参指定的是Fruit,而传入的实参类是Person
// generateTest.show_1(person);
// 使用这两个方法都可以成功
generateTest.show_2(apple);
generateTest.show_2(person);
// 使用这两个方法也都可以成功
generateTest.show_3(apple);
generateTest.show_3(person);
}
}
什么时候使用泛型方法
无论何时,如果你能做到,你就该尽量使用泛型方法。也就是说,如果使用泛型方法将整个类泛型化,那么就应该使用泛型方法。另外对于一个static的方法而已,无法访问泛型类型的参数。所以如果static方法要使用泛型能力,就必须使其成为泛型方法。
泛型接口
public interface Comparable<T> {
public int compareTo(T o);
}
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
}
和泛型类差不多
泛型接口的实现
public interface List <E>{
abstract boolean add(E e);
}
接口里的常量不能是用泛型修饰,静态方法也不能.
实现类,先实现接口,不理会泛型
public class ArrayList<E> implements List<E>{
}
调用者 : new ArrayList<String>() 后期创建集合对象的时候,指定数据类型
实现类,实现接口的同时,也指定了数据类型
public class XXX implements List<String>{
}
new XXX()
泛型的限定
java中用通配符和边界来限制T的范围
extends:可读不可写 限制父类 上限限制 可以传递T,传递他的子类对象
super :可写不可读 限制子类 下限限制 可以传递T,传递他的父类对象
都没有 :没有继承很开心,一遇继承就完蛋
java是单继承,所有继承的类构成一棵树。
T<? super B>和T<? extends B>
假设A和B都在一颗继承树里(否则super,extend这些词没意义)。
A super B 表示A是B的父类或者祖先,在B的上面。
A extend B 表示A是B的子类或者子孙,在B下面。
由于树这个结构上下是不对称的,所以这两种表达区别很大。假设有两个泛型写在了函数定义里,作为函数形参(形参和实参有区别):
1) 参数写成:T<? super B>,对于这个泛型,?代表容器里的元素类型,由于只规定了元素必须是B的超类,导致元素没有明确统一的“根”(除了Object这个必然的根),所以这个泛型你其实无法使用它,对吧,除了把元素强制转成Object。所以,对把参数写成这样形态的函数,你函数体内,只能对这个泛型做插入操作,而无法读
2) 参数写成: T<? extends B>,由于指定了B为所有元素的“根”,你任何时候都可以安全的用B来使用容器里的元素,但是插入有问题,由于供奉B为祖先的子树有很多,不同子树并不兼容,由于实参可能来自于任何一颗子树,所以你的插入很可能破坏函数实参,所以,对这种写法的形参,禁止做插入操作,只做读取
链接:https://www.zhihu.com/question/20400700/answer/117624335
来源:知乎
?无限定通配符
所有的都可以传,只能读,不能写
泛型的约束和局限性
不能使用基本数据类型实例化类型参数
运行时类型信息不使用与泛型
不能创建参数化类型的数组
类型擦除可能会引发一些冲突
不能通过类型参数创建对象
不能用于静态变量
(好多没很好的理解先往后面学着应给能理解了吧)
共同学习,写下你的评论
评论加载中...
作者其他优质文章