JavaSE高级(一)

面向对象

设计对象并使用

  • 类(设计图):是对象共同特征的描述;
  • 对象:是真实存在的具体东西
  • 在Java中,必须先设计类,才能获得对象。

如何获得对象

arduino 复制代码
public class 类名 {
   1.成员变量(代表属性的,一般是名词)
   2.成员变方法(代表行为的,一般是动词)
}

类名 变量名=new 类名();

定义类的补充注意事项

  • 用来描述一类事物的类,专业叫做JavaBean类。在JavaBean类中,是不写main方法的。
  • 在以前,编写的main方法的类,叫做测试类。我们可以在测试类中创建JavaBean类的对象并进行赋值调用
  • 类名首字母建议大写,需要见名知意,驼峰模式
  • 一个java文件中可以定义多个class类,且只能一个类是public修饰,而且public修饰的类名必须成为代码文件名
  • 成员变量的完整定义格式是:修饰符 数据类型 变量名称=初始化值;一般无需指定初始化值,存在默认值。

封装

告诉我们,如何正确的设计对象的属性和方法 原则:对象代表什么,就得封装对应的数据,并提供数据对应的行为

好处:

  • 让编程变得简单,有什么事,找对象,调方法就行
  • 降低我们的学习成本,可以少学,少记,或者说压根不用学,不用记对象有哪些方法,有需要时去找就行

this关键字

概念

this是java中的一个重要关键字,表示当前对象的引用,可以理解为这个对象,它指向当前正在调用方法或构造器的对象实例

主要用途

  • 解决成员变量与局部变量同名问题
  • 在构造器中调用其他构造器(构造器重载)
    • 这种调用必须放在构造器的第一行
    • 一个构造器内只能调用一次其他构造器
  • 作为参数传递
  • 返回当前对象

使用限制

  • this只能在非静态方法中使用(静态方法没有this概念),因为静态方法属于类而非对象,调用时可能没有对象实例的存在
  • 不能在静态代码块中使用
  • 构造器调用(this())必须放在构造器第一行

实际应用

示例一

Builder模式

arduino 复制代码
public class User {
    private final String name;
    private final int age;

    private User(Builder builder) {
        this.name = builder.name;
        this.age = builder.age;
    }

    public static class Builder {
        private String name;
        private int age;

        public Builder name(String name) {
            this.name = name;
            return this;
        }

        public Builder age(int age) {
            this.age = age;
            return this;
        }

        public User build() {
            return new User(this);
        }
    }
}

    // 使用
    User user = new User.Builder()
            .name("张三")
            .age(25)
            .build();
示例二

链式调用

csharp 复制代码
public class Calculator {
    private int result;

    public Calculator add(int value) {
        this.result += value;
        return this;
    }

    public Calculator subtract(int value) {
        this.result -= value;
        return this;
    }

    public int getResult() {
        return this.result;
    }
}

    // 使用
    int result = new Calculator()
            .add(10)
            .subtract(5)
            .add(20)
            .getResult();

构造方法(Constructor)

构造方法是一种特殊的方法,用于创建对象时初始化对象。

基本概念

  • 作用:初始化对象
  • 特点:
    • 没有返回类型(连void都没有)
    • 方法名必须跟类名完全相同
    • 可以重载(一个类可以有多个不同参数的构造方法)

基本语法

arduino 复制代码
public class MyClass {
    // 无参构造方法
    public MyClass() {
        // 初始化代码
    }

    // 带参数的构造方法
    public MyClass(int param1, String param2) {
        // 初始化代码
    }
}

构造方法类型

  • 默认构造方法
    • 如果类中没有定义任何构造方法,编译器会自动提供一个无参的默认构造方法
    • 如果定义了至少一个构造方法,编译器不会提供默认的构造方法
  • 无参构造方法
  • 带参构造方法

super关键字

super是Java中的一个重要关键字,主要用于在子类中访问父类的成员(属性,方法和构造方法)

主要用途

  1. 调用父类的构造方法
    在子类构造方法中,必须首先调用父类的构造方法(显式或者隐式)。如果不显式调用,编译器会自动插入super()(调用父类无参构造方法)。
scala 复制代码
class Parent {
    Parent() {
        System.out.println("父类无参构造方法");
    }

    Parent(String name) {
        System.out.println("父类带参构造方法: " + name);
    }
}

class Child extends Parent {
    Child() {
        // 隐式调用 super();
        System.out.println("子类无参构造方法");
    }

    Child(String name) {
        super(name); // 显式调用父类带参构造方法
        System.out.println("子类带参构造方法");
    }
}

