JAVA 期末复习总结
类的声明、定义
在Java中,类是对象的蓝图或模板,定义了对象的属性(字段)和行为(方法)。下面是关于类的声明和定义的一些基础知识。
1. 类的基本声明和定义
一个类的声明包括类名、类体以及其中的成员(字段、方法等)。
基本语法:
1 | public class ClassName { |
解释:
public:修饰符,表示该类是公共的,可以被其他类访问。class ClassName:声明一个类,ClassName是类名,按照Java命名规范,类名通常以大写字母开头。{}:类体,包含类的字段和方法。int field1;:字段声明,field1是类的一个属性,数据类型为int。public ClassName():构造方法,用于创建类的实例。void method1():类的方法,void表示该方法不返回值。
2. 类的构造方法
构造方法在创建对象时调用,它用于初始化对象的状态。构造方法的名字与类名相同,并且没有返回值类型。
例子:
1 | public class Person { |
解释:
Person(String name, int age):这是一个带有参数的构造方法,用于初始化name和age字段。this.name = name;:this关键字指的是当前对象的实例,name是参数传递的值。
3. 创建类的实例
一旦定义了类,你就可以通过构造方法来创建类的实例(对象)。
例子:
1 | public class Main { |
解释:
Person person1 = new Person("Alice", 30);:创建了一个Person对象,传递了构造方法所需的参数"Alice"和30。person1.greet();:调用greet方法,输出对象的介绍信息。
4. 类的访问修饰符
Java 中的类可以使用不同的访问修饰符来控制其访问范围:
public:类是公共的,任何其他类都可以访问。private:类只能在当前文件中访问(不过通常类本身不能是private,private更多地用于类的成员)。protected:类只能在同一包内或其子类中访问。- 默认(无修饰符):类只能在同一包内访问。
5. 总结
- 类的定义包括类名、字段、构造方法和方法。
- 构造方法用于初始化对象的字段。
- 类可以有不同的访问修饰符,控制类及其成员的可见性。
构造函数的定义
构造函数的定义
构造函数(Constructor)是类中的一种特殊方法,用于在创建对象时初始化该对象的状态。构造函数的名字与类名相同,并且没有返回类型。构造函数在创建对象时自动调用,可以用于初始化对象的成员变量。
1. 构造函数的基本语法
1 | public class ClassName { |
关键点:
- 构造函数的名称:构造函数的名字必须与类的名字完全相同。
- 没有返回类型:构造函数没有返回类型(包括
void)。 - 自动调用:构造函数在创建对象时自动调用,不需要显式调用。
2. 构造函数的类型
构造函数有两种类型:
- 无参构造函数(默认构造函数)
- 带参构造函数(自定义构造函数)
3. 无参构造函数
无参构造函数没有参数,通常用于创建对象时不需要初始化字段的情况。如果没有定义任何构造函数,Java 会自动提供一个默认的无参构造函数。
例子:
1 | public class Person { |
解释:
public Person():这是一个无参构造函数,在创建Person对象时会被自动调用,字段name和age被初始化为默认值。
4. 带参构造函数
带参构造函数可以让你在创建对象时传递参数,从而为对象的字段赋予特定的值。
例子:
1 | public class Person { |
解释:
public Person(String name, int age):带参构造函数,用于在创建对象时提供具体的初始化值。this.name = name;和this.age = age;:this关键字指的是当前对象,防止局部变量和字段重名。
5. 构造函数的重载
Java 允许构造函数的重载,这意味着一个类可以有多个构造函数,只要它们的参数列表不同。
例子:
1 | public class Person { |
解释:
- 类
Person有三个构造函数:- 一个无参构造函数。
- 一个接受
name和age参数的构造函数。 - 一个只接受
name参数的构造函数。
6. 构造函数的默认行为
如果你没有显式定义构造函数,Java 会为你提供一个默认的无参构造函数,且该构造函数不会做任何初始化工作。
默认构造函数的示例:
1 | public class Person { |
7. 构造函数与 this()
this() 关键字用于调用当前类的另一个构造函数。它只能在构造函数内调用,且必须是构造函数的第一行。
例子:
1 | public class Person { |
解释:
- 在
Person(String name)构造函数中,通过this(name, 0)调用另一个构造函数,传递默认的年龄值0。
8. 构造函数与继承
当一个类继承另一个类时,子类会继承父类的构造函数,但父类的构造函数需要显式调用,特别是当父类没有无参构造函数时。
例子:
1 | public class Animal { |
解释:
super(name);:在Dog类的构造函数中,调用了父类Animal的构造函数来初始化name字段。
总结:
- 构造函数 用于在创建对象时初始化对象的状态。
- 它的名称与类名相同,没有返回类型。
- 可以有多个构造函数(构造函数重载),根据传入的参数来初始化对象。
this()用于在构造函数中调用其他构造函数。super()用于调用父类的构造函数。
对象的实例化及使用
对象的实例化及使用
在 Java 中,对象实例化指的是使用 new 关键字创建类的实例,并通过实例访问类的成员(字段、方法等)。实例化过程将类的定义转化为内存中的一个具体对象,允许你对其进行操作。
1. 实例化一个对象
通过 new 关键字,我们可以实例化一个类,创建一个对象。new 关键字会调用类的构造函数来初始化对象。
语法:
1 | ClassName objectName = new ClassName(); |
例子:
1 | public class Person { |
解释:
Person person1 = new Person("Alice", 30);:使用new关键字实例化Person类,传递构造函数的参数"Alice"和30来初始化person1对象。person1.greet();:通过person1访问类中的greet方法,输出对象的信息。
2. 使用对象的字段和方法
实例化对象后,你可以通过对象引用来访问类的字段(成员变量)和方法。字段可以通过直接访问(如果是公共的),方法则通过调用。
例子:
1 | public class Car { |
解释:
car1.model:访问car1对象的model字段。car1.displayInfo();:调用car1对象的displayInfo()方法,输出车的详细信息。
3. 通过对象调用方法
在实例化对象后,可以通过对象调用类中的方法。方法可以是实例方法(非静态方法)或静态方法(类方法)。
例子:实例方法
1 | public class Calculator { |
例子:静态方法
静态方法是属于类本身而不是某个对象的,因此可以通过类名直接调用。
1 | public class MathUtils { |
解释:
calc.add(10, 20):实例化Calculator对象calc后,调用add方法。MathUtils.add(10, 20):直接通过类名调用add静态方法,不需要创建对象。
4. 多重实例化
你可以创建多个对象,并通过每个对象独立地访问类的成员。
例子:
1 | public class Student { |
解释:
- 创建了两个
Student对象student1和student2,并通过各自的introduce()方法输出信息。 - 每个对象是独立的,
student1和student2拥有自己的name和grade。
5. 对象的生命周期
Java 中的对象在实例化时被分配到堆内存中,当没有任何引用指向该对象时,Java 会通过垃圾回收(GC)机制自动销毁该对象。
对象生命周期的关键点:
- 对象创建:通过
new关键字实例化对象。 - 对象使用:通过对象引用访问和修改对象的字段、调用方法。
- 对象销毁:当对象不再被任何引用变量引用时,垃圾回收器会自动回收该对象所占的内存。
6. 总结
- 对象实例化:通过
new关键字创建类的对象,并调用构造函数初始化对象。 - 访问对象字段和方法:通过对象引用来访问或修改对象的字段,调用对象的方法。
- 静态与实例方法:实例方法通过对象调用,静态方法通过类名调用。
- 多重实例化:可以创建多个对象,且每个对象具有独立的状态。
面向抽象的编程
面向抽象的编程(Abstraction)
面向抽象的编程是面向对象编程(OOP)中的一个核心概念,旨在隐藏实现的细节,仅暴露必要的功能或接口给用户。通过抽象,程序员可以专注于高层次的逻辑,而不必关心底层的实现细节,从而提高代码的可维护性、可扩展性和可重用性。
1. 抽象的定义
抽象是一种编程技术,通过将复杂系统的具体细节隐藏起来,只保留与外部交互所必需的功能。它有两个主要形式:
- 抽象类:提供模板,但不能直接实例化。
- 接口(Interface):提供一个规范,要求实现类遵守。
2. 抽象的优点
- 简化复杂性:通过隐藏复杂实现,程序员可以专注于接口层次。
- 增强可维护性:具体实现的更改不会影响到使用抽象的代码。
- 提高可扩展性:通过抽象,新的功能可以在不改变现有代码的情况下扩展。
- 促进代码复用:相同的抽象接口可以被多个实现类共享和复用。
3. 抽象类(Abstract Class)
抽象类是无法直接实例化的类。它可以包含已实现的方法和未实现的方法(抽象方法)。抽象类用于定义共有行为,并强制其子类实现某些方法。
3.1 抽象类的语法:
1 | abstract class Animal { |
3.2 解释:
abstract关键字标识抽象类。sound()是一个抽象方法,它没有方法体,表示所有继承Animal的类必须实现这个方法。sleep()是一个普通方法,子类可以直接继承并使用。
3.3 继承抽象类:
1 | class Dog extends Animal { |
解释:
Dog类继承了Animal类,必须实现sound()方法。sleep()方法直接被Dog继承并使用。
4. 接口(Interface)
接口定义了一组抽象方法(没有方法体),并且所有实现该接口的类必须提供具体的实现。接口是实现抽象的另一种方式,它定义了类的行为,但不提供具体实现。
4.1 接口的语法:
1 | interface Animal { |
4.2 解释:
interface关键字定义接口。- 所有的方法都默认为
public abstract,即使不加修饰符,也代表抽象方法。
4.3 实现接口:
1 | class Dog implements Animal { |
解释:
Dog类实现了Animal接口,并提供了sound()和sleep()方法的具体实现。- 使用
implements关键字表明一个类实现了某个接口。
5. 抽象类 vs 接口
| 特性 | 抽象类 | 接口 |
|---|---|---|
| 是否能有构造函数 | 有 | 没有 |
| 是否能有字段 | 可以有字段(成员变量) | 只能有 public static final 字段 |
| 是否可以实现方法 | 可以有实现的方法(非抽象方法) | 只能有方法声明,不能有方法实现 |
| 是否支持多继承 | 不支持多继承(单继承) | 支持多继承(一个类可以实现多个接口) |
| 访问修饰符 | 可以有各种访问修饰符(public, private, protected) |
默认是 public |
6. 使用抽象的例子:支付系统
假设我们有一个支付系统,要求不同的支付方式(如支付宝、微信支付、信用卡支付)都有一个统一的支付接口。
6.1 定义支付接口
1 | interface Payment { |
6.2 实现支付接口
1 | class Alipay implements Payment { |
6.3 使用抽象接口
1 | public class Main { |
解释:
Payment接口定义了一个支付的方法,所有的支付方式类都实现了这个接口。- 每种支付方式类提供了具体的支付实现,调用接口方法时,会执行具体类的逻辑。
7. 抽象的总结
- 抽象类:允许部分方法实现,子类必须实现抽象方法。
- 接口:完全不提供实现,只定义方法的签名,类必须实现接口并提供具体实现。
- 抽象帮助我们将复杂的系统设计为具有更高复用性的模块,通过隐藏实现细节,暴露接口,使得系统更加灵活和可扩展。
- 使用抽象类和接口时,可以方便地实现多态(Polymorphism),允许不同对象通过统一接口进行交互。
面向接口的编程(含lambda表达式)
面向接口的编程(Interface-based Programming)
面向接口的编程(Interface-based Programming)是一种通过接口而非具体实现来编写代码的编程方式。通过接口,代码可以被设计得更加灵活、可扩展和解耦。接口定义了类的行为,而不关心具体的实现,通常用于实现多态和依赖注入等设计模式。
1. 接口的基本概念
在 Java 中,接口(interface)是一个只包含常量和抽象方法(没有方法体)的类。接口用于指定类必须提供的行为,而不关心具体的实现。
1.1 接口的定义和实现
接口的定义:
1 | interface Animal { |
接口的实现:
1 | class Dog implements Animal { |
解释:
Animal接口定义了一个方法sound(),没有实现。Dog和Cat类实现了Animal接口,并提供了sound()方法的具体实现。Animal类型的引用可以指向任何实现了Animal接口的对象。
2. 面向接口的编程的优势
- 解耦:代码的依赖关系更加松散,因为实现类的具体实现被封装在接口后面。
- 多态性:通过接口,类可以实现多种不同的行为。不同的实现类可以通过统一的接口来调用。
- 灵活性:接口的使用使得系统更加灵活,可以通过接口轻松切换不同的实现。
3. Java 8 引入的 Lambda 表达式
在 Java 8 中,Lambda 表达式和函数式接口(functional interface)被引入,为面向接口编程提供了更简洁和表达力更强的方式。Lambda 表达式是一个匿名函数,可以用来实现接口的抽象方法,尤其是对于只包含一个抽象方法的接口(函数式接口)非常有用。
3.1 函数式接口
一个函数式接口是指仅包含一个抽象方法的接口,Java 8 引入了 @FunctionalInterface 注解来明确标记接口为函数式接口。Lambda 表达式的目标是实现函数式接口。
1 |
|
3.2 Lambda 表达式的语法
Lambda 表达式可以用来简化接口实现的代码,尤其是在处理函数式接口时。其基本语法如下:
1 | (parameters) -> expression |
例如,使用 Lambda 表达式实现 Calculator 接口:
1 | public class Main { |
解释:
(a, b) -> a + b是一个 Lambda 表达式,它实现了Calculator接口的add方法。calculator.add(10, 20)调用了通过 Lambda 表达式实现的add方法,返回值为 30。
4. Lambda 表达式与函数式接口的结合
Lambda 表达式通常与函数式接口一起使用,下面是几个常见的函数式接口的示例:
4.1 Runnable 接口
Runnable 是一个没有返回值的函数式接口,它常用于线程的创建。
1 | public class Main { |
4.2 Comparator 接口
Comparator 接口用于对象的比较,通常在排序时使用。我们可以通过 Lambda 表达式提供自定义的比较规则。
1 | import java.util.Arrays; |
解释:
(s1, s2) -> s1.length() - s2.length()是一个 Lambda 表达式,表示根据字符串的长度进行排序。
5. 常见的函数式接口
Java 8 提供了许多内置的函数式接口,常见的包括:
- **
Consumer<T>**:接受一个输入参数并执行某些操作,但没有返回值。1
Consumer<String> print = str -> System.out.println(str);
- **
Supplier<T>**:不接受输入参数,返回一个结果。1
Supplier<Double> randomValue = () -> Math.random();
- **
Function<T, R>**:接受一个输入参数,并返回一个结果。1
Function<String, Integer> stringLength = str -> str.length();
- **
Predicate<T>**:接受一个输入参数,返回一个布尔值,用于测试某个条件。1
Predicate<Integer> isEven = num -> num % 2 == 0;
6. 方法引用(Method Reference)
方法引用是 Lambda 表达式的简写形式,允许直接引用现有方法而不需要重写其逻辑。方法引用有几种形式:
静态方法引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14class MathUtils {
public static int add(int a, int b) {
return a + b;
}
}
public class Main {
public static void main(String[] args) {
// 使用方法引用调用静态方法
Calculator calculator = MathUtils::add;
int result = calculator.add(10, 20);
System.out.println(result); // 输出:30
}
}实例方法引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14class Person {
public void greet() {
System.out.println("Hello!");
}
}
public class Main {
public static void main(String[] args) {
Person person = new Person();
// 使用方法引用调用实例方法
Runnable greetTask = person::greet;
greetTask.run(); // 输出:Hello!
}
}构造函数引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class Person {
String name;
public Person(String name) {
this.name = name;
}
}
public class Main {
public static void main(String[] args) {
// 使用构造函数引用
Supplier<Person> personSupplier = () -> new Person("Alice");
Person person = personSupplier.get();
System.out.println(person.name); // 输出:Alice
}
}
7. 总结:面向接口的编程与 Lambda 表达式
- 面向接口的编程:通过接口定义行为,允许灵活的扩展和替换具体的实现,提高代码的可维护性和解耦性。
- Lambda 表达式:简化了函数式接口的实现,让代码更加简洁和易读。Lambda 表达式可以用于简化回调、事件监听、线程等多种场景。
- 函数式接口:是与 Lambda 表达式紧密结合的接口类型,它们定义了单一抽象方法,允许使用 Lambda 表达式实现该方法。
- 方法引用:方法引用是 Lambda 表达式的一种简化形式,使代码更加简洁和表达性更强。
异常处理
异常处理(Exception Handling)
异常处理是程序设计中的一个重要概念,目的是处理程序在运行过程中可能发生的错误,以保证程序能够稳定运行。Java 提供了强大的异常处理机制,可以有效地捕获、处理和抛出异常。
1. 异常的基本概念
- 异常(Exception):程序执行过程中遇到的问题,通常是无法预料的错误,比如文件未找到、网络连接失败等。
- 错误(Error):是更严重的程序错误,通常是无法恢复的,比如
OutOfMemoryError,StackOverflowError等。
在 Java 中,所有异常和错误类都继承自 Throwable 类。异常分为两大类:
- 受检异常(Checked Exception):必须处理的异常。它是
Exception类的子类,但不是RuntimeException的子类。 - 非受检异常(Unchecked Exception):可以不处理的异常。它是
RuntimeException类的子类。
2. 异常处理的基本结构
Java 的异常处理使用 try-catch-finally 语句块。基本语法如下:
1 | try { |
2.1 try 块:包含可能抛出异常的代码。
2.2 catch 块:用来捕获异常并进行处理。如果 try 块中的代码抛出异常,控制会转到匹配的 catch 块。
2.3 finally 块:用于执行无论是否发生异常都需要执行的代码,通常用于资源的释放,比如关闭文件流、数据库连接等。
3. 示例:简单的异常处理
1 | public class ExceptionDemo { |
解释:
try块中的10 / 0会抛出ArithmeticException。- 异常被
catch块捕获并处理,输出"Error: / by zero"。 finally块无论异常是否发生,都会执行,输出"This is the finally block."。
4. 常见的异常类型
- **
ArithmeticException**:数学运算异常,如除以零。 - **
NullPointerException**:空指针异常,访问一个空对象引用。 - **
ArrayIndexOutOfBoundsException**:数组下标越界异常。 - **
FileNotFoundException**:文件未找到异常。 - **
IOException**:输入输出异常。
5. 多重 catch 块
Java 7 引入了多重 catch 语法,可以捕获多个异常类型,并通过 | 连接多个异常类。
1 | public class MultiCatchDemo { |
解释:
catch (NullPointerException | ArrayIndexOutOfBoundsException e):捕获NullPointerException或ArrayIndexOutOfBoundsException类型的异常。- 这样做可以减少代码重复。
6. 自定义异常
除了使用 Java 提供的异常类型外,还可以创建自己的异常类。自定义异常通常用于程序中特定的错误处理需求。
6.1 自定义异常类
1 | // 定义一个自定义异常类 |
解释:
InvalidAgeException是一个自定义的异常类,继承自Exception类。- 在
main方法中,如果age小于 0,就抛出这个自定义异常。
7. 受检异常 vs 非受检异常
7.1 受检异常(Checked Exceptions)
受检异常是 Java 编译器强制要求处理的异常。如果代码中可能抛出受检异常,必须使用 try-catch 块来捕获异常,或者通过 throws 声明将异常抛给调用者。
1 | public class CheckedExceptionDemo { |
7.2 非受检异常(Unchecked Exceptions)
非受检异常(也称为运行时异常)继承自 RuntimeException,编译器不会强制要求你捕获或声明这些异常。常见的非受检异常有 NullPointerException、ArrayIndexOutOfBoundsException 等。
1 | public class UncheckedExceptionDemo { |
8. throws 关键字
throws 用于声明一个方法可能抛出的异常。当方法内部可能会抛出受检异常时,需要使用 throws 将异常抛出给调用者处理。
1 | public class ThrowsDemo { |
解释:
test()方法声明了它可能抛出IOException异常,所以调用test()时需要捕获异常。- 使用
throws关键字将异常传递给调用者进行处理。
9. throw 关键字
throw 用于在方法内部抛出一个异常。与 throws 不同,throw 是用来实际抛出异常的。
1 | public class ThrowDemo { |
解释:
throw new InvalidAgeException("Age cannot be negative!")抛出一个自定义异常。
10. 总结
- 异常处理:通过
try-catch-finally语句可以捕获和处理程序中的异常,finally块用于执行无论是否发生异常都需要执行的代码。 - 受检异常和非受检异常:受检异常必须进行处理,而非受检异常可以不处理。
- 自定义异常:可以根据业务需求定义自己的异常类。
- **
throw和throws**:throw用于抛出异常,throws用于声明方法可能抛出的异常。 - 异常的作用:异常处理可以提高程序的健壮性,防止程序在遇到错误时崩溃。
匿名类
匿名类(Anonymous Class)
匿名类是 Java 中一种没有名字的类。它通常用于临时创建实现了某个接口或者继承了某个类的类的实例,适用于当你只需要该类的一次性实现时。匿名类提供了一种简洁的方式来表达某些行为,而不需要定义一个额外的类。
1. 匿名类的定义
匿名类的基本语法如下:
1 | new 类名或接口名() { |
1.1 匿名类的特点
- 匿名类没有类名,它是一个局部类,通常在创建该类的地方直接实例化。
- 匿名类可以实现接口或者继承类。
- 匿名类通常用于临时的类实现,不能单独重用。
2. 匿名类的示例:实现接口
匿名类最常见的用途是实现接口。当你需要创建一个接口的实现类,并且只在一个地方使用时,使用匿名类是一个非常简洁的方式。
示例:实现接口
1 | interface Greeting { |
解释:
Greeting接口只有一个方法greet(),我们通过匿名类来实现它。- 在创建匿名类时,不需要显式地定义类名,直接在
new Greeting()后定义接口方法的实现。
3. 匿名类的示例:继承类
匿名类也可以继承一个类并重写其方法。当你需要临时创建一个类,并覆盖其某些方法时,匿名类也是一种非常有效的方式。
示例:继承类
1 | class Animal { |
解释:
Animal类有一个sound()方法。- 使用匿名类创建了一个
Animal的子类,并重写了sound()方法来输出 “Bark”。
4. 匿名类的特点与限制
- 局部类:匿名类是局部类,通常只能在其所在的代码块中使用(如
main()方法)。它不能在其他地方重复使用。 - 只能有一个方法:匿名类通常用于实现接口或者重写单个方法,因此它没有构造函数和其他成员。
- 不能有构造函数:匿名类不能有显式的构造函数,因此实例化时只能使用默认构造方法。
5. 使用匿名类的场景
匿名类常用于以下场景:
- 事件监听器:在 GUI 编程中(如 Swing 或 AWT),匿名类常用来实现事件监听器。
- 回调函数:在一些回调机制中,匿名类是实现接口或抽象方法的一种便捷方式。
- 一次性使用的接口实现:当你只需要一次性使用某个接口的实现时,匿名类能简化代码。
示例:事件监听器
1 | import java.awt.*; |
解释:
- 在
addActionListener()方法中,我们使用匿名类来实现ActionListener接口。 - 匿名类提供了
actionPerformed()方法的实现,当按钮被点击时,它会输出 “Button clicked!”。
6. 匿名类与 Lambda 表达式的比较
在 Java 8 中,Lambda 表达式被引入,提供了一种更简洁的方式来实现函数式接口。与匿名类相比,Lambda 表达式使代码更加简洁,尤其是在实现单一方法的接口时。
示例:Lambda 表达式 vs 匿名类
1 | // 使用匿名类实现接口 |
区别:
- 匿名类在实现接口时需要明确地声明类体,包括
@Override和方法的实现。 - Lambda 表达式通过
()和->符号简洁地表示方法实现,更加简洁。
7. 匿名类与局部变量
在匿名类中,可以使用外部方法中的局部变量,但有一些限制:
- 局部变量必须是 final 或 隐式 final,即变量的值不能在匿名类内部发生改变。
- 这是因为匿名类可能会在外部方法执行完毕后仍然存在,因此局部变量的值必须在匿名类创建时就确定。
示例:访问外部局部变量
1 | public class AnonymousClassDemo { |
解释:
multiplier是一个外部局部变量,它在匿名类中被使用。- 由于
multiplier是final,它的值在匿名类中可以被安全访问。
8. 总结
- 匿名类是没有名字的类,常用于临时实现接口或继承类的场景。
- 匿名类在语法上较为简洁,但限制较多,无法重用,并且不能有构造函数。
- 匿名类常见应用:事件监听器、回调机制和一次性接口实现。
- 与 Lambda 表达式相比,匿名类更为冗长,而 Lambda 表达式提供了更简洁的语法。
匿名类在许多应用中仍然非常有用,特别是当你需要快速实现接口或者重写类的某个方法时。如果你有更多关于匿名类的问题,或者想了解 Lambda 表达式等其他高级特性,随时告诉我!
字符串的处理(字符串、正则表达式、stringtokenizer、scanner)
字符串的处理(String, 正则表达式, StringTokenizer, Scanner)
在 Java 中,字符串处理是非常常见且重要的任务。Java 提供了多种工具来帮助开发者高效地处理字符串,包括字符串类 String 和 StringBuilder,以及用于解析字符串的 StringTokenizer 和 Scanner。此外,正则表达式(Regular Expressions)也是字符串处理中的一项强大工具。
1. 字符串类:String
1.1 字符串的创建与操作
在 Java 中,字符串是不可变的对象。String 类提供了许多方法来操作字符串。
示例:常见的 String 方法
1 | public class StringDemo { |
解释:
length():返回字符串的长度。indexOf(String str):返回指定子字符串首次出现的索引。replace():替换字符串中的部分内容。toUpperCase()/toLowerCase():将字符串转换为大写或小写。
1.2 字符串的比较
String 类提供了以下方法用于字符串的比较:
equals(String str):比较两个字符串的内容是否相同(区分大小写)。equalsIgnoreCase(String str):比较两个字符串的内容是否相同(忽略大小写)。compareTo(String str):按照字典顺序比较两个字符串。
1 | public class StringCompareDemo { |
2. 正则表达式(Regular Expressions)
正则表达式(Regex)是字符串模式匹配的一种工具。在 Java 中,正则表达式的主要使用类是 Pattern 和 Matcher。
2.1 基本用法
1 | import java.util.regex.*; |
解释:
Pattern.compile():编译正则表达式。matcher.find():查找匹配项。matcher.start():获取匹配项的起始位置。matcher.replaceAll():替换所有匹配的字符串。
2.2 常用的正则表达式
.:匹配任何单个字符。\d:匹配任何数字字符(0-9)。\w:匹配字母、数字或下划线。\s:匹配任何空白字符(如空格、制表符等)。[]:字符集,匹配括号内的任意字符。+:匹配前面的子表达式 1 次或多次。*:匹配前面的子表达式 0 次或多次。^:匹配输入的开始位置。$:匹配输入的结束位置。
示例:邮箱验证
1 | public class EmailValidation { |
3. StringTokenizer
StringTokenizer 是一个较老的类,用于将字符串分割为多个标记(tokens)。它通常用于解析以分隔符为基础的字符串。
示例:使用 StringTokenizer
1 | import java.util.StringTokenizer; |
解释:
StringTokenizer用于将字符串拆分为多个标记。第二个参数是分隔符,可以是一个或多个字符。nextToken()返回当前标记并移动到下一个标记。
注意:
StringTokenizer 已经被标记为过时(deprecated),推荐使用 split() 方法或者 Scanner 类来代替它。
4. Scanner 类
Scanner 是一个功能强大的类,可以用于从不同的输入源读取数据,如键盘输入、文件、字符串等。它还可以通过正则表达式来分割字符串。
4.1 Scanner 用法:读取字符串
1 | import java.util.Scanner; |
4.2 Scanner 用法:使用正则分割
1 | import java.util.Scanner; |
解释:
next()方法返回下一个输入项(例如,单词或数字)。nextInt()、nextDouble()等方法用于读取特定类型的输入。useDelimiter()方法允许使用正则表达式作为分隔符。
5. 总结
String是 Java 中用于处理字符串的基础类,提供了丰富的方法来操作和比较字符串。- 正则表达式 提供强大的字符串模式匹配功能,
Pattern和Matcher类用于在 Java 中使用正则表达式。 StringTokenizer用于将字符串分割为多个标记,但已经不推荐使用,建议使用split()或Scanner。Scanner提供了更灵活的方式来解析字符串和读取输入,支持使用正则表达式来分割字符串。
泛型
泛型(Generics)
泛型(Generics)是 Java 中的一种强大机制,它允许你在编写类、接口和方法时使用类型参数。通过使用泛型,Java 提供了对类型的抽象,从而增强了代码的可重用性、类型安全性和可读性。
泛型允许我们编写适用于不同数据类型的代码,而不必为每种数据类型编写重复的代码。泛型常用于集合类中,比如 List、Set 和 Map 等。
1. 泛型的基础
泛型的核心思想是类型参数化,它使得代码在编译时能更好地检查类型,避免了运行时的类型错误。
1.1 泛型类
1 | // 定义一个泛型类 |
解释:
Box<T>是一个泛型类,其中T是类型参数,表示该类可以使用任何类型。- 在实例化时,可以指定类型(如
Integer、String等),并且Box类的方法将根据指定类型进行类型安全检查。
1.2 泛型方法
泛型不仅可以用于类,也可以用于方法。我们可以定义一个泛型方法,使得方法能够处理不同类型的参数。
1 | public class GenericMethodDemo { |
解释:
- 泛型方法
printArray可以接受任何类型的数组,并打印出数组中的元素。 - 方法的类型参数
<T>放在方法的返回类型前面,表示该方法可以接受任意类型的数组。
2. 泛型接口
泛型不仅可以应用于类和方法,还可以应用于接口。
1 | interface Pair<K, V> { |
解释:
Pair<K, V>是一个泛型接口,接受两个类型参数K和V,分别表示键和值。ConcretePair是一个实现了Pair接口的泛型类,它实现了getKey()和getValue()方法,返回K类型和V类型的值。
3. 泛型与通配符(Wildcard)
在泛型中,通配符(Wildcard) 是一个非常有用的概念,通常用于方法的参数类型上,表示接受任何类型。通配符有以下几种常见的形式:
3.1 ?(通配符)
? 表示任意类型。它可以在泛型中作为类型的占位符。
1 | public class WildcardDemo { |
解释:
- 使用
List<?>可以接受任何类型的List,这意味着方法printList可以接受任何类型的List。
3.2 上界通配符:? extends T
上界通配符 ? extends T 表示接受 T 类型及其子类型。
1 | public class UpperBoundWildcardDemo { |
解释:
List<? extends Number>可以接受任何类型是Number的子类的List,包括Integer、Double等。
3.3 下界通配符:? super T
下界通配符 ? super T 表示接受 T 类型及其父类型。
1 | public class LowerBoundWildcardDemo { |
解释:
List<? super Integer>表示接受Integer类型及其父类型的List(如Number或Object)。
4. 泛型的类型擦除
Java 中的泛型在编译时会进行类型擦除(Type Erasure)。这意味着,泛型类型的信息在编译时会被移除,并且被替换成原始类型。类型擦除的目的是为了与 Java 的兼容性,使得泛型代码能够在运行时与非泛型代码一起工作。
4.1 示例:类型擦除
1 | public class TypeErasureDemo { |
解释:
- 尽管
Box<Integer>和Box<String>是不同类型的泛型类,但它们的实际类型在编译后都会变成Box(原始类型)。 - 因此,
intBox.getClass()和strBox.getClass()会返回相同的Box类型。
5. 总结
- 泛型类:通过使用类型参数,可以创建适用于多种类型的类。
- 泛型方法:允许方法根据不同的类型进行操作,使得方法更具通用性。
- 泛型接口:接口也可以使用泛型,定义多个类型参数。
- 通配符:
?表示任意类型,? extends T表示类型的上界,? super T表示类型的下界。 - 类型擦除:泛型在编译时会进行类型擦除,泛型的类型信息在运行时并不可用。
泛型提高了代码的类型安全性和可重用性,减少了类型转换的错误。

