Java面试题

Java面试题

文章目录

1、抽象类和接口的区别

抽象类和接口都是面向对象编程的概念,用于实现多态性和组织代码。它们的主要区别在于:

  1. 定义方式
    • 抽象类:用 abstract 声明,可包含抽象方法和实例成员。
    • 接口:用 interface 声明,仅包含抽象方法和常量。
  2. 继承和实现
    • 抽象类:单继承,可提供部分实现。
    • 接口:多实现,提供方法契约。
  3. 构造方法
    • 抽象类:可有构造方法。
    • 接口:无构造方法。
  4. 成员类型
    • 抽象类:实例成员、抽象方法、具体方法。
    • 接口:仅抽象方法和常量。
  5. 设计目的
    • 抽象类:共享代码和行为。
    • 接口:定义契约、强调协作。

2、重载和重写的区别

重载(Overloading)和重写(Overriding)是面向对象编程中两个重要的概念,它们涉及方法的多态性和继承。

  1. 重载(Overloading)

    • 重载指的是在同一个类中定义多个方法,它们具有相同的名称但参数列表不同(参数个数、类型或顺序)。重载方法可以有不同的返回类型,但不能仅仅通过返回类型的不同来区分。重载方法在编译时会根据传入的参数选择匹配的方法进行调用。
  2. 重写(Overriding)

    • 重写是指在子类中实现一个与父类中具有相同名称、参数列表和返回类型的方法。重写方法必须有相同的方法签名。重写方法允许子类提供自己的实现,从而覆盖父类的方法。重写用于实现运行时多态性,即通过基类引用调用子类方法。

3、==和equals的区别

  1. == 操作符

    • == 操作符用于比较两个对象的引用是否相同,即它们是否指向同一个内存地址。在基本数据类型(如 intfloat)的比较中,== 比较它们的值是否相等。
  2. equals 方法

    • equals 方法是在 Object 类中定义的,它的默认行为是比较两个对象的引用是否相同,即与 == 的效果一样。然而,很多类会覆盖(override)equals 方法,以实现自己的对象比较逻辑。例如,StringInteger 等类都重写了 equals 方法,实现了基于内容的对象比较。

总结:

  • == 比较对象比较的是对象的引用(内存地址)是否相同,比较基本数据类型比较的是值是否相等。
  • equals 方法通常比较对象内容是否相同,可以根据自己的逻辑进行重写。
  • 自定义类的 equals 方法重写时,一般需要同时重写 hashCode 方法。

4、讲一讲什么是map、set、list

Map(映射)

  • Map 是一种键值对的集合,每个键唯一地对应一个值。它通过键来快速查找对应的值,类似于字典或电话簿。在 Java 中,常见的 Map 实现类有 HashMap、LinkedHashMap、TreeMap 等。Map 是通过键来索引值,因此它可以用于建立键到值的映射关系,例如存储学生的学号和姓名。

Set(集合)

  • Set 是一种不允许重复元素的集合,它可以用于存储一组唯一的元素。Set 不关心元素的顺序,只关心元素的唯一性。在 Java 中,常见的 Set 实现类有 HashSet、LinkedHashSet、TreeSet 等。Set 可以用于存储需要去重的数据,例如存储用户的唯一标识。

List(列表)

  • List 是一种有序的集合,允许存储重复元素。它通过索引来访问元素,类似于数组。在 Java 中,常见的 List 实现类有 ArrayList、LinkedList、Vector 等。List 可以用于存储一组有序的数据,例如存储待办事项列表。

总结时,强调:

  • Map 是键值对的集合,通过唯一的键来查找值。
  • Set 是不允许重复元素的集合,用于存储唯一的元素。
  • List 是有序集合,可以存储重复元素,通过索引来访问元素。

