目录:
1、泛型简述
2、泛型类
3、泛型接口
4、泛型方法
5、类型擦除机制
6、通配符
1、泛型简述:
泛型java SE1.5的新特性,泛型本质参数化类型。即将所操作的数据类型被指定为一个参数,将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可叫做类型形参),再使用/调用时传入具体的类型(类型实参)。
这种参数可在类、接口、方法的创建中使用,分别叫做:泛型类、泛型接口、泛型方法。
ArrayList arrayList = new ArrayList();
arrayList.add(100);
arrayList.add("string");
String str = (String) arrayList.get(0);
System.out.println(str);
输出
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
类型转换异常
上面示例中,存在2问题:
1、当将一个对象放入集合,集合并不会记住对象的类型(idea中,参数是Object类型),当从集合取出对象时,该对象的编译类型变成了Object类型,但其运行时类型依然为本身类型。
2、所以,取出集合元素时需要人为的强制转化到具体的目标类型,且很容易出现 java.lang.ClassCastException:异常。
2、泛型类:
2.1泛型类定义
泛型类的声明和非泛型类的声明相似,不过是在类型后面添加参数声明部分。
泛型类的类型参数声明部分也包含一个或多个类型参数,中间用逗号隔开。
一个泛型参数也叫一个类型变量,是用于一个泛型名称的标识符。因为他们接受一个或多个参数,这些类型被称为参数化的类或参数化类型
class 类名<泛型标识:可以随便写任意标识符,标识指定的泛型的类型>{
private 泛型标识 /*(成员变量类型)*/var;
}
2、2示例:
Class Generic<T>{//T为随便可写的标识符,常见如T,E,V,等形式的参数
private T key; //在泛型类实例化对象时,必须指定泛型参数T的具体类型,没有指定默认为OObject类型
public Gnerric(T key){//构造方法 形参也是T 实参是参数化类型的具体类型
thiskey=key
}
}
public static void main(String args[]){
Generic<Integer> gern = new Generic<>(10);
Integer val = gern.getkey();
Generic<Integer> gernString = new Generic<>("string");
String str = gernString.getkey();
}
TIPS:泛型类,并不一定要传入泛型实参。
传入了,则为传入的类型。
没有传入,则可以是任何类型。
如:ArrayList arrayList = new ArrayList();//没有传入任何泛型实参
arrayList.add(100);//可以是任何类型
arrayList.add("string");
ArrayList<String> stringArraylist = new ArrayList<>();//传入了泛型实参String类型
stringArraylist.add("a");//就只能传入String类型的参数
stringArraylist.add("b");
//stringArraylist.add(1);编译器就提示报错
2、3泛型类注意点
1、泛型类只是对类进行自动检查。。注意:不是替换,仅仅在编译器用来进行类型安全检查
自动对类型进行转换
2、泛型的类型参数只能是引用数据类型,不能是基本数据类型,但可以用基本数据类型的包装类型。如:int 的Integer
3、不能对确切的泛型类型使用 instanceof操作。如
if (stringArraylist instanceof ArrayList<String>)
System.out.println(90);
会报错。
4、不能直接new泛型数组。
// ArrayList<String> arrayList1[] = new ArrayList<>()[]; 不能直接new泛型数组
5、不能产生泛型类型对象。
6、在static方法中,不能使用泛型类型参数。static是不依赖于对象存在的,所以无法推知static泛型参数类型
public static void NAME(T name){}//无法通过编译,提示没有this引用
3、泛型接口
3、1泛型接口定义
public interface Generator <T>{
T next();
}
3、2泛型接口示例演示
class FruitGenerator<E> implements Generator<E>{
@override
pubic E next(){
retuen null;
}
}
4、泛型方法
泛型方法和所处的类是否是泛型类没有关系。
泛型方法的参数化类型列表放在方法类符的后面返回值之前。
4、1泛型方法定义
class GenericMethod{
/*
*(1)public 与返回值中间的String,可理解为 声明此方法为泛型方法。
*(2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法
*(3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
*(4)与泛型类的定义一样,此处的T可随便写作其他的任意标识,常见如T,E,K,V等形式表示泛型
*/
public static <T> String genericMethod(Class<T> tCLASS){
return tCLASS.getName();
}
4、2泛型方法注意点
1、如果泛型方法的泛型参数与类型参数相同,编译器产生警告。(方法的泛型参数隐藏了外部类的泛型参数)
class GenericMethod<T>{
private T name;
public <T> void setNum(T name){
this.name = name;//报错 invalid types
}
}
class GenericMethod<T>{
private T name;
public <E> void setNum(T name){//E 和泛型类T 不同 则行
this.name = name;
}
}
直接可以通过"方法名()"的形式进行泛型方法的调用,编译器根据传入的方法实参推演出形参类型。使用约束对泛型类型参数起到约束作用。
public class GenericTest {
public static <T extends Comparable<T> >void swap(T[] arr,int index1,int index2){
//类型擦除 指定传入的泛型参数必须实现比较接口中的抽象方法 CompareTo()方法
T temp = arr[index1];
arr[index1]=arr[index2];
arr[index2]=temp;
}
public static <T extends Comparable<T>>void bubbleSort(T arr[]){
boolean flag;
for (int i = 0; i <arr.length ; i++) {
flag = false;
for (int j = 0; j <arr.length-1-i ; j++) {
if (arr[j].compareTo(arr[j+1])>0){//对象比较方式 compareTo
flag = true;
swap(arr,j,j+1);
}
}
if (!flag)
break;
}
}
public static void main(String[] args) {
Integer arr[] = {1,2,6,3,11,9};
bubbleSort(arr);
//传入的泛型参数是Integer类型,该类型实现了Compareable<Integer>
/接口中的抽象方法compareTo方法
System.out.println(Arrays.toString(arr));
}
}
5、类型擦除机制
5、1 类型擦除的定义
java的泛型是伪泛型,因在编译期间,所有的泛型信息都会被擦除掉。java中的泛型基本都是在编译器这个层次实现。生成的java字节码中,不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器编译的时候去掉。 称为类型擦除。
如上的List<object>和List<String>等类型,编译后都会编成List。jvm看到的只是List,而由泛型附加的类信息对jvm来说是不可见的。java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时出现类型转换异常的情况。
import java.util.Arraylist;
class TestDemo{
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("bad");
ArrayList<Integer>arrayList1 = new ArrayList<>();
arrayList1.add(123);
System.out.println(arrayList.getClass()==arrayList1.getClass());
}
}
//输出结果 true
可以看到,无论是 arrayList 还是 arrayList1 getclass是一样的,因为类型擦除到了Object
import java.util.Arraylist;
class TestDemo{
public static void main(String[] args) {
ArrayList<Integer> arrayList2 = new ArrayList<>();
arrayList2.add(1);
arrayList2.getClass().getMethod("add",Object.class).invoke(arrayList2,"adsd");
for (int i = 0; i <arrayList2.size() ; i++) {
System.out.println(arrayList2.get(i));
}
}
程序中定义了一个Arrayist泛型类实例化为Integer的对象,如果直接调用了add方法,那么只能存储整型的数据。当使用反射调用add方法的时候,却可以存储字符串。说明了Integer泛型实例在编程之后被擦除了,只保留了原始类型。
5、2类型擦除实例演示
public static <T extends Comparable<T> >void swap(T[] arr,int index1,int index2){
T temp = arr[index1];
arr[index1]=arr[index2];
arr[index2]=temp;
}
public static <T extends Comparable<T>>void bubbleSort(T arr[]){
boolean flag;
for (int i = 0; i <arr.length ; i++) {
flag = false;
for (int j = 0; j <arr.length-1-i ; j++) {
if (arr[j].compareTo(arr[j+1])>0){
flag = true;
swap(arr,j,j+1);
}
}
if (!flag)
break;
}
}
5.3 类型擦除示例分析
上面提到的原始类型,原始类型即 擦除泛型信息后,体现在字节码中的类型变量的真正类型。无论何时定义一个泛型类型,相应的原始类型都会被自动地类型擦除到原生类型,并使用其限定类型(无限定的变量用Object)替换。
先检查,再编译,及检查编译的对象和引用传递的问题。
问题:既然类型变量会在编译的时候擦掉,那为什么在 ArrayList<String>arraylist = new ArrayList<>();所创建的数组列表arraylist中,不能使用add方法添加Integer类型呢?前面所提,不是擦除变为Object了么?为什么不能存别的类型?既然类型擦除了,那如何保证我们只能使用泛型变量限定的类型变量呢?
解决:java编译器是先进行代码中的泛型类型检查,通过后,才进行类型擦除,之后才是编译。就保证了add进去的和泛型类型限定的必须是一个类型。
public static void main(String args[]){
ArrayList<String> arraylist = new ArrayList<String>();
arrayList.add("123string");
arrayList.add(123);//编译报错 泛型类型变量的使用,在编译前会检查
}
代码中出现 代码兼容以及引用传值之间的问题:
ArrayList<String> arrayList2 = new ArrayList();//第一种情况,和使用泛型参数效果一样
ArrayList arrayList3 = new ArrayList<String>();//第二种情况,完全没有效果
//以上两种写法都会出现编译时警告
arrayList2.add("1");//编译报错
//arrayList2.add(1); 编译报错
String str1 = arrayList2.get(0);//返回类型就是String
arrayList3.add("1");//编译通过
arrayList3.add(1);//编译通过
Object object = arrayList2.get(0);//返回类型就是Object
产生的原因是?
本来类型检查就是编译时完成的。new ArrayList()只是在内存中开辟一个存储空间,可以存储任何的类型对象。而真正涉及类型检查的是它的引用。所以arraylist2引用能完成泛型类型的检查。而引用arraylist3没有使用泛型,所以不行。因此,可以明白 类型检查就是针对引用的,谁是 一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。
默认情况,没有约束条件的泛型类型参数(Sample<T>) T称为未绑定类型参数,当创建未绑定类型参数的泛型类实例时,可以给参数类型指定任意类型。想要让泛型参数支持特定类型时,可使用关键字extends或super关键字对泛型类型进行约束。
extends主要限定泛型类型参数的上界(常见的使用如下):
<T extends 基类>://T只能是基类或者基类的派生类
<T extend 基接口>://只能是实现基接口的派生类
6、通配符
public class FanTest3 {
public static void show(ArrayList<Object> arrayList){
Iterator<Object> iterator = arrayList.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
public static void main(String[] args) {
Object o1 = new Integer(10);
Object o2 = new String("adb");
//满足向上造型的条件
ArrayList<Integer> arrayList = new ArrayList<>();
//show(arrayList);编译报错 解决方法 通配符
}
6、1通配符边界定义
<?extends基类>:其中表明?未知类型限定为指定类或者指定类的派生类。指定上界
<?super派生类>:其中?未知类型限定为指定类或者指定类的基类。指定下届
6、2通配符示例演示
public class FanTest3 {
public static void show(ArrayList<?> arrayList){//里面的?号
Iterator<?> iterator = arrayList.iterator();//里面的?号
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
public static void main(String[] args) {
Object o1 = new Integer(10);
Object o2 = new String("adb");
ArrayList<Integer> arrayList = new ArrayList<>();
show(arrayList);//编译通过
}
}
未知类型参数<T>一般用于泛型方法的参数。可以声明未知类型的集合的变量,但不能直接创建未知类型<?>集合的对象实例,也不能像未知类型<?>集合的变量中添加元素(null除外,null匹配任何对象实例。)
public class FanTest3 {
public static void show(ArrayList<? extends Number> arrayList){
Iterator<?>iterator = arrayList.iterator();
//定义泛型参数是Number类或者Number类的子类就行
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();//Integer是Number的子类
show(arrayList);
}
}