2024最新Java面试题——java基础

1. 如何理解OOP面向对象编程


       在面向对象编程中,程序被组织成一系列互相关联和相互作用的对象,每个对象都有自己的属性和方法。对象之间通过消息传递的方式进行交互,通过定义类和实例化对象来创建具体的对象。

       面向对象是一种编程思想,也是一种编程模式,将现实中的实物抽象成对象,因此,也有人将抽象定义为面向对象编程的第四大特点。

        面向对象编程的主要特点包括:

  • 封装
           封装是将数据和操作数据的方法封装在一个对象内部,隐藏内部实现细节,只对外提供公共接口。这样可以保护数据的完整性和安全性,并且提高代码的模块化和可维护性。
  • 继承
           继承允许一个类(子类)继承另一个类(父类)的属性和方法。子类可以重用父类代码,并且可以通过过添加新的特性或者修改已有特性来扩展或定制父类的功能。
  • 多态
           多态是指同一个方法可以根据不同的对象类型表现出不同的行为。多态使得代码更加灵活,可以处理不同类型的对象,并且可以在运行时多态确定对象的实际类型。


       通过面向对象编程,开发人员可以更好地模拟现实世界的问题领域,将复杂的问题分解为简单的对象,并通过对象之间的交互来解决问题。面向对象编程提供了一种结构化和模块化的方法,是的代码更易于维护。重用和扩展。
常见的面向对象编程语言包括Java,C++,Python等。

2. JVM、JRE、JDK的区别

  • JVM:java虚拟机,负责将Java字节码解释编程成特定平台的机器码来执行。
  • JRE:java运行环境,由JVM和运行java程序所必需的内库等组成。
  • JDK:java开发工具包。由JRE和开发java程序所需的命令等组成。

3. 面向对象和面向过程的区别

       面向对象编程(OOP) 和面向过程编程是两种不同的编程范式。主要区别是:

  • 核心思想
    • 面向对象编程:将程序中的数据和方法组织在一起,形成”对象“,并通过对象之间的互相通信与协作来完成任务,强调的是数据和行为的封装。
    • 面向过程编程:将问题划分为一系列的步骤或过程,通过调用函数来依次执行这些步骤来解决问题。强调的是算法和逻辑的执行流程。
  • 代码结构
    • 面向对象编程:程序被看做一组对象的集合,每个对象包含数据和方法,对象之间通过消息传递进行通信,可以实现数据的封装、继承和多态。
    • 面向过程编程:程序被看做一系列的函数或者过程的集合,数据和函数是分离的,函数通过参数传递数据进行处理。
  • 重用性
    • 面向对象编程:支持代码的重用行,通过类和对象的继承关系可以实现代码的复用,同时也能提高代码的可维护性和扩展性。
    • 面向过程编程:代码重用主要依靠函数的复用,缺乏类和对象的概念,因此在复杂项目中可能会导致代码的冗余和难以维护。
  • 维护性
    • 面向对象编程:由于具有良好的封装性和模块化特性,使得程序结构清晰,易于维护和修改。
    • 面向过程编程:较为直接的线性执行方式,可能随着程序规模的增大而变得难以维护。
  • 使用场景
    • 面向对象编程:适用于大型,复杂的软件系统开发,能更高的应对需求变化和扩展。
    • 面向过程编程:适用于简单,小规模的程序开发,以及对性能要求较高的场景。
      总的来说,面向对象编程更加注重数据的封装和抽象,适合于大型软件系统的开发。而面向过程编程更加注重算法和流程的执行,适合于较为简单和直接的问题求解。

4. 重载与重写的区别

  • 重载(overload)指的是在同一个类中,可以有多个方法拥有相同的名称,但是他们的参数列表不同,编译器会区分这些方法,并且根据调用时传入的数据类型和数量来确定具体调用哪一个方法。重载能够使同一个方法名进行不同的操作,提高了代码的灵活性和可读性。

  • 重写(override)指的是子类重新定义了父类中已有的方法,使得子类的方法覆盖了父类中的方法。在重写中,方法名、返回类型和参数列表必须与父类中的方法完全相同,这样才能实现方法的重写。重写允许子类根据自身的需要对继承而来的方法进行修改,实现了多态性的特性。

