Java基础面试题

面向对象的三大特性是什么?

  • 封装:属性私有化,对外提供GET、SET方法
  • 继承:子类继承父类的属性和方法。
  • 多态 :父类型引用指向子类型对象,例如Animal a = new Cate()

重载(Overload)和重写(Override)的区别?

答案:

  • 重载:同一个类中,方法名相同,参数列表不同(类型、个数、顺序),与返回值无关。编译时多态。
  • 重写:子类中,方法名、参数列表、返回值类型(或子类)完全相同,访问权限不能更严格,不能抛出更大的异常。运行时多态。

接口和抽象类的区别?

答案:

特性 抽象类 接口(JDK8+)
关键字 abstract class interface
构造方法 可以有 不能有
实例变量 可以有 只能有 static final 常量
普通方法 可以有具体方法 抽象方法、默认方法(default)、静态方法
继承/实现 单继承 多实现
访问修饰符 任意 方法默认 public abstract(可写可不写)
新增方法 直接加,不影响子类 加抽象方法会破坏实现类,但加默认方法不影响
使用场景 表示一种"is-a"关系 表示一种"can-do"能力或契约

为什么 Java 是单继承,但可以实现多个接口?

  • 多继承会带来菱形继承问题(如 C++),造成方法调用的歧义和状态的重复。
  • 接口没有状态(属性),只有方法声明,因此多接口相对安全。

Java 8 后默认方法带来状态,如果两个没有继承关系 的接口提供了同名同参数的默认方法,实现类必须显式覆盖该方法,否则编译错误。

java 复制代码
interface A {
    default void go() { System.out.println("A"); }
}
interface B {
    default void go() { System.out.println("B"); }
}
class C implements A, B {
    // 编译错误!必须覆盖 go() 方法
    @Override
    public void go() {
        A.super.go();  // 选择调用 A 的默认方法
        // 或 B.super.go();
    }
}

内部类有哪些?

  • 成员内部类:普通内部类,可以访问外部类的所有成员(包括 private)。
  • 静态内部类:不持有外部类引用,相当于独立类。
  • 局部内部类:定义在方法内部,只能在该方法内使用。
  • 匿名内部类:没有名字的类,常用于实现接口

静态内部类和非静态内部类(成员内部类)的区别?TODO

特性 静态内部类 非静态内部类(成员内部类)
是否持有外部类引用 否 是(隐含 Outer.this)
实例化方式 new Outer.StaticInner() outer.new Inner()
能否有静态成员 可以 不能(JDK 16 前)
能否访问外部类静态成员 可以 可以
能否访问外部类实例成员 不能(无外部类引用) 可以
使用场景 辅助类,与外部类实例无关 需要访问外部类实例状态

this() 和 super() 可以同时出现在一个构造器中吗?为什么?

不能 。因为 this() 和 super() 都必须作为构造器的第一条语句,二者矛盾。

  • this() 调用当前类的其他构造器。
  • super() 调用父类的构造器。
  • 如果都没有显式写出,编译器会默认插入 super()。

代码块(静态、实例、构造器)的执行顺序?

父类 → 子类,静态 → 实例 → 构造器。

  1. 父类静态代码块
  2. 子类静态代码块
  3. 父类实例代码块
  4. 父类构造器
  5. 子类实例代码块
  6. 子类构造器

每次创建对象时,实例代码块和构造器都会执行;静态代码块只在类首次加载时执行一次。

static 关键字的作用有哪些?

  • 静态变量:所有该类的对象共享同一个变量,内存中只有一份。
  • 静态方法 :不需要创建对象,直接通过 类名.方法名() 就能调用。
  • 静态代码块:类加载时执行一次,用于初始化静态资源。
  • 静态内部类 :被 static 修饰的内部类。它不依赖外部类的对象,可以独立创建实例。

final 修饰的变量就是常量吗?修饰引用类型时有什么陷阱?

  • final 修饰基本类型:值不可变,是真正的常量。
  • final 修饰引用类型:不能指向其他对象,但对象内部属性可以改变。
java 复制代码
final StringBuilder sb = new StringBuilder("hello");
sb.append(" world");           // 允许
sb = new StringBuilder("new"); // 编译错误

Object 类中常用的方法有哪些?(至少说出 5 个)

  • equals(Object obj):判断对象是否相等。
  • hashCode():返回哈希码,与 equals 协同。
  • toString():返回对象的字符串表示,通常重写。
  • getClass():返回运行时类对象。
  • clone():创建并返回对象副本(需实现 Cloneable 接口)。
  • finalize():GC 前调用(已过时,不推荐使用)。
  • wait() / notify() / notifyAll():线程同步(多线程范畴,了解即可)。

枚举(enum)是什么?有什么优点?

  • 枚举是一种特殊的类,用于定义固定数量的常量集合(如季节、星期、状态码)。

  • 优点:

    • 类型安全,编译期检查。
    • 可以添加字段、方法、构造器,实现接口。
    • 单例模式的首选方案(Enum 保证序列化/反射安全)。
    • 自带 values() 方法遍历所有枚举值。

    说明:

    类型安全意味着:一个变量只能被赋予其类型所允许的值,任何不匹配的赋值或操作都会被编译器阻止,不会在运行时才出现意外。

    对于枚举而言:

    • 枚举变量只能指向该枚举类型中定义的常量之一,或者 null(虽然允许 null,但通常避免)。
    • 不能赋值为其他类型的值(如整数、字符串、其他枚举常量)。
    java 复制代码
    // 限制赋值:拒绝非法值
    public enum Season { SPRING, SUMMER, AUTUMN, WINTER }
    
    Season s = Season.SPRING;      // ✅ 正确
    Season s = SPRING;              // ✅ 正确(静态导入后)
    Season s = "SPRING";            // ❌ 编译错误:类型不匹配(String 无法转为 Season)
    Season s = 1;                   // ❌ 编译错误:int 无法转为 Season
    Season s = null;                // ✅ 允许(但使用时需判空)
    Season s = Season.MONDAY;       // ❌ 编译错误:MONDAY 不是 Season 的成员

Java 是值传递还是引用传递?

Java 只有值传递。

  • 基本类型传递的是值的副本,修改形参不影响实参。
  • 引用类型传递的是对象地址的副本,形参和实参指向同一对象,因此修改对象的属性会影响原对象;但重新赋值形参不会影响实参。
