Java 基础

Java 面向对象编程的三大特性是封装、继承和多态:

封装

封装是将对象的属性和行为方法结合在一起,只暴露出一个可以被外界访问的接口 ,不允许外部对象直接访问对象的内部信息。

通常使用关键字 privateprotectedpublic 等来定义访问权限,以实现封装。

在代码层面,通常会通过 private 关键字修饰成员变量,然后提供 publicgettersetter 方法来控制访问权限。

继承

允许一个类(子类)继承另一个类(父类)的属性和方法的机制。Java 支持单继承,一个类只能直接继承一个父类。

子类可以复用 父类的代码,并且可以通过添加 新的方法或重写 已有的方法来扩展或改进功能,提高代码的可扩展性和可复用性。

例如,定义一个 Animal 父类拥有 eat() 方法,Cat 子类继承后,自动获得了 eat() 的能力,还可以新增 run() 方法或重写 eat() 实现具体细节。

多态

  1. 定义: 同一个行为具有多个不同表现形式。父类引用指向子类实例,调用方法时执行的是子类的实现。
  2. 多态有两种形式:编译时多态(静态多态)和运行时多态(动态多态)。
  • 编译时多态:这是通过方法的重载来实现的。在编译阶段,编译器根据方法的参数数量、类型或顺序来选择调用合适的方法。
  • 运行时多态:这是通过方法的重写向上转型 来实现的。具体表现为父类的引用指向子类的实例。编译时编译器,只看父类,也就是引用类型,运行时,JVM看子类,也就是运行类型,调用方法时执行子类重写后的逻辑,且只能调用父类中定义的方法。
    例如:它使得程序具有极高的扩展性 。例如,我们编写一个 feed(Animal animal) 方法,参数是父类 Animal。无论是传入 Dog 还是 Cat,方法都能正常工作。如果未来新增了 Elephant 类,feed 方法不需要做任何修改,

向上转型:父类引用指向子类实例,只能使用共有方法(方法名)

Java 方法 重写和重载的区别

Java 中的重载和重写是实现多态的两种不同方式

方法的重载 ,指的是在同一个类中 ,可以有多个方法具有相同的名称,但是它们的参数列表不同(参数的类型、个数、顺序)。编译器根据调用时传入的参数在编译期确定调用哪个方法,属于编译时多态。返回类型和访问修饰符可以不同,但不能仅靠它们区分重载。

方法的重写 ,指在子类和父类之间 ,子类可以重新定义父类中已经定义的方法,方法名、参数列表和返回类型都必须相同。重写的方法的访问级别不能低于被重写的父类方法,抛出的异常不能比父类更宽泛,重写的方法在运行时根据实际对象类型 确定调用,属于运行时多态

总结来说,重载关注的是方法的多样性,允许同一个类中存在多个同名方法。而重写关注的是方法的一致性,允许子类提供特定于其自己的行为实现。

接口和抽象类的区别

接口 和 抽象类 的共同点
  • 实例化 :接口和抽象类都不能直接实例化,只能被实现(接口)或继承(抽象类)后才能创建具体的对象。
  • 抽象方法 :接口和抽象类都可以包含抽象方法 (且只能由它们)。抽象方法没有方法体,必须在子类或实现类中实现
接口 和 抽象类 的区别
  • 继承和实现
    因为 Java 不支持多继承,一个类只能继承一个类(包括抽象类)。
    但一个类可以实现多个接口,一个接口也可以继承多个其他接口。
  • 成员变量
    接口中的成员变量只能是 public static final 类型的,不能被修改且必须有初始值。
    抽象类的成员变量可以有任何修饰符(private, protected, public),可以在子类中被重新定义或赋值。
  • 方法
    • Java 8 之前,接口中的方法默认是 public abstract ,也就是只能有方法声明。(只能包含抽象方法)
      自 Java 8 起,可以在接口中定义 default(默认实现) 方法和 static (静态)方法。
      自 Java 9 起,接口可以包含 private 方法。
    • 抽象类可以包含抽象方法(默认是protected,也可以使用public、protected、private)和非抽象方法。
  • 构造器:
    抽象类有构造器,接口没有构造器。
  • 设计目的
    接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。
    抽象类主要用于代码复用,强调的是所属关系。

