Java-SE进阶
2023-04-13 19:05:12 0 举报
AI智能生成
Java-SE进阶是Java编程语言的高级学习阶段,主要涉及Java核心API、多线程、网络编程、数据库连接、数据结构与算法等深入主题。在这个阶段,开发者将掌握更复杂的编程技巧,如泛型、注解、反射、JVM调优等,以实现高性能、可扩展和安全的应用程序。此外,Java-SE进阶还包括对Java生态系统的深入了解,如常用的开源框架(如Spring、Hibernate等)和工具(如Maven、Gradle等)。通过Java-SE进阶的学习,开发者将能够编写出更复杂、更具挑战性的Java应用程序,为进入Java企业级开发领域奠定坚实基础。
作者其他创作
大纲/内容
javaSE基础
基础
面向对象
三大特征
封装(⭐⭐⭐⭐⭐)
继承 (⭐⭐⭐⭐⭐)
继承关键字: extends
例
public class Person { // 父类 超类 基类
public int a;
public void show(){
System.out.println("helloworld");
}
}
public class Student extends Person { // 子类 派生类
}
public class Demo {
public static void main(String[] args){
Student stu = new Student();
System.out.println(stu.a); //0
stu.show(); // 子类继承了父类之后 就可以使用父类的 (自己有权限访问的)内容
}
}
注意事项:子类继承父类 不能访问权限不足的内容
子类可以继承父类的私有成员(成员变量,方法),只是子类无法直接访问而已,可以通过getter/setter方法访问父类的private成员变量
继承的好处和弊端:
好处:
提高代码的复用性
弊端:
开发中设计程序的原则: 高内聚 低耦合
继承的特点:
java是单继承的,不支持多继承
永远使用最近的那一个 就近原则
父类不能用子类的方法
子类不能继承父类的构造方法
一个类可以有多个子类
可以多层继承
继承中成员特点
成员变量
成员变量不重名
如果子类父类中出现不重名的成员变量,这时的访问是没有影响的
成员变量重名
子父类中出现了同名的成员变量时,子类会优先访问自己对象中的成员变量
可以使用super关键字访问父类成员变量
super访问父类成员变量
使用格式:
super.父类成员变量名
super就不允许打印 因为super不是对象。
super在哪个类里面, 就指的是这个类的父类
成员方法
成员方法不重名
如果子类父类中出现不重名的成员方法,这时的调用是没有影响的。
对象调用方法时,会先在子类中查找有没有对应的方法,
若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。
对象调用方法时,会先在子类中查找有没有对应的方法,
若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。
成员方法重名
如果子类父类中出现重名的成员方法,
则创建子类对象调用该方法的时候,
子类对象会优先调用自己的方法。
则创建子类对象调用该方法的时候,
子类对象会优先调用自己的方法。
方法重写
构造方法
子类的所有构造方法的第一行 都默认有一个 super();(只不过你看不到而已) 用于访问父类的无参构造。
而且当你显示给出 super(), super(参数),, 系统就不再默认提供 super()
而且当你显示给出 super(), super(参数),, 系统就不再默认提供 super()
当父类没有无参构造方法的时候, 你必须要做 子类初始化之前先初始化 父类这一点
①访问 父类的其他有参构造。
②访问 子类其他的构造方法。 无论哪一种, 你只需要做到 子类初始化之前 必须先初始化父类就可以。
访问构造方法的语句 必须放在 构造方法的第一行。
子类初始化之前,必须先初始化父类,只能初始化一次(必须只能是一次),所以 this() 和super() 都必须放在第一行,所以他俩不能共存。
不能被继承
super(...)和this(...)
多态(⭐⭐⭐⭐⭐)
多态的前提
必须要有 继承或者实现关系。
必须要有 父类的变量(引用) 指向子类对象
有方法的重写(非必须的),之所以很多的教程中说必须有方法的重写,是因为 我们去应用多态的时候99%以上的情况 全部都是用方法的重写, 如果
你没有方法的重写,那么你使用多态就没有任何意义,所以 很多的教程中 必须有方法的重写。
必须要有 父类的变量(引用) 指向子类对象
有方法的重写(非必须的),之所以很多的教程中说必须有方法的重写,是因为 我们去应用多态的时候99%以上的情况 全部都是用方法的重写, 如果
你没有方法的重写,那么你使用多态就没有任何意义,所以 很多的教程中 必须有方法的重写。
多态的形式
多态是继封装、继承之后,面向对象的第三大特性
多态是出现在继承或者实现关系中的
必须要有 父类的变量(引用) 指向子类对象
父类类型 变量名 = new 子类/实现类构造器;
变量名.方法名();
多态的使用场景
例
父类:
public class Person {
private String name;
private int age;
空参构造
带全部参数的构造
get和set方法
public void show(){
System.out.println(name + ", " + age);
}
}
子类1:
public class Administrator extends Person {
@Override
public void show() {
System.out.println("管理员的信息为:" + getName() + ", " + getAge());
}
}
子类2:
public class Student extends Person{
@Override
public void show() {
System.out.println("学生的信息为:" + getName() + ", " + getAge());
}
}
子类3:
public class Teacher extends Person{
@Override
public void show() {
System.out.println("老师的信息为:" + getName() + ", " + getAge());
}
}
测试类:
public class Test {
public static void main(String[] args) {
//创建三个对象,并调用register方法
Student s = new Student();
s.setName("张三");
s.setAge(18);
Teacher t = new Teacher();
t.setName("王建国");
t.setAge(30);
Administrator admin = new Administrator();
admin.setName("管理员");
admin.setAge(35);
register(s);
register(t);
register(admin);
}
//这个方法既能接收老师,又能接收学生,还能接收管理员
//只能把参数写成这三个类型的父类
public static void register(Person p){
p.show();
}
}
public class Person {
private String name;
private int age;
空参构造
带全部参数的构造
get和set方法
public void show(){
System.out.println(name + ", " + age);
}
}
子类1:
public class Administrator extends Person {
@Override
public void show() {
System.out.println("管理员的信息为:" + getName() + ", " + getAge());
}
}
子类2:
public class Student extends Person{
@Override
public void show() {
System.out.println("学生的信息为:" + getName() + ", " + getAge());
}
}
子类3:
public class Teacher extends Person{
@Override
public void show() {
System.out.println("老师的信息为:" + getName() + ", " + getAge());
}
}
测试类:
public class Test {
public static void main(String[] args) {
//创建三个对象,并调用register方法
Student s = new Student();
s.setName("张三");
s.setAge(18);
Teacher t = new Teacher();
t.setName("王建国");
t.setAge(30);
Administrator admin = new Administrator();
admin.setName("管理员");
admin.setAge(35);
register(s);
register(t);
register(admin);
}
//这个方法既能接收老师,又能接收学生,还能接收管理员
//只能把参数写成这三个类型的父类
public static void register(Person p){
p.show();
}
}
要注意的是:
- 当一个方法的形参是一个类,我们可以传递这个类所有的子类对象。
- 当一个方法的形参是一个接口,我们可以传递这个接口所有的实现类对象(后面会学)。
- 而且多态还可以根据传递的不同对象来调用不同类中的方法。
多态的定义和前提
定义
多态: 是指同一行为,具有多个不同表现形式
前提
1. 有继承或者实现关系
2. 方法的重写【意义体现:不重写,无意义】
3. 父类引用指向子类对象【格式体现】
父类类型:指子类对象继承的父类类型,或者实现的父接口类型。
多态的作用
提高了程序的扩展性。
多态的运行特点
调用成员变量时:编译看左边,运行看左边
调用成员方法时:编译看左边,运行看右边
调用成员方法时:编译看左边,运行看右边
代码示例:
Fu f = new Zi();
//编译看左边的父类中有没有name这个属性,没有就报错
//在实际运行的时候,把父类name属性的值打印出来
System.out.println(f.name);
//编译看左边的父类中有没有show这个方法,没有就报错
//在实际运行的时候,运行的是子类中的show方法
f.show();
//编译看左边的父类中有没有name这个属性,没有就报错
//在实际运行的时候,把父类name属性的值打印出来
System.out.println(f.name);
//编译看左边的父类中有没有show这个方法,没有就报错
//在实际运行的时候,运行的是子类中的show方法
f.show();
多态的弊端
父类的引用不能调用子类特有的内容的。
引用类型转换
基本类型:
自动类型转换
int a = 10;
long lo = a;
int a = 10;
long lo = a;
强制类型转换
long lo = 100L;
int a = (int)lo;
long lo = 100L;
int a = (int)lo;
引用类型(类):
自动类型转换
向上转型:多态本身是子类类型向父类类型向上转换(自动转换)的过程,这个过程是默认的。
当父类引用指向一个子类对象时,便是向上转型。
父类类型 变量名 = new 子类类型();
如:Animal a = new Cat();
如:Animal a = new Cat();
原因是:父类类型相对与子类来说是大范围的类型
强制类型转换
向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。
一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。
子类类型 变量名 = (子类类型) 父类变量名;
如:Aniaml a = new Cat();
Cat c =(Cat) a;
如:Aniaml a = new Cat();
Cat c =(Cat) a;
instanceof关键字
为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验
格式
变量名 instanceof 数据类型
如果变量属于该数据类型或者其子类类型,返回true。
如果变量不属于该数据类型或者其子类类型,返回false。
如果变量属于该数据类型或者其子类类型,返回true。
如果变量不属于该数据类型或者其子类类型,返回false。
例
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
if (a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
} else if (a instanceof Dog){
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse
}
}
}
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
if (a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
} else if (a instanceof Dog){
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse
}
}
}
多态解释历史遗留问题-方法的重写
私有的不算重写
静态的也不算重写
之前我们总说 重写父类的方法 方法声明必须一模一样。 其实返回值可以不一样。
这个不一样,也是有关系的
子类方法的返回值类型 必须 是 父类方法返回值类型的子类
关键字
this | super
super和this的用法格式
访问成员
this.成员变量 -- 本类的
super.成员变量 -- 父类的
this.成员方法名() -- 本类的
super.成员方法名() -- 父类的
调用构造方法格式
super(...) -- 调用父类的构造方法,根据参数匹配确认
this(...) -- 调用本类的其他构造方法,根据参数匹配确认
小结
子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。
super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
super(..)和this(...)是根据参数去确定调用父类哪个构造方法的。
super(..)可以调用父类构造方法初始化继承自父类的成员变量的数据。
this(..)可以调用本类中的其他构造方法。
package | import
package
定义包的关键字
自己定义的类 不同的包下使用就需要导包
jdk核心类库的类 java.lang 包不需要导包
其他的包下就需要导包
jdk核心类库的类 java.lang 包不需要导包
其他的包下就需要导包
导包的注意事项
①:不同包的类 使用的时候 要么使用全类名 要么导包
②: 如果不同的包下 出现了相同的类 怎么办呢?
③: 如果不同的包下 出现了相同的类 ,并且其中一个类使用另一个类的情况
package com.itheima.day01.test01;
import
引入包的关键字
import java.util.ArrayList;
abstract | interface
抽象类⭐⭐⭐⭐⭐
抽象类的定义
包含抽象方法的类就是抽象类
抽象方法 : 没有方法体的方法。
案例
抽象类:包含抽象方法的类。
案例
abstract使用格式
抽象方法
使用abstract 关键字修饰方法,
该方法就成了抽象方法,
抽象方法只包含一个方法名,而没有方法体。
该方法就成了抽象方法,
抽象方法只包含一个方法名,而没有方法体。
定义格式:
修饰符 abstract 返回值类型 方法名 (参数列表);
抽象类
如果一个类包含抽象方法,那么这个类就必须要定义为抽象类 abstract。
注意:抽象类不一定有抽象方法,但是有抽象方法的类必须定义成抽象类。
注意:抽象类不一定有抽象方法,但是有抽象方法的类必须定义成抽象类。
定义格式:
abstract class 类名字 {
}
}
抽象类的使用
要求:继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类
此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法
抽象类的特点
抽象类不能创建对象
其他成员(构造方法,实例方法,静态方法等)抽象类都是具备的。
案例
抽象类里面可以没抽象方法,但是有抽象方法的类 必须是抽象类了
案例
普通的类继承抽象类,则必须全部重写里面的抽象方法 ,不重写就报错。
抽象类继承抽象类 就不是必须要重写了
抽象类存在的意义
抽象类存在的意义是为了被子类继承,否则抽象类将毫无意义。抽象类可以强制让子类,一定要按照规定的格式进行重写。
接口⭐⭐⭐⭐⭐
接口是一种数据类型
数据类型:
基本类型
引用类型
数组: [ ]
类 : class
接口: interface
定义
格式
//接口的定义格式:
interface 接口名称{
// 抽象方法
}
// 接口的声明:interface
// 接口名称:首字母大写,满足“驼峰模式”
interface 接口名称{
// 抽象方法
}
// 接口的声明:interface
// 接口名称:首字母大写,满足“驼峰模式”
interface
inter internet因特网 face 脸面 网络的脸面。
脸面最大的特点 就是 暴露出来。
接口里面的所有成员 都是 public的
脸面最大的特点 就是 暴露出来。
接口里面的所有成员 都是 public的
public interface Spile {
}
}
特点
在JDK7,包括JDK7之前,
接口中的只有包含:抽象方法和常量
接口中的只有包含:抽象方法和常量
抽象方法
注意:接口中的抽象方法默认会自动加上public abstract修饰程序员无需自己手写!!
按照规范:以后接口中的抽象方法建议不要写上public abstract。因为没有必要啊,默认会加上。
常量
在接口中定义的成员变量默认会加上: public static final修饰。
也就是说在接口中定义的成员变量实际上是一个常量。
这里是使用public static final修饰后,变量值就不可被修改,
并且是静态化的变量可以直接用接口名访问,所以也叫常量。
常量必须要给初始值。常量命名规范建议字母全部大写,多个单词用下划线连接。
也就是说在接口中定义的成员变量实际上是一个常量。
这里是使用public static final修饰后,变量值就不可被修改,
并且是静态化的变量可以直接用接口名访问,所以也叫常量。
常量必须要给初始值。常量命名规范建议字母全部大写,多个单词用下划线连接。
接口不能创建对象啊
抽象类不能创建对象
接口没有构造方法
子类和接口的关系是实现关系,而且可以多实现
回顾一下单继承
同理 接口不也是这样的吗 为什么接口就可以多实现呢?
子类实现接口 必须重写里面的所有抽象方法
为什么子类要全部重写呢
基本的实现
实现接口的概述
类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。
实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements关键字。
实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements关键字。
实现接口的格式
/**接口的实现:
在Java中接口是被实现的,实现接口的类称为实现类。
实现类的格式:*/
class 类名 implements 接口1,接口2,接口3...{
}
类实现接口的要求和意义
1. 必须重写实现的全部接口中所有抽象方法。
2. 如果一个类实现了接口,但是没有重写完全部接口的全部抽象方法,这个类也必须定义成抽象类。
3. 意义:接口体现的是一种规范,接口对实现类是一种强制性的约束,要么全部完成接口申明的功能,要么自己也定义成抽象类。这正是一种强制性的规范。
接口的成员
构造方法
接口中没有构造方法的
成员变量
接口中的成员变量 全部都是 常量 前面都有默认修饰符 public static final
接口没有构造方法
静态代码块 不允许存在于接口中
静态代码块 不允许存在于接口中
案例1
案例2
成员方法
jdk8之前: 接口中的方法 只能有抽象方法 即使你什么修饰符都不写 也有默认的 public abstract
interface Law {
/* public abstract*/void show(); //正确
public /* abstract*/ void method();
}
/* public abstract*/void show(); //正确
public /* abstract*/ void method();
}
jdk8之后:
出现默认方法
default
接口中就具备了 具有功能的方法
接口中就具备了 具有功能的方法
案例1
案例2
案例3
案例4
出现静态方法
接口中的 static方法 是给自己准备的 子类无论如何调用不了。
案例1:类中的静态方法
案例2:接口中的静态方法
jdk9之后 :
出现私有方法
总结类和接口的关系
类和类 :是继承关系, 可单 不可多 但可多层
案例
类和接口:实现 可单 可多
类与接口是实现关系
案例1
案例2
案例3
案例4
接口和接口:继承 可单 可多
接口与接口是继承关系
案例
接口的作用
解耦合性:
通常情况下,为了能够从一个类中调用另外一个类的方法,在编译时这两个类都需要存在,
进而使Java编译器能够进行检查以确保方法签名是兼容的《Java8编程参考官方教程》。
而接口则可以解决这个限制,因为接口被设计为支持运行时动态方法解析。
通常情况下,为了能够从一个类中调用另外一个类的方法,在编译时这两个类都需要存在,
进而使Java编译器能够进行检查以确保方法签名是兼容的《Java8编程参考官方教程》。
而接口则可以解决这个限制,因为接口被设计为支持运行时动态方法解析。
接口开发中的应用举例:
模拟生活中的一个场景
电脑可以 通过 usb接口 使用 鼠标设备。
电脑可以 通过 usb接口 使用 鼠标设备。
接口的边缘作用:
1. 规范性,接口中有抽象方法,子类必须强制重写。
2. 拓展性,你只要按照接口的规则 去创建的, 就能和接口对接上,所有按照接口创建的 都可以对接上
接口的细节
接口和抽象类的区别
语法上 非常的相似
设计理念上 大相径庭
扩展
1. 当两个接口中存在相同抽象方法的时候,该怎么办?
只要重写一次即可。此时重写的方法,既表示重写1接口的,也表示重写2接口的。
2. 实现类能不能继承A类的时候,同时实现其他接口呢?
继承的父类,就好比是亲爸爸一样
实现的接口,就好比是干爹一样
可以继承一个类的同时,再实现多个接口,只不过,要把接口里面所有的抽象方法,全部实现。
实现的接口,就好比是干爹一样
可以继承一个类的同时,再实现多个接口,只不过,要把接口里面所有的抽象方法,全部实现。
3. 实现类能不能继承一个抽象类的时候,同时实现其他接口呢?
实现类可以继承一个抽象类的同时,再实现其他多个接口,只不过要把里面所有的抽象方法全部重写。
4.实现类Zi,实现了一个接口,还继承了一个Fu类。假设在接口中有一个方法,父类中也有一个相同的方法。子类如何操作呢?
处理办法一:如果父类中的方法体,能满足当前业务的需求,在子类中可以不用重写。
处理办法二:如果父类中的方法体,不能满足当前业务的需求,需要在子类中重写。
处理办法二:如果父类中的方法体,不能满足当前业务的需求,需要在子类中重写。
5. 如果一个接口中,有10个抽象方法,但是我在实现类中,只需要用其中一个,该怎么办?
可以在接口跟实现类中间,新建一个中间类(适配器类)
让这个适配器类去实现接口,对接口里面的所有的方法做空重写。
让子类继承这个适配器类,想要用到哪个方法,就重写哪个方法。
因为中间类没有什么实际的意义,所以一般会把中间类定义为抽象的,不让外界创建对象
让这个适配器类去实现接口,对接口里面的所有的方法做空重写。
让子类继承这个适配器类,想要用到哪个方法,就重写哪个方法。
因为中间类没有什么实际的意义,所以一般会把中间类定义为抽象的,不让外界创建对象
static | final
static (⭐⭐⭐⭐⭐)
共享
静态的东西 可以被该类所有的对象共享
是因为 静态的东西 是放在 方法区的静态区中。
是因为 静态的东西 是放在 方法区的静态区中。
类名
静态的东西,可以用对象名调用,也可以用类名调用,但是 推荐用类名调用。
Student s = new Student();
s.university = "aaa";
Student.university = "aaaa";
先后人
静态: 先人
-静态的随着类的加载而加载进来的。 比创建对象,要加载的早。
-非静态的东西随着对象的创建而存在的, 他比静态加载的晚。
-非静态的东西随着对象的创建而存在的, 他比静态加载的晚。
-先人能不能使用后人的东西啊??? 不能
-后人能不能使用先人的东西啊??? 我们站在巨人的肩膀上。 肯定是能的。
-后人能不能使用先人的东西啊??? 我们站在巨人的肩膀上。 肯定是能的。
-记住 静态的不能直接访问非静态的。 非静态的可以直接访问静态的。
非静态的: 后人
this
静态的方法里面不能有 this
一次
static的内容 在内存中 只能执行一次。
局部代码块:
目的是让你尽早的把变量从内存中清理出去。
局部变量的使用范围 只在当前这个大括号
构造代码块:
每次调用构造方法的时候,都会执行构造代码块,而且是在构造方法之前执行的。
静态代码块(⭐⭐⭐⭐⭐):
是随着类的加载而执行, 而且仅执行唯一的一次
final(⭐⭐⭐⭐⭐)
概述
学习了继承后,我们知道,子类可以在父类的基础上改写父类内容,比如,方法重写。
如果有一个方法我不想别人去改写里面内容,该怎么办呢?
Java提供了final 关键字,表示修饰的内容不可变。
如果有一个方法我不想别人去改写里面内容,该怎么办呢?
Java提供了final 关键字,表示修饰的内容不可变。
使用方式
修饰类
修饰的类不能被继承
final class Person {
}
class Student extends Person { //编译报错
}
}
class Student extends Person { //编译报错
}
修饰方法
修饰的方法不能被重写
class Person {
public final void show(){
}
}
class Student extends Person {
public void show(){ //编译报错
System.out.println("chongxie");
}
}
public final void show(){
}
}
class Student extends Person {
public void show(){ //编译报错
System.out.println("chongxie");
}
}
修饰变量
修饰的变量就变成了常量(只能赋值一次)
常量分类
自面值常量
能够直接写出来的数据
System.out.println(100);
System.out.println(3.14);
System.out.println('a');
System.out.println(true);
System.out.println("afdsdfsa");
//System.out.println(null); //null不允许直接打印
自定义常量
变量前面加final
自定义常量的起名:回顾标识符
硬性要求: 字母和数字$_ 不能是关键字 不能是数字开头
软件要求:
大驼峰: 类名 接口名
小驼峰:方法名 变量名
峡谷先锋: 常量名 JAVA_HOME
自定义常量按照数据类型分
基本类型:所以 就有基本类型的常量
class Demo {
public static void main(String[] args){
final int A = 10;
//A = 20; //编译报错
}
}
public static void main(String[] args){
final int A = 10;
//A = 20; //编译报错
}
}
引用类型:所以 就有引用类型的常量
class Demo {
public static void main(String[] args){
final int[] ARR = new int[]{3,4,5}; // ARR 记录的是 地址 比如0xab4561
//ARR = new int[3]; // 编译报错。
ARR[0] = 30; //正确的 引用类型的常量 记录的地址是绝对不能变的,但是地址所指向的空间里面的内容可以变。
}
}
public static void main(String[] args){
final int[] ARR = new int[]{3,4,5}; // ARR 记录的是 地址 比如0xab4561
//ARR = new int[3]; // 编译报错。
ARR[0] = 30; //正确的 引用类型的常量 记录的地址是绝对不能变的,但是地址所指向的空间里面的内容可以变。
}
}
自定义常量的形成: 变量前面加final
按照位置分
局部常量
基本类型的局部变量,被final修饰后,只能赋值一次,不能再更改。
class Demo {
public static void main(String[] args){
final int A; //正确的。 局部常量使用之前必须先赋值。 只不过 赋值之后就不能再次赋值了
/*int A;
A = 20;
System.out.println(A);
A = 30;
System.out.println(A);*/
/*
final int A;
A = 20;
System.out.println(A);
//A = 30; // 编译报错。
*/
}
}
public static void main(String[] args){
final int A; //正确的。 局部常量使用之前必须先赋值。 只不过 赋值之后就不能再次赋值了
/*int A;
A = 20;
System.out.println(A);
A = 30;
System.out.println(A);*/
/*
final int A;
A = 20;
System.out.println(A);
//A = 30; // 编译报错。
*/
}
}
成员常量
注意事项
案例1:原因
class Student{
final int A; //A 是成员 有默认值 0 那你为什么给我报错啊?
// 就是因为你有默认值才给你报错
// 常量只能赋值一次, 如果你有默认值, 就相当于你已经让默认值赋值了。 今后就再也不能赋值了,一辈子只能用默认值了
// 但是你要知道 默认值 是用来占位的
// 不是为了让你去用的。 想让你珍惜这个常量的赋值机会。
}
案例2:解决
class Student{
final int A = 10; //正确了。
}
案例3:解决的理解
class Teacher {
final int A = 20;
}
class Student{
final int A = 10;
}
class Demo {
public static void main(String[] args){
//System.out.println(A);// 编译报错
//System.out.println(Student.A); // A 被final修饰 并不是被static修饰啊。 所以不能用类名调用。
Student s = new Student();
System.out.println(s.A); //10
// 我在使用这个A之前 A必须不能是默认值。 只要在A存在之前 A是绝对不能是默认值的。
// 也就是说 在使用A之前 而A只能是由对象来去调用,所以 在Student 创建学生对象之前 你能够把A的默认值改掉就行了。
// 不非得 定义的时候 直接写死。 final int A = 10;
// 你想啊 创建对象之前都做了哪些事情啊?
// 静态代码块 构造代码块 构造方法
}
}
案例4:构造
class Student {
public final int a ;
public Student(){
a = 10; // 正确
}
}
案例5:赋值加构造
class Student {
public final int a=20 ;
public Student(){
a = 10; // 编译报错 因为赋值了两次。
}
}
案例6:多个构造1
class Student {
public final int a ; //编译报错
public Student(int age){
}
public Student(){
a = 10;
}
}
class Demo {
public static void main(String[] args){
Student s = new Student();
System.out.println(s.a); //10
Student s = new Student(20);
System.out.println(s.a); //只能是默认值 而成员常量 不能是默认值、
}
}
案例7:多个构造2
class Student {
public final int a =20; // 正确
public Student(int age){
}
public Student(){
}
}
案例8:多个构造3
class Student {
public final int a ;
public Student(int age){
a = age
}
public Student(){
a = 10;
}
}
class Demo {
public static void main(String[] args){
Student s = new Student();
System.out.println(s.a); //10
Student s = new Student(20);
//s.a = 20; //编译报错
System.out.println(s.a); //只能是默认值 而成员常量 不能是默认值、
}
}
案例9:多个构造4
class Student {
public final int a; // 正确
public Student(int age){
a=20;
}
public Student(){
this(10);
}
}
案例10:构造代码块1
class Student {
public final int a;
{ //构造代码块
a = 20; //正确
}
}
案例11:构造代码块2
class Student {
public final int a =10;
{
a = 20; //编译报错 final修饰的数据 只能赋值一次。 而你现在赋值两次
}
}
案例12:构造代码块3
class Student {
public final int a ;
{
a = 20;
}
public Student(){
a = 10; // 编译报错 因为赋值了两次。
}
}
案例13:构造代码块4
class Student {
public final int a ;
{
a=20; //正确
}
public Student(int age){
}
public Student(){
}
}
案例14:静态代码块1
class Student {
public final int a ;
static {
a = 20; //肯定编译报错 因为 static 是先人 a是后人 先人不能访问后人的东西
}
}
案例15:静态代码块2
class Student {
public static final int a ; //
{
a = 20; // 构造代码块是后人 a是先人 虽然可以访问 但是 a是静态的 可以用类名使用, 因为在类加载的时候去把 默认值取消掉。
//而构造代码块只能在创建对象的时候执行,已经晚了。
}
}
class Demo {
public static void main(String[] args){
System.out.println(Student.a);
}
}
案例16:静态代码块3
class Student {
public static final int a ;
static {
a = 20; // 正确
}
}
class Demo {
public static void main(String[] args){
System.out.println(Student.a);
}
}
main
main()方法是Java应用程序的入口方法
方法的名字必须是main,方法必须是public static void 类型的,方法必须接收一个字符串数组的参数
public static void main(String args[]) {
System.out.println("Hello World!");
}
类(class)
权限修饰符(⭐⭐⭐⭐⭐)
权限修饰符
本类 同一个包的类 不同包的子类 不同包的无关类
private √
默认 √ √
protected √ √ √
public √ √ √ √
private √
默认 √ √
protected √ √ √
public √ √ √ √
属性
成员变量
成员变量: 定义在类中,方法的外面 ,用来描述类的属性
局部变量
局部变量的使用范围 只在当前这个大括号
方法
方法声明与使用
方法重写
重写:
子父类关系中 子类出现了和父类方法 声明
一摸一样的方法, 那么这就是方法的重写
子父类关系中 子类出现了和父类方法 声明
一摸一样的方法, 那么这就是方法的重写
方法重写 :
子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),
会出现覆盖效果,也称为重写或者复写。声明不变,重新实现。
子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),
会出现覆盖效果,也称为重写或者复写。声明不变,重新实现。
重写的注意事项
1:权限不足的方法不能被重写
@Override 关键字表达的意思是 这个关键字下面的方法 是重写的父类的方法。
2:静态的方法 不能被重写
3:重写的时候 子类方法的权限修饰符 必须大于等与父类权限修饰符
重载:
方法的重载 同一个类中 方法名相同 参数列表
不同 返回值无关的 这些方法 形成了重载的关系
方法的重载 同一个类中 方法名相同 参数列表
不同 返回值无关的 这些方法 形成了重载的关系
构造器
构造方法(构造器)
格式
方法名和类名相同
不能有返回值,也就不能有返回值类型 void也不能写
public 构造方法的名字(){
}
作用
给成员变量初始化值
注意(重点)
如果不写构造,java会默认提供一个无参的构造方法,如果自己写了一个有参数的构造方法,那么java就不会再提供无参的构造方法了,
需要自己把有参无参的构造都写出来
总结一下
1.什么是构造器?
答:构造器其实是一种特殊的方法,但是这个方法没有返回值类型,方法名必须和类名相 同。
2.构造器什么时候执行?
答:new 对象就是在执行构造方法; 注意 是堆里先创建好了对象 然后才执行的构造方法
3.构造方法的应用场景是什么?
答:在创建对象时,可以用构造方法给成员变量赋值
4.构造方法有哪些注意事项?
1)在设计一个类时,如果不写构造器,Java会自动生成一个无参数构造器。
2)一定定义了有参数构造器,Java就不再提供空参数构造器,此时建议自己加一个无参数构造器。
容易出的问题
在构造方法里 一定不要忘记 初始化成员变量
一个标准的javabean
alt+insert 快捷键
成员变量私有化
给成员 变量提供公共的get和set方法
提供空的构造, 根据需求提供有参数的构造
代码块
静态代码块
非静态代码块
内部类
访问特点
内部类访问外部类的内容 直接可以访问的,包括私有的。
外部类要想访问内部类的内容, 必须创建内部类的对象 对象调用。
外部类要想访问内部类的内容, 必须创建内部类的对象 对象调用。
案例
内部类的分类:
public class Outer {
// 成员位置 去定义类 : 成员内部类
public void show(){
// 局部位置 去定义类 : 局部内部类。
}
}
public class Outer {
// 成员位置 去定义类 : 成员内部类
public void show(){
// 局部位置 去定义类 : 局部内部类。
}
}
内部类的分类
静态成员内部类
静态内部类
静态内部类特点
静态内部类是一种特殊的成员内部类。
有static修饰,属于外部类本身的。
总结:
静态内部类与其他类的用法完全一样。只是访问的时候需要加上外部类.内部类。
静态内部类与其他类的用法完全一样。只是访问的时候需要加上外部类.内部类。
拓展
1:静态内部类可以直接访问外部类的静态成员。
2:静态内部类不可以直接访问外部类的非静态成员,如果要访问需要创建外部类的对象。
-3:静态内部类中没有隐含的Outer.this。
静态内部类的使用格式:
外部类.内部类
案例
静态内部类对象的创建格式
外部类.内部类 变量 = new 外部类.内部类构造器;
调用方法的格式:
调用非静态方法的格式:先创建对象,用对象调用
调用静态方法的格式:外部类名.内部类名.方法名();
非静态
成员内部类
位置
类定义在了成员位置
(类中方法外称为成员位置,无static修饰的内部类)
(类中方法外称为成员位置,无static修饰的内部类)
成员内部类特点
无static修饰的内部类,属于外部类对象的。
宿主:外部类对象。
内部类的使用格式
内部类创建对象的格式理解
获取成员内部类对象的两种方式:
方式一:外部直接创建成员内部类的对象
外部类.内部类 变量 = new 外部类().new 内部类();
方式二:在外部类中定义一个方法提供内部类的对象
成员内部类访问当前外部类对象
案例
私有成员内部类
案例
面试题(⭐⭐⭐⭐)
局部内部类访问局部变量, 局部变量前面必须有final修饰,
在jdk1.8的时候,这个final可以省略,但其实并不是真正的省略,
而是 前面默认有一个 final ,只是你看不到而已。 这样做的目的是为了延长局部变量的声明周期。
在jdk1.8的时候,这个final可以省略,但其实并不是真正的省略,
而是 前面默认有一个 final ,只是你看不到而已。 这样做的目的是为了延长局部变量的声明周期。
局部内部类
位置
类定义在方法内
局部内部类如何把对象扔到外界使用
案例
匿名的局部内部类(⭐⭐⭐⭐⭐)
匿名内部类
位置
没有名字的内部类,
可以在方法中,也可以在类中方法外。
可以在方法中,也可以在类中方法外。
匿名内部类的由来
案例
匿名内部类的应用
匿名内部类一般 会作为方法的实参去传递 ,返回值去返回。
案例
匿名内部类的使用场景:
当一个接口 只有一个抽象方法的时候, 你用匿名内部类是比较合适的。
因为这样的时候 用匿名内部类比较方便 少建java文件,少写class类,
但是当一个接口中 有多个抽象方法的时候, 就不如 额外去建一个java文件键一个类 实现接口 好了。
因为这样的时候 用匿名内部类比较方便 少建java文件,少写class类,
但是当一个接口中 有多个抽象方法的时候, 就不如 额外去建一个java文件键一个类 实现接口 好了。
匿名内部类可以是 普通类的 也可以是抽象类的 也可以是接口的
作为参数的传递
lambda表达式(⭐⭐⭐⭐)
lambda的格式和前提条件
jdk8 出现了 Lambda表达式 用来 优化匿名内部类的写法
lambda 定义格式 --- 由三部分组成
() 代表的是 重写的方法的小括号。
-> 代表着 小括号的参数被方法体使用。
{} 重写方法的方法体
() 代表的是 重写的方法的小括号。
-> 代表着 小括号的参数被方法体使用。
{} 重写方法的方法体
匿名内部类如果使用lambda表达式优化。则需满足三个条件
1: 匿名内部类 必须是 实现接口的匿名内部类
2: 接口中 必须 只能是一个抽象方法。 ---确定方法
3: 必须要有上下文的推导环境。 ---确定哪个接口
(java 为了 设置推导环境语法 故意把 lambda 设置成了表达式 -- 表达式 不能单独成语句 )
1: 匿名内部类 必须是 实现接口的匿名内部类
2: 接口中 必须 只能是一个抽象方法。 ---确定方法
3: 必须要有上下文的推导环境。 ---确定哪个接口
(java 为了 设置推导环境语法 故意把 lambda 设置成了表达式 -- 表达式 不能单独成语句 )
案例
lambda省略格式
省略格式1
( )内如果只有一个参数 可以省略数据类型和小括号本身。
( )内多个参数的 只能省略参数的数据类型不能省略小括号本身
{ }内如果只有一句话 可以省略分号 还有return 还有大括号本身
省略格式2-方法引用
要想使用方法引用来优化lambda表达式,需要再额外遵守下面三个条件。
1: lambda 的方法体中 只能有一条语句。
2: 而且这条语句 必须得是 调用方法的语句 (调用构造方法 调用普通方法 调用静态方法)
int a =10+20; //这就不是调用方法的语句
3: 调用的方法 使用的参数,恰好是 重写接口方法的 参数。
1: lambda 的方法体中 只能有一条语句。
2: 而且这条语句 必须得是 调用方法的语句 (调用构造方法 调用普通方法 调用静态方法)
int a =10+20; //这就不是调用方法的语句
3: 调用的方法 使用的参数,恰好是 重写接口方法的 参数。
案例
案例1
案例2
lambda和匿名内部类的区别:
1:
匿名内部类 可以对 普通类 抽象类 接口 都可以使用
但是lambda必须对接口使用
2:
匿名内部类 可以要求 普通类 抽象类 接口 里面有多少抽象方法
lambda 要求接口中只能有一个抽象方法
3:
匿名内部类会生成class文件
而lambda不生成class文件
案例
案例1
案例2
面向对象进阶
核心API
Math⭐⭐⭐
Math类是用来做数学运算的
Math的这些方法 都是静态的
Math的构造方法私有的
Math是不能创建对象的
Math的这些方法 都是静态的
Math的构造方法私有的
Math是不能创建对象的
常见方法
public static int abs(int a)
// 返回参数的绝对值
// 返回参数的绝对值
public static double ceil(double a)
// 返回大于或等于参数的最小整数
// 返回大于或等于参数的最小整数
public static double floor(double a)
// 返回小于或等于参数的最大整数
// 返回小于或等于参数的最大整数
public static int round(float a)
// 按照四舍五入返回最接近参数的int类型的值
// 按照四舍五入返回最接近参数的int类型的值
public static int max(int a,int b)
// 获取两个int值中的较大值
// 获取两个int值中的较大值
public static int min(int a,int b)
// 获取两个int值中的较小值
// 获取两个int值中的较小值
public static double pow (double a,double b)
// 计算a的b次幂的值
// 计算a的b次幂的值
public static double random()
// 返回一个[0.0,1.0)的随机值
// 返回一个[0.0,1.0)的随机值
案例
生成一个 [49-61] 范围内的随机数
System⭐⭐⭐
System的这些方法 都是静态的
System的构造方法私有的
System是不能创建对象的。
System的构造方法私有的
System是不能创建对象的。
获取到当前时间的毫秒值的意义:
我们常常来需要统计某一段代码的执行时间。
此时我们就可以在执行这段代码之前获取一次时间,
在执行完毕以后再次获取一次系统时间,然后计算两个时间的差值,
这个差值就是这段代码执行完毕以后所需要的时间。
我们常常来需要统计某一段代码的执行时间。
此时我们就可以在执行这段代码之前获取一次时间,
在执行完毕以后再次获取一次系统时间,然后计算两个时间的差值,
这个差值就是这段代码执行完毕以后所需要的时间。
常见方法
public static long currentTimeMillis()
// 获取当前时间所对应的毫秒值(当前时间为0时区所对应的时间即就是英国格林尼治天文台旧址所在位置)
// 获取当前时间所对应的毫秒值(当前时间为0时区所对应的时间即就是英国格林尼治天文台旧址所在位置)
public static void exit(int status)
// 终止当前正在运行的Java虚拟机,0表示正常退出,非零表示异常退出
// 终止当前正在运行的Java虚拟机,0表示正常退出,非零表示异常退出
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
// 进行数值元素copy
// 进行数值元素copy
方法参数说明
// src: 源数组
// srcPos: 源数值的开始位置
// dest: 目标数组
// destPos: 目标数组开始位置
// length: 要复制的元素个数
// srcPos: 源数值的开始位置
// dest: 目标数组
// destPos: 目标数组开始位置
// length: 要复制的元素个数
arraycopy方法底层细节:
1.如果数据源数组和目的地数组都是基本数据类型,那么两者的类型必须保持一致,否则会报错
2.在拷贝的时候需要考虑数组的长度,如果超出范围也会报错
3.如果数据源数组和目的地数组都是引用数据类型,那么子类类型可以赋值给父类类型
案例
System
Object⭐⭐⭐⭐
Object类所在包是java.lang包。
Object 是类层次结构的根,每个类都可以将 Object 作为超类。
所有类都直接或者间接的继承自该类;换句话说,该类所具备的方法,其他所有类都继承了。
Object 是类层次结构的根,每个类都可以将 Object 作为超类。
所有类都直接或者间接的继承自该类;换句话说,该类所具备的方法,其他所有类都继承了。
常见方法
public String toString()
//返回该对象的字符串表示形式(可以看做是对象的内存地址值)
//返回该对象的字符串表示形式(可以看做是对象的内存地址值)
toString :
类重写了toString, 打印语句就打印toString的内容
Object重写的toString打印的是地址
类重写了toString, 打印语句就打印toString的内容
Object重写的toString打印的是地址
idea自动重写Object的toString方法
alt + insert
选择toString
同时选择name和age属性,点击OK
选择toString
同时选择name和age属性,点击OK
小结
1. 在通过输出语句输出一个对象时,默认调用的就是toString()方法
2. 输出地址值一般没有意义,我们可以通过重写toString方法去输出对应的成员变量信息
(快捷键:atl + insert , 空白处 右键 -> Generate -> 选择toString)
(快捷键:atl + insert , 空白处 右键 -> Generate -> 选择toString)
3. toString方法的作用:以良好的格式,更方便的展示对象中的属性值
4. 一般情况下Jdk所提供的类都会重写Object类中的toString方法
案例
toString
public boolean equals(Object obj)
//比较两个对象地址值是否相等;true表示相同,false表示不相同
//比较两个对象地址值是否相等;true表示相同,false表示不相同
equals();
Object 的equals方法是比较的地址。
要想让自己定义的类的对象比较内容,就必须重写equals方法 来比较内容:
Object 的equals方法是比较的地址。
要想让自己定义的类的对象比较内容,就必须重写equals方法 来比较内容:
idea自动重写Object的equals方法
alt + insert
选择equals() and hashCode()方法
点击next
选择neme和age属性点击next
取消name和age属性(因为此时选择的是在生成hashCode方法时所涉及到的属性,关于hashCode方法后期再做重点介绍),点击Finish完成生成操作
选择equals() and hashCode()方法
点击next
选择neme和age属性点击next
取消name和age属性(因为此时选择的是在生成hashCode方法时所涉及到的属性,关于hashCode方法后期再做重点介绍),点击Finish完成生成操作
小结
1. 默认情况下equals方法比较的是对象的地址值
2. 比较对象的地址值是没有意义的,因此一般情况下我们都会重写Object类中的equals方法
案例
equals()
protected Object clone()
//对象克隆
//对象克隆
Objects⭐
Object 类的 工具类
private 私有构造
里面全部都是 静态方法
里面全部都是 静态方法
工具类:
对于开发者来说 也是经常的去调用。
所以就是因为工具类里面的方法比较常用,所以尽量方便调用,类名调用比较方便,所以我们加static
尽管你加了static,但是还是有很多人可以去创建对象 用调用调用static的内容的,那么就失去了方便调用的特点了,
所以我们把构造方法私有,让别人不能创建对象即可
所以就是因为工具类里面的方法比较常用,所以尽量方便调用,类名调用比较方便,所以我们加static
尽管你加了static,但是还是有很多人可以去创建对象 用调用调用static的内容的,那么就失去了方便调用的特点了,
所以我们把构造方法私有,让别人不能创建对象即可
什么叫工具 :某种职业 把一种东西用的比较常用,这时候就会把这个东西叫做 工具。
常见方法
public static String toString(Object o)
// 获取对象的字符串表现形式
// 获取对象的字符串表现形式
public static boolean equals(Object a, Object b)
// 比较两个对象是否相等
// 比较两个对象是否相等
public static boolean isNull(Object obj)
// 判断对象是否为null
// 判断对象是否为null
public static boolean nonNull(Object obj)
// 判断对象是否不为null
// 判断对象是否不为null
要了解的Objects类中的常见方法
public static <T> T requireNonNull(T obj)
// 检查对象是否不为null,如果为null直接抛出异常;如果不是null返回该对象;
// 检查对象是否不为null,如果为null直接抛出异常;如果不是null返回该对象;
public static <T> T requireNonNullElse(T obj, T defaultObj)
// 检查对象是否不为null,如果不为null,返回该对象;如果为null返回defaultObj值
// 检查对象是否不为null,如果不为null,返回该对象;如果为null返回defaultObj值
public static <T> T requireNonNullElseGet(T obj, Supplier<? extends T> supplier)
// 检查对象是否不为null,如果不为null,返回该对象;如果为null,返回由Supplier所提供的值
// 检查对象是否不为null,如果不为null,返回该对象;如果为null,返回由Supplier所提供的值
案例
Objects
BigDecimal⭐⭐⭐⭐
保证了小数运算的准确性,今后开发 做小数运算 全部使用BigDecimal
常见方法
构造方法
BigDecimal(int val)将int 转换为 BigDecinal。
BigDecimal(long val)将long 转换为 BigDecimal。
BigDecinal(String val)将BiDecinal的字符串表示形式转换为 BigDecimal。
BigDecimal(long val)将long 转换为 BigDecimal。
BigDecinal(String val)将BiDecinal的字符串表示形式转换为 BigDecimal。
常见成员方法
public BigDecimal add(BigDecimal value) // 加法运算
public BigDecimal subtract(BigDecimal value) // 减法运算
public BigDecimal multiply(BigDecimal value) // 乘法运算
public BigDecimal divide(BigDecimal value) //除法运算
BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
divisor: 除数对应的BigDecimal对象;
scale: 精确的位数;
roundingMode: 取舍模式;
取舍模式被封装到了RoundingMode这个枚举类中(关于枚举我们后期再做重点讲解),
在这个枚举类中定义了很多种取舍方式。最常见的取舍方式有如下几个:
UP(直接进1) , FLOOR(直接删除) , HALF_UP(4舍五入),我们可以通过如下格式直接访问这些取舍模式:枚举类名.变量名
divisor: 除数对应的BigDecimal对象;
scale: 精确的位数;
roundingMode: 取舍模式;
取舍模式被封装到了RoundingMode这个枚举类中(关于枚举我们后期再做重点讲解),
在这个枚举类中定义了很多种取舍方式。最常见的取舍方式有如下几个:
UP(直接进1) , FLOOR(直接删除) , HALF_UP(4舍五入),我们可以通过如下格式直接访问这些取舍模式:枚举类名.变量名
案例
BigDecimal
Arrays⭐⭐⭐⭐
binarySearch
toString
sort
案例
Arrays
Date⭐⭐⭐⭐⭐
概述
java.util.Date类 表示特定的瞬间,精确到毫秒。
常用方法
构造方法
成员方法
public long getTime() 把日期对象转换成对应的时间毫秒值。
public void setTime(long time) 把方法参数给定的毫秒值设置给日期对象
public void setTime(long time) 把方法参数给定的毫秒值设置给日期对象
案例
Date
SimpleDateFormat⭐⭐⭐⭐⭐
概念
java.text.SimpleDateFormat 是日期/时间格式化类,
我们通过这个类可以帮我们完成日期和文本之间的转换,
也就是可以在Date对象与String对象之间进行来回转换。
我们通过这个类可以帮我们完成日期和文本之间的转换,
也就是可以在Date对象与String对象之间进行来回转换。
格式化:按照指定的格式,把Date对象转换为String对象。
解析:按照指定的格式,把String对象转换为Date对象。
解析:按照指定的格式,把String对象转换为Date对象。
格式规则
常用的格式规则为:
标识字母(区分大小写) 含义
y 年
M 月
d 日
H 时
m 分
s 秒
y 年
M 月
d 日
H 时
m 分
s 秒
备注:更详细的格式规则,可以参考SimpleDateFormat类的API文档。
常用方法
构造方法
由于DateFormat为抽象类,不能直接使用,
所以需要常用的子类java.text.SimpleDateFormat。
这个类需要一个模式(格式)来指定格式化或解析
的标准。构造方法为:
所以需要常用的子类java.text.SimpleDateFormat。
这个类需要一个模式(格式)来指定格式化或解析
的标准。构造方法为:
public SimpleDateFormat(String pattern):
用给定的模式和默认语言环境的日期格式符号构造SimpleDateFormat。
参数pattern是一个字符串,代表日期时间的自定义格式。
用给定的模式和默认语言环境的日期格式符号构造SimpleDateFormat。
参数pattern是一个字符串,代表日期时间的自定义格式。
成员方法
public String format(Date date):将Date对象格式化为字符串。
public Date parse(String source):将字符串解析为Date对象。
public Date parse(String source):将字符串解析为Date对象。
format
Date ---> String
parse
String--> Date
ParseException 编译期异常
案例
format
parse
parse
案例
两个时间的差值
一个时间加一段时间 是哪个时间
基本类型的包装类⭐⭐⭐⭐⭐
包装类作用:
1:基本类型只能表示值,就这么一个单一的功能, 但是引用类型里面有很多的属性和功能,
如果你把基本类型变成了引用类型, 你就可以在里面有更多的功能了。
如果你把基本类型变成了引用类型, 你就可以在里面有更多的功能了。
2:集合只能存储 引用类型,要想存储数字 ,就要把数字变成引用类型。
包装类名
基本类型 对应的包装类(位于java.lang包中)
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean
Integer类
Integer类概述
包装一个对象中的原始类型 int 的值
创建对象格式
Integer in = new Integer(100); // in 就是100
Integer in1 = new Integer("100"); // in1 就是100
Integer in2 =Integer.valueOf(100);
Integer in3 =Integer.valueOf("100");
Integer in1 = new Integer("100"); // in1 就是100
Integer in2 =Integer.valueOf(100);
Integer in3 =Integer.valueOf("100");
常用方法
构造方法
public Integer(int value)
根据 int 值创建 Integer 对象(过时)
public Integer(String s)
根据 String 值创建 Integer 对象(过时)
静态方法
public static Integer valueOf(int i)
返回表示指定的 int 值的 Integer 实例
public static Integer valueOf(String s)
返回保存指定String值的 Integer 对象
static string tobinarystring(int i)
得到二进制
static string tooctalstring(int i)
得到八进制
static string toHexstring(int i)
得到十六进制
static int parseInt(string s)
将字符串类型的整数转成int类型的整数
装箱与拆箱
装箱与拆箱
基本类型与对应的包装类对象之间,来回转换的过程称为”装箱“与”拆箱“
装箱:从基本类型转换为对应的包装类对象。
基本数值---->包装对象
Integer i = new Integer(4);//使用构造函数函数
Integer iii = Integer.valueOf(4);//使用包装类中的valueOf方法
Integer in = new Integer(100); // in 就是100
Integer in1 = new Integer("100"); // in1 就是100
Integer in2 =Integer.valueOf(100);
Integer in3 =Integer.valueOf("100");
Integer in1 = new Integer("100"); // in1 就是100
Integer in2 =Integer.valueOf(100);
Integer in3 =Integer.valueOf("100");
包装对象---->基本数值
int num = i.intValue();
拆箱:从包装类对象转换为对应的基本类型
自动装箱与自动拆箱
由于我们经常要做基本类型与包装类之间的转换,从
Java 5(JDK 1.5)开始,基本类型与包装类的装箱、拆箱动作可以自动完成。
Java 5(JDK 1.5)开始,基本类型与包装类的装箱、拆箱动作可以自动完成。
Integer i = 4;//自动装箱。相当于Integer i = Integer.valueOf(4);
i = i + 5;//等号右边:将i对象转成基本数值(自动拆箱) i.intValue() + 5;
//加法运算完成后,再次装箱,把基本数值转成对象。
案例
自动拆装箱
基本类型和 String之间的转换
基本类型转换为String
方式一:直接在数字后加一个空字符串
方式二:通过String类静态方法valueOf()
String转换成基本类型
方式一:先将字符串数字转成Integer,再调用valueOf()方法
方式二:通过Integer静态方法parseInt()进行转换
除了Character类之外,其他所有包装类都具有
parseXxx静态方法可以将字符串参数转换为对应的基本类型
parseXxx静态方法可以将字符串参数转换为对应的基本类型
其他的也相同.... //byte b = Byte.parseByte("126");
注意:Character 里面没有 parseChar的。。。。
注意:Character 里面没有 parseChar的。。。。
案例
基本类型和String之间的转换
练习题:字符串里面的数字 装入int[]数组
File类⭐⭐⭐⭐
File类的介绍
IO流 是用来 传输文件数据的
如果复制文件,还是下载文件到本地,还是本地文件上传到网上 都需要操作文件或者文件夹
Java中万物皆对象,Java中对文件和文件夹用File类的对象来表示
File方法
File 的构造方法
File代表文件或者文件夹的。
File 创建对象的时候 你需要把让File代表的那个文件或者文件夹的路径 传入构造方法当中,
这样 创建出来的File对象 才能知道自己是代表的哪个文件或者文件夹
这样 创建出来的File对象 才能知道自己是代表的哪个文件或者文件夹
如果你没有参数,你使用无参构造创建一个Flie对象 那么这个File对象代表的是哪个文件或者文件夹呢?? 矛盾!
File没有 无参构造 所有的构造方法都是有参构造
File没有 无参构造 所有的构造方法都是有参构造
File的成员方法
创建
public boolean createNewFile() 创建文件 如果父目录不存在则报错。 创建成功返回true 创建失败则为false (重名就会创建失败)
当且仅当具有该名称的文件尚不存在时,创建一个新的空文件
当且仅当具有该名称的文件尚不存在时,创建一个新的空文件
案例
public boolean mkdir() 创建文件夹 如果父目录不存在不会报错 但是会创建失败。 创建成功返回true 创建失败则为false (重名也会创建失败)
public boolean mkdirs() 创建文件夹 如果父目录不存在则会连通父目录一起创建出来 创建成功返回true 创建失败则为false (重名就会创建失败)
//public boolean delete() 删除文件或者文件夹 , 删除东西不走回收站的, 如果删除的是文件夹 那么必须是空文件夹才可以删除。
判断
public boolean isDirectory() 测试此抽象路径名表示的File是否为目录
public boolean isFile() 测试此抽象路径名表示的File是否为文件
案例
public boolean canWrite() 测试应用程序是否可以修改此抽象路径名表示的文件。
public boolean isHidden() 测试此抽象路径名指定的文件是否是一个隐藏文件。
public boolean exists() 测试此抽象路径名表示的File是否存在
案例
修改
public boolean setWritable(boolean writable) 设置此抽象路径名所有者写权限的一个便捷方法。
public boolean setLastModified(long time) 设置此抽象路径名指定的文件或目录的最后一次修改时间。
public boolean renameTo(File dest) 重新命名此抽象路径名表示的文件
案例
获取
public long getTotalSpace() 返回此抽象路径名指定的分区大小
public long getFreeSpace() 返回此抽象路径名指定的分区中未分配的字节数。
public long getUsableSpace() 返回此抽象路径名指定的分区上可用于此虚拟机的字节数
public long length() 返回由此抽象路径名表示的文件的长度。
public long lastModified() 返回此抽象路径名表示的文件最后一次被修改的时间。
public String getName() 返回由此抽象路径名表示的文件或目录的名称
//注意点:
//1.如果调用者是文件,那么获取的是文件名和后缀名
//2.如果调用者是一个文件夹,那么获取的是文件夹的名字
public String getParent() 返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回 null。
public String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串。
案例
列表
public File[] listFiles() 返回由此文件夹下第一层所有的文件和文件夹
//进入文件夹,获取这个文件夹里面所有的文件和文件夹的File对象,并把这些File对象都放在一个数组中返回.
//包括隐藏文件和隐藏文件夹都可以获取.
//注意事项:
//1.当调用者是一个路径不真实存在的File对象时 ----返回的数组是null
//2.当调用者是一个文件时 -----返回的数组是null
//3,当调用者是一个空文件夹时 ------返回的数组是一个长度为0的数组
//4.当调用者是一个有内容的文件夹时 -----正常返回
//5.当调用者是一个需要权限才能进入的文件夹时,但是你jvm却没有足够的权限 ------则返回的数组是null
案例
public File[] listFiles(FileFilter filter) 返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。
案例
案例
案例1-删除文件夹
定义一个方法,要求传入一个文件夹的File对象,
删除整个文件夹 无论文件夹中有多少层内容
删除整个文件夹 无论文件夹中有多少层内容
案例2-打印文件夹
遍历文件夹 :把文件夹中 所有的文件名称全部
都给我打印出来, 不管这个文件夹有多少层
都给我打印出来, 不管这个文件夹有多少层
案例3-统计每种文件个数
定义一个方法 要求传入一个 一个HashMap 和一个文件夹File对象,
要求统一文件夹里面的每种类型的文件出现的次数 无论文件夹里面有多少层
要求统一文件夹里面的每种类型的文件出现的次数 无论文件夹里面有多少层
相对路径和绝对路径
绝对路径
你写路径的时候, 以盘符开始的路径 都是 绝对路径
(从一个绝对的根结点开始 往下推 可以推到你这个路径。)
(从一个绝对的根结点开始 往下推 可以推到你这个路径。)
File f = new File("d:\\develop\\a.txt"); // 从盘符开始的 这就是一个绝对路径
System.out.println(f.exists());
File f1 = new File("C:\\Users\\56516\\IdeaProjects\\basic25\\b.txt"); // 从盘符开始的 这就是一个绝对路径
System.out.println(f1.exists());
// 如果我每次写我项目下面的一些文件 都用这么长的路径来表示的话 总觉得有些麻烦啊。
相对路径
相对是什么意思 就是有参照物, 参照的是你代码所在的这个位置,
并不是参照代码所在的java文件 而是参照的 你的代码所在的那个项目。
并不是参照代码所在的java文件 而是参照的 你的代码所在的那个项目。
File f2 = new File("b.txt");
System.out.println(f2.exists()); //true
// 使用 \\ 必须是两个 如果是这种/ 用一个就可以了。
File f3 = new File("advance31/src/test\\day11\\Demo.java");
System.out.println(f3.exists()); //true
//并不是 只有项目里面的文件 才可以使用相对路径, 项目外面的文件依然也可以使用相对路径, 你只要做到 你相对的是项目即可。
File f4 = new File("../a.txt");
System.out.println(f4.exists()); //true
File f5 = new File("../c.txt");
System.out.println(f5.exists()); //false
File f6 = new File("../../hydra.txt");
System.out.println(f6.exists()); //true
File f4 = new File("../advance/day07/a.txt");
System.out.println(f4.exists());
正则表达式
pattern
x 字符 x
\\ 反斜线字符
\0n 带有八进制值 0 的字符 n (0 <= n <= 7)
\0nn 带有八进制值 0 的字符 nn (0 <= n <= 7)
\0mnn 带有八进制值 0 的字符 mnn(0 <= m <= 3、0 <= n <= 7)
\xhh 带有十六进制值 0x 的字符 hh
\uhhhh 带有十六进制值 0x 的字符 hhhh
\t 制表符 ('\u0009')
\n 新行(换行)符 ('\u000A')
\r 回车符 ('\u000D')
\f 换页符 ('\u000C')
\a 报警 (bell) 符 ('\u0007')
\e 转义符 ('\u001B')
\cx 对应于 x 的控制符
\\ 反斜线字符
\0n 带有八进制值 0 的字符 n (0 <= n <= 7)
\0nn 带有八进制值 0 的字符 nn (0 <= n <= 7)
\0mnn 带有八进制值 0 的字符 mnn(0 <= m <= 3、0 <= n <= 7)
\xhh 带有十六进制值 0x 的字符 hh
\uhhhh 带有十六进制值 0x 的字符 hhhh
\t 制表符 ('\u0009')
\n 新行(换行)符 ('\u000A')
\r 回车符 ('\u000D')
\f 换页符 ('\u000C')
\a 报警 (bell) 符 ('\u0007')
\e 转义符 ('\u001B')
\cx 对应于 x 的控制符
[abc] a、b 或 c(简单类)
[^abc] 任何字符,除了 a、b 或 c(否定)
[a-zA-Z] a 到 z 或 A 到 Z,两头的字母包括在内(范围)
[a-d[m-p]] a 到 d 或 m 到 p:[a-dm-p](并集)
[a-z&&[def]] d、e 或 f(交集)
[a-z&&[^bc]] a 到 z,除了 b 和 c:[ad-z](减去)
[a-z&&[^m-p]] a 到 z,而非 m 到 p:[a-lq-z](减去)
. 任何字符(与行结束符可能匹配也可能不匹配)
\d 数字:[0-9]
\D 非数字: [^0-9]
\s 空白字符:[ \t\n\x0B\f\r]
\S 非空白字符:[^\s]
\w 单词字符:[a-zA-Z_0-9]
\W 非单词字符:[^\w
regex
案例
异常
Throwable⭐⭐⭐
Error
严重的错误 基本上一个项目中 遇到一个Error 这个项目基本会挂了。 出现了Error 就要修改代码。
只要一遇到 立刻找到 错误的地方, 并且研究为什么出现错误, 改正代码。
只要一遇到 立刻找到 错误的地方, 并且研究为什么出现错误, 改正代码。
Error 错误类(Throwable 直接子类)
错误处理 因为非常的严重 尽量避免
错误处理 因为非常的严重 尽量避免
StackOverflowError
栈溢出
OutOfMemoryError
内存溢出错误
Exception
Exception 异常类 (Throwable 直接子类)
异常的处理 不可避免的 因为你是跟用户的交互出现的问题 提前预防
异常的处理 不可避免的 因为你是跟用户的交互出现的问题 提前预防
运行期异常 RuntimeException
当出现这种问题 jvm 会作何反应呢?
1:打印出 异常类名 异常原因 异常的位置
2:终止程序 (当前线程)
可以不管: 因为终止的当前线程,因为一个程序中有很多条线程。 一条停了没啥关系。
可以管: 不想让异常 影响到 当前线程继续执行。
1:打印出 异常类名 异常原因 异常的位置
2:终止程序 (当前线程)
可以不管: 因为终止的当前线程,因为一个程序中有很多条线程。 一条停了没啥关系。
可以管: 不想让异常 影响到 当前线程继续执行。
捕获异常
try{
}catch(异常类型 变量名){
做一些提示的动作。
}
}catch(异常类型 变量名){
做一些提示的动作。
}
多个catch
一个catch 但是catch里面 写Exception
catch里面 e.printStackTrace
catch到之后 后面的代码继续运行
案例
案例一
案例二
编译期异常
当出现这种问题 jvm 会作何反应呢?
编译报错,
编译错误的后果 --不会出现class文件 而运行的是class文件,所以 无法运行。
只能管了,因为不管,程序无法运行。
1:如果只让编译通过 , 在这个代码的方法声明上 写 throws 异常
2:我们不止让编译通过,还要防止后续运行时报错 终止程序。 用trycatch
编译报错,
编译错误的后果 --不会出现class文件 而运行的是class文件,所以 无法运行。
只能管了,因为不管,程序无法运行。
1:如果只让编译通过 , 在这个代码的方法声明上 写 throws 异常
2:我们不止让编译通过,还要防止后续运行时报错 终止程序。 用trycatch
不是给用户设置的 是给程序员看的
检查异常 提示异常
如果程序员检查了之后没有问题 throws 此时编译不再报错
当我们编译期间检查不出来的话 可以trycatch
案例
案例一
案例二
异常的设计理念
运行期异常:
实 并不是 程序员出的错。 是用户出的错。
我们无法预料。 所以只能提前加 trycatch 防止啊。
我们无法预料。 所以只能提前加 trycatch 防止啊。
编译期异常:
其实 并不是针对用户的, 针对程序员的。 往往 是程序员容易犯的错。
Java的设计者 提示程序员 注意检查。
Java的设计者 提示程序员 注意检查。
自定义异常
因为我们有时候要设计一些类 让其他人来调用。 如果对方传入一些非法的参数,我们可以故意的制造异常 让对方停止程序
异常的名字应该体现出 具体的业务 所以jdk不提供这种业务的异常名字 ,所以我们需要自定义异常
传入异常信息或者原因, 定义一个 有参构造方法 super(信息)
案例
抛出异常throw
throw new 异常();
声明异常throws
修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{ }
extends Exception 编译期异常
extends RuntimeException 运行期异常
自定义异常
throw 和 throws 的区别
throw是制造异常对象,并使得当前线程终止
throws 是声明此方法有某种异常。 起到提示虚拟机 我已经检查异常的目的。
finally 代码块
有一些特定的代码无论异常是否发生,都需要执行。
另外,因为异常会引发程序跳转,导致有些语句执行不到。
而finally就是解决这个问题的,在finally代码块中存放的代码都是一定会被执行的。
另外,因为异常会引发程序跳转,导致有些语句执行不到。
而finally就是解决这个问题的,在finally代码块中存放的代码都是一定会被执行的。
finally的语法:
try...catch....finally:自身需要处理异常,最终还得关闭资源。
注意:finally不能单独使用。
算法
二分法
前提: 被查找的数组 必须是有序的。 否则不能会用二分法查找的。
基本思想
也称为是折半查找,
属于有序查找算法。
用给定值先与中间结点比较。
属于有序查找算法。
用给定值先与中间结点比较。
比较完之后有三种情况:
- 相等
说明找到了
- 要查找的数据比中间节点小
说明要查找的数字在中间节点左边
- 要查找的数据比中间节点大
说明要查找的数字在中间节点右边
案例
二分法
冒泡排序⭐⭐⭐⭐
基本思想
冒泡排序(Bubble Sort)也是一种简单直观的排序算法。
它重复的遍历过要排序的数列,一次比较相邻的
两个元素,如果他们的顺序错误就把他们交换过来。
这个算法的名字由来是因为越大的元素会经由交换慢慢"浮"到最后面。
它重复的遍历过要排序的数列,一次比较相邻的
两个元素,如果他们的顺序错误就把他们交换过来。
这个算法的名字由来是因为越大的元素会经由交换慢慢"浮"到最后面。
算法步骤
1. 相邻的元素两两比较,大的放右边,小的放左边
2. 第一轮比较完毕之后,最大值就已经确定,第二轮可以少循环一次,后面以此类推
3. 如果数组中有n个数据,总共我们只要执行n-1轮的代码就可以
案例
冒泡排序
选择排序⭐⭐⭐⭐
算法步骤
1. 从0索引开始,跟后面的元素一一比较
2. 小的放前面,大的放后面
3. 第一次循环结束后,最小的数据已经确定
4. 第二次循环从1索引开始以此类推
5. 第三轮循环从2索引开始以此类推
6. 第四轮循环从3索引开始以此类推。
案例
选择排序
递归⭐⭐⭐⭐
基本思想
1:递归一定要有出口(返回具体的值)
2:递归的过程就是 把问题逐渐缩小化的过程, 最终涌向出口
3:不断的压栈,最后再不断弹栈 递归每次的参数传递 要去靠近出口
4:递归就算有出口 次数也不能太多 大概最大到 18000次左右
案例
递归求1-100的和
递归求10的阶乘
快速排序
(难) : 用到递归。
案例
快排
时间复杂度
O(1) 常数
O(n) 一次循环
O(n^2) 循环嵌套
O(log2n) 二分法的时间复杂度
7种排序算法
4种简单排序
直接插入
O(n^2)
冒泡
O(n^2)
选择
O(n^2)
希尔
O(n^1.3)
3种高级排序
快速
O(nlog2n)
堆
O(nlog2n)
归并
O(nlog2n)
集合⭐⭐⭐⭐⭐
java中容器
变量
数组
集合
集合体系
Collection -- 单列集合顶层接口
List -- 接口 不去重 有索引
List
List 特有方法
void add(int index , E e); 指定位置添加
E remove(int index); 指定位置删除
E set(int index , E e); 指定位置修改
E get(int index); 指定位置获取
E remove(int index); 指定位置删除
E set(int index , E e); 指定位置修改
E get(int index); 指定位置获取
因为他有这个 通过索引获取元素的 get方法
所以 List 体系的所有集合, 多了 一种 通过普通for循环的遍历方式。
所以 List 体系的所有集合, 多了 一种 通过普通for循环的遍历方式。
案例
ArrayList 类
自定义了 固定长度的ArrayList集合
案例
自定义了 可变长度的ArrayList集合
ArrayList 的底层源码
底层数组初始长度是10,
每次扩容1.5倍(下面的代码是我自己写的,每次扩容原来的二倍)
底层数组初始长度是10,
每次扩容1.5倍(下面的代码是我自己写的,每次扩容原来的二倍)
案例
LinkedList 类
特有方法:头和尾相关的方法
因为 LinkedList 底层是双向链表。 操作里面的元素, 都要从头往后 或者从后往前找。
因为 LinkedList 底层是双向链表。 操作里面的元素, 都要从头往后 或者从后往前找。
自定义双向链表
链表的结点使用一个内部类对象表示
每个节点有三个属性
定义了他的迭代器遍历
LinkedList特有方法
public void addFirst(E e) 在该列表开头插入指定的元素
public void addLast(E e) 将指定的元素追加到此列表的末尾
public E getFirst() 返回此列表中的第一个元素
public E getLast() 返回此列表中的最后一个元素
public E removeFirst() 从此列表中删除并返回第一个元素
public E removeLast() 从此列表中删除并返回最后一个元素
public void addLast(E e) 将指定的元素追加到此列表的末尾
public E getFirst() 返回此列表中的第一个元素
public E getLast() 返回此列表中的最后一个元素
public E removeFirst() 从此列表中删除并返回第一个元素
public E removeLast() 从此列表中删除并返回最后一个元素
案例
自定义LinkedList集合
案例
LinkedList的源码分析
数据结构 中 只有数组有索引,
双向链表是没有索引的,也就是
说 LinkedList的底层是没有索引的
双向链表是没有索引的,也就是
说 LinkedList的底层是没有索引的
但是 LinkedList 是 List 接口的子类 那么他就具备 List 的那些方法啊。
LinkedList 底层是怎么 模拟索引的
LinkedList 的源码总结:大致理解就可以了
1:LinkedList 底层是用的双向链表
2:LinkedList 存储进去的元素, 他给你把元素封装为了 Node 对象。
(Node 对象中 封装三个属性 当前值 上一个元素的地址 下一个元素的地址值)
(Node 对象中 封装三个属性 当前值 上一个元素的地址 下一个元素的地址值)
3:LinkedList 只能从头往后 或者从后往前找 但是他却提供了一个 get(int index) 的方法。
LinkedList<String> ll = new LinkedList<>();
ll.add("a");
ll.add("b");
ll.add("c");
String str = ll.get(1); // 这岂不是也能通过索引获取元素吗?
// 为什么LinkedList 提供了一个get方法呢。 因为他实现了List接口 , 而List接口中有一个get方法。
// 但是 LinkedList 的get方法底层 绝对不是通过索引拿的,
// 而是先去判断 你拿的位置是属于 链表的前半部分 还是后半部分。
// 如果属于前半部分 就会从第一个往后找, 如果属于后半部分 就会从最后一个往前找。
ll.add("a");
ll.add("b");
ll.add("c");
String str = ll.get(1); // 这岂不是也能通过索引获取元素吗?
// 为什么LinkedList 提供了一个get方法呢。 因为他实现了List接口 , 而List接口中有一个get方法。
// 但是 LinkedList 的get方法底层 绝对不是通过索引拿的,
// 而是先去判断 你拿的位置是属于 链表的前半部分 还是后半部分。
// 如果属于前半部分 就会从第一个往后找, 如果属于后半部分 就会从最后一个往前找。
Set -- 接口 存储元素自动去重 没有索引(存取无序)
HashSet 类
HashSet集合概述和特点
底层数据结构是哈希表
存取无序
不可以存储重复元素
没有索引,不能使用普通for循环遍历
存储的原理
先拿着哈希值 对应到 哈希表的数组上 哈希算法
如果哈希表位置上没有值 就直接存
如果哈希表对应的位置上 有值, 就依次equals比较链表
哈希值
是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
HashCode方法的返回值 就是 哈希值
Object类中的public int hashCode():返回对象的哈希码值
HashCode方法 是 Object 的, 所有类的对象 都是可以调用HashCode方法的。
Object 的HashCode方法 是底层调用的一个本地函数, 他是通过你的对象的地址值
通过一些算法得到一个唯一的数字, 不同的对象 这个数字是绝对不会相同的。
Object 的HashCode方法 是底层调用的一个本地函数, 他是通过你的对象的地址值
通过一些算法得到一个唯一的数字, 不同的对象 这个数字是绝对不会相同的。
哈希值的特点
- 同一个对象多次调用hashCode()方法返回的哈希值是相同的
- 默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同
类 继承了 Object 也是可以重写 HashCode方法的, 重写之后,HashCode方法的返回值 就不再代表地址了。
HashSet 去重原理
底层 是哈希表结构:
他什么保证元素唯一的呢?
HashSet 存储元素的时候:
首先拿着元素的 HashCode值,和哈希表所有的哈希值去比。
看看哈希表中,有没有存在存在和元素的哈希值相同的元素;
看看哈希表中,有没有存在存在和元素的哈希值相同的元素;
如果不存在 : 就直接把这个元素 添加到哈希表中
如果存在: 就让这个元素 拿着自己的equals方法,和哈希值相同的那些元素,依次去equals
如果比了一遍:都是false 则添加到集合中
如果比了一遍:有返回true的 则不添加集合(替换)
解释HashSet存储元素的流程,以及只重写equals方法是否可取
如何重写hashCode方法
哈希表比单数组和单链表强在哪
HashSet的add方法的源码解析
HashSet去重 依赖hashCode和equals方法
如果咱们让hashSet存储 Student对象
如果我认为 学生里面 姓名 年龄 相同了 我就认为是同一个人。
则Student 需要重写HashCode和equals方法
如果我认为 学生里面 姓名 年龄 相同了 我就认为是同一个人。
则Student 需要重写HashCode和equals方法
重写hashCode
不能不重写
不能返回固定值
让属性相同的元素返回相同的哈希值,让属性不同的元素返回不同的哈希值
return 31*name.hashCode() + age;
重写equals方法
小结
HashSet集合存储自定义类型元素,要想实现元素的唯一,要求必须重写hashCode方法和equals方法
LinkedHashSet
去重 存取有序 无索引
案例
TreeSet 类
排序方法
使用 自然排序Comparable
如果你想存储 自定义对象(学生对象),
因为TreeSet 有按照规则排序的特点,
那么你在自定义类中,必须提供对应的排序规则。
因为TreeSet 有按照规则排序的特点,
那么你在自定义类中,必须提供对应的排序规则。
怎么提供呢?
只需要让 自定义类 实现 Comparable 接口 重写里面的 compareTo方法,
并把规则写在方法里面就可以了,这种实现Comparable的排序方式称为自然排序。
并把规则写在方法里面就可以了,这种实现Comparable的排序方式称为自然排序。
自然排序这种方式相当于是: 我这个TreeSet容器 存储学生对象,
然而你学生对象 自己就具备 规则(compareTo),所以就给你排了。
然而你学生对象 自己就具备 规则(compareTo),所以就给你排了。
案例
String和Integer也实现了Comparable接口
案例1
案例2
有一个学生类, 有这些属性,姓名name, 数学成绩 math , 英语成绩 English , 语文 Chinese
有5个学生, 按照学生的总成绩 从大到小排序。
如果总成绩相同, 请按照 数学排
如果总成绩同了 数学也同了,语文排。
语文也同了,安英语排。
英语也同了,按姓名排。
姓名也同了,就是同一个人 去除掉。
有5个学生, 按照学生的总成绩 从大到小排序。
如果总成绩相同, 请按照 数学排
如果总成绩同了 数学也同了,语文排。
语文也同了,安英语排。
英语也同了,按姓名排。
姓名也同了,就是同一个人 去除掉。
案例3
在集合里面 存储10个 1-20的随机数。
然后不能有重复,然后 从小到大排序。 打印出来。
然后不能有重复,然后 从小到大排序。 打印出来。
使用 比较器排序Comparator(比较器> 自然排序)
TreeSet(Comparator comparator) :根据指定的比较器进行排序
案例1
在集合里面 存储10个 1-20的随机数。
然后不能有重复,然后 从大到小排序。 打印出来。
然后不能有重复,然后 从大到小排序。 打印出来。
案例2
按照学生年龄排序,
当年龄相同就按照姓名排序
当年龄相同就按照姓名排序
两种比较方式总结
两种比较方式小结
- 自然排序: 自定义类实现Comparable接口,重写compareTo方法,根据返回值进行排序
- 比较器排序: 创建TreeSet对象的时候传递Comparator的实现类对象,重写compare方法,根据返回值进行排序
- 在使用的时候,默认使用自然排序,当自然排序不满足现在的需求时,必须使用比较器排序
两种方式中关于返回值的规则
- 如果返回值为负数,表示当前存入的元素是较小值,存左边
- 如果返回值为0,表示当前存入的元素跟集合中元素重复了,不存
- 如果返回值为正数,表示当前存入的元素是较大值,存右边
TreeSet add方法的源码解析
自然排序源码解析
比较器排序源码解析
单列集合的共性内容
Collection的共性的方法
boolean add(E e) 添加元素
boolean remove(Object o) 从集合中移除指定的元素
boolean removeif(Predicate o) 根据条件进行删除
void clear() 清空集合
boolean contains(Object o) 判断集合中是否存在指定的元素
boolean isEmpty() 判断集合是否为空
int size() 集合的长度,也就是集合中元素的个数
boolean remove(Object o) 从集合中移除指定的元素
boolean removeif(Predicate o) 根据条件进行删除
void clear() 清空集合
boolean contains(Object o) 判断集合中是否存在指定的元素
boolean isEmpty() 判断集合是否为空
int size() 集合的长度,也就是集合中元素的个数
add
remove
案例:删除所有的a元素
针对ArrayList
正着遍历 remove(i) i--
O(n)
倒着遍历 remove(i) 不用i--
O(n)
while(al.remove("a"));
O(n^2)
针对共性
迭代器 it.remove();
O(n)
removeIf
O(n)
removeif
源码解析
迭代器
clear
contains
isEmpty
size
共性编译方式
迭代器 iterator
迭代器的原理 指针
迭代器共性遍历
案例
连续调用两次next
迭代器遍历删除
案例
所有的a元素删除掉
增强for循环
格式 底层是迭代器
增强for可以遍历数组
实现了Iterable接口的所有的子类对象都可以作为for的目标
增强for和普通for的比较:
案例
重写iterator方法 返回一个迭代器对象
案例
自定义了 固定长度的ArrayList集合
自定义可变长度的ArrayList集合
Map -- 双列集合顶层接口
Map
双列集合特点
interface Map<K,V> K:键的类型;V:值的类型
双列集合,一个键对应一个值
键不可以重复,值可以重复
Map的方法
V put(K key,V value)
添加元素
添加一个键值对, 会去找 如果这个集合中没有这个键 就添加键值对,并返回一个null
如果这个集合中已经有即将添加的键了, 就让即将添加的键对应的值 把已经存在的键值对的 那个值 替换掉,并返回被替换的值
V remove(Object key)
根据键删除键值对元素
返回删除键的值
boolean isEmpty()
判断集合是否为空
void clear()
移除所有的键值对元素
boolean containsKey(Object key)
判断集合是否包含指定的键
boolean containsValue(Object value)
判断集合是否包含指定的值
int size()
集合的长度,也就是集合中键值对的个数
Map集合的获取功能
V get(Object key)
根据键获取值
Set<K> keySet()
获取所有键的集合
Collection<V> values()
获取所有值的集合
Set<Map.Entry<K,V>> entrySet()
获取所有键值对对象的集合
案例
Map的遍历方式
遍历方式一:根据键找值(时间复杂度n^2)
步骤分析
- 获取所有键的集合。用keySet()方法实现
- 遍历键的集合,获取到每一个键。用增强for实现
- 根据键去找值。用get(Object key)方法实现
案例
遍历方式二:根据键值对找键和值(时间复杂度n)
步骤分析
- 获取所有键值对对象的集合
- Set<Map.Entry<K,V>> entrySet():获取所有键值对对象的集合
- 遍历键值对对象的集合,得到每一个键值对对象
- 用增强for实现,得到每一个Map.Entry
- 根据键值对对象获取键和值
- 用getKey()得到键
- 用getValue()得到值
案例
遍历方式三: Map 接口提供了一个 default 的方法 foreach
(底层是用的遍历方式二)
(底层是用的遍历方式二)
案例
HashMap 类
HashMap集合概述和特点
- HashMap底层是哈希表结构的
- 依赖hashCode方法和equals方法保证键的唯一
- 如果键要存储的是自定义对象,需要重写hashCode和equals方法
- 依赖hashCode方法和equals方法保证键的唯一
- 如果键要存储的是自定义对象,需要重写hashCode和equals方法
HashSet 的底层 是使用的
HashMap 的键来完成的
值 直接置为null
HashMap 的键来完成的
值 直接置为null
如果我们想使用 HashMap
在键位置 存储 Student 对象。
在键位置 存储 Student 对象。
1:如果 我们认为 每 new 一个 Student对象 就是一个
不同的对象,就不能去重。 不需要重写 equals HashCode方法
不同的对象,就不能去重。 不需要重写 equals HashCode方法
2:如果 我们认为 Student的 姓名和年龄 如果相同了 就认为是
同一个对象 需要去掉重复。 需要重写 equals HashCode方法
同一个对象 需要去掉重复。 需要重写 equals HashCode方法
案例
案例-统计字符个数
代码
LinkedHashMap
HashTable
Properties
是一个双列集合,键和值都存储字符串
TreeMap 类
TreeMap集合概述和特点
- TreeMap底层是红黑树结构
- 依赖自然排序或者比较器排序,对键进行排序
- 如果键存储的是自定义对象,需要实现Comparable接口或者在创建TreeMap对象时候给出比较器排序规则
- 依赖自然排序或者比较器排序,对键进行排序
- 如果键存储的是自定义对象,需要实现Comparable接口或者在创建TreeMap对象时候给出比较器排序规则
TreeSet 的底层 是使用的
TreeMap 的键来完成的
值直接置为null
TreeMap 的键来完成的
值直接置为null
如果我们想使用 TreeMap
在键位置 存储 Student对象。
在键位置 存储 Student对象。
1: 如果Student 啥都不继承 啥都不实现。
直接 让TreeMap 的键位置存储 。 请问有没有事情发生?
运行报错 因为 TreeMap的键 是红黑树结构 是需要排序的
然而 Student没有提供排序规则 所以 存储添加的时候就报错。
直接 让TreeMap 的键位置存储 。 请问有没有事情发生?
运行报错 因为 TreeMap的键 是红黑树结构 是需要排序的
然而 Student没有提供排序规则 所以 存储添加的时候就报错。
2: 所以必须提供排序规则
① 让 Student 实现Comparable 接口
重写 CompareTo方法 一个参数 this-参数 升序
重写 CompareTo方法 一个参数 this-参数 升序
② 创建 TreeMap 的时候 在构造方法的参数上 传一个
Comparator 的子类对象 compare 两个采纳数 o1-o2 升序
Comparator 的子类对象 compare 两个采纳数 o1-o2 升序
案例1
案例2
Stream流
Stream 流(流水线) 用来优化操作集合代码
Stream流 是操作集合的
Stream流 是操作集合的
Stream流的原理是基于:匿名内部类的延迟执行现象
分为三类方法
生成流方法
Stream流的常见生成方式
1:Collection体系的集合可以使用默认方法stream()生成流default Stream<E> stream()
2:Map体系的集合间接的生成流
entrySet
3:数组可以通过Stream接口的静态方法of(T... values)生成流
可以通过Arrays中的静态方法stream生成流
案例
中间操作方法
Stream流的中间操作
Stream<T> filter(Predicate predicate):用于对流中的数据进行过滤
Predicate接口中的方法 boolean test(T t):对给定的参数进行判断,返回一个布尔值
案例
过滤Stream中的元素,返回一个新的Stream,其中只包含满足条件的元素
Stream<T> limit(long maxSize):返回此流中的元素组成的流,截取前指定参数个数的数据
截取Stream中前N个元素
Stream<T> skip(long n):跳过指定参数个数的数据,返回由该流的剩余元素组成的流
跳过Stream中前N个元素
案例
static <T> Stream<T> concat(Stream a, Stream b):合并a和b两个流为一个流
Stream<T> distinct():返回由该流的不同元素(根据Object.equals(Object) )组成的流
去重Stream中的元素,返回一个新的
Stream,其中不包含重复的元素
Stream,其中不包含重复的元素
distinct()是Stream接口的方法
distinct()使用hashCode()和equals()方法来获取不同的元素
因此,我们的类必须实现hashCode()和equals()方法
distinct()使用hashCode()和equals()方法来获取不同的元素
因此,我们的类必须实现hashCode()和equals()方法
案例
Stream<T> sorted():返回由此流的元素组成的流,根据自然顺序排序
Stream<T> sorted(Comparator comparator):返回由该流的元素组成的流,根据提供的Comparator进行排序
Comparator接口中的方法 int compare(T o1, T o2)
案例
<R> Stream<R> map(Function mapper):返回由给定函数应用于此流的元素的结果组成的流
Function接口中的方法 R apply(T t)
将Stream中的每个元素映射为另一个元素,
返回一个新的Stream,其中包含映射后的元素
返回一个新的Stream,其中包含映射后的元素
IntStream mapToInt(ToIntFunction mapper):返回一个IntStream其中包含将给定函数应用于此流的元素的结果
IntStream:表示原始 int 流
ToIntFunction接口中的方法 int applyAsInt(T value)
案例
1
2
3
flatMap
将Stream中的每个元素映射为一个Stream,
然后将这些Stream合并为一个新的Stream
然后将这些Stream合并为一个新的Stream
map 这 一转一
flatMap 这 一转多
flatMap 这 一转多
案例
Stream<T> peek(Consumer<? super T> action)
在Stream中的每个元素上执行一些操作,
返回一个新的Stream,其中包含原来的元素
返回一个新的Stream,其中包含原来的元素
其中,action是一个Consumer接口类型的参数,
用于定义要在Stream中的每个元素上执行的操作。例如
用于定义要在Stream中的每个元素上执行的操作。例如
List<String> names = Arrays.asList("Tom", "Jerry", "Mickey", "Donald");
names.stream()
.peek(System.out::println) // 打印每个元素
.map(String::toUpperCase) // 将元素转换为大写
.forEach(System.out::println); // 打印转换后的元素
names.stream()
.peek(System.out::println) // 打印每个元素
.map(String::toUpperCase) // 将元素转换为大写
.forEach(System.out::println); // 打印转换后的元素
案例
现在有3个班级,每个班级有3个组。 al1 al2 al3 分别代表的每个班级, 班级存储了3个组的成绩。
1:要求把 每个班级的 每个人的成绩 提取出来 组成一个Stream, 每个班一个Stream 总共三个Stream
2:将这三个Stream 合并成一个Stream
3:把每个成绩 封装成一个 Student对象(Student的score属性就是成绩)。 形成一个 Stream<Student>流
4:发现有很多学生的成绩都是个位数,那是因为 他们的选择题分数没有被加上, 所以 每个小于10的Student的成绩 都加上50
5:把Student 从大到小排序 ,要求不能去重。
6:所有Student 只选前10名打印出来。
1:要求把 每个班级的 每个人的成绩 提取出来 组成一个Stream, 每个班一个Stream 总共三个Stream
2:将这三个Stream 合并成一个Stream
3:把每个成绩 封装成一个 Student对象(Student的score属性就是成绩)。 形成一个 Stream<Student>流
4:发现有很多学生的成绩都是个位数,那是因为 他们的选择题分数没有被加上, 所以 每个小于10的Student的成绩 都加上50
5:把Student 从大到小排序 ,要求不能去重。
6:所有Student 只选前10名打印出来。
答
终结方法
Stream流的常见终结操作方法
void forEach(Consumer action):对此流的每个元素执行操作
Consumer接口中的方法 void accept(T t):对给定的参数执行此操作
forEach() 方法是一个终止操作,执行后会使 Stream 流变为关闭状态,因此它不能再用于后续的操作
long count():返回此流中的元素数
案例
Stream流的收集方法
R collect(Collector collector)它是通过工具类Collectors提供了具体的收集方式
public static <T> Collector toList():把元素收集到List集合中
public static <T> Collector toSet():把元素收集到Set集合中
public static Collector toMap(Function keyMapper,Function valueMapper):把元素收集到Map集合中
案例
数据结构
数据结构的介绍
数据结构: 数据在容器中的排列方式 就叫做数据结构。
数据结构与算法
拿空间换时间 拿时间换空间。
排列方式:
菜鸟驿站 房屋-容器
快递-数据
快递-数据
堆满 --- 优 空间占用率高 缺点 查询慢。
平铺 --- 优 查询快 缺点 空间利用率低。
货架 --- 空间利用率 查询不满
菜鸟驿站
一个门
两个门
案例
题目的思想 :拿时间换空间, 拿空间换时间
题: 有一个容器 int[], 里面存储了1万个 连续的数 0-9999 (数的顺序是打乱的) , 但是这些数里面 其中一个数 是缺失的, 有一个数 是重复的。
比如: 0 1 2 3 7 5 6 7 8 9 -- 7 是重复的 4是缺失的。
顺序乱的: 9 6 7 3 2 7 5 1 0 8 -- 7 是重复的 4是缺失的。
请你 用最快速的方法, 找到 那个缺失的数, 找到那个重复的数。
本题的时间复杂度最快 2n 就可以解决。
比如: 0 1 2 3 7 5 6 7 8 9 -- 7 是重复的 4是缺失的。
顺序乱的: 9 6 7 3 2 7 5 1 0 8 -- 7 是重复的 4是缺失的。
请你 用最快速的方法, 找到 那个缺失的数, 找到那个重复的数。
本题的时间复杂度最快 2n 就可以解决。
研究进出顺序
栈 -先进后出
队列 -先进先出去
列表
数组 -有索引 增删慢
数组的get(int index)的方法 O(常数)
链表 -无索引 增删快
分类
单向链表
空间小 效率慢
双向链表
空间大 效率高
LinkedList底层 就是用双向链表
底层也提供了get(int index)方法 ,但是底层是模拟的索引 O(n)
普通for带索引的 遍历数组 O(n) 遍历链表 O(n^2)
当我们不确定 遍历的方式快慢的时候 优先选用迭代器增强for
哈希表 -兼具查询和增删效率
横向是数组
纵向是链表
菜鸟驿站 就是典型的 哈希表思维
树-查找快
左小 右边大
每次查询都是 二分法 log2n 走128次 能够遍历沙漠里面的每一粒沙子
二叉树:
二查查找树
平衡二叉树
红黑树
原理
就是2-3树的 双节点 拆开 产生了两个节点 通过红色链接
红黑树的定义
红黑树是含有红黑链接并
满足下列条件的二叉查找树:
满足下列条件的二叉查找树:
1. 红链接均为左链接;
2. 没有任何一个结点同时和两条红链接相连;
3. 该树是完美黑色平衡的,即任意空链接到根结点的路径上的黑链接数量相同;
规则
红色链接都在左侧(右侧)
添加新节点都是红色链接
当红色链接出现在了右侧 进行左旋
左旋
当某个结点的左子结点为黑色,右子结点为红色,此时需要左旋。因为红黑树的规定是 所有的红色链接都在左侧
不可以出现连续的两个红链接
出现了这种情况 右旋
右旋
当某个结点的左子结点是红色,且左子结点的左子结点也是红色,需要右旋,因为红黑树的定义里面规定,不可以出现连续的两个红链接。
右旋之后 必反转
右旋后颜色反转
由于右旋后左右两侧的连接都是红色连接, 这在红黑树中是不允许的,所以需要进行颜色反转,下面是颜色反转的原理(对比着2-3树去记)。
根结点颜色反转
红黑树插入元素案例
依次添加 10 6 8 3 1 4 7
2-3树 三叉树
2-3树的性质
1. 任意空链接到根结点的路径长度都是相等的。
2. 4-结点变换为3-结点时,树的高度不会发生变化,只有当根结点是临时的4-结点,分解根结点时,树高+1。
3. 2-3树与普通二叉查找树最大的区别在于,普通的二叉查找树是自顶向下生长,而2-3树是自底向上生长。
是什么
插入元素的 平衡过程
完全平衡的 从低向上生长
图 结构
记录数据之间的关系
泛型
泛型的介绍
jdk1.5的新特性
泛指的 泛泛的 类型 。 泛指某一种类型。(必须是引用类型)
泛型的作用,要求集合只能存储一种类型
泛型的底层是Object 不明确泛型就是Object
设计泛型时 只能用 不能new创建对象
用的时候只能当Object用,除非用前先判断是什么类型
用的时候只能当Object用,除非用前先判断是什么类型
明确时机:
1: 有时候,创建一个对象的时候, 明确泛型。
2: 有的时候, 调用一个方法的时候,明确泛型。
3: 有的时候, 创建一个类,然后类实现一个接口的时候,明确泛型。
2: 有的时候, 调用一个方法的时候,明确泛型。
3: 有的时候, 创建一个类,然后类实现一个接口的时候,明确泛型。
泛型类
案例
泛型方法
案例
泛型接口
案例
沿用父类的泛型
继承或者实现的时候 明确泛型
限定泛型
泛型限定?
?的泛型的集合 都不允许 去添加数据... 只能遍历数据
单独的? 一般不用 还不如不加泛型
会用限定的?
?extends Cat
?super Cat
? extents QQ
? super QQ
案例
QQ
QQ extends Cat
QQ extends Cat & Iterable & Face
不能用super限定
可变参数
在JDK1.5之后,如果我们定义一个方法需要接受多个参数,
并且多个参数类型一致,我们可以对其简化.
并且多个参数类型一致,我们可以对其简化.
格式:
修饰符 返回值类型 方法名(数据类型... 参数名){ }
修饰符 返回值类型 方法名(数据类型... 参数名){ }
底层:其实就是一个数组
方法的参数声明如果多个参数 可变参数必须得放在最后
可变参数在核心类库的应用
集合和数组的转换
数组 --> 集合
Arrays.asList
集合 --> 数组
of方法(jdk9之后出现的)
案例
IO流
根据数据的流向分为:
输入流
i 是in的意思
input 输入流
input 输入流
硬盘(永久数据) ----> 内存(实时动态数据)
网络中(永久数据)----> 内存(实时动态数据)
网络中(永久数据)----> 内存(实时动态数据)
输出流
o 是out的意思
output 输出流
output 输出流
内存(实时动态数据)----> 硬盘(永久数据)
内存(实时动态数据)----> 网络中(永久数据)
内存(实时动态数据)----> 网络中(永久数据)
根据数据的类型分为:
字节流
一切皆为字节
字节流是字节的搬运工 不做任何的额外操作
类型
输出流
字节输出流(OutputStream)
基本共性功能方法
- public void close() :关闭此输出流并释放与此流相关联的任何系统资源。
当完成流的操作时,必须调用此方法,释放系统资源
- public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
- public void write(byte[] b):将 b.length字节从指定的字节数组写入此输出流。
- public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
- public abstract void write(int b) :将指定的字节输出流。
FileOutputStream
构造方法
public FileOutputStream(File file):创建文件输出流以写入由指定的 File对象表示的文件。
public FileOutputStream(String name): 创建文件输出流以指定的名称写入文件。
写出字节数据
写出字节:write(int b) 方法,
每次可以写出一个字节数据
每次可以写出一个字节数据
1. 虽然参数为int类型四个字节,但是只会保留一个字节的信息写出。
2. 流操作完毕后,必须释放系统资源,调用close方法,千万记得。
2. 流操作完毕后,必须释放系统资源,调用close方法,千万记得。
写出字节数组:write(byte[] b)
每次可以写出数组中的数据
写出指定长度字节数组:write(byte[] b, int off, int len)
每次写出从off索引开始,len个字节
数据追加续写
public FileOutputStream(File file, boolean append): 创建文件输出流以写入由指定的 File对象表示的文件
public FileOutputStream(String name, boolean append): 创建文件输出流以指定的名称写入文件
这两个构造方法,参数中都需要传入一个boolean类型的值,true 表示追加数据,false 表示清空原有数据。
这样创建的输出流对象,就可以指定是否追加续写了
这样创建的输出流对象,就可以指定是否追加续写了
案例
ByteArrayOutputStream
PipedOutputStream等
缓冲输出流:
BufferedOutputStream类
构造方法: 必须传一个 具体能够操作文件的流。比如 FileOutputStream
为什么? 因为 这两个缓冲流, 他只提供缓冲作用,并不实际去操作文件。
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("a.txt"));
为什么? 因为 这两个缓冲流, 他只提供缓冲作用,并不实际去操作文件。
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("a.txt"));
字节缓冲流原理:
底层提供了一个 8192个长度的字节数组, 看似是
一次读写一个字节,其实是把字节存储到字节数组
中,等字节数组满了之后,写出到文件中。
一次读写一个字节,其实是把字节存储到字节数组
中,等字节数组满了之后,写出到文件中。
输入流
字节输入流(InputStream)
基本共性功能方法
- public void close() :关闭此输入流并释放与此流相关联的任何系统资源。
- public abstract int read(): 从输入流读取数据的下一个字节。
- public int read(byte[] b): 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。
FileInputStream
构造方法
FileInputStream(File file): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。
FileInputStream(String name): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。
当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有该文件,会抛出FileNotFoundException
读取字节数据
读取字节:
读取一个字节代码
循环读每个字节代码
方式一:不够简便
方式二:代码重复
方式三:有数据风险
方式四:完美
使用字节数组读取
读取一个字节数组代码
循环读取每个字节数组代码
使用数组读取,每次读取多个字节,减少了系统间的
IO操作次数,从而提高了读写的效率,建议开发中使用
IO操作次数,从而提高了读写的效率,建议开发中使用
复制文件
案例1-一次一个字节复制
案例2:一次一个字节数组
流的关闭原则:先开后关,后开先关。
ByteArrayInputStream
PipedInputStream等
缓冲输入流:
BufferedInputStream类
字节流注意事项
字节输出流创建对象的时候 如果a.txt 存在 则清空a.txt的内容, 如果不存在则新建一个。
FileOutputStream fos = new FileOutputStream("D:\\a.txt");
追加写入的话 需要在FileOutputStream的构造方法里面加一个true
FileOutputStream fos = new FileOutputStream("D:\\a.txt",true); //这样做 就不会清空 a.txt的内容 而是在他后面继续写。
如果写一个字符串出去的话 可以先把字符串转为字节数组
FileOutputStream fos = new FileOutputStream("D:\\a.txt");
//fos.write("abc"); //编译报错 不能直接写出字符串
String s = "abc";
byte[] bytes = s.getBytes(); //转换成 字节数组
fos.write(bytes);
fos.close();
写出换行
\ 是转义的意思:
\ 转义
\ 代表
\t table键
\r return 返回 --- 把光标返回到行首位置
\r 的用法:
System.out.print("abcde");
System.out.print("\r");
System.out.print("---");
// 这三句代码只能在 黑窗口中运行才能看出效果哈,在idea中运行看不出效果
\r\n 也是换行 他的意思是先把光标返回行首然后在开始新的一行,之所以还要先返回行首,是因为刚出现电脑的时候的编辑器是可以不从行首位置开始写的,但是现在已经没有那样的编辑器了。
\n newLine 新一行
\n 的用法:
流对象使用完成后必须要
记着 调用close方法关闭
记着 调用close方法关闭
流对象 是内存链接硬盘的通道。 这种流对象(通道)
如果你不手动关闭, 垃圾回收器 是不会回收的。
为了在任何的中断逻辑中都能保证close代码的执行 我们采用使用finally代码块
如果你不手动关闭, 垃圾回收器 是不会回收的。
为了在任何的中断逻辑中都能保证close代码的执行 我们采用使用finally代码块
finally使用格式:
finally的强大之处
使用finally把close代码包裹:字节输出流异常处理代码
第一版:有不足之处 第二版改进
第一版:有不足之处 第二版改进
第二版:有不足之处 第三版改进
第三版:有不足之处 第四版改进
第四版:有不足之处 第五版改进
第五版:完美版
jdk7的IO流异常处理的写法 :(简化书写的)
字符流
字符流 不是用来搬运的。 不是用来复制文件的。
而是用来只读取文字 或者 只写出文字的。
而是用来只读取文字 或者 只写出文字的。
字节流复制任意的文件(文本 图片 视频)
当使用字节流读取文本文件时,可能会有一个小问题。就是遇到中文字符时,
可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储。
所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件
可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储。
所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件
当我们只想把文本文件中的中文读取到程序中,
打印出来或者其他操作的时候。(并不想写到其他的文件中。)
这个时候字节流 根本就做不到。 为什么呢?
打印出来或者其他操作的时候。(并不想写到其他的文件中。)
这个时候字节流 根本就做不到。 为什么呢?
比如:GBK编码下 两个字节 一个字符
UTF-8编码下 两个字节一个字符
三个字节一个字符
四个字节 一个字符
所以这时候 就迫切的需要有字符流,帮我们
把说中文读取到程序中展示。 (字符流 一次读一个字符)
UTF-8编码下 两个字节一个字符
三个字节一个字符
四个字节 一个字符
所以这时候 就迫切的需要有字符流,帮我们
把说中文读取到程序中展示。 (字符流 一次读一个字符)
编码解码乱码的原理
按照什么样的字符集编码,就按照什么样的字符集解码,否则出现乱码。
详见视频讲解;
详见视频讲解;
编码表
详见如下链接
乱码原理分析(一)—常用编码底层详解:
乱码原理分析(一)—常用编码底层详解:
编码解码的方法
字符----> 根据编码翻译为 字节
字节----> 根据编码组合成 字符
类型
输入流
字符输入流(Reader):
基本共性功能方法
- public void close() :关闭此流并释放与此流相关联的任何系统资源。
- public int read(): 从输入流读取一个字符。
- public int read(char[] cbuf): 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。
InputStreamReader:
读取
一次一个字符读取
一次一个字符数组读取
FileReader:从文件读取字符流
OutputStreamWriter 和 FileWriter 的关系 ----子父类关系
构造方法
- FileReader(File file): 创建一个新的 FileReader ,给定要读取的File对象。
- FileReader(String fileName): 创建一个新的 FileReader ,给定要读取的文件的名称。
读取字符数据
读取字符:
虽然读取了一个字符,但是会自动提升为int类型
使用字符数组读取
注意
1. 字符编码:字节与字符的对应规则。
Windows系统的中文编码默认是GBK编码表。
idea中UTF-8
Windows系统的中文编码默认是GBK编码表。
idea中UTF-8
2. 字节缓冲区:一个字节数组,用来临时存储字节数据。
字符缓冲输出流BufferedReader
BufferedReader:带有缓冲区的字符输入流,读取效率高
案例
特有方法
public String readLine(): 读一行文字
BufferedReader 的readLine
输出流
字符输出流(Writer):
基本共性功能方法
- void write(int c) 写入单个字符。
- void write(char[] cbuf)写入字符数组。
- abstract void write(char[] cbuf, int off, int len)写入字符数组的某一部分,off数组的开始索引,len写的字符个数。
- void write(String str)写入字符串。
- void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数。
- void flush()刷新该流的缓冲。
- void close() 关闭此流,但要先刷新它。
OutputStreamWriter:
Writer 有五个方法
注意事项
1:创建输出流对象 如果a.txt 不存在
则创建一个,如果存在则 清空里面的内容
则创建一个,如果存在则 清空里面的内容
2:写出字符串 是原样输出
3: 字符流里面有缓冲区 缓冲区的作用是 用来把字符转换成字节的。
字符输出流把字符在缓冲区里转换成字节数据之后 再使用FileOutputStream写出去。
字符输出流把字符在缓冲区里转换成字节数据之后 再使用FileOutputStream写出去。
4:flush和close的区别:
//flush()刷新流。刷新完毕之后,还可以继续写数据
//close()关闭流。释放资源。一旦关闭,就不能写数据
// 底层在关闭之前也会调用一次flush方法。 所以即使不flush 直接调用close方法也是会刷新出去的。
案例
案例
将键盘录入的用户名和密码保存到本地实现永久化存储
要求:用户名独占一行,密码独占一行
要求:用户名独占一行,密码独占一行
FileWriter:向文件写入字符流
InputStreamReader 和 FileReader 的关系 --子父类关系
构造方法
- FileWriter(File file): 创建一个新的 FileWriter,给定要读取的File对象。
- FileWriter(String fileName): 创建一个新的 FileWriter,给定要读取的文件的名称。
基本写出数据
写出字符
注意
1. 虽然参数为int类型四个字节,但是只会保留一个字符的信息写出。
2. 未调用close方法,数据只是保存到了缓冲区,并未写出到文件中。
写出其他数据
写出字符数组
写出字符串
续写和换行:操作类似于FileOutputStream。
小贴士:
字符流,只能操作文本文件,不能操作图片,视频等非文本文件。
当我们单纯读或者写文本文件时 使用字符流 其他情况使用字节流
关闭和刷新
- flush :刷新缓冲区,流对象可以继续使用。
- close:先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。
即便是flush方法写出了数据,操作的最后还是要调用close方法,释放系统资源
字符缓冲输出流BufferedWriter
BufferedWriter:带有缓冲区的字符输出流,写入效率高
特有方法
BufferedWriter:public void newLine(): 写一行行分隔符,由系统属性定义符号
BufferedWriter 的newLine 跨平台的换行符
案例
1.73 JDK11之后的FileWriter和FileReader
目前的状况是
InputStreamReader 为了提高可读性 出现了 FileReader
但是 FileReader 只能使用默认UTF8了 如果我们要想
使用GBK 就还得继续使用 InputStreamReader
InputStreamReader 为了提高可读性 出现了 FileReader
但是 FileReader 只能使用默认UTF8了 如果我们要想
使用GBK 就还得继续使用 InputStreamReader
在jdk11的时候 jdk把 FileReader FileWriter 都改为了 可以指定编码了。
对象操作流(序列化流)
就是用来 把 对象 写出到文件中, 或者把 文件中的对象读取到内容中
类型
写出对象流(序列化流)
ObjectOutputStream
序列化操作
1. 一个对象要想序列化,必须满足两个条件:
- 该类必须实现java.io.Serializable 接口,Serializable 是一个标记接口,
不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException 。
不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException 。
- 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,
则该属性必须注明是瞬态的,使用transient 关键字修饰。
则该属性必须注明是瞬态的,使用transient 关键字修饰。
InvalidClassException
2.写出对象方法
- public final void writeObject (Object obj) : 将指定的对象写出。
读入对象流(反序列化流):
ObjectInputStream
ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。
反序列化操作1
如果能找到一个对象的class文件,我们可以进行
反序列化操作,调用ObjectInputStream读取对象的方法
反序列化操作,调用ObjectInputStream读取对象的方法
public final Object readObject () : 读取一个对象
对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 ClassNotFoundException 异常
反序列化操作2
另外,当JVM反序列化对象时,能找到class文件,但是class文件在
序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException异常
序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException异常
这个异常的原因如下:
- 该类的序列版本号与从流中读取的类描述符的版本号不匹配
- 该类包含未知数据类型
- 该类没有可访问的无参数构造方法
- 该类的序列版本号与从流中读取的类描述符的版本号不匹配
- 该类包含未知数据类型
- 该类没有可访问的无参数构造方法
Serializable 接口给需要序列化的类,提供了一个序列版本号。
serialVersionUID 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。
serialVersionUID 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。
注意事项
1:序列号的问题:
当我们把一个 Student对象写出到文件中。 之后我们对 Student 这个类 修改了。
再次读取出来的学生对象,就和 Student类 对应不上了。 这时候就会报错。
怎么解决这个情况。
在Student类里面放一个 变量 名字是规定好的。 serialVersionUID 。 定义这个类的版本号。
你写出的对象,这个对象是根据什么样的版本号的类 创建出来的。
那么再次读出来的时候, 就会去找 对应的版本号的类, 能匹配多少就匹配多少。
class Student implements Serializable{
private static final long serialVersionUID = 213123123L;
private String name;
private String sex;
}
2:transient : 被这个关键字修饰的内容,是不运行持久化的。
class Student implements Serializable{
public static final long seriaVersionUID = 213123123L;
private String name;
private String sex;
private transient int age; // age属性就不会让你写出到文件中。 读出Student的时候 也没有age 所以读出来的Student对象 age是0
}
3:反序列化多个文件的做法
方法一
方法二
Properties
引入
Map
HashMap
线程不安全的,多个线程共享的时候
HashTable
线程安全的,多个线程共享的时候=
子类:Properties
是一个集合类 双列集合类
TreeMap
Properties 是一个双列集合
案例
Properties 里面有自己特有的方法
案例
Properties 和IO流结合的方法
方法
load(InputStream inStream)把文件中的键值对 读取到程序里面的集合中(字节流不支持中文)
load(Reader reader): 把文件中的键值对 读取到程序里面的集合中(字符流)
store(OutputStream out, String comments)把集合中的键值对 写出到文件中(字节流不支持中文)
store(Writer writer, String comments); 把集合中的键值对 写出到文件中(字符流) String comments是 properties文件的头注释
list(PrintStream out)把集合中的键值对 写出到文件中(字节流不支持中文)
list(PrintWriter out)把集合中的键值对 写出到文件中(字符流)
save(OutputStream out, String comments)把集合中的键值对 写出到文件中(字节流不支持中文) 已过时。
如果在保存属性列表时发生I / O错误,此方法不会抛出IOException。
如果在保存属性列表时发生I / O错误,此方法不会抛出IOException。
专门写键值对的文件 .properties
#和!为注释
=和:分割键跟值
案例
案例
数据流
DataOutputStream
多线程
概念
并行: 多个任务 并列执行
在同一时刻,有多个指令在多个CPU上同时执行
并发: 多个任务 之间高速切换
在同一时刻,有多个指令在单个CPU上交替执行
进程:一个软件 就是一个进程。
进程:是正在运行的程序
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的
并发性:任何进程都可以同其他进程一起并发执行
线程:一个软件里面的 多条执行路径。
线程:是进程中的单个顺序控制流,是一条执行路径
单线程:一个进程如果只有一条执行路径,则称为单线程程序
多线程:一个进程如果有多条执行路径,则称为多线程程序
多线程的原理:
cpu 在同一时刻,只能执行一条线程。
我们看到的多线程在同时执行, 其实是
cpu在 多线程之间做着高速的切换,以至于我们觉得是同时执行的。
我们看到的多线程在同时执行, 其实是
cpu在 多线程之间做着高速的切换,以至于我们觉得是同时执行的。
调度方式:
java中的 多线程的执行方式 是抢占式调度。 (随机的)
线程实现
线程的实现方式一
方法
void run()
在线程开启后,此方法将被调用执行
void start()
使此线程开始执行,Java虚拟机会调用run方法()
1: 先有一个类 继承 Thread 类。
2: 重写run方法 把你想让线程做的内容写在run方法中。
(run方法是 线程的任务)
3: 在测试中类 创建这个类的对象。
4: 不是调用run方法。 而是调用start方法。线程就开启了。
在线程开启后,run方法将被调用执行
2: 重写run方法 把你想让线程做的内容写在run方法中。
(run方法是 线程的任务)
3: 在测试中类 创建这个类的对象。
4: 不是调用run方法。 而是调用start方法。线程就开启了。
在线程开启后,run方法将被调用执行
案例
小问题
为什么要重写run()方法?
因为run()是用来封装被线程执行的代码
因为run()是用来封装被线程执行的代码
run()方法和start()方法的区别?
run():封装线程执行的代码,直接调用,相当于普通方法的调用
start():启动线程;然后由JVM调用此线程的run()方法
run():封装线程执行的代码,直接调用,相当于普通方法的调用
start():启动线程;然后由JVM调用此线程的run()方法
线程的实现方式二
1: 先有一个类 实现 Runnable 接口。(这个类就是线程任务)
2: 重写run方法 把你想让线程做的内容写在run方法中。
3: 在测试中类 创建这个类的对象。 再创建一个 Thread 对象,
把自定义的类的对象 当做 Thread的参数传递进去
4: 让Thread 的对象调用 start();
2: 重写run方法 把你想让线程做的内容写在run方法中。
3: 在测试中类 创建这个类的对象。 再创建一个 Thread 对象,
把自定义的类的对象 当做 Thread的参数传递进去
4: 让Thread 的对象调用 start();
案例
线程的实现方式三
方法
V call()
计算结果,如果无法计算结果,则抛出一个异常
FutureTask(Callable<V> callable)
创建一个 FutureTask,一旦运行就执行给定的 Callable
V get()
如有必要,等待计算完成,然后获取其结果
1:定义一个类MyCallable 实现Callable接口
2:在MyCallable类中重写call()方法
3:创建MyCallable类的对象
4:创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
5:创建Thread类的对象,把FutureTask对象作为构造方法的参数
6:启动线程
7:再调用get方法,就可以获取线程结束之后的结果。
2:在MyCallable类中重写call()方法
3:创建MyCallable类的对象
4:创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
5:创建Thread类的对象,把FutureTask对象作为构造方法的参数
6:启动线程
7:再调用get方法,就可以获取线程结束之后的结果。
案例
三种实现方式的对比
- 实现Runnable、Callable接口
- 好处: 扩展性强,实现该接口的同时还可以继承其他的类
- 缺点: 编程相对复杂,不能直接使用Thread类中的方法
- 继承Thread类
- 好处: 编程比较简单,可以直接使用Thread类中的方法
- 缺点: 可以扩展性较差,不能再继承其他的类
线程的方法
设置和获取线程名
void setName(String name)
将此线程的名称更改为等于参数name
String getName()
返回此线程的名称
Thread currentThread()
返回对当前正在执行的线程对象的引用
案例
获取当前线程
案例
线程睡眠
static void sleep(long millis)
使当前正在执行的线程停留(暂停执行)指定的毫秒数
案例
设置获取线程优先级
线程调度
- 两种调度方式
- 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
- 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
Java使用的是抢占式调度模型
- 随机性
假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,
线程只有得到CPU时间片,也就是使用权,才可以执行指令。
所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的
线程只有得到CPU时间片,也就是使用权,才可以执行指令。
所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的
优先级相关方法
final int getPriority()
返回此线程的优先级
final void setPriority(int newPriority)
更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10
案例
设置守护线程
相关方法
void setDaemon(boolean on)
将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
普通线程执行完 守护线程就会停止
案例
线程插队
案例
线程安全
卖票案例
案例:现在火车站卖100张票,分四个窗口卖。
用多线程的实现方式一
用多线程的实现方式二
卖票案例问题分析
卖重复票的原因
卖的100号票在50号票下面打印的原因
卖负号票的原因
厕所原理
问题产生原因
线程执行的随机性导致的,可能在卖票过程中丢失cpu的执行权,导致出现问题
线程出现问题的跟层次原因和解决方案
安全问题出现的条件
1:是多线程环境 多个线程操作共享数据
2:多个线程操作共享数据的代码 是分开的。
线程可以在执行这些分开的代码之间被其他线程抢掉。
解决卖票安全问题:
同步代码块
格式:用synchronized关键字包起来的代码。就叫做同步代码块。
synchronized (任意锁对象){
}
}
同步的
如果多个线程 在同步代码块上使用的锁对象是 同一个。
我们就说这个几个线程 是同步的
我们就说这个几个线程 是同步的
synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁
synchronized(obj){
代码1 //如果t1线程在代码1这个位置睡眠了, 那么t2 是不能进入代码2执行的。
}
synchronized(obj){
代码2 // t2进不来。
}
代码1 //如果t1线程在代码1这个位置睡眠了, 那么t2 是不能进入代码2执行的。
}
synchronized(obj){
代码2 // t2进不来。
}
举例代码
同步的好处和弊端
- 好处:解决了多线程的数据安全问题
- 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
同步方法解决
数据安全问题
数据安全问题
同步方法的格式
同步方法:
就是把synchronized关键字加到方法上
修饰符 synchronized 返回值类型 方法名(方法参数) {
方法体;
}
非静态的同步方法的锁对象 是this
静态同步方法
同步静态方法:就是把synchronized关键字加到静态方法上
修饰符 static synchronized 返回值类型 方法名(方法参数) {
方法体;
}
静态的同步方法的锁对象 是 当前方法所在的类的 class 文件对象
Lock 接口
jdk1.5的时候 出现了一个 类 Lock 接口
Lock是接口不能直接实例化,这里
采用它的实现类ReentrantLock来实例化
采用它的实现类ReentrantLock来实例化
ReentrantLock构造方法
ReentrantLock() 创建一个ReentrantLock的实例
加锁解锁方法
void lock() 获得锁
void unlock() 释放锁
void unlock() 释放锁
死锁现象
概述
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行
什么情况下会产生死锁
1. 资源有限
2. 同步嵌套
锁的嵌套就很容易出现死锁的情况,开发中应当尽量避免锁的嵌套
面向对象的一个注意事项:
//编译报错 父类没有抛异常,子类也不能抛
// 父类不生病 儿子也不能生病
// 父类生病了 子类不能比他生病的严重
// 父类不生病 儿子也不能生病
// 父类生病了 子类不能比他生病的严重
线程通信
生产者与消费者模式(这不是设计模式哈)
图解
所谓生产者消费者问题,实际上主要是包含了两类线程:
一类是生产者线程用于生产数据
一类是消费者线程用于消费数据
为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库
生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为
消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为
生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为
消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为
Object类的等待和唤醒方法
void wait()
导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
void notify()
唤醒正在等待对象监视器的单个线程
void notifyAll()
唤醒正在等待对象监视器的所有线程
wait和notify总结
wait和sleep的区别:
wait 方法 睡了之后 会把锁打开,wait方法必须要在 同步代码块中调用。
sleep 方法 抱着锁睡觉,可以在任何位置调用
wait 和notify 方法 必须 使用 锁对象来调用
锁对象 是任意对象 任意对象 为什么可以调用wait和notify呢?
所以 你发现了没 wait和notify 定义在 Object 中的
wait 和notify 方法 必须 在同步代码块中被调用, 在其他地方被调用 就报错。
notify :是唤醒 锁对象上等待的线程中的随机一个,如果没有等待的线程则自动忽略。
notifyAll :是唤醒 锁对象上所有等待的线程,如果没有等待的线程则自动忽略
wait 方法 睡了之后 会把锁打开,wait方法必须要在 同步代码块中调用。
sleep 方法 抱着锁睡觉,可以在任何位置调用
wait 和notify 方法 必须 使用 锁对象来调用
锁对象 是任意对象 任意对象 为什么可以调用wait和notify呢?
所以 你发现了没 wait和notify 定义在 Object 中的
wait 和notify 方法 必须 在同步代码块中被调用, 在其他地方被调用 就报错。
notify :是唤醒 锁对象上等待的线程中的随机一个,如果没有等待的线程则自动忽略。
notifyAll :是唤醒 锁对象上所有等待的线程,如果没有等待的线程则自动忽略
顾客厨师代码实现
生产者消费者模式+容器改进
需求
- 将Desk类中的变量,采用面向对象的方式封装起来
- 生产者和消费者类中构造方法接收Desk类对象,之后在run方法中进行使用
- 创建生产者和消费者线程对象,构造方法中传入Desk类对象
- 开启两个线程
在吃货和厨师案例中,假设这个桌子可以存储10个汉堡,
只要桌子上不够10个汉堡厨师就会一直生产,只要桌子上
的汉堡没有吃光 那么吃货就会一直吃, 桌子上没有汉堡
了吃货就会等待,桌子上汉堡满10个了,厨师就会等待。
只要桌子上不够10个汉堡厨师就会一直生产,只要桌子上
的汉堡没有吃光 那么吃货就会一直吃, 桌子上没有汉堡
了吃货就会等待,桌子上汉堡满10个了,厨师就会等待。
线程队列
BlockingQueue
常见BlockingQueue:
ArrayBlockingQueue: 底层是数组,有界
LinkedBlockingQueue: 底层是链表,无界.但不是真正的无界,最大为int的最大值
BlockingQueue的核心方法:
put(anObject): 将参数放入队列,如果放不进去会阻塞
take(): 取出第一个数据,取不到会阻塞
阻塞队列 ArrayBlockingQueue
案例
阻塞队列充当生产者消费者容器
案例需求
- 生产者类(Cooker):实现Runnable接口,重写run()方法,设置线程任务
1.构造方法中接收一个阻塞队列对象
2.在run方法中循环向阻塞队列中添加包子
3.打印添加结果
- 消费者类(Foodie):实现Runnable接口,重写run()方法,设置线程任务
1.构造方法中接收一个阻塞队列对象
2.在run方法中循环获取阻塞队列中的包子
3.打印获取结果
- 测试类(Demo):里面有main方法,main方法中的代码步骤如下
创建阻塞队列对象
创建生产者线程和消费者线程对象,构造方法中传入阻塞队列对象
分别开启两个线程
案例
线程池
线程状态
1. NEW
尚未启动的线程处于此状态。
2. RUNNABLE
在Java虚拟机中执行的线程处于此状态。
3. BLOCKED
被阻塞等待监视器锁定的线程处于此状态。
4. WAITING
正在等待另一个线程执行特定动作的线程处于此状态。
5. TIMED_WAITING
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
6. TERMINATED
已退出的线程处于此状态。
图解
// 获取当前线程的状态
public State getState() {
return jdk.internal.misc.VM.toThreadState(threadStatus);
}
线程池-基本原理
线程池也是可以看做成一个池子,在该池子中存储很多个线程
线程池存在的意义
系统创建一个线程的成本是比较高的,因为它涉及到与操作系统交互,当程序中需要创建大量生存期很短暂的线程时,
频繁的创建和销毁线程对系统的资源消耗有可能大于业务处理是对系统资源的消耗,这样就有点"舍本逐末"了。针对这一种情况,
为了提高性能,我们就可以采用线程池。线程池在启动的时,会创建大量空闲线程,当我们向线程池提交任务时,
线程池就会启动一个线程来执行该任务。等待任务执行完毕以后,线程并不会死亡,而是再次返回到线程池中称为空闲状态。等待下一次任务的执行。
频繁的创建和销毁线程对系统的资源消耗有可能大于业务处理是对系统资源的消耗,这样就有点"舍本逐末"了。针对这一种情况,
为了提高性能,我们就可以采用线程池。线程池在启动的时,会创建大量空闲线程,当我们向线程池提交任务时,
线程池就会启动一个线程来执行该任务。等待任务执行完毕以后,线程并不会死亡,而是再次返回到线程池中称为空闲状态。等待下一次任务的执行。
线程池的设计思路 :
1. 准备一个任务容器
2. 一次性启动多个(2个)消费者线程
3. 刚开始任务容器是空的,所以线程都在wait
4. 直到一个外部线程向这个任务容器中扔了一个"任务",就会有一个消费者线程被唤醒
5. 这个消费者线程取出"任务",并且执行这个任务,执行完毕后,继续等待下一次任务的到来
Executors默认线程池
概述 :
JDK对线程池也进行了相关的实现,在真实企业开发中我们也很少去自定义线程池,
而是使用JDK中自带的线程池。我们可以使用Executors中所提供的静态方法来创建线程池
JDK对线程池也进行了相关的实现,在真实企业开发中我们也很少去自定义线程池,
而是使用JDK中自带的线程池。我们可以使用Executors中所提供的静态方法来创建线程池
static ExecutorService newCachedThreadPool()
创建一个默认的线程池
创建一个默认的线程池
创建一个无限线程数的线程池
底层代码
特点
核心线程数是 0, 最大线程数是 Integer.MAX_VALUE,救急线程的空闲生存时间是 60s,意味着全部都是救急线程(60s 后可以回收),救急线程可以无限创建,队列采用了 SynchronousQueue 实现特点是,它没有容量,没有线程来取是放不进去的(一手交钱、一手交货)
评价:整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲 1分钟后释放线程。 适合任务数比较密集,但每个任务执行时间较短的情况
案例
static newFixedThreadPool(int nThreads)
创建一个指定最多线程数量的线程池
创建一个指定最多线程数量的线程池
创建一个指定线程数的线程池
底层代码
特点
核心线程数 == 最大线程数(没有救急线程被创建),因此也无需超时时间
阻塞队列是无界的,可以放任意数量的任务
评价:适用于任务量已知,相对耗时的任务
案例
static ExecutorService newFixedThreadPool(int nThreads)
创建一个指定最多线程数量的线程池
创建一个指定最多线程数量的线程池
创建装有一条线程的线程池
newSingleThreadExecutor
底层源码
使用场景:
希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程也不会被释放。
区别:
1. 自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建一个线程,保证池的正常工作
2. Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了ExecutorService 接口,因此不能调用 ThreadPoolExecutor 中特有的方法
3. Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改
对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize 等方法进行修改
ThreadPoolExecutor
创建线程池对象 :
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
核心线程数量,最大线程数量,空闲线程最大存活时间,时间单位,任务队列,创建线程工厂,任务的拒绝策略);
核心线程数量,最大线程数量,空闲线程最大存活时间,时间单位,任务队列,创建线程工厂,任务的拒绝策略);
更灵活的方式创建线程池
构造方法
corePoolSize 核心线程数目 (最多保留的线程数)不能小于0
maximumPoolSize 最大线程数目,不能小于等于0,maximumPoolSize >= corePoolSize
keepAliveTime 生存时间 - 针对救急线程,不能小于0
unit 时间单位 - 针对救急线程
workQueue 阻塞队列,不能为null
threadFactory 线程工厂 - 可以为线程创建时起个好名字,不能为null
handler 拒绝策略,不能为null
工作方式
线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务。
当线程数达到 corePoolSize 并没有线程空闲,这时再加入任务,新加的任务会被加入workQueue 队列排队,直到有空闲的线程。
如果队列选择了有界队列,那么任务超过了队列大小时,会创建 maximumPoolSize - corePoolSize 数目的线程来救急。
如果线程到达 maximumPoolSize 仍然有新任务这时会执行拒绝策略。
拒绝策略 jdk 提供了 4 种实现,其它著名框架也提供了实现
拒绝策略 jdk 提供了 4 种实现,其它著名框架也提供了实现
AbortPolicy 让调用者抛出 RejectedExecutionException 异常,这是默认策略
CallerRunsPolicy 让调用者运行任务
DiscardPolicy 放弃本次任务
DiscardOldestPolicy 放弃队列中最早的任务,本任务取而代之
其它
Dubbo 的实现,在抛出 RejectedExecutionException 异常之前会记录日志,并 dump 线程栈信息,方便定位问题
Netty 的实现,是创建一个新线程来执行任务
ActiveMQ 的实现,带超时等待(60s)尝试放入队列,类似我们之前自定义的拒绝策略
PinPoint 的实现,它使用了一个拒绝策略链,会逐一尝试策略链中每种拒绝策略
当高峰过去后,超过corePoolSize 的救急线程如果一段时间没有任务做,需要结束节省资源,这个时间由keepAliveTime 和 unit 来控制。
注:明确线程池对多可执行的任务数 = 队列容量 + 最大线程数
案例
码
任务的拒绝策略
用于任务调度的线程池
在任务调度线程池功能加入之前,可以使用 java.util.Timer 来实现定时功能,
Timer 的优点在于简单易用,但由于所有任务都是由同一个线程来调度,
因此所有任务都是串行执行的,同一时间只能有一个任务在执行,
前一个任务的延迟或异常都将会影响到之后的任务。
Timer 的优点在于简单易用,但由于所有任务都是由同一个线程来调度,
因此所有任务都是串行执行的,同一时间只能有一个任务在执行,
前一个任务的延迟或异常都将会影响到之后的任务。
案例
使用 ScheduledExecutorService 改写
scheduleAtFixedRate 例子
案例1
案例2
scheduleWithFixedDelay 例子
案例
线程高级
多线程的原子性
出现的问题
当A线程修改了共享数据时,B线程没有及时获取到
最新的值,如果还在使用原先的值,就会出现问题
最新的值,如果还在使用原先的值,就会出现问题
1,堆内存是唯一的,每一个线程都有自己的线程栈。
2 ,每一个线程在使用堆里面变量的时候,都会先拷贝一份到变量的副本中。
3 ,在线程中,每一次使用是从变量的副本中获取的。
volatile
Volatile关键字 : 强制线程每次在使用的时候,都会看一下共享区域最新的值
多线程的可见性--volatile 解决
多线程的有序性--volatile 解决
指令重排 :jvm
volatile关键字不能保证原子性
synchronized
synchronized解决 :
1 ,线程获得锁
2 ,清空变量副本
3 ,拷贝共享变量最新的值到变量副本中
4 ,执行代码
5 ,将修改后变量副本中的值赋值给共享数据
6 ,释放锁
原子类解决(底层采用的乐观锁机制)
直观想法: 原子性 原子 原子是世界上 最小的单元 不可再分割
出现问题代码:
a++ 不是一个原子性操作, 他在执行的过程中,有可能被其他线程打断
java从JDK1.5开始提供了java.util.concurrent.atomic包(简称Atomic包)
这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式
这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式
使用原子的方式更新基本类型Atomic包提供了以下3个类:
AtomicBoolean: 原子更新布尔类型
AtomicInteger: 原子更新整型
AtomicLong: 原子更新长整型
AtomicInteger
public AtomicInteger(): 初始化一个默认值为0的原子型Integer
public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integer
int get(): 获取值
int getAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值。
int incrementAndGet(): 以原子方式将当前值加1,注意,这里返回的是自增后的值。
int addAndGet(int data): 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
int getAndSet(int value): 以原子方式设置为newValue的值,并返回旧值。
问题的解决代码:
悲观锁和乐观锁
乐观锁
乐观锁的概念
乐观锁:指的是在操作数据的时候非常乐观,乐观地认为别人不会同时修改数据,因此乐观锁默认是不会上锁的,只有在执行更新的时候才会去判断在此期间别人是否修改了数据,如果别人修改了数据则放弃操作,否则执行操作。
冲突比较少的时候, 使用乐观锁(没有悲观锁那样耗时的开销) 由于乐观锁的不上锁特性,所以在性能方面要比悲观锁好,比较适合用在DB的读大于写的业务场景。
冲突比较少的时候, 使用乐观锁(没有悲观锁那样耗时的开销) 由于乐观锁的不上锁特性,所以在性能方面要比悲观锁好,比较适合用在DB的读大于写的业务场景。
乐观锁的实现方式主要有两种,一种是CAS(Compare and Swap,比较并交换)机制,一种是版本号机制。
CAS机制:
CAS操作包括了三个操作数,分别是需要读取的内存位置(V)、进行比较的预期值(A)和拟写入的新值(B),操作逻辑是,如果内存位置V的值等于预期值A,则将该位置更新为新值B,否则不进行操作。另外,许多CAS操作都是自旋的,意思就是,如果操作不成功,就会一直重试,直到操作成功为止。
版本号机制:
版本号机制的基本思路,是在数据中增加一个version字段用来表示该数据的版本号,每当数据被修改版本号就会加1。当某个线程查询数据的时候,会将该数据的版本号一起读取出来,之后在该线程需要更新该数据的时候,就将之前读取的版本号与当前版本号进行比较,如果一致,则执行操作,如果不一致,则放弃操作。
1: 只针对 值的修改 只在修改处 加校验。 具体大段的逻辑 他不管。
2: 针对 多查 少改。
悲观锁(synchronized)
悲观锁的概念
悲观锁:指的是在操作数据的时候比较悲观,悲观地认为别人一定会同时修改数据,因此悲观锁在操作数据时是直接把数据上锁,直到操作完成之后才会释放锁,在上锁期间其他人不能操作数据。
冲突比较多的时候, 使用悲观锁(没有乐观锁那么多次的尝试)对于每一次数据修改都要上锁,如果在DB读取需要比较大的情况下有线程在执行数据修改操作会导致读操作全部被挂载起来,等修改线程释放了锁才能读到数据,体验极差。所以比较适合用在DB写大于读的情况。
冲突比较多的时候, 使用悲观锁(没有乐观锁那么多次的尝试)对于每一次数据修改都要上锁,如果在DB读取需要比较大的情况下有线程在执行数据修改操作会导致读操作全部被挂载起来,等修改线程释放了锁才能读到数据,体验极差。所以比较适合用在DB写大于读的情况。
1: 针对 大段的逻辑 上下文关联的。 要把大段的代码 变成 原子性的。
2: 针对 多改 少查 用悲观锁。
悲观锁和乐观锁的区别
相同点:在多线程情况下,都可以保证共享数据的安全性。
不同点:
synchronized总是从最坏的角度出发,认为每次获取数据的时候,别人都有可能修改。
所以在每次操作共享数据之前,都会上锁。(悲观锁)
所以在每次操作共享数据之前,都会上锁。(悲观锁)
cas是从乐观的角度出发,假设每次获取数据别人都不会修改,所以不会上锁。
只不过在修改共享数据的时候,会检查一下,别人有没有修改过这个数据。
如果别人修改过,那么我再次获取现在最新的值。
如果别人没有修改过,那么我现在直接修改共享数据的值.(乐观锁)
只不过在修改共享数据的时候,会检查一下,别人有没有修改过这个数据。
如果别人修改过,那么我再次获取现在最新的值。
如果别人没有修改过,那么我现在直接修改共享数据的值.(乐观锁)
线程安全集合类可以分为三大类:
遗留的线程安全集合如
Hashtable (悲观锁)
synchronized 每个方法都是 同步方法。 效率比较低,之所以效率低,是因为 有一个线程在使用Hashtable的时候
其他的线程是无法使用Hashtable的数据,就连最基本的查找(并不做修改)都做不到,所以特别影响效率。因为效率很低所以 开发并不用,
所以出现了 效率高一些的 数据安全的双列集合。Properties 是他的子类 也是线程安全的,但是 鉴于Properties的特殊性,开发中依然使用。
但是一般都是服务器开启时刻执行Properties代码,服务器高并发的代码是绝对不会使用Properties的。
其他的线程是无法使用Hashtable的数据,就连最基本的查找(并不做修改)都做不到,所以特别影响效率。因为效率很低所以 开发并不用,
所以出现了 效率高一些的 数据安全的双列集合。Properties 是他的子类 也是线程安全的,但是 鉴于Properties的特殊性,开发中依然使用。
但是一般都是服务器开启时刻执行Properties代码,服务器高并发的代码是绝对不会使用Properties的。
HashMap(与Hashtable都继承Map类)
线程不安全的 多线程环境下会有数据安全问题 效率比较高。 开发中在局部位置定义双列集合,
首选HashMap,因为局部位置不涉及共享数据 ,属于单线程开发,使用HashMap效率最高。
首选HashMap,因为局部位置不涉及共享数据 ,属于单线程开发,使用HashMap效率最高。
代码:体现线程不安全
Vector
使用 Collections 装饰的线程安全集合,如:
Collections.synchronizedCollection
Collections.synchronizedList
Collections.synchronizedMap
Collections.synchronizedSet
Collections.synchronizedNavigableMap
Collections.synchronizedNavigableSet
Collections.synchronizedSortedMap
Collections.synchronizedSortedSet
JU安全集合
java.util.concurrent.*
Concurrent 类型的容器
内部很多操作使用 cas 优化,一般可以提供较高吞吐量
弱一致性
遍历时弱一致性,例如,当利用迭代器遍历时,如果容器发生修改,迭代器仍然可以继续进行遍历,这时内容是旧的
求大小弱一致性,size 操作未必是 100% 准确
读取弱一致性
Concurrent系列的集合(乐观锁-也可以成为局部锁)
jdk1.7之前原理:
使用的 哈希表的嵌套, 并使用悲观锁synchronized对 小哈希表进行局部锁定,所以他可以同时使用16条线程共同操作此集合。
jdk1.8之后的原理:
对横向的数组数据 使用乐观锁cas
对竖向的链表和红黑树 使用悲观锁synchronized 锁对象是红黑树或者链表的头结点。
案例
CopyOnWrite类
CopyOnWrite 系列的集合(乐观锁-也可以成为局部锁)
案例
Blocking类
Blocking 系列的类之前我们已经讲过
比如ArrayBlockingQueue,生产者和消费者模式里面讲过 ,此处不再赘述
比如ArrayBlockingQueue,生产者和消费者模式里面讲过 ,此处不再赘述
大部分实现基于锁,并提供用来阻塞的方法
网络编程
网络编程概述
- 计算机网络
是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,
在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统
在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统
- 网络编程
在网络通信协议下,不同计算机上运行的程序,可以进行数据传输
网络编程三要素
ip地址
IP是电脑在网络中的唯一标识
我的电脑和另一台电脑进行通信,需要通过这台电脑在网络中的唯一标识来找到这台计算机
IP地址分为两大类
IPv4
IPv6
DOS常用命令
ipconfig:查看本机IP地址
ipconfig /all
ping IP地址:检查网络是否连通
ping 192.168.14.28
ping www.baidu.com 如果你不知道对方(服务器)的ip 但是知道对方(服务器)的网址。 自动显示ip
特殊IP地址:
127.0.0.1:是回送地址,可以代表本机地址,一般用来测试使用
localhost
InetAddress:此类表示Internet协议(IP)地址
相关方法
static InetAddress getByName(String host)
确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址
String getHostName()
获取此IP地址的主机名
String getHostAddress()
返回文本显示中的IP地址字符串
String getHostName()
获取此IP地址的主机名
String getHostAddress()
返回文本显示中的IP地址字符串
Java中描述IP的类 InetAddress
端口:程序在电脑中的唯一标识
我电脑上的qq和另一台电脑上的qq进行通信,通过ip找到了对方的计算机,还需要通过端口找到对方电脑上的qq程序。
端口号
用两个字节表示的整数,它的取值范围是0-65535 其中,0~1023之间的端口号用于一些知名的网络服务和应用,
普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败
普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败
协议:就是一种传输规则
UDP协议
用户数据报协议(User Datagram Protocol)
面向无连接。
数据不安全。
数据有大小限制。
传输效率高。
举例:收音机 视频会议,群聊。
UDP通信程序
Java中的UDP通信
UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象,但是这两个Socket只是发送,
接收数据的对象,因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念
接收数据的对象,因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念
Java提供了DatagramSocket类作为基于UDP协议的Socket
UDP发送数据
构造方法
DatagramSocket()
创建数据报套接字并将其绑定到本机地址上的任何可用端口
DatagramPacket(byte[] buf,int len,InetAddress add,int port)
创建数据包,发送长度为len的数据包到指定主机的指定端口
相关方法
void send(DatagramPacket p)
发送数据报包
void close()
关闭数据报套接字
void receive(DatagramPacket p)
从此套接字接受数据报包
发送数据的步骤
- 创建发送端的Socket对象(DatagramSocket)
- 创建数据,并把数据打包
- 调用DatagramSocket对象的方法发送数据
- 关闭发送端
UDP接收数据
接收数据的步骤
- 创建接收端的Socket对象(DatagramSocket)
- 创建一个数据包,用于接收数据
- 调用DatagramSocket对象的方法接收数据
- 解析数据包,并把数据在控制台显示
- 关闭接收端
构造方法
DatagramPacket(byte[] buf, int len)
创建一个DatagramPacket用于接收长度为len的数据包
相关方法
byte[] getData()
返回数据缓冲区
int getLength()
返回要发送的数据的长度或接收的数据的长度
案例
UDP的组播和广播
广播地址是255
案例
组播
案例
TCP协议
传输控制协议 (Transmission Control Protocol)
面向连接 (三次握手)
三次握手:TCP协议中,在发送数据的准备阶段,
客户端与服务器之间的三次交互,以保证连接的可靠
客户端与服务器之间的三次交互,以保证连接的可靠
第一次握手,客户端向服务器端发出连接请求,等待服务器确认
第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求
第三次握手,客户端再次向服务器端发送确认信息,确认连接
数据安全
数据没有大小限制。(迅雷下载 上传文件 浏览网页)
传输效率低。
举例:上传下载文件。http。ftp协议。
TCP通信程序
Java中的TCP通信
Java对基于TCP协议的的网络提供了良好的封装,使用Socket对象
来代表两端的通信端口,并通过Socket产生IO流来进行网络通信。
来代表两端的通信端口,并通过Socket产生IO流来进行网络通信。
Java为客户端提供了Socket类,为服务器端提供了ServerSocket类
TCP发送数据
构造方法
Socket(InetAddress address,int port)
创建流套接字并将其连接到指定IP指定端口号
Socket(String host, int port)
创建流套接字并将其连接到指定主机上的指定端口号
相关方法
InputStream getInputStream()
返回此套接字的输入流
OutputStream getOutputStream()
返回此套接
TCP接收数据
构造方法
ServletSocket(int port)
创建绑定到指定端口的服务器套接字
相关方法
Socket accept()
监听要连接到此的套接字并接受它
注意事项
1. accept方法是阻塞的,作用就是等待客户端连接
2. 客户端创建对象并连接服务器,此时是通过三次握手协议,保证跟服务器之间的连接
3. 针对客户端来讲,是往外写的,所以是输出流
针对服务器来讲,是往里读的,所以是输入流
针对服务器来讲,是往里读的,所以是输入流
4. read方法也是阻塞的
5. 客户端在关流的时候,还多了一个往服务器写结束标记的动作
6. 最后一步断开连接,通过四次挥手协议保证连接终止
案例
供大家发送数据代码
UDP协议代码
我的电脑作为发送端(电台),不断的给所有同学的的接收端(收音机)发送消息,我是怎么做到的? 我用的while循环 和 广播地址
我的电脑作为接收端(收音机),可以不断的接收同学们的发送端(电台)发送过来的消息,我是怎么做到的?我用的while循环
TCP协议代码
我的电脑作为服务器的时候:
我无限循环接收 同学们提交的请求。
每接收到一个请求。 我就开一个线程。处理和这个同学建立的链接。
为什么要开线程呢 : 如果不开线程, 比如50个学生同时想和我建立链接。 我只能一个一个的去处理和他们的链接。 50个同学就要排队去处理。
但是我作为服务器,不能让客户去等待, 我应该同时处理这50个链接。 所以如何同时处理呢。所以开50条线程
我无限循环接收 同学们提交的请求。
每接收到一个请求。 我就开一个线程。处理和这个同学建立的链接。
为什么要开线程呢 : 如果不开线程, 比如50个学生同时想和我建立链接。 我只能一个一个的去处理和他们的链接。 50个同学就要排队去处理。
但是我作为服务器,不能让客户去等待, 我应该同时处理这50个链接。 所以如何同时处理呢。所以开50条线程
我电脑作为客户端的时候。为啥能够给全班50个服务器发消息呢?
我也是开了50个线程。
每个线程里面 一个客户端 去链接 一个同学的服务器。
我也是开了50个线程。
每个线程里面 一个客户端 去链接 一个同学的服务器。
枚举
之前提到过枚举的地方
1:
数据类型
基本类型
byte short char int long double float boolean
引用类型
数组 []
类 class
String Class
接口 interface
枚举(类) enum
注解(接口) @interface
2:
switch (){ // byte short char int jdk1.5枚举 jdk1.7String
case :
}
3:
api 时间的类 获取星期 也是用的枚举
线程池 时间单位用的枚举
应用: 有限个数 不能新创建。
回顾单例设计模式
整个内存的使用过程中 这个对象 只有一个实例
枚举的特点
多例设计模式
案例
枚举其实就是 在内存的使用过程中, 这个对象 只有 有限个数。
不能少 也不能多创建, 比如 星期 只有7个。
不能少 也不能多创建, 比如 星期 只有7个。
代码一:
public enum EWeek {
w1,w2,w3,w4,w5,w6,w7;
}
代码二:
案例
枚举的方法
案例
枚举的应用
案例
反射
类加载
作用:
类加载器 是把 .class 文件 用过IO流 把数据读取到 方法区里 加载器并把读到的字节 整合成 类模板
java中 万物皆对象 任何东西都会用对象去表示
包括你这个 模板类 java也会 创建一个代表模板类的对象 以后只要调用这个对象 其实就是在去使用那个模板。
java中 万物皆对象 任何东西都会用对象去表示
包括你这个 模板类 java也会 创建一个代表模板类的对象 以后只要调用这个对象 其实就是在去使用那个模板。
加载时机:
jvm开启之后 不使用这个类 是不会加载这个类.class文件的。
第一次使用这个类 才把这个类的.class 读进入方法区,后续使用不再加载
方法区里面的东西 一经读入 是不会被垃圾回收的。 直到jvm关闭。
静态区 也在方法区里面 也是第一次使用才加载 不使用也不加载的。 后续再使用也不加载了。
加载过程:
加载:
这个过程是通过包名和类名 确定class文件的路径 再把.class 文件读取到内存中 形成类模板
并创建一个 Class 类的对象 来代表这个类模板
验证:
检查class文件内容是否符合规范,是否安全,是否是木马等攻击型文件。
准备:
给静态变量分配空间 并对空间赋值默认值
class Student {
static int a=10; // 比如a在准备阶段的时候 有了存储空间 而且这个空间用0来占位 但是这个10 的赋值是在初始化阶段完成的
}
static int a=10; // 比如a在准备阶段的时候 有了存储空间 而且这个空间用0来占位 但是这个10 的赋值是在初始化阶段完成的
}
解析:
把 类中涉及到的其他类类型的 符号引用替换为直接引用。
class Student {
String name; // 比如 Student中使用了String 在把Student加载进入内存的时候 要在解析阶段 和String的class文件进行关联。
}
String name; // 比如 Student中使用了String 在把Student加载进入内存的时候 要在解析阶段 和String的class文件进行关联。
}
初始化:
给静态变量进行赋值
class Student {
static int a=10; // 比如a在准备阶段的时候赋值为了0 但是在初始化阶段 就把0替换成了10
}
static int a=10; // 比如a在准备阶段的时候赋值为了0 但是在初始化阶段 就把0替换成了10
}
链接
四种类加载器
BootStrapClassLoader -- (启动类)根类加载器。 加载 核心类库里面的类 java.lang包 ..
PlatformClassLoader -- 平台类加载器。 也是加载核心类库里面的一些类,但是这些类 大部分都与模块二有关。
AppClassLoader -- 系统类加载器。 加载的是 自己编写的类。
自定义类加载器 -- 比如 加载 网络环境下的类。
案例
双亲委派机制
图示
简介
介绍到这里相信大家心里有个疑问,程序定义类的目的是在jvm中使用它,那么为什么要划分出3中类加载器,如果直接设计为一个类加载器不是更加方便吗?
其实这是为了系统安全,而使用的双亲委派机制,双亲委派模式是在Java 1.2后引入的,
其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,
如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,
倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,
即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成,这不就是传说中的实力坑爹啊?
那么采用这种模式有啥用呢??
采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,
而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,
而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。
其实这是为了系统安全,而使用的双亲委派机制,双亲委派模式是在Java 1.2后引入的,
其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,
如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,
倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,
即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成,这不就是传说中的实力坑爹啊?
那么采用这种模式有啥用呢??
采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,
而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,
而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。
作用
可能你会想,如果我们在classpath路径下自定义一个名为java.lang.SingleInterge类(该类是胡编的)呢?
该类并不存在java.lang中,经过双亲委托模式,传递到启动类加载器中,由于父类加载器路径下并没有该类,所以不会加载,
将反向委托给子类加载器加载,最终会通过系统类加载器加载该类。
但是这样做是不允许,因为java.lang是核心API包,需要访问权限,强制加载将会报出如下异常java.lang.SecurityException: Prohibited package name: java.lang
该类并不存在java.lang中,经过双亲委托模式,传递到启动类加载器中,由于父类加载器路径下并没有该类,所以不会加载,
将反向委托给子类加载器加载,最终会通过系统类加载器加载该类。
但是这样做是不允许,因为java.lang是核心API包,需要访问权限,强制加载将会报出如下异常java.lang.SecurityException: Prohibited package name: java.lang
作用
自定义类加载器
类
测试类
案例
类加载器读取配置文件
getSystemClassLoader()方法获取系统类加载器
getClass().getClassLoader()返回当前类的类加载器
getResourceAsStream()方法根据配置文件的相对路径获取该文件的输入流
案例
反射概述
反射是框架的灵魂 : 框架 底层大量使用了反射。
框架。 这个架子 以后 你要谁都可以在这个架子上去写代码。
架子和你自己写的代码 耦合性 降到最低
反射:
好处: 降低耦合性 。 运行时执行即可,编译期不需要同时存在。
运行过程中。动态的获取 任意的类 包括里面的 构造方法 成员变量 成员方法。
Student类
案例
获取 Class文件
类的class文件:
创建对象 都需要把对象的类 的class文件加载进入方法区。
类的class文件 在java中 用 Class 类的对象去表示。
class文件中的成员变量 在java中 Field 类的对象去表示
class文件中的构造方法 在java中 Constructor 类的对象去表示
class文件中的成员方法 在java中 Method 类的对象去表示
Class这个类里面的静态方法forName(“全类名”)
获取class字节码文件对象
1: 通过类 获取class文件对象。
通过class属性获取
//类名.class
//因为class文件在硬盘中是唯一的,所以,当这个文件加载到内存之后产生的对象也是唯一的
System.out.println(clazz1 == clazz2);//true
Class aClass = Student.class;
System.out.println(aClass);
System.out.println(aClass);
应用: 多线程中 静态同步方法的 锁对象 是 当前类的class文件对象。
2:通过对象 来获取class文件对象
Student s = new Student();
Class aClass1 = s.getClass();
System.out.println(aClass1);
Class aClass1 = s.getClass();
System.out.println(aClass1);
应用:多态的转型, 重写equals方法的时候。 instanceof 或者 getClass
3:通过 类的名字(String) 获取class文件对象。
Class<?> aClass2 = Class.forName("com.itheima.day15.Student");
System.out.println(aClass2);
System.out.println(aClass2);
Class.forName("类的全类名"): 全类名 = 包名 + 类名
Class clazz1 = Class.forName("com.itheima.reflectdemo.Student");
//源代码阶段获取 --- 先把Student加载到内存中,再获取字节码文件的对象
//clazz 就表示Student这个类的字节码文件对象。
//就是当Student.class这个文件加载到内存之后,产生的字节码文件对象
我们最常用的 是用 第三种。
应用:框架中。
字节码文件和字节码文件对象
java文件:就是我们自己编写的java代码。
字节码文件:就是通过java文件编译之后的class文件(是在硬盘上真实存在的,用眼睛能看到的)
字节码文件对象:当class文件加载到内存之后,虚拟机自动创建出来的对象。
这个对象里面至少包含了:构造方法,成员变量,成员方法。
而我们的反射获取的是什么?字节码文件对象,这个对象在内存中是唯一的。
通过class文件获取构造方法并使用
Constructor<?>[] getConstructors()
获得所有的构造(只能public修饰)
获取无参构造
Constructor<?>[] getDeclaredConstructors()
获得所有的构造(包含private修饰)
获取所有构造方法
Constructor<T> getConstructor(Class<?>... parameterTypes)
获取指定构造(只能public修饰)
获取有参构造
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
获取指定构造(包含private修饰)
获取私有构造并创建对象
获取构造方法并创建对象
newInstance
获取空参,并创建对象
获取带参构造,并创建对象
通过class文件获取成员变量
Field[] getFields()
返回所有成员变量对象的数组(只能拿public的)
Field[] getDeclaredFields()
返回所有成员变量对象的数组,存在就能拿到
获取类中所有的成员变量
Field getField(String name)
返回单个成员变量对象(只能拿public的)
获取类中公共成员变量
Field getDeclaredField(String name)
返回单个成员变量对象,存在就能拿到
获取类中私有成员变量
获取成员变量并获取值和修改值
void set(Object obj, Object value)
赋值
Object get(Object obj)
获取值
通过class文件获取成员方法
Method[] getMethods()
返回所有成员方法对象的数组(只能拿public的)
获取类中公共成员方法
Method[] getDeclaredMethods()
返回所有成员方法对象的数组,存在就能拿到
获取类中所有的成员方法
Method getMethod(String name, Class<?>... parameterTypes)
返回单个成员方法对象(只能拿public的)
Method getDeclaredMethod(String name, Class<?>... parameterTypes)
返回单个成员方法对象,存在就能拿到
获取类中私有成员方法
获取成员方法并运行
Object invoke(Object obj, Object... args) :运行方法
参数一:用obj对象调用该方法
参数二:调用方法的传递的参数(如果没有就不写)
返回值:方法的返回值(如果没有就不写)
案例
案例1:编写程序,将下面方法对应的功能实现。
案例2:编写程序,将下面方法对应的功能实现。
案例3:编写程序,将下面方法对应的功能实现。
案例4:已知 配置文件a.properties 和 Student类, 请读取配置文件中的类名className 和方法名methodName ,通过反射 运行该方法
案例5:模拟 idea的提示功能// 键盘录入一个 类的名字。 你立刻给我打印出 他有哪些方法 哪些成员变量
案例6:反射越过反射检查
动态代理
案例
XML
xml概述
x - extensable m- markup l -language
可以扩展的 标记 语言
可以扩展的 标记 语言
HTML 超文本标记语言。
不可扩展的标记语言
不可扩展的标记语言
xml的用途
用来当做配置文件的
用来作为传输数据的格式的
xml的语法
1:xml第一行必须有声明 <?xml version="1.0"?>
version :版本 1.0 1.1 但是 一般1.0用的多。 版本必须要写。
encoding : 告诉浏览器用什么编码去解析这个xml的中文。
所以你的xml用什么编码编写的, 这儿就应该写什么编码。
所以你的xml用什么编码编写的, 这儿就应该写什么编码。
standalone :只能写 yes 或者 no 意思是是否独立存在
2: 必须只有一个根标签。
3: 必须正确的闭合标签
标签: 是自定义的标签 也就是说可以随便写。
随便写也要遵循语法:
①: 组成 英文数字_
②: 数字不能开头。
③: 不能是xml
4:属性 可以用单引号或者双引号引起来,
5:区分大小写。
6:文本:
不能 出现 < > && 特殊符号。
< 小于 <
> 大于 >
& 和号 &
' 单引号 '
" 引号 "
如果非要出现,要把这些符号表示成纯文本,就是不带特殊含义了。
就可以用下面的包起来。
就可以用下面的包起来。
<![CDATA[文本数据]]>
7:注释
和html的注释是一样的。
<!-- -->
java中
//
/**/
/***/
8:标签需要正确的嵌套
这是正确的: <student id="1"> <name>张三</name> </student>
这是错误的: <student id="1"><name>张三</student></name>
xml的代码演示
xml的解析
DOM4J: 开源组织提供了一套XML的解析的API-dom4j,全称:Dom For Java
1. 我们可以通过网站:https://dom4j.github.io/ 去下载dom4j
今天的资料中已经提供,我们不用再单独下载了,直接使用即可
今天的资料中已经提供,我们不用再单独下载了,直接使用即可
2. 将提供好的dom4j-1.6.1.zip解压,找到里面的dom4j-1.6.1.jar
3. 在idea中当前模块下新建一个libs文件夹,将jar包复制到文件夹中
4. 选中jar包 -> 右键 -> 选择add as library即可
案例
xml的约束
xml的标签是自定义标签,自己随便写。但是用在框架中的。 一般都要按照框架的约束去写
DTD约束
DTD的语法规则
定义元素
格式为:<!ELEMENT 元素名 元素类型>
简单元素:
EMPTY: 表示标签体为空
ANY: 表示标签体可以为空也可以不为空
(#PCDATA): 表示该元素的内容部分为字符串
复杂元素:
直接写子元素名称. 多个子元素可以使用","或者"|"隔开;
","表示定义子元素的顺序 ; "|": 表示子元素只能出现任意一个
"?"零次或一次, "+"一次或多次, "*"零次或多次;如果不写则表示出现一次
定义属性
格式为:<!ATTLIST 元素名称 属性名称 属性的类型 属性的约束>
属性的类型:
CDATA类型:普通的字符串
属性的约束:
// #REQUIRED: 必须的
// #IMPLIED: 属性不是必需的
// #FIXED value:属性值是固定的
编写dtd ---创建一个文件,这个文件的后缀名为.dtd 比如 Teachers.dtd
引入DTD约束 ---引入本地dtd
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE teachers SYSTEM './Teachers.dtd'> <!--./Teachers.dtd 相对与xml的路径 -->
<teachers>
<!–这是第一个学生 –>
<teacher id="10">
<name>张三</name>
<age></age>
</teacher>
<!–这是第二个学生 –>
<teacher id="10">
<name>李四</name>
<age></age>
<adress> <![CDATA[ <<<<<<<<原则2>>>>>>>>> “” "" '' &&& %% $$$ ]]> </adress>
</teacher>
<teacher id="10">
<name>王五<></name>
<age></age>
<adress> <![CDATA[山东省济南市开发区]]> </adress>
</teacher>
</teachers>
引入DTD约束 ---在xml文件内部引入
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE persons [
<!ELEMENT persons (person)>
<!ELEMENT person (name,age)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>
]>
<persons>
<person>
<name>张三</name>
<age>23</age>
</person>
</persons>
引入DTD约束 ---引入网络dtd
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE persons PUBLIC "dtd文件的名称" "dtd文档的URL">
<persons>
<person>
<name>张三</name>
<age>23</age>
</person>
</persons>
Schema 约束
schema和dtd的区别
1. schema约束文件也是一个xml文件,符合xml的语法,这个文件的后缀名.xsd
2. 一个xml中可以引用多个schema约束文件,多个schema使用名称空间区分(名称空间类似于java包名)
3. dtd里面元素类型的取值比较单一常见的是PCDATA类型,但是在schema里面可以支持很多个数据类型
4. schema 语法更加的复杂
一个schema文件代码如下
<?xml version="1.0" encoding="UTF-8" ?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="ThreeGoodStudent" elementFormDefault="qualified">
<!--定义persons复杂元素-->
<element name="students">
<complexType>
<sequence>
<!--定义person复杂元素-->
<element name = "student">
<complexType>
<sequence>
<!--定义name和age简单元素-->
<element name = "name" type = "string"></element>
<element name = "age" type = "string"></element>
</sequence>
<!--定义属性,required( 必须的)/optional( 可选的)-->
<attribute name="id" type="string" use="required"></attribute>
</complexType>
</element>
<any minOccurs="0" maxOccurs="unbounded"></any>
</sequence>
</complexType>
</element>
</schema>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="ThreeGoodStudent" elementFormDefault="qualified">
<!--定义persons复杂元素-->
<element name="students">
<complexType>
<sequence>
<!--定义person复杂元素-->
<element name = "student">
<complexType>
<sequence>
<!--定义name和age简单元素-->
<element name = "name" type = "string"></element>
<element name = "age" type = "string"></element>
</sequence>
<!--定义属性,required( 必须的)/optional( 可选的)-->
<attribute name="id" type="string" use="required"></attribute>
</complexType>
</element>
<any minOccurs="0" maxOccurs="unbounded"></any>
</sequence>
</complexType>
</element>
</schema>
另一个schema约束文档如下
<?xml version="1.0" encoding="UTF-8" ?>
<schema
xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="LittleGoodStudent"
elementFormDefault="qualified"
>
<!--定义person复杂元素-->
<element name = "littleStudent">
<complexType>
<sequence>
<!--定义name和age简单元素-->
<element name = "name" type = "string"></element>
<element name = "age" type = "string"></element>
<element name = "taoQiZhi" type = "string"></element>
</sequence>
<!--定义属性,required( 必须的)/optional( 可选的)-->
<attribute name="id" type="string" use="required"></attribute>
</complexType>
</element>
</schema>
xml引入schema约束
<?xml version="1.0" encoding="UTF-8" ?>
<students
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="ThreeGoodStudent"
xmlns:lgs="LittleGoodStudent"
xsi:schemaLocation="
ThreeGoodStudent ../test5/student.xsd
LittleGoodStudent ../test5/litteStudent.xsd
"
>
<student id="1">
<name>zhangsan</name>
<age>13</age>
</student>
<lgs:littleStudent id="2">
<lgs:name>李四</lgs:name>
<lgs:age>7</lgs:age>
<lgs:taoQiZhi>99</lgs:taoQiZhi>
</lgs:littleStudent>
</students>
举例:后期即将学习的 Spring框架的配置文件的导入约束写法
<?xml version="1.0" encoding="utf-8"?>
<beans
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
</beans>
xml命名空间详解
日志
概述
程序中的日志可以用来记录程序在运行的时候点点滴滴。并可以进行永久存储
之前的日志都是打印到控制台,但是控制台只能展示几万行信息,
导致之前的信息被覆盖掉,这样不利于程序员分析程序
导致之前的信息被覆盖掉,这样不利于程序员分析程序
控制台只展示有限信息,但是文件却可以存放无限的日志的,
想要把日志信息输出到文件中可以改变System.out和System.err的输出目标,
但是由于out和err是单例的,所以修改之后,不会再输出到控制台了。
想要把日志信息输出到文件中可以改变System.out和System.err的输出目标,
但是由于out和err是单例的,所以修改之后,不会再输出到控制台了。
如果既想在控制台输出日志信息,又想在文件中记录日志信息。那就需要写额外的IO流代码了
日志体系结构和logback
体系结构
logback
通过使用logback,我们可以控制日志信息输送的目的地是控制台、文件等位置。
我们也可以控制每一条日志的输出格式。
通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
入门案例
使用步骤
1. 导入logback的相关jar包
2. 编写logback配置文件
3. 在代码中获取日志的对象
4. 按照级别设置记录日志信息
代码示例
日志级别
logback有5种级别
分别是TRACE < DEBUG < INFO < WARN < ERROR,
定义于ch.qos.logback.classic.Level类中。
分别是TRACE < DEBUG < INFO < WARN < ERROR,
定义于ch.qos.logback.classic.Level类中。
Trace:是追踪,就是程序推进以下,你就可以写个trace输出,所以trace应该会特别多,不过没关系,我们可以设置最低日志级别不让他输出.
Debug:指出细粒度信息事件对调试应用程序是非常有帮助的.
Info:消息在粗粒度级别上突出强调应用程序的运行过程.
Warn:输出警告及warn以下级别的日志.
Error:输出错误信息日志.
此外OFF表示关闭全部日志,ALL表示开启全部日志。
级别等级
等级从低到高分别是TRACE < DEBUG < INFO < WARN < ERROR
日志输出的时候,级别大的会输出,根据当前ROOT 级别,
日志输出时,级别高于root默认的级别时会输出,
比如如果root的级别是info,那么会输出info以及info级别以上的日志。
日志输出时,级别高于root默认的级别时会输出,
比如如果root的级别是info,那么会输出info以及info级别以上的日志。
注解
注解的概念
代码的元数据(元代码)
元数据:用来修饰其他数据的 数据 叫做元数据。
元代码:用来修饰其他代码的 代码 叫做元代码。
注解是引用类型
Java的数据类型:
基本类型:
byte short int long
float double
boolean
char
float double
boolean
char
引用类型:
数组 []
类 class
枚举 enum jdk1.5的时候出现的, 枚举的底层就是类
接口 interface
注解 @interface jdk1.5的时候出现的,注解的底层就是接口
注解的定义
例
定义注解的意义
注解的定义是 元代码,也就是修改代码的代码。
所以注解的意义在于 修饰代码,在于给代码加上一个标记。
所以注解的意义在于 修饰代码,在于给代码加上一个标记。
被加上标记的代码,本质上也没啥变化,但是可以通过反射 只获取带有该标记的代码,
只执行带有该标记的代码,没有标记的可以不指定。 从而使得这种标记具有了意义。
只执行带有该标记的代码,没有标记的可以不指定。 从而使得这种标记具有了意义。
举例如下:
使用反射并执行Teacher中带有
@MyAnnotation注解的方法,
其他的不执行。
使用反射并执行Teacher中带有
@MyAnnotation注解的方法,
其他的不执行。
注解内的属性
定义属性的本质(抽象方法)
定义属性的意义
属性的类型
注解的意义主要是用来在解析的时候去比对属性值,提前规定好这些属性值的意义,
到时候解析属性的时候,解析到某属性值 就按照该属性值具有的意义去执行代码即可。
到时候解析属性的时候,解析到某属性值 就按照该属性值具有的意义去执行代码即可。
所以 注解里面的抽象方法 必须要有返回值类型,不能是void。
即就是属性值必须要有类型的。否则解析的时候也就没有了意义。
即就是属性值必须要有类型的。否则解析的时候也就没有了意义。
而且类型还必须是能够轻松比对的类型。
所以注解属性的类型只能是以下几种。
所以注解属性的类型只能是以下几种。
基本类型 byte short int long float double char boolean
字符串类型 String
枚举类型
注解类型
以上类型的数组
属性的类型 不能是类 接口等类型,因为到时候解析 无法比对。因为类每new一次会创建新的对象,无法比对。
代码演示:
总结注意事项
元注解
元注解:就是修饰注解的注解
元注解常见的有下面几个
@Target: 描述注解能够作用的位置
ElementType取值:
TYPE:可以作用于类上
METHOD:可以作用于方法上
FIELD:可以作用于成员变量上
@Retention: 描述注解被保留的阶段
@Retention(RetentionPolicy.RUNTIME):当前被描述的注解,会保留到class字节码文件中,并被JVM读取到
@Documented: 描述注解是否被抽取到api文档中
@Inherited: 描述注解是否被子类继承
@Target 描述注解能够作用的位置
@Retention: 描述注解被保留的阶段
@Inherited: 描述注解是否被子类继承
核心类库提供的注解
文档注释的注解
@author Joseph D. Darcy 作者
@since 1.0 版本
@param 参数说明
@return 返回值说明
@see 方法中涉及到的其他类的内容
案例
编译期的注解
@Override 重写
@Deprecated 过时
@FunctionalInterface 函数式接口
@SuppressWarnings("all") 压制警告
注意:
我们自己定义的注解是没有办法在编译期起到提示代码作用的。
因为编译期注解都是jvm的底层代码中提前预定义号的。
我们自己定义的注解是没有办法在编译期起到提示代码作用的。
因为编译期注解都是jvm的底层代码中提前预定义号的。
案例练习
自定义注解应用案例1
现有一个包路径 String packagePath = "com.itheima.day14.test10";
已知该包路径下全部都是Java类,没有其他子包。请扫描该包
获取到 带有@ClassLoadSingle注解的类, 并且创建该类对象,
把该类对象存入到Map集合中,其中Map集合的键是@ClassLoadSingle
注解中的value属性值,值是该类对象。
已知该包路径下全部都是Java类,没有其他子包。请扫描该包
获取到 带有@ClassLoadSingle注解的类, 并且创建该类对象,
把该类对象存入到Map集合中,其中Map集合的键是@ClassLoadSingle
注解中的value属性值,值是该类对象。
ClassLoadSingle
Demo
如果扫描的包 还有很多的子包,多层包。
则可以递归遍历包。递归代码如下
则可以递归遍历包。递归代码如下
自定义注解应用案例2
会自动执行行被检测的所有方法(加了Check注解的方法),
判断方法是否有异常,如果有就把异常记录到文件中
判断方法是否有异常,如果有就把异常记录到文件中
断言
测试工程师
功能测试 4-5k 7k
大公司:自动化测试 15k
测试外包公司: 测试工程师
有牌照:第三方测试公司
甲方(金主) ---> 第三方 ---> 乙方 (做事儿)
案例
案例1
程序员自己写的程序 一般很少使用到断言,但是在自动化测试中 经常会使用断言。
比如下方 我们测试一下 小明的计算器类里面的加减乘除是否有问题呢?
有问题的就会报错,没问题的就不会报错,经过一波自动化测试之后,
我只需要看测试生成的文件,哪个报错 哪个就是有问题,而没报错的就是没有问题。
比如下方 我们测试一下 小明的计算器类里面的加减乘除是否有问题呢?
有问题的就会报错,没问题的就不会报错,经过一波自动化测试之后,
我只需要看测试生成的文件,哪个报错 哪个就是有问题,而没报错的就是没有问题。
计算器类
测试类
Junit单元测试
Junit是一个 Java 编程语言的单元测试工具。JUnit 是一个非常重要的测试工具
之前main的弊端
创建一个类 写一个 Main方法 写完代码后 右键运行。
弊端:
一个类中只能写一个main方法,
想测试多段代码, 为了消除上面的代码对下面的代码的影响,
这时候 就需要注释前面的代码, 这种不方便管理的。
这时候 就需要注释前面的代码, 这种不方便管理的。
junit的优点:
JUnit是一个开放源代码的测试工具。
提供注解来识别测试方法。
JUnit测试可以让你编写代码更快,并能提高质量。
JUnit优雅简洁。没那么复杂,花费时间较少。
JUnit在一个条中显示进度。如果运行良好则是绿色;如果运行失败,则变成红色。
junit的使用:
击小红灯,导入jar包 add to classpath
junit测试的其他功能:
@Before 会在单元测试之前执行
@After 会在单元测试之后执行
案例:
请用 @Before @After 去测试一下 你的 10000次循环打印空格。 用多长时间。 如果时间超过 150毫秒 就不通过。
步骤:
1:@Before 里面写当前时间
2:@Test 里面写10000次循环
3:@After 里面写当前时间 减去 before里面的时间。
并断言 时间差 < 150
代码
设计模式
说明
面向对象应用到 项目中
确切的说是 项目中的设计
设计中的 架构设计。
大量使用设计模式 可以说 设计模式 是面向对象语法的集大成者。
确切的说是 项目中的设计
设计中的 架构设计。
大量使用设计模式 可以说 设计模式 是面向对象语法的集大成者。
单例设计模式(⭐⭐⭐⭐⭐)
需求(语法模式):请你定义一个类(Student), 这个类,不管在任何的地方 执行任何的代码,
管执行多少次代码, 要求这个类的对象在内存中只有唯一一个,不能多也不是少。
管执行多少次代码, 要求这个类的对象在内存中只有唯一一个,不能多也不是少。
模板设计模式(⭐⭐⭐⭐)
需求:请设计一类,使得这个类写一些功能时 必须按照 模板类的 模板来写。
静态工厂设计模式(⭐⭐⭐⭐)
0 条评论
下一页