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 中,引用类型 是指那些不是基本数据类型(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、类

接口和抽象类的区别

共同点:

  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中集合的用法。

请尽请期待!

相关推荐
弹简特35 分钟前
【Java项目-企悦抽】15-活动管理模块02-活动创建测试与活动列表实现
java·开发语言·springboot
斯内普吖37 分钟前
(开源)水果蔬菜商城实战指南 基于 Java + SSM + Vue + MySQL
java·vue.js·mysql·开源
wang_shu_mo_ran44 分钟前
Spring IoC和DI概念篇
java·后端·spring
小狼154541 小时前
拼多多订单数据导出 CSV 实战:接口分页、字段平铺与 5 个数据坑,多多开票助手
java·前端·javascript
Joe_Wang51 小时前
【从0到1学习JVM · 25】同样都要停顿所有线程,Parallel比Serial到底强在哪
java·jvm·学习·垃圾回收
wuminyu1 小时前
JVM虚拟线程的底层实现原理分析
java·linux·c语言·jvm·c++
弹简特2 小时前
【Java项目-企悦抽】13-奖品管理模块-奖品列表和创建奖品的实现
java·开发语言·网络·springboot
小蒜学长2 小时前
基于Spring Boot+Vue的“禾源”农产品销售系统设计与实现(代码+数据库+LW)
java·spring boot·后端·农产品销售系统·产品溯源
光年20002 小时前
Mock-Server开箱即用的Mock服务
java·maven·mock·jetty
弹简特2 小时前
【Java项目-企悦抽】14-活动管理模块01-创建活动的实现
java·开发语言·springboot