Post

java笔记

java笔记

类(Class)

  • 类是 Java 中的基本构建块,用于定义对象的属性和行为。
  • 类包含字段、方法、构造函数等组成部分。
  • 通过类可以创建对象,对象是类的实例。
  • 类可以继承、包含静态成员,并且可以通过访问修饰符控制可见性。

1. 类的定义

类是 Java 中的一种 模板 或蓝图,用于创建对象。它定义了对象的 属性(字段) 和 行为(方法)。

语法:

class 类名 {
    // 字段(属性)
    数据类型 字段名;

    // 方法(行为)
    返回类型 方法名(参数列表) {
        // 方法体
    }
}

2. 类的组成部分

一个类通常包含以下部分:

  1. 字段(Fields):
    1. 也称为属性或成员变量,用于描述对象的状态。
    2. 示例:
      • int age; String name;
  2. 方法(Methods):
    1. 用于定义对象的行为。
    2. 示例:
      • void sayHello() { System.out.println("Hello!"); }
  3. 构造函数(Constructors):
    1. 用于创建对象时初始化对象的状态。
    2. 示例:
      • public Person(String name, int age) { this.name = name; this.age = age; }
  4. 代码块(Blocks):
    1. 静态代码块和非静态代码块,用于初始化操作。
    2. 示例:
      • static { System.out.println("静态代码块"); }
  5. 内部类(Inner Classes):
    1. 定义在类内部的类。
    2. 示例:
      • class Outer { class Inner { } }

3. 类的示例

以下是一个简单的 Person 类的示例:

class Person {
    // 字段
    String name;
    int age;

    // 构造函数
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 方法
    void sayHello() {
        System.out.println("Hello, my name is " + name + " and I am " + age + " years old.");
    }
}

4. 创建对象

通过类可以创建对象。对象是类的实例。

语法:

类名 对象名 = new 类名(参数列表);

示例:

public class Main {
    public static void main(String[] args) {
        // 创建 Person 对象
        Person person = new Person("Alice", 25);

        // 调用对象的方法
        person.sayHello(); // 输出:Hello, my name is Alice and I am 25 years old.
    }
}

5. 类的访问修饰符

类的访问修饰符用于控制类的可见性:

  • public:类可以被任何其他类访问。
  • default(默认):类只能被同一个包中的类访问。

示例:

public class PublicClass { // 公共类
}

class DefaultClass { // 默认类
}

6. 类的命名规范

  • 类名通常使用 大驼峰命名法(PascalCase),即每个单词的首字母大写。
  • 示例:Person, Student, Car。

7. 类的继承

类可以通过继承(extends 关键字)从另一个类派生,从而复用父类的字段和方法。

示例:

class Animal {
    void eat() {
        System.out.println("Animal is eating.");
    }
}

class Dog extends Animal {
    void bark() {
        System.out.println("Dog is barking.");
    }
}

8. 类的静态成员

  • 使用 static 关键字修饰的字段或方法属于类本身,而不是类的实例。
  • 静态成员可以通过类名直接访问。

示例:

class Counter {
    static int count = 0; // 静态字段

    static void increment() { // 静态方法
        count++;
    }
}

9. 类的 final 修饰符

  • 使用 final 修饰的类不能被继承。
  • 示例:
    • final class FinalClass { }

10. static 关键字

(1)static 的作用

static 是 Java 中的一个关键字,用于修饰类的成员(字段、方法、代码块和内部类)。它的主要作用是:

  • 将成员与类本身关联,而不是与类的实例关联。
  • 静态成员属于类,而不是对象。

(2)静态字段(Static Fields)

  • 静态字段是类的共享变量,所有对象共享同一个静态字段。
  • 静态字段在类加载时初始化,且只有一份拷贝。

示例:

class Counter {
    static int count = 0; // 静态字段

    Counter() {
        count++; // 每次创建对象时,count 自增
    }
}

public class Main {
    public static void main(String[] args) {
        Counter c1 = new Counter();
        Counter c2 = new Counter();
        System.out.println(Counter.count); // 输出 2
    }
}

(3)静态方法(Static Methods)

  • 静态方法属于类,而不是对象。
  • 静态方法可以直接通过类名调用,无需创建对象。
  • 静态方法中不能直接访问非静态成员(字段或方法)。

示例:

class MathUtils {
    static int add(int a, int b) {
        return a + b;
    }
}

public class Main {
    public static void main(String[] args) {
        int result = MathUtils.add(5, 3); // 直接通过类名调用
        System.out.println(result); // 输出 8
    }
}

(4)静态代码块(Static Block)

  • 静态代码块在类加载时执行,且只执行一次。
  • 通常用于初始化静态字段。

示例:

class MyClass {
    static int value;

    static {
        value = 10; // 静态代码块初始化静态字段
        System.out.println("静态代码块执行");
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(MyClass.value); // 输出 10
    }
}

(5)静态内部类(Static Inner Class)

  • 静态内部类是定义在类内部的静态类。
  • 静态内部类不依赖于外部类的实例。

示例:

class Outer {
    static class Inner {
        void display() {
            System.out.println("静态内部类");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Outer.Inner inner = new Outer.Inner(); // 直接创建静态内部类对象
        inner.display(); // 输出:静态内部类
    }
}

(6)static 的总结

  • 静态成员属于类,而不是对象。
  • 静态字段是共享的,所有对象共享同一份拷贝。
  • 静态方法可以直接通过类名调用。
  • 静态代码块在类加载时执行。
  • 静态内部类不依赖于外部类的实例。

2. public 和 private 访问修饰符

(1)访问修饰符的作用

访问修饰符用于控制类、字段、方法和构造函数的访问权限。Java 中有四种访问修饰符:

  1. public:公开的,任何类都可以访问。
  2. private:私有的,只有本类可以访问。
  3. protected:受保护的,本类、子类和同一包中的类可以访问。
  4. default(默认):同一包中的类可以访问。

(2)public 修饰符

  • public 是最高级别的访问权限。
  • 被 public 修饰的成员可以在任何地方访问。

示例:

class MyClass {
    public int publicField = 10; // 公开字段

    public void publicMethod() { // 公开方法
        System.out.println("Public Method");
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass obj = new MyClass();
        System.out.println(obj.publicField); // 访问公开字段
        obj.publicMethod(); // 调用公开方法
    }
}

(3)private 修饰符

  • private 是最严格的访问权限。
  • 被 private 修饰的成员只能在本类中访问。

示例:

class MyClass {
    private int privateField = 20; // 私有字段

    private void privateMethod() { // 私有方法
        System.out.println("Private Method");
    }

    public void accessPrivate() {
        System.out.println(privateField); // 本类中可以访问私有字段
        privateMethod(); // 本类中可以调用私有方法
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass obj = new MyClass();
        // System.out.println(obj.privateField); // 错误:无法访问私有字段
        // obj.privateMethod(); // 错误:无法调用私有方法
        obj.accessPrivate(); // 通过公开方法间接访问私有成员
    }
}

(4)public 和 private 的区别

特性publicprivate
访问范围任何类都可以访问只有本类可以访问
字段公开字段,外部可以直接访问私有字段,外部无法直接访问
方法公开方法,外部可以直接调用私有方法,外部无法直接调用
构造函数公开构造函数,外部可以创建对象私有构造函数,外部无法创建对象

(5)public 和 private 的使用场景

