Java基础(三)

1. 反射

附上之前的博客

1.1 反射概述

什么是反射?

  • 程序运行时,对于任意一个类,动态地获取其所有属性和方法;对于任意一个对象,动态地访问其所有属性或方法
  • 这种动态获取信息、动态调用方法的机制,在Java中被叫做反射

为什么是反射?(自己的理解)

  • 反射的两大支柱之一,Class类,实例化后的Class对象对应一个真实的Java类,包含了该类的属性和方法信息。
  • 编译Java类生成.class文件,保存的是该类对应的class对象
  • class对象就是一个类/对象照镜子的结果,通过照镜子,可以知道有哪些属性、方法,实现了哪些接口等
  • 可以说,反射就是利用Class对象和反射机制,动态获取信息、动态调用方法,就像是通过照镜子去了解类/对象,从而让其执行某些动作

反射的作用

  1. 通过反射,可以动态判断一个对象所属的类:obj.getClass()
  2. 通过反射,可以动态实例化对象:获取class对象,或类的contructor对象 —> 通过newInstance()实例化对象
  3. 通过反射,可以动态获取类的信息,以支持一些扩展功能,如类浏览器:获取class对象 —> 然后获取其属性、方法、构造函数
  4. 通过反射,可以动态调用方法、访问或修改对象属性:获取class对象 —> 实例化对象 —> 获取对应的method对象、field对象 —> 通过invoke动态调用对象的方法,通过get/set动态访问或修改属性值

反射的优点

  1. 灵活性: 通过反射,可以动态创建对象并编译,最大限度地发挥了Java的灵活性
    例如,J2EE开发中,新版本的发布不要求用户卸载旧版本。而是通过反射机制,运行时动态创建对象并编译代码
  2. 可扩展性: 通过Class.forName()获取外部的用户自定义类的class对象,然后通过newInstance()实例化对象 —— (不是很理解) 😂

反射的缺点

  1. 开销大: 反射涉及动态类型的解析,JVM无法对代码进行优化,反射操作的效率要比非反射操作的效率低得多
  2. 安全限制: 反射机制只能在无安全限制的环境中使用,在像Applet有安全限制的环境中,无法使用 —— (不是很理解) 😂
  3. 内部信息暴露: 通过反射可以访问私有成员,使得原本的访问控制权限成为摆设,对象的内部信息暴露

反射的应用

  1. 类浏览器和IDE: 类浏览器和IDE需要展示一个类的所有属性和方法,通过反射可以做到
  2. 调试器: 调试器需要检查一个类的私有成员 —— (不是很理解) 😂
  3. 测试工具: 测试工具需要通过反射调用类中的API,以保证测试的覆盖率
  4. IOC机制: spring 的IOC底层使用反射技术,构建用户所需的bean实例
  5. JDBC驱动的加载: 通过类名限定,如Class.forName('com.mysql.jdbc.Driver'),指定对应的JDBC驱动,从而创建对应的驱动对象

1.2 反射的使用

  • 先介绍反射的两大支柱:
    (1)Class类:一个真正的Java类在JVM中的映射,是反射的基础,只有获取类的class对象,才能执行后续的反射操作
    (2)java.lang.reflect包:三个重要类,Field对应成员变量,Method对应成员方法,Constructor对应构造方法。
    (3)通过class对象获取上述的三种对象,才能实现访问对象属性、调用方法、实例化对象等

  • 反射的使用,在博客Java反射整理-2021.01.02中,有具体的总结

class对象

  1. class对象的获取
    (1)通过对象获取:obj.getClass()
    (2)通过类名获取: ClassName.class
    (3) 通过完全限定名获取:Class.forName(“package.path.class_name”),如Class.forName('com.mysql.jdbc.Driver')
  2. 动态实例化对象:class.newInstance()实例化对象,只能调用无参构造函数

Field

  1. getFields()获取所有的公有属性,getDeclaredFields()获取所有的属性
  2. getField(String name)获取指定name的公有属性,getDeclaredField(String name)获取指定name的属性
  3. 获取Field的基本信息:filed对象的getModifiers()获取属性的权限修饰符,getName()获取属性名等
  4. 访问或修改属性值:
    (1)属性为基本数据类型,使用filed对象的getXXX(Object obj)获取对象中该属性的值;
    (2)属性为基本数据类型,使用filed对象的setXXX(Object obj, T value)更新对象中该属性的值;
    (3)属性为引用数据类型,使用filed对象的get(Object obj)获取对象中该属性的值,T与基本数据类型一致
    (4)属性为引用数据类型,使用filed对象的set(Object obj, Object value value)更新对象中该属性的值;