重要规则:

  • super()必须是子类构造方法的第一条语句
  • 如果父类没有无参构造方法,子类必须显式调用父类的其他构造方法
  1. 访问父类的成员变量
    当子类和父类有同名成员变量时,可以用super区分
scala 复制代码
class Parent {
    String name = "父类名称";
}

class Child extends Parent {
    String name = "子类名称";

    void printNames() {
        System.out.println(name);       // 输出子类名称
        System.out.println(super.name); // 输出父类名称
    }
}
  1. 调用父类方法

当子类重写了父类方法,但仍想调用父类的原始实现时

scala 复制代码
class Parent {
    void show() {
        System.out.println("父类show方法");
    }
}

class Child extends Parent {
    @Override
    void show() {
        super.show(); // 先调用父类方法
        System.out.println("子类show方法");
    }
}

使用注意事项

  • 必须在子类中使用:super只能在子类中使用,普通类中使用会报错
  • 构造方法调用规则:
    • 每个构造方法最多只能有一个super()调用
    • 如果没有显示调用,编译器会自动插入super()
    • super()和this()不能同时出现在一个构造方法中
  • 静态上下文中不可用:不能在静态方法,静态代码块中使用super

实际应用实例

typescript 复制代码
// 父类
class Animal {
    String name;

    Animal(String name) {
        this.name = name;
    }

    void eat() {
        System.out.println(name + "正在吃东西");
    }
}

// 子类
class Dog extends Animal {
    String breed;

    Dog(String name, String breed) {
        super(name); // 必须调用父类构造方法
        this.breed = breed;
    }

    @Override
    void eat() {
        super.eat(); // 先执行父类eat方法
        System.out.println(name + "正在啃骨头");
    }

    void display() {
        System.out.println("名字: " + super.name + ", 品种: " + breed);
    }
}

/**
 * @author ZG
 */ // 使用
public class Main {
    public static void main(String[] args) {
        Dog myDog = new Dog("旺财", "金毛");
        myDog.eat();
        myDog.display();
    }
}

输出结果:

旺财正在吃东西

旺财正在啃骨头

名字: 旺财, 品种: 金毛

常见面试题

  1. super和this的区别
    • this引用当前对象,super引用父类成员
    • this()调用本类其他构造方法,super()调用父类构造方法
    • this可以在任何非静态方法中使用,super只能在子类中使用
  2. 为什么子类构造方法必须调用父类构造方法?
    • 确保父类的初始化先于子类
    • 保证对象创建的完整性(先有父才有子)
  3. 能否在静态方法中使用super?
    • 不能,因为super与对象实例相关,而静态方法不依赖于实例

标准JavaBean

JavaBean是符合特定规范的java类,主要用于封装数据。以下是标准的JavaBean规范

基本要求:

  1. 公共无参构造方法:必须提供一个public修饰的无参构造方法
  2. 属性私有化:所有属性必须声明为private
  3. Getter/Setter方法:每个属性提供pubilc的getter和setter方法
  4. 可序列化:实现Serializable接口(可选但推荐)

标准示例

arduino 复制代码
public class User implements Serializable {
    // 属性私有化
    private Long id;
    private String username;
    private String password;
    private Integer age;

    // 公共无参构造方法
    public User() {
    }

    // 带参构造方法(可选)
    public User(Long id, String username, String password, Integer age) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.age = age;
    }

    // Getter 和 Setter 方法
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    // 可选:重写 toString() 方法
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + ''' +
                ", password='" + password + ''' +
                ", age=" + age +
                '}';
    }

    // 可选:重写 equals() 和 hashCode() 方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(id, user.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}

Getter/Setter方法命名规范

  1. Getter方法:
    • 命名格式:get+属性名(首字母大写)
    • 布尔类型可以使用is+属性名(首字母大写)
  2. Setter方法:
    • 命名格式:set+属性名(首字母大写)
    • 返回类型为void,带一个属性类型相同的参数

为什么使用JavaBean

  1. 封装性:隐藏实现细节,只暴露必要的访问方法
  2. 可重用性:标准化的类结果便于重用
  3. 工具支持:许多框架(如Spring、Hibernate)依赖JavaBean规范
  4. 可序列化:便于网络传输和持久化存储

JavaBean在框架中的使用

  1. Spring MVC:自动将请求参数绑定到 JavaBean
  2. Hibernate/JPA:将数据库记录映射为 JavaBean
  3. Jackson/Gson:将 JSON/XML 与 JavaBean 相互转换

对象内存图

基本内存结构

