概念
Java几种异常,Error和Exception的区别
在Java中,异常分为三种主要类型:受检查异常(Checked Exceptions)、运行时异常(Unchecked Exceptions)和错误(Errors)。
受检查异常(Checked Exceptions)
- Checked 异常是在编译时强制要求程序员处理的异常。在代码中必须显式地捕获或者声明方法可能抛出的 Checked 异常,否则编译器将报错。
- Checked 异常通常是程序员能够预见并且可以通过合适的处理来避免的异常情况。
- 常见的有:IOException:输入输出操作异常,例如文件找不到、文件无法读写等。 SQLException:数据库访问异常,例如连接数据库失败、SQL语句错误等。 ClassNotFoundException:未找到类异常,例如使用 Class.forName() 加载类时找不到指定的类。
运行时异常(Unchecked Exceptions)
- Unchecked 异常是在运行时才会被抛出的异常,编译器不要求对其进行处理。运行时异常继承自RuntimeException类及其子类
- Unchecked 异常通常是由于编程错误或者环境异常导致的,例如空指针异常、数组下标越界异常等。
- 常见的有:NullPointerException:空指针异常,当应用程序试图在 null 对象上调用方法或访问属性时抛出。
ArrayIndexOutOfBoundsException:数组下标越界异常,当试图访问数组中不存在的索引时抛出。IllegalArgumentException:非法参数异常,当方法接收到非法参数时抛出。 IllegalStateException:非法状态异常,当对象的状态不适合调用方法时抛出,例如在不允许调用的时间调用了某个方法。
错误(Errors)
- Error 是指在程序运行过程中无法恢复的严重问题,通常是系统级的问题或者虚拟机无法处理的问题,例如内存溢出错误等。
- 与异常不同,程序通常无法通过捕获 Error 来进行恢复,因此一般情况下不需要在代码中对 Error 进行处理。
- 常见的有:OutOfMemoryError:堆内存溢出错误,当 Java 虚拟机无法分配更多的内存空间时抛出。
StackOverflowError:栈溢出错误,当递归调用或者方法调用层级过深导致栈空间耗尽时抛出。
异常处理方式有哪些;throw和throws区别;什么时候不用throws
Java异常处理方式有哪些?
throw和throws区别
- try-catch语句块:用于捕获并处理可能抛出的异常。try块中包含可能抛出异常的代码,catch块用于捕获并处理特定类型的异常。可以有多个catch块来处理不同类型的异常。
- throw语句:用于手动抛出异常。可以根据需要在代码中使用throw语句主动抛出特定类型的异常。
- throws关键字:用于在方法声明中声明可能抛出的异常类型。如果一个方法可能抛出异常,但不想在方法内部进行处理,可以使用throws关键字将异常传递给调用者来处理。
- finally块:用于定义无论是否发生异常都会执行的代码块。通常用于释放资源,确保资源的正确关闭。
抛出异常什么时候不用throws?
- 如果异常是未检查异常或者在方法内部被捕获和处理了,那么就不需要使用throws。Unchecked Exceptions:未检查异常(unchecked exceptions)是继承自RuntimeException类或Error类的异常,编译器不强制要求进行异常处理。因此,对于这些异常,不需要在方法签名中使用throws来声明。示例包括NullPointerException、ArrayIndexOutOfBoundsException等。
- 另一种常见情况是,在方法内部捕获了可能抛出的异常,并在方法内部处理它们,而不是通过throws子句将它们传递到调用者。这种情况下,方法可以处理异常而无需在方法签名中使用throws。
Java特点,为什么跨平台,为什么解释和编译都有
Java的特点
主要有以下的特点:
- 台无关性:Java的“编写一次,运行无处不在”哲学是其最大的特点之一。Java编译器将源代码编译成字节码(bytecode),该字节码可以在任何安装了Java虚拟机(JVM)的系统上运行。
- 面向对象:Java是一门严格的面向对象编程语言,几乎一切都是对象。面向对象编程特性使得代码更易于维护和重用,包括类(class)、对象(object)、继承(inheritance)、多态(polymorphism)、抽象(abstraction)和封装(encapsulation)。
- 内存管理:Java有自己的垃圾回收机制,自动管理内存和回收不再使用的对象。这样,开发者不需要手动管理内存,从而减少内存泄漏和其他内存相关的问题。
Java为什么是跨平台的?
Java 能支持跨平台,主要依赖于 JVM 关系比较大。
JVM也是一个软件,不同的平台有不同的版本。我们编写的Java代码,编译后会生成一种 .class 文件,称为字节码文件。Java虚拟机就是负责将字节码文件翻译成特定平台下的机器码然后运行。也就是说,只要在不同平台上安装对应的JVM,就可以运行字节码文件,运行我们编写的Java程序。
为什么Java解释和编译都有?
首先我们需要了解Java 程序从源代码到运行的过程,Java 程序从源代码到运行一般有下面 3 步:
- 代码书写完成之后,保存成Java文件。
- Java文件运行时,Java不是直接编译或解释成机器码文件的,它会先通过JDK中的javac.exe编译成Java虚拟机(JVM )可理解的字节码文件,此时,Java使用了编译。
- 字节码文件之后会被虚拟机JVM逐行解释成机器可执行的二进制机器码,此时,Java使用了解释。
- 从.class文件到机器码这一步, JVM 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的(也就是所谓的热点代码),所以后面(JDK1.2)引进了 JIT 编译器与JVM协同工作,而 JIT 属于运行时编译。
当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言。
JVM 的运作可以分为两个阶段:
1.解释器(Interpreter):逐行解释字节码,直接将其转换为机器码并执行,这种方式的好处是启动快,但性能较低,尤其是当某些代码块反复执行时。
2.JIT 编译器:当 JVM 运行时检测到某些“热点”代码(即频繁执行的代码片段,如循环体或多次调用的方法)时,JIT 编译器会将这些代码编译成原生机器码,并进行优化。下一次这些代码块被执行时,JVM 就可以直接使用编译后的机器码,而无需再次解释执行,这大大提高了执行效率。
编译型语言和解释型语言的区别
编译型语言和解释型语言的区别在于:
- 编译型语言:在程序执行之前,整个源代码会被编译成机器码或者字节码,生成可执行文件。执行时直接运行编译后的代码,速度快,但跨平台性较差。
- 解释型语言:在程序执行时,逐行解释执行源代码,不生成独立的可执行文件。通常由解释器动态解释并执行代码,跨平台性好,但执行速度相对较慢。
- 典型的编译型语言如C、C++,典型的解释型语言如Python、JavaScript。
语法
JDK和CGLIB动态代理
1.介绍一下JDK动态代理
JDK动态代理是Java提供的一种在运行时创建代理对象的机制。它允许我们为一组接口动态地生成代理类,从而在不修改原始代码的情况下为方法添加自定义的逻辑,如日志记录、安全检查等。
关键点:
- 核心类: java.lang.reflect.Proxy 用于创建代理对象,java.lang.reflect.InvocationHandler 用于定义方法调用的处理程序。
- 实现方式: 代理类在运行时实现指定的接口,所有方法调用都会被转发到 InvocationHandler 的 invoke 方法中进行处理。
- 使用步骤:
1.定义一个或多个接口;
2.创建实现了 InvocationHandler 接口的处理器,重写 invoke 方法;
3.使用 Proxy.newProxyInstance 方法创建代理对象。 - 限制:只能代理实现了接口的类,无法代理没有接口的类。
2.介绍一下CGLIB动态代理
CGLIB(Code Generation Library)动态代理是基于底层字节码操作的代理机制。它通过在运行时生成目标类的子类,并重写其中的方法,实现方法调用的拦截和增强。
关键点:
- 核心类: net.sf.cglib.proxy.Enhancer 用于创建代理对象,net.sf.cglib.proxy.MethodInterceptor 用于方法拦截。
- 实现方式: 代理类继承自目标类,重写非 final 的方法,在方法内部调用 MethodInterceptor 的 intercept 方法进行处理。
- 使用步骤:
1.引入CGLIB库;
2.定义一个实现了 MethodInterceptor 接口的拦截器;
3.使用 Enhancer 类设置父类和回调,创建代理对象。 - 优势:可以代理没有实现接口的类。
- 限制:不能代理被 final 修饰的类和方法,因为无法继承和重写。
3.对比一下JDK动态代理和CGLIB动态代理
对比维度 | JDK动态代理 | CGLIB动态代理 |
---|---|---|
代理对象类型 | 实现了指定接口的代理类 | 目标类的子类 |
实现机制 | 基于Java反射机制,代理类在运行时实现接口 | 基于字节码生成库,代理类在运行时继承目标类 |
是否需要接口 | 是,目标类必须实现接口 | 否,可以代理没有实现接口的类 |
代理范围 | 接口中声明的方法 | 目标类中的所有非 final 方法 |
性能 | 方法调用速度较快,创建代理对象速度较慢 | 创建代理对象速度较慢,方法调用速度较快 |
限制 | 不能代理未实现接口的类 | 不能代理 final 类和 final 方法 |
使用场景 | 目标对象有接口,且需要对接口方法进行代理 | 目标对象没有接口,或需要代理类中的所有方法 |
总结:
- 选择依据: 当目标类有接口时,优先使用JDK动态代理;当目标类没有接口时,使用CGLIB动态代理。
- 性能考虑: JDK动态代理在方法调用时由于使用了反射,在大量创建代理对象的场景,不涉及复杂的字节码生成和修改,JDK动态代理由于创建速度快,可能性能更优;CGLIB是直接通过字节码操作生成目标类的子类,方法调用通过继承目标类并重写其方法进行处理,不涉及反射,因此创建代理对象的过程较为复杂,速度较慢,所以在高频次方法调用的场景,CGLIB动态代理由于方法调用速度更快,可能会有更好的性能表现。日常开发中两者的性能差异在大部分应用中不会成为瓶颈,选择合适的代理技术更多依赖于是否必须实现接口等。
- 注意事项: CGLIB代理无法代理 final 类和方法,使用时需要避免这种情况。
反射概念;特性;应用场景
详细介绍->Java高级: 反射
反射概念 反射是Java语言中一种动态获取类信息的机制,允许开发者在运行时探索类的结构,包括类的属性、方法、构造函数等。通过反射,程序可以创建对象、调用方法、访问和修改字段,即便是私有的。
反射的特性
- 运行时类信息访问:反射可以获取类的完整信息,例如类名、包名、父类、接口、字段、方法和构造函数等。这使得开发者可以在不知道类的具体实现的情况下,仍然可以操作这个类。
Class类:在Java中,每个类在运行时都有一个对应的Class对象。可以通过Class.forName(“类名”)来获取该对象。
获取信息的方法:getName():获取类的完全限定名。getSuperclass():获取父类的Class对象。getInterfaces():获取实现的接口。getDeclaredMethods():获取该类声明的所有方法。getDeclaredFields():获取该类声明的所有字段。
- 动态对象创建:反射允许在运行时创建对象。例如,使用Class.forName()方法获取类的Class对象,并利用newInstance()方法动态创建实例。
使用Class对象的newInstance()方法或Constructor类来创建对象实例。
常用API:Class.getConstructor():获取公共构造函数。Class.getDeclaredConstructor():获取所有声明的构造函数,包括私有构造函数。Constructor.newInstance():创建对象的实例。
- 动态方法调用:通过Method类,开发者可以在运行时调用对象的方法。即使是私有方法,也可以使用setAccessible(true)绕过访问控制。
常用API:getMethod(String name, Class<?>... parameterTypes):获取公共方法。getDeclaredMethod(String name, Class<?>… parameterTypes):获取所有声明的方法,包括私有方法。Method.invoke(Object obj, Object… args):调用指定对象的指定方法。
- 访问和修改字段值:反射可以获取和修改对象的字段,包括私有字段。通过Field类的get()和set()方法,可以实现对字段的动态访问和修改。
常用API:getField(String name):获取公共字段。getDeclaredField(String name):获取所有声明的字段,包括私有字段。Field.get(Object obj):获取指定对象的字段值。Field.set(Object obj, Object value):设置指定对象的字段值。
应用场景
动态加载服务实现:在企业级应用中,可能需要根据不同的配置动态加载服务实现类。例如,考虑一个支付系统,根据用户选择的支付方式(如信用卡、PayPal等),通过反射根据配置文件中的类名动态实例化相应的支付处理类。这种方式使得系统扩展性强,方便在未来添加新的支付方式。
Spring框架的IoC:Spring框架利用反射实现控制反转(IoC)。通过XML或注解配置文件,Spring可以动态加载和配置Bean。启动时,Spring容器会解析配置文件,获取Bean的类名及其依赖关系,然后通过反射创建这些Bean的实例,并自动注入所需的依赖。这使得应用程序的组件解耦,提高了灵活性和可维护性。
创建对象的方式;什么时候被回收
Java创建对象有哪些方式?
常见的包括
- 使用new关键字。通过new关键字直接调用类的构造方法来创建对象。
- 通过反射创建对象。通过 Java 的反射机制可以在运行时动态地创建对象。可以使用 Class 类的 newInstance() 方法或者通过 Constructor 类来创建对象。
- 通过反序列化创建对象。通过将对象序列化(保存到文件或网络传输)然后再反序列化(从文件或网络传输中读取对象)的方式来创建对象,对象能被序列化和反序列化的前提是类实现Serializable接口。
- 通过clone创建对象。所有 Java 对象都继承自 Object 类,Object 类中有一个 clone() 方法,可以用来创建对象的副本,要使用 clone 方法,我们必须先实现 Cloneable 接口并实现其定义的 clone 方法
实现序列化和反序列化为什么要实现Serializable接口?
在java中,实现Serializable接口是为了支持对象的序列化和反序列化操作,Serializable接口是java提供的一个标记接口,其实他没有定义任何方法,只是起到一个标记作用。当一个类实现了Serializable接口的时候表明这个类的对象可以被序列化成字节流,或者从字节流反序列化成对象。
在我看来之所以要对序列化实现Serializable这个接口,原因是:
1.可以确保只有那些被设计为可以序列化的对象才能被序列化,这是一种类型安全的保障,防止对不可序列化对象进行序列化操作;
2.它规范了类的行为,表示该类的对象可以被序列化,判断是否实现了Serializable接口,确保对象的序列化操作是合法的
New出的对象什么时候回收?
通过过关键字new创建的对象,由Java的垃