Java 面试题超详细解析(二)
1. 是否可以继承String类?
不可以继承String
类,因为String
类被final
关键字修饰。在Java中,final
修饰的类不能被继承,这是为了保证String
类的不可变性、安全性和效率。不可变性使得String
对象在多线程环境下是线程安全的,并且在字符串常量池的实现中起到关键作用。
2. String s = new String(“xyz”);创建了几个String Object? 二者之间有什么区别?
创建了一个或两个String
对象。如果字符串常量池中已经存在"xyz"
,则只在堆中创建一个String
对象;如果常量池中不存在"xyz"
,则先在常量池中创建一个"xyz"
对象,再在堆中创建一个String
对象。常量池中的对象是共享的,而堆中的对象是独立的。
3. String 和StringBuffer的区别
- 可变性:
String
是不可变的,一旦创建,其值不能被改变。每次对String
进行修改操作时,都会创建一个新的String
对象。而StringBuffer
是可变的,可以通过append
、insert
等方法对其内容进行修改。 - 线程安全性:
StringBuffer
是线程安全的,因为它的方法大多使用synchronized
关键字修饰。而String
由于是不可变的,天然就是线程安全的。 - 性能:在单线程环境下,由于
String
的不可变性,频繁修改会产生大量临时对象,性能较差。而StringBuffer
的性能相对较好。但在多线程环境下,由于StringBuffer
的同步机制,会有一定的性能开销。
4. 如何把一段逗号分割的字符串转换成一个数组?
可以使用String
类的split
方法。示例代码如下:
String str = "apple,banana,orange";
String[] arr = str.split(",");
for (String s : arr) {System.out.println(s);
}
split
方法根据指定的分隔符将字符串分割成一个字符串数组。
5. 数组有没有length()这个方法? String有没有length()这个方法?
数组没有length()
方法,数组有一个length
属性,用于获取数组的长度。而String
有length()
方法,用于获取字符串的长度。示例代码如下:
int[] arr = {1, 2, 3};
System.out.println(arr.length); String str = "hello";
System.out.println(str.length());
6. 下面这条语句一共创建了多少个对象? String s=“a”+“b”+“c”+“d”;
在编译期,Java编译器会对字符串常量的拼接进行优化,将其合并为一个字符串常量。所以这条语句只会在字符串常量池中创建一个"abcd"
对象。
7. try {}里有一个return语句,那么紧跟在这个try后的finally {}里的code会不会被执行,什么时候被执行,在return前还是后?
finally
块里的代码会被执行。在try
块中的return
语句执行前,finally
块中的代码会先执行。但需要注意的是,finally
块中的代码不会影响try
块中return
语句的返回值(除了一些特殊情况,如在finally
块中也有return
语句)。示例代码如下:
public class FinallyExample {public static int test() {try {return 1;} finally {System.out.println("Finally block executed");}}public static void main(String[] args) {System.out.println(test());}
}
8. 下面的程序代码输出的结果是多少?
由于没有给出具体的程序代码,这里无法给出具体结果。但在分析代码输出结果时,需要考虑变量的初始化、方法的调用、控制流语句(如if
、for
、while
等)以及异常处理等因素。
9. final, finally, finalize的区别。
- final:是一个关键字,用于修饰类、方法和变量。修饰类时,类不能被继承;修饰方法时,方法不能被重写;修饰变量时,变量成为常量,一旦赋值就不能再改变。
- finally:是
try-catch
异常处理机制的一部分,无论try
块中是否发生异常,finally
块中的代码都会被执行。常用于释放资源,如关闭文件、数据库连接等。 - finalize:是
Object
类的一个方法,用于在对象被垃圾回收之前执行一些清理工作。但由于垃圾回收的不确定性,不建议依赖finalize
方法进行资源清理。
10. 运行时异常与一般异常有何异同?
- 相同点:都是
Throwable
的子类,都表示程序运行过程中出现的异常情况。 - 不同点:运行时异常(
RuntimeException
及其子类)是可以不被捕获或声明抛出的异常,通常是由程序逻辑错误引起的,如NullPointerException
、ArrayIndexOutOfBoundsException
等。而一般异常(受检查异常)必须被捕获或在方法签名中声明抛出,如IOException
、SQLException
等。
11. error和exception有什么区别?
- error:表示系统级的错误,是程序无法处理的严重问题,如
OutOfMemoryError
、StackOverflowError
等。这些错误通常是由系统资源耗尽、硬件故障等原因引起的,程序一般无法通过代码来解决这些问题。 - exception:表示程序可以处理的异常情况,分为运行时异常和受检查异常。程序可以通过
try-catch
语句捕获并处理异常,或者在方法签名中声明抛出异常。
12. Java中的异常处理机制的简单原理和应用。
Java的异常处理机制基于try-catch-finally
语句和throws
关键字。当程序执行过程中发生异常时,会创建一个异常对象,并将控制权转移到最近的异常处理代码块。try
块中包含可能抛出异常的代码,catch
块用于捕获并处理特定类型的异常,finally
块中的代码无论是否发生异常都会被执行。throws
关键字用于在方法签名中声明该方法可能抛出的异常。示例代码如下:
public class ExceptionExample {public static void main(String[] args) {try {int result = 1 / 0;} catch (ArithmeticException e) {System.out.println("Division by zero error: " + e.getMessage());} finally {System.out.println("Finally block executed");}}
}
13. 请写出你最常见到的5个runtime exception。
NullPointerException
:当试图访问一个null
对象的方法或属性时抛出。ArrayIndexOutOfBoundsException
:当访问数组时,使用的索引超出了数组的有效范围。ArithmeticException
:当进行算术运算时出现错误,如除以零。ClassCastException
:当试图将一个对象强制转换为不兼容的类型时抛出。IllegalArgumentException
:当传递给方法的参数不合法时抛出。
14. java中有几种方法可以实现一个线程?用什么关键字修饰同步方法? stop()和suspend()方法为何不推荐使用?
- 实现线程的方法:
- 继承
Thread
类,重写run
方法。 - 实现
Runnable
接口,实现run
方法,并将其传递给Thread
类的构造函数。 - 实现
Callable
接口,实现call
方法,并通过FutureTask
和Thread
类来启动线程。
- 继承
- 同步方法:使用
synchronized
关键字修饰同步方法,确保同一时间只有一个线程可以访问该方法。 - 不推荐使用
stop()
和suspend()
方法的原因:stop()
方法会立即终止线程,可能导致线程持有的资源无法正确释放,造成数据不一致等问题。suspend()
方法会暂停线程,但不会释放线程持有的锁,可能导致死锁。
15. sleep() 和 wait() 有什么区别?
- 所属类:
sleep()
是Thread
类的静态方法,wait()
是Object
类的实例方法。 - 作用:
sleep()
用于暂停当前线程的执行,让出CPU资源,但不会释放对象的锁。wait()
用于线程间的协作,调用该方法的线程会释放对象的锁,并进入等待状态,直到其他线程调用该对象的notify()
或notifyAll()
方法唤醒它。 - 使用场景:
sleep()
常用于模拟延迟、定时任务等。wait()
常用于生产者 - 消费者模型等线程间的同步场景。
16. 同步和异步有何异同,在什么情况下分别使用他们?举例说明。
- 相同点:都是用于处理多个任务的执行方式。
- 不同点:同步是指在执行一个任务时,必须等待该任务完成后才能继续执行下一个任务。而异步是指在执行一个任务时,不需要等待该任务完成,可以继续执行其他任务,当任务完成后会通过回调等方式通知调用者。
- 使用场景:同步适用于对数据一致性要求较高的场景,如银行转账操作。异步适用于处理耗时较长的任务,如文件下载、网络请求等,可以提高系统的并发性能。示例代码如下:
// 同步示例
public class SynchronousExample {public static void main(String[] args) {long startTime = System.currentTimeMillis();task1();task2();long endTime = System.currentTimeMillis();System.out.println("Total time: " + (endTime - startTime) + " ms");}public static void task1() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Task 1 completed");}public static void task2() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Task 2 completed");}
}// 异步示例
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class AsynchronousExample {public static void main(String[] args) {long startTime = System.currentTimeMillis();ExecutorService executor = Executors.newFixedThreadPool(2);executor.submit(() -> task1());executor.submit(() -> task2());executor.shutdown();while (!executor.isTerminated()) {// 等待所有任务完成}long endTime = System.currentTimeMillis();System.out.println("Total time: " + (endTime - startTime) + " ms");}public static void task1() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Task 1 completed");}public static void task2() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Task 2 completed");}
}
17. 多线程有几种实现方法?同步有几种实现方法?
- 多线程实现方法:继承
Thread
类、实现Runnable
接口、实现Callable
接口。 - 同步实现方法:
- 使用
synchronized
关键字修饰方法或代码块。 - 使用
ReentrantLock
类。 - 使用
Semaphore
、CountDownLatch
等并发工具类。
- 使用
18. 启动一个线程是用run()还是start()?
启动一个线程应该使用start()
方法。start()
方法会启动一个新的线程,并调用线程的run()
方法。而直接调用run()
方法只是在当前线程中执行run()
方法的代码,不会启动新的线程。示例代码如下:
public class ThreadStartExample {public static void main(String[] args) {Thread thread = new Thread(() -> {System.out.println("Thread is running");});// 正确启动线程thread.start(); // 错误,不会启动新线程// thread.run(); }
}
19. 当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?
这取决于其他方法是否也是同步方法以及是否使用了相同的锁。如果其他方法不是同步方法,那么其他线程可以进入该方法。如果其他方法也是同步方法,并且使用了相同的锁(即同一个对象的锁),那么其他线程必须等待当前线程释放锁后才能进入该方法。示例代码如下:
public class SynchronizedMethodExample {public synchronized void method1() {System.out.println("Method 1 is running");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Method 1 completed");}public synchronized void method2() {System.out.println("Method 2 is running");}public void method3() {System.out.println("Method 3 is running");}public static void main(String[] args) {SynchronizedMethodExample example = new SynchronizedMethodExample();Thread t1 = new Thread(() -> example.method1());Thread t2 = new Thread(() -> example.method2());Thread t3 = new Thread(() -> example.method3());t1.start();try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}t2.start();t3.start();}
}
20. 线程的基本概念、线程的基本状态以及状态之间的关系。
- 线程的基本概念:线程是程序执行的最小单位,是进程中的一个执行单元。一个进程可以包含多个线程,这些线程共享进程的资源。
- 线程的基本状态:
- 新建状态(New):当创建一个
Thread
对象时,线程处于新建状态。 - 就绪状态(Runnable):调用
start()
方法后,线程进入就绪状态,等待CPU调度。 - 运行状态(Running):当CPU调度该线程时,线程进入运行状态。
- 阻塞状态(Blocked):线程在等待某个资源或锁时,进入阻塞状态。
- 等待状态(Waiting):调用
wait()
、join()
等方法后,线程进入等待状态,需要其他线程唤醒。 - 超时等待状态(Timed Waiting):调用
sleep()
、wait(long timeout)
等方法后,线程进入超时等待状态,在指定时间后自动唤醒。 - 终止状态(Terminated):线程执行完
run()
方法或因异常终止后,进入终止状态。
- 新建状态(New):当创建一个
- 状态之间的关系:新建状态的线程调用
start()
方法后进入就绪状态,就绪状态的线程获得CPU调度后进入运行状态。运行状态的线程可能因为等待资源、调用wait()
等方法进入阻塞、等待或超时等待状态,当条件满足时,线程可以从这些状态转换回就绪状态。线程执行完任务或出现异常后进入终止状态。