Java内存主要分为以下几个区域:

  • 栈(Stack):存储局部变量和方法调用
  • 堆(Heap):存储对象实例和数组
  • 方法区(Method Area):存储类信息,常量,静态变量等
  • 程序计数器:线程私有,记录当前线程执行的位置
  • 本地方法栈:为本地方法服务

对象内存图示例

ini 复制代码
class Person {
    String name;
    int age;

    void sayHello() {
        System.out.println("Hello, I'm " + name);
    }
}

public class Main {
    public static void main(String[] args) {
        Person p1 = new Person();
        p1.name = "张三";
        p1.age = 25;

        Person p2 = p1;
        p2.age = 30;

        p1.sayHello();
    }
}

详细解释

  1. 栈内存:
    • main方法的栈帧中包含局部变量p1和p2
    • p1存储的是Person对象在堆中的地址值(引用)
    • p2=p1使得p2也指向同一个对象
  2. 堆内存:
    • 存储实际的Person对象实例
    • 包含两个字段:name(引用类型)和age(基本类型)
    • name字段指向字符串常量池中的"张三"
  3. 方法区:
    • 存储Person类的元数据
    • 包含方法信息(如sayHello)
    • 包含字段的类型信息

重要结论

  1. 对象引用是地址:变量存储的是对象的引用(地址),不是对象本身
  2. 多引用指向同一对象:多个引用可以指定向同一个对象
  3. 基本类型直接存储值:基本类型变量直接存储值,不是引用
  4. 方法调用不影响原始引用:方法参数传递是值传递(对于引用类型是传递引用的值)

成员和局部

字符串(String)类

String

String是Java中最常用的类之一,用于表示和操作字符串。以下是关于String类的全面介绍:

String的基本特性

  • 不可变性:String对象一旦创建就不能被修改
  • 存储在字符串常量池:提高字符串的重用效率
  • final类:不能被继承
  • 实现了多个接口:SerializableComparable<String>CharSequence

创建String对象的两种方式

ini 复制代码
//方式一:使用字面量(推荐)  
String s1 = "hello";  // 存储在字符串常量池
String s2 = "hello";  // 重用常量池中的对象
System.out.println(s1 == s2); // true,相同引用
javascript 复制代码
//方式二:使用new关键字  
String s3 = new String("hello");  // 在堆中创建新对象
String s4 = new String("hello");  // 另一个新对象
System.out.println(s3 == s4); // false,不同引用

字符串常量池(String Pool)

  • 位置:Java之前位于方法区,Java之后位于堆内存
  • 作用:避免重复创建相同的字符串对象
  • intern()方法:将字符串显式放入常量池
ini 复制代码
String s5 = new String("world").intern();
String s6 = "world";
System.out.println(s5 == s6); // true

String常用方法

ini 复制代码
//获取信息
String str = "Hello World";
int length = str.length();       // 11
char ch = str.charAt(1);        // 'e'
int index = str.indexOf('o');   // 4
//字符串比较
String a = "hello";
String b = "HELLO";
boolean eq1 = a.equals(b);          // false
boolean eq2 = a.equalsIgnoreCase(b); // true
int cmp = a.compareTo(b);           // 32 (h > H)
//字符串操作
String sub = str.substring(6);      // "World"
String upper = str.toUpperCase();   // "HELLO WORLD"
String lower = str.toLowerCase();   // "hello world"
String trimmed = "  hello  ".trim(); // "hello"
//字符串检查
boolean starts = str.startsWith("Hello"); // true
boolean ends = str.endsWith("ld");       // true
boolean contains = str.contains("or");   // true
boolean empty = "".isEmpty();            // true
//字符串转换
char[] chars = str.toCharArray();
String joined = String.join("-", "a", "b", "c"); // "a-b-c"
String formatted = String.format("Name: %s, Age: %d", "Tom", 25);// Name: Tom, Age: 25

字符串拼接

ini 复制代码
// +操作符
String s1 = "a" + "b" + "c"; // 编译时优化为 "abc"
String s2 = s1 + 123;       // 运行时创建新对象
String s3 = "abc";
String s4 = new String("abc");
System.out.println(s1 == s3);// true
System.out.println(s1 == s4);// false
System.out.println(s2);// "abc123"
// concat() 方法
String s5="abcd";
String s6="abcdefg";
String s7 = s5.concat("efg");
System.out.println(s6==s7);// false