5、HashMap原理

  1. 存储方式: HashMap 以键值对(key-value)的形式存储数据。每个键对应一个值,可以通过键快速查找对应的值。
  2. 哈希表结构: HashMap 内部使用一个数组来存储数据,每个数组元素称为 “桶”(bucket)。每个桶可以存储一个或多个键值对。当添加一个键值对时,HashMap 会通过哈希函数计算键的哈希码,然后将键值对存放到对应的桶中。
  3. 哈希冲突: 不同的键可能会产生相同的哈希码,这就是哈希冲突。HashMap 使用数组+链表(Java 8 之前)或数组+红黑树(Java 8 及以后)来解决哈希冲突问题。当多个键映射到同一个桶时,它们会存储在链表或红黑树中,以便快速查找和插入。
  4. 哈希函数: 哈希函数是用于将键映射到桶的索引位置。HashMap 使用键的哈希码与桶数组长度取模来计算桶的索引。合适的哈希函数可以减少冲突,提高性能。
  5. 扩容和负载因子: 当存储的键值对数量超过一定阈值(负载因子)时,HashMap 会自动进行扩容,即创建一个更大的桶数组,并将原有的键值对重新分配到新的桶中。这可以保持哈希表的均匀分布,提高性能。
  6. 性能: HashMap 提供了常数时间复杂度的增、删、查操作(平均情况下),但在极端情况下,如哈希冲突严重时,性能可能会降低。通过合理选择哈希函数和负载因子,可以减少冲突,提高性能。

总结时,强调 HashMap 的基本原理是使用哈希表来实现键值对的存储,涉及哈希函数、解决冲突的机制以及自动扩容等。这些特性使得 HashMap 成为一种高效的数据结构,适用于快速查找和插入数据。

6、什么是 JSP?它与 Servlet 有什么区别

JSP(JavaServer Pages)是一种在 Java Web 应用中生成动态内容的技术。它允许我们在 HTML 页面中嵌入 Java 代码,以便在服务器端生成动态内容。与 Servlet 相比,JSP 更适合用于生成页面内容,因为我们可以在页面中直接编写 HTML,而不需要在 Java 代码中嵌入大量的 HTML。

区别在于

  • Servlet 是以 Java 代码为主,我们需要在 Java 类中编写 HTML 和逻辑代码。而 JSP 允许我们在 HTML 页面中插入 Java 代码,更适合于页面生成。
  • JSP 最终会被编译成 Servlet 类,然后由容器执行。Servlet 是独立的 Java 类,不需要和 HTML 混合。

7、synchronized 关键字

synchronized 是 Java 中的一个关键字,用于实现线程的同步和互斥,以确保多个线程在访问共享资源时的安全性。它可以应用于方法或代码块,具体范围涵盖以下几个方面:

  1. 方法级别的同步: 当你将 synchronized 关键字应用于一个方法时,这个方法就被称为同步方法。这意味着一次只能有一个线程可以执行这个方法,其他线程需要等待直到这个方法执行完成。同步方法适用于需要保证整个方法的执行过程是原子性的情况。
  2. 代码块级别的同步: 你还可以使用 synchronized 来创建同步代码块。在同步代码块中,只有一个线程可以进入同步块内部,执行其中的代码,其他线程需要等待。同步代码块的作用是限制对指定的共享资源的访问,以保证多个线程之间不会产生并发问题。
  3. 对象锁和类锁: 在方法级别的同步中,当你将 synchronized 关键字应用于实例方法时,它使用的是对象锁(也称为实例锁)。这意味着同一对象的不同实例可以并发执行该方法。如果将 synchronized 应用于静态方法,它使用的是类锁,确保同一类的不同实例之间的同步。
  4. 避免竞态条件: 竞态条件是多线程环境下出现的并发问题,可能导致数据不一致性和程序的不可预测行为。使用 synchronized 可以有效避免竞态条件,确保在同一时间只有一个线程可以访问共享资源。
  5. 互斥性和可见性: synchronized 不仅提供了互斥性,即保证同一时间只有一个线程能够访问同步代码块或方法,还提供了可见性,即一个线程修改了共享资源的值,其他线程能够立即看到修改后的值。
  6. 性能考虑: 尽管 synchronized 可以确保线程安全,但过多地使用可能会引入性能问题。因为每次获取和释放锁都会有一定的开销,频繁的锁竞争可能导致性能下降。因此,在设计并发程序时,需要权衡锁的使用以及锁的粒度。

