Java泛型

前言:突然有一天发现被人代码写用了很多的泛型定义,结果自己一敲不通, 太难过了, 下面简单介绍几种常用的泛型定义

1、泛型基本用法

泛型表现一(集合类中的泛型举例)
//这里举例说明List<E>,Comparable<T>
public interface List<E> extends Collection<E> {
    // Query Operations
	boolean add(E e);
	E get(int index);
    ...
}

public interface Comparable<T> {
      public int compareTo(T o);
}

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
	...
}
//总结: 类名中指定传入的参数类型为E, 然后List类中的方法add, get就只能处理包含E类型的参数, 这种就是属于传入参数就结果也是返回入参类型E
定义在方法中的泛型
  • 普通类中的泛型方法 (直接使用<>表示就可以,在返回类型之前, 权限修饰符后面)
  • 泛型类中的泛型方法
// 1.普通类的泛型方法
//普通类中使用泛型方法需要<T>表示,没有<T>编译报错
class Pair {
    //这个是普通类的泛型方法的定义
    public <T> T flusTeeth(T a) {
        System.out.println("我是普通类中的泛型方法,吃东西前先刷牙!");
        return null;
    }

    //这个是普通类的泛型方法的定义
    public static <T> T  getBane(T t) {
        System.out.println("我是普通类中的泛型方法,吃东西前先刷牙!"+t.getClass());
        return t;
    }
}

// 2.泛型类中的泛型方法 
interface Person<E> {
    void eat(E e);
    
    //泛型类中的泛型方法
    <T>  void sleep(T t);
}

class Son<E> implements Person<E> {
    private String name="gongxs";
    
    @Override
    public void eat(E e) {
        System.out.println("我是son,准备吃东西"+e.getClass().getName());
    }

    //我是泛型类中的泛型方法
    @Override
    public <T> void sleep(T t) {
        System.out.println("睡觉");
    }
}

//总结: 不管你是在泛型类或者是普通类中你就把泛型想象成Object类型类看待, 只要注意一点就是泛型方法需要使用<>号来声明一下我是泛型方法,其实其他的和正常的方法都是一样的
定义在接口中的泛型
  • 我们在接口中已经声明了只可以处理E类型的参数,那么他的实现了也就只能处理父类声明,或者自己额外声明的类型参数
interface Person<E> {
    void eat(E e);

    <T>  void sleep(T t);
}

//注意:我们在接口中已经声明了只可以处理E类型的参数,那么他的实现了也就只能处理父类声明,或者自己额外声明的类型参数
//由此可见,E是父类声明的参数,T是自己扩张的
class LitterSon<T, E>  implements Person<E>{

    @Override
    public void eat(E e) {

    }
  
    public T eat(T t, E e) {
        return t;
    }

    //泛型方法
    @Override
    public <U> void sleep(U t) {

    }
}

泛型的通配符?

泛型限定

泛型代码和JVM

  • 类型擦除
  • 泛型翻译表达式
泛型擦除

泛型定义我们有一个原始类型(就是我们常写的普通类,<>符号之前),擦除类型变量,并替换成限定的类型(没有限定的类型就是Object类型,如果 ? extend 类就只能是类以及是类的子类 , 类 super ?就只能是类以及是类的父类)

//普通类中使用泛型方法需要<T>表示
class Pair<T> {

    private T first;
    private T  second;

    Pair(T first, T second)  {
        this.first = first;
        this.second = second;
    }

    public T getFirst() {
        return first;
    }

    public T getSecond() {
        return second;
    }
}

//<T>因为上面的T没有限定, 所以这里直接是Object类
class Pair<Object> {

    private Object first;
    private TObject  second;

    Pair(Object first, Object second)  {
        this.first = first;
        this.second = second;
    }

    public Object getFirst() {
        return first;
    }

    public Object getSecond() {
        return second;
    }
}

泛型翻译

解释:是含有泛型参数的父类在被子类所继承的同时,指定了泛型参数的具体类型,并重写了含有泛型参数的方法;但在编译后泛型擦除了,T 会变成 Object 类型,和子类中的的方法同名类型不一样, 但是其实应该是同一个才对,但是实际上不是一样的,所以要多加一个方法桥接在子类方法和父类方法之间。

  • 泛型返回类型将变成Object类型(没有限定类情况)
  • 由JVM自动的插入类型转换操作
桥接方法

先声明一个父类泛型类,代码如下所示:

//普通类中使用泛型方法需要<T>表示
class Pair<T> {