  • public:
    • 用于暴露类的核心功能。
    • 例如,工具类的方法通常声明为 public。
  • private:
    • 用于隐藏类的内部实现细节。
    • 例如,类的字段通常声明为 private,并通过公开的 getter 和 setter 方法访问。

(6)public 和 private 的总结

  • public 是公开的,任何类都可以访问。
  • private 是私有的,只有本类可以访问。
  • 使用 public 和 private 可以实现封装,保护类的内部实现细节。

对象(Object)

1. 对象的定义

  • 对象是类的实例。
  • 类是对象的模板,而对象是类的具体表现。
  • 每个对象都有自己的状态(字段)和行为(方法)。

2. 对象的创建

通过 new 关键字可以创建对象。创建对象的过程包括:

  1. 分配内存:为对象分配内存空间。
  2. 初始化字段:调用构造函数初始化对象的状态。
  3. 返回引用:返回对象的引用(地址)。

语法:

类名 对象名 = new 类名(参数列表);

示例:

class Person {
    String name;
    int age;

    // 构造函数
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    void sayHello() {
        System.out.println("Hello, my name is " + name);
    }
}

public class Main {
    public static void main(String[] args) {
        // 创建对象
        Person person = new Person("Alice", 25);

        // 调用对象的方法
        person.sayHello(); // 输出:Hello, my name is Alice
    }
}

3. 对象的状态和行为

  • 状态:对象的字段(属性)表示对象的状态。
  • 行为:对象的方法表示对象的行为。

示例:

class Car {
    // 状态(字段)
    String brand;
    int speed;

    // 行为(方法)
    void accelerate() {
        speed += 10;
    }

    void displaySpeed() {
        System.out.println("Current speed: " + speed);
    }
}

public class Main {
    public static void main(String[] args) {
        Car myCar = new Car();
        myCar.brand = "Toyota"; // 设置状态
        myCar.accelerate();      // 调用行为
        myCar.displaySpeed();    // 输出:Current speed: 10
    }
}

4. 对象的引用

  • 对象是通过引用访问的。
  • 引用是指向对象内存地址的变量。
  • 多个引用可以指向同一个对象。

示例:

Person person1 = new Person("Alice", 25);
Person person2 = person1; // person2 和 person1 指向同一个对象

person2.name = "Bob";
System.out.println(person1.name); // 输出:Bob

5. 对象的生命周期

  • 创建:通过 new 关键字创建对象。
  • 使用:通过引用访问对象的字段和方法。
  • 销毁:当对象不再被引用时,垃圾回收器(Garbage Collector)会自动回收其内存。

6. 对象的比较

  • ==:比较两个引用是否指向同一个对象。
  • equals():比较两个对象的内容是否相等。

示例:

String str1 = new String("Hello");
String str2 = new String("Hello");

System.out.println(str1 == str2);      // false,引用不同
System.out.println(str1.equals(str2)); // true,内容相同

7. 对象的数组

可以创建对象的数组,数组中的每个元素都是一个对象。

示例:

Person[] people = new Person[3];
people[0] = new Person("Alice", 25);
people[1] = new Person("Bob", 30);
people[2] = new Person("Charlie", 35);

for (Person person : people) {
    person.sayHello();
}

8. 对象的总结

  • 对象是类的实例,具有状态和行为。
  • 通过 new 关键字创建对象。
  • 对象通过引用访问,多个引用可以指向同一个对象。
  • 对象的生命周期包括创建、使用和销毁。
  • 可以使用 == 和 equals() 比较对象。

继承(Inheritance)

继承(Inheritance) 是面向对象编程(OOP)的四大特性之一(其他三个是封装、多态和抽象)。继承允许一个类(子类)基于另一个类(父类)来构建,从而复用父类的字段和方法。下面我们详细讲解继承的概念、语法和使用场景。

1. 继承的定义

  • 继承是一种 “is-a” 关系,表示子类是父类的一种特殊类型。
  • 子类继承父类的字段和方法,并可以扩展或修改它们。
  • 通过继承,可以实现代码复用和层次化设计。

2. 继承的语法

在 Java 中,使用 extends 关键字实现继承。

语法:

class 父类 {
    // 父类的字段和方法
}

class 子类 extends 父类 {
    // 子类的字段和方法
}

3. 继承的示例

以下是一个简单的继承示例:

// 父类
class Animal {
    String name;

    void eat() {
        System.out.println(name + " is eating.");
    }
}

// 子类
class Dog extends Animal {
    void bark() {
        System.out.println(name + " is barking.");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.name = "Buddy"; // 继承自父类的字段
        dog.eat();  // 继承自父类的方法
        dog.bark(); // 子类自己的方法
    }
}

输出:

Buddy is eating.
Buddy is barking.

4. 继承的特点

(1)子类继承父类的字段和方法

  • 子类可以直接使用父类的非私有字段和方法。
  • 父类的私有字段和方法不能被直接访问,但可以通过公共方法间接访问。

示例:

class Animal {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

class Dog extends Animal {
    void bark() {
        System.out.println(getName() + " is barking."); // 通过公共方法访问私有字段
    }
}

(2)子类可以扩展父类

  • 子类可以添加新的字段和方法。
  • 子类可以重写(Override)父类的方法。

示例:

class Animal {
    void makeSound() {
        System.out.println("Animal is making a sound.");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("Dog is barking.");
    }

    void fetch() {
        System.out.println("Dog is fetching.");
    }
}

(3)构造函数的继承

  • 子类的构造函数会隐式调用父类的无参构造函数(super())。
  • 如果父类没有无参构造函数,子类必须显式调用父类的构造函数(super(参数))。

示例:

class Animal {
    String name;

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

class Dog extends Animal {
    Dog(String name) {
        super(name); // 显式调用父类的构造函数
    }
}

(4)单继承

  • Java 只支持单继承,即一个子类只能有一个父类。
  • 但可以通过接口实现多继承的效果。

5. 方法重写(Override)

  • 子类可以重写父类的方法,以提供不同的实现。
  • 重写的方法必须与父类方法具有相同的签名(方法名、参数列表和返回类型)。

示例:

class Animal {
    void makeSound() {
        System.out.println("Animal is making a sound.");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("Dog is barking.");
    }
}

6. super 关键字

  • super 用于引用父类的字段、方法和构造函数。
  • 在子类中,super 可以调用父类的构造函数或方法。

示例:

class Animal {
    String name;

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

    void eat() {
        System.out.println(name + " is eating.");
    }
}

class Dog extends Animal {
    Dog(String name) {
        super(name); // 调用父类的构造函数
    }

    void bark() {
        System.out.println(name + " is barking.");
    }

    void eatAndBark() {
        super.eat(); // 调用父类的方法
        bark();
    }
}

7. 继承的使用场景

  • 代码复用:通过继承复用父类的字段和方法。
  • 层次化设计:将通用的功能放在父类中,特殊的功能放在子类中。
  • 多态:通过继承实现多态(父类引用指向子类对象)。

8. 总结

