java学习笔记--基础知识--泛型

我们都知道,使用集合保存对象引用时,都会被转换成Object类型,取出时需要进行类型转换。这时容易出现的问题就是,集合中存储了不同的类型,转换时发生 java.lang.ClassCastException异常。

泛型正是为解决这个问题而来的。

 

什么是泛型

泛型,就是参数化的类型。 就是类型由原来的具体的类型参数化,类似了方法中的变量参数。 这时,类型定义成参数形式(可以称为类型形参), 使用类型时传入具体的类型(类型实参)。

还是举个例子吧,说的自己都晕。

public class GenericTest { 
  public static void main(String[] args) {

     List<String> list = new ArrayList<String>();
     list.add("zhangsan");
     list.add("lisi");
     //list.add(100);   // 1  提示编译错误

     for (int i = 0; i < list.size(); i++) {
       String name = list.get(i); // 2  这里不用再进行类型转换了
       System.out.println("name:" + name);
     }
  }
}

上面的代码中,String就是List类型的实参。使用泛型后,集合中只能存储形参规定的类型,取出数据时,数据的类型也是形参的类型。

泛型是如何实现的

我们直接来看ArrayList的代码吧

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{

    transient Object[] elementData;

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }


    .....

}

泛型其实只是在编译阶段有效的,看下面的代码:

List<String> stringArrayList = new ArrayList<String>();
List<Integer> integerArrayList = new ArrayList<Integer>();

Class classStringArrayList = stringArrayList.getClass();
Class classIntegerArrayList = integerArrayList.getClass();

if(classStringArrayList.equals(classIntegerArrayList)){
    Log.d("泛型测试","类型相同");
}

上面代码的输出是:泛型测试: 类型相同

这说明编译之后的程序是要去泛型化的。 在编译过程中,泛型检查正确后,会将泛型信息擦除,并且在对象进入和离开方法时添加类型检查和类型转换(类型检查和转换,是如何实现的呢)。 

 

泛型的使用方式

包括3中使用方式:泛型类,泛型接口,泛型方法

泛型类

就是将泛型用于类的定义中,最典型的就是集合类,如List,Set 和 Map

泛型类的基本写法

class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{
  private 泛型标识 /*(成员变量类型)*/ var; 
  .....

  }
}
class People<T>{
    private T loveToEat;

    public People(T loveToEat){
        this.loveToEat = loveToEat;
    }

    public void setLoveToEat(T loveToEat) {
        this.loveToEat = loveToEat;
    }

    public T getLoveToEat(){
        return this.loveToEat;
    }
}

public class test {
    public static void main(String[] args) {
        People<String>  liming = new People<String>("字符串");

        People<Integer>  zhangsan = new People<Integer>(123);

        System.out.println(liming.getLoveToEat().getClass().getName());
        System.out.println(zhangsan.getLoveToEat().getClass().getName());

    }
}

输出是:

java.lang.String
java.lang.Integer

 

泛型定义中的上限

//用extends指定上限,这里extends后面既可以跟class也可以跟interface
class Box<T extends Number>{

    private T data;

    public Box(T data){
        this.data = data;
    }

    public T getData() {
        return data;
    }
}



public class test {
    
    public static void main(String[] args) {
        //Box<String> name = new Box<String>("zhangsan"); String不是继承自Number,报错
        Box<Integer> age = new Box<Integer>(88);
        Box<Number> number = new Box<Number>(123);
        //Box<Object> obj = new Box<Object>(123); Object不是继承自Number,报错
    }
}

 

需要注意的地方:

  1. 泛型的类型参数只能是类类型,不能是基本类型
  2. 泛型类使用时,也可以不传入实参的,这时就不再进行类型检查了,就像你用ArrayList,里面可以保存各种类型的对象。
  3. 对于一个确切的泛型类型,不能使用instanceof。if(ex_num instanceof Generic<Number>){ }

泛型接口

泛型接口的定义与泛型类基本相同

public interface Generator<T> {
    public T next();
}
interface Generator<T> {
    public T next();
}

/**
 * 我们要实现一个泛型类,实现Generator泛型接口
 * */
class FruitGenerator<T> implements Generator<T>{

    @Override
    public T next() {
        return null;
    }
}

/**
 * 我们要实现一个普通类,实现Generator泛型接口。这时必须为泛型接口传入实参,同时
 * 实现几口函数时的返回值类型也必须是我们选定的实参类型
 * */
class FoodGenerator implements Generator<String>{

    @Override
    public String next() {
        return null;
    }
}

泛型方法

参考 https://www.cnblogs.com/csyuan/p/6908303.html,讲的非常好

在java中,泛型类的定义非常简单,但是泛型方法就比较复杂了。

泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型。 我们上面泛型类中的方法,不是泛型方法。

只有满足了下面的定义格式的方法,才是泛型方法:

修饰符 <类型参数列表> 返回类型 方法名(形参列表) { 方法体 }

public static <T, S> int func(List<T> list, Map<Integer, S> map) { ... }

其中T和S是泛型类型参数

 

类型参数也是有作用域的:

  1. class A<T> { ... },这里的T的作用域是整个A。 但这里要注意,对于A中静态区域和静态方法,是不能使用这个类型T的
  2. public <T> func(...) { ... },这里T的作用域是整个func
  3. 也存在作用域覆盖,内部会覆盖外部
