这篇文章整理了Java基础的知识点,覆盖比较全面。具体内容如下图所示:

1.面向对象三大特性及其他
封装、继承、多态
1、封装(Encapsulation)
定义:将数据(属性)和操作数据的方法(行为)绑定在一起,并隐藏内部实现细节,仅对外暴露必要的访问接口。
核心要素
-
信息隐藏:通过访问修饰符(private/protected/public)控制可见性
-
接口隔离:对外提供稳定的公共方法,内部实现可自由变更
java
public class BankAccount {
// 私有属性,隐藏内部状态
private String accountNo;
private BigDecimal balance;
// 对外提供受控的访问接口
public void deposit(BigDecimal amount) {
if (amount.compareTo(BigDecimal.ZERO) > 0) {
this.balance = this.balance.add(amount);
}
}
public BigDecimal getBalance() {
return this.balance;
}
}
2、继承(Inheritance)
定义:子类继承父类的属性和方法,实现代码复用,并可在子类中扩展或重写父类行为。
核心要素
-
单继承:Java中类只能单继承,接口可多实现
-
方法重写:子类可覆盖父类方法,实现多态
java
// 父类
public class Card {
protected String cardNo;
public void pay(BigDecimal amount) {
System.out.println("支付:" + amount);
}
}
// 子类继承并扩展
public class CreditCard extends Card {
private BigDecimal creditLimit;
@Override
public void pay(BigDecimal amount) {
// 重写父类方法,增加额度校验
if (amount.compareTo(creditLimit) <= 0) {
super.pay(amount);
creditLimit = creditLimit.subtract(amount);
}
}
}
3、多态(Polymorphism)
定义:同一类型的引用,调用同一方法时,根据实际指向的对象类型,表现出不同的行为。
核心要素
-
编译时多态:方法重载(Overloading)
-
运行时多态:方法重写 + 父类引用指向子类对象
java
// 父类
public abstract class Payment {
public abstract void process();
}
// 子类1 信用卡支付类
public class CreditPayment extends Payment {
@Override
public void process() {
System.out.println("信用卡支付处理");
}
}
// 子类2 借记卡支付类
public class DebitPayment extends Payment {
@Override
public void process() {
System.out.println("借记卡支付处理");
}
}
// 多态使用
public class PaymentService {
public void execute(Payment payment) {
payment.process(); // 运行时动态绑定,执行具体子类的实现
}
public static void main(String[] args) {
PaymentService service = new PaymentService();
service.execute(new CreditPayment()); // 输出:信用卡支付处理
service.execute(new DebitPayment()); // 输出:借记卡支付处理
}
}
| 特性 | 核心作用 | 类比 |
|---|---|---|
| 封装 | 保护数据,简化接口 | 汽车方向盘(暴露必要操作,隐藏发动机内部) |
| 继承 | 复用代码,建立层次 | 车型继承汽车基础,扩展特殊功能 |
| 多态 | 统一处理,动态扩展 | 统一刹车操作,不同车型有不同刹车表现 |
重载和重写
1、重载(Overloading)
在同一个类 中,多个方法拥有相同的名称 ,但参数列表不同(参数类型、个数或顺序不同)。
java
public class Calculator {
// 参数个数不同
public int add(int a, int b) {
return a + b;
}
public int add(int a, int b, int c) {
return a + b + c;
}
// 参数类型不同
public double add(double a, double b) {
return a + b;
}
// 参数顺序不同
public void show(String name, int age) {
System.out.println(name + ":" + age);
}
public void show(int age, String name) {
System.out.println(age + "岁的" + name);
}
// 以下不是重载(只有返回值不同,编译器无法区分)
// public String add(int a, int b) { // 编译错误!
// return String.valueOf(a + b);
// }
}
2、重写(Overriding)
子类对父类的方法进行重新实现,方法名、参数列表、返回类型必须与父类方法一致。
java
// 父类
public class Payment {
protected BigDecimal calculateFee(BigDecimal amount) throws PaymentException {
// 基础计费逻辑
return amount.multiply(new BigDecimal("0.01"));
}
public void process() {
System.out.println("处理支付");
}
}
// 子类重写
public class CreditPayment extends Payment {
// 重写:参数、返回类型相同,访问权限扩大(protected -> public)
@Override
public BigDecimal calculateFee(BigDecimal amount) throws PaymentException {
// 信用卡特殊计费逻辑
if (amount.compareTo(new BigDecimal("1000")) > 0) {
return amount.multiply(new BigDecimal("0.005"));
}
return super.calculateFee(amount);
}
// 以下不是重写(参数列表不同,属于重载,重载的是Payment类中的process()无参方法。)
public void process(String channel) {
System.out.println("通过" + channel + "处理支付");
}
}
3、泛型(Generics)
泛型 是Java SE 5引入的核心特性,允许在定义类、接口、方法时使用类型参数 ,实现参数化类型。它让代码能够在不指定具体类型的情况下被编写,在编译时进行类型安全检查。
泛型是Java的类型安全机制
java
// 没有泛型(Java 5之前)
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // 必须强制转换
// 有泛型(现代Java)
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0); // 无需转换,编译器自动处理
通过代码可以看出来,泛型的特点是有个**<>** 而这个 <> 里面放的就是通配符。
泛型类、泛型接口、泛型方法
javascript
// 定义泛型类
public class Box<T> {
private T content;
public void set(T content) {
this.content = content;
}
public T get() {
return content;
}
// 泛型方法:定义在方法返回值前
public <T> T getValue(T value) {
return value;
}
// 静态方法不能使用类的泛型参数
// public static T staticMethod() { } // 编译错误!
}
// 使用 泛型类
Box<String> stringBox = new Box<>();
stringBox.set("hello");
String value = stringBox.get();
Box<Integer> intBox = new Box<>();
intBox.set(123);
// 调用 泛型方法
String ssss = intBox.getValue("ssss");
System.out.println(ssss);
// ---------------------------------------------------------------
// 定义泛型接口
public interface Repository<T, ID> {
T findById(ID id);
void save(T entity);
ID getId(T entity);
}
// 实现方式1:指定具体类型
public class UserRepository implements Repository<User, Long> {
@Override
public User findById(Long id) {
return new User(id);
}
@Override
public void save(User entity) { }
@Override
public Long getId(User entity) {
return entity.getId();
}
}
类型边界
1、上界通配符 extends
java
// 限制类型必须是Number或其子类
public class NumberBox<T extends Number> {
private T number;
public double doubleValue() {
return number.doubleValue();
}
}
2、下界通配符 super
java
// 只能用于通配符,不能用于类型参数定义
public void addNumbers(List<? super Integer> list) {
list.add(1); // ✅ 可以添加Integer
list.add(2); // ✅ 可以添加Integer子类
// Integer num = list.get(0); // ❌ 不能确定取出类型
}
3、无界通配符 ?
java
public void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
// list.add("hello"); // ❌ 不能添加元素(除null外)
list.add(null); // ✅ 只能添加null
}
| 通配符 | 语法 | 读作 | 适用场景 |
|---|---|---|---|
? |
List<?> |
未知类型 | 只读操作,不关心具体类型 |
? extends T |
List<? extends Number> |
T或T的子类 | 生产者(Producer)- 读取数据 |
? super T |
List<? super Integer> |
T或T的父类 | 消费者(Consumer)- 写入数据 |
序列化和反序列化
什么是序列化与反序列化?
序列化(Serialization):将Java对象转换为字节序列的过程,便于网络传输或持久化存储。
反序列化(Deserialization):将字节序列恢复为Java对象的过程。
Java序列化是将对象转为字节流的核心机制,通过Serializable 接口实现,配合transient控制敏感字段,实际生产环境建议使用JSON、Protobuf等更安全高效的序列化框架。
2.基本数据类型
基本数据类型(Primitive Types)
| 类型 | 字节数 | 范围 | 默认值 |
|---|---|---|---|
| byte | 1 | -128 ~ 127 | 0 |
| short | 2 | -32,768 ~ 32,767 | 0 |
| int | 4 | -2^31 ~ 2^31-1 | 0 |
| long | 8 | -2^63 ~ 2^63-1 | 0L |
| float | 4 | 约 ±3.4E-38 | 0.0f |
| double | 8 | 约 ±1.8E-308 | 0.0d |
| char | 2 | 0 ~ 65,535 | '\u0000' |
| boolean | 不固定 | true/false | false |
引用数据类型(Reference Types)
-
类(Class)
-
接口(Interface)
-
数组(Array)
-
枚举(Enum)
-
注解(Annotation)
数据类型的转换
1、自动类型转换(隐式转换)
规则:小范围类型自动转换为大范围类型(无精度损失)
java
public class AutoConversion {
public static void main(String[] args) {
// byte → int
byte b = 100;
int i = b; // 自动转换
System.out.println(i); // 100
// char → int
char c = 'A';
int charToInt = c;
System.out.println(charToInt); // 65(ASCII码)
// int → long
int intVal = 1000;
long longVal = intVal;
// long → float(可能损失精度)
long bigLong = 123456789012345L;
float floatVal = bigLong; // 自动转换,可能丢失精度
System.out.println(floatVal); // 1.2345679E14
// int → double
int intNum = 100;
double doubleNum = intNum; // 100.0
// ❌ 不能自动转换:大范围 → 小范围
// long l = 100L;
// int i2 = l; // 编译错误!
}
}
2、强制类型转换(显式转换)
语法 :(目标类型) 要转换的值
javascript
public class ExplicitConversion {
public static void main(String[] args) {
// 大范围 → 小范围(可能溢出或精度损失)
long longVal = 130L;
byte byteVal = (byte) longVal;
System.out.println(byteVal); // -126(溢出:130 - 256 = -126)
// double → int(截断小数部分)
double doubleVal = 3.14159;
int intVal = (int) doubleVal;
System.out.println(intVal); // 3(直接截断,不四舍五入)
// 精度损失
float floatVal = 3.14f;
int floatToInt = (int) floatVal;
System.out.println(floatToInt); // 3
// 特殊转换:int → char
int asciiCode = 65;
char charVal = (char) asciiCode;
System.out.println(charVal); // 'A'
// 溢出处理
int bigInt = 300;
byte overflowByte = (byte) bigInt;
System.out.println(overflowByte); // 44(300 % 256 = 44)
}
}
3、包装类与基本类型转换
java
public class WrapperConversion {
public static void main(String[] args) {
// 字符串 → 基本类型
int intFromStr = Integer.parseInt("123");
double doubleFromStr = Double.parseDouble("3.14159");
boolean boolFromStr = Boolean.parseBoolean("true");
long longFromStr = Long.parseLong("9999999999");
// 字符串 → 包装类
Integer intObj = Integer.valueOf("456");
Double doubleObj = Double.valueOf("2.718");
// 基本类型 → 字符串
String intStr = Integer.toString(100);
String doubleStr = Double.toString(3.14);
String str = String.valueOf(123); // 推荐方式
// 进制转换
String binaryStr = Integer.toBinaryString(10); // "1010"
String hexStr = Integer.toHexString(255); // "ff"
String octalStr = Integer.toOctalString(8); // "10"
// 解析不同进制字符串
int binaryVal = Integer.parseInt("1010", 2); // 10
int hexVal = Integer.parseInt("FF", 16); // 255
}
}
包装类
什么是包装类?
1、基本类型与包装类对应关系
| 基本类型 | 包装类 | 字节数 | 默认值 | 缓存范围 |
|---|---|---|---|---|
| byte | Byte | 1 | 0 | -128 ~ 127 |
| short | Short | 2 | 0 | -128 ~ 127 |
| int | Integer | 4 | 0 | -128 ~ 127 |
| long | Long | 8 | 0L | -128 ~ 127 |
| float | Float | 4 | 0.0f | 无缓存 |
| double | Double | 8 | 0.0d | 无缓存 |
| char | Character | 2 | '\u0000' | 0 ~ 127 |
| boolean | Boolean | 不固定 | false | TRUE/FALSE |
2、自动装箱与拆箱原理
java
public class AutoBoxing {
public static void main(String[] args) {
// 自动装箱:int → Integer
Integer intObj = 100;
// 等价于:Integer intObj = Integer.valueOf(100);
// 自动拆箱:Integer → int
int intVal = intObj;
// 等价于:int intVal = intObj.intValue();
// 方法参数中的自动装箱/拆箱
printInteger(200); // int → Integer
int result = getInteger(); // Integer → int
}
public static void printInteger(Integer num) {
System.out.println(num);
}
public static Integer getInteger() {
return 300;
}
}
3、Integer 的缓存机制(重要)
java
public class CacheMechanism {
public static void main(String[] args) {
// Integer缓存:-128 ~ 127 (Integer的值在这个范围之内都是正常的,超过这个值的范围就会创建一个新对象进行比较)
Integer a = 100;
Integer b = 100;
System.out.println(a == b); // true(使用缓存对象)
Integer c = 200;
Integer d = 200;
// 这里==进行的是引用地址比较,而两个对象的引用地址是不一样的
System.out.println(c == d); // false(创建新对象)
// 正确比较方式 (进行的是值对比,值是相同的)
System.out.println(c.equals(d)); // true
// Byte、Short、Long都有相同缓存范围
Byte byte1 = 100;
Byte byte2 = 100;
System.out.println(byte1 == byte2); // true
Long long1 = 100L;
Long long2 = 100L;
System.out.println(long1 == long2); // true
Long long3 = 200L;
Long long4 = 200L;
System.out.println(long3 == long4); // false
// Character缓存:0 ~ 127
Character ch1 = 'a';
Character ch2 = 'a';
System.out.println(ch1 == ch2); // true
Character ch3 = '中';
Character ch4 = '中';
System.out.println(ch3 == ch4); // false
// Boolean缓存:TRUE/FALSE
Boolean bool1 = true;
Boolean bool2 = true;
System.out.println(bool1 == bool2); // true
// Float、Double没有缓存
Float f1 = 1.0f;
Float f2 = 1.0f;
System.out.println(f1 == f2); // false
// 强制使用新对象
Integer newObj = new Integer(100);
Integer cached = Integer.valueOf(100);
System.out.println(newObj == cached); // false
// 修改缓存范围(通过JVM参数)
// -Djava.lang.Integer.IntegerCache.high=256
}
}
3、引用数据类型
在 Java 中,引用类型 是指那些不是基本数据类型(int、double、boolean 等)的变量类型。它们指向堆内存中的对象,而不是直接存储值。
1、String
String 不可变性的原理
String 被设计为不可变对象,一旦创建,其内容就不能被改变。
javascript
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** 存储字符串的字符数组 */
private final char[] value;
/** 字符串的哈希码缓存 */
private int hash; // 默认为 0
// ... 其他字段和方法
}
关键点:
-
final类:不能被继承,防止子类破坏不可变性 -
final字段 :value数组引用不可变,无法指向新数组 -
private字段:外部无法直接访问内部数组
String 类没有提供 任何可以修改内部字符数组的公共方法:
-
没有
setCharAt()方法 -
没有
setLength()方法
String、StringBuffer、StringBuilder
三者的区别和适用场景。核心区别对比表
| 特性 | String | StringBuffer | StringBuilder |
|---|---|---|---|
| 可变性 | 不可变 | 可变 | 可变 |
| 线程安全 | 安全(不可变天然安全) | 安全(方法有 synchronized) | 不安全 |
| 性能 | 最差(频繁修改时) | 中等(同步开销) | 最好 |
| 存储区域 | 字符串常量池(字面量)或堆 | 堆 | 堆 |
| 适用场景 | 字符串不常变化 | 多线程环境下的字符串操作 | 单线程环境下的字符串操作 |
StringBuffer
java
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
StringBuilder
java
public StringBuilder append(String str) {
super.append(str); // 无 synchronized 修饰
return this;
}
三者常用场景
String 常用场景
java
// 场景一:字符串内容不变
String greeting = "Hello"; // 常量
String errorMsg = "404 Not Found"; // 固定错误信息
// 场景二:作为 HashMap 的 Key
Map<String, User> userMap = new HashMap<>();
userMap.put("admin", adminUser); // String 不可变,保证 key 不变
// 场景三:少量字符串操作
String fullName = firstName + " " + lastName; // 简单的拼接
StringBuffer 常用场景
java
// 场景一:多线程环境下的字符串操作
public class LogCollector {
private StringBuffer logBuffer = new StringBuffer();
// 多个线程可能同时调用此方法
public synchronized void appendLog(String log) {
logBuffer.append(log).append("\n");
}
// 或者使用 StringBuffer 自身的方法锁
public void addLog(String log) {
logBuffer.append(log); // append 是同步的
}
}
// 场景二:共享的字符串构建器
public class SharedStringBuilder {
private static StringBuffer sharedBuffer = new StringBuffer();
public static void appendToShared(String text) {
sharedBuffer.append(text); // 线程安全
}
}
StringBuilder 常用场景
java
// 场景一:单线程环境下的字符串拼接(最常见)
public String buildHtml() {
StringBuilder sb = new StringBuilder();
sb.append("<html>");
sb.append("<body>");
sb.append("<h1>Hello World</h1>");
sb.append("</body>");
sb.append("</html>");
return sb.toString();
}
// 场景二:循环中的字符串拼接
public String joinStrings(List<String> items) {
StringBuilder sb = new StringBuilder();
for (String item : items) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(item);
}
return sb.toString();
}
// 场景三:JSON/XML 动态构建
public String buildJSON(Map<String, Object> data) {
StringBuilder json = new StringBuilder("{");
for (Map.Entry<String, Object> entry : data.entrySet()) {
json.append("\"").append(entry.getKey()).append("\":\"")
.append(entry.getValue()).append("\",");
}
json.setCharAt(json.length() - 1, '}');
return json.toString();
}
字符串拼接是如何实现的
1、使用 + 运算符拼接
java
public class Test {
public static void main(String[] args) {
String s = "Hello" + " " + "World";
}
}
// 编译后直接变为常量
String s = "Hello World";
2、变量与常量拼接
java
public class Test {
public static void main(String[] args) {
String str1 = "Hello";
String str2 = str1 + " World";
}
}
3、循环中的拼接
java
public class Test {
public static void main(String[] args) {
String result = "";
for (int i = 0; i < 10; i++) {
result = result + i; // 每次循环都创建新的 StringBuilder
}
}
}
// 每次循环都会执行以下操作
new StringBuilder()
append(result)
append(i)
toString()
// 性能问题:每次循环都创建新的 StringBuilder 对象,产生大量临时对象。
String 创建对象的数量
java
// 场景 1:纯字面量
String s1 = "abc";
// 对象创建数量:1 个或 0 个
// 原因:常量池中如果有abc,则0个。若常量池中没有abc。则创建 1 个对象(在常量池)
// 场景 2:new String("abc")
// 情况1:常量池中没有 "abc"
String s = new String("abc");
// 创建 2 个对象:
// 1. 常量池中的 "abc"
// 2. 堆中的 String 对象(复制常量池的内容)
// 情况2:常量池中已有 "abc"
String s1 = "abc"; // 创建常量池对象
String s2 = new String("abc"); // 只创建 1 个堆对象,复用常量池对象
// 场景 3:字面量拼接
String s = "a" + "b" + "c";
// 编译后直接变为
String s = "abc";
// 只创建 1 个常量池对象
// 场景 4:变量与字面量拼接
String a = "a";
String b = "b";
String c = a + b;
String a = "a"; // 创建常量池对象:1
String b = "b"; // 创建常量池对象:2
String c = a + b; // 创建 StringBuilder + 最终 String + 临时char[]?
2、类
接口和抽象类的区别
共同点:
- 都属于「抽象层」,都不能被直接实例化,只能被继承 / 实现;
- 都可以定义「抽象方法」,约束子类 / 实现类必须实现具体的业务逻辑。
不同点:
- 单继承多实现
java
// 抽象类:只能继承一个
class Dog extends Animal { // 只能继承一个父类
// ...
}
// 接口:可以实现多个
class SmartDog extends Animal implements Runnable, Swimmable, Trainable {
// 继承一个抽象类,实现多个接口
@Override
public void run() {
System.out.println("Dog running");
}
@Override
public void swim() {
System.out.println("Dog swimming");
}
@Override
public void train() {
System.out.println("Dog training");
}
}
- 接口的多继承
java
// 接口可以多继承
interface A {
void methodA();
}
interface B {
void methodB();
}
interface C extends A, B { // 接口支持多继承
void methodC();
}
class D implements C {
// 必须实现所有方法
public void methodA() { }
public void methodB() { }
public void methodC() { }
}
对象
1、Java的对象的创建过程
java
// 一个简单的对象创建语句
Person person = new Person("张三", 25);
1. 类加载检查 → 2. 分配内存 → 3. 内存空间初始化 → 4. 设置对象头 → 5. 执行实例初始化方法(<init>) → 6. 返回引用
2、Java对象的四种引用
四种引用对比表
| 引用类型 | 回收时机 | 获取对象 | 使用场景 | 内存泄漏风险 |
|---|---|---|---|---|
| 强引用 | 永不回收(只要引用存在) | 直接访问 | 普通对象 | 高 |
| 软引用 | 内存不足时回收 | get() 方法 |
内存敏感缓存 | 中 |
| 弱引用 | 下次 GC 时回收 | get() 方法 |
WeakHashMap、监听器 | 低 |
| 虚引用 | 任何时候(无法获取) | get() 返回 null |
对象回收跟踪 | 无 |
深克隆与浅克隆
- 深克隆 :不仅复制对象本身,还递归复制对象内部的所有引用类型字段,生成完全独立的全新对象。
- 浅克隆 :只复制对象本身,对于对象内部的引用类型字段,只复制其引用地址(即新旧对象共享同一个子对象)。
浅克隆
浅克隆创建一个新对象,然后将原对象的非静态字段复制到新对象。如果字段是值类型 ,则复制该字段本身;如果字段是引用类型 ,则复制引用 (即内存地址),因此原对象和新对象的该引用字段指向同一个子对象。
java
class Address {
String city;
Address(String city) { this.city = city; }
}
class Person implements Cloneable {
String name;
Address address;
Person(String name, Address address) {
this.name = name;
this.address = address;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone(); // 浅克隆
}
}
// 使用
Address addr = new Address("北京");
Person p1 = new Person("张三", addr);
Person p2 = (Person) p1.clone();
p2.address.city = "上海"; // 修改克隆对象的地址
System.out.println(p1.address.city); // 输出 "上海" ------ 原对象也被修改了
深克隆
深克隆会递归复制对象及其所有引用的子对象,生成一个完全独立的对象副本。原对象和克隆对象之间没有任何共享的引用,彼此完全隔离。
java
@Override
protected Object clone() throws CloneNotSupportedException {
Person cloned = (Person) super.clone();
cloned.address = new Address(this.address.city); // 手动复制引用对象
return cloned;
}
-
浅克隆:快速、简单,但存在数据共享的风险。
-
深克隆:安全、独立,但实现成本较高。
值传递和引用传递
值传递: 将实际参数的值(或其副本)传递给方法的形式参数。方法内部对参数的修改不会影响原始变量。
java
public class Test {
public static void change(int num) {
num = 20; // 修改的是副本
}
public static void main(String[] args) {
int a = 10;
change(a);
System.out.println(a); // 输出 10 ------ 原始值未变
}
}
引用传递: 将实际参数的内存地址 (即引用本身)传递给方法。方法内部对参数的修改会直接影响原始变量。
-
传递的不是数据的副本,而是数据所在内存的地址。
-
方法内部操作的是同一个内存地址的数据。
关键字
**1、&和&&区别:**一个是按位与/逻辑与(非短路),一个是逻辑与(短路)。
| 运算符 | 短路行为 | 说明 |
|---|---|---|
&& |
是 | 如果第一个操作数为 false,直接返回 false,不再计算第二个操作数 |
& |
否 | 无论第一个操作数是什么,都会计算第二个操作数 |
2、==和equals区别
| 特性 | == |
equals() |
|---|---|---|
| 类型 | 运算符 | 方法(Object 类提供) |
| 基本类型 | 比较值是否相等 | 不能用于基本类型 |
| 引用类型 | 比较内存地址(是否同一个对象) | 默认比较地址,但可重写为比较内容 |
| 是否可重写 | 不可重写 | 可重写 |
java
public class Test {
public static void main(String[] args) {
// 基本类型:== 比较值
int a = 10;
int b = 10;
System.out.println(a == b); // true
// 引用类型:== 比较地址
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false(不同对象)
System.out.println(s1.equals(s2)); // true(内容相同)
// 字符串常量池的特殊情况
String s3 = "hello";
String s4 = "hello";
System.out.println(s3 == s4); // true(指向常量池同一对象)
// 自定义类
Person p1 = new Person("张三");
Person p2 = new Person("张三");
System.out.println(p1 == p2); // false(不同对象)
System.out.println(p1.equals(p2)); // false(未重写equals,默认比较地址)
}
}
3、hashCode() 和 equals() 的区别
| 方法 | 作用 | 默认实现 |
|---|---|---|
equals() |
判断两个对象逻辑相等 | 比较内存地址(==) |
hashCode() |
返回对象的哈希码(整数),用于哈希表快速定位 | 返回对象内存地址转换的整数(不一定唯一) |
Java 规定 hashCode() 和 equals() 必须遵循以下契约:
-
一致性 :在程序运行期间,只要对象的
equals比较所用信息未修改,多次调用hashCode()应返回相同整数。 -
equals 相等则 hashCode 必须相等 :如果
a.equals(b)为true,则a.hashCode() == b.hashCode()必须成立。 -
hashCode 相等不一定 equals 相等 :如果
a.hashCode() == b.hashCode(),a.equals(b)可能为true也可能为false(哈希冲突)。
4、为什么重写 equals() 必须重写 hashCode()
**核心原因:**保证哈希集合的正常工作
当对象作为 HashMap、HashSet、Hashtable 等哈希集合的键时,集合内部逻辑是:
-
存数据时:
-
调用
key.hashCode()计算哈希值,确定存储的桶(bucket)位置 -
在该桶内通过
equals()判断是否存在相同键(避免重复)
-
-
取数据时:
-
调用
key.hashCode()定位桶 -
在该桶内通过
equals()找到目标键值对
-
如果违反契约(equals 相等但 hashCode 不等):
-
存入时:对象 A 和对象 B 逻辑相等,但因哈希码不同,被放入不同的桶
-
取出时:用对象 B 去取,定位到另一个桶,找不到对象 A 存入的值
-
结果:集合中出现逻辑重复的键,且无法通过 equals 相等的键取出数据
java
// 错误示例的实际后果
HashSet<Person> set = new HashSet<>();
Person p1 = new Person("张三", 20);
Person p2 = new Person("张三", 20);
set.add(p1);
set.add(p2);
System.out.println(set.size()); // 输出 2(应该是 1,集合出现了重复)
System.out.println(set.contains(p2)); // 可能返回 true(取决于实现)
System.out.println(set.contains(new Person("张三", 20))); // 很可能返回 false
this, final, finally
1、this 关键字
this 是一个引用变量 ,指向当前对象本身。它存在于每个非静态方法中,用于区分成员变量和局部变量,或调用当前对象的其他构造方法/方法。
2、final 关键字
final 表示不可变,可以修饰类、方法、变量。
3、finally 关键字
finally 是异常处理机制的一部分,与 try-catch 配合使用,确保无论是否发生异常,某些代码都会执行。
| 关键字 | 类型 | 作用 | 使用场景 |
|---|---|---|---|
this |
引用变量 | 指向当前对象 | 区分变量、调用构造方法、链式调用 |
final |
关键字 | 表示不可变 | 常量定义、防止继承/重写、线程安全 |
finally |
关键字 | 异常处理的保证块 | 释放资源、清理工作、保证代码执行 |
Java 访问修饰符
四种访问修饰符对比
| 修饰符 | 同类 | 同包 | 子类(不同包) | 任意类 |
|---|---|---|---|---|
private |
✅ | ❌ | ❌ | ❌ |
| 默认(无修饰符) | ✅ | ✅ | ❌ | ❌ |
protected |
✅ | ✅ | ✅ | ❌ |
public |
✅ | ✅ | ✅ | ✅ |
html
访问范围从小到大:
private ────→ 默认 ────→ protected ────→ public
│ │ │ │
│ │ │ └── 所有类
│ │ └── 不同包子类(继承)
│ └── 同包(包括子类和非子类)
└── 仅当前类
4、Java 8 新特性
Lambda 表达式
Lambda 表达式是一种匿名函数,允许将行为作为参数传递,简化了匿名内部类的写法。
java
// 1. 无参数,无返回值
Runnable r1 = () -> System.out.println("Hello");
// 等价于
Runnable r2 = new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
};
// 2. 一个参数,无返回值
Consumer<String> c1 = (String s) -> System.out.println(s);
Consumer<String> c2 = s -> System.out.println(s); // 类型可省略
// 3. 多个参数,有返回值
Comparator<Integer> comp1 = (Integer a, Integer b) -> {
return a.compareTo(b);
};
Comparator<Integer> comp2 = (a, b) -> a.compareTo(b); // 简化
// 4. 方法引用(进一步简化)
Comparator<Integer> comp3 = Integer::compareTo;
函数式接口
java
// 常用函数式接口
@FunctionalInterface
public interface MyInterface {
void doSomething(String param);
// 可以有默认方法和静态方法
default void defaultMethod() { }
static void staticMethod() { }
}
// 使用
MyInterface mi = param -> System.out.println(param);
mi.doSomething("test");
Java 8 内置的函数式接口:
| 接口 | 参数 | 返回值 | 用途 |
|---|---|---|---|
Predicate<T> |
T | boolean | 条件判断 |
Consumer<T> |
T | void | 消费数据 |
Function<T,R> |
T | R | 类型转换 |
Supplier<T> |
无 | T | 提供数据 |
UnaryOperator<T> |
T | T | 一元运算 |
BinaryOperator<T> |
(T,T) | T | 二元运算 |
java
// Predicate 示例
Predicate<String> isEmpty = s -> s == null || s.isEmpty();
Predicate<String> isLong = s -> s.length() > 5;
boolean result = isEmpty.or(isLong).test("hello"); // false
// Function 示例
Function<String, Integer> strLength = String::length;
Integer len = strLength.apply("hello"); // 5
// Consumer 示例
Consumer<String> printer = System.out::println;
printer.accept("Hello"); // 输出 Hello
// Supplier 示例
Supplier<Double> random = Math::random;
Double value = random.get(); // 随机数
Stream API
Stream 的特点
-
不存储数据:数据源来自集合、数组或 I/O
-
函数式编程:对 Stream 的操作产生新结果,不修改数据源
-
惰性执行:中间操作(如 filter、map)是惰性的,遇到终止操作才执行
-
可并行 :通过
parallelStream()轻松实现并行处理
创建 Stream
java
List<String> list = Arrays.asList("apple", "banana", "cherry", "date", "apple");
// filter:过滤
list.stream()
.filter(s -> s.length() > 5)
.forEach(System.out::println); // banana, cherry
// map:转换
list.stream()
.map(String::toUpperCase)
.forEach(System.out::println); // APPLE, BANANA, CHERRY, DATE, APPLE
// distinct:去重
list.stream()
.distinct()
.forEach(System.out::println); // apple, banana, cherry, date
// sorted:排序
list.stream()
.sorted()
.forEach(System.out::println);
// limit:限制数量
list.stream()
.limit(2)
.forEach(System.out::println); // apple, banana
// skip:跳过
list.stream()
.skip(2)
.forEach(System.out::println); // cherry, date, apple
// peek:查看中间结果(调试用)
list.stream()
.peek(System.out::println)
.map(String::toUpperCase)
.collect(Collectors.toList());
Optional 类
Optional 是一个容器对象 ,用于优雅地处理 null,避免 NullPointerException。
java
Optional<String> optional = Optional.ofNullable(getName());
// isPresent:判断是否存在
if (optional.isPresent()) {
System.out.println(optional.get()); // 存在则获取
}
// ifPresent:存在则执行
optional.ifPresent(name -> System.out.println(name));
// orElse:存在则返回,否则返回默认值
String name = optional.orElse("默认姓名");
// orElseGet:存在则返回,否则执行 Supplier 获取
String name2 = optional.orElseGet(() -> generateDefaultName());
// orElseThrow:存在则返回,否则抛出异常
String name3 = optional.orElseThrow(() -> new IllegalArgumentException("姓名不存在"));
// map:转换
Optional<String> upperName = optional.map(String::toUpperCase);
// flatMap:转换(返回值必须是 Optional)
Optional<Integer> length = optional.flatMap(name -> Optional.of(name.length()));
// filter:过滤
Optional<String> filtered = optional.filter(name -> name.length() > 3);
总结:
| 特性 | 核心作用 | 主要优势 |
|---|---|---|
| Lambda | 行为参数化,简化匿名内部类 | 代码简洁、函数式编程基础 |
| Stream | 集合操作的高级抽象 | 链式调用、声明式编程、支持并行 |
| Optional | 优雅处理 null 值 | 避免 NPE、强制空值检查、代码更安全 |
5、反射机制
反射(Reflection) 是指在运行时动态地获取类的信息(如类名、方法、字段、构造方法等)并操作它们的能力。
核心思想:将类的各个组成部分(类、方法、字段、构造方法等)映射为 Java 对象,从而可以在运行时动态操作。
反射的核心类
| 类 | 作用 |
|---|---|
Class |
代表类或接口 |
Constructor |
代表构造方法 |
Method |
代表方法 |
Field |
代表字段(成员变量) |
Modifier |
访问修饰符信息 |
Array |
动态创建和操作数组 |
反射怎么用
获取 Class 对象的三种方式
java
// 方式一:通过类名.class
Class<Person> clazz1 = Person.class;
// 方式二:通过对象.getClass()
Person person = new Person();
Class<? extends Person> clazz2 = person.getClass();
// 方式三:通过 Class.forName()(全限定类名)
Class<?> clazz3 = Class.forName("com.example.Person");
// 三种方式获取的是同一个 Class 对象(类的唯一模板)
System.out.println(clazz1 == clazz2); // true
System.out.println(clazz1 == clazz3); // true
获取类的信息
java
public class Person {
private String name;
private int age;
public Person() { }
public Person(String name, int age) {
this.name = name;
this.age = age;
}
private void privateMethod() {
System.out.println("私有方法");
}
public String publicMethod(String param) {
return "Hello " + param;
}
// getter/setter 省略
}
// 反射获取信息
Class<?> clazz = Class.forName("com.example.Person");
// 获取类名
System.out.println("类名: " + clazz.getName()); // com.example.Person
System.out.println("简单类名: " + clazz.getSimpleName()); // Person
// 获取修饰符
int modifiers = clazz.getModifiers();
System.out.println("是否为 public: " + Modifier.isPublic(modifiers));
// 获取父类
Class<?> superclass = clazz.getSuperclass(); // Object.class
// 获取接口
Class<?>[] interfaces = clazz.getInterfaces();
// 获取构造方法
Constructor<?>[] constructors = clazz.getConstructors(); // 所有 public 构造方法
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors(); // 所有构造方法
// 获取方法
Method[] methods = clazz.getMethods(); // 所有 public 方法(包括继承的)
Method[] declaredMethods = clazz.getDeclaredMethods(); // 所有声明的方法(不包括继承的)
// 获取字段
Field[] fields = clazz.getFields(); // 所有 public 字段
Field[] declaredFields = clazz.getDeclaredFields(); // 所有字段(包括 private)
动态创建对象
java
// 方式一:通过无参构造方法
Class<?> clazz = Class.forName("com.example.Person");
Person person1 = (Person) clazz.newInstance(); // 已废弃,推荐下面方式
// 方式二:通过 Constructor 创建(推荐)
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true); // 如果是 private 构造方法,需要设置
Person person2 = (Person) constructor.newInstance();
// 方式三:通过有参构造方法
Constructor<?> paramConstructor = clazz.getDeclaredConstructor(String.class, int.class);
Person person3 = (Person) paramConstructor.newInstance("张三", 25);
动态调用方法
java
Class<?> clazz = Class.forName("com.example.Person");
Person person = (Person) clazz.getDeclaredConstructor().newInstance();
// 调用 public 方法
Method publicMethod = clazz.getMethod("publicMethod", String.class);
String result = (String) publicMethod.invoke(person, "World");
System.out.println(result); // Hello World
// 调用 private 方法
Method privateMethod = clazz.getDeclaredMethod("privateMethod");
privateMethod.setAccessible(true); // 突破 private 限制
privateMethod.invoke(person); // 输出: 私有方法
动态操作字段
java
Class<?> clazz = Person.class;
Person person = (Person) clazz.getDeclaredConstructor().newInstance();
// 操作私有字段
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true); // 突破 private
nameField.set(person, "李四");
Field ageField = clazz.getDeclaredField("age");
ageField.setAccessible(true);
ageField.set(person, 30);
// 获取字段值
String name = (String) nameField.get(person);
int age = (int) ageField.get(person);
System.out.println(name + " - " + age); // 李四 - 30
反射的实际应用
几乎所有主流框架都大量使用反射:
java
// Spring 通过反射创建 Bean 实例
public Object createBean(Class<?> beanClass) {
// 通过反射获取构造方法并实例化
Constructor<?> constructor = beanClass.getDeclaredConstructor();
constructor.setAccessible(true);
return constructor.newInstance();
}
// Spring 通过反射注入依赖
public void injectDependencies(Object bean) {
Field[] fields = bean.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Autowired.class)) {
Object dependency = getDependency(field.getType());
field.setAccessible(true);
field.set(bean, dependency);
}
}
}
动态代理(AOP 实现基础)
java
// JDK 动态代理(基于接口)
public class LogProxy implements InvocationHandler {
private Object target;
public Object bind(Object target) {
this.target = target;
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置通知: " + method.getName());
Object result = method.invoke(target, args);
System.out.println("后置通知: " + method.getName());
return result;
}
}
// 使用
UserService service = new UserServiceImpl();
UserService proxy = (UserService) new LogProxy().bind(service);
proxy.save(); // 自动添加日志
优点
-
动态性:运行时加载和操作类
-
灵活性:实现通用框架和工具
-
解耦:降低代码间的直接依赖
缺点
-
性能开销:比直接调用慢
-
安全问题:可突破封装限制
-
维护困难:代码可读性下降
-
编译时检查缺失:错误只能在运行时发现
核心应用场景
-
框架开发:Spring、MyBatis、Hibernate 等
-
注解处理:自定义注解处理器
-
动态代理:AOP 实现基础
-
ORM 映射:数据库记录与对象转换
-
序列化/反序列化:JSON、XML 解析
本章节就结束了!这些基础的Java用法。希望能帮助到大家!下面会更新Java中集合的用法。
请尽请期待!