• java基础面试题


     今年环境越来越难了, 准备了一些面试题分享一下,持续更新中!着急的可看最后

    1. equals 与==区别

    == 运算符

    1. 基本类型:当用于基本数据类型(如 int、char、boolean、float 等)时,== 比较的是它们的值是否相等。

    2. 引用类型:对于引用类型(如 String、自定义类的对象等),== 比较的是两个对象引用(内存地址)是否相同,即它们是否指向内存中同一个对象实例。如果两个引用指向同一个对象,== 返回 true;否则,返回 false

    equals() 方法:

    1. 默认行为:equals() 是 Object 类中的一个方法,所有 Java 类都继承了它。在没有被子类重写的情况下,equals() 的默认实现与 == 对于引用类型的行为相同,即比较对象的引用是否指向同一个内存地址。

    2. 自定义比较逻辑:关键在于,很多类(如 String、Integer、Date 等)重写了 equals() 方法,提供了基于类特性和业务需求的值相等的判断逻辑。例如,String 类的 equals() 方法会逐个字符比较字符串内容是否相同,而不关心它们是否位于内存中的同一位置。对于自定义类,通常建议在需要比较对象内容时重写 equals() 方法,以定义“逻辑相等”的标准,如比较属性值是否一致。

    总结起来,"=="比较的是变量的值或引用的地址值,而"equals()"比较的是对象的内容。

    2. final,finally,finalize的区别

    1. final:
      • final是一个修饰符,可以用于修饰类、方法和变量。
        • 用于修饰类时,表示该类不能被继承,即为最终类。
        • 用于修饰方法时,表示该方法不能被子类重写。
        • 用于修饰变量时,表示该变量是一个常量,其值不能被修改。
    1. finally:
      • finally是一个关键字,用于定义一个代码块,通常与try-catch结构一起使用。
      • finally块中的代码无论是否抛出异常,都会被执行。
      • finally块通常用于释放资源、关闭连接或执行必要的清理操作。
    1. finalize:
      • finalize是Object类中的一个方法,被用于垃圾回收机制。
      • finalize方法在对象被垃圾回收之前被调用,用于进行资源释放或其他清理操作。
      • 通常情况下,我们不需要显式地调用finalize方法,而是交由垃圾回收器自动调用。

    总结:

    • final是修饰符,用于限定类、方法和变量的性质。
    • finally是一个关键字,用于定义一个代码块,在异常处理中用于确保特定代码无论如何都会被执行。
    • finalize是一个Object类中的方法,用于对象的垃圾回收前的清理操作。

    3. 重载和重写

    1. 重载:
      1. 重载是指在同一个类中,可以有多个方法名相同但参数类型、参数个数或参数顺序不同的方法。
      2. 重载的作用是增加方法的灵活性和可读性,让同一个方法名可以对不同情况进行处理。
      3. 重载方法的返回类型可以相同也可以不同,但不足以区分重载方法。
    2. 重写:
      1. 重写是指在子类中,可以对父类的方法进行重写,即对父类的方法名、返回值类型、参数列表和访问修饰符等进行重新定义。
      2. 重写方法必须与被重写方法拥有相同的方法名、返回值类型和参数列表,但是可以更改访问修饰符、抛出的异常类型和方法体等。
      3. 重写的作用是实现多态性,通过父类引用调用子类对象的方法,实现对同一方法名的不同实现。

    总结:重载和重写都是Java中多态性的体现,但是它们的实现方式和作用有所不同,需要根据具体的需求进行选择。

    4. 两个对象 hashCode相同,则equals也一定为true吗

    不一定。

    根据Java的规范,如果两个对象的hashCode()返回值相同,那么它们可能相等,但并不保证一定相等。

    equals()方法用于比较两个对象的内容是否相等,而hashCode()方法用于获取对象的哈希码。根据Java规范,如果两个对象相等(通过equals()方法比较),它们的哈希码必须相等

    总结:

    两个对象的hashCode()方法相同,并不能保证它们的equals()方法一定返回true,因此在比较对象的相等性时,需要同时使用equals()方法和hashCode()方法。

    5. String、StringBuffer、StringBuilder的区别

    String是不可变的,如果尝试去修改,会新生成一个字符串对象,StringBuffer和StringBuilder是可变的

    StringBuffer是线程安全的,StringBuilder是线程不安全的,所以在单线程环境下StringBuilder效率会更高

    6. 抽象类和接口有什么区别

    1. 定义

      • 抽象类包含抽象方法(无实现)和非抽象方法(有实现),允许有变量、构造器和初始化块。
      • 接口仅包含抽象方法(JDK 8后支持默认方法)和常量,不允许有变量、构造器和初始化块。
    2. 继承与实现

      • 单继承抽象类,可同时实现多个接口
      • 接口间可互相继承
    3. 访问修饰符

      • 抽象类方法可为 publicprotected 或 default
      • 接口方法默认 public,支持 private(JDK 9起)和 default
    4. 设计目的与使用场景

      • 抽象类侧重描述类族共性,提供部分实现,适合定义共性属性与行为,强制子类实现特定方法。
      • 接口关注行为约定,定义方法签名,规定实现类必须提供的行为规范,适用于定义不同类间的通用行为契约,实现多重继承,为已有类添加新行为。

    总结:

    抽象类与接口在实现抽象化和多态时侧重点不同:抽象类提供更丰富的实现细节和结构约束,适用于描述类族关系;

    接口着重于行为规范,强调纯粹的契约定义,支持灵活的组合与扩展。根据实际需求选择适当的抽象机制有助于构建清晰、可扩展的面向对象设计。

    7. Java中的基本数据类型有哪些?大小多少?

    在Java中,基本数据类型有以下几种:

    1. 整数类型:
      • byte:1字节,在内存中范围为-128到127
      • short:2字节,在内存中范围为-32768到32767
      • int:4字节,在内存中范围为约-21亿到21亿
      • long:8字节,在内存中范围为约-922亿亿到922亿亿
    1. 浮点数类型:
      • float:4字节,在内存中约范围为±3.40282347E+38F(有效位数为6-7位)
      • double:8字节,在内存中约范围为±1.79769313486231570E+308(有效位数为15位)
    1. 字符类型:
      • char:2字节,在内存中范围为0到65535,表示一个Unicode字符
    1. 布尔类型:
      • boolean:1位,在内存中只能表示true或false

    8. String类能被继承吗,为什么

    不可继承:在Java中,String类是被final关键字修饰的,即不可继承。final关键字表示一个类不允许被其他类继承,也就是说,String类不能被任何其他类继承。

    设计意图:将String类设计为 final 是出于多种考虑。首先,String类代表不可变的字符序列,这种不可变性对于安全性、并发性和性能优化至关重要

    总结:String类不能被继承,这是由Java语言的 final 修饰符所决定的,这一设计旨在保护String类的不可变性、优化性能以及维护API的长期稳定性

    9. int和Integer的区别

    nt和Integer之间的区别主要在以下几个方面:

    1. 数据类型类别

      • int基本数据类型,是Java语言的内置数据类型之一,直接在栈内存中存储
      • Integer包装类(wrapper class),是Java为int基本类型提供的一个对象封装,属于引用类型,
    2. 内存分配与存储

      • int:直接存储数值本身,占用内存大小固定(通常为4个字节),无需额外的内存开销。
      • Integer:存储的是指向堆中Integer对象的引用,引用本身占内存大小与平台相关(通常为32位或64位),对象在堆中占用额外内存(包括对象头、实例数据和可能存在的填充字节)。
    3. 默认值与初始状态

      • int:如果没有显式初始化,其默认值为 0
      • Integer:声明为对象变量时,默认值为 null。只有在创建并初始化Integer对象后,才会有具体的整数值。

    总结:int是基本数据类型,适用于简单的整数运算和存储,没有对象的特性和可空性。而Integer是int的包装类,可以作为对象使用,具有更多的方法和一些方便的功能

    10. Java中的异常处理机制是怎样的,分别哪些异常

                 

    • Throwable 类是所有异常和错误的基类。
    • Exception 类及其子类代表程序运行时可预见的异常情况,如逻辑错误、资源问题等。进一步细分为:
      • Checked Exceptions(受检异常):如 IOExceptionSQLException 等,编译器要求必须显式处理(捕获或声明抛出)。
      • Unchecked Exceptions(非受检异常):继承自 RuntimeException,如 NullPointerExceptionArrayIndexOutOfBoundsException,编译器不强制处理,但建议预防或妥善处理。
    • Error 类及其子类表示严重的系统级错误或资源耗尽,如 OutOfMemoryErrorStackOverflowError,通常无法在程序中修复。

    11. Java 创建对象有几种方式

    1. 使用new关键字:这是最常见的创建对象的方式。通过调用类的构造函数,使用new关键字可以在内存中分配一个新的对象。
    2. 使用反射:Java的反射机制允许在运行时动态地创建对象。通过获取类的Class对象,并调用其构造函数,可以实现对象的创建。
    3. 使用newInstance()方法:某些类提供了newInstance()方法来创建对象,这种方式只适用于具有默认无参构造函数的类。
    4. 使用clone()方法:如果类实现了Cloneable接口,就可以使用clone()方法创建对象的副本。
    5. 使用对象的反序列化:通过将对象序列化到一个字节流中,然后再进行反序列化,可以创建对象的副本。

    12. 什么是守护线程?与普通线程的区别

    守护线程 是一种特殊类型的线程,其生命周期与程序中其他线程(特别是用户线程)密切相关。当所有非守护线程(即普通线程)终止时,即使有守护线程仍在运行,JVM也会退出。守护线程主要用于执行辅助性后台任务,如监控、清理、资源回收(如Java垃圾回收线程)、日志记录、定时任务等。

    1. 生命周期关联

      • 守护线程:在所有非守护线程结束后,JVM会退出,即使守护线程仍在运行。
      • 普通线程(用户线程):只要有一个普通线程仍在运行,JVM会继续运行,直到所有普通线程结束或通过 System.exit() 显式退出。
    2. 功能定位

      • 守护线程:服务于其他线程,执行辅助性后台任务,非程序运行必需。
      • 普通线程:承载核心业务逻辑,直接关乎程序运行流程和输出结果。

    13. 什么是Java的序列化

    Java的序列化是指将Java对象转换为字节流的过程,可以将这些字节流保存到文件中或通过网络传输。反序列化则是指将字节流恢复成对象的过程。

    在Java中,要使一个类可序列化,需要满足以下条件:

    1. 实现java.io.Serializable接口,该接口是一个标记接口,没有任何方法。
    2. 所有的非静态、非瞬态的字段都可以被序列化。

    总结:序列化的主要目的是实现对象的持久化存储和传输

    14. 说说你对内部类的理解

    1. 成员内部类:定义在一个类的内部,并且不是静态的。成员内部类可以访问外部类的所有成员,包括私有成员。在创建内部类对象时,需要先创建外部类对象,然后通过外部类对象来创建内部类对象。
    2. 静态内部类:定义在一个类的内部,并且是静态的。与成员内部类不同,静态内部类不能访问外部类的非静态成员,但可以访问外部类的静态成员。在创建静态内部类对象时,不需要先创建外部类对象,可以直接通过类名来创建。
    3. 局部内部类:定义在一个方法或作用域块中的类,它的作用域被限定在方法或作用域块中。局部内部类可以访问外部方法或作用域块中的 final 变量和参数。
    4. 匿名内部类:没有定义名称的内部类,通常用于创建实现某个接口或继承某个类的对象。匿名内部类会在定义时立即创建对象,因此通常用于简单的情况,而不用于复杂的类结构。

    15. 说说你对泛型的理解

    首先,泛型提供了代码重用和通用性。通过使用泛型,我们可以编写可重用的代码,可以在不同的数据类型上执行相同的操作。这样,我们可以避免重复编写类似的代码,提高了开发效率。

    其次,泛型强调类型安全。编译器可以在编译时进行类型检查,阻止不符合类型约束的操作。这样可以避免在运行时出现类型错误的可能,增加了程序的稳定性和可靠性。

    另外,使用泛型可以避免大量的类型转换和强制类型转换操作。在使用泛型集合类时,不需要进行强制类型转换,可以直接获取正确的数据类型,提高了代码的可读性和维护性。

    此外,泛型还可以在编译时进行类型检查,提前发现潜在的类型错误。这种类型检查是在编译时进行的,避免了一些常见的运行时类型异常,减少了错误的可能性。

    最后,泛型可以增加代码的可读性和可维护性。通过使用泛型,我们可以明确指定数据类型,并在代码中表达清晰,使得其他开发人员更容易理解代码的意图和功能。

    16. notify()和 notifyAll()有什么区别

    notify()方法用于唤醒在当前对象上等待的单个线程。如果有多个线程同时在某个对象上等待(通过调用该对象的wait()方法),则只会唤醒其中一个线程,并使其从等待状态变为可运行状态。具体是哪个线程被唤醒是不确定的,取决于线程调度器的实现。

    notifyAll()方法用于唤醒在当前对象上等待的所有线程。如果有多个线程在某个对象上等待,调用notifyAll()方法后,所有等待的线程都会被唤醒并竞争该对象的锁。其中一个线程获得锁后继续执行,其他线程则继续等待。

    17. 深拷贝和浅拷贝区别

    深拷贝通常用于需要完全隔离新旧对象状态的场景,而浅拷贝则适用于不需要或不关心对象内部引用对象状态独立性的场景。在实际编程中,是否需要进行深拷贝应根据具体业务需求和对象结构来决定。

    18. 设计模式是如何分类的

    • 创建型模式是关于对象创建过程的总结,包括单例、工厂、抽象工厂、建造者和原型模式。
    • 结构型模式是针对软件设计结构的总结,包括桥接、适配器、装饰者、代理、组合、外观和享元模式。
    • 行为型模式是从类或对象之间交互、职责划分等角度总结的模式,包括策略、解释器、命令、观察者、迭代器、模板方法和访问者模式。

    19. 什么是值传递和引用传递

    • 值传递是指在函数调用时,将实际参数的值复制一份传递给形式参数,在函数内对形式参数的修改不会影响到实际参数的值。这意味着函数内部对形参的改变不会影响到函数外部的变量。在值传递中,对形参的修改只作用于函数内部。

    • 引用传递是指在函数调用时,将实际参数的引用或地址传递给形式参数,函数内部对形参的修改会影响到实际参数。这意味着函数内部对形参的改变会影响到函数外部的变量。在引用传递中,对形参的修改会直接作用于函数外部的变量。

    20. BIO、NIO、AIO有什么区别

    1. 阻塞与非阻塞:
      • BIO是阻塞式I/O模型,线程会一直被阻塞等待操作完成。
      • NIO是非阻塞式I/O模型,线程可以去做其他任务,当I/O操作完成时得到通知。
      • AIO也是非阻塞式I/O模型,不需要用户线程关注I/O事件,由操作系统通过回调机制处理。
    1. 缓冲区:
      • BIO使用传统的字节流和字符流,需要为输入输出流分别创建缓冲区。
      • NIO引入了基于通道和缓冲区的I/O方式,使用一个缓冲区完成数据读写操作。
      • AIO则不需要缓冲区,使用异步回调方式进行操作。
    1. 线程模型:
      • BIO采用一个线程处理一个请求方式,面对高并发时线程数量急剧增加,容易导致系统崩溃。
      • NIO采用多路复用器来监听多个客户端请求,使用一个线程处理,减少线程数量,提高系统性能。
      • AIO依靠操作系统完成I/O操作,不需要额外的线程池或多路复用器。

     今年环境越来越难了, 准备了一些面试题分享一下,持续更新中!

  • 相关阅读:
    视野修炼-技术周刊第51期
    SpringBoot项目打包成jar后,使用ClassPathResource获取classpath(resource)下文件失败
    JavaScript基础(13)_原型、原型对象
    影视广告创意与制作(三)
    Docker中快速安装RabbitMQ
    【大魔王送书第二期】搞懂大模型的智能基因,RLHF系统设计关键问答
    docker 学习
    【可扩展性】谷歌可扩展和弹性应用的模式
    【测试功能篇 01】Jmeter 压测接口最大并发量、吞吐量、TPS
    【ArcGIS模型构建器】04:根据矢量范围批量裁剪影像栅格数据
  • 原文地址:https://blog.csdn.net/luChenH/article/details/137937782