8、HashTable与HashMap的区别

线程安全性

  • HashTable 是线程安全的,内部的操作都是同步的,这意味着多个线程可以同时访问和修改 HashTable 实例,而不会导致数据不一致。然而,这种同步会影响性能。
  • HashMap 默认情况下是非线程安全的。在多线程环境中,如果多个线程同时对 HashMap 进行操作,可能会导致不可预测的结果。如果需要在多线程环境中使用,应该选择 ConcurrentHashMap

性能

  • 由于 HashTable 内部使用同步来保证线程安全,性能通常会比较低下,特别是在高并发情况下。
  • HashMap 在没有同步的情况下性能更好,因为它不会为了线程安全而付出同步开销。但是在多线程环境中,必须采取额外的措施来保证线程安全。

空值(null)

  • HashTable 不允许键或值为 null,任何试图存储 null 值的操作都会抛出异常。
  • HashMap 允许一个键为 null,以及多个值为 null 的情况。

迭代器

  • HashTable 的迭代器是同步的,因此在遍历时不需要额外的同步机制。但迭代器本身也有性能开销。
  • HashMap 的迭代器是不同步的,如果在迭代过程中对 HashMap 进行修改,可能会抛出 ConcurrentModificationException 异常。

总结时,强调 HashTable 是线程安全的、对空值敏感且性能较差的集合类,而 HashMap 是非线程安全、对空值更灵活且性能较好的集合类。在多线程环境下,应该优先考虑使用 ConcurrentHashMap,而在单线程环境下,根据需求选择 HashTableHashMap

9、ArrayList和LinkedList的区别

ArrayList

  1. 内部实现:ArrayList 使用动态数组来存储元素。当元素数量超过当前数组容量时,会自动进行扩容。
  2. 随机访问:由于底层使用数组,ArrayList 支持通过索引来随机访问元素,因此在随机访问时具有较好的性能。
  3. 插入和删除:在列表末尾进行插入和删除操作是高效的,因为只需要调整数组的长度。但在中间插入或删除元素时,需要进行数组元素的移动,可能会导致性能下降。

LinkedList

  1. 内部实现:LinkedList 使用双向链表来存储元素。每个元素都包含指向前一个和后一个元素的引用。
  2. 随机访问:由于需要从头部或尾部开始遍历链表,LinkedList 的随机访问性能较差。
  3. 插入和删除:在链表中插入和删除元素时,由于只需要调整相邻节点的引用,所以在中间插入和删除操作时比 ArrayList 更高效。
  4. 内存占用:由于每个元素都需要存储前后引用,LinkedList 的内存占用通常会比 ArrayList 更高。

总结时,强调:

  • ArrayList 适用于随机访问较多、插入和删除操作较少的场景。
  • LinkedList 适用于频繁的插入和删除操作,但不太适合需要随机访问的场景。

10、Session 和 Cookie 的区别

  1. 存储位置:
    • Cookie:存储在客户端浏览器的小文本文件中。
    • Session:存储在服务器端的内存或持久化存储中。
  2. 存储容量:
    • Cookie:通常每个域名下的浏览器可以存储多个 Cookie,但每个 Cookie 的存储容量有限制(通常为几KB)。
    • Session:在服务器端存储,通常没有存储容量限制,但过多的 Session 数据会占用服务器内存。
  3. 安全性:
    • Cookie:存储在客户端,因此容易受到信息泄露和篡改的风险。可以通过设置 HttpOnlySecure 属性来增加安全性。
    • Session:存储在服务器端,相对来说更安全,但仍然需要注意防止 Session 劫持等风险。
  4. 跨域支持:
    • Cookie:可以设置为跨域共享,但受到同源策略的限制。
    • Session:不易在不同域名之间共享,因为存储在服务器端。
  5. 失效时间:
    • Cookie:可以设置失效时间,可以是会话级(浏览器关闭后失效)或持久性的。
    • Session:通常在用户关闭浏览器或超过一定时间没有活动后失效。
  6. 用途:
    • Cookie:常用于存储少量临时数据,如用户偏好设置、购物车信息等。
    • Session:通常用于存储用户的登录状态、购物车内容、用户数据等。

