转载

Java 包教不包会系列——泛型

第一次接触 java 的时候,一次编译,到处运行,给人的感觉就很强大,必须入坑啊。不过现在 java 主流使用场景还是后端这块,图形化界面开发并不擅长。java 在后端语言的竞争中依然屹立不倒,和 java 的生态有很大关系,但不可否认的是,java 在高并发场景下的稳定性确实可靠。今天还看了一个关于够浪(golang) 语言的评价,也准备考虑入门下作为技术储备。有时间学习的时候,我一定要多学点。

最近打算花时间重拾 java ,决定看看 jdk 源码,再提升一个层次。也想写一个关于 java 和 SpringBoot 的系列博客。

泛型

JDK 1.5 之前

当我入坑 java 的时候,那时候还是 jdk 1.7 的天下。不过泛型确实是个好东西。没有泛型之前我们可能会这样写代码

@Test
    public void run1() {
        // 有个集合你想保存 String 类型数据
        List list =new ArrayList();

        list.add("1111");

        String o = (String) list.get(0);
        System.out.println(o.length());

        // 可能会导致别的开发人员存储的是 int 类型
        list.add(2222);
    }
复制代码

上述代码存在的问题:

  • 集合存储的元素需要强制类型转换,才能使用对应的 Api。
  • 编译期语法检测不到我提到的这个问题,在运行时期会有隐患

JDk 1.5

1.7 增加的特性 菱形泛型我掺杂到一起说了

// 有个集合你想保存 String 类型数据
        List<String> list =new ArrayList();

        list.add("1111");

        String s = list.get(0);
        System.out.println(s.length());

        // 下列会在编译时期就会报错
        // list.add(2222);
复制代码

是不是喜大普奔,不那么反人类了

泛型

泛型:参数类型化

  • 泛型可以在类上或方法声明,方法上的泛型优先级高于类上的优先级。为便于理解最好不和类上声明相同命名。
  • 静态方法不能使用类上泛型声明,只能在方法上声明
  • 常用的泛型声明(T,E,K,V,?)
    • ?表示不确定的 java 类型
    • T (type) 表示具体的一个java类型
    • K V (key value) 分别代表java键值中的Key Value
    • E (element) 代表Element
    • S、U、V 等:多参数情况中的第 2、3、4 个类型

代码验证

  • 泛型可以在类上或方法声明,方法上的泛型优先级高于类上的优先级
public class ClientTest<T> {

    /**
     * 验证泛型可以在类上和方法上声明
     * 不能这样 <K1 super Parent>
     */

    public <T2 extends ArgsParent> T2 run2(T k) {
        System.out.println(k.getName());
        System.out.println(k);
        return (T2) k;
    }
}
复制代码

以上验证了泛型可以在类上和方法上声明

package com.fly.study.java.generics;

import org.junit.Before;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;

/**
 * @author 张攀钦
 * @date 2019-09-16-00:04
 * @description 泛型验证
 */
public class ClientTest<T> {

    private ClientTest clientTest;

    private Parent parent;

    private Son son;

    private ClientTest<Son> sonClientTest;

    private ArgsParent argsParent;

    private ArgsSon argsSon;


    /**
     * 限制方法参数的泛型和返回值类型,泛型优先使用方法上的,当方法上没有使用类上的
     * 不能这样 <K1 super Parent>
     */

    public <T extends ArgsParent, T2 extends ArgsParent> T2 run2(T k) {
        System.out.println(k.getName());
        System.out.println(k);
        return (T2) k;
    }



    @Before
    public void before() {
        clientTest = new ClientTest();
        parent = new Parent();
        parent.setName("parent");

        son = new Son();
        son.setAge(18);

        sonClientTest = new ClientTest<>();

        argsParent = new ArgsParent();
        argsParent.setName("argsParent");
        argsSon = new ArgsSon();
    }

    @Test
    public void test2() {
        System.out.println(clientTest.run2(argsParent));
        System.out.println(clientTest.run2(argsSon));
        // 验证方法上的泛型优先于类上的泛型
        sonClientTest.run2(argsSon);
//        下面用法错误
//        sonClientTest.run2(son);
    }
}

@Data
public class ArgsParent {
    private String name;
}

@Data
public class ArgsSon extends ArgsParent {
    private Integer age;
}

@Data
public class Parent {
    private String name;
}

@Data
public class Son extends Parent {
    private Integer age;
}
复制代码

我在类上和方法上使用了同一个泛型 T ,但是类型限制不同,类上泛型 T 我在 new 对象的时候是 ClientTest sonClientTest,而我在方法上指定了T 为 ,运行方法的时候,如果参数只能传入 ArgsParent及其子类即可验证我的结论。

// public class ClientTest<T> 
        // public <T extends ArgsParent, T2 extends ArgsParent> T2 run2(T k)
        // argsSon 为 ArgsParent 的子类对象
        sonClientTest.run2(argsSon);
        //  下面用法错误,son 为 Son 的实例对象
        //  sonClientTest.run2(son);
复制代码
  • 静态方法不能使用类上泛型声明,只能再方法上声明
public class ClientTest<T> {
    /**
     * 静态方法上的泛型不能使用类上的,只能再方法上声明泛型
     */
    public static <T extends ArgsParent, T2 extends ArgsParent> void run4(T k) {
        System.out.println(k);
    }
  	
  	// 语法错误
    public static  void run4(T k) {
        System.out.println(k);
    }
}
复制代码
  • 泛型限定符

字面意思理解泛型限定符很方便,代码中的注释即可说明意思

// 限定参数类型只能为 ArgsParent及其子类
<S extends ArgsParent>

// 语法错误,jdk 1.8
<S super ArgsParent>