java 复制代码
class Person {
    String name;
    Person(String name) { this.name = name; }
}

public class ReferenceTest {
    public static void changeName(Person p) {
        p.name = "李四";  // 通过地址副本找到同一个对象,修改属性
    }

    public static void reassign(Person p) {
        p = new Person("王五");  // 只改变形参的指向,不影响实参
    }

    public static void main(String[] args) {
        Person person = new Person("张三");
        changeName(person);
        System.out.println(person.name);  // 李四 ------ 对象属性被修改

        reassign(person);
        System.out.println(person.name);  // 仍然是李四,实参未指向新对象
    }
}

1. Java 的基本数据类型有哪些?对应的包装类是什么?

基本类型 字节数 包装类
byte 1 Byte
short 2 Short
int 4 Integer
long 8 Long
float 4 Float
double 8 Double
char 2(Unicode) Character
boolean 通常 1 字节 Boolean

注意:boolean 在 JVM 中通常用 int 表示,单个 boolean 占 4 字节,数组中的每个元素占 1 字节。

什么是自动装箱与拆箱?

  • 装箱 :基本类型 → 包装类(如 int → Integer),编译期调用 Integer.valueOf()。
  • 拆箱 :包装类 → 基本类型(如 Integer → int),编译期调用 xxxValue()。

包装类常量池

  • Integer、Short、Byte、Long、Character 都缓存了 -128 ~ 127 之间的值。
  • 例:Integer a = 100; Integer b = 100; a == b 为 true;Integer c = 128; Integer d = 128; c == d 为 false(超出缓存范围,创建新对象)。

为什么需要包装类?

  1. 支持泛型和集合:泛型和集合只能使用引用数据类型,无法使用基本类型。
  2. 引用数据类型可以表达 null 值。
  3. 包装类提供了更丰富的API。

Integer 的 valueOf() 和 parseInt() 的区别?

  • parseInt(String s) :返回 int 基本类型 。
    解析字符串为十进制有符号整数,无法解析时抛出 NumberFormatException。
  • valueOf(String s) :返回 Integer 包装类对象 。
    内部实际调用了 parseInt() 获取 int 值,然后通过 Integer.valueOf(int) 装箱成 Integer 对象,因此会利用到 Integer 的缓存机制(-128~127)。

Integer 类中 int 的最大值和最小值是多少?如何获取?

  • 最大值 (MAX_VALUE) :2^31 - 1,即 2,147,483,647 。获取:Integer.MAX_VALUE;
  • 最小值 (MIN_VALUE) :-2^31,即 -2,147,483,648 。获取:Integer.MIN_VALUE;

对包装类进行算术运算(如 Integer i = 1; i++)发生了什么?

对包装类(如 Integer)进行算术运算(例如 i++ 或 i = i + 1),其本质是自动拆箱、基本类型运算、自动装箱三个步骤的结合。

short s = 1; s = s + 1; 和 short s = 1; s += 1; 哪个正确?为什么?

  • s = s + 1 编译错误 :s + 1 自动提升为 int,赋值给 short 需要强制转型。
  • s += 1 正确 :+= 是 Java 规定的复合赋值运算符,隐含了强制转型,相当于 s = (short) (s + 1)。

switch 语句支持哪些数据类型?

  • 原始支持:byte、short、int、char。
  • 包装类:Byte、Short、Character、Integer(自动拆箱)。
  • String(Java 7+)。
  • enum(枚举)。

switch 语句能作用于 long 类型吗?

在 Java 5 及之前:不能。Java 7 开始支持 String 和枚举,但仍然不支持 long 。支持的类型:byte、short、char、int、String、enum 以及它们的包装类(Byte、Short、Character、Integer)。

原因:switch 的底层使用 lookupswitch 或 tableswitch 指令,这些指令只支持 int 及以下类型。

break 与 continue 的区别?支持标签吗?

  • break:跳出当前循环体(或 switch),不再执行剩余迭代。
  • continue:跳过本次循环剩余语句,继续下一次迭代。
  • Java 支持带标签的 break 和 continue ,可以跳出多层循环。
java 复制代码
outer: for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        if (i == 1 && j == 1) break outer;
    }
}

& 和 && 的区别?| 和 || 的区别?

&&是短路与:左边为 false 时,右边不执行;&是逻辑与,两边都会执行。

||是短路或:左边为 true 时,右边不执行;|是逻辑或,两边都会执行。

float 和 double 的精度问题?如何解决?

  • float 和 double 是二进制浮点数,无法精确表示某些十进制小数(如 0.1),导致计算误差(如 0.1 + 0.2 == 0.3 返回 false)。
  • 解决方法 :使用 BigDecimal,尤其是金融计算。
java 复制代码
BigDecimal bd1 = new BigDecimal("0.1");
BigDecimal bd2 = new BigDecimal("0.2");
System.out.println(bd1.add(bd2).equals(new BigDecimal("0.3"))); // true

注意:BigDecimal 构造器应使用 String 参数,避免使用 double 构造器。

String为什么不可变?有什么好处?

  1. 底层数组被 final 和 private 修饰 :
    String 内部使用一个数组来存储字符数据(JDK 8 及以前是 char[] value,JDK 9 及以后优化为 byte[] value)。这个数组被 private 修饰,外部无法直接访问;同时被 final 修饰,保证了数组的引用一旦初始化就永远不能指向其他数组。
  2. 没有提供任何修改内部数组的方法
  3. 类本身被 final 修饰 :
    String 类被 final 关键字修饰,意味着它不能被继承。这防止了子类通过继承来篡改 String 的不可变特性。

好处:1、实现字符串常量池,极大节省内存 2、保证线程安全

String、StringBuffer、StringBuilder 的区别?

答案:

  • String :不可变(final 修饰字符数组),线程安全,适合少量字符串操作。
  • StringBuffer :可变,线程安全(方法使用 synchronized 修饰)。因为加锁的开销导致性能略低。适合多线程环境下的字符串操作。
  • StringBuilder:可变,非线程安全。性能最高,推荐在单线程下使用。

字符串常量池的作用?

  • 节省内存空间:通过共享相同内容的字符串,确保它们在内存中只保留一份,避免重复创建对象。
  • 提升程序性能 :可以直接使用 == 进行高效的引用地址比较,从而避免使用 .equals() 逐个字符比对的开销。

字符串常量池的位置(随 JDK 版本变化)

