【Java面试】常见面试题以及参考答案

本文详细介绍了Java中equals与==的区别,Integer对象在不同情况下的比较,final关键字的用法,以及StringBuffer和StringBuilder在多线程安全性上的差异。同时,讨论了线程安全的集合,HashMap和Hashtable的异同,以及线程池的相关知识。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、== 和 equals 的区别是什么

Integer a=120

Integer b=120

Integer c=300

Integer d=300

输出:a==b结果 true

输出:c==d结果 false

参考答案
//todo
逐字稿
== 对于基本类型来说是值比较,对于引用类型来说是比较的是引用;而 equals 默认情况下是引用比较,只是很多类重写了 equals 方法,比如 String、Integer、Long 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等
对于基本数据类型和包装类型的比较(String 不属于基础类型):当 "=="运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象的地址。当 "=="运算符的1个操作数是基本类型,比较的就是基本类型是否相等。
拔高内容
  • 题目1代码
Integer i5 = 97;
Integer i6 = 97;
System.out.println(i5 == i6);//输出结果是true
System.out.println(i5.equals(i6));//输出结果是true
Integer i7 = 197;
Integer i8 = 197;
System.out.println(i7 == i8);//输出结果是false
System.out.println(i7.equals(i8));//输出结果是true
  • 题目1解析

i5 == i6 比较的是引用,但是Integer有常量池 -127-128,同一个常量池的值对象,所以输出true.

i5.equals(i6);这个方法被Integer重写了,底层就是比较数值,所以输出也是true.

i7 == i8 比较的是引用,没有在常量池,所以是2个不同的引用,输出false.

i7.equals(i8);比较的是数值,输出true.

  • 题目2代码
Integer i3 = 200;
int aint = 200;
long along = 200;
System.out.println(i3.equals(aint));//输出true
System.out.println(i3 == aint);//输出true
System.out.println(i3 == along);//输出true
  • 题目2解析

i3.equals(aint);引用类型于基本类型equals比较的是数值,==两侧有包装类和基础类型,比较的也是数值.

  • 题目3代码
Integer a = 100;
Integer b = 200;
Integer c = 300;
Integer d = a + b;
int result = a.intValue() + b.intValue();
System.out.println(c == (a + b));//引用==数值,比较数值,true
System.out.println(c == d); // 引用==引用,比较内存首地址,不在常量池,false
System.out.println(c.equals(a + b));//引用equals数值,比较数值,true
System.out.println(c == result);//引用==数值,比较数值,true
System.out.println(c.equals(result));//引用equals(数值),比较数值,true
  • 题目3解析

有算数运算会触发自动拆箱,上述题目中a+b等价于a.intValue()+b.intValue().

2、两个对象的 hashCode() 相同,则 equals() 也一定为 true?equal()相等的两个对象他们的hashCode()肯定相等?

参考答案
//todo
逐字稿
hashCode()计算散列值,两个对象相等散列值一定相同,相等的判断就是对象的equals()方法,两个对象不相等散列值相同的概率极小但是存在,所以两个对象hashCode相同,则equals()为true是错的,equals()为true则两个对象一定hashCode()相等是对的

3、说下final关键字?final 能修饰抽象类?

参考答案
//todo
逐字稿
final 修饰的类叫最终类,该类不能被继承
final 修饰的方法不能被重写。
final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改
定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类
拔高内容
public class FinalClass{
    private final User user=new User();
    public User getUser(){
        return user;
    }
    public void setUser(User user){
        this.user=user;
    }
}
class User {
    private String name;
    private String gender;
    public String getName(){
        return this.name;
    }
    public String getGender(){
        return this.gender;
    }
    public void setName(String name){
        this.name=name;
    }
    public void setGender(String gender){
        this.gender=gender;
    }
}

这里我们final属性user已经初始化,值就是内存的一个引用,所以这个引用是不能改的.

如图所示:
图1

虽然编写了setUser(User user)方法,但是编译提示错误.

但是已经初始化的user中属性name和gender是可以改的,因为这两个属性不是final,也没有改变final User属性的初始值(引用值).

如图所示:
在这里插入图片描述

4、String、StringBuffer、StringBuilder的区别?

参考答案
//todo
逐字稿
String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。
StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。

5、String str="i"与 String str=new String(“i”)一样吗?

参考答案
//todo
逐字稿
String str="i"的方式,Java 虚拟机会将其分配到常量池中;
而 
String str=new String("i") 则会被分到堆内存中。
如果是==返回false
如果是equals()返回true
所以一样不一样问法不准确

