IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java基础复习(九):异常、枚举、反射、注解 -> 正文阅读

[Java知识库]Java基础复习(九):异常、枚举、反射、注解

Java复习的差不多了,但是怕面试挑细碎知识点问,还是集中做一个总结吧

异常

异常也是一种类型,它是JDK对“异常事件”的一种抽象。配合throw 关键字可以抛出一个异常对象,这个异常对象如果一直不能被捕获并处理,最终将会抛给JVM,JVM执行默认行为:在标准错误输出流system.err中打印堆栈信息。

所以,如果问我什么是异常,我会说:Java的异常是对非正常事件的一种抽象,具体的异常信息通常都会创建一个异常实例,用于保存这异常的元信息

分类

异常的顶层父类是throwable。Throw关键字底层对应athrow命令,只能抛出throwable的实例。

Throwable有两大子类:
【1】error
Error一般是系统相关的错误,例如OOM、Stack Overflow等
【2】exception
异常又可以分为运行时异常runtimeException编译期异常
runtimeException以及它的子类都是非受检异常,无论是主动抛出这个异常还是调用了声明该异常的方法。都可以不去处理,而交给JVM去处理。JVM会调用注册在线程上未捕获异常处理器(能抛给JVM异常处理器的异常一定是运行时异常)

public static void main(String[] args) {
    Thread.currentThread().setUncaughtExceptionHandler((r,e)->{
        System.out.println("没事,抛者玩的");
        System.out.println(r.getName()+" "+e.getMessage());
    });
    throw new RuntimeException();
}

通过设置异常处理器,也可以实现子线程与主线程的通信,如错误报告。

除了runtimeException以及它的子类,其他的异常(Exeption类型)都是受检异常。必须进行try-catch处理或者throws向上抛出。(当然了,如果抛给了JVM则执行默认的处理器,标准错误输出流进行输出)

常见的受检异常包括SQL异常、IO异常、文件无法找到异常、EOF异常等。
注意:Error类及其子类表示程序本身无法修复的错误,javac不会检查他们,当程序运行时出现这些异常,Java进程会直接终止——Error是非受检的
不过Error类型一般是JDK预设好的,用户一般不会主动扩展错误类型,而用户总是选择扩展RuntimeException类型。

异常处理机制

一个异常的执行顺序:
【1】new一个异常对象
【2】中止当前正在执行的程序
【3】弹出异常对象的引用
【4】异常处理机制接管被中止的程序
【5】进入异常处理程序的catch代码块继续执行。

JVM使用**方法调用栈(method invocation stack)**来跟踪每一个线程中一系列方法的调用过程。该栈保存了每个调用方法的本地信息(如方法的局部变量)。每个线程都有一个独立的方法调用栈。Java主线程中栈底方法是main()方法。每当一个新方法被调用时,JVM将描述该方法的栈结构放入栈顶,位于栈顶的方法就是正在执行的方法。

当一个方法正常执行完毕,JVM就会从调用栈中弹出该方法的栈结构(出栈),然后继续执行栈中下一个方法。
如果在执行方法的过程中抛出了异常,则JVM必须能找到catch代码块,如果在当前方法找不到,JVM就会从调用栈中弹出该方法的栈结构,继续到下一个方法中查找合适的catch代码块。

每个方法调用栈的catch结构可以看到当前栈帧的异常处理器,如果找不到就会栈帧出栈,并寻找上一调用级别的异常处理器。最终main方法栈帧出栈后,交给JVM的未捕获异常处理器进行处理。如果没有设置则执行默认行为:
【1】调用异常对象的printStackTrace()方法,打印来自方法调用栈的异常信息
【2】如果该线程不是主线程,则终止这个线程,其他线程继续正常运行。如果是主线程,那么整个应用程序被终止。

finally

如果finally和try/catch都有返回语句,那么后者的return内容将会被覆盖掉。后者返回之前会执行finally的内容。
Finally语句不被执行的唯一情况就是:执行了system.exit()方法——用于终止当前的Java进程

如果在try和catch进行return,首先这个return的值是赋给了一个局部变量,如果finally中不存在任何return语句,那么返回的就是这个局部变量。(字面量或者引用被赋值给一个局部变量)
如果finally中存在return ,则最终返回的是finally中的return。Catch中的return X 相当于将X存放到了一个局部变量(如果X=a++也是会执行的)。

return覆盖的情况:

static int fun(){
    int a =0;
    int b =1;
    try{
        return a++; //a++会被执行
    }finally {
        return a+b; //最终返回2
    }
}

异常丢失的情况:

static int fun(){
    int a =0;
    int b =1;
    try{ 
	//异常对象仅仅是被保存起来,但是抛出异常之前就返回了
        throw new RuntimeException((a++)+" ");
    }finally {
        return a+b; //没有异常抛出,并返回
    }
}

局部变量是引用类型

