Java 面向对象编程的三大特性是封装、继承和多态:
封装 :
封装是将对象的属性和行为方法结合在一起,只暴露出一个可以被外界访问的接口 ,不允许外部对象直接访问对象的内部信息。
通常使用关键字 private、protected、public 等来定义访问权限,以实现封装。
在代码层面,通常会通过 private 关键字修饰成员变量,然后提供 public 的 getter 和 setter 方法来控制访问权限。
继承 :
允许一个类(子类)继承另一个类(父类)的属性和方法的机制。Java 支持单继承,一个类只能直接继承一个父类。
子类可以复用 父类的代码,并且可以通过添加 新的方法或重写 已有的方法来扩展或改进功能,提高代码的可扩展性和可复用性。
例如,定义一个 Animal 父类拥有 eat() 方法,Cat 子类继承后,自动获得了 eat() 的能力,还可以新增 run() 方法或重写 eat() 实现具体细节。
多态:
- 定义: 同一个行为具有多个不同表现形式。父类引用指向子类实例,调用方法时执行的是子类的实现。
- 多态有两种形式:编译时多态(静态多态)和运行时多态(动态多态)。
- 编译时多态:这是通过方法的重载来实现的。在编译阶段,编译器根据方法的参数数量、类型或顺序来选择调用合适的方法。
- 运行时多态:这是通过方法的重写 和 向上转型 来实现的。具体表现为父类的引用指向子类的实例。编译时编译器,只看父类,也就是引用类型,运行时,JVM看子类,也就是运行类型,调用方法时执行子类重写后的逻辑,且只能调用父类中定义的方法。
例如:它使得程序具有极高的扩展性 。例如,我们编写一个feed(Animal animal)方法,参数是父类Animal。无论是传入Dog还是Cat,方法都能正常工作。如果未来新增了Elephant类,feed方法不需要做任何修改,
向上转型:父类引用指向子类实例,只能使用共有方法(方法名)
Java 方法 重写和重载的区别
Java 中的重载和重写是实现多态的两种不同方式。
方法的重载 ,指的是在同一个类中 ,可以有多个方法具有相同的名称,但是它们的参数列表不同(参数的类型、个数、顺序)。编译器根据调用时传入的参数在编译期确定调用哪个方法,属于编译时多态。返回类型和访问修饰符可以不同,但不能仅靠它们区分重载。
方法的重写 ,指在子类和父类之间 ,子类可以重新定义父类中已经定义的方法,方法名、参数列表和返回类型都必须相同。重写的方法的访问级别不能低于被重写的父类方法,抛出的异常不能比父类更宽泛,重写的方法在运行时根据实际对象类型 确定调用,属于运行时多态。
总结来说,重载关注的是方法的多样性,允许同一个类中存在多个同名方法。而重写关注的是方法的一致性,允许子类提供特定于其自己的行为实现。
接口和抽象类的区别
接口 和 抽象类 的共同点
- 实例化 :接口和抽象类都不能直接实例化,只能被实现(接口)或继承(抽象类)后才能创建具体的对象。
- 抽象方法 :接口和抽象类都可以包含抽象方法 (且只能由它们)。抽象方法没有方法体,必须在子类或实现类中实现。
接口 和 抽象类 的区别
- 继承和实现 :
因为 Java 不支持多继承,一个类只能继承一个类(包括抽象类)。
但一个类可以实现多个接口,一个接口也可以继承多个其他接口。 - 成员变量 :
接口中的成员变量只能是public static final类型的,不能被修改且必须有初始值。
抽象类的成员变量可以有任何修饰符(private,protected,public),可以在子类中被重新定义或赋值。 - 方法 :
- Java 8 之前,接口中的方法默认是
public abstract,也就是只能有方法声明。(只能包含抽象方法)
自 Java 8 起,可以在接口中定义default(默认实现) 方法和static(静态)方法。
自 Java 9 起,接口可以包含private方法。 - 抽象类可以包含抽象方法(默认是protected,也可以使用public、protected、private)和非抽象方法。
- Java 8 之前,接口中的方法默认是
- 构造器:
抽象类有构造器,接口没有构造器。 - 设计目的 :
接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。
抽象类主要用于代码复用,强调的是所属关系。
String、StringBuffer、StringBuilder的区别
可变性:
String 是不可变的;StringBuffer 和 StringBuilder 是可变;
String 内部的字符数组使用了final修饰。
每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。
StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串 ,不过没有使用 final 和 private 关键字修饰,最关键的是这个 AbstractStringBuilder 类还提供了很多修改字符串的方法比如 append 方法。
java
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
//...
}
线程安全性:
String 中的对象是不可变的,也就可以理解为常量,线程安全。
StringBuffer 对方法加了同步锁(synchronized),所以是线程安全的 。
StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。

