java: 泛型编程(泛型类、泛型方法、泛型接口、泛型的类擦除机制、通配符)

目录:

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称为未绑定类型参数,当创建未绑定类型参数的泛型类实例时,可以给参数类型指定任意类型。想要让泛型参数支持特定类型时,可使用关键字extendssuper关键字对泛型类型进行约束。

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);
    }
}

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

约翰兰博之西安分博

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值