计算机 · 2021年12月18日 0

Java Language

总是想重新学习一遍Java,或者觉得自己掌握不够扎实想补全一下,除了Oracle的官方教程外,最近发现了logicbig.com这个网站,上面的基本都带有完整的代码实例,讲解也清楚简洁,排版也很好看,所以最近开始认真落实这个一直以来都有的想法。Oracle的官方教程其实也很好,就是枯燥了点,代码没有高亮显示,看着容易睡觉。

checked vs. unchecked exception

下面是Exception的继承关系图:

除了Error、RuntimeException这两个类以及他们的子类,其他类型的Exception都是需要用catch处理的。当程序中出现Error、RuntimeException及相应子类时代表jvm遇到了错误(unrecoverable),直接让程序出错退出就好,不用catch。这些exception就是unchecked exception。而对于其他类型的exception,他们是编写程序是就预计到可能会产生的程序状态,因此需要程序进行相应的处理。这些exception是checked exception。
unchecked exception也可以像checked exception一样进行catch处理,但反过来checked exception不能像unchecked exception一样不被catch。

covariant return type

子类在重载父类的方法时,被重载父类方法的返回值类型在子类的实现中可以用该类型的子类代替。

public class CovariantReturnExample {

    interface SuperType {
    }

    interface SubType extends SuperType {
    }

    interface A {
        SuperType getType ();
    }

    interface B extends A {
        SubType getType ();
    }
}

关于泛型、数组、方法返回值的协变、逆变、双变和不变性

关于数组协变的理解,参考这个回答

我们经常或者Java Specificatoin说Subtype[]是Supertype[]的子类,但其实应该不是!。一个自然的想法:如果称一个类为另一个类的子类,那么子类应该能做父类能够做的事情。但是Supertype[]可以存储Supertype类型的对象,Subtype[]却不可以。所以把Subtype[]当做Supertype[]的子类可看作为Java的一个瑕疵,作为兼容性被保留了下来。而Java在后面引入泛型时就理清了这个问题:Generic<Supertype>和Generic<Subtype>没有任何关系。

关于数组协变这个瑕疵导致的问题:

public class CovariantArraysExample {

  public static void main(String[] args) {
      Integer[] integers = new Integer[10];
      Number[] numbers = integers;
      numbers[0] = new Double(25);
  }
}

因为把Subtype[]当做Supertype[]子类这种错误理解(编译器也是这么理解的),上面这个程序是可以编译通过的,我们觉得Integer[]这个子类就是可以像Number[]父类一样存储一个Number对象,但是到了运行时的时候,Integer[]决定要存储这个Number对象,然后发现这个Number对象实际类型为Double,不管把这个对象看作是Number还是Double,都不是Integer[]能够存储的Integer类型,于是就只有报ArrayStoreException。

public class CovariantArraysExample {

  public static void main(String[] args) {
      Integer[] integers = new Integer[10];
      Number[] numbers = integers;
      numbers[0] = new Integer(25);
  }
}

对于这个程序情况稍有不同,Integer[]在决定要存储这个Number对象时,发现这个Number对象实际是个自己可以存储的Integer,因此就可以正确存储该对象,不会报ArrayStoreException。

上面写的两个程序均可以通过编译,但是运行时却有一个会报错,编译器对这种代码写法的容忍潜在地增加了我们写出bug代码的可能性。

更好的泛型
引入泛型的原因: 保证编译时的类型安全,通过使用泛型不会出现编译通过了但到了运行时却又类型不匹配的问题。
首先,泛型明确了Generic<Subtype>和Generic<Supertype>是两个路人,没有任何关系。
其次,泛型要想办法避免上面那种编译过了运行时却可能出错的情况。

public class InvariantGenericsExample {

  public static void main(String[] args) {
      List<Integer> integers = new ArrayList<>();
      List<Number> numbers = new ArrayList<>();
      numbers = integers;
  }
}

由于List<Integer>和List<Number>是不相干的两个类型,因此上面的代码在编译时就会出错。

public class CovariantGenericsExample2 {

  public static void main(String[] args) {
      List<Integer> integers = new ArrayList<>();
      integers.add(1);

      List<? extends Number> numbers = integers;
      Double d = new Double(23);
      numbers.add(d);
  }
}

把List<Integer>赋值给List<? extends Number>是可取的,但是上面的程序是会编译出错的,因为List<? extends Number>里的通配符类型(?)是capture类型,无论后面这个变量d是Double还是Integer类型,都是和这个capture类型不相匹配的,因此会编译出错。像上面这种赋值后的numbers只能读取或者删除列表里的内容,不能对其进行添加或者修改。

public class ContravariantGenericsExample {

    public static void main(String[] args) {
        List<A> aList = new ArrayList<>();
        aList.add(new A());

        List<? super B> bList = aList;
        bList.add(new B());

        System.out.println(bList);
    }

    private static class A {
    }

    private static class B extends A {
    }
}

上面的程序却又是可以正常编译运行的,因为List<? super B>里的这个capture类型始终是B的父类,那么向一个父类的列表里添加一个子类对象自然是可以的。

关于这个泛型、协变之类的东西感觉自己也还没有搞清楚,需要以Oracle官方文档为准。

Java中条件运算符的结果的类型判定规则

这真是钻牛角尖了,不知道为何搞这么复杂,拿去装逼还不错,传送门

参考