JDK 版本 存储位置 说明
JDK 6 及以前 方法区(永久代) 字符串常量池位于永久代(PermGen),与 Java 堆分离。
JDK 7 堆(Heap) 将字符串常量池从永久代移到堆中,因为永久代空间有限,移入堆可减少 OOM 风险。
JDK 8+ 堆(Heap) 永久代被元空间(Metaspace)取代,但字符串常量池仍保留在堆中。

总结(当前主流环境 JDK 8+) :字符串常量池存储在 堆 中

String s = new String("abc") 创建了几个对象?

  • 若字符串常量池中没有 "abc",则创建 2 个 :一个在堆中的 String 对象,一个在常量池中的 "abc" 字符串实例(实际上池中存储的是引用,但通常说创建了对象)。
  • 若池中已存在,则只创建 1 个 (堆中的 String 对象)。

== 和 equals() 的区别?

答案:

  • == :对于基本类型(int, char, boolean 等),比较的是值 ;对于引用类型(对象),比较的是内存地址(是否同一个对象)。
  • equals() :是 Object 类的方法,默认实现也是比较地址(==)。但很多类(如 String、Integer、Date)重写了 equals(),用于比较内容是否相等。

异常体系结构

复制代码
Throwable
├── Error(严重错误,程序无法处理,如 OOM、StackOverflowError)
└── Exception
    ├── 编译时异常(受检异常,如 IOException、SQLException)
    └── RuntimeException(如空指针异常、数组越界异常)

受检异常(Checked Exception)和非受检异常(Unchecked Exception)的区别?

核心区别在于编译器是否强制要求开发者处理该异常

对比项 受检异常 非受检异常
是否需要处理异常 必须 try-catch 或 throws 声明 不强制处理
继承关系 继承自 Exception 类,但不包括 RuntimeException 及其子类 包括 RuntimeException 及其子类,以及 Error 及其子类。
常见例子 IOException, ClassNotFoundException NullPointerException, 数组越界异常

3. finally 块一定会执行吗?

  • 正常情况下会执行(无论是否发生异常,也无论 try 中是否有 return)。
  • 以下几种情况不执行 :
    • 在 try 或 catch 中调用 System.exit(0)。
    • 虚拟机崩溃。
    • 守护线程被强制终止。

4. try-with-resources 是什么?

  • 用于自动关闭实现了 AutoCloseable 接口的资源(如流、数据库连接)。
  • 示例:
java 复制代码
try (FileInputStream fis = new FileInputStream("test.txt")) {
    // 使用 fis
} catch (IOException e) {
    e.printStackTrace();
}
// fis 自动关闭

final、finally、finalize 的区别?

答案:

  • final :关键字。
    • 修饰类:类不能被继承。
    • 修饰方法:方法不能被重写。
    • 修饰变量:变量一旦赋值后不可修改(基本类型值不变,引用类型不能指向其他对象,但对象内部状态可变)。
  • finally :异常处理的一部分。无论是否捕获异常,finally 块中的代码都会执行。通常用于释放资源(关闭文件、数据库连接)。
  • finalize() :Object 类的方法。垃圾回收器在回收对象前会调用此方法(仅一次)。JDK 9 已标记为弃用。

try-catch-finally 中,如果 catch 和 finally 都有 return,哪个生效?

finally 中的 return 会覆盖 catch 中的 return 。

同样,finally 中的 return 也会覆盖 try 中的 return。

这是危险行为,编译器会警告,实际开发中应避免在 finally 中使用 return。

throws 和 throw 的区别?

throw 在方法体内实际抛出异常对象,throws 在方法签名处声明可能抛出的异常类型。

大量异常 catch 块的顺序有什么要求?

在多个 catch 块中,子类异常必须写在父类异常之前,否则编译报错。

对性能而言,try-catch 放在循环内部和外部哪个好?为什么?

放在循环外部更好 。原因:如果 try-catch 放在循环内部,每次循环迭代都会进入和退出 try 块,JVM 需要反复检查异常表结构,增加额外开销;而放在外部只需建立一次异常处理上下文,避免了这种重复成本。此外,如果循环内抛出异常,外部 catch 可以统一处理后终止或继续循环,逻辑也更清晰。

如何自定义异常?什么时候需要自定义异常?

自定义异常只需继承 Exception(受检异常)或 RuntimeException(非受检异常),并提供构造方法(通常提供无参构造和带消息的构造)。

java 复制代码
// 自定义受检异常
public class BusinessException extends Exception {
    public BusinessException() {}
    public BusinessException(String message) {
        super(message);
    }
}

// 自定义非受检异常
public class ValidationException extends RuntimeException {
    public ValidationException(String message) {
        super(message);
    }
}

什么时候需要自定义异常?

​ 当标准异常(如 IllegalArgumentException)无法清晰表达业务错误含义时

异常处理中 e.printStackTrace() 有什么问题?生产环境应该怎么记录异常?

​ e.printStackTrace() 会输出到标准错误流(System.err)、难以集成日志系统且可能阻塞性能,生产环境应使用 SLF4J 等日志框架并调用 log.error("描述", e) 记录完整堆栈。

什么是泛型?泛型原理?类型擦除?List<String和List<Integer在运行中一样吗?什么情况下泛型擦除会出问题?

泛型是 JDK 5 引入的类型参数化机制,允许在定义类、接口、方法时使用类型参数(如 <T>),在使用时再传入具体类型(如 String、Integer)。

泛型的作用:

提供编译时类型安全检查:避免运行时出现 ClassCastException

避免强制类型转换:使用泛型后,从集合或泛型对象中获取数据就是目标类型,不需要强制类型转换。

代码复用:通过泛型编写通用算法/数据结构(如 ArrayList<T>),适用于多种类型。

Java 泛型的实现原理可以概括为:

编译时类型检查 + 运行时类型擦除。

类型擦除:编译后泛型信息被移除,替换为原始类型(Object或上限类型),并插入强制转换代码。

List 和 List 在运行时是同一个类型吗?为什么?

是,运行时会进行类型擦除,List<String> 和 List<Integer> 在运行时都是List。

类型擦除会导致哪些问题?

1、无法用泛型类型做 instanceof 检查

java 复制代码
// 编译错误:Illegal generic type for instanceof
if (obj instanceof List<String>) { ... }

只能检查原始类型:obj instanceof List。

2、不能创建泛型数组