性能
每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。
StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。StringBuilder 也是一样,因此对于复杂的字符串操作(例如多次拼接、插入、删除), StringBuffer 和 StringBuilder 效率高于 String
因为StringBuilder 没有对方法加同步锁,相同情况下使用 StringBuilder 相比使用 StringBuffer 能获得 10%~15% 左右的性能提升(性能更高),但却要冒多线程不安全的风险。
三者总结:
- 操作少量的数据: 适用
String - 单线程环境下,频繁进行字符串运算: 适用
StringBuilder - 多线程下,频繁进行字符串运算: 适用
StringBuffer
字符串常量池(String Pool)是 Java 中一个专门用于存储字符串字面量的特殊内存区域,它的主要作用是 提高内存利用率和运行时性能 。
使用的原因:
1.String在Java中使用频率很高
2.Java对Sring做了性能优化:可以不用new来实例化对象,而可以使用"字面量"
3.new产生的实例化对象,即使有对象属性一致也会创建多个对象
4.为了字符串的复用,创建字符串常量池(JVM堆中,jdk1.7以后)
5.具体实现:当使用字面量创建字符串时,字符串常量池将其对象引用进行保存,若之后创建相同属性的字符串,就讲字符串常量池中的引用进行返回
6.类似技术扩展:线程池,连接池,目的节省资源提高效率
String 两种创建方式的区别:
① 内存位置不同
字面量
"abc":存放在 字符串常量池(String Pool) 中
new String("abc"):存放在 堆内存(Heap) 中,对象内部引用常量池里的字符串
② 对象是否复用(最重要区别)字面量 :相同内容只会创建一个对象 ,多次使用会直接复用常量池里的同一个对象
new :每次调用都会创建一个全新的对象,即使内容一样,也是不同对象
Java 中 == 和 equals 的区别
Java 中 == 和 equals 的区别?
==对于基本数据类型和引用数据类型的作用效果是不同的:
- 对于基本数据类型来说,
==比较的是值 - 对于引用数据类型来说,
==比较的是对象的内存地址,也就是两个引用是否指向内存中同一位置,用于判断它们是否是同一个对象。
equals 方法是定义在 Object 类中的一个方法,默认情况下 ,equals 方法比较的是对象的引用(内存地址),与 == 相同。但在子类中通常被重写 ,比如 String、 Integer 等,已经重写了equals以便用于比较对象的内容是否相等。
总结:
- 一般来说,是使用 == 比较对象的引用(内存地址),用equals () 比较对象的内容。
- 需要注意的是,在重写 equals 方法时,应同时重写 hashCode 方法,以保持 equals 和 hashCode 的一致性,否则在
HashMap、HashSet等集合中会出现逻辑上相等但无法正确存储或查找的问题。
使用 equals 时还要注意空指针安全 :若调用者为 null,会抛出 NullPointerException,建议使用 Objects.equals(a, b) 来避免。
Java常见的异常类有哪些
继承关系

常见异常:

