7天八股速记之Java 后端——Day 1

发布于:2024-04-05 ⋅ 阅读:(151) ⋅ 点赞:(0)

接口和抽象类的区别

接口 抽象类
方法 抽象方法 既可以有抽象方法,也可以有普通方法
关键字修饰 interface abstract
定义常量变量 只能定义静态常量 成员变量
子类方法 所有方法必须实现 实现所有的抽象方法
子类继承 多继承 单继承
构造方法 不能有构造方法 可以有构造方法
接口实现 只能继承接口,不能实现接口 可以实现接口,并且不实现接口中的方法

Java 中的继承和 C++ 有什么不同

Java 中的继承和 C++ 中的继承在概念上是相似的,但在语法和实现细节上有一些不同之处。以下是 Java 中的继承和 C++ 中的继承的主要不同点:

  1. 语法差异

    • 在 Java 中,继承使用关键字 extends,例如:class ChildClass extends ParentClass {...}
    • 在 C++ 中,继承使用关键字 :,例如:class ChildClass : public ParentClass {...}
  2. 默认访问控制

    • 在 Java 中,如果子类没有显式指定访问修饰符,子类只能继承父类的 publicprotected 成员,而不能继承 private 成员。
    • 在 C++ 中,默认情况下子类继承父类的所有成员,无论是 publicprotected 还是 private
  3. 多继承

    • Java 不支持多继承,一个类只能继承自一个父类。
    • C++ 支持多继承,一个类可以同时继承自多个父类。
  4. 虚函数和方法重写

    • 在 Java 中,所有的成员函数默认都是虚函数(即可以被子类重写),如果不希望子类重写,可以使用 final 关键字修饰。
    • 在 C++ 中,默认情况下成员函数不是虚函数,需要使用 virtual 关键字声明虚函数,而且在子类中重写父类的虚函数时,不需要显式添加 virtual 关键字。
  5. 构造函数和析构函数

    • 在 Java 中,子类的构造函数必须调用父类的构造函数,可以使用 super() 关键字来调用父类的构造函数。
    • 在 C++ 中,子类的构造函数可以显式调用父类的构造函数,也可以省略,如果省略,则默认调用父类的默认构造函数;析构函数也类似。

总的来说,Java 中的继承和 C++ 中的继承在语法和部分实现细节上有所不同,但在概念上都是为了实现代码的重用性和扩展性而设计的。

Java 中有哪些数据结构?用过 HashMap 吗,说一下 HashMap 底层实现

Java 中常用的数据结构包括:

  1. 数组(Array):一组按顺序存储的相同类型元素的集合,通过索引访问元素。

  2. 链表(Linked List):一种线性表数据结构,由节点组成,每个节点包含数据元素和指向下一个节点的引用。

  3. 栈(Stack):一种后进先出(LIFO)的数据结构,只能在栈顶进行插入和删除操作。

  4. 队列(Queue):一种先进先出(FIFO)的数据结构,支持在队尾插入元素,在队头删除元素。

  5. 堆(Heap):一种特殊的树形数据结构,通常用于实现优先队列。

  6. 树(Tree):一种层次结构的数据结构,包括二叉树、平衡二叉树、红黑树等。

  7. 图(Graph):一种非线性的数据结构,由节点(顶点)和边组成,用于描述各种复杂关系。

  8. 哈希表(Hash Table):一种通过哈希函数将键映射到值的数据结构,实现了键值对的存储和快速检索。

HashMap 是 Java 中常用的哈希表实现之一,其底层实现主要包括数组和链表(或红黑树)。HashMap 的工作原理是将键值对映射到数组的一个位置上,通过哈希函数计算键的哈希码,然后将键值对存储在数组中对应位置的链表(或红黑树)中。当发生哈希冲突时,即多个键映射到了同一个数组位置上,HashMap 使用链表(或红黑树)来解决冲突,将冲突的键值对按顺序存储在链表中。在 JDK 8 中,当链表长度超过一定阈值时,链表会转换成红黑树,以提高查找、插入和删除操作的效率。

HashMap 在 JDK 8 中引入了一种称为“拉链法”的解决冲突方法,通过数组和链表(或红黑树)结合的方式来提高插入、查找和删除操作的性能,是 Java 中最常用的数据结构之一。

Java 中用的是值传递还是引用传递?

在 Java 中,参数传递采用的是值传递(pass by value)。