5. 接口与抽象类的异同

  • 相同点
    1. 都是用于实现多态性和代码重用的机制。
    2. 都可以包含抽象方法,需要子类提供具体实现。
    3. 都不能被实例化,只能被其它类继承或实现。
  • 不同点

    1. 定义方式不同
    • 接口只能包含抽象方法、常量和默认方法,不能包含字段和构造函数。
    • 抽象类可以包含抽象方法、具体方法、字段和构造函数。

    1. 继承方式不同
    • 一个类可以实现多个接口,但只能继承一个抽象类。
    • 接口之间可以相互继承,形成接口的继承链,而抽象类只能单继承。

    1. 目的不同
    • 接口用于定义类之间的契约,描述了类应该具有的行为,强调接口的实现。
    • 抽象类用于捕捉子类之间的通用行为,提供一种模板或者结构,强调代码的复用性和扩展性。

    1. 设计思想不同
    • 接口强调“做什么”而不关心“怎么做”,侧重于约定规范和行为。
    • 抽象类强调子类之间的共性,并提供默认的实现,侧重于代码的重用和扩展。

6. 深拷贝与浅拷贝的理解

       深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是编程中用于复制对象或数据结构的两种不同方式。主要区别是:

  • 浅拷贝
    • 浅拷贝是指在复制过程中,只复制对象本身(对象地址),而不复制对象内部的引用类型数据。
    • 在浅拷贝中,新对象和原对象共享相同的内存地址,因此修改新对象可能会影响原对象,反之亦然。
    • 浅拷贝通常是通过复制对象的字段值或引用来实现的,如使用赋值操作符或copy()方法。
  • 深拷贝
    • 深拷贝是指在复制过程中,不仅复制对象本身,还递归复制对象内部的所有引用类型数据,确保新对象与原对象完全独立,互不影响。
    • 在深拷贝中,新对象和原对象拥有不同的内存地址,修改一个对象不会影响另一个对象。
    • 深拷贝通常需要遍历对象的所有属性,对每个引用类型属性进行独立复制,以确保所有层级的引用关系都被复制。
      在实际编程中,选择深拷贝还是浅拷贝取决于需求:
  • 如果对象的内部包含了可变对象,并且需要保持原对象和新对象的独立性,避免相互影响,应该使用深拷贝。
  • 如果对象的内部没有可变对象,或者共享内部对象并不会造成问题,可以使用浅拷贝来提高效率。

Tip:深拷贝可能会比浅拷贝消耗更多的时间和资源,特别是当对象结构较为复杂时。因此,在选择拷贝方式时,需要综合考虑程序的性能和需求,选择最合适的方式。

7. "=="和equals的区别

       "=="和equals是用于比较两个对象的不同方式。主要区别在于:

  • "=="操作符
    • "=="操作符用于比较两个对象的引用是否指向同一内存地址,即比较对象的引用是否相同。
    • 当使用"=="来比较基本数据类型时,比较的是它们的值;当比较对象时,比较的是它们在内存中的存储地址。
  • "equals"方法
    • "equals"方法是Object类中定义的方法,用于比较两个对象的内容是否相等。
    • 在Object类中,"equals"方法默认行为是和"=="操作符相同,即比较对象的引用是否相同。
    • 但是,很多类(String, Integer, ...)会重写”equals“方法,以便比较对象的内容是否相等。

8. sleep和wait的异同

        sleep() 和 wait() 是用于线程控制的两个方法。

  • 相同点
    1. 都是用于线程控制,可以使线程进入暂停状态。
    2. 在调用这两个方法时,都会暂停当前线程的执行。

  • 不同点
    1. 来源不同
      • sleep() 方法来自Thread类,是静态方法。
      • wait() 方法来自Object类,需要在同步代码块或同步方法中调用。
    2. 使用场景不同
      • sleep() 方法通常用于让线程休眠一段时间,在等待一定时间后自动恢复执行,他不会释放对象的锁。
      • wait() 方法通常用于线程间的协作和通信,当调用wait() 方法时,当前线程会释放对象的锁,进入等待状态,知道另一个线程调用notify() 或notifyAll() 方法唤醒该线程。
    3. 锁的释放情况不同
      • sleep() 方法在休眠期间不会释放对象的锁,其他线程无法获得对象的监视器。
      • wait() 方法在等待期间会释放对象的锁,允许其他线程获得该对象的监视器并执行。
    4. 唤醒方式不同
      • sleep() 方法只能通过指定的时间间隔或者被中断才能唤醒。
      • wait() 方法在等待期间需要等待其他线程调用notify() 或notifyAll() 方法来唤醒。
               总体来说,sleep() 主要用于线程的休眠和定时任务的处理,而wait() 主要用于多线程之间的协调和通信。