String、StringBuffer、StringBuilder的区别

可变性:

String 是不可变的;StringBufferStringBuilder 是可变;

String 内部的字符数组使用了final修饰。

每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。

StringBuilderStringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串 ,不过没有使用 finalprivate 关键字修饰,最关键的是这个 AbstractStringBuilder 类还提供了很多修改字符串的方法比如 append 方法。

java 复制代码
abstract class AbstractStringBuilder implements Appendable, CharSequence {
    char[] value;
    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }
    //...
}

线程安全性:

String 中的对象是不可变的,也就可以理解为常量,线程安全。

StringBuffer 对方法加了同步锁(synchronized),所以是线程安全的

StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的

性能

每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。

StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。StringBuilder 也是一样,因此对于复杂的字符串操作(例如多次拼接、插入、删除), StringBufferStringBuilder 效率高于 String

因为StringBuilder 没有对方法加同步锁,相同情况下使用 StringBuilder 相比使用 StringBuffer 能获得 10%~15% 左右的性能提升(性能更高),但却要冒多线程不安全的风险。

三者总结:

  • 操作少量的数据: 适用 String
  • 单线程环境下,频繁进行字符串运算: 适用 StringBuilder
  • 多线程下,频繁进行字符串运算: 适用 StringBuffer

字符串常量池(String Pool)是 Java 中一个专门用于存储字符串字面量的特殊内存区域,它的主要作用是 提高内存利用率和运行时性能

使用的原因:

1.String在Java中使用频率很高

2.Java对Sring做了性能优化:可以不用new来实例化对象,而可以使用"字面量"

3.new产生的实例化对象,即使有对象属性一致也会创建多个对象

4.为了字符串的复用,创建字符串常量池(JVM堆中,jdk1.7以后)

5.具体实现:当使用字面量创建字符串时,字符串常量池将其对象引用进行保存,若之后创建相同属性的字符串,就讲字符串常量池中的引用进行返回

6.类似技术扩展:线程池,连接池,目的节省资源提高效率

String 两种创建方式的区别:

① 内存位置不同

字面量 "abc" :存放在 字符串常量池(String Pool)

new String("abc") :存放在 堆内存(Heap) 中,对象内部引用常量池里的字符串
② 对象是否复用(最重要区别)

字面量相同内容只会创建一个对象 ,多次使用会直接复用常量池里的同一个对象

new每次调用都会创建一个全新的对象,即使内容一样,也是不同对象

Java 中 == 和 equals 的区别

Java 中 ==equals 的区别?

==对于基本数据类型和引用数据类型的作用效果是不同的:

  • 对于基本数据类型来说,==比较的是值
  • 对于引用数据类型来说,==比较的是对象的内存地址,也就是两个引用是否指向内存中同一位置,用于判断它们是否是同一个对象。

equals 方法是定义在 Object 类中的一个方法,默认情况下equals 方法比较的是对象的引用(内存地址),与 == 相同。但在子类中通常被重写 ,比如 StringInteger 等,已经重写了equals以便用于比较对象的内容是否相等。

总结:

  • 一般来说,是使用 == 比较对象的引用(内存地址),用equals () 比较对象的内容。
  • 需要注意的是,在重写 equals 方法时,应同时重写 hashCode 方法,以保持 equals 和 hashCode 的一致性,否则在 HashMapHashSet 等集合中会出现逻辑上相等但无法正确存储或查找的问题。

使用 equals 时还要注意空指针安全 :若调用者为 null,会抛出 NullPointerException,建议使用 Objects.equals(a, b) 来避免。

Java常见的异常类有哪些

继承关系

常见异常:

Java 所有的异常和错误都继承自Throwable类。

  1. Error(错误)
  • 属于 JVM (Java虚拟机)层面的严重系统错误,程序无法通过代码捕获、处理和恢复。
  • 由 JVM 自动抛出,与代码逻辑无关。
  • 常见示例
    • StackOverflowError:栈溢出
    • OutOfMemoryError:内存溢出
  1. Exception(异常)
    异常分为两大类:运行时异常(非受检异常) 和 编译时异常(受检异常)。
    ① 运行时异常( 继承自RuntimeException类)
  • 运行时才抛出编译器不强制检查 / 处理
  • 通常是 代码逻辑错误 导致,属于程序自身问题。
  • 常见示例
    • NullPointerException:空指针异常
    • IndexOutOfBoundsException:数组 / 集合下标越界
    • ArithmeticException:数学运算异常(除零)
    • ClassCastException:类型转换异常
    • IllegalArgumentException:非法参数异常

② 编译时异常(继承自 Exception 类)

  • 编译期间就必须处理(必须捕获 或者 抛出),否则无法编译通过。
  • 通常由 外部环境 / 不可预知因素 导致。
  • 常见示例
    • IOException:IO 异常(基类)
    • FileNotFoundException:文件未找到
    • SQLException:数据库相关异常
    • ClassNotFoundException:类未找到

相关知识点:

抛出:

java 复制代码
public void myMethod(String param) throws IOException {
    if (param == null) {
        throw new IOException("参数不能为空");
    }
}

捕获

java 复制代码
try {
    // 可能抛出异常的代码
    myMethod(null);
} catch (IOException e) {
    e.printStackTrace();  // 或打印日志
}

自定义异常 ,只需要继承 Exception(成为编译时异常) 或 RuntimeException(成为运行时异常),不需要重写任何方法。


final 关键字有什么用

final 关键字有什么用?

final 就是不可变的意思,可以修饰变量、方法、和类。

  1. final 修饰的类,不能被继承,是最终类

    典型例子:StringInteger等包装类,System 类都是 final 的。

    注意: 一旦类被声明为 final,它的所有方法都隐式地是 final 的(但成员变量可以不是 final ,可以改变)

  2. final 修饰方法,表示该方法不能被重写

    注意:private 方法是隐式 final 的,因为子类无法访问它,也就无法重写

  3. final 修饰变量

    区分 基本数据类型 还是 引用类型

  • final 修饰基本数据类型,变量一旦被赋值,不能改变
  • final 修饰引用数据类型 (对象、数组),变量的引用地址不能改变(即不能再指向另一个对象),但是对象本身的内容(对象属性)是可以改变的。
    区分
  • final 修饰成员变量 时,成员变量必须显式赋值 ,且赋值只能发生一次:
    1、声明时直接赋值 2、构造方法中赋值(有多个构造方法,每个构造方法必须对该变量赋值)3、在构造代码块中赋值
  • final 修饰局部变量时,不要求声明时赋值,但是必须在使用前赋值,且只能赋值一次。

补充:

final 与 不可变性 的区别:

  • final 保证的是引用不可变(指向地址不可变)

  • 要实现对象不可变 (如 String),需要满足:

    1. 类用 final 修饰(防止被继承破坏不可变性)。
    2. 所有成员变量用 final 修饰且为 private
    3. 不提供修改成员变量的 setter 方法。
    4. 如果有返回可变对象的 getter 方法,需要进行防御性拷贝(返回副本),不直接返回内部可变对象,而是使用其副本,防止修改影响原对象内部状态。

Java 8 新特性

1. Lambda 表达式

Lambda 允许将函数作为方法参数传递,简化匿名内部类,支持函数式编程

java 复制代码
// 传统方式
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello");
    }
}).start();

// Lambda 表达式
new Thread(() -> System.out.println("Hello")).start();

// 带参数和返回值的 Lambda
List<String> list = Arrays.asList("a", "b", "c");
Collections.sort(list, (s1, s2) -> s1.compareTo(s2));

函数式接口

函数式接口是 只有一个抽象方法的接口 (可以有默认方法和静态方法),通常使用 @FunctionalInterface 注解标记。它是 Lambda 表达式的目标类型,任何需要函数式接口的地方都可以用 Lambda 表达式或方法引用来实现。

java 复制代码
@FunctionalInterface
interface Converter {
    String convert(String input);
}

// 使用 Lambda 实现
Converter toUpper = s -> s.toUpperCase();
System.out.println(toUpper.convert("hello")); // HELLO

