Java基础总结三(泛型异常)
Posted <天各一方>
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java基础总结三(泛型异常)相关的知识,希望对你有一定的参考价值。
Java基础总结三(泛型、异常)
泛型
泛型的创建
泛型类
我们最常用泛型的地方就是集合,因此,我们编写自己的List来体会泛型:
public class TestList<T> {
private Object[] instances = new Object[0];
public T get(int index) {
return (T) instances[index];
}
public void set(int index,T newInstance) {
instances[index] = newInstance;
}
public void add(T newInstance) {
instances = Arrays.copyOf(instances,instances.length + 1);
instances[instances.length - 1] = newInstance;
}
}
看一下使用方法:
TestList<String> testList = new TestList<>();
testList.add("wlk");
String str = testList.get(0);
System.out.println(str); // wlk
泛型接口
我们除了创建泛型类,泛型接口与其很类似:
public interface Shop <T>{
T buy();
float refund(T item);
}
// 实现
public class RealShop<E> implements Shop<E>{
@Override
public E buy() {
return null;
}
@Override
public float refund(E item) {
return 0;
}
}
再比如,我们想要实现一个水果商店:
public class Fruit {
}
public class Apple extends Fruit{
}
public class FruitShop<E> implements Shop<E>{
@Override
public E buy() {
return null;
}
@Override
public float refund(E item) {
return 0;
}
}
FruitShop<Apple> fruitShop = new FruitShop<>(); // 这个商店卖苹果
FruitShop<Phone> stringShop = new FruitShop<>(); // 这个水果商店卖手机??? 当然不和逻辑
由于我们的商店泛型 E 没有加以限制,会导致错误,我们来给他加上限制:
public class FruitShop<E extends Fruit> implements Shop<E>{
@Override
public E buy() {
return null;
}
@Override
public float refund(E item) {
return 0;
}
}
现在这个水果商店只可以卖 Fruit 或者 Fruit 的子类,如Apple,而不能卖手机。
泛型方法
自己声明了泛型的方法
泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 。
我们给我们的苹果商店创建一个方法,它支持用其他水果换取苹果:
public class FruitShop<E extends Fruit> implements Shop<E>{
@Override
public E buy() {
return null;
}
@Override
public float refund(E item) {
return 0;
}
public <T extends Fruit> E exchange(T item){
return null;
}
}
// 使用
FruitShop<Apple> appleShop = new FruitShop<>();
Apple apple = appleShop.<Banana>exchange(new Banana());
//类型推导,<Banana>可省略
Apple apple = appleShop.exchange(new Banana());
类型擦除
泛型的好处是在编译期进行类型检查和类型转换,且泛型只在编译期有效。泛型是在 JDK 1.5 里引入的,如果不做泛型擦除,那么 JVM 需要对应使得能正确的的读取和校验泛型信息;另外为了兼容老程序,需为原本不支持泛型的 API 平行添加一套泛型 API。
在运行期,声明时的泛型参数会被擦除,在使用处的泛型会被泛型的父类替换,如<E extends Fruit>
,此时使用E
的地方会被Fruit替换。
泛型的协变与逆变
定义:如果A、B表示类型,f()
表示一个类型的构造函数,Type1≤Type2
表示Type1是Type2的子类型,Type1≥Type2
表示Type1是Type2的超类型;
f()
是协变(covariant)的,当A≤B
时有f(A)≤f(B)
成立;
f()
是逆变(contravariant)的:当A≤B
时有f(B)≤f(A)
成立;f()
是不变(invariant)的,当A≤B
时上述两个式子均不成立,即f(A)
与f(B)
相互之间没有继承关系。
对于任意两个不同的类型 Type1 和 Type2,不论它们之间具有什么关系,给定泛型类 G<T>
,G<Type1>
和 G<Type2>
都是没有关系的,即 Java 中泛型是不变的,而数组是协变的,可以验证以下:
ArrayList<Number> list = new ArrayList<Integer>(); // 编译器报错
// 可以顺利通过编译,正常使用。
Number[] arr = new Integer[5];
arr[0] = 1;
// 但也有一定的问题,这个数组接受Number其他子类也不会在编译期报错,而在运行期报错。泛型从根源杜绝了这种错误
在Java的泛型中,是可以支持协变与逆变的,但会有很大的限制:
? extends T
(上边界通配符)实现协变关系,表示?
是继承自T
的任意子类型。也表示一种约束关系,只能提供数据,不能接收数据。? super T
(下边界通配符)实现逆变关系,表示?
是T
的任意父类型。也表示一种约束关系,只能接收数据,不能提供数据。
协变所谓只能提供数据,不能接收数据,是指在实例化出泛型类后,仅可以调用该类中返回值为泛型的方法,而不可以调用参数为该泛型的方法。
逆变则恰好相反。
那么如此限制,?
在我们实际开发中应该如何应用呢?
List<? extends Number> list = new ArrayList<Integer>();
这样的用法在我们平时是用不到的,只有一些场景化的需要,才会使用到泛型通配符。就拿我们的水果商店举例,我们现在有这样一个需求,需要计算集合中水果的总重量,所以编写了如下方法:
public int totalWeight(List<? extends Fruit> list) {
int total = 0;
for(Fruit fruit : list) {
total += fruit.weight;
}
return total;
}
// 使用起来,就可以向这个方法中传入任意Fruit子类的集合了
List<Apple> appleList = new ArrayList<>();
totalWeight(appleList);
List<Banana> bananaList = new ArrayList<>();
totalWeight(bananaList);
再举一个逆变的用法:
List<Apple> appleList = new ArrayList<Fruit>();
当然这种用法是极其少有的,不做讨论。我们给苹果类添加一个方法,这个方法的作用就是给苹果装入集合:
public class Apple extends Fruit{
public void addToList(List<? super Apple> list) {
list.add(this);
}
}
这样,当我们传来Apple集合的时候,就会把Apple装入这个集合,同时,我们也可以传入Fruit的集合:
List<Apple> appleList = new ArrayList<>();
List<Fruit> fruitList = new ArrayList<>();
List<Banana> bananaList = new ArrayList<>();
Apple apple = new Apple();
apple.addToList(appleList); // 编译通过
apple.addToList(fruitList); // 编译通过
apple.addToList(bananaList); // 报错
异常
异常体系
Java中异常的体系是树形结构,所有异常的超类是Throwale,它有俩个子类:Error和Exception,分别表示错误和异常,其中异常类Exception又分为运行时异常(RuntimeException)和非运行时异常,这两种异常有很大的区别,也称之为不检查异常(Unchecked Exception)和检查异常(Checked Exception)。
- Error与Exception
- Error是程序无法处理的错误,比如OutOfMemoryError、ThreadDeath等。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
- Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常,程序中应当尽可能去处理这些异常。
- 运行时异常和非运行时异常
- 运行时异常都是RuntimeException类及其子类异常,如NullPointerException、IndexOutOfBoundsException等,程序中可以选择捕获处理,也可以不处理。
- 非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类,从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过,如IOException、SQLException等以及用户自定义的Exception异常。
异常处理
- 在try中return后,依旧会执行finally。
- finally语句在return语句执行之后return返回之前执行的。
- finally块中的return语句会覆盖try块中的return返回。
- 如果finally语句中没有return语句覆盖返回值,那么原来的返回值可能因为finally里的修改而改变也可能不变(具体区分值和引用)。
- 当发生异常后,catch中的return执行情况与未发生异常时try中return的执行情况完全一样。
- 在Java中如果不发生异常的话,try/catch不会造成任何性能损失。在 Java 类编译后,正常流程与异常处理部分是分开的,类会跟随一张异常表,每一个try-catch都会在这个表里添加行记录。当执行抛出了异常时,首先去异常表中查找是否可以被catch,如果可以则跳到异常处理的起始位置开始处理,如果没有则原地return,并且copy异常的引用给父调用方,接着看父调用的异常表,以此类推。
我们来看一个例子:
public int test(){
int i = 1;
try {
i++;
return i;
}catch (Exception e){
e.printStackTrace();
}finally {
System.out.println("finally");
i++;
}
return 0;
}
System.out.println(test());
// out ->
finally
2
我们看到,finally执行了,那么i++应该也是执行了,为什么返回2,我们通过查看字节码可知
Code:
stack=2, locals=4, args_size=1
0: iconst_1
1: istore_1
2: iinc 1, 1
5: iload_1
6: istore_2
7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
10: ldc #12 // String finally
12: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
15: iinc 1, 1
18: iload_2
19: ireturn
20: astore_2
21: aload_2
22: invokevirtual #15 // Method java/lang/Exception.printStackTrace:()V
25: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
28: ldc #12 // String finally
30: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
33: iinc 1, 1
36: goto 53
39: astore_3
40: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
43: ldc #12 // String finally
45: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
48: iinc 1, 1
51: aload_3
52: athrow
53: iconst_0
54: ireturn
总结一下,总体的意思就是,当执行完try块的时候,会计算return表达式的值,然后把这个返回值存在另一个临时变量里面,最后返回的时候会重新读取存放的变量,因此在finally后面无论如何修改,都不会影响返回值。那么,如果finally中加了return后,结果就会覆盖原有的返回值了。
以上是关于Java基础总结三(泛型异常)的主要内容,如果未能解决你的问题,请参考以下文章