java 复制代码
// 编译错误:Cannot create a generic array of T
T[] array = new T[10];

因为擦除后类型信息丢失,运行时无法知道应该分配什么类型的数组。可以创建 ArrayList<T> 代替。

3、不能实例化泛型类型(new T()、new T[])

java 复制代码
public <T> void create() {
    T obj = new T();   // 编译错误
}

泛型有哪些使用场景?

  1. 泛型类

场景:定义容器类、集合框架、工具类,使其能处理任意类型。

java 复制代码
public class Box<T> {
    private T item;
    public void set(T t) { this.item = t; }
    public T get() { return item; }
}
// 使用:Box<String> box = new Box<>();
  1. 泛型接口

场景:定义生成器、比较器、工厂等通用契约,让实现类指定具体类型。

java 复制代码
public interface Comparator<T> {
    int compare(T o1, T o2);
}
// 实现:class StringComparator implements Comparator<String>
  1. 泛型方法

场景:编写独立于类的通用算法,例如交换数组元素、集合转换等。

java 复制代码
public static <T> T getMiddle(T... a) {
    return a[a.length / 2];
}
// 调用:String s = getMiddle("a", "b", "c");

什么是原始类型(Raw Type)?使用原始类型有什么风险?

原始类型 是指在使用泛型类或泛型接口时,省略了类型参数的写法。例如:

java 复制代码
List list = new ArrayList();        // 原始类型,等价于 List<Object>
Comparable c = "hello";             // 原始类型

原始类型主要是为了 兼容 JDK 5 之前没有泛型的旧代码。

风险:丧失编译时的类型安全检查,可能导致运行时异常。

泛型通配符 ?、? extends T、? super T 的区别?

  • ?:无界通配符,表示任意类型,只能读不能写(除了 null)。
  • ? extends T:上界通配符,表示类型是 T 或 T 的子类。可以读为 T,但不能写入(因为不知道具体子类型)。
  • ? super T:下界通配符,表示类型是 T 或 T 的父类。可以写入 T 及其子类,但读取时只能得到 Object。
  • PECS 原则 :Producer Extends, Consumer Super(生产数据用 extends,消费数据用 super)。

怎么理解生产者?

在泛型编程中,当我们声明一个 List<? extends Number> 时,这个列表是作为参数传入当前方法的。对于当前方法而言,它不需要往里面塞东西,而是需要从中**获取(提取)已有的数据来进行计算或处理。
因为它是向当前方法
提供、产出(Produce)**数据的源头,所以被称为"生产者"。

1. 无界通配符 ?:我只负责"看",不负责"装"

场景:你需要写一个通用的工具方法,比如打印任意类型的集合,或者判断集合是否为空。你根本不关心集合里装的是猫、狗还是数字。

java 复制代码
public void printList(List<?> list) {
    // ✅ 能读:因为不管里面是什么,它一定是一个 Object
    for (Object obj : list) {
        System.out.println(obj);
    }
    
    // ❌ 不能写:编译器彻底懵了,它不知道这个 List 原本装的是 String 还是 Integer
    // list.add("hello"); // 编译报错!万一你传进来的是 List<Integer> 呢?
    
    // ✅ 唯一能写的只有 null,因为 null 是所有类型的子集
    list.add(null); 
}

核心逻辑 :用了 ?,等于告诉编译器:"我也不知道这里面是啥"。所以编译器为了绝对安全,除了 null,禁止你往里面放任何东西。

2. 上界通配符 ? extends T:只进不出(Producer)

场景 :假设有一个动物继承体系:Animal(父类)、Dog(子类)、Cat(子类)。你需要写一个方法,接收一堆动物并让它们叫。

java 复制代码
public void makeSound(List<? extends Animal> animals) {
    // ✅ 能读:虽然我不知道具体是 Dog 还是 Cat,但我知道它们肯定都是 Animal
    Animal a = animals.get(0); 
    a.shout(); 
    
    // ❌ 不能写:编译器会想,"万一你传进来的是一个专门装 Cat 的笼子(List<Cat>),
    // 我却让你塞进去一只 Dog,那这个笼子不就乱套了吗?"
    // animals.add(new Dog()); // 编译报错!
}

核心逻辑 :extends 限制了上限。因为无法确定具体的子类是谁,为了防止把"狗"塞进"猫"的笼子里,编译器直接禁止写入。它就像一个生产者(Producer),只负责往外提供数据。

3. 下界通配符 ? super T:只出不进(Consumer)

场景 :你需要写一个方法,往一个集合里批量添加一堆 Dog 对象。

java 复制代码
public void addDogs(List<? super Dog> dogs) {
    // ✅ 能写:编译器会想,"这个笼子要么是装 Dog 的,要么是装 Dog 的父类(比如 Animal 或 Object)的。
    // 那我往里面塞 Dog 或者 Dog 的子类(比如 Puppy),肯定都是安全的!"
    dogs.add(new Dog());
    dogs.add(new Puppy()); 
    
    // ❌ 不能精准读:编译器会想,"这个笼子可能是 List<Object>,
    // 我如果让你读出来直接当 Dog 用,万一里面其实混进了一个 Cat 怎么办?"
    // Dog d = dogs.get(0); // 编译报错!
    
    // ✅ 只能读成 Object,因为 Object 是所有类的老祖宗,最安全
    Object obj = dogs.get(0);
}

核心逻辑 :super 限制了下限。因为保证了容器里的东西一定比 Dog 更"大"或相等,所以往里塞 Dog 绝对没问题。它就像一个消费者(Consumer),只负责把数据吃进去。

无界通配符 ? 和 Object 作为类型参数有什么区别?

  • <?> (无界通配符):代表一个**"未知但确定"**的具体类型。它表示这个容器内部装的是某种特定的数据类型(可能是 String,也可能是 Integer),只是编译器目前不知道具体是哪一种。
  • Object :是一个明确且具体 的类型,它是 Java 类继承树的顶层父类。List<Object> 明确表示这个列表里存放的就是 Object 及其子类对象。

什么是反射?优缺点?应用场景?

答案:

  • 反射:在运行时动态获取类的完整信息(构造器、方法、属性)并操作对象,甚至可以访问私有成员。
  • 核心类 :Class、Constructor、Method、Field。

优点:在运行时动态创建对象、调用方法或访问字段,极大提升了代码的灵活性和框架的通用性。