方法引用

简化 Lambda 表达式,直接引用现有方法。

java 复制代码
// 静态方法引用
List<Integer> nums = Arrays.asList(1, 2, 3);
nums.forEach(System.out::println);  // 等同于 n -> System.out.println(n)

// 实例方法引用
String[] strings = {"a", "b", "c"};
Arrays.sort(strings, String::compareToIgnoreCase);

// 构造器引用
Supplier<List<String>> supplier = ArrayList::new;
List<String> list = supplier.get();
2. Stream API

Stream 支持对集合进行函数式操作,如过滤、映射等,支持串行与并行处理。

java 复制代码
List<String> list = Arrays.asList("apple", "banana", "cherry");

// 过滤 + 收集
List<String> filtered = list.stream()
    .filter(s -> s.startsWith("a"))
    .collect(Collectors.toList());

// 映射 + 求和
int totalLength = list.stream()
    .mapToInt(String::length)
    .sum();

// 并行处理
long count = list.parallelStream()
    .filter(s -> s.length() > 5)
    .count();
3. 新的日期时间 API(java.time)

替代了旧的 DateCalendar提供线程安全、更加清晰和易用的日期和时间处理方式

java 复制代码
// 获取当前日期
LocalDate today = LocalDate.now();

// 指定日期
LocalDate birthday = LocalDate.of(1990, Month.JANUARY, 1);

// 日期运算
LocalDate nextWeek = today.plusWeeks(1);

// 时间差
Period period = Period.between(birthday, today);

// 带时区的日期时间
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
4. 接口的默认方法与静态方法!!!

接口可定义默认实现和静态方法,增强扩展性,默认方法使用default关键字,静态方法使用static关键字

java 复制代码
interface Vehicle {
    default void print() {
        System.out.println("I am a vehicle");
    }
    
    static void blowHorn() {
        System.out.println("Blowing horn");
    }
}

class Car implements Vehicle {
    // 可以选择重写默认方法
}

// 使用
Car car = new Car();
car.print();                // I am a vehicle
Vehicle.blowHorn();         // Blowing horn
5. Optional 类

封装可能为 null 的对象,减少空指针异常 NullPointerException

java 复制代码
Optional<String> optional = Optional.ofNullable(getName()); // gatName 返回 字符串 或 null

// 如果存在则打印
optional.ifPresent(System.out::println);

// 获取值,否则返回默认值
String name = optional.orElse("default");

// 抛出异常
String value = optional.orElseThrow(IllegalArgumentException::new);

匿名内部类 和 lambda 表达式区别:

  • 匿名内部类

    它是一个类 。编译后会生成一个单独的 .class 文件(通常命名为 OuterClass$1.class)。它是在编译时被确定的,属于类的结构。

  • Lambda 表达式

    它不是类 。它是 Java 8 引入的函数式编程特性,依赖于 invokedynamic 指令。它在运行时才动态生成实现类,不会产生单独的 .class 文件,也没有类加载的过程,性能通常更优

相关推荐
cfm_29141 小时前
SpringBoot启动原理
java·spring boot·spring
ttwuai2 小时前
Go 后台接口 401/403 排查:JWT 过期、刷新请求和权限码怎么定位
开发语言·golang·状态模式
漫随流水2 小时前
Java——springboot web案例
java·开发语言·spring boot
从此以后自律2 小时前
Java Object 类常用方法全讲解
java·开发语言
XH华2 小时前
C++语言第二章类和对象(下)
开发语言·c++
念何架构之路2 小时前
Docker 容器状态机 学习
java·学习·docker
你有我备注吗2 小时前
SQL学习之查询
java·sql·学习
从零开始的代码生活_2 小时前
C++ stack、queue 与 priority_queue:容器适配器原理与实战
开发语言·c++·后端·学习·算法
techdashen3 小时前
Go 1.25 新增 `reflect.TypeAssert`:更直接、更高效地从 `reflect.Value` 取出类型值
开发语言·后端·golang
2zcode3 小时前
项目文档:基于MATLAB低采样率ISAR成像的快速稀疏重建算法研究
开发语言·算法·matlab