6、String 类的常用方法都有那些?

参考答案
逐字稿
indexOf():返回指定字符的索引。
charAt():返回指定索引处的字符。
replace():字符串替换。
trim():去除字符串两端空白。
split():分割字符串,返回一个分割后的字符串数组。
getBytes():返回字符串的 byte 类型数组。
length():返回字符串长度。
toLowerCase():将字符串转成小写字母。
toUpperCase():将字符串转成大写字符。
substring():截取字符串。
equals():字符串比较。

7、接口和抽象类有什么区别?

参考答案
//todo
逐字稿
实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。
构造函数:抽象类可以有构造函数;接口不能有。
实现数量:类可以实现很多个接口;但是只能继承一个抽象类。
访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符。

8、Java 容器都有哪些?线程安全的集合有哪些?

参考答案

//todo

逐字稿
Java容器类包含List、ArrayList、Vector及map、HashTable、HashMap、Hashset
线程安全的集合有:Vector、HashTable、Stack、ArrayBlockingQueue、ConcurrentHashMap、ConcurrentLinkedQueue、CopyOnWriteArraySet、CopyOnWriteArrayList

9、HashMap 和 Hashtable 有什么区别?

参考答案
逐字稿

10、HashMap 与 ConcurrentHashMap 的异同?(难)

参考答案

//todo

逐字稿
都是 key-value 形式的存储数据;
HashMap 是线程不安全的,ConcurrentHashMap 是 JUC 下的线程安全的;
HashMap 底层数据结构是数组 + 链表(JDK 1.8 之前)。JDK 1.8 之后是数组 + 链表 + 红黑树。当链表中元素个数达到 8 的时候,同时元素个数达到64个,链表的查询速度不如红黑树快,链表会转为红黑树,红黑树查询速度快;
HashMap 初始数组大小为 16(默认),当出现扩容的时候,以 0.75 * 数组大小的方式进行扩容;
ConcurrentHashMap 在 JDK 1.8 之前是采用分段锁来现实的 Segment + HashEntry,Segment 数组大小默认是 16,2 的 n 次方;JDK 1.8 之后,采用 Node + CAS + Synchronized来保证并发安全进行实现

11、ArrayList 和 LinkedList 的区别是什么(带拔高点)

参考答案
逐字稿
LinkedList底层实现是双链表;ArrayList底层实现是动态数组。即LinkedList使用的离散内存,插入速度快,而ArrayList使用的是连续内存空间,查询比较快。
从扩容看:LinkedList是离散空间,不需要主动扩容。ArrayList需要主动扩容为原尺寸的1.5倍.

12、synchronized 和 volatile 的区别是什么?

参考答案
逐字稿
volatile 是变量修饰符;synchronized 是修饰类、方法、代码段。
volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。
volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。

13、synchronized 和 Lock 有什么区别?

参考答案
逐字稿
synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
Lock是一个接口,而synchronized是Java中的关键字
synchronized有异常自动释放锁,lock要手动释放锁
synchronized可重入 不可中断 非公平 不可判断(是否获取到锁),lock是可重入 可中断 可公平 可判断(是否获取到锁)
synchronized线程只能一直等待直到锁的释放,lock可以选择trycatch不等待
synchronized底层实现是cas,lock底层实现是基于aqs

14、你能说说线程的状态么?

参考答案
逐字稿
新建状态(New):线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
就绪状态(Runnable):也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
运行状态(Running):线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种: (01) 等待阻塞 – 通过调用线程的wait()方法,让线程等待某工作的完成。 (02) 同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。 (03) 其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

15、sleep和wait区别?

参考答案
逐字稿
sleep 等待指定时间,到期后自动唤醒,wait 一直等待,直到被唤醒
sleep方法没有释放锁,不让出系统资源,而wait方法释放了锁,让出系统资源
wait,notify和notifyAll只能在同步sychronized关键字控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常

16、说一下java锁有哪些?

参考答案
逐字稿

在这里插入图片描述

17、说下线程池参数

参考答案
逐字稿
public ThreadPoolExecutor
(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
RejectedExecutionHandler handler
) 
//corePoolSize:线程池核心线程数量
//maximumPoolSize:线程池最大线程数量
//keepAliverTime:当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间
//unit:存活时间的单位
//workQueue:存放任务的队列
//handler:超出线程范围和队列容量的任务的处理程序

18、说下线程池实现原理:

参考答案
逐字稿
如果核心线程数空闲或者还有核心线程数可以创建,则创建一个新的工作线程来执行任务(即使其他空闲的基本线程能够执行新任务也会创建核心线程),否则,
看工作队列有没有满,如果没有,提交任务存储在工作队列中等待执行,如果满了
线程池还没有超过最大线程数,就会创建普通线程来执行任务,否则会交给拒绝策略来处理这个任务。
当一个线程完成任务时,它会从队列中取下一个任务来执行。
当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
拒绝策略是:
JAVA提供了4中策略:
1、AbortPolicy:直接抛出异常
2、CallerRunsPolicy: 只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。
3、DiscardOldestPolicy:丢弃队列里最老的一个任务,也就是下一个即将被执行的任务,并执行当前任务。
4、DiscardPolicy:不处理,丢弃掉。

在这里插入图片描述

19、说下常见线程池:

参考答案
逐字稿
1.单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
2.创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务,线程数收操作系统影响
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
3.创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
4.创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);

20、说下线程池状态:

参考答案
逐字稿
running:正在执行,接受新任务并处理
Shutdown:不接受新任务,可以处理已接收的任务
Stop:调用shutdownnow()后的状态,不接受新任务,不处理已接收的任务,并且会中断正在处理的任务
tidying:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING,
当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING
TERMINATED:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。
terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现

21、try catch finally的异常处理

参考答案
逐字稿
1、不管有木有出现异常,finally块中代码都会执行;
2、当try和catch中有return时,finally仍然会执行;
3、finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,不管finally中的代码怎么样,
返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;
4、finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
拔高内容
  • 代码1
public static int TestTryFinally4() {
   int result = 10;
   try {
      int i = 0 / 0;
   } catch (Exception e) {
      System.out.println("catch");
      result = 20;
      return result;
   } finally {
      System.out.println("finally");
      result = 30;
   }
   System.out.println(result + "----");
   return result;
}
  • 运行结果
catch
finally
20
  • 代码2
 public static int TestTryFinally3() {
      int result = 10;
      try {
      } catch (Exception e) {
         System.out.println("catch");
         result = 20;
         return result;
      } finally {
         System.out.println("finally");
         result = 30;
//       return result;
      }
      return result;
 }
  • 执行结果
执行结果:
finally
30

22、java中实现多 线程有几种方法?

参考答案
逐字稿
继承Thread类;
实现Runnable接口;
实现Callable接口通过FutureTask包装器来创建Thread线程;
使用ExecutorService、Callable、Future实现有返回结果的多线程(也就是使用了ExecutorService来管理前面的三种方式)
拔高内容
 // 1.创建一个实现callable接口的实现类