缺点:性能较低(比直接调用慢,因为需要解析元数据)。破坏封装性,有安全隐患。

应用场景:

  • Spring / MyBatis / Hibernate等框架开发:例如动态创建 Bean、依赖注入、ORM 映射。
  • 动态代理(JDK 动态代理)
  • 序列化与反序列化(JSON/XML 转换)

如何获取 Class对象?

  • 类名.class:Class<Person> clazz = Person.class;
  • 对象.getClass():Person p = new Person(); Class<?> clazz = p.getClass();
  • Class.forName("全限定类名"):Class<?> clazz = Class.forName("com.example.Person");

如何通过反射创建对象?有哪两种方式?

方式一:使用 Class 类的 newInstance() 方法

java 复制代码
Class<?> clazz = Person.class;
Object obj = clazz.newInstance();  // 要求类必须有 public 无参构造方法
  • 特点 :只能调用 无参构造方法 ,且构造方法必须是 public 的。
  • 注意 :从 JDK 9 开始,此方法已被标记为 过时,推荐使用方式二。

方式二:使用 Constructor 类的 newInstance() 方法

java 复制代码
Class<?> clazz = Person.class;
// 获取指定参数类型的构造方法
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object obj = constructor.newInstance("张三", 25);  // 可传入实际参数
  • 特点 :可以调用 任意有参/无参构造方法 (包括 private 构造方法,需先 setAccessible(true))。
  • 优势 :更灵活、更安全,是 推荐的反射创建对象方式。

如何通过反射获取类的构造方法、方法和字段?

  1. 获取 Class 对象(反射的入口)
  2. 使用 Class 对象提供的 API 获取对应成员
  3. 通过 setAccessible(true) 访问私有成员
java 复制代码
// 获取Class对象
Class<?> aClass = Class.forName("com.atguigu.product.Reflect");
// 创建对象
Constructor<?> constructor = aClass.getDeclaredConstructor(String.class, int.class);
Object obj = constructor.newInstance("zhangsan", 16);
System.out.println(obj);
// 获取方法
Method method = aClass.getDeclaredMethod("doSome", String.class, String.class);
method.setAccessible(true);
method.invoke(obj,"userName","passWd");
// 获取属性
Field field = aClass.getDeclaredField("name");
field.setAccessible(true);
Object o = field.get(obj);
field.set(obj,"lisi");
System.out.println(field.get(obj));

反射中 setAccessible(true) 的作用是什么?有什么风险和注意事项?

通过 setAccessible(true) 访问私有成员,破坏封装性、有安全隐患(恶意代码可能通过反射获取敏感数据)、性能较低(JVM 禁用优化(如内联),调用速度比直接访问慢很多)。

反射与注解:如何获取类、方法、字段上的注解信息?

使用 Class、Method、Field 的 getAnnotation() ˌænəˈteɪʃn或 getAnnotations() 方法即可获取运行时注解信息。

注:被获取的注解必须使用 @Retention(RetentionPolicy.RUNTIME) 元注解标注,否则运行时无法读取。

java 复制代码
// 定义注解(运行时保留)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@interface MyAnnotation {
    String value();
}

// 使用注解
@MyAnnotation("Class")
public class Demo {
    @MyAnnotation("Field")
    private String name;

    @MyAnnotation("Method")
    public void run() {}
}

// 反射获取注解信息
public class AnnotationTest {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Demo.class;

        // 1. 获取类上的注解
        MyAnnotation classAnno = clazz.getAnnotation(MyAnnotation.class);
        System.out.println(classAnno.value()); // "Class"

        // 2. 获取字段上的注解
        Field field = clazz.getDeclaredField("name");
        MyAnnotation fieldAnno = field.getAnnotation(MyAnnotation.class);
        System.out.println(fieldAnno.value()); // "Field"

        // 3. 获取方法上的注解
        Method method = clazz.getDeclaredMethod("run");
        MyAnnotation methodAnno = method.getAnnotation(MyAnnotation.class);
        System.out.println(methodAnno.value()); // "Method"

        // 获取所有注解(包括继承的)
        Annotation[] allAnno = clazz.getAnnotations();
        // 仅获取直接声明的
        Annotation[] declaredAnno = clazz.getDeclaredAnnotations();
    }
}

注解(Annotation)的作用与使用场景?

  • 提供元数据信息,供编译器或运行时框架读取。
  • 场景 :
    • 编译检查(如 @Override 防止重写错误)
    • 框架配置(Spring 的 @Autowired、@Service)
    • 测试(JUnit 的 @Test)
    • 代码生成(如 Lombok 的 @Data 生成 getter/setter)

注解按运行机制分为哪几类?@Retention 的作用是什么?取值有哪些?

保留策略 对应 @Retention 取值 注解信息保留范围 典型应用
源码注解 SOURCE 仅保留在源代码中,编译时被丢弃 @Override、@SuppressWarnings(仅编译期检查,不进入字节码)
编译时注解 CLASS(默认值) 保留在 .class 字节码文件中,但运行时 JVM 无法读取 注解处理器(APT)在编译期处理,如 Lombok、ButterKnife;运行时不使用
运行时注解 RUNTIME 保留在字节码中,且运行时可通过反射读取 Spring (@Autowired)、JUnit (@Test)、自定义框架注解

@Retention 的作用

@Retention 是一个元注解 (用于注解的注解),用来指定被标注的注解可以保留到哪个阶段,即注解的生命周期。

@Retention 的取值

java 复制代码
public enum RetentionPolicy {
    SOURCE,   // 仅源代码,编译后丢弃
    CLASS,    // 保留到字节码,但运行时不可见(默认值)
    RUNTIME   // 保留到运行时,可通过反射获取
}

注意 :若要注解在运行时通过反射被读取,必须使用 @Retention(RetentionPolicy.RUNTIME)。

@Target 元注解的作用是什么?常用的 ElementType 有哪些?

@Target 元注解的作用

@Target 用于限制注解可以应用在哪些 Java 元素上 (如类、方法、字段等)。如果不指定 @Target,该注解可以用于任何位置。

常用的 ElementType 取值

ElementType 说明 示例
TYPE 类、接口、枚举、注解类型 public class MyClass {}
FIELD 字段(包括枚举常量) private String name;
METHOD 方法 public void run() {}
PARAMETER 方法参数 void say(String name) 中的 name
CONSTRUCTOR 构造方法 public MyClass() {}
LOCAL_VARIABLE 局部变量 int i = 0;
ANNOTATION_TYPE 注解类型 用于元注解
PACKAGE 包 package-info.java
TYPE_PARAMETER (Java 8+) 泛型类型参数 class Box<T> { } 中的 T
TYPE_USE (Java 8+) 类型使用(任何类型出现的地方) List<@NonNull String>