11、String、StringBuffer、StringBuilder的区别

String

  1. 不可变性String 是不可变的,一旦创建就不能被修改。每次对 String 进行修改操作时,实际上是创建了一个新的 String 对象,旧的对象会被丢弃。这会导致频繁的对象创建和销毁,影响性能。
  2. 线程安全:由于不可变性,String 在多线程环境下是线程安全的。多个线程可以同时共享一个 String 实例,不会出现并发问题。

StringBuffer

  1. 可变性StringBuffer 是可变的,允许修改已经存在的字符序列,而不会创建新的对象。这样可以减少对象的创建和销毁,提高性能。
  2. 线程安全StringBuffer 是线程安全的,内部的操作都是同步的。这意味着多个线程可以同时访问和修改 StringBuffer 实例,而不会导致数据不一致。但这种同步会影响性能。

StringBuilder

  1. 可变性StringBuilder 也是可变的,与 StringBuffer 类似,允许修改已经存在的字符序列,而不会创建新的对象。但与 StringBuffer 不同,StringBuilder 并不是线程安全的。
  2. 线程安全StringBuilder 在多线程环境下是不安全的,不同线程同时访问和修改同一个 StringBuilder 实例可能会导致数据不一致。因此,StringBuilder 更适用于单线程环境下的操作,它不会带来额外的同步开销。

总结时,强调:

  • String 适用于不需要频繁修改的字符串,但会导致对象创建和销毁。
  • StringBuffer 适用于多线程环境下需要频繁修改的字符串。
  • StringBuilder 适用于单线程环境下需要频繁修改的字符串,可以提供更好的性能。

12、hashCode和equals

hashCode

  1. 作用hashCode 是一个方法,用于计算对象的哈希码值。哈希码是一个整数,通常用于在哈希表等数据结构中确定对象在内存中的存储位置,从而加速查找操作。
  2. 特点:相同的对象调用 hashCode 方法应该返回相同的哈希码值,但不同的对象可能会返回相同的哈希码值(哈希冲突)。因此,哈希码不能作为确定对象相等性的绝对标准。
  3. 实现:为了保证哈希码的正确性,如果 equals 方法返回 true,则两个对象的 hashCode 方法应该返回相同的值。但是反过来不一定成立:hashCode 相同并不意味着两个对象一定相等。

equals

  1. 作用equals 是一个方法,用于比较对象的内容是否相等。默认情况下,equals 方法会比较对象的引用是否相等,即是否指向同一个内存地址。
  2. 特点equals 应该满足以下特点:
    • 自反性:x.equals(x) 应该返回 true
    • 对称性:如果 x.equals(y) 返回 true,那么 y.equals(x) 也应该返回 true
    • 传递性:如果 x.equals(y)y.equals(z) 都返回 true,那么 x.equals(z) 也应该返回 true
    • 一致性:多次调用 x.equals(y) 应该返回相同的结果,只要对象没有被修改。
    • null 的处理:x.equals(null) 应该返回 false
  3. 实现:如果重写了 equals 方法,通常也需要重写 hashCode 方法,以确保两个方法的一致性。当两个对象通过 equals 方法比较为相等时,它们的 hashCode 方法应该返回相同的值。

在面试中,强调 hashCode 用于快速查找对象的存储位置,而 equals 用于比较对象的内容是否相等。重写这两个方法时,应该遵循它们的约定和特性。