Java 所有的异常和错误都继承自Throwable类。
- Error(错误)
- 属于 JVM (Java虚拟机)层面的严重系统错误,程序无法通过代码捕获、处理和恢复。
- 由 JVM 自动抛出,与代码逻辑无关。
- 常见示例 :
StackOverflowError:栈溢出OutOfMemoryError:内存溢出
- Exception(异常)
异常分为两大类:运行时异常(非受检异常) 和 编译时异常(受检异常)。
① 运行时异常( 继承自RuntimeException类)
- 运行时才抛出 ,编译器不强制检查 / 处理。
- 通常是 代码逻辑错误 导致,属于程序自身问题。
- 常见示例 :
NullPointerException:空指针异常IndexOutOfBoundsException:数组 / 集合下标越界ArithmeticException:数学运算异常(除零)ClassCastException:类型转换异常IllegalArgumentException:非法参数异常
② 编译时异常(继承自 Exception 类)
- 编译期间就必须处理(必须捕获 或者 抛出),否则无法编译通过。
- 通常由 外部环境 / 不可预知因素 导致。
- 常见示例 :
IOException:IO 异常(基类)FileNotFoundException:文件未找到SQLException:数据库相关异常ClassNotFoundException:类未找到
相关知识点:
抛出:
java
public void myMethod(String param) throws IOException {
if (param == null) {
throw new IOException("参数不能为空");
}
}
捕获
java
try {
// 可能抛出异常的代码
myMethod(null);
} catch (IOException e) {
e.printStackTrace(); // 或打印日志
}
自定义异常 ,只需要继承 Exception(成为编译时异常) 或 RuntimeException(成为运行时异常),不需要重写任何方法。
final 关键字有什么用
final 关键字有什么用?
final 就是不可变的意思,可以修饰变量、方法、和类。
-
final修饰的类,不能被继承,是最终类典型例子:
String、Integer等包装类,System类都是final的。注意: 一旦类被声明为
final,它的所有方法都隐式地是final的(但成员变量可以不是final,可以改变) -
final修饰方法,表示该方法不能被重写注意:
private方法是隐式final的,因为子类无法访问它,也就无法重写 -
final修饰变量区分 基本数据类型 还是 引用类型
final修饰基本数据类型,变量一旦被赋值,不能改变final修饰引用数据类型 (对象、数组),变量的引用地址不能改变(即不能再指向另一个对象),但是对象本身的内容(对象属性)是可以改变的。
区分final修饰成员变量 时,成员变量必须显式赋值 ,且赋值只能发生一次:
1、声明时直接赋值 2、构造方法中赋值(有多个构造方法,每个构造方法必须对该变量赋值)3、在构造代码块中赋值final修饰局部变量时,不要求声明时赋值,但是必须在使用前赋值,且只能赋值一次。
补充:
final 与 不可变性 的区别:
-
final保证的是引用不可变(指向地址不可变)。 -
要实现对象不可变 (如
String),需要满足:- 类用
final修饰(防止被继承破坏不可变性)。 - 所有成员变量用
final修饰且为private。 - 不提供修改成员变量的 setter 方法。
- 如果有返回可变对象的 getter 方法,需要进行防御性拷贝(返回副本),不直接返回内部可变对象,而是使用其副本,防止修改影响原对象内部状态。
- 类用
Java 8 新特性
1. Lambda 表达式
Lambda 允许将函数作为方法参数传递,简化匿名内部类,支持函数式编程
java
// 传统方式
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
}).start();
// Lambda 表达式
new Thread(() -> System.out.println("Hello")).start();
// 带参数和返回值的 Lambda
List<String> list = Arrays.asList("a", "b", "c");
Collections.sort(list, (s1, s2) -> s1.compareTo(s2));
函数式接口
函数式接口是 只有一个抽象方法的接口 (可以有默认方法和静态方法),通常使用 @FunctionalInterface 注解标记。它是 Lambda 表达式的目标类型,任何需要函数式接口的地方都可以用 Lambda 表达式或方法引用来实现。
java
@FunctionalInterface
interface Converter {
String convert(String input);
}
// 使用 Lambda 实现
Converter toUpper = s -> s.toUpperCase();
System.out.println(toUpper.convert("hello")); // HELLO
方法引用
简化 Lambda 表达式,直接引用现有方法。
java
// 静态方法引用
List<Integer> nums = Arrays.asList(1, 2, 3);
nums.forEach(System.out::println); // 等同于 n -> System.out.println(n)
// 实例方法引用
String[] strings = {"a", "b", "c"};
Arrays.sort(strings, String::compareToIgnoreCase);
// 构造器引用
Supplier<List<String>> supplier = ArrayList::new;
List<String> list = supplier.get();
2. Stream API
Stream 支持对集合进行函数式操作,如过滤、映射等,支持串行与并行处理。
java
List<String> list = Arrays.asList("apple", "banana", "cherry");
// 过滤 + 收集
List<String> filtered = list.stream()
.filter(s -> s.startsWith("a"))
.collect(Collectors.toList());
// 映射 + 求和
int totalLength = list.stream()
.mapToInt(String::length)
.sum();
// 并行处理
long count = list.parallelStream()
.filter(s -> s.length() > 5)
.count();
3. 新的日期时间 API(java.time)
替代了旧的 Date 和 Calendar,提供线程安全、更加清晰和易用的日期和时间处理方式。
java
// 获取当前日期
LocalDate today = LocalDate.now();
// 指定日期
LocalDate birthday = LocalDate.of(1990, Month.JANUARY, 1);
// 日期运算
LocalDate nextWeek = today.plusWeeks(1);
// 时间差
Period period = Period.between(birthday, today);
// 带时区的日期时间
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
4. 接口的默认方法与静态方法!!!
接口可定义默认实现和静态方法,增强扩展性,默认方法使用default关键字,静态方法使用static关键字
java
interface Vehicle {
default void print() {
System.out.println("I am a vehicle");
}
static void blowHorn() {
System.out.println("Blowing horn");
}
}
class Car implements Vehicle {
// 可以选择重写默认方法
}
// 使用
Car car = new Car();
car.print(); // I am a vehicle
Vehicle.blowHorn(); // Blowing horn
5. Optional 类
封装可能为 null 的对象,减少空指针异常 NullPointerException。
java
Optional<String> optional = Optional.ofNullable(getName()); // gatName 返回 字符串 或 null
// 如果存在则打印
optional.ifPresent(System.out::println);
// 获取值,否则返回默认值
String name = optional.orElse("default");
// 抛出异常
String value = optional.orElseThrow(IllegalArgumentException::new);
匿名内部类 和 lambda 表达式区别:
-
匿名内部类 :
它是一个类 。编译后会生成一个单独的
.class文件(通常命名为OuterClass$1.class)。它是在编译时被确定的,属于类的结构。 -
Lambda 表达式 :
它不是类 。它是 Java 8 引入的函数式编程特性,依赖于
invokedynamic指令。它在运行时才动态生成实现类,不会产生单独的.class文件,也没有类加载的过程,性能通常更优