Java总结
总结
一些关键字用法
1、instanceof关键字
instanceof是一个关键字,
用于检查一个对象是否属于特定类或特定类的子类。
class Animal { }
class Dog extends Animal { }
class Cat extends Animal { }
public class Main {
public static void main(String[] args) {
Animal animal = new Dog();
if (animal instanceof Dog) {
System.out.println("animal is a Dog");
} else if (animal instanceof Cat) {
System.out.println("animal is a Cat");
} else if (animal instanceof Animal) {
System.out.println("animal is an Animal");
}
}
}
2、synchronized关键字
synchronized
是 Java 中用于实现线程同步的关键字。在多线程环境中,多个线程可能会同时访问共享资源,如果没有适当的同步控制,可能会导致数据不一致或竞态条件。
通过使用 synchronized
关键字,您可以确保在同一时间只有一个线程能够访问被同步的代码块或方法,从而防止多个线程之间的竞争条件。synchronized
提供了互斥访问,即一个线程在执行被 synchronized
保护的代码块时,其他线程将被阻塞,直到执行完毕。
在 Java 中,synchronized
有两种用法:同步代码块和同步方法。
-
同步代码块:
synchronized (lockObject) { // 要同步的代码块 }
在这里,
lockObject
是一个用于同步的对象。多个线程共享同一个lockObject
,只有一个线程能够进入同步代码块执行。 -
同步方法:
public synchronized void synchronizedMethod() { // 要同步的方法体 }
在这里,整个方法体都是被同步的,即使方法内有多个代码块,只要线程进入该方法,其他线程就无法进入该方法。
以下是一个使用同步代码块的示例,保证了对共享资源的安全访问:
class Counter {
private int count = 0;
private Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
public int getCount() {
synchronized (lock) {
return count;
}
}
}
public class Main {
public static void main(String[] args) {
Counter counter = new Counter();
Runnable incrementTask = () -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
};
Thread thread1 = new Thread(incrementTask);
Thread thread2 = new Thread(incrementTask);
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final count: " + counter.getCount()); // 应该输出 2000
}
}
3、其它关键字作用
- assert:
assert
关键字用于在代码中插入断言(assertion),用于在调试和开发过程中验证代码的正确性。如果断言条件为false
,则会引发AssertionError
异常。 - native:
native
关键字用于声明一个方法是用非Java代码(通常是用C或C++编写的本地代码)实现的。这使得Java程序可以与底层系统和硬件进行交互。 - strictfp:
strictfp
关键字用于修饰类、接口或方法,指示使用 IEEE 754 标准来执行浮点运算。这可以确保不同平台上的浮点运算结果保持一致。 - transient:
transient
关键字用于修饰类的实例变量,指示这些变量不会被默认的序列化机制序列化。这可以用于防止敏感信息在序列化时被暴露。 - volatile:
volatile
关键字用于修饰类的实例变量,指示这些变量是易变的(即可能被多个线程同时修改)。使用volatile
关键字可以确保在不同线程中对变量的修改能够正确地进行同步。
一、什么是变量
1.1 概念:
计算机内存中的一块存储空间,是存储数据的基本单元。
计算机用于存储数据的其中一种容器,都有特定的地址(用于区分其它变量)。
1.2 变量的作用域
局部变量
局部变量在方法、代码块或语句中定义,并且只能在其定义的范围内访问。
全局变量(成员变量或类级别变量)
类级别的变量,也称为成员变量,定义在类中但在方法外,可以被类中的任何方法访问。
1.3 数据类型
Java中的变量具有严格的数据类型区分。(强类型语言)
在Java语言中,任何一个值,都有其对应类型的变量。
基本类型 | 引用类型 | 缓冲(存)区(数组)范围 |
---|---|---|
boolean | Boolean | [true, false] |
byte | Byte | |
short | Short | |
int | Integer | [-128, 127] |
long | Long | |
float | Float | 创建的都是新对象 |
double | Double | 创建的都是新对象 |
char | Character | [0, 127],字符对应的ASCII值 |
无 | String | 字符串缓冲区,equals比较 |
无 | Object(对象) |
1.4 位运算符
- 按位与
&
:对两个操作数的每个位执行逻辑与操作,如果两个操作数的相应位都是1,则结果位为1,否则为0。 - 按位或
|
:对两个操作数的每个位执行逻辑或操作,如果两个操作数的相应位中有一个为1,则结果位为1,否则为0。 - 按位异或
^
:对两个操作数的每个位执行逻辑异或操作,如果两个操作数的相应位不同,则结果位为1,否则为0。 - 按位取反
~
:对操作数的每个位执行逻辑取反操作,即将0变为1,将1变为0。 - 左移
<<
:将操作数的二进制表示向左移动指定的位数,低位补0。 - 右移
>>
:将操作数的二进制表示向右移动指定的位数,高位填充符号位(对于负数)或0(对于正数)。 - 无符号右移
>>>
:类似于右移操作,但无论正负数都在高位填充0,用于逻辑位移。
int a = 5; // 二进制 0101
int b = 3; // 二进制 0011
int andResult = a & b; // 0101 & 0011 = 0001 (1)
int orResult = a | b; // 0101 | 0011 = 0111 (7)
int xorResult = a ^ b; // 0101 ^ 0011 = 0110 (6)
int notResult = ~a; // ~0101 = 1010 (负数)
int leftShiftResult = a << 2; // 0101 << 2 = 10100 (20)
int rightShiftResult = a >> 1; // 0101 >> 1 = 0010 (2)
// 右移1位:0101 - 0010 = 2
// 右移2位:0101 - 0001 = 1
System.out.println(5 >> 2);
// 左移1位: 0101 - 01010 = 10
// 左移2位: 0101 - 010100 = 20
System.out.println(5 << 2);
二、什么是方法
2.1 概念
可以实现特定功能的一段代码,可以反复使用。方法(Method)是一种**用于执行特定任务或操作的代码块。用于表示类的行为或功能。 **
2.2 作用
方法的作用:便于阅读(更加结构化)、可以使程序更加模块化、降低后续的维护难度和可以反复使用(可以减少代码重复)。
2.3 方法重写
方法的重写是方法的覆盖,方法的重载是方法的扩展。
什么时候使用:父类中的方法满足不了子类的需求。
方法重写(或覆盖,Method Overriding) 方法重写是指在子类中定义一个与父类中具有相同名称、参数列表和返回类型的方法,从而覆盖(替换)父类中的方法实现。
重写原则:
-
必须在继承关系中发生。
-
方法名称、参数列表和返回值类型与父类相同。
-
修饰符可与父类相同或是比父类更宽泛,但修饰符不能比父类更严格。
-
不能抛出比父类更多或更宽泛的异常,可以不抛出异常或相同异常。
// 父类
class Animal {
void makeSound() {
System.out.println("Animal makes a sound.");
}
}
// 子类
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Dog barks.");
}
}
2.4 方法的重载
方法的重写是方法的覆盖,方法的重载是方法的扩展。
什么时候使用:当单个方法满足不了要实现的功能需求。
方法重载(Method Overloading)是指在同一个类中定义多个方法,它们具有相同的方法名但具有不同的参数列表(参数的类型、顺序或个数)。
class MathOperations {
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
}
重载原则
- 同一个类里的方法。
- 相同方法名不同参数列表(参数类型、顺序或个数)。
- 与方法的返回值和访问修饰符无关。
- 调用重载方法时,编译器会根据传入的参数的类型和个数来确定调用哪个重载方法。
2.5 限定修饰符
public
: 表示最大的访问权限,被public
修饰的类、方法、变量和内部类可以在任何地方被访问。protected
: 表示受保护的访问权限,被protected
修饰的成员可以在其所属类、子类以及同一个包中被访问。default
(包级访问): 如果没有指定访问修饰符,则默认是default
修饰符。被default
修饰的成员只能在同一个包中被访问。private
: 表示最小的访问权限,被private
修饰的成员只能在其所属类中被访问,其他类无法访问。
2.6 可变与不可变参数
可变参数
-
用…标记的参数,在参数列表的最后一个位置。
-
可接收任意数量的参数,包括零参数。
-
本质上可以理解为数组。
-
一个方法最多只能有一个可变参数,而且必须是最后一个参数。
不可变参数
-
明确指定了参数的数量和类型,不允许在调用方法时传入其他数量或类型的参数。
-
在方法体内,使用传入的参数进行相应的操作。
-
在 Java 中,大部分方法都属于不可变参数的情况。
三、什么是数组
3.1 概念
数组(Array)是一组连续的存储的空间,存储多个相同类型的数据。
**数组(Array)是一种用于存储多个相同类型数据项的数据结构。**数组允许在单个变量名下存储一组数据,并通过索引(地址)来访问其中的元素。每个元素都有一个唯一的索引(从0开始),用于区分不同的数据项。
3.2 特点
- 长度固定
- 类型相同
- 连续的内存空间
- 通过索引(指针或地址)访问
- 效率高
四、面向对象(OOP)
对象(Object)是类的实例化 ,类(Class)是对象的模板或蓝图,定义了对象将具有的行为和特征(属性)。
4.1 什么是对象
对象(Object):世间一切客观事物都可为对象。对象具有自己的行为与特征(属性)。
对象具有以下特征:
- 状态(State):对象的状态由其属性或成员变量定义。属性是对象的数据或状态信息,例如一个人的姓名、年龄等。
- 行为(Behavior):对象的行为由其方法或成员函数定义。方法是对象的操作或功能,例如一个人可以说话、走路等。
- 标识(Identity):每个对象都有一个唯一的标识,通过它可以区分不同的对象。在Java中,对象的标识通常是对象的引用地址。
对象的创建过程(即new 对象):
- 内存中开辟对象空间
- 为各个属性赋予初始值
- 执行构造方法中的代码
- 将对象的地址赋值给变量
4.2 什么是类
在Java中,类是一种重要的程序组织结构,它是一种用来封装数据和方法的模板或蓝图。类可以看作是一种自定义类型,用于描述具有相同属性和行为的对象。
类是一种用户自定义的数据类型,它是一个包含了数据成员(属性)和成员函数(方法)的逻辑单元。属性是类的状态或数据,用于存储对象的信息。而方法是类的行为或功能,用于描述对象的操作。
4.2.1 构造和toString()方法
在类中已定义的toString方法,调用类对象时自动调用toString方法,没有定义则输入的是类对象的内存地址。
构造方法(构造器)是一种特殊类型的方法,用于**在创建对象时初始化对象的状态。 **
- 对象的创建:构造方法在使用
new
关键字创建对象时自动调用,它负责为对象分配内存空间,并初始化对象的成员变量。 - 对象的初始化:构造方法用于设置对象的初始状态,可以在构造方法中为对象的成员变量赋初始值,或者执行其他必要的初始化操作。
- 限制对象的创建:通过定义构造方法,可以控制对象的创建过程,例如可以设置构造方法为私有的,这样外部代码就无法直接通过
new
关键字创建对象,而是通过静态工厂方法等方式创建对象。 - 验证对象的有效性:构造方法可以在对象创建时对参数进行验证,确保创建的对象是合法的,避免出现不合理的状态。
4.2.2 局部属性和全局属性
局部属性(Local variables)和全局属性(Global variables)是在编程中用于表示变量作用域的两个概念。
当全局属性与局部属性的名相同时,局部属性的优先级最大,即最先使用局部属性。
- 局部属性(Local Variables):
- 局部属性是在特定代码块(如方法、循环、条件语句等)内声明的变量,其作用域仅限于该代码块内部。
- 它们在声明的代码块执行期间有效,并在代码块执行结束后被销毁,不再可见。
- 由于局部属性的生命周期较短,它们在栈内存中分配空间,可以在方法或代码块内有效地重用相同名称的局部属性。
- 属性无访问修饰符。
- 属性需要手动初始化。
- 全局属性(Global Variables):
- 全局属性是在整个程序的顶层声明的变量,其作用域覆盖整个程序的范围。
- 它们在程序启动时创建,在程序结束时销毁,因此其生命周期与程序运行时间一致。
- 全局属性通常在堆内存中分配空间,因为它们的生命周期可能跨越多个方法或代码块。
- 属性有访问修饰符。
- 属性在对象实例化时自动初始化。
区别总结:
- 局部属性的作用域限定在特定的代码块内,其生命周期较短,在栈内存中分配。
- 全局属性的作用域覆盖整个程序,其生命周期与程序运行时间一致,在堆内存中分配。全局属性的使用应当谨慎,因为过多的全局属性可能导致程序的可维护性和可读性下降。在大多数情况下,推荐尽量使用局部属性,仅在必要时才使用全局属性。
4.2.3 static关键字
在Java中,static
是一个关键字,用于修饰类的成员(字段和方法)以及代码块。使用static
关键字可以将成员或方法与特定的类关联,而不是与类的实例(对象)关联。静态不可以方法非静态的属性或方法等之间不可以调用,但非静态可以访问所有(包括静态)。
static的成员(字段和方法)以及代码块,会被共享一份数据。
执行顺序:前提是new 对象
static:静态的,静态的代码与字节码文件一同加载进内存,优先于类对象
1.修饰属性为静态属性
可以直接通过类名调用,也可以通过对象名调用(不推荐使用)
静态属性只有个一份,多个对象共享一份属性
2.修饰方法为静态方法
可以直接通过类名调用,也可以通过对象名调用(不推荐使用)
静态方法只能调用静态方法和静态属性
一般工具类中的方法使用静态方法
3.修改构造代码块为静态构造代码块
当类被使用时,静态构造代码块优先执行,而且只会执行一次
构造代码块:存在类中的代码块,每次实例化对象之前都会优先执行
静态实例属性 > 静态构造代码块 > 构造代码块 > 构造方法
new 对象实例化 > 属性初始化 > 构造代码块 > 构造方法
1、静态成员变量:
- 静态成员变量也称为类变量,它们在所有类的实例之间共享相同的值。
- 静态成员变量使用
static
关键字进行修饰,并且通常在类加载时被初始化,而不是在类的实例被创建时初始化。 - 静态成员变量可以通过类名直接访问,而无需创建类的实例;也可以创建类的实例,通过对象.属性名访问。
public class MyClass {
static int count = 0; // 静态成员变量
public MyClass() {
count++;
}
}
2、静态方法:
- 静态方法也称为类方法,它们与类关联,而不是与类的实例关联。
- 静态方法使用
static
关键字进行修饰,并且可以在类加载时直接通过类名调用,而无需创建类的实例。 - 静态方法不能直接访问非静态成员变量或非静态方法,因为它们没有隐式的实例引用(即没有
this
)。
public class MyClass {
static void staticMethod() {
System.out.println("This is a static method.");
}
}
3、静态代码块:
- 静态代码块使用
static
关键字和花括号包围的代码块来定义,它在类加载时执行,且只会执行一次和最先执行。 - 静态代码块通常用于在类加载时进行初始化操作,例如初始化静态成员变量或执行其他一次性任务。
public class MyClass {
static {
System.out.println("This is a static block.");
}
}
需要注意的是,静态成员变量和静态方法属于类本身,而不属于类的实例。因此,可以通过类名直接访问静态成员和调用静态方法,无需创建类的实例。而非静态成员和方法则必须通过类的实例来访问和调用。
4.2.4 this关键字
this:指向是当前类。
this():调用无参构造方法,调用时必须写在第一行。
this(参数列表):调用有参构造方法,调用时必须写在第一行。
this.名:名可以为属性名、方法名等,调用该类的属性名、方法名等。
this关键字只能在非静态方法(实例方法)中使用**,因为静态方法是属于类而不是实例的。**在静态方法中不能使用this
,因为它们没有当前对象的引用 。
public class MyClass {
private int value;
// 构造函数重载
public MyClass() {
this(0); // 调用带参数的构造函数并设置默认值
}
public MyClass(int value) {
this.value = value;
}
public MyClass setValue(int value) {
this.value = value;
return this; // 返回当前对象引用
}
}
4.2.5 super关键字
**super:指向的是当前的对象的父类。 用于引用父类(超类)的成员或构造函数。它允许在子类中访问和调用父类的方法、字段和构造函数。 **
- super.属性:调用当前对象的父类属性。
- super.方法:调用父类的方法。
- super():调用父类的无参构造方法,调用时必须写在第一行。
- supper(参数列表):调用父类的有参构造方法,调用时必须写在第一行。
// 父类
class Parent {
int age = 30;
Parent(int age) {
this.age = age;
}
void display() {
System.out.println("Parent's display method");
}
}
// 子类
class Child extends Parent {
int age = 20;
int grade
Child(int age, int grade) {
super(age); // 调用父类的构造函数
this.grade = grade;
}
void display() {
System.out.println("Child's display method");
System.out.println("Child's age: " + age); // 子类自己的成员
System.out.println("Parent's age: " + super.age); // 父类的成员
}
}
4.3 三大特性
4.3.1 封装
概念:尽可能隐藏对象的内部实现细节,控制对象的修改及访问的权限。
优点: 封装有助于实现代码的模块化、安全性和可维护性。
4.3.2 继承
继承(extends):子类继承父类的行为和特征(属性),当多个类具有多个相同的属性或方法时,可以把这些抽取到一个类中,然后用extends继承这个类的属性或方法。
它允许一个类(称为子类或派生类)从另一个类(称为父类或基类或超类)继承属性和行为。继承是一种表现“是一个”关系的概念,即子类是父类的特殊化。
特点:
- 类与类之间只能是单继承,不能多继承,但支持多层继承。
- 子类继承父类的所有属性和方法,私有的无法访问。
- 父类的构造方法无法被子类继承。
- 每个子类的构造方法默认存在super(),即初始化父类对象,所以可以自动调用父类的无参构造方法。
继承中的成员变量访问特点:就近原则
(先在局部找,本类成员位置找,父类位置找,逐级往上),没使用this和super关键字的情况下。
4.3.3 多态
多态: 对象的多种形态
,实现多态的方式是通过方法的重写(覆盖)和方法的动态绑定。
- 必须发生在继承关系中,子类要重写(或覆盖)父类的方法。
- 对象行为的多种表现形式。
- 父类对象引用子类对象。
多态的表现形式:
父类类型 对象名称 = 子类对象
多态的优势:
在多态的形式下,右边对象可以实现解耦,便于扩展和维护。
定义方法的时候,使用父类类型作为参数,可以接收所有子类对象,体现了多态的扩展和便利。
弊端:
不能使用子类的特有功能
// 方法的重写(覆盖)
class Animal {
void makeSound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Dog barks");
}
}
// 方法的动态绑定
public class Main {
public static void main(String[] args) {
Animal animal = new Dog();
animal.makeSound(); // 输出:Dog barks
}
}
多态性可以通过继承和接口实现。在继承中,多个子类可以继承自同一个父类,但每个子类可以**根据自己的特性和需求来重写父类的方法(即对象行为的多种表现形式)。**在接口中,多个类可以实现同一个接口,但每个类可以根据接口定义的规范来实现自己的方法。
多态的主要特点是:**同一种方法调用可以根据对象的实际类型(即运行时类型)来执行不同的行为。这意味着在编译时并不知道对象的真实类型,而是在运行时动态地确定。 **
多态性的核心概念是**“一个接口,多种实现”。**通过多态性,我们可以编写更加通用和通用的代码,以适应不同类型的对象,而不需要针对每种具体的类型编写专门的代码。这使得程序更加灵活、可扩展和易于维护。
4.4 接口
4.4.1 概念
接口相当于特殊的抽象类,定义方式、组成部分与抽象类类似。使用interface关键字定义接口。
implements是一个关键字,用于表示一个类实现(implements)一个或多个接口的功能。当一个类实现了一个接口,它必须提供接口中定义的所有抽象方法的具体实现。接口可以继承接口,而且是是多继承的。
微观概念:接口是一种能力和约定,功能的扩展。
- 接口的定义:代表了某种能力。
- 方法的定义:能力的具体要求
宏观概念:接口是一种标准、规范。
4.4.2 特点
- 没有构造方法,不能创建对象(实例化)。
- 只能定义:默认公开静态常量、公开抽象方法。
- 无法直接实例化 : 接口中的方法没有具体实现 。
- 属性无法直接初始化:接口的属性默认为公开静态常量,需要手动赋值。
- JDK8新特性允许接口存在static和final修饰的普通方法。
4.5 抽象类
4.5.1 概念
Java中的抽象类(Abstract Class,**模板**)是一种特殊的类,它**不能直接被实例化,而是用作其他类的基类。**抽象类通常用于定义一组相关的类的公共接口,而**具体的实现由其子类完成。**
- 被abstract修饰的类,称为抽象类。
- 抽象类意为不够完整的类、不够具体的类,抽象方法没有方法体。
- 抽象类对象无法独立存在,即不能实例化对象,因为存在抽象方法。
- 抽象类存在构造方法,因为子类的构造方法中默认存在super(),父类也可以是创建有参和无参构造方法(作用:当子类创建对象时,给属性赋值)。
- 抽象类的子类可以是抽象类。
default:
这是一种允许在接口中提供方法实现的机制。默认方法允许您在接口中定义具有默认实现的方法,而不会破坏已经实现该接口的类的兼容性。
这对于向已有接口添加新功能非常有用,因为不需要修改所有实现该接口的类。
interface MyInterface {
// 默认方法
default void printHello() {
System.out.println("Hello from MyInterface");
}
void regularMethod(); // 抽象方法
}
class MyClass implements MyInterface {
// 不必实现 printHello 方法,因为它有默认实现
@Override
public void regularMethod() {
System.out.println("Regular method implementation");
}
}
public class Main {
public static void main(String[] args) {
MyClass myClass = new MyClass();
myClass.printHello(); // 使用默认方法
myClass.regularMethod(); // 使用实现的方法
}
}
4.5.2 作用和特点
作用:
- 可被子类继承,提供共性属性和方法(抽象方法和普通方法)。
- 可声明为引用,更自然的使用多态。
抽象类的特点:
- 抽象类不能被实例化,即不能使用
new
关键字创建抽象类的对象。 - 抽象类可以包含成员变量、常量、构造方法、普通方法以及抽象方法。
- 抽象方法是没有方法体的,只有方法声明,由子类继承后进行具体实现。
- 如果一个类继承自抽象类,则它必须实现(重写)抽象类中的所有抽象方法,除非该子类也是抽象类。
- 抽象类可以拥有普通方法的实现,子类可以直接继承和使用这些普通方法。
/**
* 定义个图形的父类
*/
abstract class Shape {
// 定义颜色属性
String color;
// 定义抽象方法
abstract double getArea();
// 普通方法
public void setColor(String color){
this.color = color;
}
}
/**
* 创建圆的类
*/
public class Circle extends Shape{
// 定义圆的半径
double radius;
// 实现父类的抽象方法
@Override
double getArea() {
return Math.PI * radius * radius;
}
}
public class Main {
public static void main(String[] args) {
// 实例化circle对象,并调用getArea()计算圆的面积
Circle circle = new Circle();
circle.radius = 10.1;
System.out.println(circle.getArea());
}
}
//输出结果:320.4738665926948
4.6 Lambda表达式
4.6.1 基础语法
在 Java 中,匿名函数通常用于函数式接口的实现。函数式接口是指只包含一个抽象方法的接口。通过匿名函数(Lambda 表达式)可以在不创建命名类的情况下直接实现函数式接口,使代码更加简洁和易读。
特点:
- 只能接收收函数式接口,不能是抽象类
- 作用域: Lambda表达式的变量作用域与匿名类的变量作用域类似。它们可以访问外部作用域的变量,但在Lambda内部不能声明与外部作用域同名的局部变量。
实现函数式接口:通过匿名函数实现函数式接口,省去了创建实现类的步骤。
// 定义一个函数式接口
public interface MyFunction {
// 定义无参无返回值的接口方法
void hello();
}
interface Method01{
// 定义无参又返回值的接口方法
int number();
}
public static void main(String[] args) {
// 使用匿名函数实现函数式接口
MyFunction my = () -> {
System.out.println("Hello");
};
my.hello();
Method01 method01 = () -> 10;
System.out.println(method01.number());
}
使用参数:匿名函数可以接收参数,并在函数体中使用这些参数。
返回值:匿名函数可以返回值,如果函数体只有一行代码,可以省略大括号和 return 关键字。
interface Method02{
// 定义无参又返回值的接口方法
int number(int a, int b);
}
public static void main(String[] args) {
// 使用匿名函数实现函数式接口
Method02 method02 = (a, b) -> a * b;
System.out.println(method02.number(10, 20));
多行代码:如果函数体有多行代码,可以使用大括号包裹代码块,并使用 return 关键字返回结果。
interface MyFunction {
String operate(int x);
}
public class Main {
public static void main(String[] args) {
// 使用匿名函数实现多行代码的函数式接口
MyFunction operation = x -> {
int result = x * 2;
return "Result: " + result;
};
System.out.println(operation.operate(10)); // Output: Result: 20
}
}
使用局部变量:Lambda 表达式可以访问外部的局部变量,但是这些局部变量必须是 final 或者 effectively final。
interface MyFunction {
String operate(int x);
}
public class Main {
public static void main(String[] args) {
// 使用匿名函数实现多行代码的函数式接口
MyFunction operation = x -> {
int result = x * 2;
return "Result: " + result;
};
System.out.println(operation.operate(10)); // Output: Result: 20
}
}
4.6.2 forEach语法
在 Java 中,forEach
是一个方法,用于对集合类(例如:List、Set、Map)中的元素进行遍历。它是在 Java 8 中引入的新方法,属于 Java 集合框架的一部分,主要用于函数式编程风格的遍历操作。
forEach
方法接受一个函数式接口(也称为函数接口)作为参数,该接口定义了一个接收元素并对其进行处理的方法。在遍历集合时,forEach
方法会依次将集合中的每个元素传递给该函数式接口,从而实现对每个元素的操作,例如输出、修改、过滤等 。
void forEach(Consumer<? super T> action)
其中,action
是一个 Consumer 接口的实例或 Lambda 表达式,用于处理集合中的每个元素。
在 Java 中,forEach
方法主要用于遍历集合类(List、Set、Map)中的元素,以及数组和流(Stream)中的元素。
遍历集合类中的元素:
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");
names.forEach(System.out::println);
// asList将数组转为集合
List<String> names2 = Arrays.asList("Alice", "Bob", "Charlie");
// 使用forEach方法输出集合中的元素
names2.forEach(System.out::println);
**遍历数组中的元素: **
int[] numbers = {1, 2, 3, 4, 5};
Arrays.stream(numbers).forEach(System.out::println);
遍历流(Stream)中的元素:
List<String> names2 = Arrays.asList("Alice", "Bob", "Charlie");
// 遍历流(Stream)中的元素:
names.stream()
// 使用流的 filter 方法对集合进行过滤,
// 筛选出以字母"A"开头的元素
.filter(name -> name.startsWith("A"))
.forEach(System.out::println);
遍历 Map 中的键值对:
// 遍历 Map 中的键值对:
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 90);
scores.put("Bob", 80);
scores.put("Charlie", 85);
scores.forEach((name, score) -> System.out.println(name + ":" + score));
4.7 泛型
在 Java 中,可以通过在类名后面使用尖括号 来创建泛型类,或者在方法签名中使用尖括号
来创建泛型方法。泛型允许我们在定义类或方法时使用一个或多个类型参数,从而实现类型的参数化。
概念:
- Java泛型是JDK1.5中引入的一个新特性,其本质是参数化类型,把类型作为参数传递。
- 常见形式有泛型类、泛型属性、泛型接口、泛型方法。
- 泛型
本质是类型的参数化。
特点:
泛型不具备继承性,但是数据具有继承性。
public class Demo01 {
public static void main(String[] args) {
ArrayList<Ye> list1 = new ArrayList<>();
ArrayList<Fu> list2 = new ArrayList<>();
ArrayList<Zi> list3 = new ArrayList<>();
// 验证泛型是否具有继承性,后两个会报错
method(list1);
// method(list2);
// method(list3);
// 数据是否具有继承性
list1.add(new Ye());
list1.add(new Fu());
list1.add(new Zi());
}
private static void method(ArrayList<Ye> list) {
}
}
class M implements MyInterface{ }
class Ye { }
class Fu extends Ye {}
class Zi extends Fu {}
优点:
- 提高代码的重用性。
- 防止类型转换异常,提高代码的安全性。
4.7.1 创建泛型属性
注意:给属性设置为泛型是,类也要定义为泛型类。
public class MyClass<T, Y> {
private Y data;
public MyClass(T data) {
this.data = data;
}
public T getData() {
return data;
}
}
4.7.2 创建泛型类:
创建泛型类或泛型接口:会牵引其它泛型都为类或接口定义的泛型类型。
public class MyClass<T> {
private T data;
public MyClass(T data) {
this.data = data;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
/*
在上面的示例中,MyClass<T> 是一个泛型类,其中的 <T> 表示类型参数,可以在类中的属性和方法中使用类型参数 T。
*/
4.7.3 创建泛型方法
泛型方法可以有多个泛型形参。
public class MyUtils {
public static <T> T getValue(T[] array, int index) {
if (index >= 0 && index < array.length) {
return array[index];
}
return null;
}
}
/*
在上面的示例中,<T> 表示类型参数,可以在方法的返回类型和参数列表中使用类型参数 T。
*/
4.7.4 泛型接口
public interface MyInterface<T> {
default void method(T t){
System.out.println(t);
}
}
4.7.5 泛型通配符
?:表示不确定类型。
? extends E:表示可以传递E或者E所有的子类类型。
? super E:表示可以传递E或者E所有的父类类型。
使用场景
定义类、方法、接口的时候,如果类型不确定,就可以定义泛型。
如果类型不确定,但是能知道是哪个继承体系中的,可以使用泛型的通配符。
-
无限通配符
?
:表示未知类型,可用于表示泛型类型的任何实例。List<?> list = new ArrayList<>();
-
上限通配符
? extends T
:表示类型的上界限制,表示类型是某个类的子类或该类本身。List<? extends Number> numbers = new ArrayList<Integer>();
-
下限通配符
? super T
:表示类型的下界限制,表示类型是某个类的父类或该类本身。List<? super Integer> integers = new ArrayList<Number>();
4.8 final
概念:最后的,不可更改的。
final修饰的变量必须在声明时或构造函数中进行初始化(即手动赋值),并且后续不能再修改其值。
4.8.1 常量
final修饰变量:此变量值不能被改变(常量),只能赋值一次,值不允许改变。常与static关键字一起出现,意为静态常量。都是需要手动赋初始值。修饰的变量是引用数据类型,变量的存储地址值不会发生改变,但对象内部的可以改变。
/**
* 注意:常量命名规范:常量的所有字母都要大写,如果有多个单词则需要用下划线(_)隔开(拼接)。
*/
// 实例常量
final double PI = 3.14;
// 静态常量
final static double PI = 3.14;
// 对象常量:存储地址值不会发生改变,但对象内部的可以改变
final Student student = new Student();
// 静态对象常量
final static Student student = new Student();
4.8.2 最终方法
被修饰的方法为最终方法,不能改变;不能被重写(即覆盖),可以被重载,即该方法的实现是最终的,不能在子类中进行修改。不可修饰构造方法。
class Parent {
final void finalMethod() {
// 方法的定义
}
}
class Child extends Parent {
// 试图重写finalMethod会导致编译错误
// void finalMethod() { }
}
4.8.3 最终类
被修饰的类为最终类,不能被继承,即该类是最终类,不能有子类 。
final class FinalClass {
// 类的定义
}
4.9 反射
反射(Reflection)是一种**在运行时动态地获取类的信息、操作类的属性和方法的机制。**它允许程序在运行时检查、访问和修改类、方法、字段和构造函数等信息,而不需要事先在编译时明确地知道这些信息。
在Java中,反射是通过java.lang.reflect
包提供的一组类和接口实现的。主要涉及的核心类有Class
、Method
、Field
、Constructor
等。
通过反射,我们可以在运行时实现以下操作:
- 获取类的信息:获取类的名称、修饰符、父类、接口、字段、方法、构造函数等信息。
- 创建类的实例:通过类的无参或有参构造函数创建对象。
- 调用类的方法:通过方法名和参数调用类的方法。
- 获取和设置类的字段:读取和修改类的字段值。
4.9.1 常见方法
方法名 | 描述 |
---|---|
public String getName() | 获取类的完全名称 |
public Package getPackage() | 获取包信息 |
public Class<? super T> getSuperclass() | 获取父类 |
public Class<?>[] getInterfaces() | 获取实现父接口 |
public Field[] getFields() | 获取字段信息 |
public Method[] getMethods() | 获取方法信息 |
public Constructor<?>[] getConstructors() | 获取构造方法 |
public T newInstance() | 反射创建对象 |
4.9.2 getMethod()
在Java的反射机制中,getMethod
是Class
类的一个方法,用于获取指定类中的公共方法(包括继承的公共方法)或接口中的公共抽象方法。
getMethod
方法的定义如下:
public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException
name
:表示要获取的方法的名称。parameterTypes
:可选参数,表示方法的参数类型的Class
对象数组。如果该方法有参数,需要指定参数类型;如果没有参数,可以省略该参数。
getMethod
方法返回一个Method
对象,表示指定的方法。Method
类是Java反射机制中用于表示方法的类,它提供了一系列方法,可用于调用和操作方法。
示例:
假设我们有一个名为MyClass
的类,其中包含一个公共方法printMessage
:
public class MyClass {
public void printMessage(String message) {
System.out.println(message);
}
}
现在,我们可以使用反射的getMethod
方法来获取printMessage
方法并调用它:
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws NoSuchMethodException, SecurityException {
MyClass obj = new MyClass();
// 获取printMessage方法
Method method = obj.getClass().getMethod("printMessage", String.class);
// 调用printMessage方法
try {
method.invoke(obj, "Hello, Reflection!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 输出结果:printMessage
在Java的反射机制中,invoke
是Method
类的一个方法,用于调用指定方法(包括公共、私有、静态和非静态方法)的执行。
invoke
方法的定义如下:
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
obj
:表示要调用方法的对象,如果方法是静态方法,则可以将obj
设置为null
。args
:表示要传递给方法的参数列表,这是一个可变参数,可以传入任意数量的参数。
4.10 异常
概念:是指程序在运行过程中出现的特殊情况。
Throwable:可抛出的,一切错误或异常的父类,位于java.lang包中。
4.10.1 异常分类
1.语法异常
受检异常(Checked Exception,也叫语法异常):受检异常是在代码编译时必须处理的异常,即在方法的声明中使用throws
关键字声明或使用try-catch
块进行处理。典型的受检异常包括IOException
、SQLException
等。如果不处理受检异常,编译器会报错,强制要求在方法调用时进行异常处理。
- Error: JVM、硬件、执行逻辑错误,不能手动处理。
- 常见错误: StackOverflowError、 OutOfMemoryError等。
2.运行异常
非受检异常(Unchecked Exception):非受检异常也称为运行时异常(Runtime Exception),它是在代码运行时可能抛出的异常。
Exception:程序在运行和配置中产生的问题,可处理。
- RuntimeException:运行时异常,可处理,可不处理。
- CheckedException:检查时异常,必须处理。
常见运行时异常:
异常 | 描述 |
---|---|
NullPointerException | 空指针异常 |
ArrayIndexOutOfBoundsException | 数组越界异常 |
IndexOutOfBoundsException | 索引越界异常 |
ClassCastException | 类型转换异常 |
NumberFormatException | 数字格式化异常 |
ArithmeticException | 算术异常 |
IllegalArgumentException | 非法参数异常 |
IllegalStateException | 非法状态异常 |
ConcurrentModificationException | 并发修改异常 |
OutOfMemoryError | 内存溢出错误 |
SecurityException | 存在安全侵犯异常 |
StringIndexOutOfBoundsException | String类型下标异常 |
UnsupportedOperationException | 请求异常。 |
3.错误
- 错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。
4.10.2 异常处理关键字【重点
】
快捷键:Ctrl + Alt + t
- try:执行可能产生异常的代码
- catch:捕获异常,并处理。
- finally:无论是否出现异常,代码总能执行。
- throw:手动抛出异常。
- throws:声明方法可能抛出的各种异常。
五、集合
5.1 集合概念
**集合(Collection)是用于存储和操作一组对象的容器类。**它提供了一系列接口和实现类,**用于存储、添加、删除、查询和遍历对象。**集合类是Java中非常重要且常用的数据结构,用于替代数组等传统的数据结构,提供更加灵活和方便的操作。
数组与集合区别:
- 数组长度固定,集合长度不固定。
- 数组可以存储基本和引用类型,集合只能存储引用类型。
5.2 Collection体系集合
Collection体系集合 |
---|
![]() |
Collection:所有集合类的根接口,即父类接口,用于表示一组对象。它包含了常用的集合操作方法,如添加、删除、查询和遍历等。
常用方法:
方法 | 描述 |
---|---|
boolean add(Object obj) | 添加一个对象数据 |
boolean addAll(Collection c) | 将一个集合中的所有对象添加到此集合中 |
void clear() | 清空此集合中的所有对象 |
boolean contains(Object o) | 检查此集合中是否包含o对象 |
boolean equals(Object o) | 比较此集合是否与指定对象相等 |
boolean isEmpty() | 判断此集合是否为空 |
boolean remove(Object o) | 在此集合中移除o对象 |
int size() | 返回此集合中的元素个数 |
Object[] toArray() | 将此集合转换成数组 |
案例:
public static void main(String[] args) {
Collection collection = new ArrayList();
collection.add("a");
collection.add("a");
// 集合长度
System.out.println(collection.size());
// 添加对象
collection.add("b");
// 检查此集合中是否包含a
System.out.println(collection.contains("a"));
// 判断此集合是否为空
System.out.println(collection.isEmpty());
// 使用迭代器
Iterator iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
// 使用列表迭代器
ListIterator listIterator = list.listIterator();
while (listIterator.hasNext()){
System.out.println(listIterator.next());
}
}
5.3 List接口与实现类
5.3.1 List接口
特点:
- 有序、有下标、可重复的集合接口。
方法:
方法 | 描述 |
---|---|
void add(int index, Object o) | 在index位置插入对象o。 |
boolean addAll(int index, Collection c) | 将一个集合中的元素添加到此集合中的index位置。 |
Object get(int index) | 返回集合中指定位置的元素。 |
List subList(int fromIndex, int toIndex) | 返回fromIndex和toIndex之间的集合元素。 |
toArray() / toArray(T[] a) | 把集合对象转为数组 |
案例:
public static void main(String[] args) {
List list = new ArrayList();
List list2 = new ArrayList();
// 插入对象
list.add("a");
list.add("b");
list.add("c");
list2.add("a");
list2.add("b");
// 集合长度
System.out.println(list.size());
// 获取特定下标的对象
System.out.println(list.get(1));
// subList(a,b)获取下标为[a,b)的对象
System.out.println(list.subList(0, 2));
// 将一个集合中的元素添加到此集合中的index位置。
list.addAll(2, list2);
System.out.println(list.size());
// 把集合对象转为数组
// 指定类型
String[] strings = (String[]) list.toArray(new String[0]);
// object类型
Object[] objects = list.toArray();
System.out.println(Arrays.toString(strings));
System.out.println(Arrays.toString(objects));
}
5.3.2 List实现类
5.3.2.1 ArrayList
底层是动态数组,存放的数组满
特点:
- 存储的数据可以重复
- 有序
- 增删效率慢
- JDK1.2版本、线程不安全。
ArrayList 类位于 java.util 包中,使用前需要引入它,语法格式如下:
import java.util.ArrayList; // 引入 ArrayList 类
ArrayList<E> objectName =new ArrayList<>(); // 初始化
可以存储重复的数据和null、有序的集合底层是动态数组,存放的数组满了正常情况下会扩容为原本的1.5倍,
但当1.5倍超过 int 最大值-8,则判断+1是否超过 int 类型最大值-8,
没有超过则把 int 最大值-8当作是新容量,如果超过则把 int 类型最大值当作新容量。
ArrayList底层的代码刨析 |
---|
![]() |
方法 | 说明 |
---|---|
add() | 添加对象 |
addAll() | 把一个集合添加到另一个集合 |
remove() | 删除对象 |
removeAll() | 删除指定集合中包含的所有元素 |
set() | 修改对象 |
contains() | 判断是否存在指定对象 |
Empty() | 判断是否是空集合 |
size() | 获取集合长度 |
toArray() | 把集合转为数组 |
clear() | 清空集合 |
练习代码:
// 创建ArrayList对象:不指定类型,集合合存的类型是Object,即任意类型
ArrayList list = new ArrayList();
// 增:add(int index, E element) ,addAll(Collection<? extends E> c)
list.add("张三");
list.add("男");
// 添加到指定的集合位置
list.add(1, 18);
System.out.println(list);
ArrayList list2 = new ArrayList();
list2.add("李四");
list2.add(28);
list2.add("女");
// 把一个集合添加到另个集合
list.addAll(list2);
System.out.println(list);
// 删:remove(Object o)
// 删除指定位置的对象
// list.remove(0);
// 删除指定对象值的对象, 可能需要装箱
list.remove(Integer.valueOf(18));
System.out.println(list);
// 改:修改指定位置的对象的值
list.set(0, "小红");
System.out.println(list);
/*
查:forEach
语法:
for (循环变量数据类型 循环变量: 遍历的数组
) {
循环体
}
*/
for (Object obj: list
) {
System.out.println(obj);
}
// 判断是否存在指定对象
System.out.println(list.contains(28));
// 判断是否是空集合
System.out.println(list.isEmpty());
// 获取集合长度
System.out.println(list.size());
// 把集合转为数组
Object[] objects = list.toArray();
System.out.println(Arrays.toString(objects));
// 清空集合
list.clear();
System.out.println(list.isEmpty());
}
5.3.2.2 LinkedList
特点:
- 双向链表结构实现,增删快,查询慢。
- JDK1.2版本、线程不安全。
LinkedList底层的代码刨析 |
---|
![]() |
5.3.2.3 Vector
数组结构实现,查询快、增删慢,线程安全。
JDK1.0版本,线程安全、运行效率比ArrayList较慢。
5.3.2.4 Stack
Vector的子类,底层是动态数组,线程安全。
模拟栈堆的集合,先进后出(FILO)或后进先出(LIFO)
5.3.2.5 Queue
双向链表,先进先出(FLFO),后进后出(LILO)类似队列。
5.4 Set接口
5.4.1 HashSet
特点:
底层调用HashMap的put的方法进行(hash表)
无序、不可重复,可以存储null对象
JDK1.7 : hash表(数组+链表)
JDK1.8 : hash表(动态数组+链表)+ 红黑树
添加对象实现流程:
当集合添加对象时,首先会调用hashCode()方法生成hash值,根据hash值生成索引(物理位置),根据索引找到hash表上的位置,如果节点没有存在元素,直接存储到节点;存在,调用equals()方法与新元素比较,返回true,不存储;返回false,直接存储当前节点的下一个节点。可重写hashCode() 和equals()方法自定义规则。
5.4.2 TreeSet
底层是红黑二叉树(平衡二叉树)
特点:
- 基于排列顺序(二叉树)实现元素不重复,无序、可排序、不可存储 null。
- 实现了SortedSet接口,对集合元素自动排序。
- 元素对象的类型必须实现Comparable接口,指定排序规则(自然排序)。
- 通过CompareTo方法确定是否为重复元素。
自然排序:
-
存储到 TreeSet 的对象类实现 Comparable 接口, 重写 compareTo(),
-
在方法中制定排序规则,返回负数,存储的对象则往左子树方向;
-
返回正数,存储的对象则往右子树方向;0则不存储
-
compareTo() 方法中,this代表的是要存储的对象,参数代表的是集合中已存在的对象
自然排序 vs 比较器
-
1.自然排序是存在要存储的对象类中,要实现自然排序会改变类结构,
-
当存储的对象类由第三方提供,无法修改或者自定义自然排序规则
-
2.比较器会额外创建一个新的类
-
3.当自然排序和比较器同时存在时,使用比较器的规则
![]() |
![]() |
5.5 Collections工具类
集合工具类,定义了除了存取以外的集合常用方法。
常见方法:
方法 | 描述 |
---|---|
public static void reverse(List<?> list) | 反转集合中元素的顺序 |
public static void shuffle(List<?> list) | 随机重置集合元素的顺序 |
public static void sort(List list) / | 升序排序(元素类型必须实现Comparable接口) |
public static int binarySearch( list, T key) | 二分查找 |
5.6 Map接口
Map接口的特点:
- 用于存储任意键值对(Key-Value)。
- 键:无下标、不可以重复(唯一)。
- 值:无下标、可以重复 。
-
方法介绍
方法名 说明 V put(K key,V value) 添加元素 V remove(Object key) 根据键删除键值对元素 void clear() 移除所有的键值对元素 boolean containsKey(Object key) 判断集合是否包含指定的键 boolean containsValue(Object value) 判断集合是否包含指定的值 boolean isEmpty() 判断集合是否为空 int size() 集合的长度,也就是集合中键值对的个数 entrySet() 返回此地图中包含的映射的Set视图。
5.6.1 HashMap
底层是哈希表 + 链表 + 红黑树。
线程不安全。
key和value允许为空(null)。
public static void main(String[] args) {
// 创建Map对象
Map<Integer, String> map = new HashMap<>();
// 增
map.put(1001, "张三");
map.put(1002, "李四");
map.put(1003, "王五");
// 删
map.remove(1001);
// 改: 修改特定键的值
map.replace(1003, "a");
// 查
// System.out.println(map);
// 遍历
// 第一种遍历
System.out.println("----第一种遍历方式----");
Set<Integer> keySet = map.keySet();
for (Integer key : keySet) {
String value = map.get(key);
System.out.println(key + "=" + value);
}
System.out.println("----第二种遍历方式----");
// 第二种遍历
for (Map.Entry<Integer, String> maps : map.entrySet()) {
System.out.println(maps.getKey() + "=" + maps.getValue());
}
}
5.6.2 TreeMap
无序,key不可重复且不能为null,value可重复并且可为null。
5.6.3 HashTable
线程安全的集合
键和值不可为空,。
5.7 Comparator接口
在 Java 中,Comparator
是一个接口或者比较器,用于定义两个对象之间的比较规则
。它常常用于集合的排序、查找以及各种需要比较对象的算法中。通过实现 Comparator
接口,您可以自定义对象的比较方式。
Comparator
接口中只有一个方法需要实现:compare(T o1, T o2)
。这个方法接受两个参数,比较这两个参数并返回一个整数值,表示它们的顺序关系。具体来说:
- 如果
o1
小于o2
,应该返回负数。 - 如果
o1
等于o2
,应该返回零。 - 如果
o1
大于o2
,应该返回正数。
创建比较器类实现 Comparator 接口,重写 compare(),
compare() 中,第一个参数代表是要存储的对象,第二个参数代表集合中已存在的对象
在方法中制定排序规则,返回负数,存储的对象则往左子树方向;
返回正数,存储的对象则往右子树方向;0则不存储
5.7 Comparable接口
Comparable
是另一个在 Java 中用于对象比较的接口。与 Comparator
不同,Comparable
接口是对象自身实现的,用于定义对象的默认排序规则。
当一个类实现了 Comparable
接口时,它必须实现 compareTo
方法,该方法接受另一个对象作为参数,并返回一个整数,表示调用对象与参数对象之间的顺序关系。
compareTo
方法返回的整数遵循以下规则:
- 如果调用对象小于参数对象,应该返回负数。
- 如果调用对象等于参数对象,应该返回零。
- 如果调用对象大于参数对象,应该返回正数。
实现代码
public class User implements Comparable<User>{
private int id;
public User() {
}
public User(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
'}';
}
//返回负数往左子树方向,正数往右子树方向,0则不存储
@Override
public int compareTo(User user) {
//this 代表的是即将要存储进集合的对象
//参数为已存储进集合的对象
//按 id 从小到大排序
return user.getId()-this.getId();
}
5.8 迭代器
-
迭代器介绍
- 迭代器,集合的专用遍历方式
- Iterator iterator(): 返回此集合中元素的迭代器,通过集合对象的iterator()方法得到
-
Iterator中的常用方法
boolean hasNext(): 判断当前位置是否有元素可以被取出
E next(): 获取当前位置的元素,将迭代器对象移向下一个索引位置
六、常用类
6.1 Object类
6.2 String类
String 对象是不可变的,这意味着一旦创建了一个字符串对象,其内容就不能被改变。
字符串常量池(String Pool)是 Java 中一种用于存储字符串的特殊内存区域。它是为了优化字符串的存储和重用而设计的,使得相同内容的字符串在内存中只有一份拷贝,从而节省内存空间。
方法 | 含义 |
---|---|
charAt(int index) | 返回 char指定索引处的值。 |
compareTo(String anotherString) | 按字典顺序比较两个字符串。 |
concat(String str) | 将指定的字符串连接到该字符串的末尾。 |
contains(CharSequence s) | 当且仅当此字符串包含指定的char值序列时才返回true。 |
equals(Object anObject) | 将此字符串与指定对象进行比较。 |
equalsIgnoreCase(String anotherString) | 将此 String与其他 String比较,忽略案例注意事项。 |
getBytes() | 将字符串转为字节数组 |
indexOf(int ch) | 返回指定字符第一次出现的字符串内的索引。 |
isEmpty() | 返回 true如果,且仅当 length()为 0 。 |
lastIndexOf(int ch) | 返回指定字符的最后一次出现的字符串中的索引。 |
matches(String regex) | 告诉这个字符串是否匹配给定的 regular expression 。 |
replace(char oldChar, char newChar) | 返回从替换所有出现的导致一个字符串 oldChar在此字符串 newChar 。 |
split(String regex) | 将此字符串分割为给定的 regular expression的匹配。 |
startsWith(String prefix) | 测试此字符串是否以指定的前缀开头。 |
substring(int beginIndex) | 返回一个字符串,该字符串是此字符串的子字符串。 |
toCharArray() | 将此字符串转换为新的字符数组。 |
toLowerCase() | 将所有在此字符 String使用默认语言环境的规则,以小写。 |
toUpperCase() | 将所有在此字符 String使用默认语言环境的规则大写。 |
valueOf(基本数据类型) | 将一种基本数据类型转为字符串 |
substring() | 返回一个字符串,该字符串是此字符串的子字符串。 |
public static void main(String[] args) {
String str = "abcde1234567890";
String str2 = "abcde1234567890a";
System.out.println("str=" + str);
System.out.println("str2=" + str2);
System.out.println("返回指定索引为0的字符:" + str.charAt(0));
// A.compareTo(B) : A > B 返回1,A = B返回0,A < B返回-1
System.out.println("比较两个字符串大小:" + str.compareTo(str2));
String str3 = str.concat("E");
System.out.println("将指定的字符串连接到该字符串的末尾:" + str3);
// 判断是否包含指定字符序列
System.out.println("判断是否包含指定字符序列:" + str.contains("abcde"));
// 字符串与指定对象进行比较
System.out.println("将此字符串与指定对象进行比较:" + "a".equals(str));
String str4 = "abcDE1234567890";
System.out.println("str4=" + str4);
// 比较字符串,忽略大小写
System.out.println("将此 String与其他 String比较,忽略大小写:" + str.equalsIgnoreCase(str4));
// 返回字符数组
byte[] bytes = str.getBytes();
System.out.println("将字符串转为字节数组:" + Arrays.toString(bytes));
// 第一次出现的字符串内的索引
System.out.println("返回指定字符第一次出现的字符串内的索引:" + str.indexOf('1'));
// indexOf(int ch, int fromIndex)
System.out.println("返回指定字符第一次出现的字符串内的索引,以指定索引为4开始搜索:" + str.indexOf('2', 4));
// 判断字符是否为空字符
String str5 = "";
System.out.println("判断字符是否为空字符:" + str5.isEmpty());
// 最后一次出现的字符串中的索引
System.out.println("返回指定字符的最后一次出现的字符串中的索引:" + str2.lastIndexOf('a'));
// lastIndexOf(int ch, int fromIndex) ,fromIndex:指定开始索引
System.out.println("返回指定字符的最后一次出现的字符串中的索引,指定开始索引:" + str2.lastIndexOf('a', 15));
// 获取字符串长度
System.out.println("字符串长度:" + str.length());
// 字符串是否匹配给定,正则表达式要符合全部字符串才为true,否则为false
String str6 = "123456";
String regex = "[0-9]+";
System.out.println("字符串是否匹配给定:" + str6.matches(regex));
// 替换字符串
// replace(char oldChar(旧字符), char newChar(新字符))
String str7 = "你好靓仔!";
String str8 = str7.replace('你', '您');
System.out.println("替换前的字符串:" + str7 + "替换后的字符串:" + str8);
// 字符序列替换,即多字符替换
// replace(CharSequence target, CharSequence replacement)
str8 = str7.replace("你好靓仔!", "你好靓女!");
System.out.println("替换前的字符串:" + str7 + "替换后的字符串:" + str8);
// 字符串替换
// replaceAll(String regex, String replacement)
regex = "[abc]";
str8 = str.replaceAll(regex, "[abc]");
System.out.println("替换前的字符串:" + str + "替换后的字符串:" + str8);
// 字符串分割
String str9 = "1a2b3c";
String[] split = str9.split(regex);
System.out.println("分割后的字符串数组:" + Arrays.toString(split));
// 字母转大小写
str = str.toUpperCase();
System.out.println("字母转大写:" + str);
System.out.println("字母转小写:" + str.toLowerCase());
// 整数转字符串
int num = 100;
String str10 = String.valueOf(num);
System.out.println("整数转字符串:" + str10);
// substring()返回一个字符串,该字符串是此字符串的子字符串。
String string = "abcdef";
// 包前不包后
System.out.println(string.substring(1)); // bcdef
System.out.println(string.substring(1,4)); // bcd
}
6.3 可变字符串
概念:可在内存中创建可变的缓冲空间,存储频繁改变的字符串。
Java中提供了两个可变字符串类:
- StringBuilder:可变长字符串,JDK5.0提供,运行效率快、线程不安全。
- StringBuffer:可变长字符串,JDK1.0提供,运行效率慢、线程安全。
- 这两个类中方法和属性完全一致。
- StringJoiner:可变长字符串,JDK1.8提供,于拼接字符串的实用工具类
常用方法:
方法名 | 属性 |
---|---|
public StringBuilder append(String str) | 追加内容。 |
public StringBuilder insert(int dstOffset, CharSequence s) | 将指定 字符串插入此序列中。 |
public StringBuilder delete(int start, int end) | 包前不包后,移除此序列的子字符串中的字符。 |
public StringBuilder replace(int start, int end, String str) | 包前不包后,使用给定字符串替换此序列的子字符串中的字符。start开始位置、end结束位置。 |
public int length() | 返回长度(字符数)。 |
StringJoiner
是 Java 中用于拼接字符串的实用工具类。它可以将多个字符串连接成一个字符串,中间用指定的分隔符分隔。
以下是 StringJoiner
的基本用法:
import java.util.StringJoiner;
public class Main {
public static void main(String[] args) {
StringJoiner joiner = new StringJoiner(", ");
joiner.add("Apple");
joiner.add("Banana");
joiner.add("Orange");
String result = joiner.toString();
System.out.println(result); // 输出:Apple, Banana, Orange
}
}
在上述示例中,我们创建了一个 StringJoiner
对象,通过 add
方法添加了三个字符串。然后,通过 toString
方法获取最终拼接好的字符串。StringJoiner
默认在元素之间使用逗号作为分隔符,但您可以通过构造函数或 setDelimiter
方法来指定其他分隔符。
您还可以指定前缀和后缀,将它们添加到拼接的结果字符串的开头和结尾:
StringJoiner joiner = new StringJoiner(", ", "[", "]");
joiner.add("Apple");
joiner.add("Banana");
joiner.add("Orange");
String result = joiner.toString();
System.out.println(result); // 输出:[Apple, Banana, Orange]
StringJoiner
还支持在空值处理上的一些选项。您可以使用 setEmptyValue
方法设置当没有元素时返回的默认值:
StringJoiner joiner = new StringJoiner(", ");
// No elements added
joiner.setEmptyValue("No fruits available");
String result = joiner.toString();
System.out.println(result); // 输出:No fruits available
StringJoiner
提供了一种简单而方便的方法来拼接字符串,尤其适用于将集合中的元素连接成一个字符串,同时指定分隔符和前缀后缀。
6.4 BigDeicmal基本用法
- 位置:java.math包中。
- 作用:精确计算浮点数。
- 创建方式:BigDecimal bd=new BigDecimal(“1.0”)。
常用方法:(BigInteger也是一样)
方法名 | 描述 |
---|---|
BigDecimal add(BigDecimal bd) | 加 |
BigDecimal subtract(BigDecimal bd) | 减 |
BigDecimal multiply(BigDecimal bd) | 乘 |
BigDecimal divide(BigDecimal bd) | 除 |
public static void main(String[] args) {
double num1 = 0.1;
double num2 = 0.2;
System.out.println(num1 + num2); // 0.30000000000000004
// 精确计算浮点数
BigDecimal bigDecimal1 = new BigDecimal("0.1");
BigDecimal bigDecima2 = new BigDecimal("0.2");
BigDecimal bigDecimal = bigDecimal1.add(bigDecima2);
System.out.println(bigDecimal); // 0.3
// 大Integer类型
int num3 = 2147483647;
int num4 = 1;
System.out.println(num3 + num4); // -2147483648
BigInteger bigInteger1 = new BigInteger(num3 + "");
BigInteger bigInteger2 = new BigInteger(num4 + "");
BigInteger bigInteger = bigInteger1.add(bigInteger2);
System.out.println(bigInteger); // 2147483648
}
6.5 Date
- Date表示特定的瞬间,精确到毫秒。
- Date类中的大部分方法都已经被Calendar类中的方法所取代。
- 时间单位
- 1秒=1000毫秒
- 1毫秒=1000微秒
- 1微秒=1000纳秒
方法 | 说明 |
---|---|
setTime(long date) | 使用给定的毫秒时间值设置现有的 Date对象。 |
Date(long date) | 构造方法 |
getTime() | 获取毫秒时间值 |
Date date = new Date();
// 获取系统时间
System.out.println(date); // Tue Aug 08 20:20:55 CST 2023
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");
System.out.println(sdf.format(date));
6.6 SimpleDateFormat
- SimpleDateFormat是以与语言环境有关的方式来格式化和解析日期的类。
- 进行格式化(日期 -> 文本)、解析(文本 -> 日期)。
常用时间模式字母:
字母 | 日期或时间 | 示例 |
---|---|---|
y | 年 | 2019 |
M | 年中月份 | 08 |
d | 月中天数 | 10 |
H | 1天中小时数(0-23) | 22 |
m | 分钟 | 16 |
s | 秒 | 59 |
S | 毫秒 | 367 |
Date date = new Date();
// 获取系统时间
System.out.println(date); // Tue Aug 08 20:20:55 CST 2023
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");
System.out.println(sdf.format(date));
// 多种格式
static SimpleDateFormat[] sdf = new SimpleDateFormat[5];
static {
sdf[0] = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");
sdf[1] = new SimpleDateFormat("HH-mm-ss yyyy-MM-dd");
sdf[2] = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
sdf[3] = new SimpleDateFormat("yyyy/MM/dd HH-mm-ss");
sdf[4] = new SimpleDateFormat("HH:mm:ss yyyy/MM/dd ");
}
public static void main(String[] args) {
Date date = new Date();
// 获取系统时间
System.out.println(date); // Tue Aug 08 20:20:55 CST 2023
System.out.println(sdf[4].format(date));
}
6.7 Calendar
- Calendar提供了获取或设置各种日历字段的方法。
- protected Calendar() 构造方法为protected修饰,无法直接创建该对象。
常用方法:
方法名 | 说明 |
---|---|
static Calendar getInstance() | 使用默认时区和区域获取日历 |
void set(int year,int month,int date,int hourofday,int minute,int second) | 设置日历的年、月、日、时、分、秒。 |
int get(int field) | 返回给定日历字段的值。字段比如年、月、日等 |
void setTime(Date date) | 用给定的Date设置此日历的时间。Date-Calendar |
Date getTime() | 返回一个Date表示此日历的时间。Calendar-Date |
void add(int field,int amount) | 按照日历的规则,给指定字段添加或减少时间量 |
long getTimeInMillis() | 毫秒为单位返回该日历的时间值 |
Calendar dates = Calendar.getInstance();
System.out.println(dates);
6.8 内部类
概念:在一个类的内部再定义一个完整的类。
特点:
- 编译之后可生成独立的字节码文件。
- 内部类可直接访问外部类的私有成员,而不破坏封装。
- 可为外部类提供必要的内部功能组件。
6.7.1 实例(成员)内部类
6.7.2 局部内部类
6.7.3 静态内部类
6.7.4 匿名内部类
没有名字的局部内部类,必须继承一个父类或实现一个接口,是一个临时的、一次性的内部类。可降低代码的冗余。
public abstract class Human {
// 抽象方法
public abstract void method();
}
// 定义个内部接口
interface T{
void method();
}
// 匿名内部类
Human human1 = new Human() {
@Override
public void method() {
System.out.println("匿名内部类方法1");
}
};
human1.method();
T t = new T() {
@Override
public void method() {
System.out.println("匿名内部类方法2");
}
};
七、内存分析(JVM)
基本概念:
JVM 是可运行 Java 代码的假想计算机 ,包括**一套字节码指令集、一组寄存器、一个栈、一个垃圾回收、堆 和 一个存储方法域。**JVM 是运行在操作系统之上的,它与硬件没有直接的交互。
main方法是Java程序与JVM交互的关键点。它标志着Java程序的入口,是JVM开始执行Java代码的起点,同时也是JVM结束程序执行的终点。
运行时数据区(Runtime Data Area):
- Java虚拟机在内存中划分出不同的数据区域,用于存放不同类型的数据。
- 主要包括:方法区、堆、栈、程序计数器和本地方法栈。
- 方法区存储类信息、常量、静态变量等,堆存储对象实例。
- 栈用于保存线程的方法调用和局部变量,程序计数器保存当前线程执行的字节码指令地址,本地方法栈保存本地方法调用。
7.1 方法区
方法区(Method Area)是Java虚拟机的一部分,**用于存储已被加载的类信息、常量、静态变量(1.7前)、即时编译器编译后的代码等数据。**它是Java虚拟机内存区域的一个重要组成部分,与堆、栈、程序计数器、本地方法栈等内存区域一起构成了Java虚拟机的整体内存模型。
主要特点和作用:
- 存储类信息:方法区用于存储已被加载的类的信息,包括类的结构(如字段、方法、构造器)、父类、实现的接口等。
- 存储常量池:方法区包含常量池(Constant Pool),用于存放编译期生成的各种字面量和符号引用。这些内容在运行时被JVM解析和使用。
- 存储静态变量:静态变量即被
static
关键字修饰的类级别的变量,它们存储在方法区中。 - 即时编译器编译后的代码:JVM在运行时可能会将字节码编译为本地机器代码,这些编译后的代码也会被存储在方法区中。
7.2 堆
堆(Heap)是一块用于存储对象实例和数组的运行时数据区域。它是Java程序中动态分配内存的主要区域,也是垃圾回收的重点管理区域。
java 1.7后出现了存储静态的属性和方法的一块内存空间,即静态区。
7.3 栈
栈是描述java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程
栈的特点包括:
- 后进先出(LIFO):栈是后进先出的数据结构,最后进栈的方法调用会首先被处理和执行,直到最先进栈的方法。
- 线程独立性:每个线程都有自己的栈,用于保存其方法调用和局部变量,这样不同线程之间的方法调用和局部变量不会相互干扰。
- 栈大小固定:栈的大小在JVM启动时就被固定了,无法动态改变。如果栈空间不足,可能会抛出
StackOverflowError
。 - 方法调用和返回:方法调用时,JVM会在栈中创建一个新的栈帧用于保存方法的执行上下文,方法返回时,该栈帧会被弹出。
- 局部变量:局部变量(例如方法的参数、局部变量等)存储在栈帧的局部变量表中。
栈帧( Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接(Dynamic Linking)、 方法返回值和异常分派( Dispatch Exception)。栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束。
八、IO流
概念
- 内存与存储设备之间传输数据的通道。
- 水借助管道传输;数据借助流传输。
按方向的流的分类 |
---|
流的分类
1. 按方向【重点
】
- 输入流:将<存储设备>中的内容读入到<内存>中。
- 输出流:将<内存>中的内容写入到<存储设备>中。
2. 按单位
字节流:以字节为单位,可以读写所有数据 。
字符流:以字符为单位,只能读写文本数据 。
3. 按功能
- 节点流:具有实际传输数据的读写功能。
- 过滤流:在节点流的基础之上增强功能。
8.1 File
概念:代表物理盘符中的一个文件或者文件夹。
构造方法 :
File(File parent, String child)
从父抽象路径名和子路径名字符串创建新的 File实例。
File(String pathname)
通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
File(String parent, String child)
从父路径名字符串和子路径名字符串创建新的 File实例。
常见方法:
方法名 | 描述 |
---|---|
createNewFile() | 创建一个新文件。 |
mkdir() | 创建一个新目录。 |
delete() | 删除文件或空目录。 |
exists() | 判断File对象所对象所代表的对象是否存在。 |
getAbsolutePath() | 获取文件的绝对路径。 |
toPath() | 获取文件的相对路径。 |
getName() | 取得名字。 |
getParent() | 获取文件/目录所在的目录。 |
isDirectory() | 是否是目录。 |
isFile() | 是否是文件。 |
length() | 获得文件的长度。 |
listFiles() | 列出目录中的所有内容。 |
list() | 返回一个字符串数组,命名由此抽象路径名表示的目录中的文件和目录。 |
renameTo() | 修改文件名为。 |
isHidden() | 测试此抽象路径名命名的文件是否为隐藏文件。 |
lastModified() | 此抽象路径名表示的文件上次修改的时间。 |
File file = new File("file/file.txt");
// 创建文件
file.createNewFile();
File file2 = new File("file/data");
// 创建目录
if (!file2.exists()) {
file2.mkdir();
}
// 删除文件或空目录
if (file2.exists()) {
file2.delete();
}
System.out.println("绝对路径:" + file.getAbsolutePath());
System.out.println("相对路径:" + file.getPath());
// 获取文件信息
System.out.println("文件名:" + file.getName());
long l = file.lastModified();
Date date = new Date(l);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String format = sdf.format(date);
System.out.println("上次修改的时间:" + format);
System.out.println("是否是目录:" + file.isDirectory());
System.out.println("是否是文件:" + file2.isFile());
System.out.println("获得文件的长度:" + file.length());
// 修改文件名
file.renameTo(new File("file/files.txt"));
System.out.println("文件是否为隐藏文件:" + file.isHidden());
// 列出目录中的所有内容
File file1 = new File("file");
File[] files = file1.listFiles();
for (File file3 : files) {
System.out.println(file3);
}
// 返回一个字符串数组,命名由此抽象路径名表示的目录中的文件和目录
String[] list = file1.list();
for (String s : list) {
System.out.println(s);
}
8.2 字节流
8.2.1 字节输入流
FileInputStream类方法:
方法 | 说明 |
---|---|
read() | 从该输入流读取一个字节的数据。 |
read(byte[] b) | 从该输入流读取最多 b.length个字节的数据为字节数组。 |
read(byte[] b, int off, int len) | 从该输入流读取最多 len字节的数据为字节数组。 |
// 创建文件对象
File file = new File("file/身份证信息.txt");
// 创建输入流对象
FileInputStream fs = new FileInputStream(file);
int count;
byte[] bytes = new byte[1024];
while ((count = fs.read(bytes)) != -1){
System.out.println(new String(bytes, 0, count));
}
fs.close();
8.2.2 字节输出流
FileOutputStream类方法:
方法 | 说明 |
---|---|
write(byte[] b) | 将 b.length个字节从指定的字节数组写入此文件输出流。 |
write(byte[] b, int off, int len) | 将 len字节从位于偏移量 off的指定字节数组写入此文件输出流。 |
write(int b) | 将指定的字节写入此文件输出流。 |
// 创建输入流对象
FileInputStream fs = new FileInputStream("file\\long-customer-train.csv");
// 创建输出流对象
FileOutputStream os = new FileOutputStream("file/data.csv");
// 一次读取多个字节
byte[] bytes = new byte[1024];
int count;
while ((count = fs.read(bytes))!= -1){
// 写入文件
os.write(bytes, 0, count);
// 读取文件
System.out.println(new String(bytes, 0, count));
}
// 释放流
fs.close();
os.close();
8.2.3 字节缓冲流
缓冲流:BufferedInputStream类
- 提高IO效率,减少访问磁盘的次数,因为它是直接读/写进内存的,字节输入流和字节输出流是直接读/写进磁盘的。
- 数据存储在缓冲区中,flush是将缓存区的内容写入文件中,也可以直接close。
// 创建字节输入缓冲流对象
BufferedInputStream bi = new BufferedInputStream(new FileInputStream("E:\\编程学习\\Java全栈开发\\code\\所有项目\\file\\data.csv"));
byte[] bytes = new byte[1024];
int len;
while ((len = bi.read(bytes)) != -1)
System.out.println(new String(bytes,0,len));
IOUtils.closeAll(bi);
BufferedOutputStream类方法:
- flush() :刷新缓冲输出流,立即将数据写入文件 。
- write(byte[] b, int off, int len) :从指定的字节数组写入 len个字节,从偏移 off开始到缓冲的输出流。
// 创建字节输入缓冲流对象
BufferedInputStream bi = new BufferedInputStream(new FileInputStream("src/io框架/缓存流/data/data.csv"));
// 创建字节输出缓冲流对象
BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream("src/io框架/缓存流/data/data.txt"));
byte[] bytes = new byte[1024];
int len;
while ((len = bi.read(bytes)) != -1)
bo.write(bytes, 0, len);
bo.flush(); // 立即将数据写入文件
IOUtils.closeAll(bi);
8.2.4 对象流
对象流:ObjectOutputStream/ObjectInputStream
- 增强了缓冲区功能。
- 增强了读写8种基本数据类型、字符串功能。
- 增强了读写对象的功能:
- readObject() 从流中读取一个对象。
- writeObject(Object obj) 向流中写入一个对象。
对象序列化的细节:
- 必须实现Serializable接口。
- 必须保证其所有属性均可序列化。
- transient:修饰的实例变量不会被序列化机制序列化,用于防止敏感信息在序列化时被暴露。
- 所有类型读取到文件尾部的标志:都会报java.io.EOFException错误。
Student类:
/**
* @author Novice programmer
* @projectName 序列化类
* @date 2023/8/17 9:30
*/
public class Student implements Serializable {
private int id;
private String name;
// private int age;报InvalidClassException错误
// private final int age = 1;
// private final Long serialVersionUID = 4191530469705363453L;
// transient:修饰的属性不会被序列化机制序列化,用于防止敏感信息在序列化时被暴露。
private transient int age;
public Student() {
}
public Student(int id, String name) {
this.id = id;
this.name = name;
}
/**
* 获取
* @return id
*/
public int getId() {
return id;
}
/**
* 设置
* @param id
*/
public void setId(int id) {
this.id = id;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
public String toString() {
return "Student{id = " + id + ", name = " + name + "}";
}
// private int age;
}
对象输入流:
/**
* 对象输入流
* @throws IOException
*/
@Test
public void method() throws IOException, ClassNotFoundException {
// 创建对象输入流
ObjectInputStream oi = new ObjectInputStream(new FileInputStream("E:\\编程学习\\Java全栈开发\\code\\所有项目\\file\\student.txt"));
ArrayList<Student> list = (ArrayList<Student>) oi.readObject();
oi.close();
System.out.println(list);
}
/**
* 对象输入流
* @throws IOException
*/
@Test
public void method03() throws IOException, ClassNotFoundException {
// 创建对象输入流
ObjectInputStream oi = new ObjectInputStream(new FileInputStream("E:\\编程学习\\Java全栈开发\\code\\所有项目\\file\\int.txt"));
int i = oi.readInt();
int j = oi.readInt();
int s = oi.readInt();
oi.close();
System.out.println(i);
System.out.println(j);
System.out.println(s);
}
对象输出流:
/**
* 对象输出流
* @throws IOException
*/
@Test
public void method02() throws IOException {
// 创建对象输入流
ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream("E:\\编程学习\\Java全栈开发\\code\\所有项目\\file\\student.txt"));
Student student = new Student(1,"李四");
Student student2 = new Student(2,"张三");
Student student3 = new Student(3,"王五");
ArrayList<Student> students = new ArrayList<>();
students.add(student);
students.add(student2);
students.add(student3);
// 写入对象
oo.writeObject(students);
oo.close();
}
/**
* 对象输出流
* @throws IOException
*/
@Test
public void method04() throws IOException, ClassNotFoundException {
// 创建对象输入流
ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream("E:\\编程学习\\Java全栈开发\\code\\所有项目\\file\\int.txt"));
oo.writeInt(10);
oo.writeInt(100);
oo.writeInt(102);
oo.close();
}
8.2.5 随机访问流
方法 | 说明 |
---|---|
length() | 返回此文件的长度。 |
getFilePointer() | 返回此文件中的当前偏移量。 |
seek(long pos) | 设置文件指针偏移,从该文件的开头测量,发生下一次读取或写入。 |
@Test
public void method01() throws IOException {
// 创建随机访问流
RandomAccessFile rw = new RandomAccessFile("file/file.txt", "rw");
System.out.println("实例化对象后指针的位置:"+rw.getFilePointer());
// 设置偏移量
rw.seek(1);
System.out.println("实例化对象后指针的位置:"+rw.getFilePointer());
//从指定位置开始进行覆盖写入
rw.write('8');
System.out.println("实例化对象后指针的位置:"+rw.getFilePointer());
rw.close();
}
//实现从指定位置开始进行读写到文件末尾
@Test
public void method02() throws IOException {
RandomAccessFile raf = new RandomAccessFile("file/file.txt", "r");
System.out.println("实例化对象后指针的位置:"+raf.getFilePointer());
//设置指针
raf.seek(2);
System.out.println("设置完指针后的位置:"+raf.getFilePointer());
//读取数据到文件末尾
byte[] b = new byte[1024];
int len;
while((len = raf.read(b)) != -1){
System.out.println(new String(b,0,len));
}
System.out.println("读取数据后指针的位置:"+raf.getFilePointer());
raf.close();
}
//中间插入的实现
@Test
public void method03() throws IOException {
//创建一个写的随机访问流
RandomAccessFile rw = new RandomAccessFile("file/file.txt", "rw");
RandomAccessFile raf = new RandomAccessFile("file/file.txt", "r");
//1.设置偏移量
rw.seek(3);
raf.seek(3);
//2.读取指定位置到文件末尾的数据
StringBuffer sb = new StringBuffer("3");
byte[] b = new byte[1024];
int len;
while((len = raf.read(b)) != -1){
//拼接字符串到 StringBuffer 的对象中
sb.append(new String(b,0,len));
}
//3.在指定位置开始进行覆盖写入
rw.write(sb.toString().getBytes());
rw.close();
raf.close();
}
8.2.6 内存流
-
内存流:效率高、操作数据量少,无法关闭的流
-
ByteArrayInputStream:内存输入流 内存 -> 程序
-
ByteArrayOutputStream:内存输出流 程序 -> 内存
public static void main(String[] args) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bos.write("有道无术".getBytes());
bos.close();
//从内存中获取写入的 byte 类型数组的数据
byte[] bytes = bos.toByteArray();
System.out.println(new String(bytes));
//清空流里的数据
bos.reset();
System.out.println(bos.toString());
}
8.2.7 打印流
PrintStream
8.3 字符流
8.3.1 字符输入流
FileReader类方法:
底层是使用FileInputStream(字节输入流)对象
// 创建字符输入流
FileReader rd = new FileReader("file/身份证信息.txt");
// 多个字符读取
char[] chars = new char[1024];
int count;
while ((count = rd.read(chars)) != -1){
//写入
fw.write(chars, 0, count);
}
// 释放流
rd.close();
8.3.2 字符输出流
FileWriter类方法:
底层是使用FileOutputStream(字节输入流)对象
//创建字符输出流
FileWriter fw = new FileWriter("file/身份信息.txt");
// 多个字符读取
char[] chars = new char[1024];
int count;
while ((count = rd.read(chars)) != -1){
//输入
fw.write(chars, 0, count);
}
// 释放流
fw.close();
8.3.3 字符缓冲流
缓冲流:BufferedReader
- 支持输入换行符。
- 可一次写一行、读一行。
BufferedReader
BufferedReader类方法:
- readLine() :读一行文字。
- read(char[] cbuf, int off, int len) :将字符读入数组的一部分。
/**
* 第一方法读文件 65-80ms,效率高
* @throws IOException
*/
@Test
public void getMethod01() throws IOException {
// 创建字符输入缓冲流
BufferedReader br = new BufferedReader(new FileReader("src/io框架/缓存流/data/身份证信息.txt"));
// 第一方法读文件
char[] chs = new char[1024];
int len;
while ((len = br.read(chs)) != -1)
System.out.println(new String(chs,0,len));
}
/**
* 第二方法读文件 176-201ms,效率低
* @throws IOException
*/
@Test
public void getMethod02() throws IOException {
// 创建字符输入缓冲流
BufferedReader br = new BufferedReader(new FileReader("src/io框架/缓存流/data/身份证信息.txt"));
String str;
while ((str = br.readLine()) != null)
System.out.println(str);
}
BufferedWriter
flush():立即将数据写入文件。
newLine() :写一行行分隔符,每写一行,定位到下一行。
write(char[] cbuf, int off, int len) : 写入字符数组的一部分。
write(String s, int off, int len) :写一个字符串的一部分。
/**
* 第一方法写进文件不换行 28-38ms,效率低
* @throws IOException
*/
@Test
public void getMethod03() throws IOException {
// 创建字符输入缓冲流
BufferedReader br = new BufferedReader(new FileReader("src/io框架/缓存流/data/身份证信息.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("src/io框架/缓存流/data/s.txt"));
String str;
while ((str = br.readLine()) != null)
bw.write(str);
}
/**
* 第二方法写进文件换行 23-26ms,效率快
* @throws IOException
*/
@Test
public void getMethod04() throws IOException {
// 创建字符输入缓冲流
BufferedReader br = new BufferedReader(new FileReader("src/io框架/缓存流/data/身份证信息.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("src/io框架/缓存流/data/s.txt"));
char[] chs = new char[1024];
int len;
while ((len = br.read(chs)) != -1)
bw.write(chs, 0, len);
// 写入并换行
bw.newLine();
// 立即将数据写入文件
bw.flush();
}
8.3.4 转换流
InputStreamReader / OutputStreamWriter
- 可将字节流转换为字符流。
- 可设置字符的编码方式。
- 本质是字符流。
InputStreamReader类:
- 用于将字节流输入流转换为字符流输入流,以便读取文件的文本内容。 同时还可以指定字符编码。
// cs是编码格式
InputStreamReader(InputStream in, String charsetName)
// 创建文件对象
File file = new File("file/身份证信息.txt");
// 字符输入流
FileReader rd = new FileReader(file);
// 字节输入流
FileInputStream fs = new FileInputStream(file);
// 转换器
InputStreamReader ir = new InputStreamReader(fs, StandardCharsets.UTF_8);
char[] chs = new char[1024];
int count;
while ((count = ir.read(chs)) != -1)
System.out.println(new String(chs, 0, count));
// 关闭流
IOUtils.closeAll(fs, ir);
OutputStreamWriter类
用于将字节流输出流转换为字符流输出流,以便读取文件的文本内容。 同时还可以指定字符编码。
OutputStreamWriter(OutputStream out, String charsetName)
/**
* 字节流转字符流的输出流
* @throws IOException
*/
@Test
public void method02() throws IOException {
// 创建文件对象
File file = new File("E:\\编程学习\\Java全栈开发\\code\\所有项目\\file\\身份证信息.txt");
FileInputStream fs = new FileInputStream(file);
// 创建字节输入缓冲流对象
FileOutputStream fo = new FileOutputStream(new File("E:\\编程学习\\Java全栈开发\\code\\所有项目\\file\\s.txt"));
// 字节流转字符流的输入流
InputStreamReader ir = new InputStreamReader(fs, StandardCharsets.UTF_8);
// 字节流转字符流的输出流
OutputStreamWriter ow = new OutputStreamWriter(fo, StandardCharsets.UTF_8);
char[] chs = new char[1024];
int len;
while ((len = ir.read(chs)) != -1)
ow.write(chs, 0, len);
ow.flush(); // 立即将数据写入文件
IOUtils.closeAll(fs, fo, ir, ow);
}
8.3.5 打印流
-
打印流:**只有输出没有输入,只操作目的源,不操作数据源。**
-
PrintStream:打印字节流
-
PrintWriter:打印字符流
@Test
public void method01() throws FileNotFoundException {
PrintStream ps = new PrintStream("io4.txt");
//写出字节 97 -- 'a'
//ps.write(97);
//打印 int 类型的 97
ps.print(97);
ps.close();
}
@Test
public void method02() throws FileNotFoundException {
PrintWriter pw = new PrintWriter("io5.txt");
pw.println(98);
pw.close();
}
//改变输出的方向
@Test
public void method03() throws FileNotFoundException {
//目的源是控制台
System.out.println("有道无术");
//改变目的源
System.setOut(new PrintStream("io6.txt"));
System.out.println("术尚可求");
}
//改变输入的方向
public static void main(String[] args) throws FileNotFoundException {
//改变输入流的方向
//System.setIn(new FileInputStream("io6.txt"));
//Scanner sc = new Scanner(System.in);
Scanner sc = new Scanner(new FileInputStream("io6.txt"));
String str = sc.next();
System.out.println("str:"+str);
}
8.4 文件过滤器
8.4.1 FileFilter接口
// 接口方法
boolean accept(File pathname);
指定的抽象路径名是否应包含在路径名列表中。
// 筛选/过滤txt文件且不是隐藏文件
File[] files = file.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return !pathname.isHidden() && pathname.toString().toLowerCase().endsWith(".txt");
}
});
8.4.2 FilenameFilter接口
指定的抽象路径名是否应包含在路径名列表中。
接口方法:
boolean accept(File dir, String name);
// 筛选/过滤txt文件且不是隐藏文件
File[] files = file.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.startsWith("file") && !dir.isHidden();
});
8.5 字符编码
常见字符编码:
编码 | 字节数 | 说明 |
---|---|---|
ISO-8859-1 | 8 | 收录除ASCII外,还包括西欧、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号。 |
UTF-8 | 1-4 | 针对Unicode的可变长度字符编码。 |
UTF-16 | 2 | |
UTF-32 | 4 | |
GB2312 | 2 | 简体中文。 |
GBK | 2 | 简体中文、扩充。 |
BIG5 | 2 | 台湾,繁体中文。 |
8.5.1 解码与编码
编码是将数据从一种表示形式转换为另一种表示形式的过程。
解码是将编码后的数据重新转换为原始数据的过程。
8.5.2 URL编码
/**
* 解码:URLDecoder.decode(String s, String enc)
* 编码:URLDecoder.decode(String s, String enc)
*/
@Test
public void method() throws UnsupportedEncodingException {
String str = "Ahh换行";
// 解码
String utf8 = URLDecoder.decode(str, "gbk");
System.out.println(utf8);
// 编码
String utf81 = URLEncoder.encode(str, "ISO-8859-1");
System.out.println(utf81);
utf8 = URLDecoder.decode(str, "utf8");
System.out.println(URLDecoder.decode(utf8, "ISO-8859-1"));
}
/*
Ahh换行
Ahh%3F%3F
Ahh换行
Ahh换行
*/
8.5.3 Base64编码
Base64 编码是一种将二进制数据转换为可打印字符的编码方式。它使用一组 64 个不同的字符(通常是 A-Z、a-z、0-9 以及一些特殊字符)来表示二进制数据的字节值。 Base64 编码不是加密,而是一种将二进制数据转换为文本形式的方法,使其更适合在网络传输、存储和处理中使用。
Base64 编码的作用有以下几个方面:
- 传输数据:在许多情况下,传输二进制数据(例如图像、文件)可能会受到限制或产生问题。Base64 编码可以将二进制数据转换为文本形式,以便于在文本协议(如HTTP、XML、JSON)中传输。这可以避免特殊字符或控制字符对数据的干扰。
- 处理数据:某些系统或应用程序可能只能处理文本数据,而无法直接处理二进制数据。使用Base64编码,可以将二进制数据转换为文本,然后在这些系统中进行处理。
- URL编码:在URL中传递参数时,可能会涉及特殊字符和编码问题。Base64编码可以避免这些问题,因为它仅包含URL安全的字符。
- 存储数据:在一些场景中,二进制数据可能需要存储在文本文件中。Base64编码可以将二进制数据存储为文本,方便读写和管理。
Base编码表 |
---|
![]() |
|
| 满足3个字节编码 |
|
|
| |
| 不足3个字节的编码 |
|
|
| |
编码的时候首先需要先把字符串转换为字节数组,每 3 个字节(ASCII码是3个字节,每个字节8位)一组进行截取,因为每个字节占有 8 位, 3 个字节正好是 24 位,能被 6 整除(Base64取6位),如果不足 3 个字节需要凑够 3 个,也就是在后面补 0;如果后面字节没有值,则需要=号代替 。
/**
* Base64 编码和解码
* 1.解码:Base64.getEncoder().encodeToString(byte[] src):将字节数组编码为 Base64 字符串。
* 2.编码:URLDecoder.decode(String s, String enc)
*/
@Test
public void method02() throws UnsupportedEncodingException {
String str = "Ahh换行";
byte[] bytes = str.getBytes();
// 解码
String utf8 = Base64.getEncoder().encodeToString(bytes);
System.out.println(utf8); // QWho5o2i6KGM
// 编码
byte[] decode = Base64.getDecoder().decode(utf8);
System.out.println(new String(decode)); // Ahh换行
}
8.5.4 字符编码和解码
String.getBytes(String charsetName)
:将字符串编码为字节数组,使用指定的字符集。new String(byte[] bytes, String charsetName)
:将字节数组解码为字符串,使用指定的字符集。
String original = "Hello, World!";
byte[] bytes = original.getBytes("UTF-8");
String decoded = new String(bytes, "UTF-8");
九、多线程
9.1 基本概念
程序:程序是一组按照特定顺序编写的计算机指令,旨在执行特定的任务或完成特定的工作。
生命周期:执行一次永久保存。
9.2 进程
进程:进程是程序执行的基本单位,一个程序是由一个或多个进程组成;一个进程可以有多个程序(多对多)。
生命周期:进程是一次执行过程。
五种状态:创建、就绪、运行、等待 / 堵塞、终止。
![]()
- 创建:创建一个或多个进程。
- 就绪:可以(准备)运行。
- 运行:占用CPU,拥有CPU使用权。
- 等待:阻塞(资源不满住会出现阻塞),等待资源的满足。一旦等待的事情满足,转为就绪状态。
- 终止:从有到无。
9.3 线程
线程:线程是CPU的基本调度单位,一个进程是由一个或多个线程组成。
线程组成
任何一个线程都具有基本的组成部分:
- CPU时间片:操作系统(OS)会为每个线程分配执行时间。
- 运行数据:
堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象。
栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈。- 线程的逻辑代码。
常见方法:
方法名 | 说明 |
---|---|
public static void sleep(long millis) | 当前线程主动休眠 millis 毫秒。 |
public static void yield() | 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。 |
public final void join() | 允许其他线程加入到当前线程中。 |
public void setPriority(int) | 线程优先级为1-10,默认为5,优先级越高,表示获取CPU机会越多。 |
public void setDaemon(boolean) | 设置为守护线程线程有两类:用户线程(前台线程)、守护线程(后台线程) |
9.4 线程创建
多线程的创建方式
-
1.自定义线程类继承 Thread 类,重写 run(),在方法中添加线程执行的任务,在其他线程中实例化自定义线程类对象,通过对象调用 start() 启动线程
-
2.自定义任务类实现 Runnable 接口,重写 run(),在方法中添加线程执行的任务,在其他线程中先实例化任务类对象,再实例化 Thread 类对象并通过构造方法传递任务类对象,通过 Thread 类对象调用 start() 启动线程。
-
3.自定义类实现 Callable 接口,重写 call(),在方法中添加线程执行的任务,call() 方法拥有返回值,返回值类型由 Callable 的泛型决定,实例化 FutureTask 对象,通过构造方法传递 Callable 实现类的对象,再实例化 Thread 类对象,把 FutureTask 的对象通过构造方法传递,通过 Thread 类对象调用 start() 启动线程。
// 继承,并重写方法
public static void main(String[] args) {
// 创建线程
Thread thread = new Demo01();
// 开始进程
thread.start();
// 执行进程任务,自动调用方法:运行完main方法,再执行进程任务,
// 手动调用方法:按顺序执行,或多执行一次相同任务
// thread.run();
System.out.println("进程结束");
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("运行进程" + i);
}
}
/**
* 创建类
*/
@Test
public void method() {
Runnable myThread = new MyThread();
myThread.run();
for (int i = 0; i < 1000; i++) {
System.out.println("结束进程" + i);
}
}
/**
* 匿名内部类
*/
@Test
public void method02() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("结束进程" + i);
}
}
});
thread.run();
for (int i = 0; i < 1000; i++) {
System.out.println("结束进程" + i);
}
}
9.5 同步锁
synchronized 是 Java 中的关键字,用于实现多线程之间的同步。它可以应用于方法或代码块,以确保在**同一时间只有一个线程可以访问被同步的代码段。**这有助于避免多线程并发访问时可能出现的数据竞争和不一致性问题。
同步规则:
只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记。
如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用。
JDK中线程安全的类:
- StringBuffer
- Vector
- Stack
- Hashtable
以上类中的公开方法,均为synchonized修饰的同步方法
9.5.1 同步方法
表示整个方法是同步的,即同一时间只能有一个线程执行该方法。
语法:
synchronized 返回值类型 方法名称(形参列表){ //对当前对象(this)加锁
// 代码(原子操作)
}
9.5.2 同步代码块
指定了一个对象(通常是一个对象的引用),只有拥有该对象的锁的线程可以执行同步代码块。
public void someMethod() {
synchronized (临界资源) {
// 同步的代码段
}
}
9.5.3 同步静态方法
确保在同一时间只有一个线程可以访问该静态方法。
public static synchronized void synchronizedStaticMethod() {
// 同步的代码段
}
一个典型的死锁情况可能如下:
- 线程A锁定资源1,等待资源2。
- 线程B锁定资源2,等待资源1。
9.5.4 什么是锁
在计算机科学中,锁(Lock)是一种同步机制,用于控制多线程或多进程之间对共享资源的访问。锁用于确保在某一时刻只有一个线程或进程能够访问共享资源,从而避免并发访问时可能出现的数据竞争和不一致性问题。
锁可以分为两种主要类型:隐式锁和显式锁。
- 隐式锁: 隐式锁是通过编程语言提供的关键字来实现的,最常见的是 Java 中的
synchronized
关键字。当一个线程进入一个被synchronized
标记的代码块或方法时,它会锁定相应的对象,阻止其他线程进入这段代码块或方法,直到该线程释放锁。隐式锁是自动管理的,编程人员不需要显式地获取和释放锁。 - 显式锁: 显式锁是通过编程接口(例如 Java 中的
Lock
接口)来实现的。显式锁需要手动获取和释放,允许更细粒度的控制和灵活性。显式锁通常提供了更多的功能,如可重入性、公平性、超时等待以及多条件等待。
锁的主要目的是确保在并发环境中对共享资源的安全访问,防止多个线程或进程同时修改或访问同一资源,从而避免出现竞态条件、数据不一致和死锁等问题。然而,使用锁可能会引起性能问题,因此在设计多线程或多进程应用程序时需要权衡并发性和性能。
9.6 线程安全集合
9.7 线程通信
线程通信方法:
方法 | 说明 |
---|---|
public final void wait() | 释放锁,进入等待队列 |
public final void wait(long timeout) | 在超过指定的时间前,释放锁,进入等待队列 |
public final void notify() | 随机唤醒、通知一个线程 |
public final void notifyAll() | 唤醒、通知所有线程 |
-
wait():会使线程进入阻塞状态。
-
notify():会唤醒由 wait() 进入阻塞状态的线程,唤醒第一个进入阻塞状态的线程。
-
notifyAll():会唤醒所有由 wait() 进入阻塞状态的线程,唤醒会按照进入阻塞状态的逆序进行,**即最后堵塞的线程,先唤醒。**
wait() 与 sleep()区别
1.来自不同的类:wait() 来自 Object 类;sleep() 来自 Thread 类的静态方法
2.使用位置不同:wait()只能在synchronized的同步区域内使用,必须通过同步监视器调用;sleep()任意位置都可以使用。注意:同步区域不包括synchronized修饰的静态方法,代码块和普通方法可以
3.是否是否资源:wait()会释放资源;sleep()不会释放资源。
9.8 Lock接口
概念:Lock 接口是 Java 中用于实现显式锁的一种机制。与隐式锁(通过synchronized关键字实现)不同,显式锁提供了更多的灵活性和功能,可以更精细地控制多线程之间的并发访问。
特点:
可重入性:可重入性是指一个线程可以多次获取同一个锁而不会发生死锁。
公平性:公平性是指锁按照获取锁的顺序来分配,即等待时间最长的线程将获得锁。
超时等待:在显式锁中,您可以指定一个时间限制,在这个时间内,如果无法获取到锁,则线程可以继续执行其他操作。
多条件等待:与 wait() 和 notify() 相比,显式锁提供了更灵活的条件(即多条件)等待机制。
常用方法:
方法名 | 描述 |
---|---|
void lock() | 获取锁,如锁被占用,则等待。 |
boolean tryLock() | 尝试获取锁(成功返回true。失败返回false,不阻塞)。 |
tryLock(long time, TimeUnit unit) | 指定时间内尝试获取锁(成功返回true。失败返回false,不阻塞)。 |
void unlock() | 释放锁。 |
newCondition() | 创建一个与锁关联的条件,用于支持等待和通知机制。 |