在 Java 中,所有的参数传递都是将参数的值复制一份传递给方法的参数,而不是传递参数的引用。具体来说:

  1. 对于基本数据类型:例如整型、浮点型、布尔型等,传递的是值本身的副本,对方法内部的参数进行修改不会影响到原始变量的值。

  2. 对于引用类型:例如对象、数组等,传递的是引用的副本,即传递的是对象的地址或引用,而不是对象本身。这意味着,虽然在方法内部可以修改对象的状态,但对于对象的重新赋值或修改引用并不会影响到原始对象。

举个例子:

public class Main {
    public static void main(String[] args) {
        int x = 10;
        changeValue(x);
        System.out.println("x after changeValue: " + x); // 输出:x after changeValue: 10
        
        StringBuilder sb = new StringBuilder("Hello");
        changeReference(sb);
        System.out.println("sb after changeReference: " + sb.toString()); // 输出:sb after changeReference: Hello World
    }
    
    public static void changeValue(int x) {
        x = 20; // 修改方法参数的值
    }
    
    public static void changeReference(StringBuilder sb) {
        sb.append(" World"); // 修改方法参数的引用对象
    }
}

在上述示例中,尽管在 changeValue 方法中修改了参数 x 的值为 20,但在 main 方法中输出 x 的值仍然是 10。而在 changeReference 方法中修改了 StringBuilder 对象的内容,main 方法中输出的结果确实被修改了。这反映了 Java 中参数传递采用的是值传递,但对于引用类型,传递的是引用的副本。

面向过程和面向对象有什么区别?

面向过程编程(Procedural Programming)和面向对象编程(Object-Oriented Programming,OOP)是两种不同的编程范式,它们有以下主要区别:

  1. 核心思想

    • 面向过程编程:将问题划分为一系列的步骤,然后使用函数来实现每个步骤,强调的是过程和行为。
    • 面向对象编程:将问题划分为一组对象,每个对象包含数据和方法,强调的是对象和数据。
  2. 编程单位

    • 面向过程编程以过程或函数作为编程的基本单位,程序由一系列函数组成,函数之间通过参数传递数据。
    • 面向对象编程以对象作为编程的基本单位,程序由一组对象组成,对象之间通过消息传递来进行通信。
  3. 数据和行为的组织方式

    • 面向过程编程将数据和行为分开,数据和函数是分离的,数据可以被多个函数共享。
    • 面向对象编程将数据和行为封装在对象中,对象是数据和行为的集合,数据和方法是紧密相关的,对象对外部隐藏了内部实现细节。
  4. 继承和多态

    • 面向对象编程支持继承和多态的特性,通过继承可以实现代码的重用和扩展,通过多态可以实现代码的灵活性和可扩展性。
    • 面向过程编程通常不支持继承和多态,代码的复用和扩展主要依靠函数的调用和组合。
  5. 适用场景

    • 面向过程编程适用于简单的、线性的问题,适合于需要执行一系列步骤的程序。
    • 面向对象编程适用于复杂的、交互式的系统,适合于需要对数据和行为进行抽象和建模的程序。

总的来说,面向过程编程和面向对象编程是两种不同的编程思想和方法,各有其优缺点和适用场景。面向对象编程通过封装、继承和多态等特性提供了更加灵活、可扩展和易维护的编程方式,已经成为了主流的编程范式。

final、finally、finalize 的区别?

finalfinallyfinalize 是 Java 中的三个不同的关键字,它们在语法和用途上有着不同的含义和作用:

  1. final

    • final 是 Java 中的关键字,用于修饰类、方法和变量,表示不可改变的意思。
    • 当用 final 修饰一个类时,表示该类是一个不可继承的最终类,不能被其他类继承。
    • 当用 final 修饰一个方法时,表示该方法是一个最终方法,子类不能重写该方法。
    • 当用 final 修饰一个变量时,表示该变量是一个常量,一旦被赋值后就不能再改变其值。
  2. finally

    • finally 是 Java 中的关键字,用于定义在 try-catch 结构中的代码块,在 trycatch 中的代码执行完毕后,无论是否发生异常,finally 中的代码块都会被执行。
    • finally 常用于资源的释放,例如关闭文件、关闭数据库连接等,确保资源被正确释放,避免资源泄漏。
  3. finalize

    • finalize 是 Object 类中的一个方法,用于在对象被垃圾回收器回收之前进行资源释放和清理工作。
    • 在 Java 中,垃圾回收器负责回收不再使用的对象,当对象被标记为垃圾时,垃圾回收器会调用其 finalize 方法,进行一些必要的清理操作。
    • finalize 方法的执行时间是不确定的,并不能保证对象什么时候被垃圾回收器回收,因此不建议过度依赖 finalize 方法进行资源释放,而应该显式地使用 finally 块或者 try-with-resources 语句来进行资源的释放。