13、JSP 四种作用域

  1. Page 作用域: 页面作用域是最小的作用域,其数据只在当前 JSP 页面中有效。数据存储在 pageContext 对象中,通过 ${pageScope}${pageScope.variable} 访问。适用于在同一个页面的不同部分共享数据,但不适用于多个请求之间的数据共享。
  2. Request 作用域: 请求作用域的数据在同一个请求中有效,不同的页面间无法共享。数据存储在 request 对象中,通过 ${requestScope}${requestScope.variable} 访问。适用于在同一个请求中的不同页面之间传递数据。
  3. Session 作用域: 会话作用域的数据在用户的整个会话期间有效,即用户在浏览器关闭之前都可以访问。数据存储在 session 对象中,通过 ${sessionScope}${sessionScope.variable} 访问。适用于需要在用户会话期间保持状态的数据,如登录状态、购物车内容等。
  4. Application 作用域: 应用程序作用域的数据在整个应用程序的生命周期内都有效,所有用户都可以访问。数据存储在 application 对象中,通过 ${applicationScope}${applicationScope.variable} 访问。适用于共享应用程序级别的数据,如全局配置信息、计数器等。

14、深拷贝和浅拷贝

浅拷贝

  1. 复制引用:浅拷贝创建一个新对象,然后将原始对象的成员变量的引用复制到新对象中。新旧对象共享同一份数据。
  2. 嵌套对象:如果原始对象中包含其他对象(如数组、集合、对象等),浅拷贝只会复制它们的引用,而不会创建新的嵌套对象。
  3. 修改影响:因为新旧对象共享同一份数据,当一个对象修改了共享的数据时,另一个对象也会受到影响。

深拷贝

  1. 复制数据:深拷贝创建一个新对象,并递归地复制原始对象及其嵌套对象的数据,而不是简单的复制引用。
  2. 独立对象:深拷贝会为新对象及其嵌套对象创建独立的副本,使得新旧对象之间的数据彼此独立。
  3. 修改不影响:因为新旧对象的数据是独立的,一个对象的修改不会影响另一个对象。

15、Bean 的生命周期

  1. 实例化: 首先,Spring 容器根据配置或注解,在内存中创建 Bean 的实例。这个阶段只是创建了一个 Java 对象,还没有进行任何属性的赋值。
  2. 属性赋值: 在实例化后,Spring 容器会根据配置文件或注解,将相应的属性值注入到 Bean 实例中。这可以通过构造函数注入、Setter 方法注入或字段注入来实现。
  3. 初始化: 在属性注入完成后,如果 Bean 类实现了 InitializingBean 接口,或者在配置文件中声明了初始化方法,Spring 将会调用 Bean 的初始化方法。这里可以进行一些初始化操作,例如建立数据库连接、加载资源等。
  4. 使用: 当 Bean 初始化完成后,它可以被其他组件引用,进行实际的业务逻辑处理。
  5. 销毁: 当 Spring 容器关闭时,如果 Bean 类实现了 DisposableBean 接口,或者在配置文件中声明了销毁方法,Spring 将会调用 Bean 的销毁方法。在这里可以进行一些资源的释放和清理工作,例如关闭数据库连接、释放文件资源等。

需要注意的是,虽然 Spring 容器会管理 Bean 的生命周期,但单个 Bean 的具体生命周期可能会因为不同的配置或注解而有所不同。在 Spring 容器启动和关闭过程中,会根据不同的阶段调用相应的方法,使得开发者能够在适当的时机进行初始化和销毁操作。

16、什么是反射

基本概念

  1. 获取类信息:反射允许您在运行时获取类的信息,包括类名、父类、接口、构造函数、方法、字段等。
  2. 操作对象:通过反射,您可以在运行时创建对象、调用对象的方法、获取和设置对象的字段。

优点

  1. 灵活性:反射使您可以在运行时动态地加载和使用类,而不需要在编译时硬编码类名。
  2. 泛化工具:许多框架和工具(如各种注解、依赖注入框架等)使用反射来实现通用和可配置的行为。

缺点

  1. 性能开销:反射通常会比直接调用代码更慢,因为它涉及到动态查找和解析类、方法等信息。
  2. 类型安全:由于反射是动态的,编译器无法在编译时捕获所有可能的错误,可能导致在运行时出现类型错误。

应用场景

  1. 要操作权限不够的类属性和方法时

  2. 实现自定义注解时

  3. 动态加载第三方jar包时

  4. 按需加载类

  5. 节省编译和初始化时间

获取class对象的方法

  • class.forName(类路径)
  • 类.class()
  • 对象的getClass()