int[] a={1};
try{
    return a; //引用指向的数组内容被改变了
}finally {
    a[0]=33;
}

局部变量是字面量类型

static int fun(){
    int[] a={1};
    try{
      return a[0]; //字面量已经被保存到了临时变量,最终返回值不变
    }finally {
        a[0]=33;
    }
}

总结:finally中不建议使用return,因为会造成“异常丢失”和“返回值内容被覆盖”

throw和throws

Throw用于抛出一个异常,写在方法体内。而throws用于声明一个方法可能产生的所有异常。
Throws更多是提醒调用者“我这个方法可能抛出哪些异常”。如果是受检异常,则必须处理,如果是运行时异常则可以不去处理。
注意:如果我们拿到的是一个受检异常,我们必须try/catch进行处理,或者throws声明。而我们拿到一个非受检异常,也可以去处理或者抛出,但是不是必要的。

    public static void a() throws IOException {

    }

throws 甚至可以加在一个空实现的方法上,但是如果另外一个方法b()调用了a(),a() ; 这行代码本身就会被视作一个异常,b()方法必须选择处理或者throws。而如果a()如果声明的是一个runtimeException则b()调用a()不需要进行额外处理。

换一个角度描述一下他们的关系:
如果一个方法体内throw new XXException() ,或是调用一个throws XXException的方法,他们(这一行代码)的效果相似。如果这是一个受检异常,调用方必须做出throws或try/catch。如果这是一个非受检异常,则调用方则不需要额外处理。

如果我们需要实现runnable接口对应的run()方法,则我们只能抛出运行时异常,而对应受检异常只能try/catch进行处理,因此重写一个方法时不允许抛出更严格的异常,而run方法没有throws任何异常,因此我们只剩下唯一一种选择:try/catch