  • 继承是一种“is-a”关系,子类继承父类的字段和方法。
  • 使用 extends 关键字实现继承。
  • 子类可以扩展父类,重写父类的方法,并通过 super 关键字访问父类的成员。
  • Java 只支持单继承,但可以通过接口实现多继承的效果。

封装(Encapsulation)

封装(Encapsulation) 是面向对象编程(OOP)的四大特性之一(其他三个是继承、多态和抽象)。封装的核心思想是将数据(字段)和行为(方法)绑定在一起,并隐藏内部实现细节,只暴露必要的接口。下面我们详细讲解封装的概念、实现方式和优点。

1. 封装的定义

  • 封装是将对象的字段和方法绑定在一起,并控制对它们的访问。
  • 通过封装,可以隐藏对象的内部实现细节,只暴露必要的接口。
  • 封装的核心是 访问控制,通过访问修饰符(如 private、public 等)来实现。

2. 封装的实现

在 Java 中,封装通常通过以下方式实现:

  1. 将字段声明为 **private**:限制外部直接访问字段。
  2. 提供公共的 getter** 和 **setter** 方法**:用于访问和修改字段的值。

3. 封装的示例

以下是一个简单的封装示例:

class Person {
    // 私有字段
    private String name;
    private int age;

    // 公共的 getter 方法
    public String getName() {
        return name;
    }

    // 公共的 setter 方法
    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        if (age > 0) { // 数据验证
            this.age = age;
        } else {
            System.out.println("Invalid age!");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("Alice");
        person.setAge(25);

        System.out.println("Name: " + person.getName());
        System.out.println("Age: " + person.getAge());
    }
}

输出:

Name: Alice
Age: 25

4. 封装的特点

(1)隐藏实现细节

  • 将字段声明为 private,外部无法直接访问。
  • 通过 getter 和 setter 方法控制对字段的访问。

示例:

private String name; // 外部无法直接访问

(2)数据验证

  • 在 setter 方法中可以添加数据验证逻辑,确保数据的有效性。

示例:

public void setAge(int age) {
    if (age > 0) { // 数据验证
        this.age = age;
    } else {
        System.out.println("Invalid age!");
    }
}

(3)灵活性

  • 可以在 getter 和 setter 方法中添加额外的逻辑(如日志记录、数据转换等),而不影响外部代码。

示例:

public void setName(String name) {
    System.out.println("Setting name to: " + name); // 日志记录
    this.name = name;
}

(4)提高安全性

  • 通过封装,可以防止外部代码直接修改对象的内部状态,从而提高程序的安全性。

5. 访问修饰符

Java 提供了四种访问修饰符,用于控制类、字段和方法的访问权限:

访问修饰符类内部同一包子类任何地方
private✔️❌❌❌
default✔️✔️❌❌
protected✔️✔️✔️❌
public✔️✔️✔️✔️

6. 封装的使用场景

  • 数据隐藏:隐藏对象的内部实现细节,只暴露必要的接口。
  • 数据验证:在 setter 方法中添加数据验证逻辑。
  • 灵活性:在 getter 和 setter 方法中添加额外的逻辑。
  • 安全性:防止外部代码直接修改对象的内部状态。

7. 总结

  • 封装是将对象的字段和方法绑定在一起,并控制对它们的访问。
  • 通过 private 字段和公共的 getter/setter 方法实现封装。
  • 封装可以隐藏实现细节、提高安全性、增加灵活性和支持数据验证。

多态(Polymorphism)

多态(Polymorphism) 是面向对象编程(OOP)的四大特性之一。多态的核心思想是同一个方法在不同对象中有不同的实现。通过多态,可以提高代码的灵活性和可扩展性。下面我们详细讲解多态的概念、实现方式和优点。

1. 多态的定义

  • 多态是指 同一个方法在不同对象中有不同的行为。
  • 多态分为两种:
    • 编译时多态(静态多态):通过方法重载实现。
    • 运行时多态(动态多态):通过方法重写和向上转型实现。

2. 多态的实现

(1)方法重载(Overload)

  • 方法重载是指在同一个类中定义多个同名方法,但参数列表不同(参数类型、参数个数或参数顺序)。
  • 方法重载是编译时多态。

示例:

class Calculator {
    int add(int a, int b) {
        return a + b;
    }

    double add(double a, double b) {
        return a + b;
    }

    int add(int a, int b, int c) {
        return a + b + c;
    }
}

public class Main {
    public static void main(String[] args) {
        Calculator calc = new Calculator();
        System.out.println(calc.add(1, 2));       // 调用 int add(int, int)
        System.out.println(calc.add(1.5, 2.5));  // 调用 double add(double, double)
        System.out.println(calc.add(1, 2, 3));   // 调用 int add(int, int, int)
    }
}

(2)方法重写(Override)

  • 方法重写是指子类重新定义父类的方法,提供不同的实现。
  • 方法重写是运行时多态。

示例:

class Animal {
    void makeSound() {
        System.out.println("Animal is making a sound.");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("Dog is barking.");
    }
}

class Cat extends Animal {
    @Override
    void makeSound() {
        System.out.println("Cat is meowing.");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal myDog = new Dog(); // 向上转型
        Animal myCat = new Cat(); // 向上转型

        myDog.makeSound(); // 调用 Dog 的 makeSound 方法
        myCat.makeSound(); // 调用 Cat 的 makeSound 方法
    }
}

输出:

Dog is barking.
Cat is meowing.

(3)向上转型(Upcasting)

  • 向上转型是指将子类对象赋值给父类引用。
  • 通过向上转型,可以实现运行时多态。

示例:

Animal myDog = new Dog(); // 向上转型

(4)向下转型(Downcasting)

  • 向下转型是指将父类引用强制转换为子类引用。
  • 向下转型需要显式类型转换,并且可能会抛出 ClassCastException。

示例:

Animal animal = new Dog(); // 向上转型
Dog dog = (Dog) animal;    // 向下转型
dog.makeSound();           // 调用 Dog 的 makeSound 方法

3. 多态的特点

(1)灵活性

  • 多态允许同一个方法在不同对象中有不同的实现,从而提高代码的灵活性。

(2)可扩展性

  • 通过多态,可以轻松扩展程序的功能,而无需修改现有代码。

(3)代码复用

  • 多态可以复用父类的方法,同时允许子类提供不同的实现。

4. 多态的使用场景

  • 方法重载:适用于需要处理不同类型或数量的参数。
  • 方法重写:适用于子类需要提供与父类不同的行为。
  • 向上转型:适用于需要统一处理不同子类对象的场景。

5. 总结