    private T first;
    private T  second;

    Pair(T first, T second)  {
        this.first = first;
        this.second = second;
    }

    public void setFirst(T first) {
        this.first = first;
    }
    
    public void setSecond(T second) {
        this.second = second;
    }
}

//泛型擦除后上面代码变成下面注释代码
/*class Pair<Object> {

    private Object first;
    private Object  second;

    Pair(Object first, Object second)  {
        this.first = first;
        this.second = second;
    }
    
    public void setFirst(Object first) {
        this.first = first;
    }
    //注意这里的入参是Object类型
    public void setSecond(Object second) {
        this.second = second;
        System.out.println("我是父父类的setEcond方法");
    }
}*/

接着子类继承这个父类,并且指定泛型类型,然后我们重写这个父类的方法setSecond 代码如下所示:

//子类继承父类然后明确了泛型类型参数:LocalDate
class DateInteval extends Pair<LocalDate> {
    //这个构造方法也是需要的, 因为父类显示声明了构造方法
    DateInteval(LocalDate first, LocalDate second) {
        super(first, second);
    }

    //注意这里的入参是LocalDate,这里大家注意下是重写???
    @Override
    public void setSecond(LocalDate second) {
        super.setSecond(second);
    }
}

public class DemoApplication {

    public static void main(String[] args) {
        Method[] methods = DateInteval.class.getDeclaredMethods();
        Arrays.stream(methods).forEach(r->{
            Arrays.stream(r.getParameterTypes()).forEach(e->{
                System.out.println("方法 "+r.getName()+" 入参类型是:"+e.getTypeName());
            });
        });
    }
}

//运行结果 如下所示:
/*
  方法 setSecond 入参类型是:java.time.LocalDate
  方法 setSecond 入参类型是:java.lang.Object
*/

看 上面输出结果为什么会出现两个方法呢?明明是父类只声明了一个方法setSecond?

这里我们是因为代码在编译时jvm在对子类进行编译的时候,生成了一个以Object为参数的方法(其实是父类泛型擦除之后生成的Object类型,T–>Object),这个jvm为子类新创建的那个方法就是桥接方法

现在知道了什么是桥接方法,这里提个问题上面子类方法setSecond是重写嘛???
上代码

//父类方法
public void setSecond(T second) {
  this.second = second;
  System.out.println("我是父父类的setEcond方法");
}
//擦除变成(仅仅便于理解)
/*public void setSecond(Object second) {
  this.second = second;
  System.out.println("我是父父类的setEcond方法");
}*/

//子类方法
@Override
public void setSecond(LocalDate second) {
  System.out.println("我是子类的setEcond方法");
}

public class DemoApplication {

    public static void main(String[] args) {
        DateInteval inteval = new DateInteval(LocalDate.now(),LocalDate.now());
        //多态调用(使用的是子类的引用调用setSecond)
        inteval.setSecond(LocalDate.now());
    }
}

//运行结果: 
//我是子类的setEcond方法

发现这里输出的我是子类的setEcond方法可以证明setSecond方法已经被子类给重写成功了,如果没有被重写肯定是输出我是父父类的setEcond方法,所以方法桥接其实可以理解是一种另类的方法重写,其实在字节码底层真正执行的还是子类具体类型的方法,为什么不是执行的Object方法呢?

上代码

public void setSecond(java.lang.String);
    ...
    Code:
      stack=2, locals=2, args_size=2
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: aload_1
         4: invokevirtual #3                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         7: return
      ...
public void setSecond(java.lang.Object);
	...
	Code:
	  stack=2, locals=2, args_size=2
		 0: aload_0
		 1: aload_1
		 2: checkcast     #4                  // class java/lang/String
		 5: invokevirtual #5                  // Method print:(Ljava/lang/String;)V
		 8: return
	  ...
  1. 方法public void setSecond(String),执行System.out.println()方法,我们重点看一Object的方法实现。
  2. 首先我们通过args_size知道这个方法有两个参数(thisObject),存放在局部变量表中。
  3. 代码依次执行aload_0aload_1将局部变量表中的thisObject依次`压入操作数栈栈顶。
  4. checkcast检查栈顶元素(object)是否可以强转成String.
  5. invokevirtual #5 也即使调用public void setSecond(String);当然会使用栈顶两个参数thisObject.
  6. 现在我们明白了,其实绕了一大圈,创建的方法通过类型转换后最终还是调用了重写的方法。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

魔道不误砍柴功

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

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

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

打赏作者

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

抵扣说明:

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

余额充值