JavaSE常见的面试题及其答案

JavaSE面试题一、Java基础部分

  1. 什么是Java的final关键字?有什么作用?

答:Java中的final关键字用于表示最终的、不可改变的。它主要有以下三种应用场景:

(1)final修饰变量:表示该变量只能被赋值一次,即它是一个常量。

(2)final修饰方法:表示该方法不能被子类重写。

(3)final修饰类:表示该类不能被继承。

  1. String、StringBuffer和StringBuilder有什么区别?

答:String、StringBuffer和StringBuilder都是Java中的字符串类型,它们的区别主要在于以下几个方面:

(1)String是一个不可变的字符串类型,即每次对String进行操作都会生成一个新的String对象,原来的String对象不会被改变。而StringBuffer和StringBuilder是可变的字符串类型,可以在原有的对象上进行修改。

(2)String使用“+”连接字符串时,内部实现是使用StringBuilder的append()方法,在循环拼接字符串时会产生大量的临时StringBuilder对象,造成性能浪费。因此,在需要进行大量字符串拼接操作时,最好使用StringBuffer或StringBuilder。

(3)StringBuffer是线程安全的,适用于多线程环境下;StringBuilder则是非线程安全的,适用于单线程环境下。

  1. 如何实现一个线程安全的单例模式?

答:可以使用双重检查锁定(Double Check Locking)来实现。

 
 

java复制代码

public class Singleton { private static volatile Singleton instance; //使用volatile保证可见性和禁止重排序 private Singleton() {} public static Singleton getInstance() { if (instance == null) { //第一次检查,如果为空,则进入同步块 synchronized (Singleton.class) { if (instance == null) { //第二次检查,只有第一个线程进入此代码块时才进行实例化 instance = new Singleton(); } } } return instance; } }

双重检查锁定对于多线程环境下的单例模式具有较好的效率和线程安全性。详细解释可以参考[1]。

  1. 什么是Java的反射?

答:Java中的反射指的是在运行时动态地获取一个类、接口、方法、属性等的相关信息,并且可以在运行时动态地创建对象、调用方法、访问属性等。反射在Java中主要通过java.lang.reflect包实现。

通过反射可以实现很多高级的功能,如在框架中动态地加载类、在编写通用代码时操作未知类型的对象等。

  1. 什么是Java的序列化?如何实现序列化和反序列化?

答:Java中的序列化指的是将对象转换成二进制流的过程,以便可以将其存储到文件或者在网络上进行传输。反序列化则是将二进制流转换成对象的过程。

要实现Java的序列化和反序列化,需要满足以下条件:

(1)被序列化的对象必须实现Serializable接口,该接口没有任何方法,只是起到标识作用。

(2)使用ObjectOutputStream类将对象序列化成二进制流,并将其写入到文件或者网络输出流中;使用ObjectInputStream类将二进制流反序列化成对象。

下面是一个简单的示例代码:

 
 

java复制代码

public class SerializableTest implements Serializable { private static final long serialVersionUID = 1L; private int id; private String name; public SerializableTest(int id, String name) { this.id = id; this.name = name; } public int getId() { return id; } public String getName() { return name; } public static void main(String[] args) throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); SerializableTest st = new SerializableTest(1, "test"); oos.writeObject(st); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); SerializableTest obj = (SerializableTest)ois.readObject(); System.out.println(obj.getId() + ", " + obj.getName()); } }

这里将一个SerializableTest对象序列化成二进制流,并反序列化回来,输出结果为“1, test”。

  1. 什么是Java的ClassLoader?它有哪些常用的子类?

答:Java中的ClassLoader(类加载器)用于将类的字节码文件加载到内存中,并生成相应的Class对象。ClassLoader主要有以下三种常用的子类:

(1)Bootstrap ClassLoader:用于加载JRE核心库的代码,如java.lang包之类的。

(2)Extension ClassLoader:用于加载JRE扩展库(如jre/lib/ext中的jar包)中的类。

(3)System ClassLoader:用于加载CLASSPATH环境变量所指定的路径中的类。

除此之外还可以自定义ClassLoader,比如可以从网络或者数据库中动态地加载类。可参考[1]。

JavaSE面试题二、多线程部分

  1. Java中如何实现线程?