  • 多态是指同一个方法在不同对象中有不同的行为。
  • 多态分为编译时多态(方法重载)和运行时多态(方法重写和向上转型)。
  • 多态可以提高代码的灵活性、可扩展性和复用性。

接口(Interface)

抽象类(Abstract Class)

泛型(Generics)

集合框架(Collections Framework)

异常处理(Exception Handling)

多线程(Multithreading)

输入输出(I/O Streams)

注解(Annotations)

反射(Reflection)

Lambda 表达式(Lambda Expressions)

流式 API(Stream API)

模块化(Modularity)

并发编程(Concurrency)

网络编程(Networking)

JDBC(Java Database Connectivity)

JVM(Java Virtual Machine)

垃圾回收(Garbage Collection)

设计模式(Design Patterns)

单元测试(Unit Testing)

构建工具(Build Tools,如 Maven、Gradle)

ArrayList和接口

ArrayList类位于java.util包中,使用ArrayList需要:

import java.util.ArrayList

接口

ArrayList实现了 List 接口,基于动态数组的数据结构。与普通数组不同,ArrayList 可以根据需要自动调整大小,提供了更灵活的元素存储和操作方式。

在 Java 中,“实现了 List 接口” 是指一个类(如 ArrayList)通过 implements 关键字实现了 List 接口,并提供了 List 接口中定义的所有方法的具体实现。

什么是接口(Interface)?

接口是 Java 中的一种抽象类型,它定义了一组方法签名(方法名、参数列表和返回类型),但没有提供方法的具体实现。接口可以被类实现(implements),实现接口的类必须提供接口中所有方法的具体实现。

List 接口是 Java 集合框架中的一个核心接口,定义了一系列操作有序集合(如列表)的方法。

List 接口的作用

List 接口定义了一个有序集合的基本行为,包括:

  1. 有序性:元素按照插入顺序存储,可以通过索引访问。
  2. 允许重复元素:同一个元素可以多次添加到列表中。
  3. 允许 null 值:列表中可以存储 null 值。
  4. 动态大小:列表的大小可以根据需要自动调整。

List 接口的常用方法包括:

  • add(E e):添加元素。
  • get(int index):获取指定位置的元素。
  • set(int index, E element):修改指定位置的元素。
  • remove(int index):删除指定位置的元素。
  • size():返回列表的大小。
  • contains(Object o):检查是否包含某个元素。

什么是“实现了 List 接口”?

当一个类(如 ArrayList)实现了 List 接口时,它必须提供 List 接口中所有方法的具体实现。

List 接口的常见实现类

Java 集合框架中有多个类实现了 List 接口,以下是常见的实现类:

  1. ArrayList:
    1. 基于动态数组实现,支持快速随机访问,适合频繁读取数据的场景。
  2. LinkedList:
    1. 基于双向链表实现,适合频繁插入和删除元素的场景。
  3. Vector:
    1. 类似于 ArrayList,但是线程安全的(方法被 synchronized 修饰)。
  4. Stack:
    1. 继承自 Vector,实现了栈数据结构(后进先出)。
  • ArrayList:适合频繁读取数据的场景。
  • LinkedList:适合频繁插入和删除元素的场景。
  • Vector:适合多线程环境。
  • Stack:适合实现栈数据结构。

主要特点

  1. 动态数组:ArrayList 内部使用数组来存储元素,当数组容量不足时,会自动扩容。
  2. 有序集合:元素按照插入顺序存储,可以通过索引访问。
  3. 允许重复元素:ArrayList 允许存储重复的元素。
  4. 允许 null 值:ArrayList 可以存储 null 值。
  5. 非线程安全:ArrayList 不是线程安全的,如果多个线程同时访问并修改 ArrayList,需要外部同步。
  6. 快速随机访问:由于基于数组实现,ArrayList 支持通过索引快速访问元素。

Q:如何构建一个存储int类型数据的ArrayList?

1
List<Integer> arraylist = new ArrayList<>();

代码解析:我们惊讶地发现,new ArrayList后面跟了一个<>和(),这是要干嘛呢?这要涉及到 泛型(Generics) 和 构造函数调用 的语法!

  1. 尖括号 <> 的含义

尖括号 <> 是 Java 泛型语法的一部分,用于指定集合中存储的元素类型。

泛型的作用

  • 类型安全:泛型允许在编译时检查类型,避免运行时类型转换错误。
  • 代码复用:泛型可以编写通用的代码,适用于多种数据类型。

具体解释

  • List<Integer>:表示这是一个 List 集合,其中存储的元素类型是 Integer。
  • new ArrayList<>():表示创建一个 ArrayList 对象,其中的元素类型与左侧的 List<Integer> 一致(即 Integer)。

尖括号中的类型

  • 在 new ArrayList<>() 中,尖括号 <> 是 钻石操作符(Diamond Operator),从 Java 7 开始引入。
  • 钻石操作符允许在创建对象时省略泛型类型,编译器会根据上下文自动推断类型。
  • 例如,new ArrayList<>() 等价于 new ArrayList<Integer>()。
  1. 圆括号 () 的含义

圆括号 () 表示调用类的构造函数。

构造函数的作用

  • 构造函数用于创建对象并初始化对象的状态。
  • ArrayList 类有多个构造函数,最常用的是无参构造函数。
  1. Integer是什么?

Integer 是 Java 中的一个类,属于 包装类(Wrapper Class)。它用于将基本数据类型 int 封装成一个对象。Integer 类位于 java.lang 包中,是 Java 标准库的一部分。

1. Integer 的作用

基本数据类型 vs 包装类

  • 基本数据类型 int:
    • int 是 Java 中的一种基本数据类型,用于表示整数。
    • 它直接存储数值,效率高,但不是对象,因此不能用于需要对象的场景(例如集合类)。
  • 包装类 Integer:
    • Integer 是 int 的包装类,它将 int 封装成一个对象。
    • 通过 Integer,可以将 int 值作为对象使用,例如存储在集合中或调用对象的方法。

为什么需要 Integer?

  1. 集合类的需求:
    1. Java 的集合类(如 ArrayList、HashMap 等)只能存储对象,不能存储基本数据类型。
    2. 如果需要将 int 值存储在集合中,必须使用 Integer。
    3. List<Integer> list = new ArrayList<>(); list.add(10); // 10 被自动装箱为 Integer 对象
  2. 对象操作的需求:
  • Integer 提供了许多有用的方法,例如将字符串转换为整数、比较整数等。

​ int num = Integer.parseInt("123"); // 将字符串转换为整数

  1. 泛型的支持:
    1. 泛型(Generics)只能使用对象类型,不能使用基本数据类型。
    2. 例如,List<int> 是非法的,必须使用 List<Integer>。

2. Integer 的常用方法

Integer 类提供了许多静态方法和实例方法,以下是一些常用的方法:

静态方法

  1. Integer.parseInt(String s):
    1. 将字符串转换为 int 值。
  2. Integer.valueOf(String s):
    1. 将字符串转换为 Integer 对象。
  3. Integer.toString(int i):
    1. 将 int 值转换为字符串。

实例方法

  1. intValue():
    1. 将 Integer 对象转换为 int 值。
  2. compareTo(Integer anotherInteger):
    1. 比较两个 Integer 对象的大小。
  3. equals(Object obj):
    1. 比较两个 Integer 对象是否相等。
  4. 自动装箱和拆箱

Java 5 引入了 自动装箱(Autoboxing) 和 自动拆箱(Unboxing) 机制,使得基本数据类型和包装类之间的转换更加方便。

自动装箱

  • 将基本数据类型自动转换为对应的包装类对象。
  • Integer num = 10; // 自动装箱,等价于 Integer num = Integer.valueOf(10);

自动拆箱

