文章目录
一、对象和封装
1.1 面向对象设计
1.1.1 回顾面向对象
什么是面向对象
-
面向对象编程
-
面向对象编程的本质就是:以类的方式组织代码,以对象的组织(封装)数据
-
抽象:(把多个事物的共同点抽取出来,形成一个类)
-
三大特性
- 封装(把代码封装起来,由外部访问)
- 继承(由子类继承父类的方法和属性)
- 多态(同一种事物的不同的形态)
-
从认识论角度考虑先有对象后有类。对象,是具体的事物。类,是抽象的,是对对象的抽象
-
从代码允许角度,先有类后有对象。类是对象的模板
面向过程 & 面向对象
-
面向过程思想
- 步骤清晰,第一步做什么,第二步做什么…
- 面对过程适合处理一些较为简单的问题
-
面向对象思想
-
;物以类聚,分类的思维模式,思考问题首先会解决问题需要哪些分类,然后对这些分类进行单独思考。最后,才对某个分类下的细节进行面向过程的思索
- 面向对象适合处理复杂的问题,适合处理需要多人协作的问题
-
面向对象的好处
- 易维护、质量高、效率高、易扩展
1.1.2 使用面向对象设计
使用面向对象分析
- 第一步:分析需求,归纳出类
- 第二步:发现类的属性
- 第三步:发现类的方法
- 为了更清楚的表示类,可以将设计结果通过类图进行描述
- 类图是软件工程的统一建模语言,是一种静态结构图
- 类图以反映类的结构(属性、操作)及类之间的关系为目的
- 示例如下
- 箭头指向的为父类
- +表示public
- -表示private
- #表示protected
- 设计类时遵循的原则
- 属性和方法是为了解决业务问题设置的
- 关注主要属性和方法
- 如果没必要,那么不要增加额外的类、属性与方法
1.1.3 构造方法
构造方法的作用
- 当需要设置的属性很多时,可在创建对象时就完成赋值
- 通过构成方法完成对象的创建,以及实例变量的初始化
- 注意:实例变量没有手动赋值的时候,系统会赋默认值。
构造方法的定义
- 语法
访问修饰符 方法名 (参数列表){
//代码块
}
-
没有参数的构造方法称之为无参构造,反之,称之为有参构造
-
注意事项
- 构造方法的名称必须和类名相同
- 没有返回值类型
- 构造方法的参数列表是可选的,可以定义无参构造和有参构造
- 一个类至少有一个无参构造,如果没有创建,系统会自动创建一个无参构造
示例
public class Main { //实例变量 public String name; public int age; //使用无参构造赋值 public Main(){ //在构造方法中给实例变量赋值 //this指当前类 this.name="Java"; this.age=28; } //使用有参构造赋值 public Main(String name,int age){ this.name=name; this.age=age; } //主方法 public static void main(String[] args) { //在创建对象时,就会调用构造方法 //此时,实例变量已被赋值 //无参构造 Main main1=new Main(); System.out.println("我的名字是:"+main1.name); System.out.println("我今年"+main1.age+"岁啦"); //有参构造 Main main2=new Main("html",33); System.out.println("我的名字是:"+main2.name); System.out.println("我今年"+main2.age+"岁啦"); } }
注意 如果无参和有参同时出现,创建方法时会根据参数类型及数量自行对应 运行不同的构造方法
this关键字
-
对一个对象的默认引用
-
this指的时当前对象
-
表示当前对象的属性或方法
-
this关键字的三种用法
-
(1) this关键字调用成员变量,解决成员变量和局部变量的同名冲突
public class Test { //this关键字调用成员变量,解决成员变量和局部变量的同名冲突 String brand; public void AppleTree(String brand){ //this.brand指成员变量 //brand指的是局部变量,也就是上方的参数 this.brand=brand; } }
-
(2)使用this关键字调用成员方法
public class Test { //使用this关键字调用成员方法 //成员方法 public void print(){ System.out.println("Hello Word"); } public void changeName(String name){ //调用成员方法 this.print(); } }
-
(3)使用this关键字调用已经定义的构造方法
构造方法只可以在构造方法中调用
this调用构造方法 语法: this(实参)
public class Test { //使用this关键字调用已经定义的构造方法 String name; String Sex; int age; String dept; public Test(String name,String Sex){ this.name=name; this.Sex=Sex; } public Test(String name,String Sex,int age,String dept){ //使用this调用构造方法 this(name, Sex); this.age=age; this.dept=dept; } }
-
1.1.4 方法重载
方法重载的定义
- 方法的重载是指一个类中可以定义有相同的名字,但参数不同的多个方法。调用时,会根据不同的参数表选择对应的方法。
方法重载的特点
-
必须在同一类
-
方法名相同
-
参数列表(方法参数的个数或参数类型不同)
-
跟返回值类型无关
-
只有返回值不同不构成方法重载。
-
只有形参的名称不同,不构成方法重载。
-
构造函数也可以重载
方法重载示例
public class Test05 {
public void getSum(int a,int b){
System.out.println("整数:"+(a+b));
}
public void getSum(double a,double b){
System.out.println("小数:"+(a+b));
}
public static void main(String[] args) {
Test05 test05=new Test05();
test05.getSum(2,3);
test05.getSum(7.5,1.5);
}
}
方法重载的优点
- 可以根据参数的不同,采用不同的实现方法
- 不需要编写多个名称
- 简化了类调用方法的代码
1.2 使用封装重构类的属性和方法
1.2.1 封装的概念
对信息的保护
- 保护代码,防止外部的代码随意访问
- 尽量暴露少的方法给外部使用
- 可对数据设置一定的限制,防止被随意赋值
- 隐藏信息,实现细节
在Java中使用封装
- 使用关键字private设置属性为私有
- 设置private私有属性,在别的类就无法调用该方法
- 调用private属性应用get和set关键字
- 使用get关键字在别的类获取封装类的数据
- 使用set关键字在别的类修改或设置封装类的数据
- 大部分使用为属性使用封装,方法一般不使用
1.2.2 实现Java封装的步骤
Stundent对象类
public class Student {
//私有属性
private String name; //姓名
private int age; //年龄
private char sex; //性别
//提供public 的get 和 set方法 获取或设置值
//获取name值
public String getName(){
return this.name;
}
//设置name值
public void setName(String name){
this.name=name;
}
}
Main主方法,运行类
public class Application {
public static void main(String[] args) {
Student s1=new Student();
//使用set方法给Student类的name值赋值
s1.setName("兰巧儿");
//使用get方法获取Student类的name值
System.out.println(s1.getName());
}
}
快捷生成get和set方法
- 使用快捷键Alt键+Ins键
- 选择要生成的get方法或set方法
- 选择要生成的属性
1.2.3 使用封装对数据赋值进行限制
Stundent对象类
一般年龄的输入要符合实际 所有要进行限制
public class Student {
//私有属性
private String name; //姓名
private int age; //年龄
private char sex; //性别
//提供public 的get 和 set方法 获取或设置值
//获取name值
public int getAge() {
return age;
}
//赋值时对年龄进行限制
public void setAge(int age) {
//如果年龄大于200岁并且小于0岁 那就默认年龄为18
if (age>200||age<0){
this.age = 18;
}else {
this.age=age;
}
}
}
Main主方法,运行类
public class Application {
public static void main(String[] args) {
//如果赋的年龄大于200岁
Student s1=new Student();
s1.setAge(230);
System.out.println(s1.getAge());
//如果赋的年龄为正常范围
Student s2=new Student();
s2.setAge(35);
System.out.println(s2.getAge());
}
}
Get和Set方法称之为属性封装
1.2.4 封装的主要步骤
1. 修改属性的可见性
2. 创建赋值(Set)和取值(Get)方法
3. 加入属性的存取控制语句
1.2.5 类和类成员的访问控制
1. 类的访问修饰符
修饰符 | 作用域 | |
同一包中 | 不同包中 | |
public | 可以使用 | 可以使用 |
default(默认修饰符) | 可以使用 | 不可以使用 |
2. 类成员的访问修饰符
修饰符 | 作用域 | |||
同一类中 | 同一包中 | 子类中 | 外部包 | |
private | 可以使用 | 不可以使用 | 不可以使用 | 不可以使用 |
default(默认修饰符) | 可以使用 | 可以使用 | 不可以使用 | 不可以使用 |
protected | 可以使用 | 可以使用 | 可以使用 | 不可以使用 |
public | 可以使用 | 可以使用 | 可以使用 | 可以使用 |
3. 在定义类时通过放维修师傅控制其他类对他的访问权限
- private 它具有最小的访问权限,仅仅能够在定义它的类中被访问,具有类可见性。它是"封装"的体现,大多数属性的修饰符为private
- default默认修饰符 只能被同一个包中的类访问,具有包可见性
- protected 可以被同一个包中的类访问,被同一个项目中不同包中的子类访问。
- public 它具有最大的访问权限,可以被所有类访问。
1.2.6 static关键字
1.static关键字概念
- static关键字可以修饰类的属性、方法、代码块
- static修饰的成员变量和方法,不再属于具体的某个对象,而是从属于类
- 普通变量和方法从属于对象
- static关键字修饰的属性被称为静态变量或类变量
- static关键字修饰的方法被称为静态方法或类方法
- 没有使用static关键字修饰的属性被称为实例变量
- 同一类当中,静态方法中不能直接调用没有被static关键字修饰的方法或属性,需要创建对象才可以调用
2. static关键字的用途
-
方便在没创建对象的情况下进行调用(方法/变量)
-
被static关键字修饰的方法或属性可以使用 类名.(方法/变量) 调用
-
被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。
二、继承
2.1 Java继承
2.1.1 继承的概念
继承的本质是对某一批类的抽象
extends的意思是"扩展"。子类是父类的扩展
Java中类只有单继承,没有多继承
继承是类和类之间的关系
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
-
父类又称基类,子类又称派生类
-
例如人,学生,老师。学生和老师都属于人,那么子类则为学生老师,父类则为人
-
子类会继承父类的特征,例如人都有五官,老师和学生也都有五官,就相当于老师和学生继承了人的特征
子类继承父类,使用关键字extends来表示
- 语法: public class 子类 extends 父类{}
子类会继承父类的除私有意外的方法和属性,私有的属性和方法无法继承
2.1.2 代码演示
父类 Person
public class Person {
String name;
int age;
public void say(){
System.out.println("我叫:"+name);
System.out.println("年龄:"+age);
}
}
子类 Student
public class Student extends Person{
}
main方法运行类
public class Application {
public static void main(String[] args) {
Student stu=new Student();
//Student类中没有任何属性和方法,但仍然能调用
//因为Student类继承了Person类中所有公共的属性和方法
stu.name="巧克力";
stu.age=17;
stu.say();
}
}
在 idea 中使用 Ctrl+H 则会显示继承关系
2.1.3 继承的特性
- 子类可以用于父类非 private 的属性和方法
- 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法。
- 子类不可继承父类的构造函数
- 子类与父类不在一个包中,使用默认访问权限的成员,不可继承
- Java不支持多继承,但支持多重继承
2.1.4 Object 类
- 事实上,所有的类都直接或间接地继承Object类
- 不会显示extends,但是会默认继承Object类
- 所有的对象都继承Objcet类的方法
Object常用的方法
方法 | 说明 |
---|---|
toString() | 返回当前对象本身的有关信息,返回字符串 |
boolean equals() | 比较两个对象,若是,则返回true |
Object clone() | 生成当前对象的一个副本,并返回 |
int hashCode() | 返回该对象的哈希代码值 |
getClass() | 获取当前对象所属的类信息,返回Class对象,即运行时类 |
2.1.5 Super关键字
1. 当继承类当中,子类和父类的属性或方法名称相同时,用于子类调用父类当中的属性和方法
2. super 能出现在实例方法和构造方法中。
3. super 不能出现在静态方法中
super的语法为 super. 和 super()
1. super.
- 用于子类调用父类当中的属性或方法
- 语法 : super.父类属性
2. super()
- 调用父类的构造方法,必须位于构造方法的第一行
- super() 只能出现在子类的构造方法中
- super() 和 this() 不能同时调用
- 一般会自动隐藏在子类构造方法当中,不需要手动调用
Super关键字演示
父类 Person
public class Person {
String name="父类名字";
public Person(){
System.out.println("我是父类构造方法");
}
}
子类 Student
public class Student extends Person{
String name="子类名字";
//super.
public void say(){
//this指当前类的属性
System.out.println("我是"+this.name);
//super指父类的属性
System.out.println("我是"+super.name);
}
//super()
public Student(){
//隐藏代码,一般不需要调用,这里为了方便演示
super();
System.out.println("我是子类构造");
}
}
main方法运行类
public class Application {
public static void main(String[] args) {
System.out.println("调用构造方法:");
Student stu=new Student();
System.out.println("调用属性");
stu.say();
}
}
2.1.6 方法的重写
概念
1. 方法的重写需要有继承关系
2. 只能是子类重写父类的方法
3. 重写需要是非静态方法
4. 参数列表必须相同
5. 修饰符: 范围可以扩大,但不能缩小
6. 抛出的异常: 范围,可以被缩小,但不能扩大
为什么要重写方法:
1. 父类的功能,子类不一定需要,或者不一定满足
indea 重写方法快捷键: Alt+Insert -->override
2.1.7 方法的重写演示
父类 Person
public class Person {
public void say(){
System.out.println("我是Person父类方法");
}
}
子类 Student
public class Student extends Person {
//重写父类say()方法
public void say(){
System.out.println("我是Student子类方法");
}
}
main方法运行类
public class Application {
public static void main(String[] args) {
Person stu=new Student();
Person per=new Person();
//子类重写了父类的方法
stu.say();
per.say();
}
}
上面的stu属于Person类型,但他运行的却是Student类中的say()方法
这是由于在编译阶段,只是检查参数的引用类型。然而在运行时,Java 虚拟机(JVM)指定对象的类型并且运行该对象的方法。因此在上面的例子中,之所以能编译成功,是因为 Student 类中存在 say()方法,然而运行时,运行的是特定对象的方法
2.1.8 方法的重载和方法重写的区别
位置 | 方法名 | 参数表 | 返回值 | 访问修饰符 | |
---|---|---|---|---|---|
方法重载 | 同类 | 相同 | 不相同 | 无关 | 无关 |
方法重写 | 子类 | 相同 | 相同 | 相同或是其子类(返回值类型小于父类) | 不能比父类访问权限小 |
2.1.9 继承关系中的构造方法
1. 一个类的构造方法在如下两种情况下总会被执行
- 创建该类的对象
- 创建该类的子类的对象(子类的实例化)
2. 创建子类的对象时,会优先执行父类的构造方法,然后再执行子类的构造方法
-
如果在子类中,没有使用super关键字调用父类的构造方法,也没有使用this关键字调用自身的构造方法,则系统会默认先调用父类的无参构造方法
-
如果子类使用 super 关键字调用父类的带参构造方法,则先执行父类的构造方法
-
构造方法中 super()和this()都只能存在在构造方法的第一行,所以两个不能同时使用
三、多态
3.1 实现多态
3.1.1 什么是多态
- 多态性是面向对象编程的又一个重要特征
- 是指在父类中定义的属性和方法被子类继承之后,具有表现多种形态的能力特征
3.1.2 多态的定义和实现
-
一个特定类型的变量可以引用不同类型的对象,并且能够自动地调用引用对象的方法
-
也可以根据作用到不同对象类,相应不同的操作
-
可以使用父类的对象调用到子类当中被重写的方法
1. 子类到父类的转换( 向上转型 )
-
子类到父类的转换被称为向上转型
-
语法 :< 父类型 > <引用变量名> =new < 子类型 >()
类似于把int类型转为double类型
- 代码演示
父类 Animal类
public class Animal {
//动物名称
String name;
public void eat(){
System.out.println(this.name+"正在吃");
}
}
子类1 Dog类
public class Dog extends Animal{
public void eat(){
name="狗";
System.out.println(this.name+"正在吃骨头");
}
}
子类2 Cat类
public class Cat extends Animal{
public void eat(){
name="猫";
System.out.println(this.name+"正在吃桃子");
}
}
运行类 Application类
public class Application {
public static void main(String[] args) {
Animal animal=new Animal();
//多态
Animal cat=new Cat();
Animal dog=new Dog();
animal.eat();
cat.eat();
dog.eat();
}
}
- 将子类作为对象传给父类,父类可直接调用子类当中重写过父类的方法
- 父类不可以调用子类当中特有的方法
- 向上转型的两种形式
- 使用父类作为方法参数实现
- 使用父类作为方法返回值实现
2. 父类到子类的转换(向下转型)
-
当父类想要调用子类特有的方法时,可以使用向下转型
-
向上转型之后,无法使用子类当中特有的方法
-
使用向下转型,可以调用子类当中特有的方法
-
可以将父类专为子类,但是必须进行强制转换
-
语法 :
< 父类型 > <父类引用变量名> =new < 子类型>()
<子类型> <子类引用变量名> = (<子类型>) 父类引用变量名
父类 Animal类
public class Animal {
//动物名称
String name;
public void eat(){
System.out.println(this.name+"正在吃");
}
}
子类1 Dog类
public class Dog extends Animal{
public void eat(){
name="狗";
System.out.println(this.name+"正在吃骨头");
}
public void play(){
name="狗";
System.out.println(this.name+"正在玩耍");
}
}
运行类 Application类
public class Application {
public static void main(String[] args) {
Animal animal=new Animal();
//多态
Animal ani1=new Dog();
Dog dog=(Dog)ani1;
dog.eat();
dog.play();
}
}
综上所述,向下转型的父类,可以调用子类当中所有的方法
实现多态的三要素
1.继承关系的父类和子类
2.子类重写父类方法
3.父类的引用指向了子类对象
3.1.3 多态的优势
**1. 可替换性 :**多态对已存在的代码具有替换性
**2. 可扩展性:**增加新的子类不影响已存在类的多态性、继承性,已经其他特性的运行和操作。
**3. 灵活性 :**在多态在应用中,体现了灵活多样性,提高了使用效率
**4. 简化性 :**多态简化了应用软件的代码编写和修改过程,尤其是在处理大量对象的运算和操作 时,尤为突出
3.1.4 instanceof 运算符
-
如果父类在进行向下转型时,没有转换为真实的子类类型,就会出现转换异常
-
语法 :对象 instanceof 类
- 左边是对象,右边是类;当对象是右边类或子类所创建对象时,返回true;否则,返回false。
-
instanceof 运算符一般用于对象的强制转换,判断子类是否是真实的子类类型
四、抽象类和接口
4.1 抽象类
4.1.1 抽象类和抽象方法
使用上 abstract 关键字修饰的类 或 方法
抽象类
-
抽象类就是抽象的类,抽象往往是相对于具体而言的。
-
例如猫是具体的对象,则动物就是抽象概念
-
抽象方法所在的类,必须是抽象类才行,在class之前写上abstract即可
-
抽象类无法创建对象,抽象出的概念,不存在具体实例
-
语法 : <访问修饰符> abstract calss 类名{}
抽象方法
- 当一个类的方法被abstract 关键字修饰的时候,这个方法被称为抽象方法
- 被抽象的方法,不会具体实现,也就是不需要写方法体,同时也不需要大括号
- 被抽象的方法,抽象类的子类必须重写该方法,除非子类也是抽象类
- 语法 <访问修饰符> abstract < 返回值 > < 方法名 >([参数列表])
抽象方法与普通方法的区别
-
普通方法有方法体,而抽象方法没有
-
示例
public abstract class Test01 { //普通方法 public void print(){ //方法体 System.out.println("hello"); } //抽象方法 public abstract void println(); }
抽象类的优势‘
- 提高可重用性
- 代码松耦合,更易于维护
- 方便实现多态
有抽象方法的类必然是抽象类,但抽象类当中的方法不一定全是抽象方法
abstract只能修饰类或方法,不能修饰属性和构造方法
4.1.2 final修饰符
final除了可以修饰变量,还可以用来修饰类和方法
1. final修饰类
- 用final修饰的类不能再被继承
- **语法 : <访问修饰符> final Class 类名 **
2. final修饰类的方法
- 用final修饰的方法不能再被重写
- 语法 : <访问修饰符> final <返回值类型> 方法名 ([参数列表]) {}
4.2 接口
4.2.1 为什么需要接口
1. java中一个类不允许有多重继承,但是可以继承n个接口
2. 我们希望在一个类中能够同时兼容多个不同类型的特征,则需要使用接口
4.2.2 接口的概念和定义
1. 接口是一个全部由抽象方法组成的集合,接口内定义的方法全部为抽象方法
2. 接口需要用interface定义
3. 接口内只能有抽象方法和常量
4. 接口没有构造方法
5. 定义接口
- 定义接口类型
语法 :[访问修饰符] interface 接口名(){
//接口成员
}
-
类实现接口
语法 : class 类名 implements 接口名{
//类成员
}
-
示例
notebook 接口
public interface notebook { public void play(); }
Student 类
//实现接口 public class Student implements notebook{ @Override public void play() { System.out.println("学生在使用电脑"); } }
4.2.3 定义复杂的接口
1. 接口直接可以通过用extends 关键字实现继承关系
- 一个接口可以继承多个接口
- 接口不可以继承类
- 语法 [访问修饰符] interface 接口名 extends 父接口1,父接口2 …{}
2. 一个普通类只能继承一个类,但能同时实现多个接口
-
接口同时继承一个父类并实现多个接口
-
语法 : calss 类名 extends 父类名 implements 接口1,接口2
3. 示例
-
接口继承接口
-
父类接口1
public interface FatherInterface1 { void one(); }
-
父类接口2
public interface FatherInterface2 { void two(); }
-
子类接口
public interface Subclass extends FatherInterface1,FatherInterface2{ @Override void one(); @Override void two(); }
-
五、异常
5.1 认识异常
5.1.1 异常的概念
在计算机程序运行的过程中,总是会出现各种各样的错误,如除法运算时除数为0、数组下标越界、数据类型不一致、内存不足、栈溢出等,在Java
中,将程序执行过程中发生的不正常的行为叫做异常.
5.1.2 异常的分类
所有的异常都定义为类
异常中有着不同类型的异常,如下
1. Throwable 类
- java语言中所有错误或异常的父类
2. Error 类
- Java.lang.Error 类包含仅靠程序本身无法恢复的严重错误,一般与JVM相关的问题
- java运行时环境的内部错误或硬件问题
3. Exception 类
-
java.lang.Exception 类 是程序本身可以处理的异常
-
Exception 类主要分为 运行时(RuntimeException)异常 和 检查(Check)异常
-
java常见的 运行时异常
-
异常 说明 ArithmeticException 当出现算术错误时,抛出此异常,例如,在一个整数"除以0"时 ArrayIndexOutOfBoundsException 当非法索引访问数组时,抛出此异常。例如,索引为负或大于等于数组长度 ClassCastException 当试图将对象强制转换为非本对象类型的子类时,抛出此异常 IllegalArgumentException 当向方法传递了一个不合法或不正常的参数时,抛出此异常 InputMismatchException 当欲得到的数据类型与实际输入的类型不匹配时,抛出此异常 NullPointerException 当应用程序试图在需要对象的地方使用null时,抛出此异常 NumberFormatException 当试图将字符串转换成一种数字类型时,但该字符串不能转换为适当格式时,抛出此异常。例如,把 “ABC” 转换成数字
-
5.2 异常处理机制
在程序设计时,必须考虑到可能发生的异常时间并进行处理
5.2.1 异常处理结构
java中针对异常的处理提供了 try、catch、finally、throws、throw
语法 :
try{
//有可能出现异常的语句
}[catch(异常类型 异常对象){
//异常处理句
}] [finally{
//一定会运行到的语句
}]
-
try 语句用于监听,当try语句块内发生异常时,异常就会被抛出,然后停止运行try代码块
-
catch 语句用于捕获异常,catch语句用来捕获try语句中抛出的异常;
-
try 语句抛出的异常类型,与 catch 捕获的异常类型相同时,则会执行catch 代码块
-
finally 语句总会被执行,主要用于回收try语句块内打开的资源,例如关闭数据库等等
-
catch 语句和 finally 语句都可选,但是 catch 和 finally 不能同时消失
-
异常格式常见的组合 :try-catch、try-catch-finally、try-finally 三种
5.2.2 异常对象常用的方法
在catch代码块当中,可以调用异常方法进行输出信息
异常对象常用的方法
方法名 | 说明 |
---|---|
void printStackTrace() | 输出异常的堆栈信息 |
String getMessage() | 返回异常信息描述字符串 |
示例
public class Demo01 {
public static void main(String[] args) {
try {
}
//异常类型 Exception
catch (Exception e){
//异常方法
e.printStackTrace();
e.getMessage();
}
}
}
5.2.3 多重 catch 语句
**当我们希望针对不同的异常类型采取不同的处理方式,可以使用 catch **
语法 :
try{
//有可能出现异常的语句
}[catch(异常类型1 异常对象){
//异常处理句
}][catch(异常类型2 异常对象){
//异常处理句
}] [finally{
//一定会运行到的语句
}]
- 如果 try 内的代码出现的异常类型为异常类型1 则执行异常类型1内的代码块
5.2.4 声明异常——throws 关键字
throws 表示该方法可能会抛出此类异常
语法:
public void 方法名() throws 异常类型[,异常类型]{
//方法体
}
-
如果方法体内发生异常,则会抛出该异常
-
在调用方法时则会发生异常
5.2.5 抛出异常——throw 关键字
当需要根据条件判断是否抛出异常时,则可以使用throw 关键字
语法:
throw new 异常名([参数列表])
例如:除数为0时抛出一个异常
public class exception {
//声明一个除法
//throws表示准备抛出一个异常
public void chu(int a,int b) throws Exception{
//除数为0时,抛出一个算术异常
if(b==0){
throw new Exception("除数不能为0");
}else{
System.out.println(a/b);
}
}
}
- throw表示抛出了一个异常
- throws 表示准备抛出一个异常,准备抛出throw抛出的异常
- throw只能抛出 Throwable 类或子类的对象
5.2.6 自定义异常
当 Java 异常体系中提供的异常类型不能满足程序的需要时,可以自定义异常类
自定义异常类的三个步骤
-
定义异常类 继承 Exception 类 或 RuntimeException 类
-
编写异常类的构造方法 ,调用父类的构造方法,常见的有四种形式
//构造方法 1
public Exception(){
super();
}
//构造方法 2
public Exception(String message){
//message是提示语句
super(message);
}
//构造方法 3
public Exception(String message,Throwable cause){
super(message,cause);
}
//构造方法 4
public Exception(Throwable cause){
super(cause);
}
- 实例化自定义异常对象,并使用 throw 关键字抛出。
创建自定义异常
public class exception extends Exception{
public exception(String message){
super(message);
}
}
六、集合框架
6.1 Java集合概述
6.1.1 集合概念
- 集合就是一个放数据容器,它主要包括Collection和Map集合
- 集合中的元素全部为对象
- 集合可以存放不同类型、不限数量的数据类型。
- 集合包含的接口 :Iterable,Collection,List,Set,Map
Java的集合类有两个接口派生而成 Collection 接口 和 Map 接口
- Collection 接口 和 Map 接口为 java 集合框架的根接口
- Collection 接口 常用的子接口包含 List 接口 和 Set 接口
- Map 接口独立一支
Java 集合框架常用接口说明
名称 | 描述 |
Collection | ● 单个值集合的根接口,最基本的集合接口 ● 一个 Collection 代表一组 Object (对象)。Java不提供实现类,只提供子接口( List 接口和 Set 接口) ● Collection 接口存储一组可重复的无序对象 |
Set | ● 继承 Collection 接口,存储一组不可重复的无序对象 |
List | ● 继承 Collection 接口,存储一组可重复的有序对象 ● 元素顺序以元素插入的次序来放置元素,不会重新排序 ● 通过索引访问数组,索引从0开始 ● List 接口常用的实现类有 ArrayList 类和 LinkedList 类 |
Map | ● 存储成对数据的根接口,可存储一组" key(键)-value(值对)"对象 ● 提供 key(键) 到 value(值) 的映射 ● Map 中的 key(键) 是无序的,不重复的 ● Map 中的 value(值) 是无序的,可重复的 ● 可以通过 key(键) 找到对应的 value(值) |
Iterator | ● 集合迭代器,可以遍历集合元素的接口 |
Collections | ● 与 Collection 是不同的概念,它提供了对象合对象进行基本操作的通用接口方法 ● 包含各种有关集合操作的静态方法 ● 工具类,不可实例化 |
6.1.2 Collection 接口
Collection 接口是 List 接口 和 Set 接口的父类,该接口定义的方法可用于操作 List集合和 Set 集合
Collection 接口常用的方法
方法 | 描述 |
---|---|
boolean add (Object o) | 用于向集合中添加一个元素,如果集合不允许重复且已经包含了指定元素,则返回 false |
boolean addAll (Collection c) | 将集合 c 里的所有元素添加到指定的集合里,添加成功返回 true |
void clear () | 清除集合中的所有元素,将集合长度变为0 |
boolean contains (Object o) | 判断集合中是否包含指定元素 o |
boolean containsAll (Collection c) | 判断集合中是否包含集合 c 里的所有元素 |
boolean remove (Object o) | 删除集合中指定的元素 o,当集合中包含了一个或多个元素 o是,这些元素将被删除,成功返回 true |
int size() | 返回集合里元素的个数 |
boolean retainAll (Collection c) | 从集合中删除集合 c 里不包含的元素,如果成功删除,返回 true |
boolean removeAll (Collection c) | 从集合中删除集合 c 里包含的所有元素,如果成功删除,返回 true |
boolean isEmpty() | 如果此集合中不包含任何元素,则返回 true |
Object [] toArray() | 把集合转换成数组, 所有的集合元素变成对应的数组元素 |
定义接口
//使用 ArrayList 类对象
Collection list=new ArrayList();
//使用 HashSet 类对象
Collection set=new HashSet();
ArrayList 是 List 的子类
HashSet 是 Set 的子类
6.1.3 集合的遍历
当直接使用 println 输出集合时,将以[1,2,3…]的形式输出
如果想一次访问集合里的每一个元素,则需要使用某种方式遍历集合元素
主要有两种遍历集合的方法
1、使用 iterator 接口遍历集合元素
-
Iterator(迭代器)不是一个集合,它是一种用于访问集合的方法
-
Iterator(迭代器)该类主要用于遍历集合对象,该类描述了遍历集合的常见方法
-
Iterator 接口定义的方法
方法 | 描述 |
---|---|
boolean hasNext() | 判断是否存在下一个遍历元素,存在则返回true |
Object next() | 返回遍历的下一个元素 |
void remove() | 删除集合里上一次 next() 方法返回的元素 |
- 获取集合迭代器 语法 :
Iterator 迭代器名=集合名.Iterator();
- Iterator 集合遍历示例
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Text01 {
public static void main(String[] args) {
//定义一个集合
Collection list=new ArrayList();
//添加元素
list.add("鼠");
list.add("牛");
list.add("虎");
list.add("兔");
//获取集合迭代器
Iterator lst=list.iterator();
while(lst.hasNext()){
System.out.println(lst.next());
}
}
}
注意 Iterator 接口对元素进行遍历时,是把集合元素的值传给了迭代器,所以修改迭代器中存储的值,对集合元素本身没有任何影响
2、使用 foreach 循环遍历集合元素
语法 :
for(数据类型 迭代变量名 : 迭代对象){
//引用迭代变量名的语句
}
示例
import java.util.ArrayList;
import java.util.Collection;
public class Text01 {
public static void main(String[] args) {
//定义一个集合
Collection list=new ArrayList();
//添加元素
list.add("鼠");
list.add("牛");
list.add("虎");
list.add("兔");
//使用foreach遍历集合
for(Object o:list){
System.out.println(o);
}
}
}
注意 foreach 循环中的迭代变量也不是集合元素本身,系统只是把集合元素付给了迭代变量
遍历集合有两种方式 foreach遍历 和 Iterator遍历
- foreach 在遍历集合时不能对集合进行操作
- iterator 在遍历集合时可以对集合进行操作,他是将自己的数据传给了 iterator迭代器
6.1.4 Java集合框架包含的主要内容
6.2 List
6.2.1 List 概述
-
List 集合是一类存储元素有序、可重复的集合,集合中每个元素都有相对应的索引
-
List 接口为 Collection 接口的子接口,可以使用 Collection 接口定义的全部方法
-
List 集合是有序集合,在 Collection 接口方法的基础上,扩展了一些方法
-
List 接口扩展的方法
方法 | 描述 |
---|---|
void add(int index, Object element) | 将元素 (element) 插入 List集合的指定位置(index) |
boolean addAll (int index, Collection c) | 将集合 c 所包含的所有元素插入 List 集合的指定位置(index) |
Object get (int index) | 返回集合 index 索引处的元素 |
int indexOf (Object o) | 返回对象 o 在 List 集合当中第一次出现的位置 |
int lastIndexOf (Object o) | 返回对象 o 在 List 集合当中最后一次出现的位置 |
Object remove (int index) | 从集合中删除指定位置的元素,返回删除的对象 |
boolean remove() | 从集合中删除指定对象,成功返回true |
Object set (int index, Object element) | 将 index 索引处的元素替换成 element 对象,返回被替换的元素 |
List subList (int fromIndex, int toIndex) | 截取并返回集合或数组中的一部分,包含起始位置 (fromIndex) 到 不包含结束位置 (toIndex) 的数据 |
List subList() 演示
import java.util.ArrayList;
import java.util.List;
public class Test02 {
public static void main(String[] args) {
List l1=new ArrayList();
l1.add("1");
l1.add("2");
l1.add("3");
l1.add("4");
//截取 l1 集合 位置下标从0 到 3 不包含3
List l2=l1.subList(0,3);
System.out.println(l2);
}
}
- 使用 List 接口的扩展方法,需要通过 List 接口的子类实例化对象调用
- List 接口常用的子类有 ArrayList 类 和 LinkedList 类
6.2.2 ArrayList 集合类
ArrayList
类现类可变数组的大小,存储在内的数据称为元素- 可以通过索引下标快速访问数据,允许对集合中的元素进行快速的随机访问
- 向
ArrayList
中插入与删除元素的速度相对较慢 ArrayList
数组的存储方式和数组相同,都是在内存中分配连续空间
6.2.3 LinkedList 集合类
-
LinkedList
集合 和ArrayList
集合都是一个List
集合,都可以根据索引下标随机访问集合中的元素 -
LinkedList
还具有双向链表结构,更加方便实现添加和删除的操作 -
但是
LinkedList
类随机访问元素的速度则相对较慢 -
除此之外,
LinkedList
类还提供了实现链表操作的方法 -
LinkedList 集合实现链表操作的常用方法
方法 描述 void addFirst (Object o) 在链表的首部添加元素 void addLast (Object o) 在链表的尾部添加元素 Object getFirst () 返回链表中的第一个元素 Object getLast () 返回链表中的最后一个元素 Object removeFirst () 删除并返回链表中的第一个元素 Object removeLast () 删除并返回链表中的最后一个元素 注意 LinkedList 特有的方法 必须是LinkedList 类型的集合才可以使用,如果是父类,则无法调用
例如:
//正确操作
LinkedList l1=new LinkedList();
l1.removeLast();
6.2.4 ArrayList 集合类和 LinkedList 集合类的异同点
相同点:
- ArrayList 和LinkedList 都是单列集合中 List 接口的实现类
- 都是存取允许重复,且有序的元素
不同点:
- ArrayList的实现是动态数组,在内存中分配连续的空间
- LinkedList 是以元素链表的形式存储它的数据,每一个元素都和它的前一个和后一个链接在一起
- ArrayList查询快,增删慢
- LinkedList查询慢,增删快
6.3 Set
6.3.1 Set 概述
- Set 集合是一个存储数据无序且不允许重复的集合
- 和 List 接口一样,都是 Collection 的子接口
- Set 集合与 Collection 集合基本上一样,没有提供额外方法
- set 没有索引
6.3.2 HashSet 集合
- HashSet 为 Set接口的实现类
- 可以实现对无序不重复数据的存储
- 具有很好的存取和查找性能
- 不允许存储重复的元素
- 没有索引,没有包含索引的方法,不能使用索引遍历
- 是无序集合,存储元素和取出元素的顺序可能不一致
- 如果出现重复数据,则会覆盖掉之前的数据
- 示例
public class Test02 {
public static void main(String[] args) {
HashSet set=new HashSet();
set.add("西瓜");
set.add("桃子");
set.add("你好");
set.add("核桃");
//输出数组 都是无序的,不可重复的
Iterator iterator=set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
6.3 Map
6.3.1 Map概述
- Map 可以存储有映射关系的数据,也就是一组"键(key)-值(value)"的对象
- Map可以存储若干个成对的 键(key)-值(value)
- 其中 键( key ) 为 Set 集合类型,无序且不可重复的
- 值( value) 也要求无序,但可重复
- Map 集合对象不能使用 Collection 接口中定义的方法
- Map 接口的常用方法
方法 | 描述 |
---|---|
Object put(Object key, Object val) | 以“键-值对”的方式进行存储,第一次添加时,返回值为null,如果添加重复的key,则会覆盖掉先添加的"键-值对",返回被顶掉的 value值 |
Object get (Object key) | 根据键返回相关联的值,如果不存在指定的键,返回null |
int size() | 返回元素个数 |
boolean remove (Object key) | 删除由指定的键映射的“键-值对” |
Set keySet () | 返回键的集合 |
Collection values () | 返回值的集合 |
boolean containsKey (Object key) | 如果存在由指定的键映射的“键-值对”,返回true |
Set entrySet () | 返回"键-值对" 集合,返回一个Set集合的 “键(key)-值(value)” |
boolean isEmpty() | 若不存在"键-值对"元素,则返回true |
void clear() | 删除该Map 对象中的所有的 “键-值对” |
- Map 接口提供了大量的实现类,典型实现类如 HashMap类、HashTable类、TreeMap类等,其中 HashMap 类是最常用的 Map 接口实现类
6.3.2 HashMap 集合的遍历
1. 通过KeySet() 方法返回键的集合为Set集合
2. 循环遍历键的集合
3. 通过每个键获取对应的值
示例
import java.util.*;
public class Test02 {
public static void main(String[] args) {
HashMap map=new HashMap();
map.put(1,"一");
map.put(2,"二");
map.put(3,"三");
map.put(4,"四");
//将key返回为 set集合
Set set=map.keySet();
Iterator iterator= set.iterator();
while (iterator.hasNext()){
//接受每一个key
Object key=iterator.next();
System.out.print(key);
//通过key获取value
System.out.println(map.get(key));
}
}
}
6.3.3 Map.Entry
-
Map.Entry是Map的一个内部接口
-
用来保存形如 “键(key)-值(value)” 的元素
-
Map.Entry接口的常用方法
方法 | 描述 |
---|---|
Object getKey () | 取得此 “键(key)-值(value)” 对应的 key 值 |
Object getValue () | 取得此 “键(key)-值(value)” 对应的 value 值 |
int hashCode() | 返回该 “键(key)-值(value)” 的哈希码值 |
Object setValue(Object value) | 用指定的值替换该 “键(key)-值(value)” 的 Value 值 |
- 使用 Map.Entry 遍历 Map 集合
import java.util.*;
public class Test02 {
public static void main(String[] args) {
HashMap map=new HashMap();
map.put(1,"一");
map.put(2,"二");
map.put(3,"三");
map.put(4,"四");
//获取键-值对
Set map1=map.entrySet();
//遍历集合
Iterator iterator=map1.iterator();
while (iterator.hasNext()){
//转为Map.Entry 类型
Map.Entry me=(Map.Entry)iterator.next();
//获取键 key
System.out.print(me.getKey());
//获取值
System.out.println(me.getValue());
}
}
}
6.4 泛型集合
6.4.1 泛型概念
- 规范容器内的数据类型,约束集合中元素的类型
- 语法 :
集合类型 <要规范的类型> 集合名=new 集合类型 <要规范的类型>();
- 示例
public class Test03 {
public static void main(String[] args) {
//集合内只可以存储Integer对象
List<Integer> list=new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
//因为已经设置了泛型,所以不需要再转换类型
int num= list.get(1);
}
}
七、实用类
7.1 Java API介绍
7.1.1 Java API 概述
- API 的意思为 Java 应用程序编程接口
- 还特指 API 的说明文档,也称API帮助文档
- Java 语言的强大之处在于它提供了多种多样的类库,从而提高了编程效率和质量
- Java API 提供常用的包如下
包 | 描述 |
---|---|
java.lang | 编写Java程序时最广泛使用的包,自动导入所有的程序中,包含了Java程序的基础类和接口。包装类、String类等常用的类都包含在此包中,还提供了用于管理类的动态加载、外部进程创建、主机环境查询和安全策略实施等系统操作的类 |
java.util | 包含系统辅助类,特别是Collection、List和Map等集合类 |
java.time | 包含对日期时间进行处理的类,如创建日期对象,格式化日期等 |
java.io | 包含与输入/输出有关的类,如文件操作等类 |
java.net | 包含与网络有关的类,如Socket、ServerSocket等类 |
java.sql | 包含与数据库相关的类,如Connection、Statement等类 |
7.2 枚举
7.2.1 枚举概述
- Java 枚举是一个特殊的类,一般表示一组常量
- Java 枚举类使用 enum 关键字来定义,各个常量使用逗号 , 来分割
- 枚举一般用来约束数据
- 定义枚举语法如下:
访问修饰符 enum 枚举名{
常量1,常量2,常量3.....
}
- 枚举中可以定义多个常量,表示不同的枚举值
- 可以使用 “枚举名.常量名” 的形式取出枚举中的指定内容
7.2.2 枚举方法
-
每一个枚举类型成员都可以调用 Enum 类的方法,实现枚举的遍历、比较等操作
-
枚举的常用方法
方法 | |
---|---|
T[] values() | 以数组形式返回枚举类型的所有成员 |
T valueOf() | 将普通字符串转换为枚举实例 |
int compareTo() | 比较两个枚举成员在定义时的顺序,结果为负整数、零或正整数,表示当前对象小于、等于或大于指定对象 |
int ordinal() | 获取枚举成员的索引位置 |
7.2.3 枚举示例
定义一个枚举,包括七个枚举常量,表示一周中的七天,遍历枚举常量,并且实现查看每一周的日程安排
枚举类
public enum Week {
//定义枚举常量
MON,TUE,WED,THU,FRI,SAT,SUN;
}
运行类
public class WeekTest {
//主方法
public static void main(String[] args) {
//创建对象
WeekTest wt=new WeekTest();
//遍历枚举
System.out.println("每天日程安排为:");
for (Week wk:Week.values()) {
wt.doWhat(wk);
}
}
/*
* 定义方法,通过枚举常量查看日程安排
* */
public void doWhat(Week day){
switch (day){
case MON:
case TUE:
case WED:
case THU:
case FRI:
System.out.println("工作日,努力写代码!");
break;
case SAT:
System.out.println("星期六,休息!看电影!");
break;
case SUN:
System.out.println("星期日,休息!陪小琳!");
break;
default:
System.out.println("拜托!地球上一星期就七天欸");
}
}
}
7.2.4 枚举的优点
- 可以使代码易于维护,有助于确保为变量指定合法的、期望的值
- 使用枚举赋值,只需要输入 枚举名和 “.” ,就可以显示所有枚举值
- 枚举使代码更加清晰,允许用描述性的数据,使用时更直观方便
7.3 包装类
7.3.1 包装类概念
-
将基本数据类型封装到一个类中,即将基本类型包装成一个类类型
-
Java为每一种基本类型都提供了一个包装类,存于java.lang包中
-
共有八个包装类,关系图如下
-
所有的数字类型都继承了 Number 类,Number 类是一个抽象类
-
包装类和基本数据类的对应关系
类型 | 长度 | 默认值 | 包装类 |
---|---|---|---|
byte | 8位 | 0 | java.lang.Byte |
short | 16位 | 0 | java.lang.Short |
int | 32位 | 0 | java.lang.Integer |
long | 64位 | 0 | java.lang.Long |
float | 32位 | 0.0 | java.lang.Float |
double | 64位 | 0.0 | java.lang.Double |
char | 16位 | \u0000~\uFFFF | java.lang.Character |
boolean | 1位 | false、true | java.lang.Boolean |
7.3.2 包装类和基本数据类型的转换
不同应用场景中,基本数据类型和包装类型间要进行相互转换以完成特定操作
1. 基本数据类型转为包装类
- 使用关键字 new 将一个基本数据类型值包装为一个对象
- 基本数据类型转为包装类语法 :
包装类对象 对象名=new 包装类对象(基本数据)
- 例如,将 int 类型 转为 包装类 Integer 类型的包装类对象
Integer integer=new Integer(32)
2. 包装类转换为基本数据类型
- 一般使用 基本数据类型Value 来转换,如 byteValue()、charValue()等
- 包装类转换为基本数据类型 语法:
基本数据类型 变量名=对象名.基本数据类型Value();
- 例如,将 Integer 类型的包装类对象 转为基本数据类型 int 类型
//定义一个Integer包装类对象
Integer integer=new Integer(32);
//转换为 基本数据类型 int
int num=integer.intValue();
7.3.3 装箱拆箱
- Java 基于数据类型变量和包装类对象之间的转换比较烦琐
- 自动装箱和自动拆箱功能就简化了此操作
- 自动装箱:把基本类型变量直接转换为对应的包装类对象,或者转换为Object类型对象
- 自动拆箱:与装箱相反,将包装类对象转换成对应的基本类型变量
- 示例
public class Test02 {
public static void main(String[] args) {
//基本数据类型变量转换为包装类(装箱)
Integer inObj=5;
Object boolObj=true;
System.out.println(inObj+","+boolObj);
//包装类转换为基本数据类型(拆箱)
int it=inObj;
System.out.println(it);
//如果 boolObj 是否属于包装类 Boolean
if(boolObj instanceof Boolean){
boolean b=(Boolean)boolObj;
System.out.println(b);
}
}
}
7.3 日期类
7.3.1 日期类概述
- JDK1.8中,所有的日期/时间基础类都包含在java.time包中
- 所有关于时间和日期的 API 在使用上都存在缺陷
- 易用性差,时间的计算方式烦琐
- 不是线程安全的
7.3.2 创建日期对象
-
日期和时间都位于 Java.time 包中,提供了一些常用的类
-
在绝大多数情况下,这些类能够有效地处理一些公共的需求
-
java.time 包中表示日期的常用类
类 | 描述 | 示例 |
---|---|---|
LocalDate | 表示日期(年/月/日),不包含时间 | 2020/11/24 |
LocalTime | 表示时间(时/分/秒/毫秒),不包含日期 | 15:32:12 |
LocalDateTime | 表示日期和时间(年/月/日/时/分/秒/毫秒) | 2020/11/24 15:32 |
- LocalDate 类、LocalTime 类、LocalDateTime 类的实例是不可变对象
- 提供简单的日期或时间
- 不包含与时区相关的信息
- 日期类的通用方法
方法 | 描述 |
---|---|
日期类 now() | 据当前时间创建对象返回日期对象 |
日期类 of(int year, Month month, int dayOfMonth) | 根据指定日期/时间创建日期类对象 |
Month getMonth() | 获取月份 |
int getDayOfMonth() | 返回当前日期对象的月份天数 |
int getYear() | 返回当前日期对象的年份 |
日期类 parse (String str) | 根据输入的字符串返回一个日期类对象 |
日期类 plusDays() 日期类 plusWeeks() 日期类 plusMonths() 日期类 plusYears() | 在指定日期上添加天数 在指定日期上添加天数 在指定日期上添加月数 在指定日期上添加年数 |
- 示例
LocalDate localDate=LocalDate.now(); //获取当前日期
LocalTime localTime=LocalTime.now(); //获取当前时间
7.3.3 日期格式化
- 按照定义的格式输出时间,需要使用 DateTimeFormatter 类进行日期格式化处理
- 日期格式化常用方法
方法 | 描述 |
---|---|
ofPattern() | 指定日期格式 使用不同的格式输出当前日期 如"yyyy年MM月dd日 HH:mm:ss" 分别表示年月日时分秒 |
format() | 将日期格式化为字符串 |
- 日期格式化示例
public class Test03 {
public static void main(String[] args) {
//获取当前日期和时间
LocalDateTime localDateTime=LocalDateTime.now();
//指定日期格式
DateTimeFormatter formatter=DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
//将日期和时间格式化为指定的格式
String str=localDateTime.format(formatter);
System.out.println(str);
}
}
- 运行结果
2023年02月16日 10:40:22
7.4 Random类
7.4.1 Random 类概述
- 用于生成随机数
- 位于 java.util 包中
- Random 类的构造方法
方法 | 描述 |
---|---|
Random() | 创建一个随机数生成器 |
Random(long seed) | 使用单个long种子创建一个随机数生成器 |
- Random 类定义了很多获取随机数的方法,最常用的为nextInt() 方法
- nextInt()重载方法
方法 | 描述 |
---|---|
int nextInt(); | 返回下一个伪随机数,它是此随机数生成器序列中均匀分布的int值 |
int nextInt(int n); | 取自此随机数生成的在0(包括)和指定值n(不包括)之间均匀分布的int值 |
7.4.2 Random 类示例
随机生成10个0-20的随机整数
public class Test04 {
public static void main(String[] args) {
Random random=new Random();
for (int i = 1; i <= 10; i++) {
System.out.println("第"+i+"个随机数:"+random.nextInt(20));
}
}
}
运行效果
第1个随机数:10
第2个随机数:16
第3个随机数:5
第4个随机数:18
第5个随机数:10
第6个随机数:7
第7个随机数:15
第8个随机数:3
第9个随机数:2
第10个随机数:2
八、Java I/O
8.1 File 类
File 类可以实现对文件或目录的新建、删除、重命名等操作
8.1.1 File 类概述
- 程序中,一个 File 对象代表一个文件或目录
- File 对象可以查出与文件相关的信息,如名称、修改日期、文件大小等
- 定义 File 类语法
File file对象名 = new File("文件路径")
- File 文件路径的注意点
- 文件路径可以中的路径分隔符可以使用正斜杠 “/”,如"C:/test.txt"
- 同时文件路径分隔符也可以使用反斜杠 “\”, 但是必须写成 “\\”,如"C:\\test.txt"
- 文件路径可以是相对路径和绝对路径
- 绝对路径是以根目录开头的完整路径
- 相对路径是以当前程序的文件的相对位置路径
- File 类的常用方法
方法 | 描述 |
---|---|
boolean createNewFile() | 创建新文件 |
boolean delete() | 删除文件 |
boolean exists() | 判断文件是否存在 |
Boolean isFile() | 判断是否是文件 |
boolean isDirectory() | 判断是否是目录 |
long length() | 返回文件长度,单位为字节若文件不存在,则返回0L |
String getPath() | 返回此对象文件名所对应的路径 |
String getAbsolutePath() | 返回此对象表示的文件的绝对路径名 |
8.1.2 使用File 类的各种方法
完成对 File 对象的基本判断。创建文件对象,判断文件是否存在,输出路径、文件名、长度及判断 File 对象存在内容是否为文件
import java.io.File;
public class Test01 {
public static void main(String[] args) {
//创建文件对象,输入文件相对路径
File file=new File("test.txt");
//创建文件
//createNewFile() 方法必须处理异常,否则无法通过编译
try {
file.createNewFile();
}catch (Exception e){
System.out.println("异常");
}
//判断是否有此文件
System.out.println("文件test.txt是否存在:"+file.exists());
//输出文件名
System.out.println("文件名为:"+file.getName());
//输出文件相对路径
System.out.println("文件相对路径为:"+file.getPath());
//输出文件绝对路径
System.out.println("文件绝对路径为:"+file.getAbsolutePath());
//判断文件是否是目录
System.out.println("文件是否是目录:"+file.isDirectory());
//判断文件是否是文件
System.out.println("文件是否是文件:"+file.isFile());
//输出文件长度
System.out.println("文件长度为:"+file.length());
}
}
运行结果
文件test.txt是否存在:true
文件名为:test.txt
文件相对路径为:test.txt
文件绝对路径为:C:\BeiDaQingNiao\S2\JavaOOP\test.txt
文件是否是目录:false
文件是否是文件:true
文件长度为:21
使用 createNewFile () 创建对象时,此处必须处理异常,否则程序无法通过编译
8.2 I/O 流概述
- I 即 input 指读入操作,读取来自磁盘、光盘、文件等存储设备的数据
- O 即 output 指写出操作,将程序数据存入磁盘、光盘、文件等存储设备中
8.2.1 流
- 一连串流动的字符,是以先进先出的方式发送和接受数据的通道
- 所谓 I/O 流就是实现数据输入和输出的流
- 流的分类
- 按方向划分:输入流和输出流
- 按处理单元划分:字节流和字符流
- 按流的角色划分:节点流和处理流
8.2.2 流的方向划分:输入流和输出流
- 流具有明确的方向性,按着流的流向来分,分为输入流和输出流
- 输入流:只能从中读取数据,不能写入数据的流
- 输出流:只能向其写入数据,而不能从中读数据的流
- 这里的 “出” 和 “入” 一定是从程序运行所在的角度来论述的
- Java 输入流以 InputStream 和 Reader 作为基类(父类)
- Java 输出流以 OutoutSetram 和 Writer 作为基类(父类)
8.2.3 按处理单元划分:字节流和字符流
- 按照处理单元划分,流可以分为字节流和字符流,区别在于操作的数据单元不同
- 字节流:以8位字节为操作数据单元的流,可用于操作二进制数据(如操作图片文件)
- 字符流:以16位的字符为操作数据单元的流,可用于操作文本数据
- 将I/O当作一个水管,水管里的每滴水就像一个单元,即字节或字符
8.2.4 按流的角色划分:节点流或处理流
- 按照流的角色划分,流可以分为节点流和处理流
- 节点流:直接向一个特定的存储介质读写数据的流,使用节点流读写数据时,程序直接连接到数据源
- 处理流:用于对一个已存在的流进行连接和封装,是通过封装后的流实现数据读写的操作的流
-
使用处理流进行读写操作时,程序不会直接连接到实际的数据源,使用处理流包装节点流,可以隐藏底层节点流的差异,并且对外提供更加方便的输入和输出的方法,因此节点流也成为包装流
-
处理流的效率更为高效
8.2.5 字节流和字符流
流按照存储单元分类主要有 字节流 和 字符流 两大类,两类都具有 输入/输出操作
- 字符流:读取文件中的文字信息
- 字节流:读取非文本文件
8.3 字节流
8.3.1 字节输出流基类:OutputStream
- OutputStream 类为抽象类,必须使用该类的子类进行实例化对象
- 操作文件,则使用 FileOutputStream 实例化
- OutputStream 类的主要操作方法
方法 | 描述 |
---|---|
void close() | 关闭输出流 |
void flush() | 刷新缓冲区 |
void write(byte[] b) | 将每个byte数组写入数据流 |
void write(byte[] b,int off,int len) | 将每一个指定范围的byte数组写入数据流 |
void write(int b) | 将一个字节数据写入数据流 |
8.3.2 字节输出流 FileOutputStream 类
- 实际应用中,一般使用 OutputS tream 类的 FileOutputStream 子类向文本文件写入数据
- FileOutputStream常用的构造方法
方法 | 描述 |
---|---|
FileOutputStream( File file) | 创建向指定File对象写数据的文件输出流file:指定目标文件的对象 |
FileOutputStream( String name) | 创建向指定路径的文件写数据的文件输出流name:指定目标文件的路径字符串 |
FileOutputStream( String name, boolean append) | 创建一个向指定路径的文件写入数据的文件输出流name:指定目标文件的路径字符串append:表示是否在文件末尾追加数据。如果为true,则表示可以在文件末尾追加数据 |
字节输出流 FileOutputStream 演示
public class Test02 {
public static void main(String[] args) throws IOException {
//创建File对象
File file=new File("test.txt");
//向指定文件输出数据
FileOutputStream fos=new FileOutputStream(file,true);
//输出数据
String str="Hello Word!";
//将字符串转换为byte数组
byte [] bs=str.getBytes();
//向文件写入数据 从第0个字节开始,到写入字符串的长度
fos.write(bs,0, bs.length);
if (fos!=null){
System.out.println("文件以成功写入");
fos.close();
}else{
System.out.println("文件不存在");
}
}
}
- FileOutputStream的前两种构造方法,都会直接覆盖原文件的内容
- FileOutputStream 类的构造方法创建对象时,如果相应的文件不存在,则会自动创建文件
- 在创建输出流对象、输入数据、关闭流时必须进行异常处理
8.3.3 字节输入流基类:InputStream
- 可以通过 InputStream 类从文件中读取数据,InputStream 类同样为抽象类
- InputStream 同样为抽象类
- InputStream 类定义的主要方法
方法 | 描述 |
---|---|
int read() | 读取一个字节数据,返回字节的阿斯玛值 |
int read(byte[] b) | 将数据读取到字节数组中,返回字节的长度 |
int read(byte[] b,int off,int len) | 从输入流中读取最多len长度的字节,保存到字节数组中,保存的位置从off开始 |
void close() | 关闭输入流 |
int available() | 返回输入流读取的字节数 |
8.3.4 字节输入流 FileInputStream 类
- 实际开发中,通常使用 InputStream 类的 FileInputStream 子类实现文本文件内容的读取
- FileInputStream 类的常用构造方法
方法 | 描述 |
---|---|
FileInputStream(File file) | 用于创建从指定File对象读取数据的文件输入流file:指定目标文件数据源对象 |
FileInputStream( String name) | 用于创建从指定路径的文件读取数据的文件输入流name:指定目标文件数据源路径字符串 |
字节输入流 FileInputStream 演示
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class Test03 {
public static void main(String[] args) throws IOException {
//创建File对象
File file=new File("test.txt");
//创建 输入流对象
FileInputStream fis=new FileInputStream(file);
int data=0;
//如果读取不到数据 则会返回-1
while ((data=fis.read())!=-1){
System.out.print((char)data);
}
fis.close();
}
}
运行结果
//文件内的数据
Hello Word!
I LOVE YOU!
Thank you!!!
Welcome!!!
一次性读取所有数据
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
/*
* FileInputStream
* */
public class Demo03 {
public static void main(String[] args) throws IOException {
//创建 File对象
File file=new File("Demo01.txt");
//创建 字节输入流 对象
FileInputStream fis=new FileInputStream(file);
//创建字节数组 接受文件内的字节
byte [] bs=new byte[1024];
//字节总长度
int len=0;
//将文件内的数据赋给字节数组,再将字节的长度赋给len变量
len=fis.read(bs);
if(len>0){
System.out.println(new String(bs,0,len));
}
}
}
- 使用 read() 方法读取文件中的数据,每次读取一个8位字节,把他转换为0~255的整数返回
- 可以将字节强制转换为 char类型
- read() 方法读取文件中的数据,当返回结果为-1时,即输入流已经读到末尾
- 与字节输出流用法相同,在创建输入流对象、读取数据、关闭流时必须进行异常处理
8.4 字符流
8.4.1 字符输出流基类:Writer
- Writer 类是字符输出流的抽象基类
- Writer 类中定义的常用方法
方法 | 描述 |
---|---|
void write(String str) | 将str字符串中包含的字符输出到输出流中 |
void write(String str, int off, int len) | 将字符串中从off位置开始,长度为len的字符输出到输出流中 |
void close() | 关闭输出流 |
void flush() | 刷新输出流 |
8.4.2 字符输出流 FileWriter 类
-
FileWriter 类是 Writer 类常用的子类
-
FileWriter 类可以以字符为数据处理单元向文本中写数据
-
FileWriter 类演示
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
public class Test04 {
public static void main(String[] args) throws IOException {
//创建 File对象
File file=new File("test.txt");
//创建字符输出流对象
FileWriter fw=new FileWriter(file,true);
//写入数据
try {
fw.write("你好");
//刷新缓冲区
fw.flush();
System.out.println("文件写入成功");
}catch (Exception e){
System.out.println("文件不存在");
}finally {
if (fw!=null){
//如果字符输出流对象不为空,那么关闭字符输出流对象
fw.close();
}
}
}
}
- 使用 flush() 方法清空流。
- 将数据写入文件的过程中,会有一些残留的数据没有被写入,这是就需要使用 flush()方法清空输出流 FileWriter,可以强制将其中残余的数据写入文本文件中
8.4.3 字符输入流基类:Reader
- Rader 类是字符输入流的基类,它是抽象类
- Reader 类中的常用方法
方法 | 描述 |
---|---|
int read() | 从输入流中读取单个字符,返回所读取的字符数据 |
int read(char[] c) | 从输入流中最多读取c.length个字符数据,并将其存储在字符数组c中,返回实际读取的字符数 |
int read(char[] c, int off, int len) | 从输入流中最多读取len个字符的数据,并将其存储在字符数组c中存入数组c中时,并不是从数组起点开始,而是从off位置开始,返回实际读取的字符数 |
8.4.4 字符输入流 FileReader
- Reader 类同样是抽象类,可以使用其子类 FileReader 创建对象
- FileReader 可以实现读取文件中的数据
- FileReader 演示
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class Test05 {
public static void main(String[] args) throws IOException {
//创建 File 对象
File file=new File("test.txt");
//创建 字符输入流对象
FileReader fr=new FileReader(file);
int data=0;
//接受数据
char [] cs=new char[1024];
while((data=fr.read(cs))>0){
System.out.print(new String(cs,0,data));
}
//关闭字符输入流对象
fr.close();
}
}
运行结果
//文件内的数据
Hello Word!
I LOVE YOU!
Thank you!!!
Welcome!!!你好
8.5 缓冲流
- 前面所介绍的都属于 I/O 流中的节点流,可以之间与文件进行数据交互
- 此外,Java.io 包还提供了缓冲流,缓冲流处于处理流
- 接收输出输入流对象,对数据进行处理
8.5.1 字符缓冲输出流 BufferedWriter类
- BufferedWriter 类 Writer 类的子类
- BufferedWriter 类可以把一批数据写到缓冲区
- 当缓冲区满的时候,才会把数据写到目的地,可以减少物理写数据的次数,从而提高 I/O操作的执行效率
- BufferedWriter 类的常用的构造方法
方法 | 描述 |
---|---|
BufferedWriter(Writer out) | 创建一个缓冲字符输出流 |
- 演示代码
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
public class Test06 {
public static void main(String[] args) throws IOException {
//创建文件对象
File file=new File("test.txt");
//创建字符输出流对象
FileWriter fw=new FileWriter(file);
//创建输出缓冲流
BufferedWriter bw=new BufferedWriter(fw);
//向文件中写入数据
bw.write("亲爱的可爱鬼:");
//换行
bw.newLine();
//刷新缓冲区
bw.flush();
//关闭缓冲区
bw.close();
//关闭字符输出流
fw.close();
}
}
8.5.2 字符缓冲输入流 BufferedReader 类
-
BufferedReader 类是 Reader 类的子类
-
BufferedReader 类与 FileReader 类的区别在于,BufferedReader 类有缓冲区,可以先把一批数据放入缓冲区
-
读取的操作都是从缓冲区内获取,避免每次都从数据源读取数据进行字符编码转换
-
BufferedReader 类常用的构造方法
方法 | 描述 |
---|---|
BufferedReader(Reader in) | 创建一个缓冲字符输入流 |
- 演示代码
import java.io.*;
public class Test07 {
public static void main(String[] args) throws IOException {
//创建 File 对象
File file=new File("test.txt");
//创建字符输入流 对象
FileReader reader=new FileReader(file);
//创建字符缓冲输入流对象
BufferedReader br=new BufferedReader(reader);
//获取数据
String line=br.readLine();
while(line!=null){
System.out.println(line);
line=br.readLine();
}
//关闭字符缓冲输入流
br.close();
//关闭字符输入流
reader.close();
}
}
8.6 数据操作流
- 实现对二进制文本的读写操作
- 使用数据操作流 DataOutputStream 和 DataInputStream进行操作
8.6.1 DataOutputStream 类和 DataInputStream 类
1、分类
- DataOutputStream类
- OutputStream类的子类
- OutputStream类的子类
- 使用DataOutputStream类写二进制文件的实现步骤与FileOutputStream类写文本文件相似
- DataInputStream类
- InputStream类的子类
- 与FileInputStream类结合使用读取二进制文件
- 使用DataInputStream类读取二进制文件的实现步骤与FileInputStream类读取文本文件相似
2、DataOutputStream类
- 按照与平台无关的方式向流中写入基本数据类型的数据
- 演示代码
FileOutputStream out1 = new FileOutputStream("D:\\doc\\test.txt");
BufferedOutputStream out2 = new BufferedOutputStream(out1);
DataOutputStream out = new DataOutputStream(out2);
out.writeByte(1);
out.writeLong(2);
out.writeChar('c');
out.writeUTF("hello");
3、DataInputStream类
- 按照与平台无关的方式从流中读取基本数据类型的数据
- 演示代码
FileInputStream in1 = new FileInputStream("D:\\doc\\test.txt");
BufferedInputStream in2 = new BufferedInputStream(in1);
DataInputStream in = new DataInputStream(in2);
System.out.println(in.readByte());
System.out.println(in.readLong());
System.out.println(in.readChar());
System.out.println(in.readUTF());
8.7 序列化与反序列化
- 序列化就是将对象的状态更轻松简单的存储到特定的存储介质中的过程,将对象状态转换为可保持或可传输格式的过程
- 反序列话是将特定的存储介质中的数据重新构建成对象的过程,通过反序列化后得到相同的对象
- 序列化和反序列化的过程
8.7.1 对象输出流 ObjectOutputStream 实现序列化
- 对象的序列化,是把一个对象变为二进制的数据流的一种方法
- 如果一个类的对象需要被序列化,必须实现 java.IO.Serializable 接口
- 定义接口: public interf Serializable()
- 此接口中没有任何方法,他代表一个标识接口,一旦实现了此接口,代表该类的对象可以被序列化
- ObjectOutputStream 类的常用方法
方法 | 描述 | 类型 |
---|---|---|
ObjectOutputStream(OutputStream out) | 创建对象输出流对象 | 构造方法 |
final void writeObject(Object obj) | 将指定对象写入流 | 实例方法 |
- 代码演示
Person 类
import java.io.Serializable;
public class Person implements Serializable{
private String name;
private int age;
private String sex;
public void print(){
System.out.println("姓名:"+name);
System.out.println("年龄:"+age);
System.out.println("性别:"+sex);
}
public Person(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public Person() {
}
}
运行类
import java.io.*;
public class Test08 {
public static void main(String[] args) throws IOException {
//创建文件对象
File file=new File("test.txt");
//创建字节输出流
FileOutputStream ops=new FileOutputStream(file);
//创建对象输出流
ObjectOutputStream oos=new ObjectOutputStream(ops);
//创建Person 对象
Person person=new Person("艾琳",17,"男");
//向文件夹写入数据
oos.writeObject(person);
System.out.println("序列化成功");
//关闭
oos.close();
ops.close();
}
}
向文件添加后的数据
�� sr CH09.Test08.Person�MjԿRw I ageL namet Ljava/lang/String;L sexq ~ xp t 艾琳t 男
-
内容全部是二进制数据,不可直接修改文件本身,否则会造成数据丢失
-
当需要保存多个对象时,可以使用集合对象,最后将对象写入文件中
运行类2
import java.io.*;
import java.util.ArrayList;
public class Tets008 {
public static void main(String[] args) throws IOException {
//创建文件对象
File file=new File("test2.txt");
//创建输出流对象
FileOutputStream ops=new FileOutputStream(file);
//创建对象输出流对象
ObjectOutputStream oop=new ObjectOutputStream(ops);
//创建对象
Person p1=new Person("艾米",21,"女");
Person p2=new Person("艾伦",21,"男");
Person p3=new Person("杰克",24,"男");
//创建对象集合
ArrayList<Person> list=new ArrayList<Person>();
list.add(p1);
list.add(p2);
list.add(p3);
//向文件写入数据
oop.writeObject(list);
System.out.println("序列化成功");
//关闭
ops.close();
oop.close();
}
}
8.7.2 对象输入流 ObjectInputStream 实现反序列化
- 反序列化就是与序列化相反的操作,从特定存储介质中读取数据并重新构建成对象的过程
- 执行反序列化操作需要使用对象输出流ObjectInputStream,可以直接把序列化后的对象还原
- ObjectInputStream 类的常用方法
方法 | 描述 | 类型 |
---|---|---|
ObjectInputStream(InputStream in) | 创建对象输入流对象 | 构造方法 |
final Object readObject() | 从指定位置读取对象 | 实例方法 |
- 演示
读取文件内序列化的对象
import java.io.*;
public class Test0008 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//创建文件对象
File file=new File("test.txt");
//创建 字节输入流对象
FileInputStream fis=new FileInputStream(file);
//创建 对象输入流对象
ObjectInputStream ois=new ObjectInputStream(fis);
//读取数据
Person person=(Person)ois.readObject();
person.print();
//关闭
fis.close();
ois.close();
}
}
运行结果
姓名:艾琳
年龄:17
性别:男
读取文件内序列化的对象集合
import java.io.*;
import java.util.ArrayList;
import java.util.Iterator;
public class Test00008 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//创建文件对象
File file=new File("test2.txt");
//创建 字节输入流
FileInputStream fis=new FileInputStream(file);
//创建 对象输入流
ObjectInputStream ois=new ObjectInputStream(fis);
//读取文件
ArrayList<Person> list=(ArrayList<Person>)ois.readObject();
//遍历集合
Iterator iterator=list.iterator();
while(iterator.hasNext()){
System.out.println("*****");
Person p=(Person)iterator.next();
p.print();
}
fis.close();
ois.close();
}
}
运行结果
*****
姓名:艾米
年龄:21
性别:女
*****
姓名:艾伦
年龄:21
性别:男
*****
姓名:杰克
年龄:24
性别:男
- 当对象中有某些属性不需要被序列化,可以使用 transient 关键字
- transient 语法:
private transient int age;
8.8 Java 体系中常用流的分类
分类 | 字节输出流 | 字节输入流 | 字符输出流 | 字符输入流 |
---|---|---|---|---|
基类 | OutputStream | InputStream | Writer | Reader |
文件流 | FileOutputSteam | FileInputStream | FileWriter | FileReader |
缓冲流 | BufferedOutputStream | BufferedInputStream | BufferedWriter | BufferedReader |
对象流 | ObjectOutputStream | ObjectInputStream | - | - |
数据操作流 | DataOutputStream | DataInputStream | - | - |
九、多线程
9.1 多线程概述
- 充分的利用计算机资源,执行不同的操作
- 同时运行多个独立的任务,每个任务对应一个进程,每个进程可产生多个线程
9.1.1 进程与线程
1. 进程
- 是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间
- 有独立的内存空间和系统资源
- 程序就是进程
2. 线程
-
CPU调度和分派的基本单位
-
是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程
-
线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。
-
进程中执行运算的最小单位,可完成一个独立的顺序控制流程
3. 进程与线程的关系
9.1.2 多线程的运行机制
- 多线程是指一个进程同时运行了多个线程,并发执行的方式,来完成不同的工作
- 多线程是并发运行的,而非并行运行,多个线程交替执行,并非同时执行
- 多个线程交替占用CPU资源,而非真正的并行执行
9.1.3 多线程的优势
1.充分利用CPU资源,在CPU处于空闲状态时,运行其他线程,提高资源利用率
- 简化编程模式,将一个复杂的进程分为多个线程,每个线程实现简单的流程,简化程序逻辑,方便编码和维护
- 用户良好体验。多个线程交替运行,减少或避免了程序阻塞或意外情况发生造成的响应时间过慢现象,减少用户等待时间,提升用户体验
9.2 多线程编程
- Java 语言提供了 java.lang.Thread 类支持多线程编码
9.2.1 Thread 类介绍
- Thread 类提供了大量的方法来控制和操作线程
方法 | 描述 | 类型 |
---|---|---|
Thread() | 创建Thread对象 | 构造方法 |
Thread(Runnable target) | 创建Thread对象,target为run()方法被调用的对象 | 构造方法 |
Thread(Runnable target , String name) | 创建Thread对象,target为run()方法被调用的对象,name为新线程的名称 | 构造方法 |
void run() | 执行任务操作的方法 | 实例方法 |
void start() | 使该线程开始执行,JVM将调用该线程的run()方法 | 实例方法 |
void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),需要异常处理 | 静态方法 |
Thread currentThread() | 返回当前线程对象的引用 | 静态方法 |
-
在 Java 程序启动时,一个线程立即随之启动,这个线程称之为主线程
- main 方法就是主线程入口
- 主线程是产生其他子线程的线程
- 主线程通常必须最后完成执行,因为他执行各种关闭动作
-
使用Thread 类的方法获取主线程信息
public class Test01 {
public static void main(String[] args) {
//获取线程信息
Thread t=Thread.currentThread();
//获取当前线程名字
System.out.println("当前线程:"+t.getName());
//修改线程名
t.setName("MainThread");
System.out.println("当前线程:"+t.getName());
}
}
- 运行结果
当前线程:main
当前线程:MainThread
- Java 语言中,实现多线程分两种方式
- 继承 Thread 类
- 实现 Runnable 接口
9.2.2 继承 Thread 类创建线程类
- 继承 Thread 类实现线程,自定义线程类时,必须满足如下要求
- 此类必须继承 Thread 类
- 重写 Thread 类的 run() 方法,将线程执行的代码下载 run() 方法中
- 线程从 run() 方法开始执行,它是线程执行的起点
- 线程对象调用 start() 方法时启动线程,会自动调用 run()方法
- 实际运行中应调用 start() 方法
- 演示代码
创建自定义线程
/*
*创建自定义线程
*/
public class MyThread extends Thread{
//设置线程名字
public MyThread(String name){
super.setName(name);
}
//重写 Thread 类的 run() 方法
@Override
public void run() {
//执行线程任务的代码
for (int i = 0; i < 25; i++) {
System.out.println("我是线程"+getName()+"==>"+i);
}
}
}
线程测试类
/*
*线程测试类
*/
public class Test {
public static void main(String[] args) {
//线程1
MyThread t1=new MyThread("线程1");
//使用 start() 方法启动线程
t1.start();
//线程2
MyThread t2=new MyThread("线程2");
t2.start();
}
}
运行结果
我是线程线程2==>0
我是线程线程1==>0
我是线程线程2==>1
我是线程线程1==>1
我是线程线程2==>2
我是线程线程2==>3
我是线程线程2==>4
我是线程线程2==>5
我是线程线程2==>6
我是线程线程2==>7
我是线程线程2==>8
我是线程线程2==>9
我是线程线程2==>10
我是线程线程2==>11
我是线程线程2==>12
我是线程线程2==>13
我是线程线程2==>14
我是线程线程2==>15
我是线程线程2==>16
我是线程线程2==>17
我是线程线程2==>18
我是线程线程2==>19
我是线程线程2==>20
我是线程线程2==>21
我是线程线程2==>22
我是线程线程2==>23
我是线程线程2==>24
我是线程线程1==>2
我是线程线程1==>3
我是线程线程1==>4
我是线程线程1==>5
我是线程线程1==>6
我是线程线程1==>7
我是线程线程1==>8
我是线程线程1==>9
我是线程线程1==>10
我是线程线程1==>11
我是线程线程1==>12
我是线程线程1==>13
我是线程线程1==>14
我是线程线程1==>15
我是线程线程1==>16
我是线程线程1==>17
我是线程线程1==>18
我是线程线程1==>19
我是线程线程1==>20
我是线程线程1==>21
我是线程线程1==>22
我是线程线程1==>23
我是线程线程1==>24
- 已启动的线程不能重复调用 start() 方法,否则会抛出异常
- sleep() 方法在 run() 方法内使用,用来控制程序线程的休眠时间,sleep() 方法必须进行异常处理
注意 线程对象调用 start() 方法和 run() 方法截然不同,前者是启动线程,后者是调用实例方法,在实际应用中切勿混淆
9.2.3 实现 Runnable 接口创建线程类
- 使用 Thread 类的方式创建线程,子类无法在继承其他父类
- 可以通过实现 Runnable 接口的方式创建线程
- Runnable 接口只有一个抽象类 run ,其他方法都要借助于 Thread 类
- 演示代码
实现 Runnable 接口方式创建线程类
/*
* 实现 Runnable 接口方式创建线程类
* */
public class MyRunnable implements Runnable{
@Override
public void run() {
//线程内容
}
}
测试类
/*
* 测试类
* */
public class Test03 {
public static void main(String[] args) {
MyRunnable mr=new MyRunnable();
//通过 Thread 类创建线程对象
Thread t=new Thread(mr);
//启动线程
t.start();
}
}
9.3 线程的转换状态
- 新建的线程通常会在五种状态中转换: 新建、就绪、运行、阻塞、死亡
- 五种状态组成了线程的生命周期
9.3.1 线程的生命周期
线程的转换状态
(1)新建状态: 一个 Thread 类或子类的对象被声明并创建,此对象已经分配了内存空间和资源,但是还未被调度
**(2)就绪状态:**也称为可运行状态,也就是调用 start() 方法后,此时已经具备了运行的条件,但是还未被运行,进入线程列队,等待使用 CPU 资源
**(3)运行状态:**当就绪状态被调度并且获得处理器资源后便进入运行状态,表示线程正在运行,拥有了 CPU 的占用权,如果线程不让出 CPU 的控制权,则会一直运行完毕
让出 CPU 控制权的情况
- 线程运行完毕
- 有比当前线程优先级更高的线程抢占了 CPU
- 线程休眠
- 线程因等待某个资源而处于阻塞状态
**(4)阻塞状态:**一个正在运行的线程因某种特殊情况需要让出 CPU 并暂停时终止运行,线程处于不可运行的状态被称为阻塞状态,线程当被阻塞时不能进入就绪状态的排队队列,只有当阻塞的运用被取消时,线程才可以转为就绪状态
**(5)死亡状态:**一个线程的 run() 方法运行完毕,表示该线程已死亡,死亡状态的线程将不具备运行能力。
导致线程死亡的原因
- 正常运行的线程完成了全部工作,即运行完 run() 方法的最后一条语句
- 当进程停止运行时,该进程中的线程将被强行终止
9.4 线程调度相关方法
9.4.1 常用的线程操作方法
方 法 | 说 明 |
---|---|
int getPriority() | 返回线程的优先级 |
void setPrority(int newPriority) | 更改线程的优先级 |
boolean isAlive() | 测试线程是否处于活动状态 |
void join() | 进程中的其它线程必须等待该线程终止后才能执行,需要异常处理 |
void interrupt() | 中断线程 |
void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
9.4.2 线程的优先级
- 每个线程运行时都具有一定的优先级,优先级高的线程获得较多的运行机会
- 每个线程的默认优先级是相同的
- 默认情况下,主线程 main 具有普通优先级
- Thread 类 提供了 getPriority() 方法设置线程的优先级,范围是1~10
- 也可以使用Thread 类的三个静态常量设置线程的优先级
- MAX_PRIORITY:值是10,表示优先级最高
- MIN_PRIORITY:值是1,表示优先级最低
- NORM_PRIORITY:值是5,表示普通优先级
- 代码演示
使用 实现 Runnable 接口创建多线程
/*
* 使用 实现 Runnable 接口创建多线程
* */
public class MyRunnable implements Runnable{
@Override
public void run() {
//输出0-25
for (int i = 0; i <=25 ; i++) {
System.out.println("我是"+Thread.currentThread().getName()+":"+i);
}
}
}
测试类
/*
* 测试类
* */
public class Test {
public static void main(String[] args) {
//通过构造方法设置多线程名称
Thread t1=new Thread(new MyRunnable(),"线程1");
Thread t2=new Thread(new MyRunnable(),"线程2");
Thread t3=new Thread(new MyRunnable(),"线程3");
//设置优先级
//优先级最高 10
t1.setPriority(Thread.MAX_PRIORITY);
//优先级最低 1
t2.setPriority(Thread.MIN_PRIORITY);
//普通优先级 5
t3.setPriority(Thread.NORM_PRIORITY);
//启动线程
t1.start();
t2.start();
t3.start();
}
}
9.4.2 线程的强制运行
- 在线程操作中,可以使用 join() 方法 让一个线程强制运行
- 强制运行期间,其他线程无法运行,必须等此线程运行完毕才可以继续运行
- join() 方法需要进行异常处理 InterruptedExcpetion 异常
- 代码演示
线程类
/*
* 线程类
* */
public class MyThread extends Thread {
public MyThread(String name){
super.setName(name);
}
@Override
public void run() {
for (int i = 0; i <= 25; i++) {
System.out.println("我是"+getName()+":"+i);
}
}
}
测试类
/*
* 测试类
* */
public class Test {
public static void main(String[] args) {
Thread t=new MyThread("线程1");
//子线程 线程1输出
t.start();
try {
//强制运行线程1
t.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//主线程 main 输出
for (int i = 0; i <= 25; i++) {
System.out.println("我是"+Thread.currentThread().getName()+":"+i);
}
}
}
运行结果
我是线程1:0
我是线程1:1
我是线程1:2
我是线程1:3
我是线程1:4
我是线程1:5
我是线程1:6
我是线程1:7
我是线程1:8
我是线程1:9
我是线程1:10
我是线程1:11
我是线程1:12
我是线程1:13
我是线程1:14
我是线程1:15
我是线程1:16
我是线程1:17
我是线程1:18
我是线程1:19
我是线程1:20
我是线程1:21
我是线程1:22
我是线程1:23
我是线程1:24
我是线程1:25
我是main:0
我是main:1
我是main:2
我是main:3
我是main:4
我是main:5
我是main:6
我是main:7
我是main:8
我是main:9
我是main:10
我是main:11
我是main:12
我是main:13
我是main:14
我是main:15
我是main:16
我是main:17
我是main:18
我是main:19
我是main:20
我是main:21
我是main:22
我是main:23
我是main:24
我是main:25
由此可见,代码是先运行完 “线程1” 之后才开始运行 主线程的
9.4.3 线程的礼让
- 当程序运行中执行了 Thread 类的 yield() 静态方法 后,系统将会选择其他相同或优先级更高的线程运行
- 执行Thread 类的 yield() 静态方法会优先让其他线程运行,但不会停止自己的线程,不能保证一定会实现礼让
- 代码演示
线程类
/*
* 线程类
* */
public class MyThread extends Thread{
public MyThread(String name){
super.setName(name);
}
@Override
public void run() {
for (int i = 0; i <=25; i++) {
System.out.println("我是"+getName()+"==>"+i);
//如果是5的倍数 则让线程礼让
if(i%5==0){
Thread.yield();
}
}
}
}
测试类
/*
* 测试类
* */
public class Test06 {
public static void main(String[] args) {
MyThread t1=new MyThread("线程1");
t1.start();
//主线程
for (int i = 0; i <=25; i++) {
System.out.println("我是"+Thread.currentThread().getName()+"==>"+i);
//如果是5的倍数 则让线程礼让
if(i%5==0){
Thread.yield();
}
}
}
}
运行结果
我是main==>0
我是线程1==>0
我是main==>1
我是main==>2
我是main==>3
我是main==>4
我是线程1==>1
我是线程1==>2
我是线程1==>3
我是main==>5
我是线程1==>4
我是线程1==>5
我是main==>6
我是main==>7
我是main==>8
我是main==>9
我是main==>10
我是线程1==>6
我是线程1==>7
我是线程1==>8
我是线程1==>9
我是线程1==>10
我是线程1==>11
我是线程1==>12
我是线程1==>13
我是main==>11
我是线程1==>14
我是线程1==>15
我是线程1==>16
我是线程1==>17
我是线程1==>18
我是线程1==>19
我是线程1==>20
我是线程1==>21
我是线程1==>22
我是线程1==>23
我是线程1==>24
我是线程1==>25
我是main==>12
我是main==>13
我是main==>14
我是main==>15
我是main==>16
我是main==>17
我是main==>18
我是main==>19
我是main==>20
我是main==>21
我是main==>22
我是main==>23
我是main==>24
我是main==>25
每当到5的倍数时,线程就有可能礼让,让其他线程运行
9.5 线程同步
9.5.1 为什么需要线程同步
- 线程都是独立且异步运行的,每个线程都包含了运行时所需要的数据和方法,不必关系其他线程的状态和行为
- 当两个线程需要操作共同的数据时,就要考虑其他线程的状态和行为
- 当两个线程运行条件都需要同一个资源时,多个线程操作同一共享资源时,则会带来数据不安全问题的原因
- 需要使用线程同步技术来解决,多个线程操作同一共享资源时,将引发数据不安全问题
9.5.2 实现线程的同步
- 当两个或多个线程需要访问同一个资源时,需要以某种顺序来确保该资源一时刻只能被一个线程使用
- 相当于为资源加上了一把锁,一旦被一个线程调用,别的线程将无法调用
- 实现线程同步有两种方法,同步代码块和同步方法
(1)同步代码块
-
使用 synchronized 关键字修饰的代码块被称为同步代码块
-
语法
//obj 为指定上锁的对象
public synchronized(obj){
//需要同步的代码块
}
- 不添加同步,模拟银行取钱,代码演示
账户类
/*
* 账户类
* */
public class User {
//余额
private double balance;
public User(double balance){
this.balance=balance;
}
//取款方法
public void getMoney(){
if(balance<=0){
System.out.println("没钱了");
return;
}
System.out.println("账户余额:"+this.balance);
this.balance-=1000;
System.out.println("取款1000,剩余余额:"+this.balance);
}
}
线程类
/*
* 线程类
* */
public class MyThread extends Thread{
//账户类
User user;
//确保是同一个账户
public MyThread(User user){
this.user=user;
}
@Override
public void run() {
//去取方法
this.user.getMoney();
}
}
测试类
/*
* 测试类
* */
public class Test07 {
public static void main(String[] args) {
User user=new User(1000);
//线程1
MyThread t1=new MyThread(user);
MyThread t2=new MyThread(user);
t1.start();
t2.start();
}
}
运行结果
账户余额:1000.0
账户余额:1000.0
取款1000,剩余余额:-1000.0
取款1000,剩余余额:0.0
-
由此可见,两个线程是同时调用资源的,当余额小于等于0时,并没有执行if内的操作,因为两个线程同时执行时,余额还没有被减少
-
添加同步代码块,模拟银行取钱,代码演示
账户类
/*
* 账户类
* */
public class User {
//余额
private double balance;
public User(double balance){
this.balance=balance;
}
//取款方法
public void getMoney(){
//表示 当有线程调用此方法时,锁定该类的状态,不能被其他线程所修改
synchronized(this){
if(balance<=0){
System.out.println("没钱了");
return;
}
System.out.println("账户余额:"+this.balance);
this.balance-=1000;
System.out.println("取款1000,剩余余额:"+this.balance);
}
}
}
线程类
/*
* 线程类
* */
public class MyThread extends Thread{
//账户类
User user;
//确保是同一个账户
public MyThread(User user){
this.user=user;
}
@Override
public void run() {
//去取方法
this.user.getMoney();
}
}
测试类
/*
* 测试类
* */
public class Test07 {
public static void main(String[] args) {
User user=new User(1000);
//线程1
MyThread t1=new MyThread(user);
MyThread t2=new MyThread(user);
t1.start();
t2.start();
}
}
运行结果
账户余额:1000.0
取款1000,剩余余额:0.0
没钱了
synchronized(this) 中 this 关键字引用的是当前对象,如果当前对象没有被其他线程所占用,则会开始执行synchronized(this) 关键字大括号内的同步代码
(2)同步方法
- 如果一个方法内的所有代码都需要被同步,则可以之间使用 synchronized 关键字来修饰整个方法
- 语法
访问修饰符 synchronized 返回值类型 方法名(参数列表){
//方法体
}
- 演示代码
代码和上方的一致,这边之间修改账户类内的方法
/*
* 账户类
* */
public class User {
//余额
private double balance;
public User(double balance){
this.balance=balance;
}
//取款方法
public synchronized void getMoney(){
//表示 当有线程调用此方法时,锁定该类的状态,不能被其他线程所修改
if(balance<=0){
System.out.println("没钱了");
return;
}
System.out.println("账户余额:"+this.balance);
this.balance-=1000;
System.out.println("取款1000,剩余余额:"+this.balance);
}
}
9.5.3 线程同步的特征
- 不同的线程在执行以同一个对象作为锁标记的同步代码块或同步方法时,因为要获得这个对象的锁而相互牵制
- 多个并发线程访问同一资源的同步代码块或同步方法时,同一刻只能有一个线程运行
- 同一时刻只能有一个线程进入synchronized(this)同步代码块
- 当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定
- 当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码
- 如果多个线程访问的不是同一共享资源,无需同步
9.5.4 线程安全的类型
- 线程的安全类型
方法是否同步 | 效率比较 | 适合场景 | |
---|---|---|---|
线程安全 | 是 | 低 | 多线程并发共享资源 |
非线程安全 | 否 | 高 | 单线程 |
- 如果程序所在的进程中,有多个线程同时运行,每次运行结果和单线程时运行结果是一样的,且其他变量的值也和预期相同,则当前程序是线程安全的
- Arraylist 和 HashMap 是非线程安全
- StringBuffer 为线程安全
<=0){
System.out.println(“没钱了”);
return;
}
System.out.println(“账户余额:”+this.balance);
this.balance-=1000;
System.out.println(“取款1000,剩余余额:”+this.balance);
}
}
线程类
~~~java
/*
* 线程类
* */
public class MyThread extends Thread{
//账户类
User user;
//确保是同一个账户
public MyThread(User user){
this.user=user;
}
@Override
public void run() {
//去取方法
this.user.getMoney();
}
}
测试类
/*
* 测试类
* */
public class Test07 {
public static void main(String[] args) {
User user=new User(1000);
//线程1
MyThread t1=new MyThread(user);
MyThread t2=new MyThread(user);
t1.start();
t2.start();
}
}
运行结果
账户余额:1000.0
账户余额:1000.0
取款1000,剩余余额:-1000.0
取款1000,剩余余额:0.0
-
由此可见,两个线程是同时调用资源的,当余额小于等于0时,并没有执行if内的操作,因为两个线程同时执行时,余额还没有被减少
-
添加同步代码块,模拟银行取钱,代码演示
账户类
/*
* 账户类
* */
public class User {
//余额
private double balance;
public User(double balance){
this.balance=balance;
}
//取款方法
public void getMoney(){
//表示 当有线程调用此方法时,锁定该类的状态,不能被其他线程所修改
synchronized(this){
if(balance<=0){
System.out.println("没钱了");
return;
}
System.out.println("账户余额:"+this.balance);
this.balance-=1000;
System.out.println("取款1000,剩余余额:"+this.balance);
}
}
}
线程类
/*
* 线程类
* */
public class MyThread extends Thread{
//账户类
User user;
//确保是同一个账户
public MyThread(User user){
this.user=user;
}
@Override
public void run() {
//去取方法
this.user.getMoney();
}
}
测试类
/*
* 测试类
* */
public class Test07 {
public static void main(String[] args) {
User user=new User(1000);
//线程1
MyThread t1=new MyThread(user);
MyThread t2=new MyThread(user);
t1.start();
t2.start();
}
}
运行结果
账户余额:1000.0
取款1000,剩余余额:0.0
没钱了
synchronized(this) 中 this 关键字引用的是当前对象,如果当前对象没有被其他线程所占用,则会开始执行synchronized(this) 关键字大括号内的同步代码
(2)同步方法
- 如果一个方法内的所有代码都需要被同步,则可以之间使用 synchronized 关键字来修饰整个方法
- 语法
访问修饰符 synchronized 返回值类型 方法名(参数列表){
//方法体
}
- 演示代码
代码和上方的一致,这边之间修改账户类内的方法
/*
* 账户类
* */
public class User {
//余额
private double balance;
public User(double balance){
this.balance=balance;
}
//取款方法
public synchronized void getMoney(){
//表示 当有线程调用此方法时,锁定该类的状态,不能被其他线程所修改
if(balance<=0){
System.out.println("没钱了");
return;
}
System.out.println("账户余额:"+this.balance);
this.balance-=1000;
System.out.println("取款1000,剩余余额:"+this.balance);
}
}
9.5.3 线程同步的特征
- 不同的线程在执行以同一个对象作为锁标记的同步代码块或同步方法时,因为要获得这个对象的锁而相互牵制
- 多个并发线程访问同一资源的同步代码块或同步方法时,同一刻只能有一个线程运行
- 同一时刻只能有一个线程进入synchronized(this)同步代码块
- 当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定
- 当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码
- 如果多个线程访问的不是同一共享资源,无需同步
9.5.4 线程安全的类型
- 线程的安全类型
方法是否同步 | 效率比较 | 适合场景 | |
---|---|---|---|
线程安全 | 是 | 低 | 多线程并发共享资源 |
非线程安全 | 否 | 高 | 单线程 |
- 如果程序所在的进程中,有多个线程同时运行,每次运行结果和单线程时运行结果是一样的,且其他变量的值也和预期相同,则当前程序是线程安全的
- Arraylist 和 HashMap 是非线程安全
- StringBuffer 为线程安全
- StringBuilder 为非线程安全