9. 什么是自动拆装箱?int和Integer有什么区别?

  • 自动拆装箱是java的一种特性,它允许基本数据类型和其对应的包装类之间进行自动切换,从而使得在需要使用包装类的地方可以直接使用基本数据类型,反之亦然。这样可以简化代码,使得代码更加易读和便于维护。
    int和Integer之间的区别:

    1. int是Java的基本数据类型,表示整数,占用4字节的存储空间,可以直接存储数值。
    2. Integer是int的包装类,属于Java中的引用数据类型,用于将基本数据类型包装成对象,从而可以在需要对象的地方是使用。Integer对象是不可变的,即一旦创建不能被修改。
    3. 主要区别包括:
    • 存储方式:int存储的是具体的数值,而Integer存储的是对象的引用。
    • 使用方式:int是基本数据类型,直接存储数值;而Integer是对象,需要通过构造函数或自动装箱才能创建。
    • null值:int不支持null值,而Integer可以赋值为null。
    • 性能:基本数据类型int的性能通常比Integer对象要高,因为不涉及对象的创建和内存管理。
  • 在一般情况下,优先使用基本数据类型,只有在需要使用对象的场景下才考虑到使用包装类。使用自动拆装箱时要注意潜在的性能影响,避免不必要的拆装箱操作。

  • Java集合框架只能存储对象类型,不能直接存储基本数据类型。通过自动拆装箱功能,可以方便的在集合中存储基本数据类型的值,而无需手动进行转换。

10. 序列化和反序列化

序列化和反序列化时Java中对象持久化的概念,他们允许将对象转换成字节序列的方式以便存储和传输,以及将字节序列重新转化为对象。

  • 序列化
    1. 序列化(Serialization) 是指将对象的状态转换为字节序列的过程。在Java中实现了 Serializable 接口的类的对象才能被序列化。
    2. 通过使用 ObjectOutputStream 类,可以将一个实现了 Sreializable 接口的对象转换为字节序列,并输出到文件、数据库或网络流中。
      实现代码如下:
import java.io.*;

public class SerializationExample{
    public static void main(String[] args){
        //创建一个对象
        Student student = new Student("Alice", 20);
        
        try{
            //创建一个ObjectOutputStream类序列化对象并写入文件
            FileOutputStream fileOut = new FileOutputStream("student.ser");
            ObjectOutputStream out = new ObjectOutputStream(fileOut);
            
            //序列化对象
            out.writeObject(sytudent);
            
            //关闭源
            out.close();
            fileOut.close();
            
            System.out.println("对象已经被序列化并写入文件中");
        }catch(IOException e){
            e.printStackTrace();
                        }
    }
}
  • 反序列化
    1. 反序列化(Deserialization) 是指将字节序列转换为对象的过程。在Java中,使用 ObiectInputStream 类可以实现反序列化操作。
    2. 通过 ObjectInputStreamreadObject() 方法,可以从文件、数据库或网络流中读取字节序列,并将其转换为原始的对象。

11. Java中的修饰词有哪些

             在Java中,修饰词用来修饰类、方法、变量等。常见修饰词包括:

  • public: 表示该类、方法或变量时公共的,可以被其它类访问。
  • private: 表示该类、方法或变量是私有的,只能在当前类内部访问。
  • protected: 表示该类、方法或变量对同一包内的类和所有子类可见。
  • static: 表示静态的,可以用来修饰方法、变量和代码块。
  • final: 表示最终的,可以用来修饰类、方法和变量,表示不可更改。
  • abstract: 表示抽象的,用来修饰类和方法,表示不能实例化或需要子类实现。
  • synchronized: 表示同步的,用来修饰方法,确保在多线程环境下方法的安全性。
  • volatile: 表示易变的,用来修饰变量,确保多线程环境下变量的可见性。
  • transient: 表示瞬时的,用来修饰变量,表示不参与序列化。
  • native: 表示本地的,用来修饰方法,表示该方法的实现由本地代码提供。

12. String能被继承吗?为什么用final修饰?

  • 在Java中,String类是一个final类,因此不能被继承。当一个类被声明为final时,意味着他不能有子类,不能被其它类所继承。
  • String类被设计为不可变类,即一旦创建了String对象,其内容就不能被修改。这是通过使用final关键字来修饰String类的方法实现的。通过将String类声明为final,Java确保了String类的不可变性,既避免了String类被子类修改或破坏其不可变特性。
  • 为了确保字符串的安全性和稳定性,将String类声明为final,这样在程序中使用String类不必担心其被子类修改导致的潜在问题。