// 限定参数类型只能为 ArgsParent 及其子类
<? extends ArgsParent>

// 限定参数只能为 ArgsParent 及其父类
<? extends ArgsParent>
复制代码
public class AllGenerics<T> {

    public void run1(T t) {
        System.out.println(t);
    }

    public <S> void run2(S s) {
        System.out.println(s);
    }
    
    // 限定参数类型只能为 ArgsParent及其子类
    public <S extends ArgsParent> void run3(S s) {
        System.out.println(s);
    }
    
    // 语法错误,
    // public <S super ArgsParent> void run3(S s) {
    // System.out.println(s);
    //}
    
    // 限定参数类型只能为 ArgsParent及其子类
    public void run4(List<? extends ArgsParent>  s) {
        System.out.println(s);
    }

    // 限定参数只能为 ArgsParent及其父类
    public void run5(List<? super ArgsParent>  s) {
        System.out.println(s);
    }
}
复制代码

泛型擦除

泛型只在编译期有效,编译之后会对泛型声明进行替换,对于能确定类型的使用确定类型,不确定的使用 Object 代替。

  • 不能确定泛型类型的时候
// 源码
public class AllGenerics<T> {
     public void run1(T t) {
        System.out.println(t);
    }
}

// 编译之后的代码可以这样理解

public class AllGenerics<Object> {
     public void run1(Object t) {
        System.out.println(t);
    }
}

复制代码
  • 对使用泛型限定符
public class AllGenerics<T> {
     public <S extends ArgsParent> void run2(S s) {
        System.out.println(s);
    }
}

// 编译之后的代码可以这样理解

public class AllGenerics<ArgsParent> {
     public void run1(ArgsParent t) {
        System.out.println(t);
    }
}
复制代码

代码验证

  • 泛型只在编译时期有效
@Data
public class ClientTestSuperT {
    @Test
    public void test1() throws Exception{
        List<String>t=new ArrayList<>();
        Method add = t.getClass().getMethod("add",Object.class);
        
        // 添加 Integer 类型数字
        add.invoke(t,1);
        System.out.println(t);
    }
}
复制代码
  • 泛型擦除使用可以确定的类型替换
package com.fly.study.java.generics;


import org.junit.Test;

import java.lang.reflect.Method;


public class AllGenerics<T> {

    public <S extends ArgsParent> void run2(S s) {
        System.out.println(s);
    }

    public void run1(T t) {
        System.out.println(t);
    }

    @Test
    public void test1() throws Exception {
        AllGenerics<String> allGenerics =new AllGenerics<>();
        Method method= allGenerics.getClass().getMethod("run1", Object.class);
// java.lang.NoSuchMethodException:
// com.fly.study.java.generics.AllGenerics.run1(java.lang.String)
//      Method method= allGenerics.getClass().getMethod("run1", String.class);
        method.invoke(allGenerics,"111");
    }

    @Test
    public void run2() throws Exception {
        AllGenerics<String> allGenerics =new AllGenerics<>();
        Method method= allGenerics.getClass().getMethod("run2", ArgsParent.class);
        ArgsParent argsParent = new ArgsParent();
        argsParent.setName("测试");
// java.lang.NoSuchMethodException: 
// com.fly.study.java.generics.AllGenerics.run1(java.lang.String)
//  Method method= allGenerics.getClass().getMethod("run2", Object.class);
        method.invoke(allGenerics,argsParent);
    }

}
复制代码
  • 验证思路
AllGenerics<T> 编译之后 AllGenerics<Object>
    
    // 编译之后为 public void run1(Object t)
    public void run1(T t) {
        System.out.println(t);
    }
    
    // 编译之后 public void run2(ArgsParent s)
    public <S extends ArgsParent> void run2(S s) {
        System.out.println(s);
    }
复制代码
  • 利用反射获取Method run1,参数 Object 的方法能获取,而获取 run1,参数 String 的找不到
@Test
public void test1() throws Exception {
    AllGenerics<String> allGenerics =new AllGenerics<>();
    Method method= allGenerics.getClass().getMethod("run1", Object.class);
    method.invoke(allGenerics,"111");
}
复制代码

上述代码可以正常运行,验证编译之后代码确实替换了

@Test
public void test1() throws Exception {
    AllGenerics<String> allGenerics =new AllGenerics<>();
    Method method= allGenerics.getClass().getMethod("run1", String.class);
    method.invoke(allGenerics,"111");
}
复制代码

上述代码运行报错 java.lang.NoSuchMethodException:com.fly.study.java.generics.AllGenerics.run1(java.lang.String),说明编译之后的代码中没有这个方法

  • 对于使用限定类型的泛型,替换为限定的类型
// 方法编译之后为:public  void run2(ArgsParent s)
public <S extends ArgsParent> void run2(S s) {
    System.out.println(s);
}
复制代码
@Test
public void run2() throws Exception {
    AllGenerics<String> allGenerics =new AllGenerics<>();
    Method method= allGenerics.getClass().getMethod("run2", ArgsParent.class);
    ArgsParent argsParent = new ArgsParent();
    argsParent.setName("测试");
    method.invoke(allGenerics,argsParent);
}
复制代码

以上获取到了方法,说明我的猜测正确

@Test
public void run2() throws Exception {
    AllGenerics<String> allGenerics =new AllGenerics<>();
    ArgsParent argsParent = new ArgsParent();
    argsParent.setName("测试");
    Method method= allGenerics.getClass().getMethod("run2", Object.class);
    method.invoke(allGenerics,argsParent);
}
复制代码

代码报错,找不到对应的方法

原文  https://juejin.im/post/5d85f25a5188250dd6739f08
正文到此结束
Loading...