说明 :TYPE_USE 可出现在类型使用的任何位置(如泛型、强制转换、异常等),用于细化类型检查。

注解可以继承吗?@Inherited 的作用是什么?

注解本身不支持继承 :在 Java 中,注解不能使用 extends 关键字去继承另一个注解(注解的声明上不允许使用 extends)。每个注解都隐式继承自 java.lang.annotation.Annotation 接口,但这是语言内置的,不是用户可扩展的继承关系。

@Inherited 元注解的作用

@Inherited 用于控制注解是否对子类产生继承效果 。当一个注解被 @Inherited 修饰后:

  • 如果该注解被用在某个类 上,那么它的子类将自动继承该注解(即子类上也存在该注解)。
  • 只对类继承有效,对接口、方法、字段等无效。
  • 如果子类自己显式使用了该注解,则子类的注解会覆盖(不继承)父类的。

示例

java 复制代码
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface MyAnnotation {}

@MyAnnotation
class Parent {}

class Child extends Parent {}  // Child 也自动拥有 @MyAnnotation

如何定义一个自定义注解?注解中可以有哪些类型的属性?

使用 @interface 关键字声明,并通常配合元注解(如 @Retention、@Target)来指定保留策略和作用目标。

java 复制代码
// 定义一个运行时注解,可用于类和方法
@Retention(RetentionPolicy.RUNTIME)  // 保留到运行时
@Target({ElementType.TYPE, ElementType.METHOD})  // 可用于类和方法
public @interface MyAnnotation {
    // 属性
    String value() default "";       // 带有默认值
    int count() default 0;
    String[] tags() default {};
}

注解中可以包含哪些类型的属性?

注解中定义的属性实质上是无参数方法,其返回值类型只能是以下类型之一:

类型 示例
基本类型(8种) int、boolean、double 等
String String name();
Class Class<?> type();
枚举(enum) Level level();
注解类型 MyAnnotation2 ref();
上述类型的数组 String[] tags(); 或 int[] ids();

注意:

  • 不支持包装类型(如 Integer、Boolean),也不支持 Object 或集合类型(如 List)。
  • 可以为属性提供默认值(使用 default 关键字),也可以不提供(使用时必须显式赋值,除非有默认值)。
  • 特殊属性 value:如果注解只有一个 value 属性,使用时可以省略属性名,直接写值。

注解的属性可以有默认值吗?如何设置?

注解的属性可以有默认值 ,使用 default 关键字来设置。

Java IO 流分类?

  • 按流向 :输入流(InputStream/Reader)、输出流(OutputStream/Writer)。
  • 按单位 :字节流(InputStream/OutputStream)、字符流(Reader/Writer)。
  • 按功能 :节点流(直接连接数据源,如 FileInputStream)、处理流(包装其他流,如 BufferedInputStream)。

字符流和字节流的区别?

  • 字节流以 字节(8 bit) 为单位,适合处理二进制数据(图片、音视频等)。
  • 字符流以 字符(Unicode code unit) 为单位,适合处理文本数据,自动处理编码转换。
  • 字节流没有缓冲区,字符流内部使用缓冲区(需要 flush)。

什么是节点流?什么是处理流?它们的区别是什么?

  • 节点流 是直接连接数据源(比如文件、内存数组、网络连接)的流,它负责最底层的实际读写操作。像 FileInputStream、ByteArrayOutputStream 这些就是节点流。
  • 处理流 不能独立存在,它必须包装在另一个流之上,用于增强功能。比如 BufferedInputStream 提供缓冲、ObjectOutputStream 支持对象序列化、InputStreamReader 实现字节转字符等。

BIO、NIO、AIO 的区别?

模型 描述 特点 适用场景
BIO(同步阻塞) 一个连接一个线程,线程在读写数据时阻塞 简单,但线程开销大,并发低 连接数少、长连接的场景(如传统 Tomcat 7)
NIO(同步非阻塞) 基于 Selector,一个线程管理多个 Channel,轮询事件 高并发,编程复杂 Netty、Tomcat 8+
AIO(异步非阻塞) 由 OS 完成读写后回调通知 适合大量读写操作且耗时长的场景 目前在 Java 中应用较少

NIO 核心组件:

  • Channel (通道):双向数据传输的通道。
  • Buffer (缓冲区):所有数据都通过 Buffer 进行读写。
  • Selector (选择器):一个线程可以管理成千上万个 Channel。它通过轮询的方式检查哪些 Channel 已经准备好进行 I/O 操作(如可读、可写)。

Java IO 的四大抽象基类是什么?

抽象类 数据单位 方向 核心方法
InputStream 字节 输入 int read()、read(byte[] b)
OutputStream 字节 输出 void write(int b)、write(byte[] b)
Reader 字符 输入 int read()、read(char[] c)
Writer 字符 输出 void write(int c)、write(char[] c)

InputStream 的 read() 方法为什么返回 int 而不是 byte?

  • byte 的取值范围是 -128 ~ 127 ,其中 -1 是一个合法的字节值(对应十六进制 0xFF),选用byte时无法区分是字节值还是流结束。
  • int 的取值范围更大,可以用 -1 专门表示流结束,而 0 ~ 255 表示实际读取到的无符号字节值。

什么是序列化?transient 关键字的作用?

  • 序列化 :将类实现了Serializable 接口的 Java 对象转换为字节流,用于持久化或网络传输;反序列化是将字节流转换成Java对象。
  • transient 修饰的字段不会被序列化 ,反序列化后其值为默认值(null、0 等),适用于敏感信息字段。

hashCode() 和 equals() 的关系?

重写 equals 时必须重写 hashCode,否则在Hash集合(如 HashSet、HashMap)中可能无法正确去重。

  • 如果两个对象 equals 相等,则 hashCode 必须相等。
  • 反之,hashCode 相等不一定 equals 相等(哈希冲突)。

