Java基础-学习-面试-校招-要点突击检查

这篇文章整理了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 中,引用类型 是指那些不是基本数据类型(intdoubleboolean 等)的变量类型。它们指向堆内存中的对象,而不是直接存储值。

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、类

接口和抽象类的区别

共同点:

  1. 都属于「抽象层」,都不能被直接实例化,只能被继承 / 实现;
  2. 都可以定义「抽象方法」,约束子类 / 实现类必须实现具体的业务逻辑。

不同点:

  1. 单继承多实现
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");
    }
}
  1. 接口的多继承
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() 必须遵循以下契约:

  1. 一致性 :在程序运行期间,只要对象的 equals 比较所用信息未修改,多次调用 hashCode() 应返回相同整数。

  2. equals 相等则 hashCode 必须相等 :如果 a.equals(b)true,则 a.hashCode() == b.hashCode() 必须成立

  3. hashCode 相等不一定 equals 相等 :如果 a.hashCode() == b.hashCode()a.equals(b) 可能为 true 也可能为 false(哈希冲突)。

4、为什么重写 equals() 必须重写 hashCode()

**核心原因:**保证哈希集合的正常工作

当对象作为 HashMap、HashSet、Hashtable 等哈希集合的键时,集合内部逻辑是:

  1. 存数据时

    • 调用 key.hashCode() 计算哈希值,确定存储的桶(bucket)位置

    • 在该桶内通过 equals() 判断是否存在相同键(避免重复)

  2. 取数据时

    • 调用 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();  // 自动添加日志

优点

  • 动态性:运行时加载和操作类

  • 灵活性:实现通用框架和工具

  • 解耦:降低代码间的直接依赖

缺点

  • 性能开销:比直接调用慢

  • 安全问题:可突破封装限制

  • 维护困难:代码可读性下降

  • 编译时检查缺失:错误只能在运行时发现

核心应用场景

  1. 框架开发:Spring、MyBatis、Hibernate 等

  2. 注解处理:自定义注解处理器

  3. 动态代理:AOP 实现基础

  4. ORM 映射:数据库记录与对象转换

  5. 序列化/反序列化:JSON、XML 解析

本章节就结束了!这些基础的Java用法。希望能帮助到大家!下面会更新Java中集合的用法。

请尽请期待!

相关推荐
郑州光合科技余经理2 小时前
海外O2O系统源码剖析:多语言、多货币架构设计与二次开发实践
java·开发语言·前端·小程序·系统架构·uni-app·php
工程师老罗8 小时前
Image(图像)的用法
java·前端·javascript
leo_messi948 小时前
2026版商城项目(一)
java·elasticsearch·k8s·springcloud
美味蛋炒饭.8 小时前
Tomcat 超详细入门教程(安装 + 目录 + 配置 + 部署 + 排错)
java·tomcat
dreamxian9 小时前
苍穹外卖day11
java·spring boot·后端·spring·mybatis
Veggie269 小时前
【Java深度学习】PyTorch On Java 系列课程 第八章 17 :模型评估【AI Infra 3.0】[PyTorch Java 硕士研一课程]
java·人工智能·深度学习
weisian1519 小时前
Java并发编程--19-ThreadPoolExecutor七参数详解:拒绝Executors,手动掌控线程池
java·线程池·threadpool·七大参数
csdn5659738509 小时前
Java打包时,本地仓库有jar 包,Maven打包却还去远程拉取
java·maven·jar
Demon_Hao9 小时前
JAVA通过Redis实现Key分区分片聚合点赞、收藏等计数同步数据库,并且通过布隆过滤器防重复点赞
java·数据库·redis