17、你平时是怎么使用线程池的

  1. 任务类型和线程池选择:首先,我会根据任务的性质选择合适的线程池类型。例如,对于需要按序执行的任务,我会选择 SingleThreadExecutor,对于大量短期任务,我会选用 CachedThreadPool。如果有固定数量的长期任务,我会选择 FixedThreadPool
  2. 线程池大小设置:我会根据系统的硬件配置、任务的性质以及预期的并发量来调整线程池的大小。如果线程数量过多,可能会导致资源竞争和性能下降,如果线程数量过少,可能会导致任务等待过长时间。
  3. 任务提交:在将任务提交到线程池之前,我会确保任务的粒度适当,不会过大或过小。然后,我会使用线程池的 submitexecute 方法将任务提交到线程池中。
  4. 处理异常:在任务执行过程中,可能会出现异常。我会在任务中捕获异常,然后根据需要进行日志记录、重试或者其他处理。
  5. 任务完成处理:一旦任务完成,线程池会自动将线程放回池中,我会确保在任务执行结束后释放资源,以避免潜在的资源泄漏问题。
  6. 监控与调优:我会通过监控线程池的状态,例如线程池大小、活动线程数、任务队列长度等,来确保线程池的健康运行。如果出现性能问题,我会根据情况考虑调整线程池的参数。
  7. 合理关闭线程池:当我不再需要线程池时,我会调用线程池的 shutdownshutdownNow 方法来合理关闭线程池,确保所有线程能够完成任务并停止。

通过合理的线程池使用,我能够优化系统的资源利用率,提高任务执行效率,并确保多线程编程的稳定性和可维护性。

18、进程和线程的区别,进程间如何通信

  1. 进程和线程的区别

    • 进程:进程是程序的一次执行,是一个独立的执行单元,拥有独立的内存空间和系统资源。不同进程之间相互独立,通信需要使用特定的机制。
    • 线程:线程是进程内的执行单元,多个线程共享同一进程的内存空间和资源。线程之间的切换更加轻量级,但也更容易出现并发问题。
  2. 进程间通信

    进程间通信(Inter-Process Communication,IPC)是指不同进程之间进行数据交换和通信的机制。常见的进程间通信方式包括:

    • 管道和命名管道:用于有亲缘关系的进程间通信,一般是父子进程或兄弟进程。
    • 消息队列:通过消息队列传递消息,可以实现多个进程之间的通信。
    • 共享内存:多个进程共享一段内存区域,从而实现数据共享。
    • 套接字(Socket):适用于不同主机上的进程通信,可以实现跨网络的通信。
    • 信号量和锁:用于控制多个进程对共享资源的访问,以避免竞争条件。
    • RPC(远程过程调用):允许进程在远程执行另一个进程的函数,实现远程通信。

    这些机制都有自己的特点和适用场景,具体选择取决于需要实现的功能和需求。

总结时,您可以强调:

  • 进程是独立的执行单元,线程是进程内的执行单元。
  • 进程间通信是为了实现不同进程之间的数据交换和通信,有多种机制可供选择,取决于需求。

19、什么是线程上下文切换

线程上下文切换是在多线程环境下,CPU 从一个线程切换到另一个线程时所涉及的过程。在这个过程中,操作系统保存当前线程的状态(也称为上下文),然后加载另一个线程的状态,以便让它继续执行。

这个过程涉及到以下几个步骤

  1. 保存当前线程状态:操作系统会保存当前线程的寄存器状态、程序计数器值、堆栈指针等信息,以便在切换回来时能够恢复执行。
  2. 加载新线程状态:操作系统会从另一个线程的上下文中恢复寄存器状态、程序计数器值、堆栈指针等信息。
  3. 切换执行:一旦新线程的状态被加载,CPU 会开始执行新线程的代码,继续运行。

线程上下文切换是一个开销较大的操作,因为涉及到保存和恢复线程状态,而且会消耗一定的处理器时间。在多线程程序中,频繁的上下文切换可能会影响程序的性能,特别是在使用多核 CPU 时。

20、什么是死锁

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值