  • 将包装类对象自动转换为基本数据类型。
  • int value = num; // 自动拆箱,等价于 int value = num.intValue();

输入输出

输入

在 Java 中,输入数据的方式有多种,常见的方式包括:

  1. 使用 Scanner 类(最常用)
  2. 使用 BufferedReader 类
  3. 使用命令行参数
  4. 使用 Console 类(适用于控制台输入)

使用 Scanner 类

Scanner 是 Java 中最常用的输入工具,位于 java.util 包中。它可以读取用户从键盘输入的数据,并支持多种数据类型的解析(如 int、double、String 等)。

使用步骤

  1. 导入 java.util.Scanner。
  2. 创建 Scanner 对象。
  3. 使用 Scanner 的方法读取输入。

常用方法

  • next():读取一个字符串(以空格为分隔符)。
  • nextLine():读取一行字符串(包括空格)。
  • nextInt():读取一个整数。
  • nextDouble():读取一个双精度浮点数。
  • nextBoolean():读取一个布尔值。

注意事项

  • next() 和 nextLine() 的区别:
    • next() 读取到空格或换行符为止,不会读取空格后的内容。
    • nextLine() 读取整行内容,包括空格。
  • 使用完 Scanner 后,最好调用 close() 方法关闭它,以释放资源。

使用 BufferedReader 类

BufferedReader 是一个高效的输入工具,位于 java.io 包中。它通常与 InputStreamReader 结合使用,适合读取大量数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class BufferedReaderExample {
    public static void main(String[] args) throws IOException {
        // 创建 BufferedReader 对象
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

        // 读取字符串
        System.out.print("请输入你的名字: ");
        String name = reader.readLine();
        System.out.println("你好, " + name + "!");

        // 读取整数
        System.out.print("请输入你的年龄: ");
        int age = Integer.parseInt(reader.readLine());
        System.out.println("你的年龄是: " + age);

        // 读取浮点数
        System.out.print("请输入你的身高 (米): ");
        double height = Double.parseDouble(reader.readLine());
        System.out.println("你的身高是: " + height + " 米");

        // 关闭 BufferedReader
        reader.close();
    }
}

使用步骤

  1. 导入 java.io.BufferedReader 和 java.io.InputStreamReader。
  2. 创建 BufferedReader 对象。
  3. 使用 readLine() 方法读取输入。

注意事项

  • BufferedReader 的 readLine() 方法返回的是字符串,如果需要其他类型的数据(如 int、double),需要手动转换(如 Integer.parseInt() 或 Double.parseDouble())。
  • BufferedReader 的效率比 Scanner 高,适合处理大量输入。

输出

在 Java 中,输出数据的方式有多种,常见的方式包括:

  1. 使用 System.out.println()(最常用)
  2. 使用 System.out.print()
  3. 使用 System.out.printf()(格式化输出)
  4. 使用 String.format()(格式化字符串)
  5. 使用 PrintWriter 类(输出到文件或网络)
  6. 使用 Logger 类(日志输出)

使用 String.format()(格式化字符串)

String.format() 与 System.out.printf() 类似,但它返回一个格式化后的字符串,而不是直接输出到控制台。

语法 String formattedString = String.format(格式字符串, 参数1, 参数2, ...);

1
2
3
4
5
6
7
8
9
10
11
12
13
public class StringFormatExample {
    public static void main(String[] args) {
        String name = "David";
        int age = 40;
        double height = 1.80;

        // 格式化字符串
        String formattedString = String.format("Name: %s, Age: %d, Height: %.2f meters", name, age, height);

        // 输出格式化后的字符串
        System.out.println(formattedString);
    }
}

Try-Catch

在 Java 中使用 try-catch 块来处理输入时可能发生的异常。这是因为 Java 的输入操作(如使用 Scanner 或 BufferedReader)可能会抛出异常(如 InputMismatchException 或 IOException),为了避免程序崩溃,通常会用 try-catch 来捕获并处理这些异常。

为什么需要 try-catch?

在 Java 中,输入操作可能会遇到以下问题:

  1. 输入类型不匹配:
    1. 例如,用户输入了一个字符串,但程序期望的是一个整数。
    2. 使用 Scanner 时,会抛出 InputMismatchException。
  2. 输入流异常:
    1. 例如,输入流被意外关闭或无法读取。
    2. 使用 BufferedReader 时,会抛出 IOException。
  3. 空指针异常:
    1. 例如,使用 Console 类时,如果当前环境不支持控制台输入,System.console() 会返回 null。

为了避免程序因为这些异常而崩溃,可以使用 try-catch 块来捕获并处理异常。

使用 try-catch 的常见场景

  1. 使用 Scanner 时的异常处理

Scanner 在读取输入时,如果用户输入的数据类型与期望的类型不匹配,会抛出 InputMismatchException。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.InputMismatchException;
import java.util.Scanner;

public class ScannerTryCatchExample {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        try {
            System.out.print("请输入一个整数: ");
            int number = scanner.nextInt(); // 如果输入的不是整数,会抛出 InputMismatchException
            System.out.println("你输入的整数是: " + number);
        } catch (InputMismatchException e) {
            System.out.println("输入错误:请输入一个有效的整数!");
        } finally {
            scanner.close(); // 确保关闭 Scanner
        }
    }
}
  1. 使用 BufferedReader 时的异常处理

BufferedReader 的 readLine() 方法可能会抛出 IOException,需要捕获并处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class BufferedReaderTryCatchExample {
    public static void main(String[] args) {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

        try {
            System.out.print("请输入你的名字: ");
            String name = reader.readLine(); // 可能会抛出 IOException
            System.out.println("你好, " + name + "!");
        } catch (IOException e) {
            System.out.println("输入错误:无法读取输入!");
        } finally {
            try {
                reader.close(); // 确保关闭 BufferedReader
            } catch (IOException e) {
                System.out.println("关闭输入流时发生错误!");
            }
        }
    }
}

HashMap

HashMap 是 Java 集合框架中的一个重要类,位于 java.util 包中。它实现了 Map 接口,基于哈希表的数据结构。HashMap 用于存储键值对(key-value pairs),并提供了快速的查找、插入和删除操作。

主要特点

  1. 键值对存储:
    1. HashMap 存储的数据是键值对形式,每个键(key)对应一个值(value)。
    2. 键和值都可以是任意类型的对象(包括 null)。
  2. 无序集合:
    1. HashMap 不保证元素的顺序,插入顺序和遍历顺序可能不一致。
  3. 允许 null 键和 null 值:
    1. HashMap 允许一个 null 键和多个 null 值。
  4. 非线程安全:
    1. HashMap 不是线程安全的。如果多个线程同时访问并修改 HashMap,需要外部同步。
  5. 基于哈希表:
    1. HashMap 使用哈希表来存储数据,通过键的哈希值快速定位存储位置。
  6. 快速查找:
    1. 在理想情况下,HashMap 的查找、插入和删除操作的时间复杂度为 O(1)。

常用方法

以下是 HashMap 的一些常用方法:

  1. 添加键值对
    1. put(K key, V value):将键值对添加到 HashMap 中。如果键已存在,则替换对应的值。
    2. HashMap<String, Integer> map = new HashMap<>(); map.put("Apple", 10); map.put("Banana", 20);
  2. 获取值
    1. get(Object key):返回指定键对应的值。如果键不存在,则返回 null。
  3. 删除键值对
    1. remove(Object key):删除指定键对应的键值对。
  4. 检查是否包含键或值
    1. containsKey(Object key):检查是否包含指定键。
    2. containsValue(Object value):检查是否包含指定值。
  5. 获取大小
    1. size():返回 HashMap 中键值对的数量。
  6. 清空 HashMap
    1. clear():移除 HashMap 中的所有键值对。
  7. 遍历 HashMap
    1. 可以使用 keySet()、values() 或 entrySet() 来遍历 HashMap。

LinkedList

LinkedList 是 Java 集合框架中的一个类,位于 java.util 包中。它实现了 List 接口和 Deque 接口,基于双向链表的数据结构。与 ArrayList 不同,LinkedList 在插入和删除操作上具有更高的效率,但在随机访问元素时性能较差。

LinkedList 的特殊操作主要来自于它实现了 Deque 接口,因此支持 双向队列 的操作。以下是一些 LinkedList 的特殊操作:

1. 双向队列操作

LinkedList 实现了 Deque 接口,因此支持在队列的两端进行插入、删除和访问操作。

常用方法

在队列头部操作

  • addFirst(E e):在队列头部插入元素。
  • offerFirst(E e):在队列头部插入元素,成功返回 true。
  • removeFirst():移除并返回队列头部的元素,如果队列为空则抛出异常。
  • pollFirst():移除并返回队列头部的元素,如果队列为空则返回 null。
  • getFirst():返回队列头部的元素,如果队列为空则抛出异常。
  • peekFirst():返回队列头部的元素,如果队列为空则返回 null。

在队列尾部操作

  • addLast(E e):在队列尾部插入元素。
  • offerLast(E e):在队列尾部插入元素,成功返回 true。
  • removeLast():移除并返回队列尾部的元素,如果队列为空则抛出异常。
  • pollLast():移除并返回队列尾部的元素,如果队列为空则返回 null。
  • getLast():返回队列尾部的元素,如果队列为空则抛出异常。
  • peekLast():返回队列尾部的元素,如果队列为空则返回 null。

示例代码

import java.util.LinkedList;

public class LinkedListDequeExample {
    public static void main(String[] args) {
        // 创建 LinkedList
        LinkedList<String> deque = new LinkedList<>();

        // 在头部插入元素
        deque.addFirst("Apple");
        deque.offerFirst("Banana");

        // 在尾部插入元素
        deque.addLast("Orange");
        deque.offerLast("Mango");

        // 查看头部和尾部元素
        System.out.println("头部元素: " + deque.getFirst()); // 输出 Banana
        System.out.println("尾部元素: " + deque.getLast());  // 输出 Mango

        // 移除头部和尾部元素
        System.out.println("移除的头部元素: " + deque.removeFirst()); // 输出 Banana
        System.out.println("移除的尾部元素: " + deque.removeLast());  // 输出 Mango

        // 查看剩余元素
        System.out.println("剩余元素: " + deque); // 输出 [Apple, Orange]
    }
}

2. 栈操作

LinkedList 还可以用作栈(后进先出,LIFO),因为它实现了 Deque 接口。

常用方法

  • push(E e):将元素压入栈顶(相当于 addFirst)。
  • pop():移除并返回栈顶元素(相当于 removeFirst)。
  • peek():返回栈顶元素,但不移除(相当于 peekFirst)。

示例代码

import java.util.LinkedList;

public class LinkedListStackExample {
    public static void main(String[] args) {
        // 创建 LinkedList 作为栈
        LinkedList<String> stack = new LinkedList<>();

        // 压入元素
        stack.push("Apple");
        stack.push("Banana");
        stack.push("Orange");

        // 查看栈顶元素
        System.out.println("栈顶元素: " + stack.peek()); // 输出 Orange

        // 弹出元素
        System.out.println("弹出的元素: " + stack.pop()); // 输出 Orange
        System.out.println("弹出的元素: " + stack.pop()); // 输出 Banana

        // 查看剩余元素
        System.out.println("剩余元素: " + stack); // 输出 [Apple]
    }
}

3. 链表操作

LinkedList 基于双向链表实现,因此支持一些链表特有的操作。

常用方法

  • listIterator(int index):返回从指定位置开始的列表迭代器。
  • descendingIterator():返回一个逆序迭代器,从尾部向头部遍历。

示例代码

import java.util.LinkedList;
import java.util.ListIterator;

public class LinkedListIteratorExample {
    public static void main(String[] args) {
        // 创建 LinkedList
        LinkedList<String> list = new LinkedList<>();
        list.add("Apple");
        list.add("Banana");
        list.add("Orange");

        // 使用 ListIterator 遍历
        ListIterator<String> iterator = list.listIterator();
        while (iterator.hasNext()) {
            System.out.println("正序遍历: " + iterator.next());
        }

        // 使用 descendingIterator 逆序遍历
        System.out.println("逆序遍历:");
        iterator = list.listIterator(list.size());
        while (iterator.hasPrevious()) {
            System.out.println("逆序遍历: " + iterator.previous());
        }
    }
}

4. 性能特点

  • 插入和删除:
    • 在链表头部或尾部插入、删除元素的时间复杂度为 O(1)。
    • 在链表中间插入、删除元素的时间复杂度为 O(n),因为需要遍历链表。
  • 随机访问:
    • 通过索引访问元素的时间复杂度为 O(n),因为需要从头或尾遍历链表。
  • 内存占用:
    • 每个元素需要额外的内存空间存储前驱和后继指针,因此内存占用比 ArrayList 高。

5. 适用场景

  • 需要频繁在列表头部或尾部插入、删除元素。
  • 需要实现栈、队列或双向队列。
  • 不需要频繁随机访问元素。

总结

LinkedList 的特殊操作主要来自于它实现了 Deque 接口,支持双向队列和栈的操作。以下是它的核心特点:

  1. 双向队列操作:
    1. 支持在队列头部和尾部插入、删除和访问元素。
  2. 栈操作:
    1. 支持 push、pop 和 peek 等栈操作。
  3. 链表操作:
    1. 支持列表迭代器和逆序迭代器。
  4. 性能特点:
    1. 插入和删除效率高,随机访问效率低。

排序

Arrays.sort 是 Java 中用于对数组进行排序的工具方法。它可以对基本类型数组(如 int[]、double[] 等)和对象数组(如 String[]、自定义对象数组等)进行排序。以下是使用 Arrays.sort 的详细说明和示例。

1. 对基本类型数组排序

对于基本类型数组(如 int[]、double[] 等),Arrays.sort 会按自然顺序(升序)排序。

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        int[] numbers = {5, 2, 9, 1, 5, 6};
        
        // 使用 Arrays.sort 对数组排序
        Arrays.sort(numbers);
        
        // 输出排序后的数组
        System.out.println(Arrays.toString(numbers)); // [1, 2, 5, 5, 6, 9]
    }
}

2. 对对象数组排序

对于对象数组(如 String[] 或自定义对象数组),Arrays.sort 要求对象实现 Comparable 接口,或者传入一个 Comparator 来指定排序规则。

