String、StringBuffer、StringBuilder 的区别
String、StringBuilder 和 StringBuffer 都是 Java 中用于处理字符串的类,但它们之间有一些重要的区别:
- 不可变性
- String 内部的字符数组使用 final 修饰,为不可变的字符串类,每当我们对 String 对象进行改变时,实际上都会创建一个新的 String 对象,旧的 String 对象会被 JVM 回收, 容易触发 GC,引起系统内存抖动。
- StringBuilder 和 StringBuffer 是可变的。即它们都允许修改字符串,而不会创建新的对象
- 线程安全
- String:由于 String 是不可变的,所以是线程安全的。
- StringBuffer 中的方法均使用 synchronized 关键字修饰,线程安全。
- StringBuilder 线程不安全。
- 性能
对于复杂的字符串操作(例如多次的拼接,插入,删除),StringBuilder 和 StringBuffer 效率高于 String,因为它们是可变的,不需要创建新的对象。
- 使用场景
- String:字符串不经常变化的场景中可以使用 String 类,例如常量的声明、少量的变量运算。
- StringBuilder:在频繁进行字符串运算(如拼接、替换、和删除等),并且运行在单线程的环境中,则可以考虑使用,如 SQL 语句的拼装、JSON 封装等。
- StringBuffer:在频繁进行字符串运算(如拼接、替换、删除等),并且运行在多线程环境中,则可以考虑使用 StringBuffer,例如 XML 解析、HTTP 参数解析和封装。
接口和抽象类的区别
- 定义
- 接口是一种抽象类型,它定义了一组方法,但没有实现任何方法的具体代码。接口中的方法默认是抽象的,且接口中只能包含常量(static final 变量)和抽象方法,不能包含成员变量。
- 抽象类是一个类,可以包含抽象方法和具体方法,也可以包含成员变量和常量。抽象类中的抽象方法是没有实现的方法,而具体方法则包含实现代码。抽象类不能直接实例化,通常需要子类继承并实现其中的抽象方法。
- 继承
- 一个类可以实现多个接口。
- Java 中不支持多继承,一个类只能继承一个抽象类。如果一个类已经继承了一个抽象类,就不能再继承其他类。
- 构造器
- 接口不能包含构造器,因为接口不能被实例化。类实现接口时,必须实现接口中定义的所有方法。
- 抽象类可以包含构造器,用于初始化抽象类的子类实例。
- 访问修饰符
- 接口中的方法默认是 public abstract 的。接口中的变量默认是 public static final 的。
- 抽象类中的抽象方法默认是 protected 的,具体方法的访问修饰符可以是 public、protected 或 private。
- 实现限制
- 类可以同时实现多个接口,接口中的方法默认为抽象方法,不包含方法体。实现接口时必须要实现这些方法。
- 一个类只能继承一个抽象类,继承抽象类的子类必须提供抽象类中定义的所有抽象方法的实现。
- 设计目的
- 接口用于定义规范,强调 “行为” 或 “能力”。
- 抽象类用于代码复用,提供通用的实现或基础功能,并且可以包含方法的具体实现。
Java 常见的异常类有哪些
Java 的异常都是派生于 Throwable 类的一个实例,所有的异常都是由 Throwable 继承而来的。异常又分为 RuntimeException 和其他异常:
- 运行时异常 RuntimeException:顾名思义,运行时才可能抛出的异常,编译器不会处理此类异常。比如数组索引越界 ArrayIndexOutOfBoundsException、使用的对象为空 NullPointException、强制类型转换错误 ClassCastException、除 0 等等。出现了运行时异常,一般是程序的逻辑有问题,是程序自身的问题而非外部因素。
- 其他异常:Exception 中除了运行时异常之外的,都属于其他异常。也可以称之为编译时异常,这部分异常编译器要求必须处置。这部分异常常常是因为外部运行环境导致,因为程序可能运行在各种环境中,如打开一个不存在的文件,此时抛出 FileNotFoundException。编译器要求 Java 程序必须捕获或声明所有的编译时异常,强制要求程序为可能出现的异常做准备工作。
说一说 Java 面向对象三大特性
Java 面向对象编程的三大特性是封装、继承和多态。
- 封装:封装是将对象的数据(属性)和行为(方法)结合在一起,并隐藏内部的实现细节,只暴露出一个可以被外界访问的接口。通常使用关键字 private、protected、public 等来定义访问权限,以实现封装。
- 继承:允许一个类(子类)继承另一个类(父类)的属性和方法的机制。子类可以重用父类的代码,并且可以通过添加新的方法或修改(重写)已有的方法来扩展或改进功能,提高了代码的可重用性和可扩展性。Java 只支持单继承,一个类只能直接继承一个父类。
- 多态:多态是指允许不同类的对象对同一消息做出响应,但具体的行为会根据对象的实际类型而有所不同。这通常通过方法重载和重写实现。
如何理解 Java 中的多态
- 当把一个子类对象直接赋给父类引用变量,而运行时调用该引用变量的方法时,其方法行为总是表现出子类方法的行为特征,而不是父类方法的行为特征,就可能出现相同类型的变量、调用同一个方法时呈现出多种不同的行为特征,这就是多态。
- 多态有两种形式:编译时多态(静态多态)和运行时多态(动态多态)。
- 编译时多态:指在编译阶段,编译器就能够确定调用哪个方法,这是通过方法的重载来实现的。编译器在编译时根据方法的参数数量、类型或顺序来选择调用合适的方法。
- 运行时多态:在程序运行时,根据实际对象的类型来确定调用的方法,这是通过方法的重写来实现的。运行时多态主要依赖于对象的实际类型,而不是引用类型。
Java 重写和重载的区别
Java 中的重载和重写是实现多态的两种不同方式。
方法的重载是编译时多态,指的是在同一个类中,可以有多个方法具有相同的名称,但是它们的参数列表不同(参数的类型、个数、顺序),可以有不同的返回类型和访问修饰符,通过静态绑定(编译时决定)实现。
方法的重写是运行时多态,指的是在子类中重新定义父类中已经定义的方法,方法名、参数列表和返回类型都必须相同。重写的方法的访问级别不能低于被重写的父类方法,虚拟机在运行时根据对象的实际类型来确定调用哪个方法。
总结来说,重载关注的是方法的多样性,允许同一个类中存在多个同名方法;而重写关注的是方法的一致性,允许子类提供特定于其自己的行为实现。
final 关键字有什么作用
final 就是不可变的意思,可以修饰变量、方法和类。
- 修饰类:final 修饰的类不可被继承,是最终类。
- 修饰方法:明确禁止该方法在子类中被覆盖的情况下才将方法设置为 final。
- 修饰变量:
- final 修饰基本数据类型的变量,其数值一旦在初始化之后便不能更改,称为常量;
- final 修饰引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。虽然不能再指向其他对象,但是它指向的对象的内容是可变的。
== 和 equals 的区别
在 Java 中,== 和 equals 方法用来比较对象,但它们在语义和使用上仍有一定的差别:
- == 运算符:对于原始数据类型,== 比较的是值是否相等,对于引用类型,== 比较的是两个引用是否指向内存中的同一位置,即它们是否是同一个对象。
- equals 是一个方法,定义在 Object 类中,默认情况下,equals() 方法比较的是对象的引用,与 == 类似。但在子类中通常被重写,比如 String、 Integer 等,已经重写了 equals() 方法以便比较对象的内容是否相等。
- 一般来说,是使用 == 比较对象的引用(内存地址),用 equals() 比较对象的内容。
- 需要注意的是,在重写 equals 方法时,应同时重写 hashCode 方法,以保持 equals 和 hashCode 的一致性。
JDK 8 有哪些新特性
- Lambda 表达式:允许以更简洁的语法编写匿名函数。
- Stream API:Stream API 提供了一种声明式的方式来对集合进行操作。它支持各种操作,如过滤、映射、排序、归约等。
- 函数式接口:Java 8 提供了一系列函数式接口,如 Consumer、Predicate、Function 等。
- 新的日期和时间 API:Java 8 引入了 java.time 包,提供了全新的日期和时间 API。它解决了旧的 java.util.Date 和 java.util.Calendar 的诸多问题,并提供了更加清晰和易用的日期和时间处理方式。
- 方法引用:方法引用允许通过方法的名称来引用一个方法,而不是执行它。它们提供了一种更简洁的方式来传递方法作为参数,如 System.out.println。