2. 浅拷贝和深拷贝的区别?

  • 浅拷贝:复制对象时,只复制基本类型和引用地址,不复制引用指向的对象。克隆后的对象与原对象共享引用类型属性。
  • 深拷贝:递归复制所有引用对象,使拷贝对象与原对象完全独立。
  • 实现:实现 Cloneable 接口并重写 clone() 方法(浅拷贝);深拷贝可通过序列化或手动递归实现。

3. 如何实现对象克隆?

  • 实现 Cloneable 接口,重写 clone() 方法(浅拷贝)。
  • 使用序列化(Serializable)实现深拷贝。

Java 8 中 java.time 包如何格式化日期?

java 复制代码
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = now.format(dtf);          // 2025-03-01 14:30:00
LocalDateTime parsed = LocalDateTime.parse("2025-03-01 14:30:00", dtf);

DateTimeFormatter 是线程安全的,可以定义为 static final 全局使用。

SimpleDateFormat 是线程安全的吗?如何处理?

不是线程安全的 ,因为其内部使用了 Calendar 对象,多线程并发调用 format() 或 parse() 会产生竞态条件。

解决方案:

  • 每次使用时 new 一个实例(开销小)。
  • 使用 ThreadLocal 包装。
  • 改用 Java 8+ 的 DateTimeFormatter(不可变、线程安全)。
java 复制代码
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime now = LocalDateTime.now();
String format = formatter.format(now);
System.out.println(format);

LocalDateTime localDateTime = LocalDateTime.parse(format, formatter);
System.out.println(localDateTime);

14. 什么是 native 方法?用途是什么?

  • native 关键字修饰的方法表示由本地代码(如 C/C++)实现,不提供 Java 方法体。
  • 用途 :
    • 调用底层操作系统 API。
    • 提高性能。某些计算密集型的算法(如矩阵运算、加密)用 C/C++ 实现,性能优于 Java。

15. Java 中的 Math.round(-1.5) 返回值是多少?

  • 四舍五入规则:向正无穷方向取整 (或常说"加 0.5 后向下取整")。
    Math.round(-1.5) = -1(因为 -1.5 + 0.5 = -1.0,向下取整得 -1)。
    验证:Math.round(-1.6) = -2。
  • 对于 float 返回 int,double 返回 long。

函数式接口有哪几个?

函数式接口是只包含一个抽象方法 的接口,可以用 @FunctionalInterface 注解标记(非强制)。Java 8 在 java.util.function 包中定义了四大基础函数式接口,以及许多扩展版本。

一、四大基础函数式接口
接口名 抽象方法 参数 返回值 用途
Function<T,R> R apply(T t) 1个 (T) R 类型转换:T → R
Predicate<T> boolean test(T t) 1个 (T) boolean 条件判断/过滤
Consumer<T> void accept(T t) 1个 (T) void 消费一个参数(无返回值)
Supplier<T> T get() 无 T 提供/生产数据
其他常见函数式接口(非 java.util.function 包)
接口 抽象方法 说明
Runnable void run() 无参无返回值,常用于线程任务
Callable<V> V call() throws Exception 有返回值,可抛异常
Comparator<T> int compare(T o1, T o2) 比较两个对象

单例模式的核心目标是什么?有哪些实现方式?

  • 目标:确保一个类仅存在一个实例,并提供全局唯一的访问入口。
  • 常见实现:饿汉式、懒汉式(线程不安全/安全)、双重检查锁(DCL)、静态内部类、枚举。
java 复制代码
// 懒汉式(同步方法,不推荐,性能差)
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