13. StringBuffer 和 StringBuilder 的区别

       StringBuffer 和 StringBuilder 都是用于处理字符串的类,他们之间的主要区别在于线程安全性和性能。

  • StringBuffer
    1. 线程安全性:StringBuffer 是线程安全的,所有其方法都是同步的,多个线程可以安全的同时访问一个 StringBuffer 对象而不会出现数据混论。
    2. 性能:由于其所有方法都是同步的,这也导致 StringBuffer 的性能相对较低。在多线程环境下,由于需要进行同步处理,可能会导致一些性能开销。
  • StringBuilder
    1. 线程安全性:StringBulider 线程不安全,它的方法并不是同步的,因此在单线程环境下使用StringBuilder更为高级。
    2. 性能:由于没有同步开销,StringBuilder 的性能通常优于 StringBuilder。因此,在单线程环境下,推荐使用 StringBuilder 来处理字符串操作。

14. final, finally, finalize的区别

  • final:用来修饰类、方法和变量。当用final修饰一个类时,表示该类不能被继承;当修饰一个方法时,表示该方法不能被子类重写;当修饰一个变量时,表示该变量是常量,其数值在初始化后不能被改变。
  • finally: 通常用于异常处理的 try-catch-finally 语句块中,无论是否发生异常,finally 块中的代码都会被执行。一般情况下,finally 块用来释放资源,如关闭文件或释放网络连接等操作。
  • finalize: finalize() 是Object类中的一个方法,在Java中的所有类都继承自Object类,因此都可以重写finalize() 方法。finalize() 方法在对象被垃圾回收之前被调用,可以在该方法中进行一些清理工作,如释放非Java资源(文件句柄、数据库连接等)。但由于Java的垃圾回收机制的不确定性,finalize() 方法并不适合用来进行资源释放,推荐使用 try-with-resources 或其他方式来确保资源的及时释放。

15. Object类中有哪些方法?

       常用方法如下:

  • equals(Object obj): 判断当前对象是否与给定对象 obj 相等;
  • hashCode(): 返回当前对象的哈希码值;
  • toString(): 返回当前对象的字符串表示;
  • getClass(): 返回当前对象的运行时类。
  • clone(): 创建并返回当前对象的副本(浅拷贝)。
  • finalize(): 在对象被垃圾回收之前调用,用于进行资源清理等操作。
  • notify(): 唤醒在当前对象上等待的单个线程。
  • notifyAll(): 唤醒在当前对象上等待的所有线程。
  • wait(): 导致当前线程等待,直到另一个线程调用 notify() 或 notifyAll() 方法时才能被唤醒。
  • wait(long timeout): 导致当前线程等待指定的毫秒数,直到另一个线程调用 notify() 或 notifyAll() 方法时才能被唤醒。
  • wait(long timeout, int nanos): 导致当前线程等待指定的毫秒数和纳米数,直到另一个线程调用 notify() 或 notifyAll() 方法时才能被唤醒。
    这些常用的Object类中的方法,其他类可以通过过继承Object类来继承这些方法,也可以重写这些方法满足需求。

16. Java异常处理机制

              异常处理机制主要通过 try-catch-finally 块来实现。基本流程框架如下:

  • 抛出异常:当程序运行发生异常状况时,可以使用 throw 关键字手动抛出一个异常对象。
  • 捕获异常:使用 try-catch 块来捕获可能抛出的异常。在 try 块中放置可能会引发异常的代码,然后在 catch 块中处理异常情况。
  • 处理异常:在 catch 块中可以编写处理异常的代码,比如记录日志、提示用户等。
  • finally块:finally 块中的代码总是被执行,不管是否发生异常。通常用于释放资源或者执行清理操作。
  • 自定义异常:除了Java提供的异常处理机制,也可以根据需求自定义异常类来表示特定的异常情况。
  • 异常处理机制能帮助开发人员更好的管理和处理程序中可能出现的异常情况,保证程序的稳定性和可靠性。

17. throw和throws的区别

  • throw
    • throw用于手动抛出异常。当程序执行到throw语句时,会立即抛出一个异常,并终止当前方法的执行。
    • 使用throw关键字可以在代码中显式地抛出异常,例如在检测到不符合预期的条件时,可以通过throw抛出一个适合的异常对象。
  • throws
    • throws关键字用于声明方法可能抛出的异常类型。当方法可能抛出异常时,可以使用throws在方法声明中列出可能抛出的异常类型,通知调用方法需要处理这些异常。
    • 调用方可以选择捕获方法可能抛出的一场,也可以将异常继续向上抛出。
posted @ 2024-05-22 20:08  墨尘儿  阅读(42)  评论(0编辑  收藏  举报