@Override
    public void run() {
        try {
            throw new IOException();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

总结:异常也是java提供的一种类型,每个异常实例代表一种错误请求,异常可以被用于throws声明或者throw抛出。

枚举

使用枚举,可以枚举可选的选项,并且限定取值的范围。例如枚举进程类的状态:就绪、运行、等待、停止。
通过enum定义的枚举,是java.lang.Enum类的子类。
Enum提供了两个成员:
【1】name。它就是枚举中定义的变量名的String形式。
【2】ordinal。序数,从0开始序号,与static块中的声明的顺序有关。compareTo就是根据ordinal进行比较的。

枚举类型的本质

enum AccountType
{
    SAVING, FIXED, CURRENT;
    private AccountType()
    {
        System.out.println("It is a account type");
    }
}

枚举的底层,实际上被转换为了继承java.lang.Enum类的实体类,枚举的成员变成了对应实体类型的字段,并且生成了一个新的构造函数,如果原来定义了构造函数,则在其基础上加上两个参数,生成新的构造函数。(Enum类不能手动扩展,它内部的构造函数只能通过编译器调用)

final class AccountType extends java.lang.Enum
{
    public static final AccountType SAVING;
    public static final AccountType FIXED;
    public static final AccountType CURRENT;
    public static final AccountType[] $VALUES;
    static{
            SAVING  =  new AccountType("SAVING", 0);
        FIXED   =  new AccountType("FIXED",1);
        CURRENT = new AccountType("CURRENT", 2);
        $VALUES = new AccountType[]{
                SAVING, FIXED, CURRENT
        };
    }
    private AccountType(String s,int i)
    {
        super(s,i);  
        System.out.println("It is a account type");  //会打印三遍
    }
}

枚举类中的所有枚举值都是静态常量(final static),而且类被final修饰,不能被继承。
除此之外,编译器还会生成两个静态方法:values()和valueOf()。
values()用于获取枚举类中的所有变量,并且作为数组返回(其实就是返回$VALUES数组),而valueOf(String name)根据名称获取枚举变量。

values和valueOf方法都是编译器插入到生成类的静态方法,因此如果将生成类向上转型为Enum,是无法调用这两个方法的。

Enum是枚举类型的公共基本类(抽象类),不能手动继承,只能编译器帮我们继承。
能被复写的只有toString方法,其他方法都被final修饰了。

枚举类特性

枚举类和正常类没什么大的区别。可以实现接口、定义属性以及方法。但是不能继承父类,因为已经继承了Enum。

enum MyEnum{
    A{
        @Override
        void fun() {
        }
    },
    B{
        @Override
        void fun() {
        }
    };
    abstract void fun();
}

可以定义抽象方法,但是必须提供实现。该方式可帮助不同的枚举实例表现出不同的行为(枚举的多态).
另一方面,枚举也可以作为类型声明、入参、返回值等,它还可以与switch搭配使用。

    MyEnum fun(MyEnum myEnum){
        switch (myEnum){
            case A:
                return myEnum;
        }
        return null;
    }

总结:把枚举看作一个类型,定义的枚举值就是它的静态常量成员,并且我们的实例取值范围只能在这些声明的成员中进行选择。这个访问控制的过程是JVM保证的。因此用枚举实现单例模式也是简单且安全的。

    enum Singleton{
        INSTANCE; //用于外部访问
        private String name; // 用来设置单例实例的业务内容...

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

反射

反射,就是在运行时,拿到一个类型的元信息。从哪里拿到这些元信息?JVM的元信息统一保存在方法区,类加载阶段,class文件被载入内存,并且转换为了保存在方法区的数据结构中。同时向堆中放入一个class实例,用作元信息访问的入口。我们可以通过class.forName()、实例.getClass()、或者类型.class这三种方法拿到这个class实例。(8大基本类型和void关键字都具有class实例)

拿到class实例,可以直接使用newInstance方法通过空参构造器创建对象,或者也可以使用getConstructor()根据参数数量、类型拿到具体的构造器实例,当找到需要调用的方法时,都会复制一份而不是使用原来的实例,保证数据隔离。反射是线程安全的。

一旦我们拿到这个class实例,就找到了访问类型元数据的入口,我们可以通过这个class实例拿到构造器实例、方法实例、字段实例、注解实例等,进而我们还可以拿到字段类型、方法参数类型等。反射技术通常用于实现各种框架包括注解的解析、依赖注入、占位符替换、动态代理等。

反射的特点可以用于实现类型之间的解耦,提供系统灵活性,例如输入全限定类名,反射创建一个实例(动态加载),避免大量if/else条件(静态加载)。包括spring的依赖注入、切面编程以及解析配置文件都离不开反射特性的支持。
但是反射的性能比较低(一般配合缓存使用)、JVM无法对某些动态加载的类型进行优化,而且一定程度上破坏了Java的封装语义。另一方面,反射技术可能减低代码可读性,使得代码难以维护。

注解

注解本质上是一个接口,它需要被第三方识别并使用包括编译器、框架、甚至是注解本身
注解的作用:生成文档、被框架反射读取(如依赖注入)、编译时格式检查如override。

元注解,是被注解识别的注解
【1】@Target用来约束注解的修饰位置。使用枚举类型ElementType,取值包括type、field、method、parameter等
【2】@Retention用于约束注解的声明周期,使用枚举类型RetentionPolicy,取值包括runtime、class、source。只有runtime会保留到运行期,被反射技术读取到,而class只会被保留在class文件中,运行时被擦除,通常用于编译器检查如@Override、@Deprecated和@SuppressWarnning
【3】@Document用于生成文档。
【4】@Inherited允许子类继承父类的注解

注解支持的元素包括8大基本类型以及对应数组类型、枚举、注解、Class、字符串。但是不支持包装类型以及其他引用类型。

注解通过和反射技术配合使用,以@value为例,用户为 name字段 修饰@value(“mike”),spring框架注入值的时候首先拿到字段集合fields,遍历每一个field,如果存在@value,那么就可以拿到这个值。此时根据field还可以拿到字段名、字段的类型type。最终spring读取value,并解析为type类型,反射注入到bean对象的name字段中。(反射的性能,一定程度上依赖类型的规模)

原理

注解底层使用到了动态代理技术
因为注解本质上是一个接口(继承自java.lang.annotio的子接口),但是我们可以通过Class对象的getAnnotation方法获得一个annotation的实例(这就是一个代理对象,调用自定义方法的时候,最终调用的是invocationHandler的invoke方法)。

该方法会从memberValue这个string-object的map中取值(k是注解属性名称,v是具体的赋值),所有生命周期在runtime的注解都会加载进这个map,并且通过引用传递给invocationHandler实例。而invoke的执行核心就是——通过方法名返回注解的属性值

进行反射获取注解(调用getAnnotations()方法)的时候,JVM会将所有生命周期在RUNTIME的注解取出来,放入一个map中,并创建一个AnnotationInvocationHandler实例,并将注解map传递给它,最后JVM 通过动态代理的方式生成目标注解的代理类,并初始化好处理器

my annotation = review.class.getAnnotation(my.class);

my是自定义注解的代理实例,有一个AnotationInvocationHandler类型的成员h,h的成员memberValues是一个map,key是注解中的方法名(注解的属性名),value是属性的值。value或是缺省值或者用户传入的值

注解@my的实例annotation是一个代理对象的实例,这个代理对象的类型是$Proxy1 extends Proxy implements my ,并且持有AnotationInvocationHandler类型的引用。

class AnnotationInvocationHandler implements InvocationHandler, Serializable 

一个注解实例本质上是一个代理类实例,通过方法名返回注解的属性。(使用这个代理类的newProxyInstance方法的时候,传入接口和AnotationInvocationHandler,最终返回一个代理实例)

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-10-19 11:44:20  更:2021-10-19 11:44:23 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 22:04:19-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码