示例 1:对 String[] 排序

String 类已经实现了 Comparable 接口,因此可以直接排序。

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        String[] names = {"John", "Alice", "Bob", "Eve"};
        
        // 使用 Arrays.sort 对字符串数组排序
        Arrays.sort(names);
        
        // 输出排序后的数组
        System.out.println(Arrays.toString(names)); // [Alice, Bob, Eve, John]
    }
}

示例 2:对自定义对象数组排序

如果要对自定义对象数组排序,有两种方式:

  1. 实现 Comparable** 接口**:在类中实现 compareTo 方法,定义自然排序规则。
  2. 使用 **Comparator**:在调用 Arrays.sort 时传入一个 Comparator,定义排序规则。

方法 1:实现 Comparable 接口

import java.util.Arrays;

class Person implements Comparable<Person> {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public int compareTo(Person other) {
        // 按年龄升序排序
        return Integer.compare(this.age, other.age);
    }

  
    public String toString() {
        return name + " (" + age + ")";
    }
}

public class Main {
    public static void main(String[] args) {
        Person[] people = {
            new Person("John", 25),
            new Person("Alice", 22),
            new Person("Bob", 30)
        };

        // 使用 Arrays.sort 对 Person 数组排序
        Arrays.sort(people);

        // 输出排序后的数组
        System.out.println(Arrays.toString(people));
        // [Alice (22), John (25), Bob (30)]
    }
}

方法 2:使用 Comparator

如果不想修改类,或者需要多种排序规则,可以使用 Comparator。

import java.util.Arrays;
import java.util.Comparator;

class Person {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
}

public class Main {
    public static void main(String[] args) {
        Person[] people = {
            new Person("John", 25),
            new Person("Alice", 22),
            new Person("Bob", 30)
        };

        // 使用 Comparator 按姓名排序
        Arrays.sort(people, new Comparator<Person>() {
            @Override
            public int compare(Person p1, Person p2) {
                return p1.name.compareTo(p2.name);
            }
        });

        // 输出排序后的数组
        System.out.println(Arrays.toString(people));
        // [Alice (22), Bob (30), John (25)]
    }
}

3. 对部分数组排序

Arrays.sort 还可以对数组的一部分进行排序。只需指定起始索引(包含)和结束索引(不包含)。

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        int[] numbers = {5, 2, 9, 1, 5, 6};
        
        // 对索引 1 到 4(不包含 4)的部分排序
        Arrays.sort(numbers, 1, 4);
        
        // 输出排序后的数组
        System.out.println(Arrays.toString(numbers)); // [5, 1, 2, 9, 5, 6]
    }
}

4. 使用 Lambda 表达式简化 Comparator

在 Java 8 及以上版本中,可以使用 Lambda 表达式简化 Comparator 的写法。

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        String[] names = {"John", "Alice", "Bob", "Eve"};

        // 使用 Lambda 表达式按字符串长度排序
        Arrays.sort(names, (s1, s2) -> Integer.compare(s1.length(), s2.length()));

        // 输出排序后的数组
        System.out.println(Arrays.toString(names)); // [Eve, Bob, John, Alice]
    }
}

总结

  • 对于基本类型数组,直接使用 Arrays.sort。
  • 对于对象数组,需要实现 Comparable 接口或传入 Comparator。
  • 可以使用 Lambda 表达式简化 Comparator 的写法。
  • 可以对数组的一部分进行排序。

希望这些示例能帮助你更好地理解和使用 Arrays.sort!

数据类型

Java 中的数据类型可以分为 基本数据类型 和 引用数据类型。下面我会详细解释它们的特点,并回答你的问题:这些数据类型是否可以看成类。

1. Java 数据类型的分类

Java 中的数据类型可以分为两大类:

(1)基本数据类型(Primitive Data Types)

  • 基本数据类型是 Java 内置的简单数据类型,直接存储数据值。
  • 它们不是对象,也没有方法。
  • 共有 8 种基本数据类型:
    • 整型:byte、short、int、long
    • 浮点型:float、double
    • 字符型:char
    • 布尔型:boolean

(2)引用数据类型(Reference Data Types)

  • 引用数据类型是指向对象的引用(类似于指针),而不是直接存储数据值。
  • 它们通常是类、接口、数组等。
  • 例如:String、Integer、ArrayList、自定义类等。

2. 基本数据类型是否可以看成类?

基本数据类型(如 int、char 等)不是类 ,它们是 Java 语言的最底层实现,直接由 JVM 支持。它们没有方法,也不能通过 new 关键字创建。

但是,Java 为每种基本数据类型提供了对应的 包装类(Wrapper Class),这些包装类是类,可以将基本数据类型“包装”成对象。

基本数据类型及其包装类

基本数据类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

示例:使用包装类

int num = 10; // 基本数据类型
Integer numObj = new Integer(10); // 包装类对象//注意:这个用法被移除了,不要这样用

3. 引用数据类型是否可以看成类?

引用数据类型(如 String、Integer、自定义类等)本身就是类。它们是通过 new 关键字创建的对象,具有属性和方法。

示例:引用数据类型

String str = new String("Hello"); // String 是类
Integer num = new Integer(10); // Integer 是类
Person person = new Person("John", 25); // Person 是自定义类

4. 自动装箱和拆箱

Java 提供了 自动装箱(Autoboxing) 和 自动拆箱(Unboxing) 机制,使得基本数据类型和其包装类之间可以自动转换。

自动装箱

将基本数据类型自动转换为对应的包装类对象。

int num = 10;
Integer numObj = num; // 自动装箱

自动拆箱

将包装类对象自动转换为基本数据类型。

Integer numObj = new Integer(10);
int num = numObj; // 自动拆箱

5. 为什么需要包装类?

包装类的主要作用是:

  1. 将基本数据类型转换为对象:
    1. 例如,ArrayList 等集合类只能存储对象,不能存储基本数据类型。
    2. 使用包装类可以将基本数据类型放入集合中。
  2. 提供更多功能:
    1. 包装类提供了许多实用的方法,例如将字符串转换为数字。
    2. 示例:
      • int num = Integer.parseInt("123"); // 将字符串转换为 int
  3. 支持泛型:
    1. 泛型(如 List<T>)只能使用引用类型,不能使用基本数据类型。
    2. 使用包装类可以解决这个问题。

6. 总结

  • 基本数据类型(如 int、char 等)不是类,它们是 Java 的最底层实现。
  • 包装类(如 Integer、Character 等)是类,用于将基本数据类型包装成对象。
  • 引用数据类型(如 String、自定义类等)本身就是类。

字符串

1. String 的本质

  • String 是一个 引用类型(类),而不是基本数据类型。
  • String 对象是不可变的(immutable),即一旦创建,其内容就不能被修改。
  • String 的底层实现是一个 char 数组(在 Java 9 之后是 byte 数组,为了节省内存)。

2. String 的创建方式

String 可以通过两种方式创建:

  1. 直接赋值:
    1. String str1 = "Hello";
    2. 这种方式会检查字符串常量池(String Pool),如果池中已存在相同内容的字符串,则直接复用;否则,创建一个新的字符串对象并放入池中。
  2. 使用 new 关键字:
    1. String str2 = new String("Hello");
    2. 这种方式会强制在堆内存中创建一个新的 String 对象,即使字符串常量池中已存在相同内容的字符串。

