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
表示类型的下界。 - 类型擦除:泛型在编译时会进行类型擦除,泛型的类型信息在运行时并不可用。
泛型提高了代码的类型安全性和可重用性,减少了类型转换的错误。