JavaSE-泛型、异常


泛型

泛型是JDK5新引入的特性,很大程度上都是为了让集合能记住元素的数据类型。在没有泛型之前,一旦把一个对象丢进Java集合中,集合就会忘记对象的类型,把所有的对象当成Object类型处理。当程序从集合中取出对象后,就需要进行强制类型转换,这种强制类型转换不仅使代码臃肿,而且容易引起ClassCastException异常
增加了泛型支持后的集合,完全可以记住集合中的元素类型,并可以在编译时检查集合中元素的类型,如果试图向集合中添加不满足类型的对象,编译器就会提示错误。增加泛型后的集合,可以让代码更加简洁,程序更加健壮

总结:泛型是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查

泛型类基本语法

泛型类定义语法:

修饰符 class 类名<类型参数,类型参数,……> {
    private 类型参数 变量名;
    ……
}

\[常见的泛型标识 \begin{cases} \text{E} \to \text{Element元素,在集合中存放元素} \\ \text{T} \to \text{Type类型,指代Java类(需要时还可以用临近的字母U和S表示任意类型)} \\ \text{K} \to \text{Key键} \\ \text{V} \to \text{Value值} \\ \text{N} \to \text{Number数值类型} \\ \text{?} \to \text{表示不确定的Java类型,是类型通配符,代表所有类型} \end{cases} \]

使用语法:类名<具体的数据类型> 对象名 = new 类名<具体的数据类型>(); 注意:泛型只能支持引用数据类型

JDK7以后支持泛型的菱形语法,后面的 <> 中的具体的数据类型可以省略不写:
类名<具体的数据类型> 对象名 = new 类名<具体的数据类型>();

  • 静态方法、静态初始化块或者静态变量的声明和初始化中不允许使用泛型类型形参
  • 由于系统中并不会真正生成泛型类,所以instanceof运算符不能使用泛型类
class Generic {
    private T key;
    public Generic() {}
    public Generic(T key) {this.key = key;}
    public T getKey() {return key;}
    public void setKey(T key) {this.key = key;}
}

public class Main {
    public static void main(String[] args) {
        Generic strGeneric = new Generic<>("泛型案例");
        String key = strGeneric.getKey();
        System.out.println(key);
        // 泛型类在创建对象的时候,如果没有指定类型,将按照Object类型来操作
        Generic generic = new Generic("ABC");
        Object genericKey = generic.getKey();
        // 同一泛型类,根据不同的数据类型创建的对象,本质上是同一字节码文件
        Generic intGeneric = new Generic<>(100);
        System.out.println(strGeneric.getClass() == intGeneric.getClass());
    }
}

泛型类派生子类

子类也是泛型类,子类和父类的泛型类型要一致

  • public ChildGeneric extends Generic

子类不是泛型类,父类要明确泛型的数据类型

  • public ChildGeneric extends Generic
  • public ChildGeneric extends Generic

如果从Apple类派生子类,则在Apple类中所有使用T类型形参的地方都将被替换成String类型。即它的子类将会继承到String getInfo()和void setInfo(String info)两个方法,如果子类需要重写父类的方法,就必须注意这一点

如果使用Apple类没有传入实际的类型参数,Java编译器可能会发出警告:使用了未经检查或不安全的操作,这就是泛型检查的警告。如果希望看到该警告的更详细信息,则可以通过为javac命令增加 -Xlint:unchecked 选项来实现。此时系统会把Apple类里的T形参当成Object类型处理

泛型方法

泛型方法可以定义在普通类中,也可以定义在泛型类中(JDK5提供了对泛型方法的支持)

  • 声明此方法为泛型方法
  • 只有声明了 的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法
  • 表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T
  • 泛型方法支持静态static修饰
修饰符  返回值类型 方法名(形参列表){
    方法体……
}

泛型通配符

泛型通配符:一般使用 ? 代替具体的类型实参。所以类型通配符是类型实参,而不是类型形参

  • 类型通配符的上限:类/接口 <? extends 实参类型> 要求该泛型的类型只能是实参类型或实参类型的子类类型
  • 类型通配符的上限:类/接口 <? super 实参类型> 要求该泛型的类型只能是实参类型或实参类型的父类类型

反省上限:? extends Car ?必须是Car或者其子类
泛型下限:? super Car ?必须是Car或者其父类

泛型擦除

泛型是JDK5才引进的,在这之前是没有泛型的,但是泛型代码能够很好地和之前版本的代码兼容。那是因为,泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,我们称之为——类型擦除

  • 无限制类型擦除:当把一个具有泛型信息的对象赋给另一个没有泛型信息的变量时,所有在尖括号之间的类型信息都将被扔掉。比如一个List类型被转换为List,则该List对集合元素的类型检查变成了类型参数的上限(即Object)

泛型数组

可以声明带泛型的数组引用,但是不能直接创建带泛型的数组对象

// 报错
ArrayList[] listArr = new ArrayList[];
// 不报错(但不安全会报ClassCastException,尽量不使用)
ArrayList[] list = new ArrayList[5];
ArrayList[] listArr = list;
// 正确的方式
ArrayList[] listArr = new ArrayList[5];

可以通过java.lang.reflect.Array的newInstance(Class,int)方法创建T[]数组

public class Fruit{
    private T[] array;
    public Fruit(Class clz,int length) {
        array = (T[]) Array.newINstance(clz,length);
    }
}

泛型没写好,随便看看吧

异常处理

异常:异常是程序在“编译”或者“执行”的过程中可能出现的问题,注意:语法错误不算在异常体系中

  • 编译时异常:没有基础RuntimeException的异常,编译阶段就会出错
  • 运行时异常:继承自RuntimeException的遗产或其子类,编译阶段不报错,运行可能报错

\[{\Large \text{Throwable} \begin{cases} \text{Error}\\ \text{Exception} \begin{cases} \text{RuntimeException及其子类}\\ \text{除RuntimeException之外所有的异常} \end{cases} \end{cases}} \]

  • ERROR:系统级别问题、JVM退出等,代码无法控制
  • Exception:java.lang包下,称为异常类,它表示程序本身可以处理的问题
  • RuntimeException及其子类:运行时异常,编译阶段不会报错 (空指针异常,数组索引越界异常)
  • 除RuntimeException之外所有的异常:编译时异常,编译期必须处理的,否则程序不能通过编译 (日期格式化异常)

运行时异常

运行时异常:一般是程序员业务没有考虑好或者是编程逻辑不严谨引起的程序错误

  • 空指针异常: NullPointerException,直接输出没有问题,但是调用空指针的变量的功能就会报错
  • 数组索引越界异常: ArrayIndexOutOfBoundsException
  • 数学操作异常:ArithmeticException
  • 数字转换异常:NumberFormatException
  • 类型转换异常:ClassCastException