3. 字符串常量池(String Pool)

  • 字符串常量池是 JVM 中的一块特殊内存区域,用于存储字符串常量。
  • 直接赋值的字符串会被放入常量池中,以便复用。
  • 使用 new 关键字创建的字符串不会放入常量池,除非显式调用 intern() 方法。
String str1 = "Hello"; // 放入常量池
String str2 = "Hello"; // 复用常量池中的对象
String str3 = new String("Hello"); // 在堆中创建新对象
String str4 = str3.intern(); // 将堆中的字符串放入常量池(如果不存在)

System.out.println(str1 == str2); // true,指向同一个对象
System.out.println(str1 == str3); // false,指向不同对象
System.out.println(str1 == str4); // true,str4 指向常量池中的对象

4. String 的不可变性

  • String 对象一旦创建,其内容就不能被修改。
  • 任何对 String 的修改操作(如拼接、替换等)都会创建一个新的 String 对象。
String str = "Hello";
str = str + " World"; // 创建一个新的 String 对象
System.out.println(str); // 输出 "Hello World"

5. String 与 int/Integer 的区别

  • int是基本数据类型:
    • 直接存储数据值。
    • 不是对象,没有方法。
  • Integer是包装类:
    • 将 int 包装成对象。
    • 提供了自动装箱和拆箱机制。
  • String 是引用类型:
    • 本身就是一个类。
    • 没有装箱和拆箱的概念,因为它本身就是对象。

6. String 的“装箱”和“拆箱”?

  • String没有装箱和拆箱的概念,因为它本身就是对象。
  • 如果指的是将其他类型转换为 String,可以通过以下方式实现:
    • 使用 String.valueOf():
      1. int num = 10; String str = String.valueOf(num); // 将 int 转换为 String
    • 使用 Integer.toString():
      1. int num = 10; String str = Integer.toString(num); // 将 int 转换为 String
    • 使用字符串拼接:
      1. int num = 10; String str = "" + num; // 将 int 转换为 String

7. String 的常用方法

String 类提供了许多实用的方法,例如:

  • 长度:length()
  • 比较:equals()、compareTo()
  • 查找:indexOf()、contains()
  • 截取:substring()
  • 替换:replace()
  • 分割:split()
String str = "Hello, World!";
System.out.println(str.length()); // 13
System.out.println(str.substring(7)); // "World!"
System.out.println(str.replace("Hello", "Hi")); // "Hi, World!"

8.StringBuilder

StringBuilder 是 Java 中用于高效处理字符串的一个类。与 String 不同,StringBuilder 是可变的(mutable),适合频繁修改字符串的场景。下面我会详细讲解 StringBuilder 的特点、用法以及与 String 的区别。

1. StringBuilder 的作用

  • StringBuilder 是一个可变的字符序列。
  • 它允许在不创建新对象的情况下修改字符串内容。
  • 适合频繁拼接、插入、删除等操作。

2. StringBuilder 的创建

可以通过以下方式创建 StringBuilder 对象:

(1)默认构造函数

StringBuilder sb = new StringBuilder(); // 初始容量为 16

(2)指定初始容量

StringBuilder sb = new StringBuilder(50); // 初始容量为 50

(3)指定初始字符串

StringBuilder sb = new StringBuilder("Hello"); // 初始内容为 "Hello"

3. StringBuilder 的常用方法

(1)追加内容

  • 使用 append() 方法在末尾追加内容。

示例:

StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // 追加字符串
sb.append(123);      // 追加数字
System.out.println(sb); // 输出:Hello World123

(2)插入内容

  • 使用 insert() 方法在指定位置插入内容。

示例:

StringBuilder sb = new StringBuilder("Hello");
sb.insert(5, " Java"); // 在索引 5 处插入字符串
System.out.println(sb); // 输出:Hello Java

(3)删除内容

  • 使用 delete() 方法删除指定范围的字符。

示例:

StringBuilder sb = new StringBuilder("Hello World");
sb.delete(5, 11); // 删除索引 5 到 10 的字符
System.out.println(sb); // 输出:Hello

(4)替换内容

  • 使用 replace() 方法替换指定范围的字符。

示例:

StringBuilder sb = new StringBuilder("Hello World");
sb.replace(6, 11, "Java"); // 替换索引 6 到 10 的字符
System.out.println(sb); // 输出:Hello Java

(5)反转内容

  • 使用 reverse() 方法反转字符串。

示例:

StringBuilder sb = new StringBuilder("Hello");
sb.reverse(); // 反转字符串
System.out.println(sb); // 输出:olleH

(6)获取长度和容量

  • 使用 length() 方法获取当前字符数。
  • 使用 capacity() 方法获取当前容量。

示例:

StringBuilder sb = new StringBuilder("Hello");
System.out.println(sb.length());    // 输出:5
System.out.println(sb.capacity());  // 输出:21(初始容量 16 + 字符串长度 5)

(7)转换为字符串

  • 使用 toString() 方法将 StringBuilder 转换为 String。

示例:

StringBuilder sb = new StringBuilder("Hello");
String str = sb.toString(); // 转换为 String
System.out.println(str); // 输出:Hello

4. StringBuilder 与 String 的区别

特性StringStringBuilder
可变性不可变(immutable)可变(mutable)
性能频繁修改时性能较低频繁修改时性能高
线程安全线程安全非线程安全
适用场景字符串内容不经常修改字符串内容频繁修改
内存占用每次修改都会创建新对象修改时不会创建新对象

5. StringBuilder 的性能优势

  • 减少内存开销:StringBuilder 在修改字符串时不会创建新对象,而是直接修改内部字符数组。
  • 提高效率:频繁拼接字符串时,StringBuilder 的性能远高于 String。

示例:性能对比

public class Main {
    public static void main(String[] args) {
        // 使用 String 拼接字符串
        long startTime1 = System.currentTimeMillis();
        String str = "";
        for (int i = 0; i < 10000; i++) {
            str += i;
        }
        long endTime1 = System.currentTimeMillis();
        System.out.println("String 耗时: " + (endTime1 - startTime1) + "ms");

        // 使用 StringBuilder 拼接字符串
        long startTime2 = System.currentTimeMillis();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 10000; i++) {
            sb.append(i);
        }
        long endTime2 = System.currentTimeMillis();
        System.out.println("StringBuilder 耗时: " + (endTime2 - startTime2) + "ms");
    }
}

输出:

String 耗时: 200ms
StringBuilder 耗时: 2ms

6. StringBuilder 的线程安全问题

  • StringBuilder 是非线程安全的,适合单线程环境。
  • 如果需要线程安全的可变字符串,可以使用 StringBuffer。

7. 总结

  • StringBuilder 是一个可变的字符序列,适合频繁修改字符串的场景。
  • 常用方法包括 append()、insert()、delete()、replace() 和 reverse()。
  • 与 String 相比,StringBuilder 在性能上有显著优势。
  • 如果需要线程安全,可以使用 StringBuffer。
This post is licensed under CC BY 4.0 by the author.