String的不可变性

  • 不可变性的实现原理:
    • Stirng类被声明为final
    • 内部字符数组private final char value[](java9之后改为了btye[])
    • 没有提供修改内部数组的方法
  • 不可变性的好处:
    • 线程安全
    • 可以缓存哈希值(提高HashMap等性能)
    • 安全性(如网络连接参数,文件路径等)
    • 适合作为HashMap的键

性能优化建议

  1. 循环中使用StringBuilder:
ini 复制代码
// 错误做法 - 每次循环创建新String对象
String result = "";
for (int i = 0; i < 100; i++) {
    result += i;
}

// 正确做法
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
    sb.append(i);
}
result = sb.toString();
  1. 使用字符串字面量而非new String():
ini 复制代码
// 推荐
String s1 = "hello";

// 不推荐 - 除非有特殊需要
String s2 = new String("hello");
  1. 合理使用intern()方法
  • 适合大量重复字符场景
  • 不适合少量或不重复字符串(会增加常量池的负担)

Java9+的String优化

从Java9开始,String内部改用byte[]存储:

  • 拉丁字符使用1个字节存储
  • 非拉丁字符使用2字节存储
  • 新增coder字段标识编码方式
  • 节省内存空间(特别是大量英文文本)

Stirng vs StringBuilder vs StringBuffer

特性 String StringBuilder StringBuffer
可变性 不可变 可变 可变
线程安全性 是(天然) 是(同步方法)
性能 低(修改操作)
使用场景 常量字符串 单线程字符串操作 多线程字符串操作

StringJoiner

StringJoiner是Java8引入的一个实用类,用于高效拼接字符串,支持分隔符、前缀和后缀,特别适合处理集合或数组的字符串拼接。

构造方法

构造方法 说明
StringJoiner(CharSequence delimiter) 仅指定分隔符
StringJoiner(CharSequence delimiter,CharSequence prefix,CharSequence suffix) 指定分隔符,前缀和后缀

代码示例一(仅使用分隔符):

csharp 复制代码
StringJoiner stringJoiner = new StringJoiner(",");
stringJoiner.add("java");
stringJoiner.add("go");
stringJoiner.add("c++");
System.out.println(stringJoiner.toString());//java,go,c++

代码示例二(指定分隔符、前缀和后缀):

csharp 复制代码
StringJoiner stringJoiner = new StringJoiner(",","[","]");
stringJoiner.add("java");
stringJoiner.add("go");
stringJoiner.add("c++");
System.out.println(stringJoiner.toString());//[java,go,c++]

常用方法

方法 说明
add(CharSequence element) 添加元素
merge(StringJoiner other) 合并另一个StringJoiner
setEmptyValue(CharSequence emptyValue) 设置空值时的默认输出
length() 返回当前字符串的长度
toString() 返回拼接后的字符串

add()方法

csharp 复制代码
StringJoiner sj = new StringJoiner("-");
sj.add("2023").add("10").add("25");
System.out.println(sj.toString()); // 输出:2023-10-25

merge()方法

csharp 复制代码
StringJoiner sj1 = new StringJoiner(",", "[", "]");
sj1.add("A").add("B");
StringJoiner sj2 = new StringJoiner("-", "(", ")");
sj2.add("C").add("D");
sj1.merge(sj2); // 合并 sj2 到 sj1
System.out.println(sj1); // 输出:[A,B,C-D]

setEmptyValue()方法

csharp 复制代码
StringJoiner sj = new StringJoiner(",");
sj.setEmptyValue("(空)"); // 如果没有添加元素,返回默认值
System.out.println(sj.toString()); // 输出:(空)
sj.add("Hello");
System.out.println(sj.toString()); // 输出:Hello

与String.join()对比

String.join()是StringJoiner的简化版本,适用于简单拼接:

ini 复制代码
List<String> languages = List.of("Java", "Python", "C++");
String result = String.join(" | ", languages);
System.out.println(result); // 输出:Java | Python | C++

但StringJoiner更灵活,支持前缀,后缀和动态添加元素

String.join()底层就是使用StringJoiner

在Stream中使用StringJoiner

Collectors.joining()底层就是StringJoiner

ini 复制代码
List<String> names = List.of("Alice", "Bob", "Charlie");
String joined = names.stream()
        .collect(Collectors.joining(", ", "[ ", " ]"));
System.out.println(joined); // 输出:[ Alice, Bob, Charlie ]

总结

场景 推荐方式
简单拼接 String.join()
需要前缀后缀 StringJoiner
Steram拼接 Collectors.joining()
动态拼接 StringJoiner

StringJoiner比StringBuilder更适用于结构化拼接(如CVS、JSON数组等),代码更简洁

字符串底层相关原理

