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. publicprivate 访问修饰符

(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)publicprivate 的区别

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

(5)publicprivate 的使用场景

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

(6)publicprivate 的总结

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

对象(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. 封装的定义

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

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,外部无法直接访问。
  • 通过 gettersetter 方法控制对字段的访问。

示例:

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

(2)数据验证

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

示例:

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

(3)灵活性

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

示例:

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

(4)提高安全性

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

5. 访问修饰符

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

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

6. 封装的使用场景

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

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
    • Integerint 的包装类,它将 int 封装成一个对象。
    • 通过 Integer,可以将 int 值作为对象使用,例如存储在集合中或调用对象的方法。

为什么需要 Integer

  1. 集合类的需求
    1. Java 的集合类(如 ArrayListHashMap 等)只能存储对象,不能存储基本数据类型。
    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 包中。它可以读取用户从键盘输入的数据,并支持多种数据类型的解析(如 intdoubleString 等)。

使用步骤

  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.BufferedReaderjava.io.InputStreamReader
  2. 创建 BufferedReader 对象。
  3. 使用 readLine() 方法读取输入。

注意事项

  • BufferedReaderreadLine() 方法返回的是字符串,如果需要其他类型的数据(如 intdouble),需要手动转换(如 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 的输入操作(如使用 ScannerBufferedReader)可能会抛出异常(如 InputMismatchExceptionIOException),为了避免程序崩溃,通常会用 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 时的异常处理

BufferedReaderreadLine() 方法可能会抛出 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. 支持 pushpoppeek 等栈操作。
  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 种基本数据类型:
    • 整型:byteshortintlong
    • 浮点型:floatdouble
    • 字符型:char
    • 布尔型:boolean

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

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

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

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

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

基本数据类型及其包装类

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

示例:使用包装类

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

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

引用数据类型(如 StringInteger、自定义类等)本身就是类。它们是通过 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. 总结

  • 基本数据类型(如 intchar 等)不是类,它们是 Java 的最底层实现。
  • 包装类(如 IntegerCharacter 等)是类,用于将基本数据类型包装成对象。
  • 引用数据类型(如 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. Stringint/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. StringBuilderString 的区别

特性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.

Trending Tags