java笔记
类(Class)
- 类是 Java 中的基本构建块,用于定义对象的属性和行为。
- 类包含字段、方法、构造函数等组成部分。
- 通过类可以创建对象,对象是类的实例。
- 类可以继承、包含静态成员,并且可以通过访问修饰符控制可见性。
1. 类的定义
类是 Java 中的一种 模板 或蓝图,用于创建对象。它定义了对象的 属性(字段) 和 行为(方法)。
语法:
class 类名 {
// 字段(属性)
数据类型 字段名;
// 方法(行为)
返回类型 方法名(参数列表) {
// 方法体
}
}
2. 类的组成部分
一个类通常包含以下部分:
- 字段(Fields):
- 也称为属性或成员变量,用于描述对象的状态。
- 示例:
int age; String name;
- 方法(Methods):
- 用于定义对象的行为。
- 示例:
void sayHello() { System.out.println("Hello!"); }
- 构造函数(Constructors):
- 用于创建对象时初始化对象的状态。
- 示例:
public Person(String name, int age) { this.name = name; this.age = age; }
- 代码块(Blocks):
- 静态代码块和非静态代码块,用于初始化操作。
- 示例:
static { System.out.println("静态代码块"); }
- 内部类(Inner Classes):
- 定义在类内部的类。
- 示例:
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 中有四种访问修饰符:
public:公开的,任何类都可以访问。private:私有的,只有本类可以访问。protected:受保护的,本类、子类和同一包中的类可以访问。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 的区别
| 特性 | public | private |
|---|---|---|
| 访问范围 | 任何类都可以访问 | 只有本类可以访问 |
| 字段 | 公开字段,外部可以直接访问 | 私有字段,外部无法直接访问 |
| 方法 | 公开方法,外部可以直接调用 | 私有方法,外部无法直接调用 |
| 构造函数 | 公开构造函数,外部可以创建对象 | 私有构造函数,外部无法创建对象 |
(5)public 和 private 的使用场景
public:- 用于暴露类的核心功能。
- 例如,工具类的方法通常声明为
public。
private:- 用于隐藏类的内部实现细节。
- 例如,类的字段通常声明为
private,并通过公开的getter和setter方法访问。
(6)public 和 private 的总结
public是公开的,任何类都可以访问。private是私有的,只有本类可以访问。- 使用
public和private可以实现封装,保护类的内部实现细节。
对象(Object)
1. 对象的定义
- 对象是类的实例。
- 类是对象的模板,而对象是类的具体表现。
- 每个对象都有自己的状态(字段)和行为(方法)。
2. 对象的创建
通过 new 关键字可以创建对象。创建对象的过程包括:
- 分配内存:为对象分配内存空间。
- 初始化字段:调用构造函数初始化对象的状态。
- 返回引用:返回对象的引用(地址)。
语法:
类名 对象名 = 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 中,封装通常通过以下方式实现:
- 将字段声明为
**private**:限制外部直接访问字段。 - 提供公共的
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 接口定义了一个有序集合的基本行为,包括:
- 有序性:元素按照插入顺序存储,可以通过索引访问。
- 允许重复元素:同一个元素可以多次添加到列表中。
- 允许
null值:列表中可以存储null值。 - 动态大小:列表的大小可以根据需要自动调整。
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 接口,以下是常见的实现类:
ArrayList:- 基于动态数组实现,支持快速随机访问,适合频繁读取数据的场景。
LinkedList:- 基于双向链表实现,适合频繁插入和删除元素的场景。
Vector:- 类似于
ArrayList,但是线程安全的(方法被synchronized修饰)。
- 类似于
Stack:- 继承自
Vector,实现了栈数据结构(后进先出)。
- 继承自
ArrayList:适合频繁读取数据的场景。LinkedList:适合频繁插入和删除元素的场景。Vector:适合多线程环境。Stack:适合实现栈数据结构。
主要特点
- 动态数组:
ArrayList内部使用数组来存储元素,当数组容量不足时,会自动扩容。 - 有序集合:元素按照插入顺序存储,可以通过索引访问。
- 允许重复元素:
ArrayList允许存储重复的元素。 - 允许
null值:ArrayList可以存储null值。 - 非线程安全:
ArrayList不是线程安全的,如果多个线程同时访问并修改ArrayList,需要外部同步。 - 快速随机访问:由于基于数组实现,
ArrayList支持通过索引快速访问元素。
Q:如何构建一个存储int类型数据的ArrayList?
1
List<Integer> arraylist = new ArrayList<>();
代码解析:我们惊讶地发现,new ArrayList后面跟了一个<>和(),这是要干嘛呢?这要涉及到 泛型(Generics) 和 构造函数调用 的语法!
尖括号 <> 是 Java 泛型语法的一部分,用于指定集合中存储的元素类型。
泛型的作用
- 类型安全:泛型允许在编译时检查类型,避免运行时类型转换错误。
- 代码复用:泛型可以编写通用的代码,适用于多种数据类型。
具体解释
List<Integer>:表示这是一个List集合,其中存储的元素类型是Integer。new ArrayList<>():表示创建一个ArrayList对象,其中的元素类型与左侧的List<Integer>一致(即Integer)。
尖括号中的类型
- 在
new ArrayList<>()中,尖括号<>是 钻石操作符(Diamond Operator),从 Java 7 开始引入。 - 钻石操作符允许在创建对象时省略泛型类型,编译器会根据上下文自动推断类型。
- 例如,
new ArrayList<>()等价于new ArrayList<Integer>()。
圆括号 () 表示调用类的构造函数。
构造函数的作用
- 构造函数用于创建对象并初始化对象的状态。
ArrayList类有多个构造函数,最常用的是无参构造函数。
Integer 是 Java 中的一个类,属于 包装类(Wrapper Class)。它用于将基本数据类型 int 封装成一个对象。Integer 类位于 java.lang 包中,是 Java 标准库的一部分。
1. Integer 的作用
基本数据类型 vs 包装类
- 基本数据类型
int:int是 Java 中的一种基本数据类型,用于表示整数。- 它直接存储数值,效率高,但不是对象,因此不能用于需要对象的场景(例如集合类)。
- 包装类
Integer:Integer是int的包装类,它将int封装成一个对象。- 通过
Integer,可以将int值作为对象使用,例如存储在集合中或调用对象的方法。
为什么需要 Integer?
- 集合类的需求:
- Java 的集合类(如
ArrayList、HashMap等)只能存储对象,不能存储基本数据类型。 - 如果需要将
int值存储在集合中,必须使用Integer。 List<Integer> list = new ArrayList<>(); list.add(10); // 10 被自动装箱为 Integer 对象
- Java 的集合类(如
- 对象操作的需求:
Integer提供了许多有用的方法,例如将字符串转换为整数、比较整数等。
int num = Integer.parseInt("123"); // 将字符串转换为整数
- 泛型的支持:
- 泛型(Generics)只能使用对象类型,不能使用基本数据类型。
- 例如,
List<int>是非法的,必须使用List<Integer>。
2. Integer 的常用方法
Integer 类提供了许多静态方法和实例方法,以下是一些常用的方法:
静态方法
Integer.parseInt(String s):- 将字符串转换为
int值。
- 将字符串转换为
Integer.valueOf(String s):- 将字符串转换为
Integer对象。
- 将字符串转换为
Integer.toString(int i):- 将
int值转换为字符串。
- 将
实例方法
intValue():- 将
Integer对象转换为int值。
- 将
compareTo(Integer anotherInteger):- 比较两个
Integer对象的大小。
- 比较两个
equals(Object obj):- 比较两个
Integer对象是否相等。
- 比较两个
自动装箱和拆箱
Java 5 引入了 自动装箱(Autoboxing) 和 自动拆箱(Unboxing) 机制,使得基本数据类型和包装类之间的转换更加方便。
自动装箱
- 将基本数据类型自动转换为对应的包装类对象。
Integer num = 10; // 自动装箱,等价于 Integer num = Integer.valueOf(10);
自动拆箱
- 将包装类对象自动转换为基本数据类型。
int value = num; // 自动拆箱,等价于 int value = num.intValue();
输入输出
输入
在 Java 中,输入数据的方式有多种,常见的方式包括:
- 使用
Scanner类(最常用) - 使用
BufferedReader类 - 使用命令行参数
- 使用
Console类(适用于控制台输入)
使用 Scanner 类
Scanner 是 Java 中最常用的输入工具,位于 java.util 包中。它可以读取用户从键盘输入的数据,并支持多种数据类型的解析(如 int、double、String 等)。
使用步骤
- 导入
java.util.Scanner。 - 创建
Scanner对象。 - 使用
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();
}
}
使用步骤
- 导入
java.io.BufferedReader和java.io.InputStreamReader。 - 创建
BufferedReader对象。 - 使用
readLine()方法读取输入。
注意事项
BufferedReader的readLine()方法返回的是字符串,如果需要其他类型的数据(如int、double),需要手动转换(如Integer.parseInt()或Double.parseDouble())。BufferedReader的效率比Scanner高,适合处理大量输入。
输出
在 Java 中,输出数据的方式有多种,常见的方式包括:
- 使用
System.out.println()(最常用) - 使用
System.out.print() - 使用
System.out.printf()(格式化输出) - 使用
String.format()(格式化字符串) - 使用
PrintWriter类(输出到文件或网络) - 使用
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 中,输入操作可能会遇到以下问题:
- 输入类型不匹配:
- 例如,用户输入了一个字符串,但程序期望的是一个整数。
- 使用
Scanner时,会抛出InputMismatchException。
- 输入流异常:
- 例如,输入流被意外关闭或无法读取。
- 使用
BufferedReader时,会抛出IOException。
- 空指针异常:
- 例如,使用
Console类时,如果当前环境不支持控制台输入,System.console()会返回null。
- 例如,使用
为了避免程序因为这些异常而崩溃,可以使用 try-catch 块来捕获并处理异常。
使用 try-catch 的常见场景
- 使用
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
}
}
}
- 使用
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),并提供了快速的查找、插入和删除操作。
主要特点
- 键值对存储:
HashMap存储的数据是键值对形式,每个键(key)对应一个值(value)。- 键和值都可以是任意类型的对象(包括
null)。
- 无序集合:
HashMap不保证元素的顺序,插入顺序和遍历顺序可能不一致。
- 允许
null键和null值:HashMap允许一个null键和多个null值。
- 非线程安全:
HashMap不是线程安全的。如果多个线程同时访问并修改HashMap,需要外部同步。
- 基于哈希表:
HashMap使用哈希表来存储数据,通过键的哈希值快速定位存储位置。
- 快速查找:
- 在理想情况下,
HashMap的查找、插入和删除操作的时间复杂度为 O(1)。
- 在理想情况下,
常用方法
以下是 HashMap 的一些常用方法:
- 添加键值对
put(K key, V value):将键值对添加到HashMap中。如果键已存在,则替换对应的值。HashMap<String, Integer> map = new HashMap<>(); map.put("Apple", 10); map.put("Banana", 20);
- 获取值
get(Object key):返回指定键对应的值。如果键不存在,则返回null。
- 删除键值对
remove(Object key):删除指定键对应的键值对。
- 检查是否包含键或值
containsKey(Object key):检查是否包含指定键。containsValue(Object value):检查是否包含指定值。
- 获取大小
size():返回HashMap中键值对的数量。
- 清空
HashMapclear():移除HashMap中的所有键值对。
- 遍历
HashMap- 可以使用
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 接口,支持双向队列和栈的操作。以下是它的核心特点:
- 双向队列操作:
- 支持在队列头部和尾部插入、删除和访问元素。
- 栈操作:
- 支持
push、pop和peek等栈操作。
- 支持
- 链表操作:
- 支持列表迭代器和逆序迭代器。
- 性能特点:
- 插入和删除效率高,随机访问效率低。
排序
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:对自定义对象数组排序
如果要对自定义对象数组排序,有两种方式:
- 实现
Comparable** 接口**:在类中实现compareTo方法,定义自然排序规则。 - 使用
**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),这些包装类是类,可以将基本数据类型“包装”成对象。
基本数据类型及其包装类
| 基本数据类型 | 包装类 |
|---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
示例:使用包装类
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. 为什么需要包装类?
包装类的主要作用是:
- 将基本数据类型转换为对象:
- 例如,
ArrayList等集合类只能存储对象,不能存储基本数据类型。 - 使用包装类可以将基本数据类型放入集合中。
- 例如,
- 提供更多功能:
- 包装类提供了许多实用的方法,例如将字符串转换为数字。
- 示例:
int num = Integer.parseInt("123"); // 将字符串转换为 int
- 支持泛型:
- 泛型(如
List<T>)只能使用引用类型,不能使用基本数据类型。 - 使用包装类可以解决这个问题。
- 泛型(如
6. 总结
- 基本数据类型(如
int、char等)不是类,它们是 Java 的最底层实现。 - 包装类(如
Integer、Character等)是类,用于将基本数据类型包装成对象。 - 引用数据类型(如
String、自定义类等)本身就是类。
字符串
1. String 的本质
String是一个 引用类型(类),而不是基本数据类型。String对象是不可变的(immutable),即一旦创建,其内容就不能被修改。String的底层实现是一个char数组(在 Java 9 之后是byte数组,为了节省内存)。
2. String 的创建方式
String 可以通过两种方式创建:
- 直接赋值:
String str1 = "Hello";- 这种方式会检查字符串常量池(String Pool),如果池中已存在相同内容的字符串,则直接复用;否则,创建一个新的字符串对象并放入池中。
- 使用
new关键字:String str2 = new String("Hello");- 这种方式会强制在堆内存中创建一个新的
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():int num = 10; String str = String.valueOf(num); // 将 int 转换为 String
- 使用
Integer.toString():int num = 10; String str = Integer.toString(num); // 将 int 转换为 String
- 使用字符串拼接:
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 的区别
| 特性 | String | StringBuilder |
|---|---|---|
| 可变性 | 不可变(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。