zoukankan      html  css  js  c++  java
  • java-基础-泛型

    java泛型通配符问题。
     
    java中的泛型基本用法参考《java编程思想》第四版 p.353
    java泛型中比较难理解的主要是类型擦除和通配符相关。
     
    1.类型擦除
    在编译期间,类型信息会被擦除,可以认为类型的检测是在编译期间进行的(见例1)
    List<String> list = new ArrayList<>();
    list.add("123");
    list.add(new Object());//编译器在编译的时候会检测到这个类型不匹配问题
    所以在生成的class文件中不包含泛型中了类型信息。
    因此下面的代码会报错
    class Test{
    public void method1(List<String> list){
    System.out.println("String list");
    }
    public void method1(List<Integer> list){
    System.out.println("Integer list");
    }
    }
    因为类型擦除的缘故,所以method1中的参数List<String> 和List<Integer> 会被擦除为 List 因此 method1方法签名相同,所以报错。
    另外,类型擦除将擦除到他的第一个边界,可以通过下面的例子来理解
    class F{
    public void f(){}
    }
    class Test{
    public static <T> void method1(T t){
    t.f(); //错误,无法确定传进来的参数就是类F的实例
    }
    public static <T extends F> void method2(T t){
    t.f();//正确,因为编译器在编译期间就检查了参数t是否为类F或者类F的子类的实例 因此可以调用f() 方法
    }
    }
    2.类型擦除与多态
    考虑下面这个例子:
    public class Main1 {
    public static void main(String[] args) {
    F z = new Z();
    z.setValue("233");
    }
    }
    class F<T>{
    private T t;
    public void setValue(T t){
    System.out.println("F's setValue method called");
    this.t = t;
    }
    }
    class Z extends F<String>{
    @Override
    public void setValue(String s) {
    System.out.println("Z's setValue method called");
    super.setValue(s);
    }
    }
    从类型擦除的方面考虑 类F的setValue方法经过类型擦除后实际的签名为 public void setValue(Object obj)。
    所以预测的打印结果应该是:
    F's setValue method called
    实际结果为
    Z's setValue method called
    F's setValue method called,也就是说实现了子类方法覆盖父类方法。
    这其中的原因是编译器替我们生成了桥方法(bridgemethod)
    可以通过反射查看类Z中有哪些方法:
    Method[] methods = Z.class.getDeclaredMethods();
    for( Method method : methods ){
    System.out.print( method.getReturnType().getSimpleName() + " " + method.getName() + "( " );
    Parameter[] parameters = method.getParameters();
    for( Parameter p : parameters ){
    System.out.print( p.getType().getSimpleName() + " " );
    }
    System.out.println( ")" );
    }
    打印结果:
    void setValue( String )
    void setValue( Object )//桥方法
    其实调用的是桥方法setValue( Object )覆盖了父类的方法,然后在该方法中调用void setValue( String )。
    桥方法内容:
    void setValue(Object str){
    this.setValue( (String)str );
    }
    这样就实现了覆盖。
    3.通配符
    3.1上界
     
    List<? extends Animal> list;//表示此list引用可以指向泛型参数为Animal或者其子类的List实现的实例。并不是说这个list容器可以添加Animal或者Animal子类的实例。(之前一直理解错误)
    在看java泛型时,最让我难以理解的就是通配符的问题,不太好理解。通过一个例子解释一下。
    前提:
    class Animal{
    public void walk(){}
    }
    class Cat extends Animal{}
    class Dog extends Animal{}
    class Alaska extends Dog{}
     
    public class Main2 {
    public static void main(String[] args) {
    List<Cat> catList = new ArrayList<Cat>();
    List<Dog> dogList = new ArrayList<Dog>();
    testList(catList);
    testList(dogList);
    }
    public static void testList(List<? extends Animal> list){
    list.add( new Cat() );//错误
    list.add( new Dog() );//错误
    list.add( new Animal() )//错误
    //正确
    for( Animal animal : list){
    animal.walk();
    }
    }
    }
    看上面这段代码,应该能有所理解。
    假设声明为List<? extends Animal> 的 list 可以添加Animal及其子类,那么调用testList方法传入catList时,可以向catList中添 加Animal及其子类, 然而catList被声明为只能存放Cat类的实例,两者矛盾,因此 声明为List<? extends Animal> 的 list 无法添加Animal及其子类(因为无法确定接收的list参数的泛型参数是什么)。 但是可以确定传递给testList方法的list中存放的对象必定为Animal或者其子类,因此迭代改list并用Animal接收。
    3.2下界
     
    List<? super Dog> list //表示此list引用可以指向泛型参数为Dog或者其父类的List实现的实例。并不是说这个list容器可以添加Dog或者Dog父类的实例。
    前提:
    class Animal{
    public void walk(){}
    }
    class Cat extends Animal{}
    class Dog extends Animal{}
    class Alaska extends Dog{}
     
    public class Main2 {
    public static void main(String[] args) {
    testList1( new ArrayList<Dog>() );
    testList1( new ArrayList<Animal>());
    }
    public static void testList1( List<? super Dog> list ){
    list.add(new Dog());
    list.add( new Alaska() );
    list.add( new Animal() );//错误
    for( Dog dog: list ){}//错误
    }
    }
    testList1方法可以接受泛型参数为Dog或者Dog父类的List实现的实例(List<Dog>或者List<Animal>),那么在方法testList1中往list添加Dog类或者Dog类的子类的实例是可行的,因为可以确定传递给testList1方法的list容器可以存放Dog或者Dog的父类。
    但是无法确定确定具体为哪个父类。因此对list迭代并用Dog引用接收是错误的。
     
    文章中的结论均为个人思考总结,并非权威,如有错误,遗漏,望指正。
     
    参考资料:
  • 相关阅读:
    springMVC中添加<mvc:resource>时的问题
    package
    mybatis 解决属性名和字段名不一致
    Linux常用命令
    Hibernate下载
    Hibernate初识
    js根据身份证获取出生年月日
    spring-Boot 热部署
    Struts2---动态action以及应用
    Struts2基础
  • 原文地址:https://www.cnblogs.com/one777/p/7587714.html
Copyright © 2011-2022 走看看