class A<T> {
    // A已经是一个泛型类,其类型参数是T
    public static <T> void func(T t) {
    // 再在其中定义一个泛型方法,该方法的类型参数也是T
    }
}
//当上述两个类型参数冲突时,在方法中,方法的T会覆盖类的T,即和普通变量的作用域一样,内部覆盖外部,外部的同名变量是不可见的。
//除非是一些特殊需求,一定要将局部类型参数和外部类型参数区分开来,避免发生不必要的错误,因此一般正确的定义方式是这样的:
class A<T> {
    public static <S> void func(S s) {

    }
} 

 

泛型方法,可以指定类型参数的上限(不能指定下限? 我试过,确实不能)。但必须在类型参数声明的地方,在其他地方指定上限会报错。

<T extends X> void func(List<T> list){ ... }

 

上面说的都是如何定义泛型方法,那么如何调用泛型方法呢?

1. 标准格式是这样的: object.<String> func(...)   其中String就是我们指定的类型实参,这样所有用到类型参数的地方,都会被替换成String类型。

2. 除了想上面那样显示调用,也支持隐式调用,不指明泛型参数,编译器根据传入的实参类型自动推断类型参数,举个例子:

<T> void func(T t){ ... }

隐式调用, 编译器根据"name"的类型String,推断出类型参数是String

object.func("name")

3. 隐式调用会产生歧义

<T> void func(T t1, T t2){ ... }

//这里编译器无法推断出类型参数,应该是String还是Integer,编译器会报错
object.func("name", 123)

 

泛型方法与通配符

通配符在下面会看到,可以先看一下。  

  1. 泛型方法可以完全替代通配符
    void func(List<? extends A> list)
    
    我可以用下面的泛型方法代替
    
    <T extends A> void func(List<T> list)

     

  2. 使用通配符后,对象list是只读的, 但是泛型方法中list是可写的
  3. 如果只读,那就用通配符,可以提供安全性,防止误修改。 如果需要修改则使用泛型方法。
  4. 如果返回值、多个参数间有类型依赖关系,则使用泛型方法
    需求是第一个参数list中的中的类型,是第二个参数t的类型或者其子类型
    <T> void func(List<? extends T> list, T t)
  5. 当多个类型参数间有依赖关系时,可以进行归约

    <T, E extends T> void func(List<T> l1, List<E> l2);
    
    这里E在形参中只出现了一次,没有其他地方用到它,那么我们可以用通配符来进行简化
    
    <T> void func(List<T> l1, List<? extends T> l2);
  6.  

    典型用法

    public static <T> void Collections.copy(List<T> dest, List<? extends T> src) { ... }
    
    从src拷贝到dest,那么dest最好是src的类型或者其父类,因为这样才能类型兼容,
    并且src只是读取,没必要做修改,因此使用“?”还可以强制避免对src做不必要的修改,增加的安全性。

     

 

 

 

泛型中的通配符

看例子吧,简单粗暴!!     通配符用在使用泛型的过程中,不是泛型定义中。 通配符可以与上下限共同使用。 【注意:我们可以在泛型定义中使用上限,但是无法使用下限。

class Box<T>{

    private T data;

    public Box(T data){
        this.data = data;
    }

    public T getData() {
        return data;
    }
}


public class test {

    //不使用通配符
    public static void showData1(Box<Number> box){
        System.out.println("data:" + box.getData());
    }

    //使用通配符,此处Box<?> box,?代表的是类型实参,Box<?>是所有Box<xxx>的父类
    public static void showData2(Box<?> box){
        System.out.println("data:" + box.getData());
    }

    //使用?实参,代表没有限制。但有时我们需要限制
    //通配符上限,就是实参类型必须是继承自某个类,或者就是那个类本身
    public static void showData3(Box<? extends Number> box){
        System.out.println("data:" + box.getData());
    }

    //通配符下限,就是实参类型必须是某个类的父类(或父类的父类等),或者就是那个类本身
    public static void showData4(Box<? super Integer> box){
        System.out.println("data:" + box.getData());
    }


    public static void main(String[] args) {
        Box<String> name = new Box<String>("zhangsan");
        Box<Integer> age = new Box<Integer>(88);
        Box<Number> number = new Box<Number>(123);
        Box<Object> obj = new Box<Object>(123);

        //可以看出来,Box<Number>和Box<Integer>是不兼容的,虽然Integer继承了Number
        showData1(number);
        //showData1(age);  //The method getData(Box<Number>) in the t ype test is not applicable for the arguments (Box<Integer>)

        //使用?通配符,这个3个都可以正常调用
        showData2(number);
        showData2(age);
        showData2(name);

        //使用通配符上限,这2个都可以正常调用
        showData3(number);
        showData3(age);
        //showData3(name);  不可用,因为String不是继承自Number

        //使用通配符下限,这3个都可以正常调用
        showData4(number);
        showData4(age);
        //showData4(name);  不属于Integer继承树中的一员
        showData4(obj);

    }
}

需要注意的是,使用了?通配符后,对象变成只读的(如果尝试写操作,编译无法通过了):

class Box<T>{

    private T data;
    private String name;

    public Box(T data){
        this.data = data;
    }

    public T getData() {
        return data;
    }
}



public class test {

    static void showInfo(Box<?> box){

        //box是只读的,因为?代表的是什么是不确定的
        //所以下面的修改操作会报错:Error:(33, 21) java: 不兼容的类型: int无法转换为capture#1, 共 ?
        //box.setData(123);

        System.out.println("" + box.getData());

    }

    public static void main(String[] args) {
        Box<Integer> age = new Box<Integer>(88);
        Box<Number> number = new Box<Number>(123);

        showInfo(age);
    }
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值