1.Jvm类加载机制
JVM类加载机制主要指的是JVM如何把类的二进制数据加载到内存中,并对数据进行校验、转换解析和初始化,最终形成可以被JVM直接使用的Java类型。
类加载主要分为以下几个步骤:
加载:查找和导入类的二进制数据。
验证:确保导入的类信息符合JVM的要求。
准备:给类分配内存,并初始化静态变量。
解析:将符号引用转为直接引用。
初始化:调用类的构造方法<clinit>(),进行类的初始化。
2.Java异常分类及处理
在Java中,所有的异常类都是从java.lang.Throwable类派生的。Throwable有两个直接子类:Error和Exception。
Error:表示严重的错误,通常是虚拟机相关的问题,如VirtualMachineError(虚拟机错误),NoClassDefFoundError(类定义未找到错误)等,这些错误是不可查的,非捕获即不可控制的,因此应用程序不应该尝试捕获这类异常,通常这种异常发生时,Java虚拟机(JVM)会选择终止线程。
Exception:表示程序可以处理的异常,它又分为两类:CheckedException(受查异常)和UncheckedException(不受查异常)。
CheckedException:编译器要求必须处理的异常,如IOException,SQLException等。通常由程序的某些操作引发,如文件读写错误、网络通信问题等。
UncheckedException:也称为运行时异常(RuntimeException),通常是由程序逻辑错误引起的,如NullPointerException,ArrayIndexOutOfBoundsException等,这类异常可以通过更正代码来避免。
处理异常的一般步骤:
捕获异常:使用try块捕获可能发生的异常。
捕获异常后,使用catch块处理异常。
可选的,使用finally块释放资源或执行必须执行的代码。
3.Stream操作map和flatMap有什么区别?
Stream操作中的map和flatMap的主要区别在于它们对流中元素的处理方式以及返回结果的结构。
map操作:
功能:对集合中的每个元素应用一个函数,将每个元素映射(转换)为另一个元素,生成一个新的流,其中包含映射后的元素。
特点:结果流中的每个元素都是通过将原始元素应用给定的函数而获得的。例如,如果有一个包含字符串的列表,使用map操作可以将每个字符串转换为其长度,从而得到一个包含整数的流。
flatMap操作:
功能:将每个元素映射为一个流,然后将所有的流合并为一个流。这通常用于将多个元素的流合并为一个流,产生一个扁平化的结果流。
特点:flatMap方法用于将流中的每个元素转换为一个新的流,然后将这些新的流合并成一个流。例如,如果有一个包含字符串的列表,其中每个字符串代表一个数字的数组,使用flatMap可以将这些数组扁平化为一个单一的数字流。
简而言之,map操作将每个元素转换为一个单独的元素,而flatMap操作则可以将每个元素转换为一个流,并将这些流合并成一个单一的流。这种区别使得在处理需要扁平化多个流的场景时,flatMap显得更为合适
4.线程的状态有哪些?
- 初始(NEW):刚被创建,还没运行(未执行线程的start()方法)
- 就绪状态(READY):线程在可运行线程池中,但未获得CPU执行权,和RUNNING并称运行
- 运行中状态(RUNNING):线程执行并获得CPU执行权,和READY并称运行
- 阻塞(BLOCK):等待其他线程释放锁的状态
- 等待(WAITING):需要其他线程做出一些约定好的动作,或被唤醒(通知或中断)
- 超时等待(TIME_WAITING):和等待的不同点在于可以在指定的时间自行醒来
- 终止(TERMENATED):线程已经执行完毕
5.Comparator和Comparable分别在什么情况下使用?
Java中的Comparable和Comparator都是用于集合排序的接口,但它们有明显的区别。
Comparable接口:
Comparable是一个排序接口,它强制实现排序规则在类本身定义。如果一个类实现了Comparable接口,那么该类的对象就可以进行自然排序。自然排序即默认排序,如数字的升序排序、字符串的字典序排序等。
Comparable接口只有一个方法compareTo,实现Comparable接口并重写compareTo方法就可以实现某个类的排序了,它支持Collections.sort和Arrays.sort的排序。
如果所需要排序的类已经实现了Comparable接口,那么可以直接使用该类默认的比较规则进行排序。
Comparator接口:
Comparator是一个比较器接口,它允许开发者在不修改原有类的情况下实现排序功能。通过实现Comparator接口并重写compare方法,可以基于不同的比较规则对同一个类进行排序。
Comparator接口的使用更加灵活,因为可以根据需要编写任意数量的比较器,并将它们与同一个类的不同实例一起使用。这在需要对同一个类使用多个不同的比较规则进行排序时非常有用。
Comparator接口的用法更简单,它可以通过JDK 1.8中的lambda很轻松地实现排序功能。
总结:
Comparable更像是对内进行排序,因为它要求开发者在类内部实现排序逻辑。而Comparator则是对外提供排序的接口,它允许开发者在不修改原有类的情况下实现排序功能。
Comparable必须由自定义类内部实现排序方法,而Comparator是外部定义并实现排序的。
在选择使用哪个接口时,需要考虑多方面的因素,并且也要结合具体的需求和实际情况来进行选择。如果类已经实现了Comparable接口,则可以直接使用默认的比较规则进行排序;否则,可以考虑实现一个或多个Comparator接口,以提供更加灵活的排序选项。
6.如何捕获子线程中的异常?
使用Thread类的 setUncaughtExceptionHandler() 方法来设置一个异常处理器,用于捕获子线程中的异常。
class MyThread implements Runnable { public void run() { // 子线程执行的代码 int result = 10 / 0; // 会抛出一个ArithmeticException异常 } } public class Main { public static void main(String[] args) { Thread thread = new Thread(new MyThread()); thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { public void uncaughtException(Thread t, Throwable e) { // 捕获子线程中的异常并进行处理 System.out.println("捕获到子线程中的异常:" + e); } }); thread.start(); } }
使用线程池捕获子线程中的异常
在实际开发中,我们通常会使用线程池来管理和复用线程。在使用线程池时,我们也需要捕获子线程中的异常。Java提供了 ExecutorService 接口来创建和管理线程池,并且使用 submit() 方法来提交任务。
以下是一个使用线程池捕获子线程中异常的示例代码:
在上面的代码中,我们使用 Executors 类的 newFixedThreadPool() 方法创建了一个大小为1的线程池。然后,我们使用 submit() 方法提交了一个任务给线程池。在子线程中,我们故意抛出了一个 ArithmeticException 异常。在 main() 方法中,我们使用 shutdown() 方法来关闭线程池,并使用awaitTermination() 方法等待所有任务执行完毕。
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class MyTask implements Runnable { public void run() { // 子线程执行的代码 int result = 10 / 0; // 会抛出一个ArithmeticException异常 } } public class Main { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(1); // 创建一个大小为1的线程池 executor.submit(new MyTask()); executor.shutdown(); // 关闭线程池 try { executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); } catch (InterruptedException e) { e.printStackTrace(); } } }
7.Java为什么不能直接实现Iterator接口,而是实现Iterable?
Java中的Iterable接口是设计用来被迭代器协议的类实现的,它只有一个方法iterator(),这个方法返回一个Iterator对象。Iterator接口提供了迭代集合内元素的方法,如hasNext()、next()和remove()等。通过实现Iterable接口,一个类可以使其对象成为"foreach"循环的目标或使其内容可以被Collections框架支持的其他算法使用。如果直接实现Iterator接口,那么类还需要实现迭代器的具体内容,这会导致大量的重复代码,而实现Iterable接口只需要实现一个返回迭代器的方法即可。简而言之,通过实现Iterable接口,Java可以将迭代器的创建推迟到实际需要迭代时,这样可以更灵活地控制迭代过程,也为后续的增强for循环提供了基础。
8.数据库隔离级别有哪些?各自的含义是什么?Mysql InnoDB默认隔离级别是什么?
数据库的隔离级别主要包括四种,分别是READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ和SERIALIZABLE。
READ UNCOMMITTED(读未提交):在这个隔离级别下,所有事务都可以看到未提交事务的执行结果。这种隔离级别可能会导致“脏读”问题,即读取到其他事务未提交的数据。由于这种隔离级别存在很多问题,并且性能提升不明显,因此在实际应用中很少使用。
READ COMMITTED(读已提交):这是大多数数据库系统的默认隔离级别(但不是MySQL的默认隔离级别)。它满足了一个事务开始时只能看到已经提交事务所做的改变的条件。这种隔离级别支持所谓的“不可重复读”,即用户运行同一个语句两次,看到的结果可能是不同的。
REPEATABLE READ(可重复读):这是MySQL数据库的默认隔离级别。它保证了同一事务的多个实例在并发读取事务时,会看到同样的数据行。这种隔离级别通过InnoDB和Falcon存储引擎的多版本并发控制机制解决了幻读问题。
SERIALIZABLE(可串行化):这是最高级别的隔离级别。它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。在这个级别,每个读的数据行上都会加锁,可能导致大量的超时和锁竞争现象。由于可能导致大量的超时和锁竞争现象,实际应用中很少使用到这个级别,但如果应用需要强制减少并发以保证数据稳定性,也可以选择这种隔离级别。
综上所述,MySQL InnoDB的默认隔离级别是REPEATABLE READ,即可重复读
9.Redis有几种模式
Redis有四种模式:单机模式,主从模式,哨兵模式,集群模式。
10.介绍Visitor设计模式
访问者模式(Visitor Pattern)是GoF提出的 23种设计模式 中的一种,属于行为模式。GoF《Design Pattern》中的定义 :表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。
11.请描述JDK和JRE的区别 ?
JDK:全称Java Development Kit,翻译为Java开发工具包,提供Java的开发和运行环境,是整个Java的核心。目前各大主流公司都有自己的jdk,比如oracle jdk(注意,生产环境使用时需要注意法律风险)、openjdk(目前生产环境主流的jdk)、dragonwell(阿里家的jdk,在金融电商物流方面做了优化)、zulujdk(巨硬家的jdk)等等
JRE:全称Java Runtime Environment,Java运行时环境,为Java提供运行所需的环境总的来说,JDK包含JRE,JAVA源码的编译器javac,监控工具jconsole,分析工具jvisualvm
总结,如果你需要运行Java程序(类似我的世界那种),只需要安装JRE;如果你需要程序开发,那么需要安装JDK就行了,不需要再重复安装JRE。
12.简述什么是值传递和引用传递?
值传递:方法调用时,实际参数把它的值传递给对应的形式参数,方法执行中形式参数值的改变不影响实际参数的
值。
引用传递:也称为传地址。方法调用时,实际参数的引用(地址,而不是参数的值)被传递给方法中相对应的形式参
数,在方法执行中,对形式参数的操作实际上就是对实际参数的操作,方法执行中形式参数值的改变将会影响实际参
数的值。
注:Java是值传递。
13.简述什么是迭代器(Iterator )?
迭代器(Iterator)是一种设计模式,Java 中的迭代器是集合框架中的一个接口,它可以让程序员遍历集合中的元素而无需暴露集合的内部结构。使用迭代器可以遍历任何类型的集合,例如 List、Set 和 Map 等。
通过调用集合类的 iterator() 方法可以获取一个迭代器,并使用 hasNext() 方法判断是否还有下一个元素,如果有,则使用 next() 方法获取下一个元素。使用迭代器的好处在于遍历集合时不需要了解集合内部的结构,从而让代码更具可维护性和可重用性。
迭代器还具有一些额外的功能 , 比如支持 remove()方法来删除 迭 代 器 返 回 的 最 后 一 个 元 素 , 以 及 可 以 使用forEachRemaining() 方法来迭代集合中余下的所有元素等。
需要注意的是,一旦使用了迭代器进行遍历,就不能在遍历时修改集合中的元素,否则可能会导致不可预知的行为。如果需要修改集合中的元素,应该使用集合提供的遍历方式(如 for-each 循环)来进行遍历,或者使用列表迭代器(ListIterator)来对列表进行修改。
14.Iterator和ListIterator的区别是什么?
Iterator和ListIterator的主要区别在于它们的使用场景、遍历方向、集合修改能力以及索引获取能力。
使用场景:
Iterator可以遍历Collection中的元素,而ListIterator只能遍历List集合中的元素。这意味着ListIterator更加专注于对列表的操作,而Iterator则具有更广泛的适用性,能够遍历任何类型的集合。
遍历方向:
Iterator只能进行正向遍历,即从前往后遍历集合中的元素。而ListIterator提供了hasPrevious()和previous()方法,使得它可以双向遍历集合中的元素,包括正向和反向遍历。
集合修改能力:
ListIterator提供了add(E e)和set(E e)方法,允许在遍历时向List中添加新元素或修改当前元素。而Iterator仅支持遍历和删除操作。
索引获取能力:
ListIterator通过nextIndex()和previousIndex()方法,可以获取当前位置的索引。而Iterator没有这个功能。
总结:虽然Iterator和ListIterator都是迭代器,具有一些相似的功能,但是它们各自适用于不同的集合场景。如果需要双向遍历List集合,或者需要对List集合中的元素进行增删改等操作,应该使用ListIterator。否则,使用Iterator即可满足需求。
15.简述快速失败( fail - fast )和安全失败( fail -safe)的区别 ?
快速失败(fail-fast)和安全失败(fail-safe)的主要区别在于它们对集合修改的处理方式以及抛出异常的情况。
快速失败(fail-fast):在使用迭代器对集合对象进行遍历时,如果其他线程或当前线程在遍历过程中对集合进行修改(增加、删除、修改),会导致快速失败的迭代器抛出ConcurrentModificationException异常。这种机制要求在遍历过程中,集合的内容不应被其他线程或当前线程修改,否则会导致迭代器失效并抛出异常。Java中的java.util包下的所有集合类都是快速失败的。
安全失败(fail-safe):采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。这种方式确保了在遍历过程中,即使原始集合被其他线程修改,遍历操作也不会受到影响,不会抛出ConcurrentModificationException异常。因此,安全失败的迭代器永远不会因为集合的修改而抛出这样的异常。Java中的java.util.concurrent包下的所有类都是安全失败的,这些容器可以在多线程下并发使用,并发修改。
总结来说,快速失败和安全失败的主要区别在于它们对并发修改的处理方式:前者要求在遍历过程中集合内容不应被修改,否则会抛出异常;后者则通过复制集合内容的方式进行遍历,确保遍历过程不受原始集合修改的影响。
16.o(1)和o(n)的区别
O(1)和O(n)主要区别在于它们对输入数据规模的变化敏感性不同。
O(1)表示常数时间复杂度:这意味着无论输入数据的大小如何变化,算法的执行时间保持不变。例如,对已排序数组进行二分查找的操作就是一个典型的O(1)操作,因为无论数组大小如何,查找操作所需的时间都是固定的。
O(n)表示线性时间复杂度:在这种情况下,算法的执行时间与输入数据的个数成正比。例如,遍历一个包含n个元素的数组的操作就是O(n),因为无论数组大小如何,每个元素都需要被访问一次。
总结来说,O(1)是最快的时间复杂度,表示算法的执行时间不随问题规模的增加而增加;而O(n)则随着问题规模的增加而线性增加。这两种时间复杂度在算法设计和分析中非常重要,帮助我们理解算法在不同数据规模下的性能表现。
17.hashCode()和equals()方法的重要性体现在什么地方?
hashCode()和equals()方法的重要性体现在它们在Java集合框架中的核心作用,尤其是在HashMap这类数据结构中。
hashCode()方法:主要用于快速定位数据在集合中的存储位置。当需要查找或添加元素到HashMap时,首先通过元素的hashCode()方法计算出一个哈希码,这个哈希码会被用来确定元素在HashMap中大致的存储位置,即索引。通过这种方式,HashMap可以在O(1)时间复杂度内实现元素的快速查找和添加,大大提高了性能。如果两个不同的对象具有相同的hashCode值,这被称为哈希冲突,HashMap需要通过某种冲突解决策略(如链表或红黑树)来处理这种情况。
equals()方法:用于判断两个对象是否相等。在HashMap中,即使两个对象的hashCode值相同,如果它们的equals()方法返回false,那么这两个对象在HashMap中被视为不同的键。equals()方法确保了键的唯一性,即每个键在HashMap中只能映射到一个值。此外,equals()方法还用于发现和处理重复元素,即在添加元素到集合时,如果通过equals()方法判断该元素已经存在,则不会添加重复元素。
重要性:正确实现hashCode()和equals()方法对于保证HashMap的正确性和性能至关重要。如果这两个方法没有正确实现,可能会导致不同的对象具有相同的哈希码,从而被错误地认为是相等的,这将会破坏HashMap的键值对映射关系,导致数据检索错误或性能下降。因此,对于自定义对象作为HashMap的键时,必须确保正确覆盖这两个方法
18.finalize()方法什么时候被调用?析构函数( finalization)的目的是什么?
finalize()方法在垃圾回收器确定没有活跃的引用指向对象时被调用。
析构函数(finalization)的主要目的是在清除对象前,完成一些清理工作,比如释放内存等。
finalize方法属于Java中的Object根类,因此所有的类都继承了这个方法。这个方法的调用时机是在销毁(回收)该对象之前,用于回收资源,可以为任何一个类添加finalize方法。
具体来说,finalize方法的调用与垃圾回收器的行为紧密相关。当垃圾回收器发现某个对象没有与GC Roots相连接的引用链,即该对象被认为是“不可达”的,此时会进行第一次标记。如果对象覆盖了finalizer()方法且未被虚拟机调用过,那么这个对象会被放置在F-Queue队列中,并在稍后由一个虚拟机自动建立的低优先级的Finalizer线程区执行触发finalizer()方法。这个过程并不承诺等待其运行结束,因此finalize方法的执行具有一定的不确定性。
尽管finalize方法提供了一种在对象被回收前执行清理工作的机制,但它的使用并不被推荐。这是因为其运行代价高昂,具有较大的不确定性,且无法保证各个对象的调用顺序。因此,开发者通常会寻找其他替代方案,如使用try-finally语句等,来处理资源清理工作
19.Java中Exception和Error有什么区别?
Java中的Exception和Error主要区别在于它们的处理方式和严重程度。
Error:Error类定义了在通常环境下不希望被程序捕获的异常,表示系统错误比较严重,无法恢复的情况。Error类对象是由Java虚拟机生成并抛出的,例如内存不足、堆栈溢出等情况。这种异常一旦出现,意味着虚拟机内部出现严重问题,通常是致命性的错误,不是程序可以控制的。程序不应该捕获并尝试恢复。
Exception:Exception类定义了由应用程序抛出的异常,这些异常是可以被用户程序捕获的。Exception异常分为运行时异常(RuntimeException)和受查异常(Checked Exception)。运行时异常也叫非检查异常,继承自RuntimeException,这类问题大部分属于逻辑问题,如数组越界、空指针异常等,只有运行时才能知道的问题。编译时不会检查这些异常,程序员可以选择是否处理它们。受查异常(Checked Exception)则需要程序员显式处理,否则编译时会报错。这些异常在使用时经常要先处理异常(使用throws或try-catch捕获)。
总结来说,Error通常表示系统级错误,如内存不足或堆栈溢出,这些错误比较严重且通常不应该由应用程序来处理。而Exception则是程序应该考虑处理的异常情况,包括运行时异常和预期异常,这些异常可以通过try-catch块来捕获和处理
20.简述异常处理的时候,finally代码块的重要性是什么?
finally代码块在异常处理中的重要性主要体现在以下几个方面:
确保资源清理:finally代码块通常用于执行资源的清理工作,如关闭打开的文件、删除临时文件等。这是因为在try块中可能抛出异常,而无论是否发生异常,finally块中的代码都会被执行,从而确保了资源的正确释放。
提供统一的出口:finally语句为异常处理提供了一个统一的出口,使得在控制流程转到程序的其他部分以前,能够对程序的状态作统一的管理。这一点在处理异常时尤为重要,因为它确保了即使在异常情况下,也能对程序状态进行适当的清理和调整。
执行必须的功能:finally语句非常值得程序员关注,因为它可以用来执行必须实现的功能,例如数据库操作等。即使在try块中发生异常并被catch块捕获处理后,finally块中的代码仍然会执行,保证了某些关键操作的必然执行。
补充try-catch语句的功能:finally段的工作类似于收尾工作,即在给出异常发生后的一个处理办法后,执行必要的清理和恢复操作。它与try-catch语句的区别在于,finally段的代码无论是否发生异常都有执行,这是一种额外的安全保障机制。
综上所述,finally代码块在异常处理中的重要性不容忽视,它确保了即使在异常情况下,程序也能正确地清理资源、管理状态,并执行一些必须的功能操作。
21.Java异常处理完成以后,Exception对象会发生什么变化?
Java异常处理完成后,Exception对象失去了程序中的有效引用,变成垃圾,等待垃圾收集器在未来的某个时间点进行回收,释放其所占用的内存资源。
在Java中,异常处理机制涉及对象的生命周期和内存管理。当一个异常被抛出并被捕获处理后,与该异常相关的Exception对象在程序中的引用就会被清除或失效。这意味着该对象变得不再被任何活动线程所引用,因此它成为了垃圾收集器(Garbage Collector)的潜在目标。垃圾收集器会定期(或根据需要)运行,回收那些不再被使用的对象所占用的内存资源。对于实现了finalize()方法的对象,可能会经历终结阶段,但这不是推荐的做法,且不应依赖它进行关键资源的释放。总之,Exception对象的命运遵循Java的内存管理和垃圾回收机制,与其他不再被引用的对象相同。
22.Java finally代码块和finalize()方法有什么区别?
Java中的finally代码块和finalize()方法的主要区别在于它们的用途和执行时机。
finally代码块:它是Java异常处理机制的一部分,用于确保无论try块中的代码是否成功执行或抛出异常,finally块中的代码总是会被执行。这通常用于清理操作,如关闭数据库连接、释放资源等,以确保资源的正确管理。finally块是try-catch结构的一部分,用于处理异常情况下的资源清理。
finalize()方法:这是Object类中的一个方法,当垃圾收集器确定一个对象不再被引用时,会调用该对象的finalize()方法。这个方法的设计初衷是在对象被垃圾回收之前执行一些清理工作,如关闭文件、释放非内存资源等。然而,由于finalize()方法的执行时机不确定,且在现代JVM中,垃圾回收的优化可能导致finalize()方法的使用变得不那么可靠,因此不推荐在应用程序中使用finalize()方法进行资源管理。
总的来说,finally代码块是用于确保在异常处理过程中执行特定的清理操作,而finalize()方法是用于在对象被垃圾回收前执行一些清理工作,但由于其执行的不确定性,现代Java开发中通常避免使用finalize()方法进行资源管理。
23.简述System.gc()和Runtime.gc()的作用?
System.gc()和Runtime.gc()的作用是提示JVM进行垃圾回收,但具体是否立即进行垃圾回收取决于JVM的实现。
这两个方法都是用来向Java虚拟机(JVM)发出提示,希望JVM进行垃圾回收操作。然而,这两个方法并不能保证JVM会立即执行垃圾回收。System.gc()和Runtime.gc()在本质上是相似的,它们都是调用Runtime类中的gc()方法,而Runtime.gc()是一个native方法,这意味着它是用Java以外的语言(如C或C++)实现的。尽管这两个方法都提供了对JVM垃圾回收的提示,但最终是否执行垃圾回收以及何时执行,完全由JVM的垃圾回收器决定。
值得注意的是,频繁调用这些方法可能会导致不必要的资源消耗。此外,如果需要在调用gc()方法后确保垃圾回收器的执行,可以通过追加调用system.runFinalization方法来实现,尽管这并不是推荐的做法,因为它可能会对性能产生负面影响。
24.什么是native方法?
Native方法是指在程序中使用的一种特殊类型的方法,其实现是由其他语言编写的,例如C或C++。这些方法使用了JNI(Java本地接口)来将Java代码与其他语言的代码进行交互。在Java中,所有的方法代码都是使用Java语言编写的,而运行时都是在Java虚拟机(JVM)上执行的。然而,有时候我们可能需要使用一些底层的功能,或者想要调用一些底层的系统库,这时就需要使用Native方法。使用Native方法需要创建一个本地库,该库包含了使用其他语言编写的代码。这个库必须在运行时被加载到Java虚拟机中,以便可以在Java代码中调用其中的方法。
要使用Native方法,需要按照以下步骤进行操作:
- 在Java中声明Native方法:在Java类中声明一个方法,并使用"native"关键字修饰。
- 生成Java头文件:使用javah命令来生成对应的头文件,该头文件包含了Native方法的声明。
- 实现Native方法:在其他语言(如C或C++)中实现Native方法的具体逻辑。需要将生成的头文件中的方法声明复制到实现文件中,并编写对应的逻辑。
一个Native Method就是一个Java调用非Java代码的接口。这些方法的实现由非Java语言实现,比如C或C++。在定义一个native method时,并不提供实现体,因为其实现体是由非Java语言在外面实现的。
25.Java中的两种异常类型是什么?他们有什么区别?
Java中的两种异常类型是受检查的(checked)异常和不受检查的(unchecked)异常。
受检查的异常(Checked Exceptions):这类异常必须在方法或构造函数的声明中使用throws语句进行声明。如果方法可能会抛出此类异常,开发人员有责任处理这些异常,例如通过try-catch块捕获它们,或者在方法签名中继续抛出。受检查的异常通常是由程序逻辑错误引起的,这些错误在编码时可以通过适当的编程实践来避免。
不受检查的异常(Unchecked Exceptions):这类异常不需要在方法或构造函数的声明中使用throws语句进行声明。它们通常是由程序运行时的一些条件引起的,如NullPointerException或IndexOutOfBoundsException,这些异常通常是由于程序逻辑错误导致的,这些错误在编码时可以通过适当的编程实践来避免。不受检查的异常不会强制要求开发人员显式声明,因为它们通常是由编程错误引起的,而这些错误应该在编写代码时被预防。
这两种异常类型的区别主要体现在是否需要在方法签名中显式声明以及它们的处理方式上。受检查的异常需要开发人员显式处理,而不受检查的异常则不需要。此外,不受检查的异常通常是由编程错误引起的,这些错误应该在编写代码时被预防,而受检查的异常则可能由外部条件引起,这些条件在编写代码时可能无法控制。
26.Java throw和throws有什么区别?
Java中的throw和throws主要用于处理异常,但它们在用法和含义上有明显的区别。
位置与功能:
throws关键字用于方法声明后面,表示该方法可能会抛出哪些类型的异常,但不处理这些异常,而是将异常向上抛给调用者处理。它可以跟多个异常类名,用逗号隔开,表示该方法可能抛出的所有异常类型。throws表示出现异常的一种可能性,并不一定会发生这些异常。
throw关键字用于方法体内,后面跟的是具体的异常对象,表示抛出一个具体的异常实例。当执行到throw语句时,方法会立即抛出该异常,并跳转到调用者,将具体的问题对象抛给调用者。throw表示抛出了异常,执行throw则一定抛出了某种异常对象。
使用场景:
throws可以单独使用,在方法声明中添加throws子句表示该方法将抛出异常,由方法的调用者来处理这些异常。这允许调用者根据需要捕获和处理这些异常。
throw要么和try-catch-finally语句配套使用,要么与throws配套使用。但throw不能单独使用,它必须抛出一个具体的异常对象。
综上所述,throws用于声明方法可能抛出的异常类型,而throw用于实际抛出一个具体的异常对象。两者都是处理Java中异常的重要机制,但它们的用途和位置有所不同。
27.解释下Marshalling和demarshalling
Marshalling和demarshalling是两种处理内存中对象状态的技术,它们分别涉及将对象状态转化为适合传输或存储的格式,以及将这种格式的数据还原为原始对象的过程。
Marshalling(序列化):这个过程将内存中的对象状态转化为适合传输或存储的格式,如字节流、JSON、XML等。这有助于实现网络通信、持久化存储或跨平台/语言交互操作。序列化通过将对象转换为可传输或存储的形式,使得对象的状态可以在不同的环境或时间点之间保持一致。
Demarshalling(反序列化):这是Marshalling的逆过程,即将序列化数据还原为原始对象。反序列化将接收到的数据流转换回原始的对象形式,以便在需要时能够重新使用这些对象。
在Java中,marshalling和demarshalling的重要性体现在多个方面:
标准化数据交换:通过序列化和反序列化,可以实现不同系统之间的数据交换和互操作。
保证数据安全:通过加密、压缩、校验等手段,可以增强数据的安全性。
优化性能:通过减少传输量、提升速度,以及合理设计降低CPU和内存使用,可以提高性能。
管理内存资源:将长生命周期或大容量对象序列化至磁盘,可以有效管理内存资源。
实现marshalling和demarshalling的方式包括Java原生序列化(通过实现Serializable接口和使用JVM内置机制),以及使用第三方库如protobuf、Avro、Jackson、Gson等,这些库提供了更灵活、高效的解决方案。此外,还有JSON、XML绑定库如Jackson、Gson(JSON)和JAXB、XMLBeans(XML),用于实现对象与数据格式的互转。
28.解释下Java Serialization和Deserialization ?
Java序列化(Serialization)是指将对象的状态转换为可以保持或传输的格式的过程。序列化后的对象可以被写到数据库、存储到文件系统,或者通过网络传输。反序列化(Deserialization)是序列化的逆过程,它将流(stream)还原成对象。
Java序列化API提供了ObjectOutputStream类用于序列化,ObjectInputStream类用于反序列化。要序列化的对象必须实现Serializable或Externalizable接口。
注意事项:
- 使用 transient 关键字修饰的字段不会被序列化。
- 序列化操作不会自动序列化对象图中的静态成员。
- 修改序列化类定义(如添加方法或字段)后,可能需要更改 serialVersionUID ,否则反序列化过程可能会失败,抛出 InvalidClassException 。
- 保密数据(如密码、PIN 码)不应序列化存储,以防止安全漏洞。
- 序列化和反序列化是 Java 中非常强大的特性,可用于多种场景,包括但不限于持久化、深度复制、对象交换等。
29.简述什么是Servlet?
Servlet是Server和Applet的缩写,是服务端小程序的意思。使用Java语言编写的服务程序,可以生成动态的web页面,Servlet主要运行在服务器端,由服务器调用执行,是一种按照Servlet标准开发的类。
Servlet本质上也是java类,但要遵循Servlet规范进行编辑,没有main方法,它的创建、使用、销毁。都由Servlet容器进行管理(如Tomact服务器),写自己的类,不用写main方法,别人自动调用。,由服务器统一调用和执行。
Servlet和http协议紧密联系,servlet可以处理所有的http协议相关内容。提供了Servlet功能的服务器,叫做Servlet容器,常见容器有Tomact,Jetty,Server等。
30.简述Ser vlet的体系结构?
Servlet的体系结构主要包括以下几个部分:
Servlet API:这是Servlet体系结构的核心,定义了开发Servlet所需使用的接口和类。它提供了从容器接收请求、生成响应以及管理会话等核心服务。
Servlet容器:Servlet容器是执行Servlet的环境,负责调用Servlet API中的方法来接收和处理客户端请求,同时也管理Servlet的生命周期。常见的Servlet容器包括Tomcat、Jetty等。
Servlet配置文件:指的是web.xml文件,用于将Servlet映射到URL模式,配置Servlet的初始化参数等。在Servlet 3.0规范中,可以使用注解来替代web.xml文件的配置。
Servlet的生命周期:包括装载、初始化、处理请求和销毁等阶段。当Servlet容器启动时,装载器会查找web.xml文件或者注解,并创建和装载相应的Servlet类;接着,Servlet容器将调用Servlet的init()方法进行初始化;接收到客户端请求后,会调用Servlet的service()方法处理请求并生成响应;最后,在Servlet不再需要时,会调用其destroy()方法进行销毁。
Web服务器:作为Servlet容器的宿主环境,负责接收HTTP请求、处理静态资源请求、管理连接池等。常用的Web服务器包括Apache HTTP Server、Nginx等。
Java虚拟机(JVM):Java虚拟机是运行Java程序的虚拟计算机,Servlet也运行在JVM上。JVM负责将Java字节码转换成机器码并执行,为Servlet提供了可移植性和跨平台性。
总结来说,Servlet的体系结构是一个复杂的系统,它涉及到多个组件和技术的协同工作,以确保Web应用程序能够正确地接收和处理客户端请求,并生成相应的响应。