Java基础知识
Java基础知识
🤔 面向对象期末考试前三天速成Java大师
变量
- 局部变量 方法 语句块中
- 成员变量 类中 方法外
- 类变量/静态变量
- 类中 方法外
- static
- 与类相关 不与实例相关
修饰符
在Java中,修饰符是用来修饰类、方法、变量以及其他数据类型的关键字。它们提供了额外的信息,用于控制访问级别、继承性、特定行为等。以下是一些常见的Java修饰符:
访问修饰符(Access Modifiers):
public
: 公共的,可以被任何类访问。protected
: 受保护的,可以被同一包内的类及其子类访问。default
(包级别): 如果没有指定修饰符,默认为包级别,可以被同一包内的类访问。private
: 私有的,只能在声明它的类内部访问。
非访问修饰符:
final
: 表示最终的,不可更改的。用于修饰类、方法、变量。abstract
: 用于声明抽象类和抽象方法。static
: 表示静态的,属于类而不是实例。用于方法、变量、代码块。transient
: 用于标记不希望序列化的字段。volatile
: 用于多线程编程,确保变量的可见性。
其他修饰符:
synchronized
: 用于多线程同步,修饰方法或代码块。native
: 表示一个方法用其他编程语言(如C、C++)实现,通常与JNI
(Java Native Interface)一起使用。strictfp
: 用于确保浮点运算在不同平台上产生相同的结果。default
(接口中): 用于指定接口中的默认方法实现。
注解修饰符:
@Override
: 表示该方法覆盖了父类的方法。@Deprecated
: 表示该元素(类、方法等)已过时,不推荐使用。@SuppressWarnings
: 抑制编译器警告。- 其他自定义注解。
这些修饰符可以根据需要进行组合使用,以满足特定的编程需求。例如,一个方法可以同时使用public
、static
和final
修饰符。
- 一个源文件中只能有一个 public 类
- 一个源文件可以有多个非 public 类
- 源文件的名称应该和 public 类的类名保持一致。例如:源文件中 public 类的类名是 Employee,那么源文件应该命名为Employee.java。
常见类
character类
Java中的Character
类是一个包装类,用于表示字符类型数据(Unicode字符)。它提供了许多用于处理字符的方法和常量。
以下是一些Character
类的常用方法和常量:
静态方法:
isDigit(char ch)
:检查字符是否为数字字符。isLetter(char ch)
:检查字符是否为字母字符。isLetterOrDigit(char ch)
:检查字符是否为字母或数字字符。isUpperCase(char ch)
:检查字符是否为大写字母。isLowerCase(char ch)
:检查字符是否为小写字母。toUpperCase(char ch)
:将字符转换为大写。toLowerCase(char ch)
:将字符转换为小写。
常量:
MIN_VALUE
:char
类型的最小值,即\u0000
。MAX_VALUE
:char
类型的最大值,即\uffff
。MIN_RADIX
:进制的最小值,即 2。MAX_RADIX
:进制的最大值,即 36。SIZE
:char
类型的位数,通常为 16。
下面是一个示例代码,演示了如何使用Character
类的一些方法:
1 |
|
string类
在Java中,String
类是一个非常常用的类,用于表示和操作字符串。它是不可变(immutable)的,意味着一旦创建,就不能更改其内容。String
类提供了许多方法来处理字符串,如下所示:
创建字符串:
- 使用双引号:
String str = "Hello, World!";
- 使用
new
关键字:String str = new String("Hello, World!");
- 使用双引号:
字符串长度:
int length()
: 返回字符串的长度。
字符串连接:
String concat(String str)
: 将指定的字符串连接到原始字符串的末尾。- 使用加号(+)运算符:
String result = str1 + str2;
字符串提取:
char charAt(int index)
: 返回指定索引位置的字符。String substring(int beginIndex)
: 返回从指定索引开始到字符串末尾的子字符串。String substring(int beginIndex, int endIndex)
: 返回从指定的开始索引到结束索引之间的子字符串(不包括结束索引)。
字符串查找:
int indexOf(String str)
: 返回指定字符串在原始字符串中第一次出现的索引。int lastIndexOf(String str)
: 返回指定字符串在原始字符串中最后一次出现的索引。boolean contains(CharSequence sequence)
: 检查原始字符串是否包含指定的字符序列。
字符串替换:
String replace(char oldChar, char newChar)
: 将原始字符串中的所有旧字符替换为新字符。String replace(CharSequence target, CharSequence replacement)
: 将原始字符串中的所有目标字符序列替换为指定的替换字符序列。
字符串拆分:
String[] split(String regex)
: 使用给定的正则表达式将字符串拆分为子字符串数组。
字符串转换:
char[] toCharArray()
: 将字符串转换为字符数组。byte[] getBytes()
: 将字符串转换为字节数组。int parseInt(String str)
: 将字符串解析为整数。
字符串比较:
boolean equals(Object obj)
: 检查字符串是否与指定对象相等。boolean equalsIgnoreCase(String anotherString)
: 检查字符串是否与指定字符串相等,忽略大小写。int compareTo(String anotherString)
: 按字典顺序比较两个字符串。
1 |
|
stringBuffer
在Java中,StringBuffer
类是一个可变的字符串类,用于处理可变字符串。与String
类不同,StringBuffer
类的内容可以修改。StringBuffer
类提供了许多方法来对字符串进行修改和操作。
以下是StringBuffer
类的一些常用方法:
创建
StringBuffer
对象:StringBuffer sb = new StringBuffer();
:创建一个空的StringBuffer
对象。StringBuffer sb = new StringBuffer("Hello");
:使用指定的字符串创建一个StringBuffer
对象。
追加和插入操作:
StringBuffer append(String str)
:在当前字符串的末尾追加指定的字符串。StringBuffer insert(int offset, String str)
:在指定的偏移量位置插入指定的字符串。
删除操作:
StringBuffer delete(int start, int end)
:删除指定索引范围内的字符。StringBuffer deleteCharAt(int index)
:删除指定索引位置的字符。
修改操作:
void setCharAt(int index, char ch)
:将指定索引位置的字符设置为给定字符。void replace(int start, int end, String str)
:将指定索引范围内的字符替换为给定字符串。
反转字符串:
StringBuffer reverse()
:反转当前字符串。
获取字符串长度:
int length()
:返回当前字符串的长度。
转换为字符串:
String toString()
:将当前字符串缓冲区转换为String
对象。
下面是一个示例代码,演示了如何使用StringBuffer
类的一些方法:
1 |
|
上述代码中,我们首先创建了一个StringBuffer
对象sb
,并使用其方法进行追加、插入、删除、修改和反转操作。最后,我们获取了字符串的长度并输出结果。
需要注意的是,StringBuffer
类是线程安全的,适用于多线程环境。如果在单线程环境下使用,建议使用效率更高但线程不安全的StringBuilder
类。
Java面向对象
继承
1 |
|
1 |
|
Java 不支持多继承,但支持多重继承。
子类拥有父类非 private 的属性、方法。
extends
1 |
|
- implements
使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。
1 |
|
- super 和 this
- super关键字:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。
- this关键字:指向自己的引用。
1 |
|
final
- 使用 final 关键字声明类,就是把类定义定义为最终类,不能被继承
- 或者用于修饰方法,该方法不能被子类重写
构造器
- 子类是不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式)。
- 如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。
- 如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。
重写
在Java中,方法的重写是指子类定义了一个与其父类中方法签名相同(方法名、参数列表、返回类型相同)的方法。重写(Override)发生在继承关系中,子类通过重写父类的方法来提供自己的实现。这样做的主要目的是为了在子类中修改或扩展父类的行为。
以下是方法重写的基本规则:
方法签名: 重写的方法与父类方法具有相同的方法签名,包括方法名、参数列表和返回类型。
访问修饰符: 重写的方法的访问修饰符不能比父类中被重写的方法的访问修饰符更严格。例如,如果父类中的方法是
public
,那么子类中的重写方法也必须是public
。返回类型: 重写的方法的返回类型必须与被重写方法的返回类型相同或是其子类。
抛出异常: 如果被重写的方法在父类中声明了异常,那么子类中重写的方法的声明异常不能超出父类方法声明的异常。子类可以不抛出异常或者只抛出父类方法声明的异常的子类。
下面是一个简单的例子:
1 |
|
在这个例子中,Dog
类继承了 Animal
类,并重写了 makeSound
方法。在 Main
类中,创建了一个 Dog
对象,并通过 Animal
类型的引用调用了 makeSound
方法。这样的调用会执行 Dog
类中重写的方法,而不是 Animal
类中的原始方法。这就是多态的一种体现。
重载
方法的重载(Overloading)是指在一个类中可以定义多个方法,这些方法具有相同的名字但具有不同的参数列表。在方法重载中,方法名相同,但参数类型、参数个数或者参数顺序不同。
重载的目的是提高代码的灵活性和可读性,使得开发者可以用一致的方式来命名不同版本的同一种操作。
以下是方法重载的基本规则:
- 方法名必须相同。
- 参数列表必须不同,包括参数类型、参数个数或者参数顺序。
- 返回类型可以相同也可以不同。
- 可以有不同的访问修饰符。
- 重载方法可以声明新的或更广泛的检查异常。
下面是一个简单的例子:
1 |
|
在这个例子中,Calculator
类定义了多个名为 add
的方法,它们的参数列表分别为两个整数、三个整数和两个浮点数。这就是方法的重载。同样,还有一个 concatenate
方法,用于字符串连接,也是方法重载的一种形式。
调用这些方法时,编译器会根据实际参数的数量和类型来确定调用哪个版本的方法。例如:
1 |
|
多态
多态是同一个行为具有多个不同表现形式或形态的能力。
多态就是同一个接口,使用不同的实例而执行不同操作
必要条件
- 继承
- 重写
- 父类引用指向子类对象:Parent p = new Child();
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。
多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。
抽象类
在Java中,抽象类(Abstract Class)是一种特殊的类,它不能被实例化,用于提供其他类的共同抽象和部分实现,所以抽象类必须被继承,才能被使用。抽象类可以包含抽象方法和非抽象方法。
关键点和特征:
关键字
abstract
: 抽象类使用关键字abstract
声明。在抽象类中可以包含抽象方法和非抽象方法。抽象方法是没有具体实现的方法,需要在具体的子类中被实现。1
2
3
4
5
6abstract class Animal {
abstract void makeSound(); // 抽象方法
void sleep() {
System.out.println("Animal sleeps"); // 非抽象方法
}
}不能实例化: 由于抽象类包含抽象方法,不能被直接实例化。可以通过继承抽象类并提供抽象方法的实现来创建具体的子类。
继承和实现: 子类继承自抽象类,可以选择性地实现抽象方法。如果子类是非抽象类,它必须提供所有抽象方法的具体实现;如果子类也是抽象类,可以选择性地实现抽象方法,或者继续将它标记为抽象。
1
2
3
4
5class Dog extends Animal {
void makeSound() {
System.out.println("Dog barks");
}
}可以有构造方法: 抽象类可以有构造方法,用于初始化抽象类的成员变量或执行其他初始化操作。子类在实例化时,会先调用父类的构造方法。
1
2
3
4
5
6
7
8
9abstract class Animal {
int age;
Animal(int age) {
this.age = age;
}
abstract void makeSound();
}可以包含成员变量和非抽象方法: 除了抽象方法外,抽象类可以包含成员变量、非抽象方法、静态方法等。
1
2
3
4
5
6
7
8
9
10
11
12
13abstract class Shape {
int sides;
Shape(int sides) {
this.sides = sides;
}
void displayInfo() {
System.out.println("This is a shape with " + sides + " sides.");
}
abstract double calculateArea(); // 抽象方法
}
抽象类用于建模一些通用的特征和行为,并要求具体的子类提供实际的实现。在继承层次结构中,抽象类为多态性提供了基础。
- \1. 抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
- \2. 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
- \3. 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
- \4. 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
- \5. 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。
封装
- 修改属性的可见性来限制对属性的访问(一般限制为private)
- 对每个值属性提供对外的公共方法访问,也就是创建一对赋取值方法,用于对私有属性的访问
1 |
|
接口
在Java中,接口(Interface)是一种抽象类型,用于定义一组抽象方法的集合,而不包含具体的实现。接口提供了一种将类与类之间以及类与接口之间进行关联的机制,支持多继承和规范化的设计。
以下是Java接口的主要特点和用法:
定义接口: 使用
interface
关键字来声明接口。接口中的方法默认是抽象的,不包含方法体。1
2
3interface MyInterface {
void myMethod(); // 抽象方法
}实现接口: 通过
implements
关键字,一个类可以实现一个或多个接口。实现接口的类必须提供接口中所有抽象方法的具体实现。1
2
3
4
5
6class MyClass implements MyInterface {
@Override
public void myMethod() {
System.out.println("Implementing MyInterface");
}
}多继承: 一个类可以实现多个接口,从而达到多继承的效果。这是Java中实现多继承的一种方式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19interface InterfaceA {
void methodA();
}
interface InterfaceB {
void methodB();
}
class MyClass implements InterfaceA, InterfaceB {
@Override
public void methodA() {
System.out.println("Implementing InterfaceA");
}
@Override
public void methodB() {
System.out.println("Implementing InterfaceB");
}
}接口的默认方法和静态方法: Java 8 引入了接口的默认方法和静态方法,使得接口可以包含具体的方法实现。
1
2
3
4
5
6
7
8
9
10
11interface MyInterface {
void myMethod(); // 抽象方法
default void defaultMethod() {
System.out.println("Default method in interface");
}
static void staticMethod() {
System.out.println("Static method in interface");
}
}在实现类中,可以选择性地重写抽象方法,并可以直接使用默认方法和静态方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14class MyClass implements MyInterface {
@Override
public void myMethod() {
System.out.println("Implementing MyInterface");
}
// 不重写 defaultMethod,使用默认实现
public static void main(String[] args) {
MyClass myObject = new MyClass();
myObject.myMethod();
myObject.defaultMethod();
MyInterface.staticMethod();
}接口的继承: 一个接口可以继承另一个接口,通过
extends
关键字。1
2
3
4
5
6
7interface InterfaceA {
void methodA();
}
interface InterfaceB extends InterfaceA {
void methodB();
}
枚举
在Java中,枚举(Enum)是一种特殊的数据类型,用于定义包含固定常量值的有限集合。枚举类型在Java中是一种引用数据类型,它可以包含字段、方法和构造方法。
以下是Java中枚举的基本用法和特点:
定义枚举: 使用
enum
关键字来定义枚举类型。枚举中的每个值都是枚举类型的一个实例。1
2
3enum Day {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
}在上面的例子中,
Day
枚举包含七个实例,分别代表星期的每一天。枚举常量: 枚举的每个值被称为枚举常量。在上面的例子中,
SUNDAY
、MONDAY
等就是枚举常量。访问枚举常量: 枚举常量可以通过枚举类型的名称访问。
1
Day today = Day.MONDAY;
枚举可以有字段、方法和构造方法: 与普通类一样,枚举可以包含字段、方法和构造方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14enum Day {
SUNDAY("Sun"), MONDAY("Mon"), TUESDAY("Tue"), WEDNESDAY("Wed"),
THURSDAY("Thu"), FRIDAY("Fri"), SATURDAY("Sat");
private final String abbreviation;
Day(String abbreviation) {
this.abbreviation = abbreviation;
}
public String getAbbreviation() {
return abbreviation;
}
}在这个例子中,
Day
枚举包含了一个字段abbreviation
,一个构造方法和一个获取缩写的方法。枚举的比较: 枚举常量之间可以使用
==
运算符进行比较。1
2
3
4
5
6Day day1 = Day.MONDAY;
Day day2 = Day.MONDAY;
if (day1 == day2) {
System.out.println("Both are the same day");
}switch语句和枚举: 枚举类型特别适合在
switch
语句中使用,因为它可以列举所有可能的情况。1
2
3
4
5
6
7
8
9
10
11Day day = Day.MONDAY;
switch (day) {
case MONDAY:
System.out.println("It's Monday");
break;
case TUESDAY:
System.out.println("It's Tuesday");
break;
// 其他情况...
}
泛型
泛型(Generics)是Java语言中的一个重要特性,它允许在类、接口和方法的定义中使用一个或多个类型参数,以实现代码的重用和类型安全性。
通过使用泛型,可以编写通用的代码,可以在不指定具体类型的情况下定义类、接口或方法。这使得代码可以适用于不同类型的数据,提高了代码的灵活性和可重用性。
泛型的主要目的是在编译时执行类型检查,以避免在运行时出现类型转换错误。它提供了类型安全性,可以在编译时捕获和修复类型错误,而不是在运行时抛出异常。
使用泛型的常见场景包括:
泛型类(Generic Class):定义一个类时,可以使用泛型来表示其中的一个或多个类型参数。例如,
ArrayList<T>
是一个泛型类,可以在创建对象时指定具体的类型参数,如ArrayList<String>
。泛型接口(Generic Interface):类似于泛型类,泛型接口允许在接口中使用类型参数。例如,
List<T>
是一个泛型接口,可以在实现接口时指定具体的类型参数。泛型方法(Generic Method):在方法的定义中使用泛型类型参数。这允许方法在调用时接受不同类型的参数,并且可以在方法内部使用泛型类型进行操作。例如,
<T> T getFirst(List<T> list)
是一个泛型方法,可以返回列表中的第一个元素,并且可以适用于不同类型的列表。
泛型的好处包括:
类型安全性:泛型提供了编译时的类型检查,可以在编译时捕获类型错误,避免在运行时出现类型转换错误。
代码重用:通过使用泛型,可以编写通用的代码,可以适用于不同类型的数据,提高了代码的灵活性和可重用性。
代码简洁性:使用泛型可以减少手动的类型转换代码,使代码更加简洁和易读。
包
一个包内的类可以互相访问,即它们之间的访问级别是包级别(默认级别)。在Java中,默认情况下,如果没有明确指定访问修饰符,类、方法和变量的访问级别就是包级别。
这意味着同一包内的类可以相互访问彼此的包级别成员。下面是一个简单的例子:
1 |
|
1 |
|
在上述例子中,MyClass
和AnotherClass
都位于com.example.myapp
包内,因此它们可以访问对方的包级别成员。
Java数据结构
Java提供了丰富的数据结构和集合类库,这些类库位于java.util
包下。以下是Java中一些常用的数据结构和集合:
ArrayList: 动态数组,可以根据需要动态增长或缩小的数组。
1
2
3
4
5import java.util.ArrayList;
ArrayList<String> list = new ArrayList<>();
list.add("Item 1");
list.add("Item 2");LinkedList: 双向链表,支持快速的插入和删除操作。
1
2
3
4
5import java.util.LinkedList;
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("Item 1");
linkedList.add("Item 2");HashMap: 键值对的散列表实现,提供快速的查找和插入。
1
2
3
4
5import java.util.HashMap;
HashMap<String, Integer> map = new HashMap<>();
map.put("Key 1", 1);
map.put("Key 2", 2);HashSet: 基于HashMap的集合,不允许重复元素。
1
2
3
4
5import java.util.HashSet;
HashSet<String> set = new HashSet<>();
set.add("Item 1");
set.add("Item 2");TreeMap: 基于红黑树的有序映射,按照键的自然顺序或自定义顺序排序。
1
2
3
4
5import java.util.TreeMap;
TreeMap<String, Integer> treeMap = new TreeMap<>();
treeMap.put("Key 1", 1);
treeMap.put("Key 2", 2);TreeSet: 基于TreeMap的有序集合,不允许重复元素。
1
2
3
4
5import java.util.TreeSet;
TreeSet<String> treeSet = new TreeSet<>();
treeSet.add("Item 1");
treeSet.add("Item 2");Queue接口和LinkedList: 队列的实现,通常用于先进先出(FIFO)的场景。
1
2
3
4
5
6import java.util.LinkedList;
import java.util.Queue;
Queue<String> queue = new LinkedList<>();
queue.add("Item 1");
queue.add("Item 2");Stack: 栈的实现,通常用于后进先出(LIFO)的场景。
1
2
3
4
5import java.util.Stack;
Stack<String> stack = new Stack<>();
stack.push("Item 1");
stack.push("Item 2");PriorityQueue: 优先队列,基于堆实现,可以按照元素的优先级进行排序。
1
2
3
4
5import java.util.PriorityQueue;
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
priorityQueue.add(3);
priorityQueue.add(1);
Java集合框架
Java集合框架提供了一套强大且灵活的数据结构和算法,用于存储、检索、操作和处理集合数据。这个框架包含了一系列的接口、实现类和算法,主要位于java.util
包中。以下是Java集合框架的主要部分:
- 接口(Interfaces):
- Collection接口: 是集合框架的根接口,表示一组对象的集合。
List
、Set
和Queue
都继承自Collection
接口。
List
:有序的集合,允许重复元素。常用实现类有ArrayList
、LinkedList
、Vector
。Set
:不允许重复元素的集合。常用实现类有HashSet
、LinkedHashSet
、TreeSet
。Queue
:代表一组元素的队列,通常用于实现先进先出(FIFO)的数据结构。常用实现类有LinkedList
和PriorityQueue
。
- Map接口: 表示键值对的集合,每个键都映射到一个值。常用实现类有
HashMap
、LinkedHashMap
、TreeMap
。
- 实现类(Classes):
List接口的实现类:
ArrayList
:动态数组实现,支持随机访问,适用于快速查找和遍历。LinkedList
:双向链表实现,支持快速插入和删除,适用于频繁操作集合元素的场景。Vector
:类似于ArrayList
,但是是线程安全的。
Set接口的实现类:
HashSet
:基于哈希表实现,不保证顺序。LinkedHashSet
:基于哈希表和链表实现,按照元素插入顺序保证顺序。TreeSet
:基于红黑树实现,按照元素的自然顺序或自定义顺序排序。
Queue接口的实现类:
LinkedList
:实现了Queue
接口,可用于实现队列。
Map接口的实现类:
HashMap
:基于哈希表实现,键值对无序。LinkedHashMap
:基于哈希表和链表实现,按照键值对插入顺序保证顺序。TreeMap
:基于红黑树实现,按照键的自然顺序或自定义顺序排序。
其他常用类:
HashSet
、LinkedHashSet
、TreeSet
等都实现了Set
接口。HashMap
、LinkedHashMap
、TreeMap
等都实现了Map
接口。
工具类(Utilities):
- Collections类: 提供了一系列静态方法,用于对集合进行操作和算法处理,如排序、反转、洗牌等。
- Arrays类: 提供了一系列静态方法,用于操作数组,如排序、二分查找等。
并发集合(Concurrent Collections):
ConcurrentHashMap
:线程安全的哈希表实现。CopyOnWriteArrayList
:线程安全的动态数组实现,适用于读多写少的场景。CopyOnWriteArraySet
:线程安全的集合,基于CopyOnWriteArrayList
实现。
Java线程
在Java中,线程相关的操作和指令主要涉及到以下方面:
创建线程:
使用
Thread
类或实现Runnable
接口创建线程。例如:
1
2Thread myThread = new Thread();
myThread.start();
线程调度和控制:
Thread.sleep(long millis)
:使当前线程休眠指定的毫秒数。Thread.yield()
:暂停当前正在执行的线程,允许其他线程执行。join()
:等待一个线程终止。interrupt()
:中断线程的执行。
同步和互斥:
synchronized
关键字:用于同步代码块或方法,确保在同一时刻只有一个线程可以访问。wait()
、notify()
、notifyAll()
:在对象上进行等待和唤醒其他线程的操作,通常与synchronized
一起使用。
线程状态控制:
getState()
:获取线程的状态。isAlive()
:判断线程是否处于活动状态。
并发集合和工具类:
java.util.concurrent
包提供了一些并发集合和工具类,如ConcurrentHashMap
、CountDownLatch
、CyclicBarrier
等,用于更方便地进行多线程编程。
- 线程池:
ExecutorService
和ThreadPoolExecutor
等类用于管理线程池,提高线程的复用性和效率。
- 原子操作和CAS(Compare and Swap):
java.util.concurrent.atomic
包提供了一系列原子操作类,如AtomicInteger
、AtomicLong
等,用于在多线程环境中执行原子操作。
并行流:
- Java 8 引入的并行流框架允许在多个线程上同时处理流的元素。
这些指令和类库提供了Java中进行多线程编程的基本工具和机制。在编写多线程代码时,要注意线程安全性、死锁和性能等问题,以确保程序的正确性和效率。
这几个方法的具体用法如下:
Thread.sleep(long millis):
使当前线程休眠指定的毫秒数,进入阻塞状态。
用法示例:
1
2
3
4
5try {
Thread.sleep(1000); // 休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread.yield():
暂停当前正在执行的线程,允许其他线程执行。
yield
是一个静态方法,通过调用Thread.yield()
可以提示调度器当前线程愿意让出CPU资源。用法示例:
1
Thread.yield(); // 暂停当前线程,让出CPU资源
join():
等待一个线程终止,即等待被调用
join
方法的线程执行完毕。用法示例:
1
2
3
4
5
6
7
8
9Thread thread = new Thread(() -> {
// 线程执行的逻辑
});
thread.start();
try {
thread.join(); // 主线程等待 thread 线程执行完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
interrupt():
中断线程的执行,给线程发送中断信号。
被中断的线程需要通过检查
Thread.interrupted()
或isInterrupted()
方法来判断是否被中断,并做相应处理。用法示例:
1
2
3
4
5
6
7
8
9
10Thread myThread = new Thread(() -> {
while (!Thread.interrupted()) {
// 线程执行的逻辑
}
System.out.println("线程被中断");
});
myThread.start();
// 在其他地方中断线程
myThread.interrupt();
一些关键字和方法主要用于实现线程之间的协调和同步,确保多个线程能够安全地共享资源。以下是它们的具体用法:
synchronized 关键字:
用于同步代码块或方法,确保在同一时刻只有一个线程可以访问被 synchronized 修饰的代码块或方法。
对象级别的同步:
1
2
3public synchronized void synchronizedMethod() {
// 同步的代码块或方法
}代码块级别的同步:
1
2
3
4
5public void someMethod() {
synchronized (lockObject) {
// 同步的代码块
}
}
wait()、notify()、notifyAll():
这些方法通常与 synchronized 一起使用,用于实现线程之间的协调和通信。
wait()
:使当前线程进入等待状态,并释放对象锁,直到其他线程调用相同对象的notify()
或notifyAll()
方法唤醒它。1
2
3
4
5
6
7synchronized (lockObject) {
try {
lockObject.wait(); // 等待其他线程唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
}notify()
:唤醒在相同对象上等待的一个线程。1
2
3synchronized (lockObject) {
lockObject.notify(); // 唤醒一个等待线程
}notifyAll()
:唤醒在相同对象上等待的所有线程。1
2
3synchronized (lockObject) {
lockObject.notifyAll(); // 唤醒所有等待线程
}
这些机制可以帮助线程之间进行协同工作,避免竞争条件和确保资源的正确共享。需要注意的是,使用 wait()、notify()、notifyAll() 时,必须在同步块或同步方法中调用,否则会抛出 IllegalMonitorStateException
异常。
Java I/O
在Java中,流(Stream)是用于处理输入和输出(I/O)操作的重要概念。流主要分为输入流和输出流,它们分别用于从外部数据源读取数据和将数据写入到外部目标。
以下是Java流的一些关键概念和总结:
输入流和输出流:
- 输入流(InputStream): 用于从外部数据源(例如文件、网络、内存等)读取数据。
- 输出流(OutputStream): 用于向外部目标(例如文件、网络、内存等)写入数据。
字节流和字符流:
- 字节流: 处理字节数据,主要用于处理二进制文件。例如,
FileInputStream
和FileOutputStream
。 - 字符流: 处理字符数据,适用于文本文件。例如,
FileReader
和FileWriter
。字符流使用了字符集,可以更好地处理文本文件中的字符编码。
- 字节流: 处理字节数据,主要用于处理二进制文件。例如,
节点流和包装流:
- 节点流: 直接连接到数据源或目标的流。例如,
FileInputStream
和FileOutputStream
。 - 包装流(或处理流): 对节点流进行包装,提供额外的功能,如缓冲、压缩、解压等。例如,
BufferedInputStream
和BufferedOutputStream
。
- 节点流: 直接连接到数据源或目标的流。例如,
字符集和编码:
- 字符流使用字符集来处理字符编码,以确保正确的字符转换。
- 常见的字符集包括UTF-8、UTF-16、ISO-8859-1等。
处理流的装饰器模式:
- 处理流通常通过装饰器模式实现。你可以通过组合多个处理流,以获得更丰富的功能。
对象流:
ObjectInputStream
和ObjectOutputStream
允许直接读写Java对象。这对于序列化和反序列化对象很有用。
标准I/O:
System.in
、System.out
和System.err
分别代表标准输入、标准输出和标准错误输出。这些流通常用于从控制台读取输入和输出结果。
try-with-resources:
- Java 7引入了
try-with-resources
语句,使得资源的管理更加简便。可以在try
语句中自动关闭实现AutoCloseable
接口的资源。
- Java 7引入了
Java错误和异常
在 Java 中,错误(Errors)和异常(Exceptions)是用于处理程序运行期间出现的问题的机制。它们都是从 Throwable
类派生的,但在处理方式和用途上有所区别。下面是对 Java 错误和异常的总结:
错误(Errors):
- 错误表示严重的问题,通常是无法恢复的情况,例如虚拟机错误、系统错误、内存溢出等。
- 错误由 JVM 抛出,并且一般不应该被程序捕获和处理。
Error
类及其子类是用于表示错误的类型,例如OutOfMemoryError
、StackOverflowError
等。
异常(Exceptions):
- 异常是程序在运行过程中遇到的非正常情况,可以被捕获和处理,以便程序继续执行。
- 异常分为两种类型:已检查异常(Checked Exceptions)和运行时异常(Unchecked Exceptions)。
- 已检查异常是在编译时强制要求处理的异常,例如
IOException
、SQLException
等。对于已检查异常,要么在方法中使用throws
声明抛出,要么使用try-catch
块进行捕获和处理。 - 运行时异常是指那些可以在运行时检测到的异常,不需要在编译时进行处理。例如,
NullPointerException
、ArrayIndexOutOfBoundsException
等。通常情况下,运行时异常是由程序错误导致的,应该尽量避免发生,但并不强制要求捕获和处理。
在处理异常时,可以使用以下几种关键字和机制:
try-catch
块:用于捕获和处理异常。try
块中放置可能抛出异常的代码,而catch
块用于捕获和处理对应的异常类型。throws
关键字:用于在方法签名中声明可能抛出的异常类型,将异常传递给调用者处理。finally
块:在try-catch
块之后使用,用于执行无论是否发生异常都需要执行的代码块,例如资源的释放。