什么是序列化?什么是反序列化?

序列化(Serialization)是指将对象转换为字节流的过程,以便将其存储到文件中、通过网络进行传输,或者将其保存在内存中。序列化的过程将对象的状态转换为字节序列,可以在需要时重新创建对象。

反序列化(Deserialization)则是指将序列化后的字节流重新转换为对象的过程。反序列化的过程将字节序列转换为对象的状态,从而使得我们能够恢复原始对象。

什么是不可变类?

不可变类(Immutable Class)是指一旦创建后,其状态(即对象的属性值)就不能被修改或改变的类。换句话说,不可变类的对象一经创建,其状态就不可再被修改,任何对其状态的修改都会导致创建一个新的对象。

不可变类具有以下特点:

  1. 状态不可变性(State Immutability):不可变类的属性值在对象创建后不能被修改。

  2. 实例共享(Instance Sharing):由于不可变类的对象状态不可变,因此可以安全地在多个线程之间共享,无需担心线程安全问题。

  3. 线程安全性(Thread Safety):由于不可变类的对象状态不可变,因此不可变类是线程安全的,无需额外的同步措施。

  4. 简化并发编程(Simplify Concurrent Programming):不可变类简化了并发编程,因为无需担心对象的状态被并发修改而导致的问题。

  5. 更易于理解和调试(Easier to Understand and Debug):不可变类更容易理解和调试,因为它的状态不会发生变化,可以避免由于状态变化而导致的复杂性。

典型的不可变类包括 StringIntegerBigIntegerBigDecimal 等 JDK 中的一些基本类型包装类,以及很多其他的第三方库中的不可变类。在设计和实现自己的类时,如果希望提供不可变性,可以通过将属性设置为私有并提供只读方法,或者在构造函数中初始化属性并不提供修改方法等方式来实现。

为什么 Java 中 String 是不可变类?

  1. 安全性(Security):字符串常常用于存储敏感信息,如密码、密钥等。如果字符串是可变的,那么在传递字符串时可能会被意外地修改,从而导致安全问题。通过将字符串设置为不可变,可以确保其值在创建后不会被修改,从而提高安全性。

  2. 线程安全性(Thread Safety):由于字符串常常被多个线程共享,如果字符串是可变的,那么在并发情况下可能会出现竞态条件和线程安全问题。通过将字符串设置为不可变,可以避免这些问题,从而简化并发编程。

  3. 字符串池(String Pool):Java 中的字符串常量池是 JVM 中的一块特殊内存区域,用于存储字符串常量。由于字符串是不可变的,因此可以安全地在字符串常量池中共享字符串对象,从而减少内存占用和提高性能。

  4. 优化(Optimization):由于字符串是不可变的,因此可以进行一些优化,如字符串的哈希码可以提前计算并缓存,字符串拼接时可以使用 StringBuilderStringBuffer,而不必担心线程安全问题。

API 和 SPI 的区别

  1. API(应用程序编程接口)

    • API 是一组定义了软件组件之间交互的规范和约定,用于描述如何调用某个组件或模块提供的功能。
    • API 提供了一种标准化的接口,使得不同的软件组件可以进行交互和通信,而无需了解其内部实现细节。
    • API 定义了对外暴露的方法、参数、返回值等,是软件开发的基础。
  2. SPI(服务提供者接口)

    • SPI 是一种服务发现机制,用于在运行时动态加载并实现特定的接口或功能。
    • SPI 允许在不修改代码的情况下扩展应用程序的功能,通过在类路径上查找并加载服务提供者的实现类。
    • SPI 包含两部分:接口(Service Interface)和服务提供者(Service Provider)。接口定义了服务的规范,而服务提供者则提供了接口的具体实现。

主要区别在于,API 是一种固定的编程接口,用于描述组件之间的交互规范;而SPI 则是一种动态加载机制,用于在运行时动态加载并实现特定接口的功能。API 是面向开发者的,SPI 则更多地面向服务的扩展。


网站公告

今日签到

点亮在社区的每一天
去签到