字符串存储的原理
  • 直接赋值会复用字符串常量池中的
  • new出来的不会复用,而是开辟一个新的空间
==比较号比较的到底是什么?
  • 基本类型数据比较的是数据值
  • 引用数据类型比较的是地址值
字符串拼接的底层原理
"+"运算符拼接:

对于常量字符串拼接,编译器会在编译器直接合并(编译期优化)

arduino 复制代码
// 编译前
String s = "a" + "b" + "c";//编译后直接合并"abc"

对于变量拼接,java1.5+会直接转换为StringBuilder

scss 复制代码
// 编译前
String s1 = str1 + str2 + str3;
// 编译后(Java 1.5+)
String s = new StringBuilder().append(str1).append(str2).append(str3).toString();

性能问题:

在循环中使用+拼接会导致大量临时StringBuilder对象的创建

ini 复制代码
// 错误示范 - 每次循环都新建 StringBuilder
String result = "";
for (int i = 0; i < 100; i++) {
    result += i; // 等同于 result = new StringBuilder(result).append(i).toString();
}
StringBuilder和StringBuffer
go 复制代码
StringBuilder sb = new StringBuilder();
sb.append("Hello").append(" ").append("World");
String result = sb.toString();

底层原理:

  • 基于可变的char数组(JDK9+改为byte数组+编码标记)
  • 默认初始容量为16字符,不够时自动扩容(2倍+2)
  • StringBuffer是线程安全版(方法加synchronized

扩容机制:

ini 复制代码
// 伪代码展示扩容逻辑
if (需要的新容量 > 当前容量) {
    int newCapacity = (当前容量 << 1) + 2; // 2倍+2
    if (newCapacity < 需要的新容量) {
        newCapacity = 需要的新容量;
    }
    // 创建新数组并拷贝数据
    char[] newValue = new char[newCapacity];
    System.arraycopy(旧数组, 0, 新数组, 0, 当前长度);
StringJoiner原理
ini 复制代码
StringJoiner sj = new StringJoiner(",");
sj.add("A").add("B");
String result = sj.toString(); // "A,B"

底层实现:

  • 内部使用StringBuilder拼接
  • 维护分隔符、前缀、后缀等状态
  • add()方法会智能处理分隔符的添加
scss 复制代码
// StringJoiner 核心实现(简化版)
public StringJoiner add(CharSequence element) {
    if (第一次添加) {
        builder.append(prefix).append(element);
    } else {
        builder.append(delimiter).append(element);
    }
    return this;
}
concat()方法
ini 复制代码
String s = "Hello".concat(" World");

底层原理:

  • 每次调用都会创建新的String对象
  • 底层使用Arrays.copyOf()和System.arraycopy()
  • 性能不如StringBuilder连续拼接
JDK9+的改进

JDK9引入了紧凑字符串(Compact Strings)

  • 不再总是使用char数组(2字节/字符)
  • 根据内容自动选择:
    • Latin-1字符(1字节/字符)
    • UTF-16字符(2字节/字符)
  • StringBuilder也改为使用byte数组

最佳实践:

  1. 单行简单拼接:使用 +String.concat()
  2. 循环或多次:使用 StringBuilder
  3. 结构化拼接(CVS/JSON/SQL):使用StringJoiner
  4. 线程安全场景:使用StringBuffer
  5. 集合转字符串:使用String.join()Collectors.joining()
相关推荐
头孢头孢1 小时前
k8s常用总结
运维·后端·k8s
TheITSea1 小时前
后端开发 SpringBoot 工程模板
spring boot·后端
Asthenia04121 小时前
编译原理中的词法分析器:从文本到符号的桥梁
后端
Asthenia04122 小时前
用RocketMQ和MyBatis实现下单-减库存-扣钱的事务一致性
后端
Pasregret2 小时前
04-深入解析 Spring 事务管理原理及源码
java·数据库·后端·spring·oracle
Micro麦可乐2 小时前
最新Spring Security实战教程(七)方法级安全控制@PreAuthorize注解的灵活运用
java·spring boot·后端·spring·intellij-idea·spring security
returnShitBoy2 小时前
Go语言中的defer关键字有什么作用?
开发语言·后端·golang
Asthenia04122 小时前
面试场景题:基于Redisson、RocketMQ和MyBatis的定时短信发送实现
后端
Asthenia04123 小时前
链路追踪视角:MyBatis-Plus 如何基于 MyBatis 封装 BaseMapper
后端
Ai 编码助手3 小时前
基于 Swoole 的高性能 RPC 解决方案
后端·rpc·swoole