java基础总结三(泛型异常)(代码片段)

<天各一方> <天各一方>     2022-12-11     250

关键词:

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)。

  1. Error与Exception
    • Error是程序无法处理的错误,比如OutOfMemoryError、ThreadDeath等。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
    • Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常,程序中应当尽可能去处理这些异常。
  2. 运行时异常和非运行时异常
    • 运行时异常都是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基础知识点笔记总结(代码片段)

文章目录1.泛型定义2.泛型的小细节3.泛型自定义泛型4.泛型自定义泛型注意点5.泛型方法6.泛型继承注意点7.泛型通配符?8.泛型有限制条件的通配符?9.File类9.1File类的使用9.2file类的常用方法9.3file类总结10.IO流11.IO流体系对... 查看详情

java面试题总结之java基础(代码片段)

 1、JAVA语言如何进行异常处理,关键字:throws,throw,try,catch,finally分别代表什么意义?在try块中可以抛出异常吗?答:Java通过面向对象的方法进行异常处理,把各种不同的异常进行分类,并提供了良好的接口。在Java中,每个... 查看详情

java基础常见面试题总结(下)(代码片段)

Java基础常见面试题总结下异常Exception和Error有什么区别?CheckedException和UncheckedException有什么区别?Throwable类常用方法有哪些?try-catch-finally如何使用?finally中的代码一定会执行吗?如何使用`try-with-resources&#... 查看详情

java基础知识点笔记总结(代码片段)

文章目录1.泛型定义2.泛型的小细节3.泛型自定义泛型4.泛型自定义泛型注意点5.泛型方法6.泛型继承注意点7.泛型通配符?8.泛型有限制条件的通配符?9.File类9.1File类的使用9.2file类的常用方法9.3file类总结10.IO流11.IO流体系对... 查看详情

大数据必学java基础(五十五):泛型深入了解(代码片段)

文章目录泛型深入了解一、引入1、什么是泛型(Generic)2、没有泛型的时候使用集合3、JDK1.5以后开始使用泛型,集合中使用泛型4、泛型总结二、自定义泛型结构1、泛型类,泛型接口2、泛型方法3、泛型参数存在... 查看详情

关于java异常基础知识/编码经验的一些总结(代码片段)

写在前面温习一下毕业以来学习的东西。准备做成一个系列。所以对于每一部分技术点进行一个笔记整理。更多详见java面试的一些总结笔记主要是以网上开源的一本《Java核心面试知识整理》面试笔记为原型,结合工作中学... 查看详情

java基础面向对象,心得总结《三》(代码片段)

1.构造方法和普通的成员方法有什么区别?构造方法是类的一个特殊成员,它会在类实例化对象时被自动调用。而普通方法只有在使用的时候才会被调用。在定义构造方法时要求方法名与类名相同、在方法名的前面没有返... 查看详情

java基础:泛型的具体介绍与使用(代码片段)

我是ABin-阿斌:写一生代码,创一世佳话,筑一览芳华。如果小伙伴们觉得我的文章不错,记得一键三连,感谢~文章目录泛型一、为什么要有泛型(Generic)1、泛型:标签2、举例:3、泛型的设计背景:... 查看详情

java泛型总结(代码片段)

一:什么是泛型?泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的... 查看详情

java泛型相关总结(上)(代码片段)

最近在看《Java核心技术》泛型相关的部分,总结下。泛型程序设计是什么?泛型编程(genericprogramming)是计算机编程中的一种风格,类型通过参数指定。意味着编写的代码可以被不同类型的对象所使用。类型参数(typeparameters)... 查看详情

java基础之多线程总结三(aqsthreadlocal和线程池)(代码片段)

AQS多线程里很多新型锁实现的关键是AQS,AQS指的是AbstractQueuedSynchronizer这个类,整个锁实现过程的关键是CAS操作加volatile。拿ReentrantLock非公平锁的lock和unlock举例,首先lock的源码中调用过程如下:ReentrantLock.lock()--&g... 查看详情

java泛型常问面试题总结(代码片段)

文章目录Java泛型常问问题1.Java中的泛型是什么?使用泛型的好处是什么?2.如何编写一个泛型方法,让它能接受泛型参数并返回泛型类型?3.下面能编译通过?4.Array中可以用泛型吗?5.编写Employee类6.你可以把`List`传递给... 查看详情

java泛型常问面试题总结(代码片段)

文章目录Java泛型常问问题1.Java中的泛型是什么?使用泛型的好处是什么?2.如何编写一个泛型方法,让它能接受泛型参数并返回泛型类型?3.下面能编译通过?4.Array中可以用泛型吗?5.编写Employee类6.你可以把`List`传递给... 查看详情

面向对象的第三单元总结(代码片段)

 (一)梳理JML语言的理论基础、应用工具链情况梳理JML语言的理论基础在JML官网上,是这样定义JML的。Java建模语言(JML)是一种行为接口规范语言,可用于指定Java模块的行为 。它结合了Eiffel的契约方法设计 和Larch... 查看详情

二刷java基础第十六天——泛型和类型通配符(代码片段)

什么是泛型泛型实际的含义就是任意类型(Object)泛型是jdk5引入的新特性,为了提高编译时期的类型安全检测机制EVK:指的就是任意类型<>:泛指<内容>:类型泛指类型:泛型2:泛型的优点可以将运... 查看详情

java基础——异常详解(代码片段)

阅读目录一、异常简介二、try-catch-finally语句三、throw和throws关键字四、java中的异常链五、结束语JAVA异常与异常处理详解回到顶部一、异常简介什么是异常?异常就是有异于常态,和正常情况不一样,有错误出错。在java中,阻... 查看详情

java零基础入门13:java中的泛型(代码片段)

🍅周周有福利,周周有惊喜哪吒社区-风火轮计划🍅Java学习路线配套文章:Java学习路线总结,搬砖工逆袭Java架构师(全网最强)🍅Java经典面试题大全:10万字208道Java经典面试题总结(附答案)&#... 查看详情

java基础考核(代码片段)

Java基础考核基础考核篇1.抽象类、接口、普通类的区别?2.异常机制如何实现?异常体系图异常类的种类异常的处理3.对泛型的理解大家好呀,我是小笙!基础考核篇1.抽象类、接口、普通类的区别?我们以普... 查看详情