Java中的泛型擦除以及Flink中的类型擦除(笔记)

package JavaTest;
import java.util.ArrayList;
/**
 * Create by zgn on 2022/2/8 12:12
 **/
//TODO 泛型擦除测试
public class genericTypeEraser {
    public static void main1(String[] args) {
        ArrayList arrayList = new ArrayList();
        ArrayList<String> list = new ArrayList<>();
//        list.add(1) 编译的时候就会报错
        arrayList.add("abc");
        arrayList.add(2);
        for (int i = 0; i < arrayList.size(); i++) {
            String item = (String) arrayList.get(i);
            System.out.println(item);
        }//运行时报错 java.lang.ClassCastException:
        // java.lang.Integer cannot be cast to java.lang.String
        /*TODO list以第一次添加的数据类型为准 即以String的方式使用 后面再添加Integer类型的数据
        * 程序就崩溃了
        * 为了在编译的时候解决类似的问题 我们可以在代码中指定泛型的类型*/
    }

    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        ArrayList<Integer> list1 = new ArrayList<>();
        Class<? extends ArrayList> aClass = list.getClass();
        Class<? extends ArrayList> aClass1 = list1.getClass();
        System.out.println(aClass.getName());
        System.out.println(aClass == aClass1);//返回结果为true
        /*TODO 该例子可以证明,编译之后程序会采取去泛型化的措施,也就是Java中的泛型,只在编译的阶段有效
        * 在编译的过程中,正确检查泛型结果后,在运行的时候将泛型的相关信息擦除
        * 编译器只会在对象进入JVM和离开JVM的边界处添加类型检查和转换的方法,泛型信息不会进入到运行时的阶段
        * 这就是Java的泛型擦除 泛型擦除有两种方式 java使用的是第一种方式 C++和C#使用的是第二种方式
        * 1)Code sharing。对同一个原始类型下的泛型类型 只生成同一根目标代码 假泛型
        * 2) Code specialization 对每一个泛型类型都生成不同的目标代码  真泛型
        * 程序在运行的时候对泛型类型没有感知 所以上述例子的代码反编译之后只剩下了list 实际上都是
        * Class<? extends ArrayList>的比较 导致上面的例子中输出的结果为true
        * TODO Java 采用Code sharing机制进行类型擦除的原因:
        * 1)Java泛型是1.5才出现的特性 在此之前JVM已经在无泛型的条件下经历了较长时间的发展,如果采用
        * Code specialization 就要对JVM的类型系统做伤筋动骨的改动 并且无法保证向前的兼容性
        * 2)Code specialization对每个泛型类型都生成不同的目标代码,如果有10个不同泛型的list 就要生成
        * 10份字节码 造成代码膨胀*/
    }
}

类型擦除对Flink的影响

public class genericTypeForFlink {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
//        Returns a fixed-size list backed by the specified array.
        env.fromCollection(Arrays.asList("hello", "world", "java", "scala"))
                .map(w -> Tuple2.of(w, 1)).print();
/*Caused by: org.apache.flink.api.common.functions.InvalidTypesException: The generic type
parameters of 'Tuple2' are missing. In many cases lambda methods don't provide enough information
 for automatic type extraction when Java generics are involved. An easy workaround is to use an
 (anonymous) class instead that implements the 'org.apache.flink.api.common.functions.MapFunction'
  interface. Otherwise the type has to be specified explicitly using type information.*/
        env.execute();

    }
}

使用匿名内部类实现

//TODO 使用匿名内部类实现
    public static void main2(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        env.fromCollection(Arrays.asList("hello", "world", "scala", "java"))
                .map(new MapFunction<String, Tuple2<String, Integer>>() {
                    @Override
                    public Tuple2<String, Integer> map(String value) throws Exception {
                        return new Tuple2<>(value, 1);
                    }
                }).print();
/*TODO Tuple2中是有两个泛型的,使用匿名内部类时,会被真正编译为class文件,在对象进入JVM和离开JVM的边界处进行类型的检查和转换,从而保证Tuple2的参数类型能够正确的被检测出来。这种方式其实是静态语言的特性。

而Lambda表达式是在运行时调用invokedynamic指令,用以支持动态语言的方法调用。具体来说,它将调用点(CallSite)
抽象成一个 Java 类,并且将原本由 Java 虚拟机控制的方法调用以及方法链接暴露给了应用程序。在运行过程中,每一条
invokedynamic 指令将捆绑一个调用点,并且会调用该调用点所链接的方法句柄。在第一次执行 invokedynamic 指令时,
Java 虚拟机会调用该指令所对应的启动方法(BootStrap Method),来生成前面提到的调用点,并且将之绑定至该
invokedynamic 指令中。在之后的运行过程中,Java 虚拟机则会直接调用绑定的调用点所链接的方法句柄。亦即在第一次
执行其逻辑时才会确定。但是,对象进入JVM后,就会进行类型擦除,导致没有足够的信息检测出Tuple2中两个泛型的具体
类型。
上面的说法可能让人有点模糊,需要懂得JVM invokedynamic的原理
为了克服类型擦除带来的问题,Flink类型系统中提供了类型暗示(type hint)机制。在map之后调用returns方法,
就可以指定返回类型了。
*/
        env.execute();
    }

类型暗示(type hint)

public static void main3(String[] args) throws Exception {
    StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
    env.setParallelism(1);
    env.fromCollection(Arrays.asList("hello", "world", "scala", "java")).map(w ->
            Tuple2.of(w, 1)).returns(Types.TUPLE(Types.STRING, Types.INT)).print();
    env.execute();
}

对于确定的数据类型(即没有泛型的数据类型),可以随意在flink中使用lambda表达式。

public static void main(String[] args) throws Exception {
    StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
    env.setParallelism(1);
    env.fromCollection(Arrays.asList("hello", "world", "scala", "java"))
            .map(w -> w + "_1").print();
    env.execute();
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值