// 双检:DCL
public class Singleton {
    private static volatile Singleton instance;  // 必须 volatile
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {          // 第一次检查
            synchronized (Singleton.class) {
                if (instance == null) {  // 第二次检查
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

// 饿汉式(简单,类加载时即创建,线程安全)
public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

// 静态内部类实现(延迟加载,线程安全,推荐)
public class Singleton {
    private Singleton() {}

    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}

// 枚举实现(最简洁、绝对防止多次实例化,且防反射/序列化)
public enum Singleton {
    INSTANCE;
    
    // 可添加业务方法
    public void doSomething() {
        System.out.println("枚举单例");
    }
}
// 调用:Singleton.INSTANCE.doSomething();

总结对比表格

实现方式 线程安全 懒加载 效率 防反射/反序列化 推荐程度
饿汉式 ✅ ❌ 高 ❌(可被反射破坏) 适合简单场景
懒汉式(线程不安全) ❌ ✅ 高 ❌ 不推荐
懒汉式(同步方法) ✅ ✅ 低 ❌ 不推荐
DCL ✅ ✅ 高 ❌ 可用,需注意 volatile
静态内部类 ✅ ✅ 高 ❌ 推荐
枚举 ✅ ❌ 高 ✅ 最安全推荐

枚举单例的优势是什么?

天然避免线程安全问题,且能防止反射破坏单例、防止序列化/反序列化破坏单例。

  1. 线程安全:JVM 在类加载时创建枚举实例,天然线程安全。
  2. 防反射:Java 禁止通过反射创建枚举实例,会直接抛异常。
  3. 防反序列化:反序列化时只根据名称返回已有常量,不会新建对象。

double-check单例为什么要加volatile?

为了防止指令重排序,new Singleton() 不是原子操作,可能先分配内存、再将引用赋值给 instance、最后初始化对象。不加 volatile 时,其他线程可能拿到未初始化完成的对象。

volatile如何解决?

volatile 会禁止指令重排序 (通过内存屏障),保证对象完全初始化后 才将引用赋值给 instance,从而避免其他线程拿到半成品对象。

不加volatile关键词存在的问题:

instance = new Singleton(); 在 JVM 中不是原子操作,大致分为三步:

  1. 分配内存空间
  2. 调用构造方法,初始化对象
  3. 将 instance 引用指向内存地址

正常情况下,步骤 2 和 3 的顺序是 2 → 3。但 指令重排序 可能导致顺序变为 3 → 2(先分配地址,后初始化)。

如果此时另一个线程调用 getInstance(),发现 instance != null(指向了地址),就直接返回该对象,但该对象的构造方法可能还未执行 ,内部字段都是默认值(如 0、null),使用时就会出错。

枚举单例如何防止反射攻击?

反射的 newInstance() 会直接禁止创建枚举实例,抛出 IllegalArgumentException。

项目中你用的哪种?

看场景:一般无特殊要求用静态内部类 ;如果需要防御反射/序列化破坏,或作为工具类单例,用枚举。

静态内部类单例的实现原理是什么?和饿汉式有什么区别?

原理 :外部类加载时不加载内部类,首次调用 getInstance() 时 JVM 才会加载内部类并创建实例,利用 JVM 类加载机制保证线程安全和唯一性。

与饿汉式的区别:

维度 静态内部类 饿汉式
加载时机 懒加载(首次使用时) 立即加载(类加载时)
内存占用 不浪费 可能浪费(未使用也创建)

简单工厂、工厂方法、抽象工厂的区别是什么?

模式 简单工厂 工厂方法 抽象工厂
核心职责 一个工厂类负责创建所有类型的产品。 每个具体工厂只负责创建一种具体产品。 一个工厂负责创建一族(多个相关联)的产品。
扩展性 差。新增产品需要修改工厂类代码(违反开闭原则)。 好。新增产品只需新增对应的工厂类(符合开闭原则)。 横向扩展好,纵向扩展差。新增产品族容易,但新增产品种类需要修改所有工厂接口(违反开闭原则)。
解决问题 解决单一产品的简单创建,客户端无需知道具体类名。 解决单一产品等级结构(如不同品牌的手机)的创建。 解决多产品族(如不同品牌的手机+对应的充电器)的配套创建。

简单工厂:

java 复制代码
// 1. 产品接口
interface Phone {
    void call();
}
// 2. 具体产品
class IPhone implements Phone {
    public void call() { System.out.println("iPhone 打电话"); }
}
class HuaweiPhone implements Phone {
    public void call() { System.out.println("华为 打电话"); }
}
// 3. 简单工厂 (核心)
class SimpleFactory {
    // 只要传入名字,我就给你造出来
    public Phone createPhone(String type) {
        if ("apple".equals(type)) {
            return new IPhone();
        } else if ("huawei".equals(type)) {
            return new HuaweiPhone();
        } else {
            throw new IllegalArgumentException("不支持的手机");
        }
    }
}
// 4. 客户端调用
// SimpleFactory factory = new SimpleFactory();
// Phone p = factory.createPhone("apple");

工厂方法:

java 复制代码
// 1. 工厂接口 (每个工厂只负责造一种手机)
interface PhoneFactory {
    Phone createPhone();
}
// 2. 具体工厂
class IPhoneFactory implements PhoneFactory {
    public Phone createPhone() {
        return new IPhone(); // 专门造 iPhone
    }
}
class HuaweiFactory implements PhoneFactory {
    public Phone createPhone() {
        return new HuaweiPhone(); // 专门造 华为
    }
}
// 3. 客户端调用
// PhoneFactory factory = new IPhoneFactory();
// Phone p = factory.createPhone();

抽象工厂

java 复制代码
// 1. 定义多个产品接口
interface Phone { void call(); }
interface Charger { void charge(); }
// 2. 具体产品 (苹果族)
class IPhone implements Phone { public void call() { System.out.println("iPhone 打电话"); } }
class AppleCharger implements Charger { public void charge() { System.out.println("苹果 充电器"); } }
// 3. 具体产品 (华为一族)
class HuaweiPhone implements Phone { public void call() { System.out.println("华为 打电话"); } }
class HuaweiCharger implements Charger { public void charge() { System.out.println("华为 充电器"); } }

// 4. 抽象工厂 (核心:能造一整套东西)
interface AbstractFactory {
    Phone createPhone();
    Charger createCharger();
}
// 5. 具体工厂 (苹果全家桶工厂)
class AppleFactory implements AbstractFactory {
    public Phone createPhone() { return new IPhone(); }
    public Charger createCharger() { return new AppleCharger(); }
}
// 6. 具体工厂 (华为全家桶工厂)
class HuaweiFactory implements AbstractFactory {
    public Phone createPhone() { return new HuaweiPhone(); }
    public Charger createCharger() { return new HuaweiCharger(); }
}

// 7. 客户端调用
// AbstractFactory factory = new AppleFactory();
// Phone p = factory.createPhone();    // 造出 iPhone
// Charger c = factory.createCharger(); // 造出 苹果充电器

抽象工厂模式的应用场景?举例说明。

当你的系统需要创建"一整套"相互关联或依赖的产品,并且要保证它们能配套使用时使用抽象工厂。

举例:数据库的适配

很多大型系统为了兼容不同的客户环境,需要同时支持 MySQL、Oracle、DB2 等多种数据库。数据库操作通常涉及连接(Connection)和命令执行(Command/Statement)。

  • 定义抽象工厂 DBFactory,包含 createConnection() 和 createCommand()。
  • MySQL 工厂:专门创建 MySQL 连接 + MySQL 命令。
  • Oracle 工厂:专门创建 Oracle 连接 + Oracle 命令。

代理模式的核心作用是什么?分为哪几类?

代理模式的核心作用是:在不修改目标对象代码的前提下,通过引入代理对象来控制对目标对象的访问,从而实现对目标对象功能的增强或访问控制。

分为静态代理和动态代理,动态代理又包括 JDK 动态代理(基于接口)和 CGLIB 动态代理(基于子类)。Spring AOP 正是根据目标类是否实现接口,自动选择 JDK 或 CGLIB 代理。

相关推荐
kimnoic4 小时前
Python数据库学习心得:SQLite、MySQL、PostgreSQL优缺点
开发语言·python
别动我齐刘海5 小时前
C++ 工程化学习
java·linux·前端·c++·学习·机器学习·机器人
必须得开心呀5 小时前
python编写带界面的软件,需要安装哪些插件
开发语言·python
code2cat6 小时前
Java进阶篇之AtomicLongFieldUpdater:把累计额度留在对象字段中
java·cas·并发编程
WeiXin_DZbishe6 小时前
基于springboot城市公交实时运营与调度系统-计算机毕设 附源码【课程设计】66173
java·vue.js·spring boot·后端·python·php·课程设计
泡茶喝茶写代码6 小时前
A股量化数据工程:从 REST 接口到策略信号(第 18 篇):放量缩量异动扫描
java·python·股票数据api·股票数据api接口·股票api数据接口·股票量化数据api·股票量化数据接口
wewowilling6 小时前
认识微服务
java·运维·微服务·springcloud
代码什么用7 小时前
Java Web 7-10
java·servlet
沫璃染墨7 小时前
《Qt从零入门系列(十三):QSS样式表详解——从选择器与层叠规则到控件美化实战》
开发语言·css·c++·qt·ui·qt6.3·qss