Method

  1. getMethods()获取所有的公有成员方法,getDeclaredMethods()获取所有的成员方法
  2. getMethod(String name, Class<?>... parameterTypes)获取指定方法名和参数类型的公有成员方法,getDeclaredMethod(String name, Class<?>... parameterTypes)获取指定方法名和参数类型的成员方法
  3. 获取Method的基本信息:使用method对象的getName()获取方法名,getReturnType()获取返回值类型等
  4. method的调用:通过method对象的invoke(Object obj, Object... args),调用对象obj中的该方法,同时由args传入方法所需的参数

Constructor

  1. getConstructors()获取所有的公有构造函数,getDeclaredConstructors()获取所有的构造函数
  2. getConstructor(Class<?>... parameterTypes)获取对应的公有构造函数,getDeclaredConstructor(Class<?>... parameterTypes)获取对应的构造函数
  3. 获取Constructor的基本信息:具体查看源码,不赘述
  4. 实例化对象:实际都是调用Constructor对象的newInstance(Object ... initargs)方法,只是无参构造函数可以不用传入任何参数,看起来像是还有一个newInstance()方法

注意: Field、Method、Constructor等,若是private修饰,需要通过setAccessible(true)去关闭权限控制,使其可以被访问。

1.3 通过反射修改String

  • 通过反射修改String对象的值,实际是修改String对象中value属性的值
  • 它是一个private属性,需要设置setAccessible(true)
  • 然后通过set()方法,完成对象value属性的更新

2. Java的异常

2.1 java异常体系

  • 如图所示,Throwable是Java所有可抛出异常的父类,分为Error和Exception
  • 工作中所说的异常,一般指Exception
    在这里插入图片描述

Error

  • JVM运行时产生并抛出的异常,是程序无法控制和处理的,会使得JVM处于不正常、不可恢复的状态
  • JVM出现Error时,一般都会退出运行(终止线程的运行)

Exception

  • 表示程序运行时不期望发生的异常情况,是可以被程序处理的异常
  • Exception分为运行时异常(RuntimeException)和非运行时异常:
    • 运行时异常通过throw抛出时,无需通过throws显式声明
    • 非运行时异常也是检查型(checked)异常,必须通过try-catch或者throw/throws语句显式处理

检查型异常和非检查型异常

  • 针对javac是否强制要求显式处理异常而言
  • 非检查型异常:RuntimeException和Error,javac在编译时不会强制要求对其进行处理
    • 一般是代码逻辑问题,处理不是最好的方法,而是需要修复代码
  • 检查型异常:除了RuntimeException的其他Exception,javac在编译时会强制要求对其进行处理
    • 一般与程序的运行环境有关,需要程序时刻为这些可以预见的异常准备着(提前想好处理办法)

Error和Exception的区别与联系

  • 二者都是Throwable的子类,只有Throwble类型的实例才可以被throw或catch(可以抛出并捕获的异常)
  • 是Java平台对异常类型的两种分类:
    • Error是非正常情况,JVM出现的错误,不便于、也不需要catch;
    • Exception是程序运行中可能出现的异常情况,需要被处理或通过修复程序bug消除异常

2.2 异常的处理

异常处理的关键字

  1. try:包裹一段可能抛出异常的代码,如果代码运行时抛出异常,则可以被检测到
  2. catch:与try一起使用,用于捕获try语句块中抛出的、指定类型的异常,然后对异常进行处理
  3. finally:无论是否发生异常,最终必须执行的代码块,可以用于资源回收
  4. throw:用于在代码中抛出一个异常,是一个动词
  5. throws:用在方法签名中,以声明该方法可能抛出的异常
  • 注意:try语句不能单独使用,必须与catch或finally一起使用;可以只使用catch或finally,也可以二者一起使用

异常的处理

  • 使用try-catch-finally语句块,对异常进行处理
  • 在出现/捕获异常时不处理,而是通过throw/throws语句抛出,交由上层处理

用户自定义异常

  • 继承Throwable或Exception类,自定义的异常最好以Exception为后缀,表示这是一个异常类
  • 自定义检查型异常,继承Exception类;自定义运行时异常,继承RuntimeException 类

异常信息

  • 获取异常信息的方法:getMessage()getLocalizedMessage()toString()
  • 打印异常堆栈信息:printStackTrace()通过标准输出打印异常堆栈信息,正式开发中,一般使用log.error()log.warn()打印异常堆栈信息
  • 个人经验: 针对NullPointer这种没有异常信息的异常,最好使用toString()获取异常信息

