Java 中 final 作用是什么?
final 关键字主要有以下三个方面的作用:用于修饰类、方法和变量。
- 修饰类:当 final 修饰一个类时,表示这个类不能被继承,是类继承体系中的最终形态。例如,Java中的 String 类就是用 final 修饰的,这保证了 String 类的不可变性和安全性,防止其他类通过继承来改变 String 类的行为和特性。
- 修饰方法:用 final 修饰的方法不能在子类中被重写。比如,java.lang.Object 类中的 getClass方法就是 final 的,因为这个方法的行为是由 Java 虚拟机底层实现来保证的,不应该被子类修改。
- 修饰变量:当 final 修饰基本数据类型的变量时,该变量一旦被赋值就不能再改变。例如,finalint num = 10; ,这里的 num 就是一个常量,不能再对其进行重新赋值操作,否则会导致编译错误。对于引用数据类型,final 修饰意味着这个引用变量不能再指向其他对象,但对象本身的内容是可以改变的。
Java中static的作用是什么?
static关键字主要用于修饰类的成员和内部类,其核心作用是将成员与类本身关联,而非与类的实例关联。具体作用如下:
- 修饰变量:被static修饰的变量属于类本身,而非类的某个实例。所有对象共享同一份静态变量,内存中只存在一份副本。可以通过“类名.变量名”直接访问,无需创建对象(也可通过对象访问,但不推荐)。通常用于存储所有对象共享的数据,如常量、计数器等。
- 修饰方法:静态方法属于类,不属于任何实例,因此不能直接访问类中的非静态成员,但可以访问静态成员。通过“类名.方法名”直接调用,无需创建对象。通常用于工具类方法(如 Math.random())、工厂方法等,不需要依赖对象状态即可完成操作
- 修饰代码块:静态代码块在类加载时执行,且只执行一次(优于对象构造方法),用于初始化静态变量或执行类级别的预处理操作。多个静态代码块按定义顺序执行,且先于非静态代码块和构造方法。
- 修饰内部类静态内部类不依赖于外部类的实例,可以独立存在,不能直接访问外部类的非静态成员(需通过外部类实例访问)。当内部类与外部类的实例无关时使用,避免内部类持有外部类的引用导致的内存泄漏。
深拷贝和浅拷贝的区别?
- 浅拷贝:浅拷贝是指只复制对象本身和其内部的值类型字段,但不会复制对象内部的引用类型字段。
- 深拷贝:深拷贝是指在复制对象的同时,将对象内部的所有引用类型字段的内容也复制一份,而不是共享引用。
实现深拷贝的三种方法是什么?
- 实现 Cloneable 接口并重写 clone() 方法:这种方法要求对象及其所有引用类型字段都实现 Cloneable 接口,并且重写 clone () 方法。在 clone () 方法中,通过递归克隆引用类型字段来实现深拷贝。
- 使用序列化和反序列化:通过将对象序列化为字节流,再从字节流反序列化为对象来实现深拷贝。要求对象及其所有引用类型字段都实现 Serializable 接口。
- 手动递归复制:针对特定对象结构,手动递复制对象及其引用类型字段。适用于对象结构复杂度不高的情况。
什么是泛型?
泛型是Java编程语言中的一个重要特性,它允许类、接口和方法在定义时使用一个或多个类型参数,这些类型参数在使用时可以被指定为具体的类型。
泛型的主要目的是 在编译时提供更强的类型检查,从而减少运行时类型转换异常的风险
为什么需要泛型?
- 适用于多种数据类型执行相同的代码:如果没有泛型,要实现不同类型的加法,每种类型都需要重载一个add方法;通过泛型,我们可以复用为一个方法:
- 泛型中的类型在使用时指定,不需要强制类型转换:引入泛型,它将提供类型的约束,提供编译前的检查:
Java创建对象有哪些方式
| 方式 | 核心原理 | 调用构造器 | 特点与应用场景 |
|---|---|---|---|
| new 关键字 | JVM 指令 | 是 | 最标准、最常用,紧密耦合 |
| 反射 | 运行时类信息 | 是 | 灵活,解耦,用于框架 |
| clone() | 复制现有对象 | 否 | 基于原型创建副本,需实现 Cloneable |
| 反序列化 | 从字节流恢复 | 否 | 用于持久化和网络通信,需实现 Serializable |
| 工厂模式 | 方法封装 new | 是 | 解耦,隐藏创建逻辑,控制实例 |
New出的对象什么时候回收?
通过关键字new创建的对象,由Java的垃圾回收器负责回收。垃圾回收器的工作是在程序运行过程中自动进行的,它会周期性地检测不再被引用的对象,并将其回收释放内存。
具体来说,Java对象的回收时机是由垃圾回收器根据一些算法来决定的,主要有以下几种情况:
- 引用计数法:某个对象的引用计数为0时,表示该对象不再被引用,可以被回收。
- 可达性分析算法:从根对象(如方法区中的类静态属性、方法中的局部变量等)出发,通过对象之间的引用链进行遍历,如果存在一条引用链到达某个对象,则说明该对象是可达的,反之不可达,不可达的对象将被回收。
- 终结器(Finalizer):如果对象重写了finalize()方法,垃圾回收器会在回收该对象之前调用finalize()方法,对象可以在finalize()方法中进行一些清理操作。然而,终结器机制的使用不被推荐,因为它的执行时间是不确定的,可能会导致不可预测的性能问题。
如何获取私有对象?
在Java中,私有对象通常指的是类中被声明为private的成员变量或方法。由于private访问修饰符的限制,这些成员只能在其所在的类内部被访问。
不过,可以通过下面两种方式来间接获取私有对象。
- 使用公共访问器方法(getter方法)
- 反射机制。反射机制允许在运行时检查和修改类、方法、字段等信息,通过反射可以绕过private访问修饰符的限制来获取私有对象。
什么是反射?
Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java 语言的反射机制。
反射具有以下特性:
- 运行时类信息访问:反射机制允许程序在运行时获取类的完整结构信息,包括类名、包名、父类、实现的接口、构造函数、方法和字段等。
- 动态对象创建:可以使用反射API动态地创建对象实例,即使在编译时不知道具体的类名。这是通过Class类的newInstance()方法或Constructor对象的newInstance()方法实现的。
- 动态方法调用:可以在运行时动态地调用对象的方法,包括私有方法。这通过Method类的invoke()方法实现,允许你传入对象实例和参数值来执行方法。
- 访问和修改字段值:反射还允许程序在运行时访问和修改对象的字段值,即使是私有的。这是通过Field类的get()和set()方法完成的。
反射在代码或框架中的应用场景
- 加载数据库驱动:项目底层数据库可能切换(如mysql、oracle),需动态加载对应驱动类。通过反射的
Class.forName()方法,可根据实际情况传入不同数据库的驱动类全限定名,实现驱动的动态加载。 - 配置文件加载:以Spring框架的IOC为例,通过配置文件(XML/properties)动态管理Bean
讲一讲 Java 注解的原理
注解本质是一个继承了 Annotation 的特殊接口,其具体实现类是Java 运行时生成的动态代理类。我们通过反射获取注解时,返回的是 Java 运行时生成的动态代理对象。
通过代理对象调用自定义注解的方法,会最终调用 AnnotationInvocationHandler 的 invoke 方法。该方法会从 memberValues 这个 Map 中索引出对应的值。而 memberValues 的来源是 Java 常量池。
对注解解析的底层实现了解吗?
注解本质上是一种特殊的接口,它继承自 java.lang.annotation.Annotation 接口,所以注解也叫声明式接口,编译后,Java 编译器会将其转换为一个继承自 Annotation 的接口,并生成相应的字节码文件。
根据注解的作用范围,Java 注解可以分为以下几种类型:
- 源码级别注解:仅存在于源码中,编译后不会保留(@Retention(RetentionPolicy.SOURCE))。
- 类文件级别注解:保留在 .class 文件中,但运行时不可见(@Retention(RetentionPolicy.CLASS))。
- 运行时注解:保留在 .class 文件中,并且可以通过反射在运行时访问(@Retention(RetentionPolicy.RUNTIME))。
只有运行时注解可以通过反射机制进行解析。
当注解被标记为 RUNTIME 时,Java 编译器会在生成的 .class 文件中保存注解信息。这些信息存储在字节码的属性表(Attribute Table)中,具体包括以下内容:
- RuntimeVisibleAnnotations:存储运行时可见的注解信息。
- RuntimeInvisibleAnnotations:存储运行时不可见的注解信息。
- RuntimeVisibleParameterAnnotations 和 RuntimeInvisibleParameterAnnotations:存储方法参数上的注解信息。
注解的解析主要依赖于 Java 的反射机制。以下是解析注解的基本流程:
1、获取注册信息:通过反射 API 可以获取类、方法、字段等元素上的注解。例如:
1 | Class<?> clazz = MyClass.class; |
2、底层原理:反射机制的核心类是 java.lang.reflect.AnnotatedElement,它是所有可以被注解修饰的元素(如 Class、Method、Field 等)的父接口。该接口提供了以下方法:
getAnnotation(Class<T> annotationClass):获取指定类型的注解。getAnnotations():获取所有注解。isAnnotationPresent(Class<? extends Annotation> annotationClass):判断是否包含指定注解。
这些方法的底层实现依赖于 JVM 提供的本地方法(Native Method),例如:
native Annotation[] getDeclaredAnnotations0(boolean publicOnly);native <A extends Annotation> A getAnnotation(Class<A> annotationClass);
JVM 在加载类时会解析 .class 文件中的注解信息,并将其存储在内存中,供反射机制使用。
因此,注解解析的底层实现主要依赖于 Java 的反射机制和字节码文件的存储。通过 @Retention 元注解可以控制注解的保留策略,当使用 RetentionPolicy.RUNTIME 时,可以在运行时通过反射 API 来解析注解信息。在 JVM 层面,会从字节码文件中读取注解信息,并创建注解的代理对象来获取注解的属性值。
Java注解的作用域
注解的作用域(Scope)指的是注解可以应用在哪些程序元素上,例如类、方法、字段等。Java注解的作用域可以分为三种:
- 类级别作用域:用于描述类的注解,通常放置在类定义的上面,可以用来指定类的一些属性,如类的访问级别、继承关系、注释等。
- 方法级别作用域:用于描述方法的注解,通常放置在方法定义的上面,可以用来指定方法的一些属性,如方法的访问级别、返回值类型、异常类型、注释等。
- 字段级别作用域:用于描述字段的注解,通常放置在字段定义的上面,可以用来指定字段的一些属性,如字段的访问级别、默认值、注释等。
除了这三种作用域,Java还提供了其他一些注解作用域,例如构造函数作用域和局部变量作用域。这些注解作用域可以用来对构造函数和局部变量进行描述和注释。
介绍一下Java异常
Java的异常体系主要基于两大类:Throwable类及其子类。Throwable有两个重要的子类:Error和Exception,它们分别代表了不同类型的异常情况。
Error(错误):表示运行时环境的错误。错误是程序无法处理的严重问题,如系统崩溃、虚拟机错误、动态链接失败等。通常,程序不应该尝试捕获这类错误。例如,OutOfMemoryError、StackOverflowError等。
Exception(异常):表示程序本身可以处理的异常条件。异常分为两大类:
- 非运行时异常:这类异常在编译时期就必须被捕获或者声明抛出。它们通常是外部错误,如文件不存在(FileNotFoundException)、类未找到(ClassNotFoundException)等。非运行时异常强制程序员处理这些可能出现的问题,增强了程序的健壮性。
- 运行时异常:这类异常包括运行时异常(RuntimeException)和错误(Error)。运行时异常由程序错误导致,如空指针访问(NullPointerException)、数组越界(ArrayIndexOutOfBoundsException)等。运行时异常是不需要在编译时强制捕获或声明的。
Java异常处理有哪些?
异常处理是通过使用try-catch语句块来捕获和处理异常。以下是Java中常用的异常处理方式:
- try-catch语句块:用于捕获并处理可能抛出的异常。try块中包含可能抛出异常的代码,catch块用于捕获并处理特定类型的异常。可以有多个catch块来处理不同类型的异常。(可选的finally块,用于定义无论是否发生异常都会执行的代码)
- throw语句:用于手动抛出异常。可以根据需要在代码中使用throw语句主动抛出特定类型的异常。
- throws关键字:用于在方法声明中声明可能抛出的异常类型。如果一个方法可能抛出异常,但不想在方法内部进行处理,可以使用throws关键字将异常传递给调用者来处理。
抛出异常为什么不用throws?
如果异常是未检查异常或者在方法内部被捕获和处理了,那么就不需要使用throws。
- Unchecked Exceptions:未检查异常(unchecked exceptions)是继承自RuntimeException类或Error类的异常,编译器不强制要求进行异常处理。因此,对于这些异常,不需要在方法签名中使用throws来声明。示例包括NullPointerException、ArrayIndexOutOfBoundsException等。
- 捕获和处理异常:另一种常见情况是,在方法内部捕获了可能抛出的异常,并在方法内部处理它们,而不是通过throws子句将它们传递到调用者。这种情况下,方法可以处理异常而无需在方法签名中使用throws。
try catch中的语句运行情况
try块中的代码将按顺序执行,如果抛出异常,将在catch块中进行匹配和处理,然后程序将继续执行catch块之后的代码。如果没有匹配的catch块,异常将被传递给上一层调用的方法。
try{return “a”} finally{return “b”}这条语句返回啥
finally块中的return语句会覆盖try块中的return返回,因此,该语句将返回”b”。