class Number implements Callable {
    // 2.实现call方法,将此线程需要执行的操作写在call方法中
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            if(i % 2 == 0) {
                sum  = sum + i;
            }
        }
        return sum;
    }
}
public class ThreadCallableTest {
    public static void main(String[] args) {
        //3.创建callable接口实现类的对象
        Number number = new Number();
        //4.将callable实现接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(number);
        //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
        new Thread(futureTask).start();
        try {
            // get方法的返回值即为futureTask构造器的参数Callable实现类重写的call方法的返回值
            //6.获取callable中call方法的返回值
            Object sum = futureTask.get();
            System.out.println("总和为:" + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
              

package com.my.java.thread;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class TestThreadExecutorService {

    private static final int taskNum = 3;

    public static void main(String[] args) throws Exception {
        ExecutorService pool = Executors.newFixedThreadPool(taskNum);
        // 创建多个有返回值的任务
        List<Future> list = new ArrayList<Future>();
        for (int i = 0; i < taskNum; i++) {
            MyCallableTask callableTask = new MyCallableTask();
            Future future = pool.submit(callableTask);
            list.add(future);
        }
        pool.shutdown();
        //拿到返回值和结果和
        int result = 0;
        for (Future future : list) {
            Integer num = (Integer) future.get();
            result += num;
            System.out.println(num);
        }
        System.out.println(result);
    }
}

class MyCallableTask implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        return new Random().nextInt(10);
    }
}
    

23、String与byte[]两者之间如何转换?

参考答案
逐字稿
在Java编程语言中,String和byte[]是两种不同的数据类型。如果你想将一个字符串转换成一个字节数组,或者将一个字节数组转换成一个字符串,可以使用以下方法:
String转byte[]
要将一个字符串转换成一个字节数组,可以使用getBytes()方法,它将返回一个表示字符串的UTF-8编码的字节数组。
byte[]转String
要将一个字节数组转换成一个字符串,可以使用String类的构造函数,它接受一个字节数组参数并返回一个字符串对象。

24、&和&& 的区别?

参考答案
逐字稿
在Java中,&和&&都是逻辑运算符,但是它们之间有一些区别。
&是按位与运算符,意味着它将操作数的每个位进行比较。而&&是逻辑与运算符,只要左侧操作数为false,则右侧操作数不会被执行。
&可以应用于整数、字符和布尔类型,但&&只适用于布尔类型。
当使用&时,不管左侧操作数是否为false,右侧操作数都会被执行。而当使用&&时,如果左侧操作数为false,那么右侧操作数将不被执行。
参考案例
int a = 5;
int b = 0;

if (a > 4 & b++ > 0) {
    // This block will be executed
}

System.out.println(b); // Output: 1

if (a > 4 && b++ > 0) {
    // This block will not be executed
}

System.out.println(b); // Output: 1

25、final、finally、finalize区别?

参考答案
逐字稿
final:
final 是 Java 中的一个关键字,可以用来修饰类、方法和变量。当 final 修饰类时,该类不能被继承;当 final 修饰方法时,该方法不能被重写;当 final 修饰变量时,该变量一旦赋值后就不能再被修改。
finally:
finally 是 Java 中的一个关键字,在异常处理中使用。无论 try catch 块中是否发生了异常,finally 中的代码都会被执行。
finalize:
finalize 是 Java 中 Object 类中的一个方法,是垃圾回收机制的一部分。当一个对象被垃圾回收器回收时,它的 finalize() 方法会被调用。
简要概括:
final:修饰符,表示不可变。
finally:语句块,表示无论如何都会执行的代码块。
finalize:方法,会在对象被回收之前调用

26、描述一下JVM加载class文件的原理机制?

参考答案
逐字稿
Java虚拟机(JVM)加载class文件的原理机制可以分为三个步骤:
加载(Loading)
在这一步骤中,JVM会根据类的全限定名在classpath路径下查找对应的.class文件。如果找到了该文件,则将其读入内存中,并生成一个对应的Class对象。如果没有找到该文件,则会抛出ClassNotFoundException异常。
链接(Linking)
在链接阶段,JVM会对已经加载进来的.class文件进行验证、准备和解析等操作。其中,验证阶段会检查类文件是否符合JVM规范;准备阶段则为类静态变量分配内存并设置默认值;解析阶段则是把符号引用转换为直接引用。
初始化(Initialization)
在初始化阶段,JVM会执行类构造器<clinit>()方法的过程。该方法是由编译器自动加入的,它会包含类所有静态

web网络部分

1、转发和重定向的区别?

参考答案
逐字稿
转发不需要客户端重新请求,是内部发起想内部的跳转,request属于同一个请求链,可以共享一个reuqest的作用域.
重定向需要客户端发送第二次请求,是服务器控制的跳转目的地,不共享一个请求链中的请求作用域,但是共享session
作用域或者更高范围的作用域

2、tcp 和 udp的区别?

参考答案
逐字稿
tcp 面向连接,udp 面向非连接即发送数据前不需要建立链接
tcp 提供可靠的服务(数据传输),udp 无法保证
tcp 面向字节流,udp 面向报文
tcp 数据传输慢,udp 数据传输快

3、get 和 post 请求有哪些区别?

参考答案

逐字稿
get 请求会被浏览器主动缓存,而 post 不会。
get 传递参数有大小限制,而 post 没有。
post 参数传输更安全,get 的参数会明文限制在 url 上,post 不会。

4、tcp 为什么要三次握手,两次不行吗?

参考答案
逐字稿
不行,原因如下:
三次握手才可以阻止重复历史连接的初始化
三次握手才可以同步双方的初始序列号
避免资源浪费

5、HTTP常见状态码?

参考答案
逐字稿
HTTP状态码是指由HTTP服务器向客户端返回的三位数值代码,用于表示服务器对请求的响应结果。常见的HTTP状态码有:
200:请求成功
301:永久重定向
302:临时重定向
400:客户端请求错误
401:未授权访问
403:禁止访问
404:未找到页面
500:服务器内部错误
HTTP状态码对于Web开发人员和网站管理员来说非常重要,因为它可以帮助他们确定用户请求的问题,从而更好地解决问题。

更多内容整理敬请期待

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值