2.3 常见面试问题

throw和throws

  • 使用位置: throw位于方法体内, throws位于方法签名中(函数名后、方法提前)
  • 意义: throw强调动作,表示抛出异常;throws是一种倾向,表示可能(实际不一定)抛出异常
  • 作用对象: throw作用于一个异常对象,表示抛出某个异常实例;throws作用于一个或多个异常类,表示抛出的异常类型

throw和throws的一些使用注意事项

  • 如果一个方法中通过throw抛出了异常,应该使用throws在方法上声明抛出的异常类型
    • 个人经验:检查型异常必须声明,非检查型异常不要求
  • 如果一个方法通过throws声明了抛出的异常,应该通过try-catch进行异常处理
  • 通过throw抛出异常,throw之后的代码将不再执行
    • 注意: finally语句中的代码仍会执行

finally、final、finalize

  • finally是异常处理中的关键字,表示不管是否发生异常,最终一定会执行的代码
  • final用于声明变量、方法或类,表示变量不可变、方法不可被重写、类不可被继承
  • finalize:Object类中的方法,JVM垃圾回收时被调用

包含finally的代码执行逻辑

  • 如下代码,不管是否发生异常,返回结果均为1

    String s = "hello";
    try {
        File file = new File("test.txt");
        return 0;
    } catch(Exception e) {
        e.printStackTrace();
    } finally {
        return 1;
    }
    
  • finally中的代码块,会在方法返回前执行,即使之前的代码已经有了返回语句,终将被finally中的返回语句覆盖;

  • 例外: 若是通过System.ext(0)退出代码执行,则finally中的代码不会被执行

JDK7异常新特性

  1. multi-catch特性: 允许在一个catch语句中,声明捕获多种类型的异常(异常类型应该不同,且不能存在异常的父类型)

    catch(IOException | SQLException | NullPointerException ex){
    	logger.error(ex);
    	throw new MyException(ex.getMessage());
    }
    
  2. ARM特性: 自动化资源管理,又称try-with-resource。可以在try-catch语句执行完后,自动回收try中创建资源,避免通过finally回收资源时的遗漏

    try (MyResource mr = new MyResource()) {
    	System.out.println("MyResource created in try-with-resources");
    } catch (Exception e) {
    	e.printStackTrace();
    }
    
  3. ARM,要求资源必须实现了java.lang.AutoCloseable或其子类java.io.Closeable接口

异常调用链

  • 面试中尚未遇到
  • 通过e2.initCase(e1)设置异常原因,或new Exception("exception a", e1)将上一个异常e1包裹到新的异常e2中

异常处理心得

  • 异常处理要优雅: 既要避免给用户带来不友好的体验,又要能帮助程序员定位异常位置与原因 —— 自己也在苦苦探索中
  • 资源回收: 要么使用ARM,要么使用finally,避免资源泄漏
  • 使用特定异常: 不要使用异常的父类,而是使用异常本身,可以有效地帮助定位问题
  • 程序要尽早抛出异常,交由上层调用者进行处理
  • 运行时异常:避免出现运行时异常,例如空指针异常,使用前需要检查对象是否为空

3. 其他

3.1 Java与C++的区别

  1. 面向对象:Java时纯粹的面向对象语言,所有类都继承 java.lang.Object;C++为了兼容C语言,既支持面向对象,又支持面向过程
  2. 跨平台:Java通过JVM实现跨平台特性,C++依赖于特性的平台
  3. 继承:Java不支持多重继承,需要通过实现多个接口达到相同目的;C++支持多重继承
  4. 指针:Java没有指针,其引用可以理解为安全的指针;C++具有和C语言一样的指针
  5. 操作符重载:Java不支持操作符重载,string对象的相加操作是语言内置操作,而非操作符重载;C++支持操作符重载
  6. 垃圾回收:Java支持自动垃圾回收;C++中,需要手动回收
  7. goto关键字:goto是Java的保留字,但不能使用goto;C++中,可以使用goto实现代码跳转

  • 这样的问题不会单独提问,可能会结合某些场景提问
  • 即使单独提问,能回答几个就行,不要求都能回答
  • 所以,结合具体场景,灵活记忆即可

3.2 JRE和JDK

JRE

  • Java Runtime Environment:Java运行环境,为Java代码的运行提供所需环境
  • JRE是一个JVM程序,包含了JVM的标准实现和Java的基本类库

JDK

  • Java Development Kit:Java开发工具包,提供了Java开发以及运行环境
  • JDK是Java的核心,集成了JRE及一些其他的工具,如Java代码编译工具javac
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值