答:Java中有两种主要的实现线程的方式:

(1)继承Thread类:直接继承Thread类,重写run()方法,并在该方法中定义线程需要执行的代码。

 
 

java复制代码

public class MyThread extends Thread { @Override public void run() { //线程需要执行的代码 } public static void main(String[] args) { MyThread t = new MyThread(); t.start(); //启动线程 } }

(2)实现Runnable接口:实现Runnable接口,重写run()方法,并在另一个线程中调用该方法。

 
 

java复制代码

public class MyRunnable implements Runnable { @Override public void run() { //线程需要执行的代码 } public static void main(String[] args) { MyRunnable r = new MyRunnable(); Thread t = new Thread(r); t.start(); //启动线程 } }

这两种方式的区别主要在于,实现接口可以避免Java单继承的限制,可以更加灵活地组织代码。

  1. 什么是Java中的线程池?它有哪些好处?

答:Java中的线程池是一种管理线程的机制,它可以复用已经创建的线程,避免频繁地创建和销毁线程带来的性能开销。

Java中的线程池主要使用线程池工厂类Executor创建,常用的线程池有以下三种:

(1)FixedThreadPool:固定大小的线程池,当线程池中的所有线程都在运行时,新的任务会被暂存到队列中等待。

(2)CachedThreadPool:缓存线程池,线程数量不固定,可以根据需求自动增加或减少。如果线程池中的线程在60秒内没有被使用,则会被销毁。

(3)ScheduledThreadPool:用于执行定时或周期性的任务,例如定时执行某个任务,每隔一段时间执行一次任务等。

线程池的好处主要有以下几点:

(1)复用线程:避免了线程的创建和销毁,提高了程序的性能和响应速度。

(2)统一管理:能够对线程进行统一的管理,包括线程的数量、运行状态、优先级等。

(3)提高可扩展性:能够方便地对线程池进行配置和管理,使得程序具有更好的可扩展性。

  1. 什么是Java中的锁机制?有哪些常见的锁?

答:Java中的锁机制是用来协调多个线程之间的并发访问的,在共享资源上加锁,保证同一时间只有一个线程可以访问该资源。

Java中的锁主要有以下几种类型:

(1)synchronized:关键字,用于对代码块或者方法进行加锁。

(2)ReentrantLock:Java中提供的锁实现类,与synchronized类似,但具有更高的灵活性和功能。

(3)ReadWriteLock:读写锁,既可以支持多个线程同时读取共享资源,又可以保证写入时只有一个线程可以访问共享资源。

除此之外还有一些其他的锁机制,如Semaphore、CountDownLatch等。

  1. 如何实现Java中的线程通信?

答:Java中的线程通信主要通过wait()、notify()和notifyAll()方法来实现:

(1)wait():使当前线程等待,直到另一个线程调用对象的notify()或notifyAll()方法唤醒它。

(2)notify():唤醒在该对象上等待的一个线程,如果有多个线程在该对象上等待,则只会唤醒其中一个线程。

(3)notifyAll():唤醒在该对象上等待的所有线程。

下面是一个简单的示例代码:

 
 

java复制代码

public class WaitNotifyTest { private boolean flag = false; public synchronized void waitMethod() throws InterruptedException { while (!flag) { wait(); } System.out.println("waitMethod"); } public synchronized void notifyMethod() { flag = true; notifyAll(); } public static void main(String[] args) throws Exception { WaitNotifyTest wnt = new WaitNotifyTest(); Thread t1 = new Thread(new Runnable() { @Override public void run() { try { wnt.waitMethod(); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { wnt.notifyMethod(); } }); t1.start(); Thread.sleep(1000); //等待t1线程先执行wait()方法 t2.start(); } }

这里定义了一个WaitNotifyTest类,在waitMethod()方法中使用while循环不断地等待flag变为true,然后输出“waitMethod”;在notifyMethod()方法中将flag设置为true,然后调用notifyAll()方法唤醒所有等待在该对象上的线程。

运行程序后,t1线程先执行waitMethod()方法进入等待状态,然后t2线程执行notifyMethod()方法将flag设置为true并唤醒t1线程,此时t1线程输出“waitMethod”。

猜你喜欢

转载自blog.csdn.net/m0_67906358/article/details/130105021
今日推荐