Java SE

zhenxi
2022-07-15 / 0 评论 / 3 阅读 / 正在检测是否收录...

学习记录1


1 static关键字

1.1 static定义

  • static可以修饰成员变量和成员方法。
  • static修饰成员变量表示该成员变量只在内存中存储一份,可以被共享访问,修改。

1.2 static的访问方法

  • 类名.静态成员变量(常用)
  • 对象.静态成员变量(不常用)

static访问方法演示

1.3 实例成员变量访问方式

  • 对象.实例成员变量
package com.liu.day01;

public class User {
    //创建静态成员变量
    public static int onlineNumber = 100;

    //实例成员变量,属于每个对象,必须要用对象名访问
    private String name;
    private  int age;

    public static void main(String[] args) {
        //1.通过 类名.静态成员变量 来访问静态成员变量
        System.out.println(User.onlineNumber);
        //2.通过 对象.静态成员变量 来访问静态成员变量
        User user = new User();
        System.out.println(++user.onlineNumber);
        //同一个类中的静态成员变量的访问可以省略类名
        user.onlineNumber++;
        System.out.println(onlineNumber);
        //实例成员变量访问
        user.name = "zhenxi";
        user.age = 23;
        System.out.println(user.age);
        System.out.println(user.name);
    }
}

1.4 两种成员变量在什么情况定义

  • 静态成员变量:对象需要共享的信息
  • 实例成员变量:属于每个对象,且每个对象的信息不同时

1.5 static成员变量的内存机制

  • 静态成员变量会在创建方法区时就在堆内存中产生。

static的内存机制

1.6 static成员方法访问方式

  • 与静态成员变量访问方法一致:类.静态成员方法
  • 实例成员方法访问方式:对象.实例成员方法
package com.liu.day01;

public class Student {
    private String studentName;
    private String studentId;
    //静态成员方法:属于该类
    public static int getMax(int age1,int age2){
        return age1>age2 ? age1:age2;
    }
    //实例方法:属于对象
    public void study(){
        System.out.println(studentName+"在好好学习。");
    }

    public static void main(String[] args) {
        //通过类直接访问静态成员方法
        System.out.println(getMax(10, 20));
        //通过对象来访问实例方法
        Student student = new Student();
        student.studentName="zhenxi";
        student.study();
        Student student1 = new Student();
        student1.studentName="努力";
        student1.study();
    }
}

1.7 static成员方法内存机制

static成员方法内存机制

1.8 static访问注意事项

  • 静态方法只能访问静态成员,不可以直接访问实例成员。

访问事宜

  • 实例方法可以访问静态成员,也可以访问实例成员。

image-20220719182445718

  • 静态方法中是不可以出现this关键字的。

不能出现this关键字

1.9 static应用知识:工具类

  • 工具类的内部都是一些静态的方法,每个静态方法完成一个功能。
  • 一次编写,处处可用,提高代码的重用性,避免重复造轮子。
  • 工具类的构造器要进行私有化处理。

例如1:编写一个创造随机验证码的工具类LoginUtils

package com.liu.day01;

import java.util.Random;
//工具类
public class LoginUtils {
    //工具类无需创建对象,故把工具类的构造器进行私有化。
    private LoginUtils(){}

    //创建一个随机验证码
    public static String createVerifyCode(int n){
        String code = "";
        String data = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        Random random = new Random();
        for (int i = 0; i < n; i++) {
            int index = random.nextInt(data.length());
            code += data.charAt(index);
        }
        return code;
    }
}

当其它类需要生成验证码时,直接使用:类.静态成员方法,即可调用。

image-20220719224100800

例如2:编写定义一个数组工具类

package com.liu.day01;

public class ArraysUtils {
    //首先,私有化构造器,使外部不能创建对象
    private ArraysUtils(){}
    //返回整数数组中的内容方法
    public static String toString(int[] array){
        //首先对传进来的数组进行校验
        if (array == null){
            return null;
        }

        //对数组中的内容进行拼接
        String result = "[";
        for (int i = 0; i < array.length; i++) {
            result += (i == array.length-1?array[i]:array[i]+",");
        }
        result += "]";
        return result;
    }
    //统计平均值 只考虑浮点型数组
    public static float getAerage(float[] array){
        //首先对传进来的数组进行校验
        if (array == null){
            return 0;
        }
        float aerAge = 0;
        float max=array[0];//最大值
        int maxIndex=0;//最大值索引
        float min=array[0];//最小值
        int minIndex=0;//最小值索引
        //通过循环找出最大值最小值
        for (int i = 0; i < array.length; i++) {
            if (max<array[i]){
                max = array[i];
                maxIndex = i;
            }
            if (min>array[i]){
                min = array[i];
                minIndex = i;
            }
        }
        //定义一个新数组,将除去最大值最小值的数据放入新数组中
        float[] newArray= new float[array.length-2];
        int j = 0;

        for (int i= 0; i < array.length; i++) {
            if (!(i == maxIndex || i == minIndex)){
                newArray[j]=array[i];
                j++;
            }
        }
        //求得新数组中的总数
        float sum=0;
        for (int i = 0; i < newArray.length; i++) {
            sum += newArray[i];
        }
        //获得平均值
        aerAge=sum/array.length;
        return aerAge;
    }
}

数组工具类的调用测试:

image-20220719232124419

1.10 static应用知识:代码块

  • 代码块是类的5大成分。
  • 格式:static{}。
  • 特点:随着类的加载而加载,并且自动触发只执行一次。
  • 使用场景:在类加载的时候做一些静态数据初始化的操作。
package com.liu.day01;

import java.util.ArrayList;

public class StaticDemo2 {
    /*
    * 静态资源*/
    public static String schoolName;

    /*
    * 实例资源*/
    private String name;

    //静态代码块 可以用于初始化静态设置
    static {
        System.out.println("---静态代码块被执行---");
        schoolName = "zhenxi";
    }

    //实例代码块 不常用了解即可
    {
        System.out.println("---实例代码块执行---");
        name="努力";
    }
    //无参构造
    public StaticDemo2(){
        System.out.println("---无参构造执行---");
    }

    public static void main(String[] args) {
        System.out.println("---main方法执行---");
        System.out.println(schoolName);
        StaticDemo2 staticDemo2 = new StaticDemo2();
        System.out.println(staticDemo2.name);
    }
}

运行结果:

---静态代码块被执行---
---main方法执行---
zhenxi
---实例代码块执行---
---无参构造执行---
努力

Process finished with exit code 0

例二:静态代码块初始化牌组

package com.liu.day01;

import java.util.ArrayList;

public class StaticDemo3 {

    public static ArrayList<String> cards = new ArrayList<>();

    static {
        String[] sizes = {"2","3","4","5","6","7","8","9","10","J","Q","K","A"};
        String[] colors = {"♥","♦","♣","桃花"};
        for (int i = 0; i < sizes.length; i++) {
            for (int j = 0; j < colors.length; j++) {
                String card = sizes[i] + colors[j];
                cards.add(card);
            }
        }
        cards.add("小🃏");
        cards.add("大🃏");
    }

    public static void main(String[] args) {
        System.out.println(cards);
    }
}

1.11 单例设计模式

饿汉单例模式

  • 定义一个类,把构造器私有。
  • 定义一个静态变量存储一个对象。
package com.liu.day01;

public class SingleInstance {
    //第一步:私有化构造器
    private SingleInstance(){}
    //第二步:创建一个静态对象
    public static SingleInstance singleInstance = new SingleInstance();
}
package com.liu.day01;

public class Test2 {
    public static void main(String[] args) {
        //通过类. 来获得对象
        SingleInstance singleInstance = SingleInstance.singleInstance;
    }
}

懒汉单例设计模式:在真正需要该对象时,才会去创建一个对象。

  • 定义一个类,私有化构造器。
  • 定义一个静态成员变量存储一个对象。
  • 提供一个返回单例对象的方法。
package com.liu.day01;

public class SingleInstance2 {
    //1.私有化构造器
    private SingleInstance2(){}
    //2.创建一个静态变量 这里私有化是为了防止通过类名.静态成员变量获得instance
    private static SingleInstance2 instance;
    //3.提供一个方法,对外返回单例对象
    public static SingleInstance2 getInstance(){
        //首先判断instance是否第一次创建,若是则创建对象,若不是则直接返回对象。
        if (instance == null){
            instance =new SingleInstance2();
        }
        return instance;
    }
}
package com.liu.day01;

public class Test2 {
    public static void main(String[] args) {
        //通过类.静态成员方法 来获得对象
        SingleInstance2 instance = SingleInstance2.getInstance();
    }
}

2 继承

2.1 继承的定义和好处

  • 继承就是Java允许我们用extends关键字,让一个类和另一个类建立一种父子关系。
  • 优点:提高代码的复用性,减少了代码冗余,增强类的功能扩展性。
  • 子类继承父类,子类可以得到父类的属性和行为,子类比父类更强大

例如:父类People

package com.liu.day01.extends_test;

public class People {
    //子类都有的属性
    private String name;
    private int age;
    //子类都有的方法
    public void lookCourse(){
        System.out.println("查看课表");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

子类:Teacher

package com.liu.day01.extends_test;

public class Teacher extends People{
    //子类特有的属性
    private String department;
    //子类特有的方法
    public void releaseIssues(){
        System.out.println("发布问题~");
    }

    public String getDepartment() {
        return department;
    }

    public void setDepartment(String department) {
        this.department = department;
    }
}

子类:Student

package com.liu.day01.extends_test;

public class Student extends People{
    //子类特有的属性
    private String className;
    //子类特有的方法
    public void learning(){
        System.out.println("听课");
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }
}

测试:

package com.liu.day01.extends_test;

public class Test1 {
    public static void main(String[] args) {
        Student student = new Student();
        student.lookCourse();
        student.learning();
        student.setName("zhenxi");
        System.out.println(student.getName());
        student.setClassName("电子信息");
        System.out.println(student.getClassName());

        Teacher teacher = new Teacher();
        teacher.lookCourse();
        teacher.releaseIssues();
        teacher.setName("努力");
        System.out.println(teacher.getName());
        teacher.setDepartment("计控学院");
        System.out.println(teacher.getDepartment());
    }
}

2.2 继承的特点

  • 子类可以继承父类的属性和行为,但是子类不能继承父类的构造器。
  • Java是单继承模式,一个类只能继承一个直接父类。
  • Java不支持多继承、但支持多层继承。
  • Java的所有类,要么直接继承Object类,要么间接继承Object类。
  • 子类可以直接使用父类的静态成员,但不是子类继承了父类的静态成员,而是父类共享给子类。
  • 子类可以继承父类的私有成员,但是不能直接访问。

2.3 继承后访问成员

遵循就近原则

  • 先在子类局部范围查找。
  • 然后在子类成员范围查找。
  • 然后在父类成员范围查找,如果在父类成员中没有找到则报错。
package com.liu.day01.extends_test;

import java.util.Calendar;

public class Test2 {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.eat();
        cat.showId();
    }
}
class Animal{
    //父类范围
    public int id = 10;
    public void eat(){
        System.out.println("吃东西~");
    }
}
class Cat extends Animal{
    //子类范围
    public int id = 11;
    public void eat(){
        System.out.println("吃猫粮");
    }
    public void showId(){
        //局部
        int id = 12;
        System.out.println(id);
    }
}

2.4 方法重写

在继承中,子类出现了和父类中一模一样的方法声明,我们就称子类这个方法是重写的方法。

重写注意事项:

  • 重写方法都加上@Override注解。
  • 重写方法的名称、形参列表必须与被重写方法的名称和参数列表一致。
  • 父类的私有方法、静态方法不能被重写。
  • 子类重写父类方法时,访问权限必须大于或者等于父类被重写的方法的权限。
package com.liu.day01;

public class Override_Test {
    public static void main(String[] args) {
        NewPhone newPhone = new NewPhone();
        newPhone.call();
        newPhone.setMessage();
    }
}
class OldPhone{
    public void call(){
        System.out.println("打电话");
    }
    public void setMessage(){
        System.out.println("发送消息");
    }
}
class NewPhone extends OldPhone{
    @Override
    public void call(){
        System.out.println("打电话");
        System.out.println("视频通话");
    }
    @Override
    public void setMessage(){
        System.out.println("发送消息");
        System.out.println("发送图片");
    }
}

2.5 子类构造器特点

子类中的所有构造器默认都会先访问父类中的无参构造,然后再执行自己的构造器。

package com.liu.day01.constructor;

public class Test1 {
    public static void main(String[] args) {
        Student student = new Student();
        Student student1 = new Student(1);
    }
}
class People{
    private String name;
    public People(){
        System.out.println("父类无参构造执行");
    }
}

class Student extends People{
    private int id;
    public Student() {
        System.out.println("子类无参构造执行");
    }

    public Student(int id){
        this.id = id;
        System.out.println("子类有参构造执行");
    }
}
运行结果:
父类无参构造执行
子类无参构造执行
父类无参构造执行
子类有参构造执行

子类构造器如何访问父类有参构造器?

通过使用super来调用父类构造器来初始化继承自父类的数据。

package com.liu.day01.constructor;

public class Test2 {
    public static void main(String[] args) {
        Zi zi = new Zi("zhenxi", 23);
    }
}
class Fu{
    private String name;
    private int id;

    public Fu() {
    }
    //父类的有参构造
    public Fu(String name, int id) {
        this.name = name;
        this.id = id;
    }
}

class Zi extends Fu{
    public Zi() {
    }
    //使用super()初始化父类有参构造中的参数
    public Zi(String name, int id) {
        super(name, id);
    }
}

2.6 this和super

关键字访问成员变量访问成员方法访问构造方法
thisthis.成员变量 访问本类成员变量this.成员方法 访问本类成员方法this(...) 访问本类构造器
supersuper.成员变量 访问父类成员变量super.成员方法 访问父类成员方法super(...) 访问父类构造器

注意:this(...)、super(...)使用注意点

  • 子类通过this(...)去调用本类的其它构造器,本类其它构造器会通过super手动调用父类构造器,最终还是会调用父类的构造器。
  • this(...) super(...)都只能放在构造器的第一行,二者不能共存在同一构造器中。

学习记录2


1 导包

  • 相同包下的类可以直接访问,不同包下的类必须导包,才可以使用,导包格式: import 包名.类名;
  • 假如一个类中需要用到不同类,而这个两个类的名称是一样的, 那么默认只能导入一个类,另一个类要带包名访问。

image-20220720162956032

2 权限修饰符

  • 主要是用来控制一个成员能够被访问的范围。
  • 可以修饰成员变量,方法,构造器,内部类,不同权限修饰符修饰的成员能够被访问的范围将受到限制。
  • 权限修饰符:有四种作用范围由小到大(private ->缺省-> protected -> public )
  • 在同一类下,四种权限修饰符修饰的变量和方法都可以在另一个方法中被调用。
package com.liu.day02.authority_Test;

public class Fu {
    private void privatePrint(){
        System.out.println("私有权限");
    }
    void print(){
        System.out.println("缺省权限");
    }
    protected void protectedPrint(){
        System.out.println("受保护权限");
    }
    public void publicPrint(){
        System.out.println("公共权限");
    }
    //同一个类中可以全部访问
    public static void main(String[] args) {
        Fu fu = new Fu();
        fu.privatePrint();
        fu.print();
        fu.protectedPrint();
        fu.publicPrint();
    }
}
  • 在同一包下(邻居关系,无继承),public、protected、default修饰的变量和方法都可以在同一包下另一类中被调用。private修饰的不可以。
package com.liu.day02.authority_Test;

public class Student {
    public static void main(String[] args) {
        //同一个包中的其它类:不能访问私有
        Fu fu = new Fu();
        fu.protectedPrint();
        fu.print();
        fu.publicPrint();
    }
}
  • 不同包下,但是有继承关系。public、protected修饰的变量和方法都可以在同一包下另一类中被调用。default、private修饰的不可以。
package com.liu.day02.authority_Test2;

import com.liu.day02.authority_Test.Fu;

public class Zi extends Fu{
    public static void main(String[] args) {
        //不同包下的子类能够访问受保护的以及公有的
        Zi zi = new Zi();
        zi.protectedPrint();
        zi.publicPrint();
    }
}
  • 不同包,且非子类public修饰的变量和方法都可以在同一包下另一类中被调用。protected、default、private修饰的不可以。
package com.liu.day02.authority_Test2;

import com.liu.day02.authority_Test.Fu;

public class Teacher {
    public static void main(String[] args) {
        //不同包,且无关系类,只能使用公有权限。
        Fu fu = new Fu();
        fu.publicPrint();
    }
}

3 final关键字

final的作用

  • final关键字是最终的意思,可以修饰(类、方法、变量)。
  • 修饰类: 表明该类是最终类,不能被继承。
  • 修饰方法: 表明该方法是最终方法,不能被重写。
  • 修饰变量: 表示该变量第一次赋值后,不能再次被赋值(有且仅能被赋值一次)。

final修饰变量的注意

  • final修饰的变量是基本类型:那么变量存储的数据值不能发生改变。
  • final修饰的变量是引用类型:那么变量存储的地址值不能发生改变,但是地址指向的对象内容是可以发生变化的。

4 常量

  • 常量是使用了public static final修饰的成员变量,必须有初始化值,而且执行的过程中其值不能被改变。
  • 常量的作用和好处:可以用于做系统的配置信息,方便程序的维护,同时也能提高可读性。
  • 常量命名规范:英文单词全部大写,多个单词下划线连接起来。

常量的执行原理

  • 在编译阶段会进行“宏替换",把使用常量的地方全部替换成真实的字面量。
  • 这样做的好处是让使用常量的程序的执行性能与直接使用字面量是一样的。

5 枚举类型

  • 枚举是Java中的一 种特殊类型。
  • 枚举的作用: "是为了做信息的标志和信息的分类"。

image-20220720195021421

public final class Season extends java.lang.Enum<Season> {
  public static final Season SPRING;
  public static final Season SUMMER;
  public static final Season AUTUMN;
  public static final Season WINTER;
  public static Season[] values();
  public static Season valueOf(java.lang.String);
  static {};
}

枚举的特征:

  • 枚举类都是继承了枚举类型: java.lang.Enum
  • 枚举都是最终类,不可以被继承。
  • 构造器的构造器都是私有的,枚举对外不能创建对象。
  • 枚举类的第一行默认都是罗列枚举对象的名称的。
  • 枚举类相当于是多例模式。

6 抽象类

6.1 抽象类的定义

  • 在Java中abstract是抽象的意思,可以修饰类、成员方法。
  • abstract修饰类,这个类就是抽象类;修饰方法,这个方法就是抽象方法。
  • 抽象方法只有方法签名,不能声明方法体。
  • 一个类中如果定义了抽象方法,这个类必须声明成抽象类,否则报错。

image-20220720214155219

6.2 抽象类使用场景

  • 抽象类可以理解成不完整的设计图,一般作为父类,让子类来继承。
  • 当父类知道子类一定要完成某些行为,但是每个子类该行为的实现又不同,于是该父类就把该行为定义成抽象方法的形式,具体实现交给子类去完成。此时这个类就可以声明成抽象类。

image-20220720221239935

  • 一个类如果继承了抽象类,那么这个类必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。

image-20220720221827861

案例:

image-20220720222020428

  • Card类
package com.liu.day02.abstract_Test;

public abstract class Card {
    private String userName;
    private Double money;

    public abstract void pay(double payMoney);

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }
}
  • ColdenCard类
package com.liu.day02.abstract_Test;

public class GoldenCard extends Card{

    @Override
    public void pay(double payMoney) {
        payMoney = 0.8 * payMoney;
        System.out.println("应支付"+payMoney);
        super.setMoney(super.getMoney() - payMoney);
        System.out.println("余额"+ super.getMoney());
    }
}
  • SilverCard类
package com.liu.day02.abstract_Test;

public class SilverCard extends Card{
    @Override
    public void pay(double payMoney) {
        payMoney = 0.85 * payMoney;
        System.out.println("应支付"+payMoney);
        super.setMoney(super.getMoney() - payMoney);
        System.out.println("余额"+ super.getMoney());
    }
}
  • 测试:
package com.liu.day02.abstract_Test;

public class Test1 {
    public static void main(String[] args) {
        GoldenCard goldenCard = new GoldenCard();
        goldenCard.setMoney(1000.0);
        goldenCard.pay(200);
        SilverCard silverCard = new SilverCard();
        silverCard.setMoney(1000.0);
        silverCard.pay(200);
    }
}
  • 输出结果
应支付160.0
余额840.0
应支付170.0
余额830.0

6.3 抽象类的特征和注意事项

  • 类有的成员(成员变量、方法、构造器)抽象类都具备。
  • 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类。
  • 一个类继承了抽象类必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
  • 不能用abstract修饰变量、代码块、构造器。
  • 最重要的特征:得到了抽象方法,失去了创建对象的能力(有得有失)

6.4 模板方法模式

使用场景说明:当系统中出现同一个功能多处在开发,而该功能中大部分代码是一样的,只有其中部分可能不同的时候。

模板方法模式实现步骤

  • 把功能定义成一个所谓的模板方法,放在抽象类中,模板方法中只定义通用且能确定的代码。
  • 模板方法中不能决定的功能定义成抽象方法让具体子类去实现。

案例需求:

image-20220721090732140

  • Student类
package com.liu.day02.abstract_Test1;

public abstract class Student {

    //将该方法加上final关键字,使其变成最终方法
    public final void write(){
        System.out.println("题目:《我的爸爸》");
        System.out.println("我的爸爸是一个普通的平凡人。");
        System.out.println(text());
        System.out.println("我爱我的爸爸。");
    }
    //编写抽象方法
    public abstract String text();
}
  • StudentPupil类
package com.liu.day02.abstract_Test1;

public class StudentPupil extends Student{
    @Override
    public String text() {
        return "谢谢你,父亲。";
    }
}
  • StudentMiddle类
package com.liu.day02.abstract_Test1;

public class StudentMiddle extends Student{
    @Override
    public String text() {
        return "愿您一生健康。";
    }
}
  • Test1类
package com.liu.day02.abstract_Test1;

public class Test1 {
    public static void main(String[] args) {
        StudentMiddle studentMiddle = new StudentMiddle();
        studentMiddle.write();
        StudentPupil studentPupil = new StudentPupil();
        studentPupil.write();
    }
}

模板方法模式解决了什么问题?

  • 提高了代码的复用性。
  • 模板方法已经定义了通用结构,模板方法把不能确定的部分定义成抽象方法,交给子类实现,因此,使用者只需要关心自己需要实现的功能即可。

7 接口

7.1 接口定义

image-20220721100706883

7.2 接口的用法

  • 接口是用来被类实现(implements) 的,实现接口的类称为实现类。实现类可以理解成所谓的子类。
  • 一个类实现接口,必须重写完全部接口的全部抽象方法,否则这个类需要定义成抽象类。

image-20220721102049829

7.3 接口小结

  • 类和类的关系: 单继承。
  • 类和接口的关系: 多实现。
  • 接口和接口的关系:多继承,一个接口可以同时继承多个接口。
  • 接口多继承的作用:规范合并,整合多个接口为同一个接口,便于子类实现。

image-20220721103634256


学习记录3


1 多态

1.1 多态的定义

  • 同类型的对象,执行同一个行为,会表现出不同的行为特征。

1.2 多态的前提

  • 有继承/实现关系;有父类引用指向子类对象;有方法重写。

1.3 多态的常见形式

  • 父类类型 对象名称 = new 子类构造器;
  • 接口 对象名称 = new 实现类构造器;

image-20220722090940696

1.4 多态中成员访问特点

  • 方法调用:编译看左边,运行看右边。
  • 变量调用:编译看左边,运行也看左边。( 多态侧重行为多态)

image-20220722091646809

1.5 多态的优劣势

优势:

  • 在多态形式下,右边对象可以实现解耦合,便于扩展和维护。
  • 定义方法的时候,使用父类型作为参数,该方法就可以接收这父类的一切子类对象,体现出多态的扩展性与便利。

劣势:

  • 多态下不能使用子类的独有功能。

1.6 多态类型转换

自动类型转换(从子到父):子类对象赋值给父类类型的变量指向。

强制类型转换(从父到子):

  • 此时必须进行强制类型转换: 子类对象变量= (子类)父类类型的变量。
  • 作用:可以解决多态下的劣势,可以实现调用子类独有的功能。
  • 注意:如果转型后的类型和对象真实类型不是同一种类型,那么在转换的时候就会出现ClassCastException

image-20220722093527056

  • Java建议强转转换前使用instanceof判断当前对象的真实类型,再进行强制转换。

image-20220722101247619

1.7 案例

需求:

  • 使用面向对象编程模拟:设计一个电脑对象,可以安装2个USB设备。
  • 鼠标:被安装时可以完成接入、调用点击功能、拔出功能。
  • 键盘:被安装时可以完成接入、调用打字功能、拔出功能。

USB接口实现:

package com.liu.day03.polymorphic;

public interface USB {
    void connect();
    void unConnect();
}

电脑类实现:

package com.liu.day03.polymorphic;

public class Computer {
    public void installUsb(USB usb){
        usb.connect();
        if (usb instanceof Mouse){
            Mouse mouse = (Mouse) usb;
            mouse.click();
        }else if (usb instanceof Keyboard){
            Keyboard keyboard = (Keyboard) usb;
            keyboard.typing();
        }
        usb.unConnect();
    }
}

鼠标类实现:

package com.liu.day03.polymorphic;

public class Mouse implements USB{
    @Override
    public void connect() {
        System.out.println("鼠标连接成功");
    }
    public void click(){
        System.out.println("鼠标点击功能");
    }

    @Override
    public void unConnect() {
        System.out.println("鼠标断开连接");
    }
}

键盘类实现:

package com.liu.day03.polymorphic;

public class Keyboard implements USB{
    @Override
    public void connect() {
        System.out.println("键盘连接成功");
    }
    public void typing(){
        System.out.println("键盘打字功能");
    }

    @Override
    public void unConnect() {
        System.out.println("键盘断开连接");
    }
}

测试:

package com.liu.day03.polymorphic;

public class Test2 {
    public static void main(String[] args) {
        USB usb1 = new Mouse();
        USB usb2 = new Keyboard();
        Computer computer = new Computer();
        computer.installUsb(usb1);
        computer.installUsb(usb2);
    }
}

运行结果:
鼠标连接成功
鼠标点击功能
鼠标断开连接
键盘连接成功
键盘打字功能
键盘断开连接

Process finished with exit code 0

2 内部类

2.1 内部类定义

  • 内部类就是定义在一个类里面的类,里面的类可以理解成(寄生),外部类可以理解成(宿主)。

2.2 内部类的使用场景、作用

  • 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构可以选择使用内部类来设计。
  • 内部类通常可以方便访问外部类的成员,包括私有的成员。
  • 内部类提供了更好的封装性,内部类本身就可以用private protectecd等修饰,封装性可以做更多控制。

2.3 静态内部类 了解即可

  • 有static修饰,属于外部类本身。
  • 它的特点和使用与普通类是完全一样的, 类有的成分它都有,只是位置在别人里面而已。
  • 可以直接访问外部类的静态成员,不能直接访问外部类的实例成员。
package com.liu.day03.innerClass;

public class StaticInnerClass {

    private String className;
    public static int classId;
    public final int CLASS_ID = 2;

    //静态内部类
    public static class InClass{
        //实例成员
        private String name;

        public InClass() {
        }

        public InClass(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
        //静态成员
        public static int id;

        //常量
        public final int USER_ID = 12;


        //静态内部类能调用主类
        public void getInnerClass(){
            //只能调用静态成员~
            classId = 10;
        }

    }

    public static void main(String[] args) {
        //创建静态内部类对象的格式
        InClass aClass = new StaticInnerClass.InClass();
    }
}

总结:静态内部类可以把其当作一个静态成员,静态成员只能访问静态的成员不能访问实例成员。

2.4 实例内部类 了解即可

  • 无static修饰,属于外部类的对象。
  • 可以直接访问外部类的静态成员,实例方法中可以直接访问外部类的实例成员。
  • 成员内部类创建对象:外部类名.内部类名对象名= new外部类构造器.new内部类枸造器();
package com.liu.day03.innerClass;

public class OnClass {
    private String name;
    private static int id;

    public static void setId(){
        id = 12;
    }
    //实例内部类
    public class InnerClass{
        private String inName;
        //这里使用的是8版本 所以不能在实例内部类中创建静态成员
        //public static int inId;
        public void getOnClass(){
            //可以调用实例成员
            name = "zhenxi";
            //可以调用静态成员
            id = 13;
            setId();
        }
    }

    public static void main(String[] args) {
        //实例内部类创建对象的格式
        InnerClass innerClass = new OnClass().new InnerClass();
        innerClass.getOnClass();
    }
}
  • 注意:在成员内部类中访问所在外部类对象,格式:外部类名.this
package com.liu.day03.innerClass;

public class People {
    private int heartbeat = 90;
    public class Heart{
        private int heartbeat = 100;

        public void show(){
            int heartbeat = 120;
            System.out.println(heartbeat); //调用局部变量
            System.out.println(this.heartbeat);//调用内部类的实例对象
            System.out.println(People.this.heartbeat);//调用外部类的实例对象
        }
    }

    public static void main(String[] args) {
        Heart heart = new People().new Heart();
        heart.show();
    }
}

/*运行结果
    120
    100
    90
*/

2.5 局部内部类 了解即可

  • 局部内部类放在方法、代码块、构造器等执行体中。
package com.liu.day03.innerClass;

public class Test {
    static {
        //静态代码块中的局部内部类
        class Dog{
            private String dogName;

            public Dog() {
            }

            public Dog(String dogName) {
                this.dogName = dogName;
            }
        }
    }
    public static void main(String[] args) {
        //局部内部类 创建于main方法中
        class Cat{
            private String name;
            private int id;

            public Cat() {
            }

            public Cat(String name, int id) {
                this.name = name;
                this.id = id;
            }

            public String getName() {
                return name;
            }

            public void setName(String name) {
                this.name = name;
            }

            public int getId() {
                return id;
            }

            public void setId(int id) {
                this.id = id;
            }
        }
        Cat cat = new Cat("橘猫",2);
    }
}
  • 局部内部类的类文件名为:外部类$N内部类.class。

image-20220722170341684

2.6 匿名内部类 重点

  • 本质上是一个没有名字的局部内部类,定义在方法中、代码块中等。
  • 作用:方便创建子类对象,最终目的为了简化代码编写。
  • 匿名内部类是一个没有名字的内部类。
  • 匿名内部类写出来就会产生一个匿名内部类的对象。
  • 匿名内部类的对象类型相当于是当前new的那个的类型的子类类型。
  • 匿名内部类可以作为方法的实际参数进行传输。

例一:

package com.liu.day03.innerClass;


public class Test2 {
    public static void main(String[] args) {
        Swimming s = new Swimming() {
            @Override
            public void swim() {
                System.out.println("学生快乐的自由泳");
            }
        };
        go(s);

        Swimming s1 = new Swimming() {
            @Override
            public void swim() {
                System.out.println("老师贼快~~~~~");
            }
        };
        go(s1);

        System.out.println("--------------");

        go(new Swimming() {
            @Override
            public void swim() {
                System.out.println("运动员🏊的贼快啊~~~~~");
            }
        });
    }

    public static void go(Swimming s){
        System.out.println("开始。。。");
        s.swim();
        System.out.println("结束。。。");
    }
}


interface Swimming{
    void swim();
}

例二:swing编程中的应用

package com.liu.day03.innerClass;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;


public class Test3 {
    public static void main(String[] args) {
        // 1、创建窗口
        JFrame win = new JFrame("登录界面");
        JPanel panel = new JPanel();
        win.add(panel);

        // 2、创建一个按钮对象
        JButton btn = new JButton("登录");

        // 注意:讲解匿名内部类的使用
        btn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(win, "点击事件");
            }
        });

//        btn.addActionListener( e ->  JOptionPane.showMessageDialog(win, "简略的匿名内部类") );


        // 3、把按钮对象添加到桌布上展示
        panel.add(btn);

        // 4、展示窗口
        win.setSize(400, 300);
        win.setLocationRelativeTo(null);
        win.setVisible(true);

    }
}

3 Object类

Object类的作用:

  • 一个类要么默认继承了0bject类,要么间接继承了0bject类,0bject类是Java中的祖宗类。
  • Object类的方法是一切子类都可以直接使用的。

Object类中的常用方法:

方法名说明
pubilc String toString()Returns a string representation of the object.
默认是返回当前对象在堆内存中的地址信息:类的全限名@内存地址
public Boolean equals(Object obj)Indicates whether some other object is "equal to" this one.
默认是比较当前对象与另一个对象的地址是否相同,相同返回true,不同返回false

当未重写toString以及equals方法时:

Student类:

package com.liu.day03.object_Test;

public class Student {
    private String name;
    private int id;

    public Student(String name, int id) {
        this.name = name;
        this.id = id;
    }

    public Student() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

测试类:Test:

package com.liu.day03.object_Test;

public class Test {
    public static void main(String[] args) {
        Student student = new Student("zhenxi",2022935775);
        Student student1 = new Student("成龙",2022935776);
        System.out.println(student.toString());
        System.out.println(student.equals(student1));
    }
}
/**
运行结果:
com.liu.day03.object_Test.Student@1540e19d
false
*/

当重写toString方法和equals方法:

    @Override
    public boolean equals(Object o) {
        //首先判断传进来的对象是不是原对象
        if (this == o) return true;
        //判断传进来的对象是否为空,以及判断是否与本对象是不是同一个类型
        if (o == null || getClass() != o.getClass()) return false;
        //将传进来的对象转成Student类型对象
        Student student = (Student) o;
        //判断对象中的内容是否相同
        return id == student.id && Objects.equals(name, student.name);
    }
    
    //将对象的内容输出出去
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", id=" + id +
                '}';
    }

再次进行测试:

package com.liu.day03.object_Test;

public class Test {
    public static void main(String[] args) {
        Student student = new Student("zhenxi",2022935777);
        Student student1 = new Student("成龙",2022935776);
        System.out.println(student.toString());
        System.out.println(student1.toString());
        System.out.println(student.equals(student));
        System.out.println(student.equals(student1));
    }
}

/**
运行结果:
Student{name='zhenxi', id=2022935775}
Student{name='成龙', id=2022935776}
true
false
*/

4 Objects类

Objects仍然继承Object类。该类最关键的就是它的equals方法,官方在进行字符串比较时,没有对象自己的equals方法,而是选择了Objects的equals方法来比较两个对象。在使用Objects的equals方法比较两个对象时,底层会先进行非空判断,从而可以避免空指针异常,再进行equals比较。

方法名说明
public static boolean equals(Object a,Object b)Returns true if the arguments are equal to each other and false otherwise.

这是Objects类的equals方法:

 public static boolean equals(Object a, Object b) {
        return (a == b) || (a != null && a.equals(b));
  }

主要用于解决两个对象进行比较时出现NullPointerException这个问题:

可以看到阿里的Java开发手册里的强制要求:

image-20220724182239344

public class Test3 {
    public static void main(String[] args) {
        String s1 = null;
        String s2 = "liu";
        s1.equals(s2);
    }
}
/**
运行结果
Exception in thread "main" java.lang.NullPointerException
    at com.liu.day03.object_Test.Test3.main(Test3.java:7)
*/

当使用Objects的静态equals方法时:

public class Test3 {
    public static void main(String[] args) {
        String s1 = null;
        String s2 = "liu";
        System.out.println(Objects.equals(s1, s2));
    }
}
/**
运行结果:
false
*/

5 StringBuilder类

  • StringBuilder是一个可变的字符串类,我们可以把它看成是一个对象容器。
  • 作用:提高字符串的操作效率,如拼接、修改等。
StringBuilder构造器说明
public StringBuilder()Constructs a string builder with no characters in it and an initial capacity of 16 characters.
构造一个其中没有字符且初始容量为 16 个字符的字符串构建器。
public StringBuilder(String str)Constructs a string builder initialized to the contents of the specified string.
构造一个初始化为指定字符串内容的字符串构建器。
StringBuilder常用方法说明
public StringBuilder append(任意类型)Appends the string representation of the xxxx argument to the sequence.
xxxx 参数的字符串表示形式附加到序列中。
public StringBuilder reverse()Causes this character sequence to be replaced by the reverse of the sequence.
导致此字符序列被相反的序列替换。
public int length()Returns the length (character count).
返回长度
public String toString()Returns a string representing the data in this sequence.
返回表示此序列中数据的字符串。

方法使用:

public class Test {
    public static void main(String[] args) {
        StringBuilder stringBuilder = new StringBuilder();
        StringBuilder str = new StringBuilder("zhenxi");
        str.append(2022).append(935).append(775).append("珍惜大学");
        //注意最终还是需要通过toString转换成String类型
        System.out.println(str.toString());
        System.out.println(str.reverse());
        System.out.println(str.length());
    }
}
/**
运行结果:
zhenxi2022935775珍惜大学
学大尔哈齐齐5775392202畅刘
18
*/

与String类比较图:

image-20220724160640831

image-20220724160744220

总结:

  • String:内容是不可变的、拼接字符串性能差。
  • StringBuilder:内容是可变的、拼接字符串性能好、代码优雅。

需求:

设计一个方法用于输出任意整型数组的内容,要求输出成如下格式:”该数组内容为: [11, 22, 33, 44,55]”

package com.liu.day03.object_Test;

public class StringBuilderTest2 {
    public static void main(String[] args) {
        int[] arr1 = null;
        System.out.println(toString(arr1));

        int[] arr2 = {10, 22, 19};
        System.out.println(toString(arr2));

        int[] arr3 = {};
        System.out.println(toString(arr3));
    }

    // 1、定义方法接收任意整型数组,返回数组内容格式
    public static String toString(int[] arr){
       if(arr != null){
            // 2、开始拼接内容。
           StringBuilder sb = new StringBuilder("[");
           for (int i = 0; i < arr.length; i++) {
               sb.append(arr[i] ).append(i == arr.length - 1 ? "" : ", ");
           }
           sb.append("]");
           return sb.toString();
       }else {
           return null;
       }
    }
}

6 Math类

Math类的常用方法说明
public static int abs(int a)Returns the absolute value of an int value.
返回 int 值的绝对值。
public static double ceil(double a)Returns the smallest (closest to negative infinity) double value that is greater than or equal to the argument and is equal to a mathematical integer.
返回大于或等于参数且等于数学整数的最小(最接近负无穷大)double 值。
public static double floor(double a)Returns the largest (closest to positive infinity) double value that is less than or equal to the argument and is equal to a mathematical integer.Returns the length (character count).
返回小于或等于参数且等于数学整数的最大(最接近正无穷大)double 值。返回长度(字符数)。
public static int round(float a)Returns the closest int to the argument, with ties rounding to positive infinity.
返回最接近参数的 int,并舍入为正无穷大。
public static int max(int a, int b)Returns the greater of two int values.
返回两个 int 值中的较大者。
public static int min(int a, int b)Returns the smaller of two int values.
返回两个 int 值中较小的一个。
static double pow(double a, double b)Returns the value of the first argument raised to the power of the second argument.
返回第一个参数的第二个参数次幂的值。
static double random()Returns a double value with a positive sign, greater than or equal to 0.0 and less than 1.0.
返回一个带正号的“double”值,大于或等于“0.0”且小于“1.0”
package com.liu.day03.math;

public class Test {
    public static void main(String[] args) {
        System.out.println(Math.abs(-1));
        System.out.println(Math.ceil(1.01));
        System.out.println(Math.ceil(1.54));
        System.out.println(Math.floor(3.01));
        System.out.println(Math.floor(3.89));
        System.out.println(Math.max(12, 10));
        System.out.println(Math.min(12, 10));
        System.out.println(Math.pow(2, 5));
        int a = (int)(Math.random()*100)+1;
        System.out.println(a);
    }
}

/**
运行结果:
1
2.0
2.0
3.0
3.0
12
10
32.0
29

Process finished with exit code 0
*/

7 System类

和Math类一样,不能实例化,直接用类名调用即可。

System常用方法说明
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)Copies an array from the specified source array, beginning at the specified position, to the specified position of the destination array.
将指定源数组中的数组从指定位置开始复制到目标数组的指定位置。
public static long currentTimeMillis()Returns the current time in milliseconds.
以毫秒为单位返回当前时间。
public class Test {
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        System.out.println(startTime);
        for (int i = 0; i < 100000; i++) {
            System.out.println("i:"+i);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("运行时间为:"+(endTime-startTime)/1000.0+"s");
    }
}

/**
i:99999
运行时间为:1.141s
*/
package com.liu.day03.System;

import java.util.Arrays;

public class SystemDemo {
    public static void main(String[] args) {
        /**
         arraycopy(Object src,  int  srcPos,
         Object dest, int destPos,
         int length)
         参数一:被拷贝的数组
         参数二:从哪个索引位置开始拷贝
         参数三:复制的目标数组
         参数四:粘贴位置
         参数五:拷贝元素的个数
         */
        int[] arr1 = {10, 20, 30, 40, 50, 60, 70};
        int[] arr2 = new int[6]; // [0, 0, 0, 0, 0, 0] ==>  [0, 0, 40, 50, 60, 0]
        System.arraycopy(arr1, 3, arr2, 2, 3);
        System.out.println(Arrays.toString(arr2));
    }
}

/**
运行结果:
[0, 0, 40, 50, 60, 0]
*/

8 BigDecima类

浮点型运算的时候直接加减乘除时可能会出现数据失真(精度问题)。用来对超过16位有效位的数进行精确的计算。此类可以完成大的小数操作,而且也可以使用此类进行精确的四舍五入。从而解决浮点型运算数据失真的问题。

BigDecima类的常用方法说明
public BigDecimal add(BigDecimal augend)加法
public BigDecimal subtract(BigDecimal subtrahend)减法
public BigDecimal multiply(BigDecimal multiplicand)乘法
public BigDecimal divide(BigDecimal divisor)除法

关于BigDecima类的一些注意事项:(图片中的内容来自阿里Java开发规范)

image-20220724182432283

image-20220724182523857

package com.liu.day03.System;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.NumberFormat;

public class BigDecimalDemo {
    public static void main(String[] args) {
        // 浮点型运算的时候直接+  * / 可能会出现数据失真(精度问题)。
        System.out.println(0.09 + 0.01);
        System.out.println(1.0 - 0.32);
        System.out.println(1.015 * 100);
        System.out.println(1.301 / 100);

        System.out.println("-------------------------");
        double a = 0.1;
        double b = 0.2;
        double c = a + b;
        System.out.println(c);
        System.out.println("--------------------------");
        // 包装浮点型数据成为大数据对象 BigDeciaml
        BigDecimal a1 = BigDecimal.valueOf(a);
        BigDecimal b1 = BigDecimal.valueOf(b);
        BigDecimal c1 = a1.add(b1);
        // BigDecimal c1 = a1.subtract(b1);
        // BigDecimal c1 = a1.multiply(b1);
        // BigDecimal c1 = a1.divide(b1);
        System.out.println(c1);

        // 目的:double
        double rs = c1.doubleValue();
        System.out.println(rs);

        // 注意事项:BigDecimal是一定要精度运算的
        BigDecimal a11 = BigDecimal.valueOf(10.0);
        BigDecimal b11 = BigDecimal.valueOf(3.0);
        /**
           参数一:除数 参数二:保留小数位数  参数三:舍入模式
         */
        BigDecimal c11 = a11.divide(b11, 2, RoundingMode.HALF_UP); // 3.3333333333
        System.out.println(c11);
    }
}

学习记录4


1 Date类

Date类的对象在Java中代表的是当前所在系统的此刻日期时间。

image-20220726145159635

案例:请计算出当前时间往后走1小时121秒之后的时间是多少。

package com.liu.day04.dateDemo;

import java.util.Date;
public class DateDemo01 {
    public static void main(String[] args) {
        //Date的无参构造
        Date date = new Date();
        System.out.println(date);
        long time = date.getTime();
        System.out.println(time);
        System.out.println(System.currentTimeMillis());

        //有参构造器
        System.out.println(new Date(time));

        //请计算出当前时间往后走1小时121秒之后的时间是多少。
        long time2 = System.currentTimeMillis();
        time2 += (60*60+121)*1000;
        //通过Date的有参构造进行转换成日期对象
        Date date1 = new Date(time2);
        System.out.println(date1);
        //或者使用setTime函数
        Date date2 = new Date();
        date2.setTime(time2);
        System.out.println(date2);
    }
}

2 SimpleDateFormat类

  • 可以对Date对象或时间毫秒值格式化成我们喜欢的时间形式。
  • 也可以把字符串的时间形式解析成日期对象。

image-20220726144811431

案例一:SimpleDateFormat类的基本方法。

package com.liu.day04.simpleDateFormat;

import java.text.SimpleDateFormat;
import java.util.Date;

public class SimpleDateFormatDemo {
    public static void main(String[] args) {
        //日期对象
        Date date = new Date();
        System.out.println(date);
        //格式化日期对象
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss EEE a");
        String s = simpleDateFormat.format(date);
        System.out.println(s);

        //求121秒后的格式日期
        long l = System.currentTimeMillis() + 121*1000;
        System.out.println(simpleDateFormat.format(l));
    }
}

image-20220726145536763

需求二:使用上述方法,计算出2021年08月06日11点11分11秒,往后走2天14小时49分06秒后的时间是多少。

package com.liu.day04.simpleDateFormat;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class SimpleDateFormatDemo2 {
    public static void main(String[] args) throws ParseException {
        //请计算出2021年08月06日11点11分11秒,往后走2天14小时49分06秒后的时间是多少。
        String dateStr = "2021年08月06日 11:11:11";
        //将字符串解析为日期对象
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
        Date date = simpleDateFormat.parse(dateStr);
        long time = date.getTime()+(2L*24*60*60+14*60*60+49*60+6)*1000;
        System.out.println(simpleDateFormat.format(time));
    }
}

需求三:

  • 小贾下单并付款的时间为: 2020年11月11日0:03:47
  • 小皮下单并付款的时间为: 2020年11月11日0:10:11
  • 用代码说明这两位同学有没有参加上秒杀活动?
package com.liu.day04.simpleDateFormat;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class SimpleDateFormatDemo3 {
    public static void main(String[] args) throws ParseException {
        //开始时间和结束时间
        String startTime="2020年11月11日 00:00:00";
        String endTime="2020年11月11日 00:10:00";

        //小贾 小皮
        String xiaoJia="2020年11月11日 00:03:47";
        String xiaoPi="2020年11月11日 00:10:11";
        //解析时间
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
        Date date1 = simpleDateFormat.parse(startTime);
        Date date2 = simpleDateFormat.parse(endTime);
        Date date3 = simpleDateFormat.parse(xiaoJia);
        Date date4 = simpleDateFormat.parse(xiaoPi);
        //时间判断函数
        if (date3.after(date1) && date3.before(date2)){
            System.out.println("小贾发货成功");
        }else {
            System.out.println("小贾抢购失败");
        }

        if (date4.after(date1) && date4.before(date2)){
            System.out.println("小皮发货成功");
        }else {
            System.out.println("小皮抢购失败");
        }
    }
}

注意点:阿里开发手册的规范要遵循

image-20220726150421553

3 Calendar类

  • Calendar代表了系统此刻日期对应的日历对象。
  • Calendar是一个抽象类,不能直接创建对象。

image-20220726145834767

案例:Calendar类的常用方法使用。

package com.liu.day04.simpleDateFormat;

import java.util.Calendar;
import java.util.Date;

public class CalendarDemo {
    public static void main(String[] args) {
        //获取日历对象
        Calendar calendar = Calendar.getInstance();
        System.out.println(calendar);
        //获取日历信息
        System.out.println(calendar.get(Calendar.YEAR));
        System.out.println(calendar.get(Calendar.MONTH)+1);
        System.out.println(calendar.get(Calendar.DAY_OF_MONTH));
        //修改日历的信息
        calendar.set(Calendar.YEAR,2023);
        System.out.println(calendar.get(Calendar.YEAR));
        //64天2分后的日历
        calendar.add(Calendar.DAY_OF_MONTH,64);
        calendar.add(Calendar.MINUTE,2);
        Date time = calendar.getTime();
        System.out.println(time);
    }
}

4 新增日期API

image-20220726150934535

4.1 LocalTime/LocalDate/LocalDateTime

image-20220726151009975

  • LocalDate常用方法:
package com.liu.day04.dateDemo;
import java.time.LocalDate;
import java.time.Month;

public class Demo01LocalDate {
    public static void main(String[] args) {
        // 1、获取本地日期对象。
        LocalDate nowDate = LocalDate.now();
        System.out.println("今天的日期:" + nowDate);//今天的日期:
        int year = nowDate.getYear();
        System.out.println("year:" + year);
        int month = nowDate.getMonthValue();
        System.out.println("month:" + month);
        int day = nowDate.getDayOfMonth();
        System.out.println("day:" + day);
        //当年的第几天
        int dayOfYear = nowDate.getDayOfYear();
        System.out.println("dayOfYear:" + dayOfYear);
        //星期
        System.out.println(nowDate.getDayOfWeek());
        System.out.println(nowDate.getDayOfWeek().getValue());
        //月份
        System.out.println(nowDate.getMonth());//AUGUST
        System.out.println(nowDate.getMonth().getValue());//8
        LocalDate bt = LocalDate.of(1991, 11, 11);
        System.out.println(bt);//直接传入对应的年月日
        System.out.println(LocalDate.of(1991, Month.NOVEMBER, 11));//相对上面只是把月换成了枚举
    }
}
  • LocalTime常用方法:
package com.liu.day04.dateDemo;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;

public class Demo02LocalTime {
    public static void main(String[] args) {
        // 1、获取本地时间对象。
        LocalTime nowTime = LocalTime.now();
        System.out.println("今天的时间:" + nowTime);//今天的时间:
        int hour = nowTime.getHour();//时
        System.out.println("hour:" + hour);//hour:
        int minute = nowTime.getMinute();//分
        System.out.println("minute:" + minute);//minute:
        int second = nowTime.getSecond();//秒
        System.out.println("second:" + second);//second:
        int nano = nowTime.getNano();//纳秒
        System.out.println("nano:" + nano);//nano:
        System.out.println(LocalTime.of(8, 20));//时分
        System.out.println(LocalTime.of(8, 20, 30));//时分秒
        System.out.println(LocalTime.of(8, 20, 30, 150));//时分秒纳秒
        LocalTime mTime = LocalTime.of(8, 20, 30, 150);
        System.out.println(LocalDateTime.of(1991, 11, 11, 8, 20));
        System.out.println(LocalDateTime.of(1991, Month.NOVEMBER, 11, 8, 20));
        System.out.println(LocalDateTime.of(1991, 11, 11, 8, 20, 30));
        System.out.println(LocalDateTime.of(1991, Month.NOVEMBER, 11, 8, 20, 30));
        System.out.println(LocalDateTime.of(1991, 11, 11, 8, 20, 30, 150));
        System.out.println(LocalDateTime.of(1991, Month.NOVEMBER, 11, 8, 20, 30, 150));
    }
}
  • LocalDateTime常用方法:

image-20220726152154473

package com.liu.day04.dateDemo;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

public class Demo03LocalDateTime {
    public static void main(String[] args) {
        // 日期 时间
        LocalDateTime nowDateTime = LocalDateTime.now();
        System.out.println("今天是:" + nowDateTime);//今天是:
        System.out.println(nowDateTime.getYear());//年
        System.out.println(nowDateTime.getMonthValue());//月
        System.out.println(nowDateTime.getDayOfMonth());//日
        System.out.println(nowDateTime.getHour());//时
        System.out.println(nowDateTime.getMinute());//分
        System.out.println(nowDateTime.getSecond());//秒
        System.out.println(nowDateTime.getNano());//纳秒
        //日:当年的第几天
        System.out.println("dayOfYear:" + nowDateTime.getDayOfYear());//dayOfYear:249
        //星期
        System.out.println(nowDateTime.getDayOfWeek());//THURSDAY
        System.out.println(nowDateTime.getDayOfWeek().getValue());//4
        //月份
        System.out.println(nowDateTime.getMonth());//SEPTEMBER
        System.out.println(nowDateTime.getMonth().getValue());//9
        LocalDate ld = nowDateTime.toLocalDate();
        System.out.println(ld);
        LocalTime lt = nowDateTime.toLocalTime();
        System.out.println(lt.getHour());
        System.out.println(lt.getMinute());
        System.out.println(lt.getSecond());
    }
}

4.2 Instant

image-20220726152431817

package com.liu.day04.dateDemo;

import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Date;

public class Demo05Instant {
    public static void main(String[] args) {
        // 1、得到一个Instant时间戳对象
        Instant instant = Instant.now();
        System.out.println(instant);

        // 2、系统此刻的时间戳怎么办?
        Instant instant1 = Instant.now();
        System.out.println(instant1.atZone(ZoneId.systemDefault()));

        // 3、如何去返回Date对象
        Date date = Date.from(instant);
        System.out.println(date);

        Instant i2 = date.toInstant();
        System.out.println(i2);
    }
}

4.3 DateTimeFormatter

image-20220726152924911

package com.liu.day04.dateDemo;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class Demo06DateTimeFormat {
    public static void main(String[] args) {
        // 本地此刻  日期时间 对象
        LocalDateTime ldt = LocalDateTime.now();
        System.out.println(ldt);
        // 解析/格式化器
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss EEE a");
        // 正向格式化
        System.out.println(dtf.format(ldt));
        // 逆向格式化
        System.out.println(ldt.format(dtf));
        // 解析字符串时间
        DateTimeFormatter dtf1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        // 解析当前字符串时间成为本地日期时间对象
        LocalDateTime ldt1 = LocalDateTime.parse("2019-11-11 11:11:11" ,  dtf1);
        System.out.println(ldt1);
        System.out.println(ldt1.getDayOfYear());
    }
}

4.4 Period

image-20220726153214800

package com.liu.day04.dateDemo;

import java.time.LocalDate;
import java.time.Period;

public class Demo07Period {
    public static void main(String[] args) {
        // 当前本地的年月日
        LocalDate today = LocalDate.now();
        System.out.println(today);//

        // 生日的的年月日
        LocalDate birthDate = LocalDate.of(1998, 10, 13);
        System.out.println(birthDate);
        Period period = Period.between(birthDate, today);//第二个参数减第一个参数
        System.out.println(period.getYears());
        System.out.println(period.getMonths());
        System.out.println(period.getDays());
    }
}

4.5 Duration

image-20220726153615990

package com.liu.day04.dateDemo;

import java.time.Duration;
import java.time.LocalDateTime;

public class Demo08Duration {
    public static void main(String[] args) {
        // 本地日期时间对象。
        LocalDateTime today = LocalDateTime.now();
        System.out.println(today);
        // 出生的日期时间对象
        LocalDateTime birthDate = LocalDateTime.of(2022,7,23,01,00,00);
        System.out.println(birthDate);
        Duration duration = Duration.between(today,birthDate);//第二个参数减第一个参数
        System.out.println(duration.toDays());//两个时间差的天数
        System.out.println(duration.toHours());//两个时间差的小时数
        System.out.println(duration.toMinutes());//两个时间差的分钟数
        System.out.println(duration.toMillis());//两个时间差的毫秒数
        System.out.println(duration.toNanos());//两个时间差的纳秒数
    }
}

4.6 ChronoUnit

image-20220726153822904

package com.liu.day04.dateDemo;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

public class Demo09ChronoUnit {
    public static void main(String[] args) {
        // 本地日期时间对象:此刻的
        LocalDateTime today = LocalDateTime.now();
        System.out.println(today);

        // 生日时间
        LocalDateTime birthDate = LocalDateTime.of(1990,10,1,
                10,50,59);
        System.out.println(birthDate);

        System.out.println("相差的年数:" + ChronoUnit.YEARS.between(birthDate, today));
        System.out.println("相差的月数:" + ChronoUnit.MONTHS.between(birthDate, today));
        System.out.println("相差的周数:" + ChronoUnit.WEEKS.between(birthDate, today));
        System.out.println("相差的天数:" + ChronoUnit.DAYS.between(birthDate, today));
        System.out.println("相差的时数:" + ChronoUnit.HOURS.between(birthDate, today));
        System.out.println("相差的分数:" + ChronoUnit.MINUTES.between(birthDate, today));
        System.out.println("相差的秒数:" + ChronoUnit.SECONDS.between(birthDate, today));
        System.out.println("相差的毫秒数:" + ChronoUnit.MILLIS.between(birthDate, today));
        System.out.println("相差的微秒数:" + ChronoUnit.MICROS.between(birthDate, today));
        System.out.println("相差的纳秒数:" + ChronoUnit.NANOS.between(birthDate, today));
        System.out.println("相差的半天数:" + ChronoUnit.HALF_DAYS.between(birthDate, today));
        System.out.println("相差的十年数:" + ChronoUnit.DECADES.between(birthDate, today));
        System.out.println("相差的世纪(百年)数:" + ChronoUnit.CENTURIES.between(birthDate, today));
        System.out.println("相差的千年数:" + ChronoUnit.MILLENNIA.between(birthDate, today));
        System.out.println("相差的纪元数:" + ChronoUnit.ERAS.between(birthDate, today));
    }
}

5 包装类

image-20220726154147476

包装类的功能:

image-20220726154215548

package com.liu.day04.packingClass;

public class PackingDemo {
    public static void main(String[] args) {
        int a = 10;//基本类型
        Integer a1 = 11;//引用类型
        System.out.println(a1);
        Integer a2 = 10;//自动装箱
        System.out.println(a2);
        int a3 = a2;
        System.out.println(a3);//自动拆箱

        //包装类最有用的:将字符串转化成数字类型
        int i = Integer.parseInt("12");
        double v = Double.parseDouble("12.31");
        System.out.println(i+20);
        System.out.println(v+0.11);

        //可以不使用上述方法,可以直接只使用valueOf方法
        int i1 = Integer.valueOf("12");
        double v1 = Double.valueOf("12.31");
        System.out.println(i1+20);
        System.out.println(v1+0.11);
    }
}

6 正则表达式

正则表达式可以用一些规定的字符来制定规则,并用来校验数据格式的合法性。

image-20220726155924858

案例一:校验qq号码的正确性。

package com.liu.day04.regex;

public class RegexDemo {
    public static void main(String[] args) {
        //需求:校验qq号码 必须全部数字6 -20位
        System.out.println(checkQQ("773395"));
        System.out.println(checkQQ("773395726111"));

        System.out.println(checkQQ2("773395"));
        System.out.println(checkQQ2("7733"));

    }
    //不使用正则表达式方法
    public static boolean checkQQ(String qq){
        if (qq == null || qq.length()<6 || qq.length()>20){
            return false;
        }
        //判断qq中是否全是数字
        for (int i = 0; i < qq.length(); i++) {
            char c = qq.charAt(i);
            if (c<'0' || c>'9'){
                return false;
            }
        }
        return true;
    }
    //使用正则表达式
    public static boolean checkQQ2(String qq){
        return qq!= null && qq.matches("\\d{6,20}");
    }
}

案例二:正则表达式一些基本规定的使用

package com.liu.day04.regex;


public class RegexDemo02 {
    public static void main(String[] args) {
        //public boolean matches(String regex):判断是否与正则表达式匹配,匹配返回true
        System.out.println("a".matches("[abc]")); // true
        System.out.println("z".matches("[abc]")); // false
        // 不能出现a  b  c ^表示不存在
        System.out.println("a".matches("[^abc]")); // false
        System.out.println("z".matches("[^abc]")); // true

        System.out.println("a".matches("\\d")); // false
        System.out.println("3".matches("\\d")); // true
        //默认匹配一个字符
        System.out.println("111".matches("\\d")); // false
        System.out.println("z".matches("\\w")); // true
        System.out.println("2".matches("\\w")); // true
        //默认匹配一个字符
        System.out.println("30".matches("\\w")); // false
        //\w判断是否是一个数字
        System.out.println("你".matches("\\w")); //false
        //\W判断一个非单词字符
        System.out.println("你".matches("\\W")); // true
        System.out.println("---------------------------------");
        //  以上正则匹配只能校验单个字符。

        // 校验密码
        // 必须是数字 字母 下划线 至少 6位
        System.out.println("23456sqxS".matches("\\w{6,}"));
        System.out.println("a557".matches("\\w{6,}"));

        // 验证码 必须是数字和字符  必须是4位
        System.out.println("135s".matches("[a-zA-Z0-9]{4}"));
        System.out.println("553_".matches("[a-zA-Z0-9]{4}"));
        System.out.println("886s".matches("[\\w&&[^_]]{4}"));
        System.out.println("996_".matches("[\\w&&[^_]]{4}"));

    }
}

案例三:

  • 请编写程序模拟用户输入手机号码、验证格式正确,并给出提示,直到格式输入正确为止。
  • 请编写程序模拟用户输入邮箱号码、验证格式正确,并给出提示,直到格式输入正确为止。
  • 请编写程序模拟用户输入电话号码、验证格式正确,并给出提示,直到格式输入正确为止。
package com.liu.day04.regex;

import java.util.Arrays;
import java.util.Scanner;

public class RegexTest3 {
    public static void main(String[] args) {
        checkPhone();
        checkEmail();
        checkTel();
    }

    public static void checkTel(){
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("请您输入您的电话号码:");
            String tel = sc.next();
            if(tel.matches("0\\d{2,6}-?\\d{5,20}")){
                System.out.println("格式正确,注册完成!");
                break;
            }else {
                System.out.println("格式有误!");
            }
        }
    }

    public static void checkEmail(){
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("请您输入您的注册邮箱:");
            String email = sc.next();
            if(email.matches("\\w{1,30}@[a-zA-Z0-9]{2,20}(\\.[a-zA-Z0-9]{2,20}){1,2}")){
                System.out.println("邮箱格式正确,注册完成!");
                break;
            }else {
                System.out.println("格式有误!");
            }
        }
    }

    public static void checkPhone(){
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("请您输入您的注册手机号码:");
            String phone = sc.next();
            // 判断手机号码的格式是否正确
            if(phone.matches("1[3-9]\\d{9}")){
                System.out.println("手机号码格式正确,注册完成!");
                break;
            }else {
                System.out.println("格式有误!");
            }
        }
    }
}
/**
运行结果:
请您输入您的注册手机号码:
13461460555
手机号码格式正确,注册完成!
请您输入您的注册邮箱:
773395777@qq.com
邮箱格式正确,注册完成!
请您输入您的电话号码:
0120-41123333
格式正确,注册完成!
*/

案例四:

image-20220726195619437

package com.liu.day04.regex;
public class RegexDemo04 {
    public static void main(String[] args) {
        String names = "你好jdlajdl努力少年jdoisajdo相信自己ldjsaoijdo";

        String[] arrs = names.split("\\w+");
        for (int i = 0; i < arrs.length; i++) {
            System.out.println(arrs[i]);
        }
        String names2 = names.replaceAll("\\w+", "  ");
        System.out.println(names2);
    }
}

/*
运行结果:
你好
努力少年
相信自己
你好  努力少年  相信自己  
**/

案例五:

正则表达式支持对信息的爬取(了解即可)

package com.liu.day04.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;


public class RegexDemo05 {
    public static void main(String[] args) {
        String rs = "来黑马程序学习Java,电话020-43422424,或者联系邮箱" +
                "itcast@itcast.cn,电话18762832633,0203232323" +
                "邮箱bozai@itcast.cn,400-100-3233 ,4001003232";

        // 需求:从上面的内容中爬取出 电话号码和邮箱。
        // 1、定义爬取规则,字符串形式
        String regex = "(\\w{1,30}@[a-zA-Z0-9]{2,20}(\\.[a-zA-Z0-9]{2,20}){1,2})|(1[3-9]\\d{9})" + "|(0\\d{2,6}-?\\d{5,20})|(400-?\\d{3,9}-?\\d{3,9})";

        // 2、把这个爬取规则编译成匹配对象。
        Pattern pattern = Pattern.compile(regex);

        // 3、得到一个内容匹配器对象
        Matcher matcher = pattern.matcher(rs);

        // 4、开始寻找对象
        while (matcher.find()) {
            String rs1 = matcher.group();
            System.out.println(rs1);
        }

    }
}

7 Arrays类

image-20220726200522794

package com.liu.day04.ArrayDemo;

import java.util.Arrays;

public class ArraysDemo1 {
    public static void main(String[] args) {
        // 目标:学会使用Arrays类的常用API ,并理解其原理
        int[] arr = {10, 21, 50, 23, 24, 100};
        System.out.println(arr);

        // 1、返回数组内容的 toString(数组)
        System.out.println(Arrays.toString(arr));

        // 2、排序的API(默认自动对数组元素进行升序排序)
        Arrays.sort(arr);
        System.out.println(Arrays.toString(arr));

        // 3、二分搜索技术(前提数组必须排好序才支持,否则出bug)
        int index = Arrays.binarySearch(arr, 55);
        System.out.println(index);

        // 返回不存在元素的规律: - (应该插入的位置索引 + 1)
        int index2 = Arrays.binarySearch(arr, 22);
        System.out.println(index2);
        
        // 注意:数组如果么有排好序,可能会找不到存在的元素,从而出现bug!!
        int[] arr2 = {12, 36, 34, 25 , 13,  24,  234, 100};
        System.out.println(Arrays.binarySearch(arr2 , 36));
    }

}

image-20220726200941494

package com.liu.day04.ArrayDemo;

import java.util.Arrays;
import java.util.Comparator;

public class ArraysDemo2 {
    public static void main(String[] args) {
        // 目标:自定义数组的排序规则:Comparator比较器对象。
        // 1、Arrays的sort方法对于有值特性的数组是默认升序排序
        int[] ages = {34, 12, 42, 23};
        Arrays.sort(ages);
        System.out.println(Arrays.toString(ages));

        // 2、需求:降序排序!(自定义比较器对象,只能支持引用类型的排序!!)
        Integer[] ages1 = {34, 12, 42, 23};
        /**
           参数一:被排序的数组 必须是引用类型的元素
           参数二:匿名内部类对象,代表了一个比较器对象。
         */
        Arrays.sort(ages1, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                // 指定比较规则。
//                if(o1 > o2){
//                    return 1;
//                }else if(o1 < o2){
//                    return -1;
//                }
//                return 0;
                // return o1 - o2; // 默认升序
                return o2 - o1; //  降序
            }
        });
        System.out.println(Arrays.toString(ages1));

        Student[] students = new Student[3];
        students[0] = new Student("吴磊",23 , 175.5);
        students[1] = new Student("谢鑫",18 , 185.5);
        students[2] = new Student("王亮",20 , 195.5);
        System.out.println(Arrays.toString(students));

        // Arrays.sort(students);  // 没有重写比较器直接运行奔溃
        Arrays.sort(students, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                // 自己指定比较规则
                // return o1.getAge() - o2.getAge(); // 按照年龄升序排序!
                // return o2.getAge() - o1.getAge(); // 按照年龄降序排序!!
                // return Double.compare(o1.getHeight(), o2.getHeight()); //比较浮点型可以这样写 升序
                return Double.compare(o2.getHeight(), o1.getHeight()); //比较浮点型可以这样写 降序
            }
        });
        System.out.println(Arrays.toString(students));
    }
}

8 选择排序、二分查找算法

8.1 选择排序

  • 每轮选择当前位置,开始找出后面的较小值与该位置交换。
  • 确定总共需要选择几轮:数组的长度-1。
  • 控制每轮从以前位置为基准,与后面元素选择几次。

选择排序动态图

选择排序代码实现:

package com.liu.day04.est;

import java.util.Arrays;


public class Test1 {
    public static void main(String[] args) {
        // 1、定义数组
        int[] arr = {5, 1, 3, 2};
        //           0  1  2  3

        // 2、定义一个循环控制选择几轮: arr.length - 1
        for (int i = 0; i < arr.length - 1; i++) {
          
         // 3、定义内部循环,控制选择几次
        for (int j = i + 1; j < arr.length; j++) {
                // 当前位:arr[i]
                // 如果有比当前位数据更小的,则交换
                if(arr[i] > arr[j]) {
                    int temp = arr[i];
                    arr[i] = arr[j];
                    arr[j] = temp;
                }
            }
        }
        System.out.println(Arrays.toString(arr));
    }
}

8.2 二分查找

  • 定义变量记录左边和右边位置。
  • 使用while循环控制查询( 条件是左边位置<= 右边位置)。
  • 循环内部获取中间元素索引。
  • 判断当前要找的元素如果大于中间元素,左边位置=中间索引+1。
  • 判断当前要找的元素如果小于中间元素,右边位置=中间索引-1。
  • 判断当前要找的元素如果等于中间元素,返回当前中间元素索引。

二分查找代码实现:

package com.liu.day04.est;

public class Test2 {
    public static void main(String[] args) {
        // 1、定义数组
        int[] arr = {10, 14, 16, 25, 28, 30, 35, 88, 100};
        System.out.println(binarySearch(arr , 35));
        System.out.println(binarySearch(arr , 350));
    }

    public static int binarySearch(int[] arr, int data){
        // 1、定义左边位置  和 右边位置
        int left = 0;
        int right = arr.length - 1;

        // 2、开始循环,折半查询。
        while (left <= right){
            // 取中间索引
            int middleIndex = (left + right) / 2;
            // 3、判断当前中间位置的元素和要找的元素的大小情况
            if(data > arr[middleIndex]) {
                // 往右边找,左位置更新为 = 中间索引+1
                left = middleIndex + 1;
            }else if(data < arr[middleIndex]) {
                // 往左边找,右边位置 = 中间索引 - 1
                right = middleIndex - 1;
            }else {
                return middleIndex;
            }
        }
        return -1; // 没有找到该元素返回-1
    }

}

9 Lambda表达式

  • Lambda表达式是JDK 8开始后的一种新语法形式。
  • 作用:简化匿名内部类的代码写法。
  • 格式:

    (匿名内部类被重写方法的形参列表) -> {

        被重写方法的方法体代码。
    

    }

    注: ->是语法形式,无实际含义

  • 注意: Lambda表达式只能简化函数式接口的匿名内部类的写法形式。
  • 什么是函数式接口?

    1.首先必须是接口、其次接口中有且仅有一个抽象方法的形式。

    2.通常我们会在接口上加上一个@FunctionalInterface注解,标记该接口必须是满足函数式接口。

  • Lambda表达式的使用:
package com.liu.day04.LambdaDemo;

public class LambdaDemo1 {
    public static void main(String[] args) {
        //Lambda的标准格式简化匿名内部类的代码形式
        Dog a = new Dog() {
            @Override
            public void run() {
                System.out.println("狗跑的贼快~~~~~");
            }
        };
        a.run();
    }
}

abstract class Dog{
    public abstract void run();
}

Lambda表达式的省略写法(进一步在Lambda表达式的基础上继续简化)

  • 参数类型可以省略不写。
  • 如果只有一个参数,参数类型可以省略,同时()也可以省略。
  • 如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写,同时要省略分号!
  • 如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写。此时,如果这行代码是return语句,必须省略return不写,同时也必须省略";"不写。
package com.liu.day04.LambdaDemo;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
import java.util.Comparator;

public class LambdaDemo3 {
    public static void main(String[] args) {
        JFrame win = new JFrame("登录界面");
        JButton btn = new JButton("按钮");
        //最简Lambda表达式
        btn.addActionListener( e -> System.out.println("点一下") );
        win.add(btn);
        win.setSize(400, 300);
        win.setVisible(true);
    }
}

学习记录5


1 集合

1.1 集合与数组的对比

相同点:集合和数组都是容器。

不同点:

对于数组来说:

  • 数组定义完成并启动后,类型确定长度固定
  • 在进行增删数据操作的时候,数组是不太合适的,增删数据都需要放弃原有数组或者移位。
  • 当业务数据的个数是固定的,且都是同一批数据类型的时候,可以采取定义数组存储。

对于集合来说:

  • 集合的大小不固定,启动后可以动态变化类型也可以选择不固定。集合更像气球。
  • 集合非常适合做元素的增删操作。
  • 集合中只能存储引用类型数据,如果要存储基本类型数据可以选用包装类。
  • 当数据的个数不确定,需要进行增删元素的时候,一般使用集合来存储。

1.2 集合类的体系结构

集合主要分为两类:

  1. Collection单列集合,每个元素(数据)只包含一个值。
  2. Map双列集合,每个元素包含两个值(键值对)。

2 Collection单列集合

2.1 Collection集合体系

image-20220727181924132

2.2 Collection集合特点

  • List系列集合:添加的元素是有序、可重复、有索引。
    ArrayList,LinekdList:有序、可重复、有索引
  • Set系列集合:添加的元素是无序、不重复、无索引。
    HashSet:无序、不重复、无索引;LinkedHashSet: 有序、不重复、无索引
    TreeSet:按照大小默认升序排序、不重复、无索引
  • 集合都是支持泛型的,可以在编译阶段约束集合只能操作某种数据类型。
package com.liu.day05.Collection;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;

public class CollectionDemo01 {
    public static void main(String[] args) {
        //典型的多态写法 使用左边父类的方法。有序、可重复、有索引
         Collection list = new ArrayList<>();
         list.add("zhenxi");
         list.add(23);
         list.add("珍惜大学");
         list.add("计算机与控制工程学院");
         list.add(true);
         list.add(177.1);
        System.out.println(list);

        //无序、不重复、无索引
        Collection set = new HashSet<>();
        set.add("zhenxi");
        set.add("zhenxi");
        set.add(123);
        set.add(false);
        System.out.println(set);

        //对泛型的支持
        Collection<Integer> list1 = new ArrayList<>();
        list1.add(11);
        list1.add(22);
        //报错:不兼容的类型: java.lang.String无法转换为java.lang.Integer
        //list1.add("dd");
        System.out.println(list1);
    }
}

2.3 Collection集合常用API

image-20220727185742840

package com.liu.day05.Collection;

import java.util.ArrayList;
import java.util.Collection;

public class CollectionDemo02 {
    public static void main(String[] args) {
        //Collection常用API
        Collection<String> collection = new ArrayList<>();
        //add方法
        collection.add("zhenxi");
        collection.add("珍惜大学");
        System.out.println(collection);
        //remove方法
        collection.remove("zhenxi");
        System.out.println(collection);
        //contains包含方法
        System.out.println(collection.contains("珍惜大学"));
        //isEmpty方法
        System.out.println(collection.isEmpty());
        //size方法
        System.out.println(collection.size());
        //toArray方法
        Object[] array = collection.toArray();
        System.out.println(array);
        for (Object o : array) {
            System.out.println(o);
        }
        //clear方法
        collection.clear();
        System.out.println(collection.isEmpty());
    }
}

2.4 Collection集合遍历方式

遍历方式一:迭代器方式

  • 遍历就是一个一个的把容器中的元素访问一遍。
  • 迭代器在Java中的代表是Iterator,迭代器是集合的专用遍历方式。

image-20220727190951298

package com.liu.day05.Collection;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class CollectionDemo3 {
    public static void main(String[] args) {
        //遍历方式一:迭代器遍历
        Collection<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        System.out.println(list);
        //获取迭代器对象
        Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()){
            //判断是否有元素存在,存在继续循环
            Integer next = iterator.next();
            System.out.println(next); // 自动拆箱
        }
    }
}

遍历方式二:增强for循环

  • 增强for循环:既可以遍历集合也可以遍历数组。
  • 它是JDK5之后出现的,其内部原理是一个lterator迭代器 ,遍历集合相当于是迭代器的简化写法。
package com.liu.day05.Collection;

import java.util.ArrayList;
import java.util.Collection;

public class CollectionDemo04 {
    public static void main(String[] args) {
        //遍历方式二:For each遍历
        Collection<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        System.out.println(list);
        //for each
        for (Integer integer : list) {
            System.out.println(integer);
        }       
    }
}

遍历方式三:Lambda表达式

image-20220727193438547

package com.liu.day05.Collection;

import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;

public class CollectionDemo04 {
    public static void main(String[] args) {
        //遍历方式三:lambda遍历
        Collection<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        System.out.println(list);
        //lambda表达式
        list.forEach(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println(integer);
            }
        });
        //简化
        list.forEach(integer -> System.out.println(integer));
    }
}

2.5 Collection集合存储自定义类型

自定义学生类:

package com.liu.day05.Collection;

public class Student {
    private String name;
    private int id;

    public Student() {
    }

    public Student(String name, int id) {
        this.name = name;
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", id=" + id +
                '}';
    }
}

集合存储并遍历:

package com.liu.day05.Collection;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class CollectionDemo05 {
    public static void main(String[] args) {
        //存储自定义类型
        Collection<Student> list = new ArrayList<>();
        list.add(new Student("zhenxi",2022956775));
        list.add(new Student("海珠",2022956776));
        list.add(new Student("贾哥",2022956777));
        System.out.println(list);
        //对自定义类型进行遍历
        for (Student student : list) {
            System.out.println(student);
        }
        Iterator<Student> iterator = list.iterator();
        while (iterator.hasNext()){
            Student student = iterator.next();
            System.out.println(student);
        }
        list.forEach(student -> System.out.println(student));
    }
}

3 List集合

3.1 List集合特点

  • ArrayList、LinekdList:有序,可重复,有索引。
  • 有序:存储和取出的元素顺序一致。
  • 有索引:可以通过索引操作元素。
  • 可重复:存储的元素可以重复。

3.2 List底层原理

  • ArrayList底层是基于数组实现的:根据索引定位元素快,增删相对慢。
  • LinkedList底层基于双链表实现的:查询元素慢,增删首尾元素是非常快的。

3.3 List集合的API

1、List集合继承了Collection集合的全部功能,"同时因为List系列集合有索引"。

2、因为List集合多了索引,所以多了很多按照索引操作元素的功能。

3、ArrayList实现类集合底层基于数组存储数据的,查询快,增删慢。

  • public void add(int index, E element): 将指定的元素,添加到该集合中的指定位置上。
  • public E get(int index):返回集合中指定位置的元素。
  • public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。
  • public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回更新前的元素值。
package com.liu.day05.Collection;

import java.util.ArrayList;
import java.util.List;

public class ListDemo01 {
    public static void main(String[] args) {
        // 1.创建一个ArrayList集合对象:
        // List:有序,可重复,有索引的。
        List<String> list = new ArrayList<>(); // 一行经典代码!
        list.add("zhenxi");
        list.add("珍惜大学");
        list.add("计算机与控制工程学院");
        list.add("2022");
        list.add("电子信息");

        // 2.在某个索引位置插入元素。
        list.add(2, "2022967775");
        System.out.println(list);

        // 3.根据索引删除元素,返回被删除元素
        System.out.println(list.remove(1));
        System.out.println(list);

        // 4.根据索引获取元素:public E get(int index):返回集合中指定位置的元素。
        System.out.println(list.get(1));

        // 5.修改索引位置处的元素: public E set(int index, E element)
        System.out.println(list.set(0, "努力!"));
        System.out.println(list);
    }
}
/**
运行结果:
[zhenxi, 珍惜大学, 2022967775, 计算机与控制工程学院, 2022, 电子信息]
珍惜大学
[zhenxi, 2022967775, 计算机与控制工程学院, 2022, 电子信息]
2022967775
zhenxi
[努力!, 2022967775, 计算机与控制工程学院, 2022, 电子信息]

Process finished with exit code 0
*/

3.4 List集合遍历方式

(1)for循环。(独有的,因为List有索引)。
(2)迭代器。
(3)foreach。
(4)Lambda表达式

package com.liu.day05.Collection;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class ListDemo02 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("zhenxi");
        list.add("珍惜大学");
        list.add("计算机与控制工程学院");
        list.add("2022");
        list.add("电子信息");
        //for循环
        for (int i = 0; i < list.size(); i++) {
            String s = list.get(i);
            System.out.println(s);
        }
        //迭代器方式
        Iterator<String> it = list.iterator();
        while (it.hasNext()){
            String ele = it.next();
            System.out.println(ele);
        }
        //for each
        for (String ele : list) {
            System.out.println(ele);
        }
        //Lambda表达式
        list.forEach(s -> {
            System.out.println(s);
        });

    }
}

3.5 LinkedList集合特有功能

  • 底层数据结构是双链表,查询慢,首尾操作的速度是极快的,所以多了很多首尾操作的特有API。

image-20220727210301320

案例:使用LinkedList模仿栈和队列

package com.liu.day05.Collection;

import java.util.LinkedList;
import java.util.List;

public class ListDemo03 {
    public static void main(String[] args) {
        // LinkedList可以完成队列结构,和栈结构 (双链表)
        // 1、做一个队列:
        LinkedList<String> queue = new LinkedList<>();
        // 入队
        queue.addLast("1号");
        queue.addLast("2号");
        queue.addLast("3号");
        System.out.println(queue);
        // 出队
        System.out.println(queue.getFirst());
        System.out.println(queue.removeFirst());
        System.out.println(queue.removeFirst());
        System.out.println(queue);

        // 2、做一个栈
        LinkedList<String> stack = new LinkedList<>();
        // 入栈 压栈 (push)
        stack.push("第1颗子弹");
        stack.push("第2颗子弹");
        stack.push("第3颗子弹");
        stack.push("第4颗子弹");
        System.out.println(stack);

        // 出栈  弹栈 pop
        System.out.println(stack.pop());
        System.out.println(stack.pop());
        System.out.println(stack.pop());
        System.out.println(stack);
    }
}

3.6 List集合修改异常问题

  • 迭代器遍历集合且直接用集合删除元素的时候可能出现。
  • 增强for循环遍历集合且直接用集合删除元素的时候可能出现。
package com.liu.day05.Collection;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class ListDemo04 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("zhenxi");
        list.add("珍惜大学");
        list.add("计算机与控制工程学院");
        list.add("2022");
        list.add("电子信息");

        //迭代器遍历集合且直接用集合删除元素的时候可能出现。
        Iterator<String> it = list.iterator();
        while (it.hasNext()){
            String ele = it.next();
            if ("zhenxi".equals(ele)){
                //报错ConcurrentModificationException 并发修改异常
                //list.remove(ele);
                it.remove(); //要用迭代器的删除方法
            }
        }
        System.out.println(list);
        //for each
        for (String ele : list) {
            //报错ConcurrentModificationException 并发修改异常
            //list.remove(ele); 
            //该问题无法解决,因为foreach调用的是迭代器,但是无法直接调用迭代器的remove方法。
            System.out.println(ele);
        }
    }
}

4 泛型

4.1 泛型概述与特点

  • 泛型:是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。
  • 泛型的格式: <数据类型>;注意:泛型只能支持引用数据类型。
  • 集合体系的全部接口和实现类都是支持泛型的使用的。
  • 统一数据类型。
  • 把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为编译阶段类型就能确定下来。

4.2 自定义泛型类

  • 定义类时同时定义了泛型的类就是泛型类。
  • 泛型类的格式:修饰符class类名<泛型变量>
  • 此处泛型变量T可以随便写为任意标识,常见的如E、T、K、V等。
  • 作用:编译阶段可以指定数据类型,类似于集合的作用。

    需求:模拟ArrayList定义一个MyArrayList ,关注泛型设计

package com.liu.day05.genericity;

public class Test {
    public static void main(String[] args) {
        MyArrayList<String> list = new MyArrayList<>();
        list.add("zhenxi");
        list.add("珍惜大学");
        list.add("计算机与控制工程学院");
        list.add("2022");
        list.add("电子信息");
        System.out.println(list);

        MyArrayList<Integer> list2 = new MyArrayList<>();
        list2.add(1);
        list2.add(2);
        list2.add(3);
        list2.remove(4);
        System.out.println(list2);
    }
}
package com.liu.day05.genericity;

import java.util.ArrayList;

public class MyArrayList<E> {
    private ArrayList lists = new ArrayList();

    public void add(E e){
        lists.add(e);
    }

    public void remove(E e){
        lists.remove(e);
    }

    @Override
    public String toString() {
        return lists.toString();
    }
}

4.3 自定义泛型方法

  • 定义方法时同时定义了泛型的方法就是泛型方法。
  • 泛型方法的格式:修饰符 <泛型变量> 方法返回值 方法名称(形参列表){}
  • 作用:方法中可以使用泛型接收一切实际类型的参数,方法更具备通用性。

案例:给你任何一个类型的数组,都能返回它的内容。也就是实现Arrays.toString(数组)的功能!

package com.liu.day05.genericity;

public class GenericDemo {
    public static void main(String[] args) {
        String[] names = {"zhenxi", "成龙", "帅博"};
        printArray(names);
        Integer[] ages = {23, 22, 21};
        printArray(ages);
        Integer[] ages2 = getArr(ages);
        String[]  names2 = getArr(names);
    }

    public static <T> T[] getArr(T[] arr){
        return arr;
    }

    public static <T> void printArray(T[] arr){
        if(arr != null){
            StringBuilder sb = new StringBuilder("[");
            for (int i = 0; i < arr.length; i++) {
                sb.append(arr[i]).append(i == arr.length - 1 ? "" : ", ");
            }
            sb.append("]");
            System.out.println(sb);
        }else {
            System.out.println(arr);
        }
    }
}

4.4 自定义泛型接口

  • 使用了泛型定义的接口就是泛型接口。
  • 泛型接口的格式:修饰符interface接口名称<泛型变量>{}
  • 作用:泛型接口可以让实现类选择当前功能需要操作的数据类型

案例:教务系统,提供一个接口可约束一定要完成数据(学生,老师)的增删改查操作

package com.liu.day05.genericity_Impl;

public interface Data<E> {
    void add(E e);
    void delete(int id);
    void update(E e);
    E queryById(int id);
}
package com.liu.day05.genericity_Impl;

public class StudentData implements Data<Student>{
    @Override
    public void add(Student student) {
    }

    @Override
    public void delete(int id) {
    }

    @Override
    public void update(Student student) {
    }

    @Override
    public Student queryById(int id) {
        return null;
    }
}
package com.liu.day05.genericity_Impl;

public class TeacherData implements Data<Teacher>{
    @Override
    public void add(Teacher teacher) {

    }

    @Override
    public void delete(int id) {

    }

    @Override
    public void update(Teacher teacher) {

    }

    @Override
    public Teacher queryById(int id) {
        return null;
    }
}

4.5 泛型通配符

  • ?可以在使用泛型的时候代表一切类型。
  • ETKV是在定义泛型的时候使用的。

案例:开发一个极品飞车的游戏,所有的汽车都能一起参与比赛。

package com.liu.day05.genericity_Impl;

import java.util.ArrayList;

public class GenericDemo {
    public static void main(String[] args) {
        ArrayList<BMW> bmws = new ArrayList<>();
        bmws.add(new BMW());
        bmws.add(new BMW());
        bmws.add(new BMW());
        go(bmws);

        ArrayList<BENZ> benzs = new ArrayList<>();
        benzs.add(new BENZ());
        benzs.add(new BENZ());
        benzs.add(new BENZ());
        go(benzs);

        ArrayList<Dog> dogs = new ArrayList<>();
        dogs.add(new Dog());
        dogs.add(new Dog());
        dogs.add(new Dog());
        // go(dogs);
    }

    /**
       所有车比赛 但该静态方法传进来的必须是继承Car的子类
     */
    public static void go(ArrayList<? extends Car> cars){
    }
}

class Dog{

}

class BENZ extends Car{
}

class BMW extends Car{
}

// 父类
class Car{
}

学习记录6


1 Set集合

  • 无序:存取顺序不一致。
  • 不重复:可以去除重复。
  • 无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素。
  • HashSet:无序、不重复、无索引。
  • LinkedHashSet:有序、不重复、无索引。
  • TreeSet:排序、不重复、无索引。
package com.liu.day06.Map;

import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;

public class SetDemo {
    public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        set.add("zhenxi");
        set.add("珍惜大学");
        set.add("计算机与控制工程学院");
        set.add("2022");
        set.add("2022");
        set.add("电子信息");
        System.out.println(set);

        Set<String> linkedHashSet = new LinkedHashSet<>();
        linkedHashSet.add("zhenxi");
        linkedHashSet.add("珍惜大学");
        linkedHashSet.add("计算机与控制工程学院");
        linkedHashSet.add("2022");
        linkedHashSet.add("2022");
        linkedHashSet.add("电子信息");
        System.out.println(linkedHashSet);

        Set<String> treeSet = new TreeSet<>();
        treeSet.add("zhenxi");
        treeSet.add("珍惜大学");
        treeSet.add("计算机与控制工程学院");
        treeSet.add("2022");
        treeSet.add("2022");
        treeSet.add("电子信息");
        System.out.println(treeSet);
    }
}
/**
运行结果:
[电子信息, 计算机与控制工程学院, 珍惜大学, zhenxi, 2022]
[zhenxi, 珍惜大学, 计算机与控制工程学院, 2022, 电子信息]
[2022, zhenxi, 电子信息, 计算机与控制工程学院, 珍惜大学]
*/

2 HashSet集合

  • HashSet集合底层采取哈希表存储的数据。
  • 哈希表是一种对于增删改查数据性能都较好的结构。

2.1 哈希值

  • 是JDK根据对象的地址,按照某种规则算出来的int类型的数值。

0bject类的API

  • public int hashCode():返回对象的哈希值。

对象的哈希值特点

  • 同一个对象多次调用hashCode()方法返回的哈希值是相同的。
  • 默认情况下,不同对象的哈希值是不同的。
package com.liu.day06.HashDemo;

import java.util.Objects;

public class Student {
    private String name;
    private int id;

    public Student() {
    }

    public Student(String name, int id) {
        this.name = name;
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", id=" + id +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return id == student.id && Objects.equals(name, student.name);
    }
    //重写的hashCode方法
    @Override
    public int hashCode() {
        return Objects.hash(name, id);
    }
}
package com.liu.day06.HashDemo;

public class HashSetDemo01 {
    public static void main(String[] args) {
        Student student = new Student();
        int hashCode = student.hashCode();
        System.out.println(hashCode);

        Student student1 = new Student("zhenxi",23);
        Student student2 = new Student("zhenxi",23);
        Student student3 = new Student("成龙",23);

        System.out.println(student1.hashCode());
        System.out.println(student2.hashCode());
        System.out.println(student3.hashCode());
        /*
        * 当在Student类中未重写hashCode方法时的运行结果:
        *   1735600054
            21685669
            2133927002
        * */
        /*
        * 当重写了hashCode方法后运行结果为:
        *   21128011
            21128011
            25392495
        * 
        * */
    }
}

2.2 底层原理

  • JDK8之前的,哈希表:底层使用数组+链表组成
  • JDK8开始后,哈希表:底层采用数组+链表+红黑树组成。

哈希表的详细流程

  • 创建一个默认长度16,默认加载因为0.75的数组,数组名table。
  • 根据元素的哈希值跟数组的长度计算出应存入的位置。
  • 判断当前位置是否为null,如果是null直接存入,如果位置不为null,表示有元素,则调用equals方法比较属性值,如果一样,则不存,如果不一样,则存入数组。
  • 当数组存满到16*0.75=12时,就自动扩容,每次扩容原先的两倍。

HashSet去重复原理

  • 创建一个默认长度16的数组,数组名table。
  • 根据元素的哈希值跟数组的长度求余计算出应存入的位置(哈希算法)。
  • 判断当前位置是否为null,如果是null直接存入。
  • 如果位置不为null,表示有元素,则调用equals方法比较。
  • 如果一样,则不存,如果不一样,则存入数组。

需求

创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合。要求:学生对象的成员变量值相同,我们就认为是同一个对象。

package com.liu.day06.HashDemo;

import java.util.HashSet;
import java.util.Set;

public class HashSetDemo02 {
    public static void main(String[] args) {
        Set<Student> set = new HashSet<>();
        set.add(new Student("zhenxi",23));
        set.add(new Student("成龙",23));
        set.add(new Student("zhenxi",23));

        //遍历
        for (Student student : set){
            System.out.println(student);
        }
        /*若是没有重写hashCode方法:运行结果为:
        * Student{name='成龙', id=23}
        Student{name='zhenxi', id=23}
        Student{name='zhenxi', id=23}*/
        /*
        * 若是重写了hashCode方法:运行结果为:
        *Student{name='zhenxi', id=23}
         Student{name='成龙', id=23}
        * */
    }
}

3 LinkedHashSet集合

  • 是HashSet的子类。
  • 有序、不重复、无索引。
  • 这里的有序指的是保证存储和取出的元素顺序一致。
  • 原理:底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。
package com.liu.day06.HashDemo;

import java.util.LinkedHashSet;
import java.util.Set;

public class LinkedHashSetDemo {
    public static void main(String[] args) {
        Set<String> set = new LinkedHashSet<>();
        set.add("zhenxi");
        set.add("珍惜大学");
        set.add("计算机与控制工程学院");
        set.add("2022");
        set.add("2022");
        set.add("电子信息");
        System.out.println(set);
        for (String s:set){
            System.out.println(s);
        }
    }
}
/**
运行结果:
[zhenxi, 珍惜大学, 计算机与控制工程学院, 2022, 电子信息]
zhenxi
珍惜大学
计算机与控制工程学院
2022
电子信息
*/

4 TreeSet集合

  • 不重复、无索引、可排序。
  • 可排序:按照元素的大小默认升序(有小到大)排序。
  • TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。
  • 对于数值类型: Integer ,Double,官方默认按照大小进行升序排序。
  • 对于字符串类型:默认按照首字符的编号升序排序。
  • 对于自定义类型如Student对象,TreeSet无法直接排序。需要制定排序规则。

    方式一:让自定义的类(如学生类)实现Comparable接口重写里面的compareTo方法来定制比较规则。

    方式二:TreeSet集合有参数构造器,可以设置Comparator接口对应的比较器对象,来定制比较规则。

package com.liu.day06.HashDemo;

import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

public class TreeSetDemo {
    public static void main(String[] args) {
        Set<Integer> set = new TreeSet<>();
        set.add(2);
        set.add(3);
        set.add(1);
        set.add(4);
        System.out.println(set);//[1, 2, 3, 4]

        Set<Double> doubleSet = new TreeSet<>();
        doubleSet.add(3.1);
        doubleSet.add(4.1);
        doubleSet.add(6.6);
        doubleSet.add(0.1);
        System.out.println(doubleSet);//[0.1, 3.1, 4.1, 6.6]

        Set<String> stringSet = new TreeSet<>();
        stringSet.add("a");
        stringSet.add("A");
        stringSet.add("/");
        stringSet.add("你");
        System.out.println(stringSet);//[/, A, a, 你]
        //第一种方式
        Set<Student> studentSet = new TreeSet<>();
        studentSet.add(new Student("zhenxi",23));
        studentSet.add(new Student("成龙",24));
        studentSet.add(new Student("zhenxi",23));
        System.out.println(studentSet);
        //第二种方式
        Set<Apple> appleSet = new TreeSet<>(new Comparator<Apple>() {
            @Override
            public int compare(Apple o1, Apple o2) {
                return o1.getWeight() - o2.getWeight();
            }
        });
        appleSet.add(new Apple("富士康",50));
        appleSet.add(new Apple("富士康",50));
        appleSet.add(new Apple("星空灰",30));
        appleSet.add(new Apple("高调白",40));
        System.out.println(appleSet);
    }   
}

/**
运行结果:
[1, 2, 3, 4]
[0.1, 3.1, 4.1, 6.6]
[/, A, a, 你]
[Student{name='zhenxi', id=23}, Student{name='成龙', id=24}]
[Apple{name='星空灰', weight=30}, Apple{name='高调白', weight=40}, Apple{name='富士康', weight=50}]
*/

Student类:

package com.liu.day06.HashDemo;

import java.util.Objects;

public class Student implements Comparable<Student>{
    private String name;
    private int id;

    public Student() {
    }

    public Student(String name, int id) {
        this.name = name;
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", id=" + id +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return id == student.id && Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, id);
    }

    @Override
    public int compareTo(Student o) {
        return this.id - o.id;
    }
}

Apple类:

package com.liu.day06.HashDemo;

import java.util.Objects;

public class Apple {
    private String name;
    private int weight;

    public Apple() {
    }

    public Apple(String name, int weight) {
        this.name = name;
        this.weight = weight;
    }

    @Override
    public String toString() {
        return "Apple{" +
                "name='" + name + '\'' +
                ", weight=" + weight +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Apple apple = (Apple) o;
        return weight == apple.weight && Objects.equals(name, apple.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, weight);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getWeight() {
        return weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }
}

5 关于Collection集合如何选用

  • 如果希望元素可以重复,又有索引,索引查询要快?

    用ArrayList集合, 基于数组的。(用的最多)

  • 如果希望元素可以重复,又有索引,增删首尾操作快?

    用LinkedList集合, 基于链表的。

  • 如果希望增删改查都快,但是元素不重复、无序、无索引。

    用HashSet集合, 基于哈希表的。

  • 如果希望增删改查都快,但是元素不重复、有序、无索引。

    用LinkedHashSet集合, 基于哈希表和双链表。、

  • 如果要对对象进行排序。

    用TreeSet集合, 基于红黑树。后续也可以用Lit集合实现排序。

6 可变参数

  • 可变参数用在形参中可以接收多个数据。
  • 可变参数的格式: 数据类型...参数名称。
  • 传输参数非常灵活,方便。可以不传输参数,可以传输1个或者多个,也可以传输一个数组。
  • 可变参数在方法内部本质上就是一个数组。
  • 一个形参列表中可变参数只能有一个。
  • 可变参数必须放在形参列表的最后面。
package com.liu.day06.HashDemo;

import java.util.Arrays;

public class MethodDemo {
    public static void main(String[] args) {
        sum();
        sum(1);
        sum(1,2);
        sum(1,2,3);

        avg(1,12);
        avg(2,12,13);
        avg(3,12,15);
        avg(4,12,19,20);
    }
    public static void sum(int...nums){
        System.out.println(nums.length);
        System.out.println(Arrays.toString(nums));
    }
    public static void avg(int sum,int...nums){
        System.out.println(nums.length);
        System.out.println(Arrays.toString(nums));
    }
}

7 Collections工具类

  • java.utils.Collections:是集合工具类。
  • Collections并不属于集合,是用来操作集合的工具类。
  • Collections有几个常用的API:

        public static <T> boolean addAll(Collection<? super T> c, T... elements):给集合对象批量添加元素!
        public static void shuffle(List<?> list) :打乱集合顺序。
        public static <T> void sort(List<T> list):将集合中元素按照默认规则排序。
        public static <T> void sort(List<T> list,Comparator<? super T> c):将集合中元素按照指定规则排序。
package com.liu.day06.CollectionsDemo;

import java.util.*;
public class CollectionsDemo01 {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        Collections.addAll(names, "zhenxi","珍惜大学","计算机与控制工程学院","2022","电子信息");
        System.out.println(names);

        // 2、public static void shuffle(List<?> list) :打乱集合顺序。
        Collections.shuffle(names);
        System.out.println(names);

        // 3、 public static <T> void sort(List<T> list):将集合中元素按照默认规则排序。 (排值特性的元素)
        List<Integer> list = new ArrayList<>();
        Collections.addAll(list, 23,41,21,31);
        System.out.println(list);
        Collections.sort(list);
        System.out.println(list);

        //4. public static <T> void sort(List<T> list,Comparator<? super T> c):将集合中元素按照指定规则排序。
        List<Apple> apples = new ArrayList<>(); // 可以重复!
        apples.add(new Apple("红富士", "红色", 9.9, 500));
        apples.add(new Apple("星空绿", "绿色", 19.9, 300));
        apples.add(new Apple("橄榄青", "青色", 29.9, 400));
        apples.add(new Apple("格调黄", "黄色", 0.9, 500));
        //方式一:Apple类重写比较规则与上个案例student类重写相同
        /*
        @Override
        public int compareTo(Apple o) {
            return this.price- o.price;
        }
        **/
        
        // 方式二:sort方法自带比较器对象
//        Collections.sort(apples, new Comparator<Apple>() {
//            @Override
//            public int compare(Apple o1, Apple o2) {
//                return Double.compare(o1.getPrice() , o2.getPrice()); // 按照价格排序!!
//            }
//        });

        Collections.sort(apples, ( o1,  o2) ->  Double.compare(o1.getPrice() , o2.getPrice()) );
        System.out.println(apples);
    }
}
/**
运行结果:
[zhenxi, 珍惜大学, 计算机与控制工程学院, 2022, 电子信息]
[珍惜大学, 电子信息, zhenxi, 2022, 计算机与控制工程学院]
[23, 41, 21, 31]
[21, 23, 31, 41]
[Apple{name='格调黄', color='黄色', price=0.9, weight=500}, Apple{name='红富士', color='红色', price=9.9, weight=500}, Apple{name='星空绿', color='绿色', price=19.9, weight=300}, Apple{name='橄榄青', color='青色', price=29.9, weight=400}]
*/

8 综合案例:斗地主游戏

package com.liu.day06.HashDemo;

public class Card {
    private String size;
    private String color;
    private int index; // 牌的真正大小

    public Card(){
    }

    public Card(String size, String color, int index) {
        this.size = size;
        this.color = color;
        this.index = index;
    }

    public String getSize() {
        return size;
    }

    public void setSize(String size) {
        this.size = size;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }

    @Override
    public String toString() {
        return size + color;
    }
}
package com.liu.day06.HashDemo;

import java.util.*;

public class GameDemo {

      //定义一个静态的集合存储54张牌对象

     public static List<Card> allCards = new ArrayList<>();

     //做牌:定义静态代码块初始化牌数据

    static {
        //定义点数:个数确定,类型确定,使用数组
        String[] sizes = {"3","4","5","6","7","8","9","10","J","Q","K","A","2"};
        //定义花色:个数确定,类型确定,使用数组
        String[] colors = {"♠", "♥", "♣", "♦"};
        //组合点数和花色
        int index = 0; // 记录牌的大小
        for (String size : sizes) {
            index++;
            for (String color : colors) {
                //封装成一个牌对象。
                Card c = new Card(size, color, index);
                //存入到集合容器中去
                allCards.add(c);
            }
        }
        //大小王存入到集合对象中去 "👲" , "🃏"
        Card c1 = new Card("" ,  "🃏", ++index);
        Card c2 = new Card("" ,  "👲",++index);
        Collections.addAll(allCards , c1 , c2);
        System.out.println("新牌:" + allCards);
    }

    public static void main(String[] args) {
        //洗牌
        Collections.shuffle(allCards);
        System.out.println("洗牌后:" + allCards);

        // 发牌(定义三个玩家,每个玩家的牌也是一个集合容器)
        List<Card> liuchang = new ArrayList<>();
        List<Card> shuang = new ArrayList<>();
        List<Card> chenglong = new ArrayList<>();

        //开始发牌(从牌集合中发出51张牌给三个玩家,剩余3张作为底牌)
        // allCards = [🃏, A♠, 5♥, 2♠, 2♣, Q♣, 👲, Q♠ ...
        //    i        0  1   2   3   4   5    6  7      %  3
        for (int i = 0; i < allCards.size() - 3; i++) {
            // 先拿到当前牌对象
            Card c = allCards.get(i);
            if(i % 3 == 0) {
                // 请阿冲接牌
                liuchang.add(c);
            }else if(i % 3 == 1){
                // 请阿鸠
                shuang.add(c);
            }else if(i % 3 == 2){
                // 请盈盈接牌
                chenglong.add(c);
            }
        }

        //拿到最后三张底牌(把最后三张牌截取成一个子集合)
        List<Card> lastThreeCards = allCards.subList(allCards.size() - 3 , allCards.size());
        System.out.println("三张底牌:" + lastThreeCards);
        
        //给玩家的牌排序(从大到小 可以自己先试试怎么实现)
        sortCards(liuchang);
        sortCards(shuang);
        sortCards(chenglong);

        //输出玩家的牌:
        System.out.println("zhenxi:" + liuchang);
        System.out.println("爽:" + shuang);
        System.out.println("成龙:" + chenglong);
    }

    private static void sortCards(List<Card> cards) {
        // cards = [J♥, A♦, 3♥, 🃏, 5♦, Q♥, 2♥
        Collections.sort(cards, new Comparator<Card>() {
            @Override
            public int compare(Card o1, Card o2) {
                // o1 = J♥
                // o2 = A♦
                // 知道牌的大小,才可以指定规则
                return o2.getIndex() - o1.getIndex();
            }
        });
    }

}

9 Map集合

9.1 Map集合的概述

  • Map集合是一种双列集合,每个元素包含两个数据。
  • Map集合的每个元素的格式: key=value(键 值对元素)。
  • Map集合也被称为“键值对集合”。
  • Map集合的完整格式: {key1=value1,key2=value2 ,key3=value3,...}

9.2 Map集合体系特点

  • Map集合的特点都是由键决定的。
  • Map集合的键是无序,不重复的,无索引的,值不做要求(可以重复)。
  • Map集合后面重复的键对应的值会覆盖前面重复键的值。
  • Map集合的键值对都可以为null。
  • HashMap:元素按照键是无序,不重复,无索引,值不做要求。( 与Map体系一致)
  • LinkedHashMap:元素按照键是有序,不重复,无索引,值不做要求。
  • TreeMap:元素按照建是排序,不重复,无索引的,值不做要求。

image-20220728113744320

9.3 Map集合常用API

  • Map是双列集合的祖宗接口,它的功能是全部双列集合都可以继承使用的。

image-20220728114112143

package com.liu.day06.Map;
package com.liu.day06.Map;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class MapDemo01 {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        //put方法
        map.put("zhenxi",23);
        map.put("成龙",22);
        map.put("爽",23);
        map.put("帅博",21);
        System.out.println(map);
        //get方法
        Integer integer = map.get("zhenxi");
        System.out.println(integer);
        //keySet方法:获取全部键的集合
        Set<String> set = map.keySet();
        for (String s : set) {
            System.out.println(s);
        }
        //values方法:获取全部值的集合
        Collection<Integer> values = map.values();
        for (Integer value : values) {
            System.out.println(value);
        }
        //remove方法
        map.remove("zhenxi");
        System.out.println(map);
        //containsKey方法
        System.out.println(map.containsKey("爽"));
        //containsValue方法
        System.out.println(map.containsValue(21));
        //isEmpty方法
        System.out.println(map.isEmpty());
        //size方法
        System.out.println(map.size());
        //clear方法
        map.clear();
        System.out.println(map.isEmpty());
    }
}

/**运行结果:
{成龙=22, zhenxi=23, 爽=23, 帅博=21}
23
成龙
zhenxi
爽
帅博
22
23
23
21
{成龙=22, 爽=23, 帅博=21}
true
true
false
3
true

Process finished with exit code 0
*/

9.4 Map集合的遍历方式

第一种:键找值

  • 先获取Map集合的全部键的Set集合。
  • 遍历键的Set集合,然后通过键提取对应值。

image-20220728120223412

package com.liu.day06.Map;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class MapDemo02 {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        //put方法
        map.put("zhenxi",23);
        map.put("成龙",22);
        map.put("爽",23);
        map.put("帅博",21);
        System.out.println(map);
        Set<String> set = map.keySet();
        for (String key : set) {
            Integer value = map.get(key);
            System.out.println(key + "=" + value);
        }
    }
}
/**
{成龙=22, zhenxi=23, 爽=23, 帅博=21}
成龙=22
zhenxi=23
爽=23
帅博=21
*/

第二种:键值对

  • 先把Map集合转换成Set集合,Set集合中每个元素都是键值对实体类型了。
  • 遍历Set集合,然后提取键以及提取值。

image-20220728120412953

package com.liu.day06.Map;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class MapDemo03 {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        //put方法
        map.put("zhenxi",23);
        map.put("成龙",22);
        map.put("爽",23);
        map.put("帅博",21);
        System.out.println(map);

        Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
        for (Map.Entry<String, Integer> entry : entrySet) {
            String key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println(key+"="+value);
        }
    }
}

第三种:Lambda表达式

  • 得益于JDK 8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式。

image-20220728120512843

package com.liu.day06.Map;

import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;

public class MapDemo04 {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        //put方法
        map.put("zhenxi",23);
        map.put("成龙",22);
        map.put("爽",23);
        map.put("帅博",21);
        System.out.println(map);
        
        //匿名内部类
        map.forEach(new BiConsumer<String, Integer>() {
            @Override
            public void accept(String s, Integer integer) {
                System.out.println(s+"="+integer);
            }
        });
        //lambda表达式
        map.forEach(((s, integer) -> System.out.println(s+"="+integer)));
    }
}

9.5 Map集合案例

需求:
某个班级80名学生,现在需要组成秋游活动,班长提供了四个最点依次是(A、B、C. D) ,每个学生只能选择一个景点,请统计出最终哪个景点想去的人数最多。
分析:

  • 将80个学生选择的数据拿到程序中去。
  • 定义Map集合用于存储最终统计的结果。
  • 遍历80个学生选择的数据,看Map集合中是否存在,不存在存入“数据=1“,存在则其对应值+1。
package com.liu.day06.Map;
import java.util.*;


public class MapTest1 {
    public static void main(String[] args) {
         // 1、把80个学生选择的数据拿进来。
        String[] selects = {"A" , "B", "C", "D"};
        StringBuilder sb = new StringBuilder();
        Random r = new Random();
        for (int i = 0; i < 80; i++) {
            sb.append(selects[r.nextInt(selects.length)]);
        }
        System.out.println(sb);

        // 2、定义一个Map集合记录最终统计的结果: A=30 B=20 C=20 D=10  键是景点 值是选择的数量
        Map<Character, Integer> infos = new HashMap<>(); //

        // 3、遍历80个学生选择的数据
        for (int i = 0; i < sb.length(); i++) {
            // 4、提取当前选择景点字符
            char ch = sb.charAt(i);
            // 5、判断Map集合中是否存在这个键
            if(infos.containsKey(ch)){
                 // 让其值 + 1
                infos.put(ch , infos.get(ch) + 1);
            }else {
                // 说明此景点是第一次被选
                infos.put(ch , 1);
            }
        }

        // 4、输出集合
        System.out.println(infos);

    }
}

9.6 Map集合的实现类HashMap

  • HashMap是Map里面的一个实现类。特点都是由键决定的:无序、不重复、无索引。
  • 没有额外需要学习的特有方法,直接使用Map里面的方法就可以了。
  • HashMap跟HashSet底层原理是一模一样的, 都是哈希表结构,只是HashMap的每个元素包含两个值而已。
  • Set系列集合的底层就是Map实现的,只是Set集合中的元素只要键数据,不要值数据而已。
  • 依赖hashCode方法和equals方法保证键的唯一。
  • 如果键要存储的是自定义对象,需要重写hashCode和equals方法。
package com.liu.day06.Map;

import com.liu.day06.HashDemo.Student;

import java.util.HashMap;
import java.util.Map;

public class HashMapDemo {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        //put方法
        map.put("zhenxi",23);
        map.put("成龙",22);
        map.put("爽",23);
        map.put("帅博",21);
        System.out.println(map);

       Map<Student,Integer> map1  = new HashMap<>();
       map1.put(new Student("zhenxi",02),23);
       map1.put(new Student("成龙",11),22);
       map1.put(new Student("辰乐",07),22);
       System.out.println(map1);
        /**
        student需要重写hashCode和equals方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return id == student.id && Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, id);
    }
        */
    }
}
/**
{成龙=22, zhenxi=23, 爽=23, 帅博=21}
{Student{name='成龙', id=11}=22, Student{name='zhenxi', id=2}=23, Student{name='辰乐', id=7}=22}
*/

9.7 Map集合的实现类LinkedHashMap

  • 由键决定:有序、不重复、无索引。
  • 这里的有序指的是保证存储和取出的元素顺序一致。
  • 原理:底层数据结构是依然哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序。
package com.liu.day06.Map;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

public class LinkedHashMapDemo {
    public static void main(String[] args) {
        Map<String, Integer> map = new LinkedHashMap<>();
        //put方法
        map.put("zhenxi",23);
        map.put("zhenxi",23);
        map.put("成龙",22);
        map.put("爽",23);
        map.put("帅博",21);
        System.out.println(map);
        //{zhenxi=23, 成龙=22, 爽=23, 帅博=21}
    }
}

9.8 Map集合的实现类TreeMap

  • 由键决定特性:不重复、无索引、可排序。
  • 可排序:按照键数据的大小默认升序(有小到大)排序。只能对键排序。
  • 注意: TreeMap集合是一定要排序的,可以默认排序,也可以将键按照指定的规则进行排序。
  • TreeMap集合自定义排序规则有2种:
    类实现Comparable接口, 重写比较规则。
    集合自定义Comparator比较器对象, 重写比较规则。
package com.liu.day06.Map;

import com.liu.day06.HashDemo.Apple;
import com.liu.day06.HashDemo.Student;

import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

public class TreeMapDemo {
    public static void main(String[] args) {
        Map<String, Integer> map = new TreeMap<>();
        //put方法
        map.put("zhenxi",23);
        map.put("成龙",22);
        map.put("爽",23);
        map.put("帅博",21);
        System.out.println(map);
        //方式一:实现Comparable接口
        /*
        *
            @Override
            public int compareTo(Student o) {
                return this.id - o.id;
            }
        * */
        Map<Student,Integer> map1  = new TreeMap<>();
        map1.put(new Student("zhenxi",02),23);
        map1.put(new Student("成龙",11),22);
        map1.put(new Student("辰乐",07),22);
        System.out.println(map1);

        //方式二:集合自定义
        Map<Apple,Integer> map2 = new TreeMap<>(new Comparator<Apple>() {
            @Override
            public int compare(Apple o1, Apple o2) {
                return o1.getWeight() - o2.getWeight();
            }
        });
        
        map2.put(new Apple("星空灰",20),5000);
        map2.put(new Apple("格调紫",40),4000);
        map2.put(new Apple("青绿",30),4500);
        System.out.println(map2);
    }
}

学习记录7


1 不可变集合

  • 不可变集合,就是不可被修改的集合。
  • 集合的数据项在创建的时候提供,并且在整个生命周期中都不可改变。否则报错。
  • 如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践。或者当集合对象被不可信的库调用时,不可变形式是安全的。

image-20220728091112259

package com.liu.day7.Collection;

import java.util.List;
import java.util.Map;
import java.util.Set;

public class CollectionDemo01 {
    public static void main(String[] args) {
        //注意JDK8版本会报错的 不可变集合是Java9的新特征
        List<Double> list = List.of(300.3,335.0,329.0,329.0);
        //list.add(22.2);//报错:UnsupportedOperationException 不支持的操作异常
        //list.set(1,334.3);//报错:UnsupportedOperationException 不支持的操作异常
        System.out.println(list.get(1));
        System.out.println(list);
        //不可更改、不可添加、能够查询

        //set集合
        //Set<Integer> set = Set.of(2,4,6,4);//报错:IllegalArgumentException
        Set<Integer> set = Set.of(2,3,6,4);
        //set.add(9);//报错:UnsupportedOperationException 不支持的操作异常
        //set.clear();//报错:UnsupportedOperationException 不支持的操作异常
        System.out.println(set);

        //map集合
        Map<String, Integer> map = Map.of("zhenxi",23,"成龙",24);
        //map.put("努力",666);//报错:UnsupportedOperationException 不支持的操作异常
        System.out.println(map.get("zhenxi"));
        System.out.println(set);
    }
}

2 Stream流

2.1 Stream流概述

  • 在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream流概念。
  • 目的:用于简化集合和数组操作的API。

Stream流思想的核心:

  • 先得到集合或者数组的Stream流(就是一根传送带)。
  • 把元素放上去。
  • 然后就用这个Stream流简化的API来方便的操作元素。
package com.liu.day7.Stream;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class StreamDemo {
    public static void main(String[] args) {
        List<String> arrayList = new ArrayList<>();
        Collections.addAll(arrayList,"zhenxi","刘珍惜","张宇","张乐雨","李金磊","李景隆");
        System.out.println(arrayList);
        //从集合中找到姓张的并存到新集合中
        List<String> zhangList = new ArrayList<>();
        for (String name : arrayList){
            if (name.startsWith("张")){
                zhangList.add(name);
            }
        }
        System.out.println(zhangList);

        //从集合中找到姓张并且是三个字的并存到新集合中
        List<String> zhangThreeList = new ArrayList<>();
        for (String name : zhangList){
            if (name.length()==3){
                zhangThreeList.add(name);
            }
        }
        System.out.println(zhangThreeList);
        
        //使用Stream流来获取
        arrayList.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(s -> System.out.println(s));
    }
}

2.2 Stream流的获取

image-20220728092309313

package com.liu.day7.Stream;

import java.util.*;
import java.util.stream.Stream;

public class StreamDemo01 {
    public static void main(String[] args) {
        //Collection集合获取流
        Collection<String> list =new ArrayList<>();
        Stream<String> stream = list.stream();
        
        //Map集合获取流
        Map<String, Integer> map=new HashMap<>();
        //键流
        Stream<String> stream1 = map.keySet().stream();
        //值流
        Stream<Integer> stream2 = map.values().stream();
        //键值对流
        Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();
        
        //数组获取流
        String[] namesArray = {"","zhenxi","珍惜大学","计算机与控制工程学院"};
        Stream<String> stringStream = Arrays.stream(namesArray);
    }
}

2.3 Stream流的API

  • 获取Stream流方法:创建一条流水线,并把数据放到流水线上准备进行操作。
  • 中间方法:流水线上的操作。一次操作完毕之后,还可以继续进行其他操作。中间方法也称为非终结方法,调用完成后返回新的Stream流可以继续使用,支持链式编程。
  • 终结方法:一个Stream流只能有一个终结方法,是流水线上的最后一个操作。终结操作方法,调用完成后流就无法继续使用了,原因是不会返回Stream了
  • 在Stream流中无法直接修改集合、数组中的数据。

image-20220728091212924

image-20220728102136673

package com.liu.day7.Stream;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;

public class StreamDemo02 {
    public static void main(String[] args) {
        //Stream流API
        List<String> arrayList = new ArrayList<>();
        Collections.addAll(arrayList,"zhenxi","刘珍惜","张宇","张乐雨","李金磊","李景隆");
        //filter的匿名内部类写法:以及使用终止方法forEach()
        arrayList.stream().filter(new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.startsWith("刘");
            }
        }).forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });//运行结果:zhenxi 刘珍惜
        //filter使用lambda表达式 以及使用终止方法count()
        long l = arrayList.stream().filter(s -> s.startsWith("刘")).count();
        System.out.println(l);//2

        //map加工方法
        arrayList.stream().map(s -> "珍惜大学"+s).forEach(s -> System.out.println(s));
        /*  珍惜大学zhenxi
            珍惜大学刘珍惜
            珍惜大学张宇
            珍惜大学张乐雨
            珍惜大学李金磊
            珍惜大学李景隆*/
        //加工成一个学生对象
        arrayList.stream().map(s -> new Student(s)).forEach(student -> System.out.println(student));
        /*  Student{name='zhenxi'}
            Student{name='刘珍惜'}
            Student{name='张宇'}
            Student{name='张乐雨'}
            Student{name='李金磊'}
            Student{name='李景隆'}
        */

        //limit方法
        arrayList.stream().filter(s -> s.startsWith("张")).limit(1).forEach(s -> System.out.println(s));//张宇
        //skip
        arrayList.stream().filter(s -> s.startsWith("张")).skip(1).forEach(s -> System.out.println(s));//张乐雨
        //distinct方法
        arrayList.add("zhenxi");
        arrayList.stream().filter(s -> s.startsWith("刘")).distinct().forEach(s -> System.out.println(s));//zhenxi 刘珍惜
        //concat方法
        Stream<String> stream = arrayList.stream().filter(s -> s.startsWith("张"));
        Stream<String> stream1 = arrayList.stream().filter(s -> s.startsWith("刘"));
        Stream.concat(stream,stream1).forEach(s -> System.out.println(s));
        /*  张宇
            张乐雨
            zhenxi
            刘珍惜
            zhenxi*/
    }
}

2.4 Stream流案例

  • 员工信息至少包含了(名称、性别、工资、奖金、处罚记录)。
  • 开发一部有4个员工、开发二部有5名员工。
  • 分别筛选出2个部门的最高工资的员工信息,封装成优秀员工对象Topperformer。
  • 分别统计出2个部门的平均月收入,要求去掉最高和最低工资。
  • 统计2个开发部门整体的平均工资,去掉最低和最高工资的平均值。

Employee类:

package com.liu.day7.Stream;

public class Employee {
    private String name;
    private char sex;
    private double salary;
    private double bonus;
    private String punish; // 处罚信息

    public Employee(){
    }

    public Employee(String name, char sex, double salary, double bonus, String punish) {
        this.name = name;
        this.sex = sex;
        this.salary = salary;
        this.bonus = bonus;
        this.punish = punish;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public char getSex() {
        return sex;
    }

    public void setSex(char sex) {
        this.sex = sex;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public double getBonus() {
        return bonus;
    }

    public void setBonus(double bonus) {
        this.bonus = bonus;
    }

    public String getPunish() {
        return punish;
    }

    public void setPunish(String punish) {
        this.punish = punish;
    }

    public double getTotalSalay(){
        return salary * 12 + bonus;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", sex=" + sex +
                ", salary=" + salary +
                ", bonus=" + bonus +
                ", punish='" + punish + '\'' +
                '}'+"\n";
    }
}

Topperformer类:

package com.liu.day7.Stream;

public class Topperformer {
    private String name;
    private double money; // 月薪

    public Topperformer() {
    }

    public Topperformer(String name, double money) {
        this.name = name;
        this.money = money;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Topperformer{" +
                "name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}
package com.liu.day7.Stream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class StreamDemo04 {
    public static double allMoney ;
    public static double allMoney2 ; // 2个部门去掉最高工资,最低工资的总和
    public static void main(String[] args) {
        List<Employee> one = new ArrayList<>();
        one.add(new Employee("猪八戒",'男',30000 , 25000, null));
        one.add(new Employee("孙悟空",'男',25000 , 1000, "顶撞上司"));
        one.add(new Employee("沙僧",'男',20000 , 20000, null));
        one.add(new Employee("小白龙",'男',20000 , 25000, null));

        List<Employee> two = new ArrayList<>();
        two.add(new Employee("武松",'男',15000 , 9000, null));
        two.add(new Employee("李逵",'男',20000 , 10000, null));
        two.add(new Employee("西门庆",'男',50000 , 100000, "被打"));
        two.add(new Employee("潘金莲",'女',3500 , 1000, "被打"));
        two.add(new Employee("武大郎",'女',20000 , 0, "下毒"));

        // 1、开发一部的最高工资的员工。(API)
        // 指定大小规则了
        Topperformer t = one.stream().max((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(),  e2.getSalary() + e2.getBonus()))
                .map(e -> new Topperformer(e.getName(),  e.getSalary() + e.getBonus())).get();
        System.out.println(t);

        // 2、统计平均工资,去掉最高工资和最低工资
        one.stream().sorted((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(),  e2.getSalary() + e2.getBonus()))
                .skip(1).limit(one.size() - 2).forEach(e -> {
                    // 求出总和:剩余员工的工资总和
            allMoney += (e.getSalary() + e.getBonus());
        });
        System.out.println("开发一部的平均工资是:" + allMoney / (one.size() - 2));

        // 3、合并2个集合流,再统计
        Stream<Employee> s1 = one.stream();
        Stream<Employee> s2 = two.stream();
        Stream<Employee> s3 = Stream.concat(s1 , s2);
        s3.sorted((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(),  e2.getSalary() + e2.getBonus()))
                .skip(1).limit(one.size() + two.size() - 2).forEach(e -> {
            // 求出总和:剩余员工的工资总和
            allMoney2 += (e.getSalary() + e.getBonus());
        });

        // BigDecimal
        BigDecimal a = BigDecimal.valueOf(allMoney2);
        BigDecimal b = BigDecimal.valueOf(one.size()  + two.size() - 2);
        System.out.println("开发部的平均工资是:" + a.divide(b,2, RoundingMode.HALF_UP));
    }
}

2.5 Stream流的收集

  • 收集Stream流的含义:就是把Stream流操作后的结果数据转回到集合或者数组中去。
  • Stream流:方便操作集合/数组的手段。

image-20220728102601401

package com.liu.day7.Stream;

import java.util.*;
import java.util.function.IntFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamDemo05 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        Collections.addAll(list,"zhenxi","刘珍惜","张宇","张乐雨","李金磊","李景隆");

        Stream<String> s1 = list.stream().filter(s -> s.startsWith("张"));
        List<String> zhangList = s1.collect(Collectors.toList()); // 可变集合
        zhangList.add("java");
        System.out.println(zhangList);

//       List<String> list1 = s1.toList(); // 得到不可变集合
//       list1.add("java");
//       System.out.println(list1);

        // 注意注意注意:“流只能使用一次”
        Stream<String> s2 = list.stream().filter(s -> s.startsWith("张"));
        Set<String> zhangSet = s2.collect(Collectors.toSet());
        System.out.println(zhangSet);

        Stream<String> s3 = list.stream().filter(s -> s.startsWith("张"));
        String[] arrs = s3.toArray(String[]::new);
        System.out.println("Arrays数组内容:" + Arrays.toString(arrs));

    }
}

3 异常

3.1 异常概述、体系

异常是什么?

  • 异常是程序在"编译"或者"执行"的过程中可能出现的问题。
  • 异常是应该尽量提前避免的。
  • 异常可能也是无法做到绝对避免的,异常可能有太多情况了,开发中只能提前干预!!
  • 异常一旦出现了,如果没有提前处理,程序就会退出JVM虚拟机而终止,开发中异常是需要提前处理的。

异常分为几类?

  • 编译时异常、 运行时异常。
  • 编译时异常: 没有继承RuntimeExcpetion的异常,编译阶段就会出错。
  • 运行时异常: 继承自RuntimeException的异常或其子类,编译阶段不报错,运行可能报错。

3.2 常见运行时异常

继承自RuntimeException的异常或者其子类,编译阶段是不会出错的,它是在运行时阶段可能出现的错误,运行时异常编译阶段可以处理也可以不处理,代码编译都能通过!!

运行时异常示例:

1.数组索引越界异常: ArrayIndexOutOfBoundsException。
2.空指针异常 : NullPointerException。直接输出没有问题。但是调用空指针的变量的功能就会报错!!
3.类型转换异常:ClassCastException。
4.迭代器遍历没有此元素异常:NoSuchElementException。
5.数学操作异常:ArithmeticException。
6.数字转换异常: NumberFormatException。

小结:

  • 运行时异常继承了RuntimeException ,编译阶段不报错,运行时才可能会出现错误!
  • 运行时异常:一般是程序员业务没有考虑好或者是编程逻辑不严谨引起的程序错误。
public class ExceptionDemo01 {
    public static void main(String[] args) {
        /** 1.数组索引越界异常: ArrayIndexOutOfBoundsException。*/
        int[] arr = {1, 2, 3};
        System.out.println(arr[2]);
        // System.out.println(arr[3]); // 运行出错,程序终止

        /** 2.空指针异常 : NullPointerException。直接输出没有问题。但是调用空指针的变量的功能就会报错!! */
        String name = null;
        System.out.println(name); // null
        // System.out.println(name.length()); // 运行出错,程序终止

        /** 3.类型转换异常:ClassCastException。 */
        Object o = 23;
        // String s = (String) o;  // 运行出错,程序终止

        /** 5.数学操作异常:ArithmeticException。 */
        //int c = 10 / 0;

        /** 6.数字转换异常: NumberFormatException。 */
        //String number = "23";
        String number = "23aabbc";
        Integer it = Integer.valueOf(number); // 运行出错,程序终止
        System.out.println(it + 1);

        System.out.println("程序结束。。。。。");
    }
}

3.3 常见编译时异常

  • 继承自Exception的异常或者其子类,没有继承RuntimeException。
  • 编译时异常是编译阶段就会报错。
  • 程序员必须编译阶段就处理的。否则代码编译就报错!!

编译时异常的作用是什么:

  • 是担心程序员的技术不行,在编译阶段就爆出一个错误,目的在于提醒!
  • 提醒程序员这里很可能出错,请检查并注意不要出bug。
package com.liu.day7.Exception;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ExceptionDemo02 {
    public static void main(String[] args) throws ParseException {
        String date = "2015-01-12 10:23:21";
        // 创建一个简单日期格式化类:
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM-dd HH:mm:ss");
        // 解析字符串时间成为日期对象
        Date d = sdf.parse(date);
        //
        System.out.println(d);
    }
}

3.4 异常的默认处理流程

  • 默认会在出现异常的代码那里自动的创建一个异常对象:ArithmeticException。
  • 异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给JVM虚拟机。
  • 虚拟机接收到异常对象后,先在控制台直接输出异常栈信息数据。
  • 直接从当前执行的异常点干掉当前程序。
  • 后续代码没有机会执行了,因为程序已经死亡。

小结:

  • 异常一旦出现,会自动创建异常对象,最终抛出给虚拟机,虚拟机。
  • 只要收到异常,就直接输出异常信息,干掉程序!!

3.5 编译时异常的处理机制

  • 编译时异常:编译阶段就会报错,一定需要程序员处理的,否则代码无法通过!!

异常处理方法一:抛出去!

  • 抛出异常格式:方法 throws 异常1 , 异常2 , ..{}
  • 建议抛出异常的方式:代表可以抛出一切异常,方法 throws Exception{}
  • 在出现编译时异常的地方层层把异常抛出去给调用者,调用者最终抛出给JVM虚拟机。
  • JVM虚拟机输出异常信息,直接干掉程序,这种方式与默认方式是一样的。
  • 虽然可以解决代码编译时的错误,但是一旦运行时真的出现异常,程序还是会立即死亡!
  • 小结:方式一出现异常层层跑出给虚拟机,最终程序如果真的出现异常,程序还是立即死亡!这种方式不好!
package com.liu.day7.ExceptionTest;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ExceptionDemo01 {

    public static void main(String[] args) throws Exception {
        System.out.println("程序开始。。。。。");
        parseTime("2011-11-11 11:11:11");
        System.out.println("程序结束。。。。。");
    }

    public static void parseTime(String date) throws Exception {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d = sdf.parse(date);
        System.out.println(d);

        InputStream is = new FileInputStream("E:/meinv.jpg");
    }
}

异常处理方法二:try catch

  • 自己捕获异常和处理异常的格式:捕获处理

        try{
           // 监视可能出现异常的代码!
        }catch(异常类型1 变量){
           // 处理异常
        }catch(异常类型2 变量){
           // 处理异常
        }...
  • 监视捕获处理异常企业级写法:

        try{
            // 可能出现异常的代码!
        }catch (Exception e){
           e.printStackTrace(); // 直接打印异常栈信息
        }
        Exception可以捕获处理一切异常类型!
  • 小结:
    第二种方式,可以处理异常,并且出现异常后代码也不会死亡。这种方案还是可以的。
    但是从理论上来说,这种方式不是最好的,上层调用者不能直接知道底层的执行情况!
package com.liu.day7.ExceptionTest;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ExceptionDemo02 {
    public static void main(String[] args) {
        System.out.println("程序开始。。。。");
        parseTime("2011-11-11 11:11:11");
        System.out.println("程序结束。。。。");
    }

    public static void parseTime(String date) {
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM-dd HH:mm:ss");
            Date d = sdf.parse(date);
            System.out.println(d);
            InputStream is = new FileInputStream("E:/meinv.jpg");
        } catch (Exception e) {
            e.printStackTrace(); // 打印异常栈信息
        }
    }
}

异常处理方法三:前两者结合

  • 在出现异常的地方把异常一层一层的抛出给最外层调用者,最外层调用者集中捕获处理!!(规范做法)

小结:

  • 编译时异常的处理方式三:底层出现的异常抛出给最外层调用者集中捕获处理。
  • 这种方案最外层调用者可以知道底层执行的情况,同时程序在出现异常后也不会立即死亡,这是

    理论上最好的方案。
    
package com.liu.day7.ExceptionTest;

import java.io.FileInputStream;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ExceptionDemo03 {
    public static void main(String[] args) {
        System.out.println("程序开始。。。。");
        try {
            parseTime("2011-11-11 11:11:11");
            System.out.println("功能操作成功~~~");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("功能操作失败~~~");
        }
        System.out.println("程序结束。。。。");
    }

    public static void parseTime(String date) throws Exception {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy、MM-dd HH:mm:ss");
        Date d = sdf.parse(date);
        System.out.println(d);
        InputStream is = new FileInputStream("D:/meinv.jpg");
    }

}

3.6 运行时异常的处理机制

  • 可以不处理,编译阶段又不报错。
  • 按照理论规则:建议还是处理,只需要在最外层捕获处理即可。但程序会默认抛出异常。

3.7 自定义异常

自定义异常的必要?

  • Java无法为这个世界,上全部的问题提供异常类。
  • 如果企业想通过异常的方式来管理自己的某个业务问题,就需要自定义异常类了。

自定义异常的好处:

  • 可以使用异常的机制管理业务问题,如提醒程序员注意。
  • 同时一旦出现bug,可以用异常的形式清晰的指出出错的地方。

自定义编译时异常:

  • 定义一个异常类继承Exception。
  • 重写构造器。
  • 在出现异常的地方用throw new 自定义对象抛出!
  • 编译时异常是编译阶段就报错,提醒更加强烈,一定需要处理!!

自定义运行时异常.

  • 定义一个异常类继承RuntimeException。
  • 重写构造器。
  • 在出现异常的地方用throw new 自定义对象抛出!
  • 提醒不强烈,编译阶段不报错!!运行时才可能出现!!
package com.liu.day7.ExceptionTest;

/**
    自定义的编译时异常
      1、继承Exception
      2、重写构造器
 */
public class ItheimaAgeIlleagalException extends Exception{
    public ItheimaAgeIlleagalException() {
    }

    public ItheimaAgeIlleagalException(String message) {
        super(message);
    }
}
package com.liu.day7.ExceptionTest;

/**
    自定义的编译时异常
      1、继承RuntimeException
      2、重写构造器
 */
public class ItheimaAgeIlleagalRuntimeException extends RuntimeException{
    public ItheimaAgeIlleagalRuntimeException() {
    }

    public ItheimaAgeIlleagalRuntimeException(String message) {
        super(message);
    }
}
package com.liu.day7.ExceptionTest;

public class ExceptionDemo {
    public static void main(String[] args) {
        try {
            checkAge2(-23);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void checkAge2(int age)  {
        if(age < 0 || age > 200){
            // 抛出去一个异常对象给调用者
            // throw :在方法内部直接创建一个异常对象,并从此点抛出
            // throws : 用在方法申明上的,抛出方法内部的异常
            throw new ItheimaAgeIlleagalRuntimeException(age + " is illeagal!");
        }else {
            System.out.println("年龄合法:推荐商品给其购买~~");
        }
    }

    public static void checkAge(int age) throws ItheimaAgeIlleagalException {
        if(age < 0 || age > 200){
            // 抛出去一个异常对象给调用者
            // throw :在方法内部直接创建一个异常对象,并从此点抛出
            // throws : 用在方法申明上的,抛出方法内部的异常
            throw new ItheimaAgeIlleagalException(age + " is illeagal!");
        }else {
            System.out.println("年龄合法:推荐商品给其购买~~");
        }
    }
}

学习总结1

这一周主要学习了大量的常用API,这些类的方法在后面编程中是非常重要的,所以需要经常复习。虽然在本科期间已经学习过Java,但是如今再次学习,依旧发现了以前学习的弊端和疏漏,对于SE的知识理解的更加深刻,并从资料中学习到了很多Java的新特性,以及一些常用类的常用API。本周学习劲头适中,不慢不快,不骄不躁,稳扎稳打,继续坚持~


学习记录8


1 日志框架

1.1 日志概述和优势

  • 用来记录程序运行过程中的信息,并可以永久存储。
  • 可以将系统执行的信息选择性的记录到指定的位置(控制台、文件中、数据库中)。
  • 可以随时以开关的形式控制是否记录日志,无需修改源代码。
输出语句日志技术
输出位置只能输出到控制台可以将日志信息写入到文件或者数据库中
取消日志需要修改代码,灵活性比较差不需要修改代码,灵活性比较好
多线程性能较差性能较好

1.2 日志常见形式

  • 日志规范大多是一些接口,提供给实现框架去设计的。
  • 常见的规范是:Commons Logging和Simple Logging Facade for Java
  • 常见的实现框架:Log4J、Logback。

1.3 logback日志框架介绍

  • logback是由log4j创始人设计的另一个开源日志组件,性能比log4j要好。
  • logback是基于slf4j的日志规范实现的框架。
  • logback-core: logback-core 模块为其他两个模块奠定了基础,必须有。
  • logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j API。
  • logback-access模块与Tomcat和Jetty等Servlet容器集成,以提供HTTP访向日志功能。

1.4 logback的使用

Maven需要引入的依赖:

<dependencies>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.9</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.36</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.9</version>
        </dependency>
 </dependencies>

logback的配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--
        CONSOLE :表示当前的日志信息是可以输出到控制台的。
    -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--输出流对象 默认 System.out 改为 System.err-->
        <target>System.out</target>
        <encoder>
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度
                %msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level]  %c [%thread] : %msg%n</pattern>
        </encoder>
    </appender>

    <!-- File是输出的方向通向文件的 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!--日志输出路径-->
        <file>C:/code/log-data.log</file>
        <!--指定日志文件拆分和压缩规则-->
        <rollingPolicy
                class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--通过指定压缩文件名称,来确定分割文件方式-->
            <fileNamePattern>C:/code/log-data2-%d{yyyy-MMdd}.log%i.gz</fileNamePattern>
            <!--文件拆分大小-->
            <maxFileSize>1MB</maxFileSize>
        </rollingPolicy>
    </appender>

    <!--

    level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF
   , 默认debug
    <root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
    -->
    <root level="ALL">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE" />
    </root>
</configuration>

Logback测试代码:

package com.day08;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogbackDemo {
    public static final Logger LOGGER = LoggerFactory.getLogger("LogbackDemo.class");
    public static void main(String[] args) {
        LOGGER.debug("main方法执行了");
        LOGGER.info("开始记录第二条日志");
        int a = 10;
        int b = 0;
        LOGGER.trace("a="+a);
        LOGGER.trace("b="+b);

    }
}
/**
运行结果:
2022-07-29 11:59:09.845 [DEBUG]  LogbackDemo.class [main] : main方法执行了
2022-07-29 11:59:09.849 [INFO ]  LogbackDemo.class [main] : 开始记录第二条日志
2022-07-29 11:59:09.850 [TRACE]  LogbackDemo.class [main] : a=10
2022-07-29 11:59:09.850 [TRACE]  LogbackDemo.class [main] : b=0
*/

生成的日志文件:

image-20220729142429774

2 阶段项目

项目准备:

  • 集成日志框架、用于后期记录日志信息。
  • 定义一个电影类Movie类,Movie类包含:片名、主演、评分、时长、票价、余票。
  • 系统包含2个用户角色:客户、商家。存在大量相同属性信息。
  • 定义User类作为父类,属性:登录名称、密码、真实名称、性别、电话、账户金额。
  • 定义Business类代表商家角色,属性:店铺名称、地址。
  • 定义Customer类代表客户角色,属性。
  • 定义集合List用户存放系统注册的用户对象信息。
  • 定义集合Map<Business, List>存放商家和其排片信息。

Bean包:

User类:

package com.day08.movies.bean;

/**
   用户类(客户和商家的父类 )
 */
public class User {
    private String loginName;  // 假名  不能重复
    private String userName; // 真名
    private String passWord;
    private char sex;
    private String phone;
    private double money;

    public User(){

    }

    public User(String loginName, String userName, String passWord, char sex, String phone, double money) {
        this.loginName = loginName;
        this.userName = userName;
        this.passWord = passWord;
        this.sex = sex;
        this.phone = phone;
        this.money = money;
    }

    public String getLoginName() {
        return loginName;
    }

    public void setLoginName(String loginName) {
        this.loginName = loginName;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassWord() {
        return passWord;
    }

    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }

    public char getSex() {
        return sex;
    }

    public void setSex(char sex) {
        this.sex = sex;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }
}

Business类:

package com.day08.movies.bean;

public class Business extends User{
    // 店铺名称
    private String shopName;
    // 店铺地址
    private String address;

    public String getShopName() {
        return shopName;
    }

    public void setShopName(String shopName) {
        this.shopName = shopName;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

Customer类:

package com.day08.movies.bean;

import java.util.HashMap;
import java.util.Map;

/**
   客户角色
 */
public class Customer extends User{
    // 定义一个属性存储购买记录。
    private Map<String, Boolean> buyMovies = new HashMap<>();

    public Map<String, Boolean> getBuyMovies() {
        return buyMovies;
    }

    public void setBuyMovies(Map<String, Boolean> buyMovies) {
        this.buyMovies = buyMovies;
    }
}

Movie类:

package com.day08.movies.bean;


import com.day08.movies.run.MovieSystem;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;

public class Movie {
    private String name;
    private String actor;
    private double time;
    private double price;
    private int number; // 余票
    private Date startTime; // 放映时间

    public Movie() {
    }

    public Movie(String name, String actor, double time, double price, int number, Date startTime) {
        this.name = name;
        this.actor = actor;
        this.time = time;
        this.price = price;
        this.number = number;
        this.startTime = startTime;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getActor() {
        return actor;
    }

    public void setActor(String actor) {
        this.actor = actor;
    }

    public double getScore() {
        List<Double> scores = MovieSystem.MOVIES_SCORE.get(name);
        if(scores!=null && scores.size() > 0){
            double sum = 0;
            for (Double score : scores) {
                sum += score;
            }
            return BigDecimal.valueOf(sum).divide(BigDecimal.valueOf(scores.size()), 2 , RoundingMode.UP).doubleValue();
        }else {
            return 0;
        }
    }


    public double getTime() {
        return time;
    }

    public void setTime(double time) {
        this.time = time;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    public Date getStartTime() {
        return startTime;
    }

    public void setStartTime(Date startTime) {
        this.startTime = startTime;
    }
}

主程序:

package com.day08.movies.run;


import com.day08.movies.bean.Business;
import com.day08.movies.bean.Customer;
import com.day08.movies.bean.Movie;
import com.day08.movies.bean.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

public class MovieSystem {
    /**
        定义系统的数据容器用户存储数据
        1、存储很多用户(客户对象,商家对象)
     */
    public static final List<User> ALL_USERS = new ArrayList<>();
    /**
       2、存储系统全部商家和其排片信息 。
           商家1 = [p1,p2,p3,...]
           商家2 = [p2,p3,...]
           ...
     */
    public static final Map<Business, List<Movie>> ALL_MOVIES = new HashMap<>();

    public static final Scanner SYS_SC = new Scanner(System.in);

    // 定义一个静态的User类型的变量记住当前登录成功的用户对象
    public static User loginUser;
    public static SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");

    public static final Logger LOGGER = LoggerFactory.getLogger("MovieSystem.class");

    /**
       3、准备一些测试数据
     */
    static {
        Customer c = new Customer();
        c.setLoginName("zyf888");
        c.setPassWord("123456");
        c.setUserName("黑马刘德华");
        c.setSex('男');
        c.setMoney(10000);
        c.setPhone("110110");
        ALL_USERS.add(c);

        Customer c1 = new Customer();
        c1.setLoginName("gzl888");
        c1.setPassWord("123456");
        c1.setUserName("黑马关之琳");
        c1.setSex('女');
        c1.setMoney(2000);
        c1.setPhone("111111");
        ALL_USERS.add(c1);

        Business b = new Business();
        b.setLoginName("baozugong888");
        b.setPassWord("123456");
        b.setUserName("黑马包租公");
        b.setMoney(0);
        b.setSex('男');
        b.setPhone("110110");
        b.setAddress("火星6号2B二层");
        b.setShopName("甜甜圈国际影城");
        ALL_USERS.add(b);
        // 注意,商家一定需要加入到店铺排片信息中去
        List<Movie> movies = new ArrayList<>();
        ALL_MOVIES.put(b , movies); // b = []

        Business b2 = new Business();
        b2.setLoginName("baozupo888");
        b2.setPassWord("123456");
        b2.setUserName("黑马包租婆");
        b2.setMoney(0);
        b2.setSex('女');
        b2.setPhone("110110");
        b2.setAddress("火星8号8B八层");
        b2.setShopName("巧克力国际影城");
        ALL_USERS.add(b2);
        // 注意,商家一定需要加入到店铺排片信息中去
        List<Movie> movies3 = new ArrayList<>();
        ALL_MOVIES.put(b2 , movies3); // b2 = []
    }


    public static void main(String[] args) {
        showMain();
    }

    /**
       首页展示
     */
    private static void showMain() {
        while (true) {
            System.out.println("===============黑马电影首页=================");
            System.out.println("1、登录");
            System.out.println("2、用户注册");
            System.out.println("3、商家注册");
            System.out.println("请输入操作命令:");
            String command = SYS_SC.nextLine();
            switch (command) {
                case "1":
                    // 登录了
                    login();
                    break;
                case "2":
                    //
                    break;
                case "3":
                    break;
                default:
                    System.out.println("命令有误,请确认!");
            }
        }
    }

    /**
       登录功能
     */
    private static void login() {
        while (true) {
            System.out.println("请您输入登录名称:");
            String loginName = SYS_SC.nextLine();
            System.out.println("请您输入登录密码:");
            String passWord = SYS_SC.nextLine();

            // 1、根据登录名称查询用户对象。
            User u = getUserByLoginName(loginName);
            // 2、判断用户对象是否存在,存在说明登录名称正确了
            if(u != null){
                // 3、比对密码是否正确
                if(u.getPassWord().equals(passWord)){
                    // 登录成功了:...
                    loginUser = u; // 记住登录成功的用户
                    LOGGER.info(u.getUserName() +"登录了系统~~~");
                    // 判断是用户登录的,还是商家登录的。
                    if(u instanceof Customer) {
                        // 当前登录的是普通用户
                        showCustomerMain();
                    }else {
                        // 当前登录的肯定是商家用户
                        showBusinessMain();
                    }
                    return;
                }else {
                    System.out.println("密码有毛病~~");
                }
            }else {
                System.out.println("登录名称错误,请确认");
            }
        }
    }

    /**
      商家的后台操作界面
     */
    private static void showBusinessMain() {
        while (true) {
            System.out.println("============黑马电影商家界面===================");
            System.out.println(loginUser.getUserName() + (loginUser.getSex()=='男'? "先生":"女士" + "欢迎您进入系统"));
            System.out.println("1、展示详情:");
            System.out.println("2、上架电影:");
            System.out.println("3、下架电影:");
            System.out.println("4、修改电影:");
            System.out.println("5、退出:");

            System.out.println("请输入您要操作的命令:");
            String command = SYS_SC.nextLine();
            switch (command){
                case "1":
                    // 展示全部排片信息
                    showBusinessInfos();
                    break;
                case "2":
                    // 上架电影信息
                    addMovie();
                    break;
                case "3":
                    // 下架电影信息
                    deleteMovie();
                    break;
                case "4":
                    // 修改电影信息
                    updateMovie();
                    break;
                case "5":
                    System.out.println(loginUser.getUserName() +"请您下次再来啊~~~");
                    return; // 干掉方法
                default:
                    System.out.println("不存在该命令!!");
                    break;
            }
        }
    }

    /**
     影片修改功能
     */
    private static void updateMovie() {
        System.out.println("================修改电影====================");
        Business business = (Business) loginUser;
        List<Movie> movies = ALL_MOVIES.get(business);

        if(movies.size() == 0) {
            System.out.println("当期无片可以修改~~");
            return;
        }

        // 2、让用户选择需要下架的电影名称
        while (true) {
            System.out.println("请您输入需要修改的电影名称:");
            String movieName = SYS_SC.nextLine();

            // 3、去查询有没有这个影片对象。
            Movie movie = getMovieByName(movieName);
            if(movie != null){
                // 修改它
                System.out.println("请您输入修改后的片名:");
                String name  = SYS_SC.nextLine();
                System.out.println("请您输入修改后主演:");
                String actor  = SYS_SC.nextLine();
                System.out.println("请您输入修改后时长:");
                String time  = SYS_SC.nextLine();
                System.out.println("请您输入修改后票价:");
                String price  = SYS_SC.nextLine();
                System.out.println("请您输入修改后票数:");
                String totalNumber  = SYS_SC.nextLine(); // 200\n
                while (true) {
                    try {
                        System.out.println("请您输入修改后的影片放映时间:");
                        String stime  = SYS_SC.nextLine();

                        movie.setName(name);
                        movie.setActor(actor);
                        movie.setPrice(Double.valueOf(price));
                        movie.setTime(Double.valueOf(time));
                        movie.setNumber(Integer.valueOf(totalNumber));
                        movie.setStartTime(sdf.parse(stime));

                        System.out.println("恭喜您,您成功修改了该影片了!!!");
                        showBusinessInfos();
                        return; // 直接退出去
                    } catch (Exception e) {
                        e.printStackTrace();
                        LOGGER.error("时间解析出了毛病");
                    }
                }
            }else {
                System.out.println("您的店铺没有上架该影片!");
                System.out.println("请问继续修改吗?y/n");
                String command = SYS_SC.nextLine();
                switch (command) {
                    case "y":
                        break;
                    default:
                        System.out.println("好的!");
                        return;
                }
            }
        }
    }

    /**
      影片下架功能
     */
    private static void deleteMovie() {
        System.out.println("================下架电影====================");
        Business business = (Business) loginUser;
        List<Movie> movies = ALL_MOVIES.get(business);
        if(movies.size() == 0) {
            System.out.println("当期无片可以下架~~");
            return;
        }

        // 2、让用户选择需要下架的电影名称
        while (true) {
            System.out.println("请您输入需要下架的电影名称:");
            String movieName = SYS_SC.nextLine();

            // 3、去查询有没有这个影片对象。
            Movie movie = getMovieByName(movieName);
            if(movie != null){
                // 下架它
                movies.remove(movie);
                System.out.println("您当前店铺已经成功下架了:" + movie.getName());
                showBusinessInfos();
                return;
            }else {
                System.out.println("您的店铺没有上架该影片!");
                System.out.println("请问继续下架吗?y/n");
                String command = SYS_SC.nextLine();
                switch (command) {
                    case "y":
                        break;
                    default:
                        System.out.println("好的!");
                        return;
                }
            }
        }
    }

    /**
       去查询当前商家下的排片
     */
    public static Movie getMovieByName(String movieName){
        Business business = (Business) loginUser;
        List<Movie> movies = ALL_MOVIES.get(business);
        for (Movie movie : movies) {
            if(movie.getName().contains(movieName)) {
                return movie;
            }
        }
        return null;
    }

    /**
      商家进行电影上架
     Map<Business , List<Movie>> ALL_MOVIES
     u1 = [p1,p2,p3]
     u2 = [p1,p2,p3]
     */
    private static void addMovie() {
        System.out.println("================上架电影====================");
        // 根据商家对象(就是登录的用户loginUser),作为Map集合的键 提取对应的值就是其排片信息 :Map<Business , List<Movie>> ALL_MOVIES
        Business business = (Business) loginUser;
        List<Movie> movies = ALL_MOVIES.get(business);

        System.out.println("请您输入新片名:");
        String name  = SYS_SC.nextLine();
        System.out.println("请您输入主演:");
        String actor  = SYS_SC.nextLine();
        System.out.println("请您输入时长:");
        String time  = SYS_SC.nextLine();
        System.out.println("请您输入票价:");
        String price  = SYS_SC.nextLine();
        System.out.println("请您输入票数:");
        String totalNumber  = SYS_SC.nextLine(); // 200\n
        while (true) {
            try {
                System.out.println("请您输入影片放映时间:");
                String stime  = SYS_SC.nextLine();
            // public Movie(String name, String actor, double time, double price, int number, Date startTime)        // 封装成电影对象 ,加入集合movices中去
                Movie movie = new Movie(name, actor ,Double.valueOf(time) , Double.valueOf(price)
                        , Integer.valueOf(totalNumber) ,  sdf.parse(stime));
                movies.add(movie);
                System.out.println("您已经成功上架了:《" + movie.getName() + "》");
                return; // 直接退出去
            } catch (ParseException e) {
                e.printStackTrace();
                LOGGER.error("时间解析出了毛病");
            }
        }
    }

    /**
        定义一个静态的Map集合存储电影的评分
     */
    public static final Map<String , List<Double>> MOVIES_SCORE = new HashMap<>();

    /**
        展示商家的详细:展示当前商家的信息。
     */
    private static void showBusinessInfos() {
        System.out.println("================商家详情界面=================");
        LOGGER.info(loginUser.getUserName() +"商家,正在看自己的详情~~~");
        // 根据商家对象(就是登录的用户loginUser),作为Map集合的键 提取对应的值就是其排片信息 :Map<Business , List<Movie>> ALL_MOVIES
        Business business = (Business) loginUser;
        System.out.println(business.getShopName() + "\t\t电话:" + business.getPhone()
                + "\t\t地址:" + business.getAddress() + "\t\t余额:" + business.getMoney());
        List<Movie> movies = ALL_MOVIES.get(business);
        if(movies.size() > 0) {
            System.out.println("片名\t\t\t主演\t\t时长\t\t评分\t\t票价\t\t余票数量\t\t放映时间");
            for (Movie movie : movies) {

                System.out.println(movie.getName()+"\t\t\t" + movie.getActor()+ "\t\t" + movie.getTime()
                        + "\t\t" + movie.getScore() + "\t\t" + movie.getPrice() + "\t\t" + movie.getNumber() + "\t\t"
                        +   sdf.format(movie.getStartTime()));
            }
        }else {
            System.out.println("您的店铺当前无片在放映~~~~");
        }
    }

    /**
      客户操作界面
     */
    private static void showCustomerMain() {
        while (true) {
            System.out.println("============黑马电影客户界面===================");
            System.out.println(loginUser.getUserName() + (loginUser.getSex()=='男'? "先生":"女士" + "欢迎您进入系统" +
                    "\t余额:" + loginUser.getMoney()));
            System.out.println("请您选择要操作的功能:");
            System.out.println("1、展示全部影片信息功能:");
            System.out.println("2、根据电影名称查询电影信息:");
            System.out.println("3、评分功能:");
            System.out.println("4、购票功能:");
            System.out.println("5、退出系统:");
            System.out.println("请输入您要操作的命令:");
            String command = SYS_SC.nextLine();
            switch (command){
                case "1":
                    // 展示全部排片信息
                    showAllMovies();
                    break;
                case "2":
                    break;
                case "3":
                    // 评分功能
                    scoreMovie();
                    showAllMovies();
                    break;
                case "4":
                    // 购票功能
                    buyMovie();
                    break;
                case "5":
                    return; // 干掉方法
                default:
                    System.out.println("不存在该命令!!");
                    break;
            }
        }
    }

    private static void scoreMovie() {
        // 1、查询当前登录成功的用户历史购买记录,看哪些电影是它可以评分的。
        Customer c = (Customer) loginUser;
        Map<String, Boolean> movies = c.getBuyMovies();
        if(movies.size() == 0 ){
            System.out.println("当前您没有看过电影,不能评价!");
            return;
        }

        // 买过了 ,看哪些电影是它可以评分的。
        movies.forEach((name, flag) -> {
            if(flag){
                System.out.println(name +"此电影已评价");
            }else {
                System.out.println("请您对:" + name +"进行打分(0-10):");
                double score = Double.valueOf(SYS_SC.nextLine());

                // 先根据电影名称拿到评分数据
                List<Double> scores = MOVIES_SCORE.get(name); // MOVIES_SCORE = [ 名称=[10] , ... ]
                if(scores == null){
                    // 说明此电影是第一次评价
                    scores = new ArrayList<>();
                    scores.add(score);
                    MOVIES_SCORE.put(name , scores);
                }else {
                    scores.add(score);
                }

                movies.put(name, true);
            }
        });
    }

    /**
      用户购票功能  ALL_MOVIES = {b1=[p1,p2,p3,..] , b2=[p2,p3,...]}
     */
    private static void buyMovie() {
        showAllMovies();
        System.out.println("=============用户购票功能=================");
        while (true) {
            System.out.println("请您输入需要买票的门店:");
            String shopName = SYS_SC.nextLine();
            // 1、查询是否存在该商家。
            Business business = getBusinessByShopName(shopName);
            if(business == null){
                System.out.println("对不起,没有该店铺!请确认");
            }else {
                // 2、此商家全部的排片
                List<Movie> movies = ALL_MOVIES.get(business);
                // 3、判断是否存在上映的电影
                if(movies.size() > 0) {
                    // 4、开始进行选片购买
                    while (true) {
                        System.out.println("请您输入需要购买电影名称:");
                        String movieName = SYS_SC.nextLine();
                        // 去当前商家下,查询该电影对象。
                        Movie movie = getMovieByShopAndName(business, movieName);
                        if(movie != null){
                            // 开始购买
                            while (true) {
                                System.out.println("请您输入要购买的电影票数:");
                                String number = SYS_SC.nextLine();
                                int buyNumber = Integer.valueOf(number);
                                // 判断电影是否购票
                                if(movie.getNumber() >= buyNumber){
                                    // 可以购买了
                                    // 当前需要花费的金额
                                    double money = BigDecimal.valueOf(movie.getPrice()).multiply(BigDecimal.valueOf(buyNumber))
                                            .doubleValue();
                                    if(loginUser.getMoney() >= money){
                                        // 终于可以买票了
                                        System.out.println("您成功购买了"+ movie.getName() + buyNumber +
                                                 "张票!总金额是:" + money);
                                        // 更新自己的金额 更新商家的金额
                                        loginUser.setMoney(loginUser.getMoney() - money);
                                        business.setMoney(business.getMoney() + money);
                                        movie.setNumber(movie.getNumber() -  buyNumber);

                                        Customer c = (Customer) loginUser;
                                        // 记录购买电影的信息
                                        // 第一个参数是购买的电影,第二个参数是没有评价的标记!
                                        c.getBuyMovies().put(movie.getName(), false);

                                        return;// 结束方法
                                    }else {
                                        // 钱不够!
                                        System.out.println("是否继续~~");
                                        System.out.println("是否继续买票?y/n");
                                        String command = SYS_SC.nextLine();
                                        switch (command) {
                                            case "y":
                                                break;
                                            default:
                                                System.out.println("好的!");
                                                return;
                                        }
                                    }
                                }else {
                                    // 票数不够
                                    System.out.println("您当前最多可以购买:" + movie.getNumber());
                                    System.out.println("是否继续买票?y/n");
                                    String command = SYS_SC.nextLine();
                                    switch (command) {
                                        case "y":
                                            break;
                                        default:
                                            System.out.println("好的!");
                                            return;
                                    }
                                }
                            }

                        }else {
                            System.out.println("电影名称有毛病~~");
                        }
                    }

                }else {
                    System.out.println("该电影院关门了~~~");
                    System.out.println("是否继续买票?y/n");
                    String command = SYS_SC.nextLine();
                    switch (command) {
                        case "y":
                            break;
                        default:
                            System.out.println("好的!");
                            return;
                    }
                }
            }
        }
    }

    public static Movie getMovieByShopAndName(Business business , String name){
        List<Movie> movies = ALL_MOVIES.get(business);
        for (Movie movie : movies) {
            if(movie.getName().contains(name)){
                return movie;
            }
        }
        return null;
    }

    /**
       根据商家店铺名称查询商家对象
     * @return
     */
    public static Business getBusinessByShopName(String shopName){
        Set<Business> businesses = ALL_MOVIES.keySet();
        for (Business business : businesses) {
            if(business.getShopName().equals(shopName)){
                return  business;
            }
        }
        return null;
    }

    /**
      用户功能:展示全部商家和其排片信息
     */
    private static void showAllMovies() {
        System.out.println("=============展示全部商家排片信息=================");
        ALL_MOVIES.forEach((business, movies) -> {
            System.out.println(business.getShopName() + "\t\t电话:" + business.getPhone() + "\t\t地址:" + business.getAddress());
            System.out.println("\t\t\t片名\t\t\t主演\t\t时长\t\t评分\t\t票价\t\t余票数量\t\t放映时间");
            for (Movie movie : movies) {
                System.out.println("\t\t\t" + movie.getName()+"\t\t\t" + movie.getActor()+ "\t\t" + movie.getTime()
                        + "\t\t" + movie.getScore() + "\t\t" + movie.getPrice() + "\t\t" + movie.getNumber() + "\t\t"
                        +   sdf.format(movie.getStartTime()));
            }
        });
    }

    public static User getUserByLoginName(String loginName){
        for (User user : ALL_USERS) {
            // 判断这个用户的登录名称是否是我们想要的
            if(user.getLoginName().equals(loginName)){
                return user;
            }
        }
        return null; // 查询此用户登录名称
    }
}

学习记录9


1 File

1.1 File类概述

  • File类在包java.io.File下,代表操作系统的文件对象(文件、文件夹)。
  • File类提供了诸如:定位文件,获取文件本身的信息、删除文件、创建文件(文件夹)等功能。

绝对路径和相对路径

在Window中:

  • 绝对路径:从盘符开始。
  • 相对路径:不带盘符,默认直接到当前工程下的目录寻找文件。

在Linux中,这是一个非常重要的概念:

  • 绝对路径是从目录树的树根“/”目录开始往下直至到达文件所经过的所有节点目录。下级目录接在上级目录后面用“/”隔开。注意:绝对路径都是从“/”开始的,所以第一个字符一定是“/”。
  • 相对路径是指目标目录相对于当前目录的位置。如果不在当前目录下,则需要使用两个特殊目录“.”和“../”了。目录“.”指向当前目录,而目录“../”指向上级目录。
package com.day09.FileDemo;

import java.io.File;

public class FileDemo01 {
    public static void main(String[] args) {
        //创建一个file对象
        //三种以绝对路径创建的文件对象
        File file = new File("F:\\Learing\\The_third_week\\xxx");
        File file1 = new File("F:/Learing/The_third_week/xxx");
        File file2 = new File("F:" + File.separator + "Learing" + File.separator + "The_third_week" + File.separator + "xxx");
        //获取file文件字节大小
        long l = file.length();
        System.out.println(l);//55
        System.out.println(file1.length());//55
        System.out.println(file2.length());//55

        //使用相对路径定位文件,相对于在同一个工程下
        File file3 = new File("xxx");
        System.out.println(file3.length());//55

        //File创建对象可以是文件也可以是文件夹
        File file4 = new File("F:/Learing");
        System.out.println(file4.length());//8192
        System.out.println(file4.exists());//true
    }
}

1.2 File类常用API

File类判断文件类型、获取文件信息功能。

image-20220730093139714

package com.day09.FileDemo;

import java.io.File;
import java.text.SimpleDateFormat;

public class FileDemo02 {
    public static void main(String[] args) {
        File file = new File("F:\\Learing\\The_third_week\\xxx");
        File file1 = new File("F:/Learing");
        //判断是文件还是文件夹
        System.out.println(file.isDirectory());
        System.out.println(file1.isDirectory());
        System.out.println(file.isFile());
        System.out.println(file1.isFile());
        //判断文件是否存在
        System.out.println(file.exists());
        System.out.println(file1.exists());
        //得到文件的绝对路径
        String fileAbsolutePath = file.getAbsolutePath();
        System.out.println(fileAbsolutePath);
        //获取路径名
        String path = file.getPath();
        System.out.println(path);
        //获取文件名
        String name = file.getName();
        System.out.println(name);
        //最后修改时间
        long l = file.lastModified();
        System.out.println(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(l));

    }
}

image-20220730093722223

package com.day09.FileDemo;

import java.io.File;
import java.io.IOException;

public class FileDemo03 {
    public static void main(String[] args) throws IOException {
        //创建一个新文件
        File file = new File("F:\\Learing\\The_third_week\\yyy");
        System.out.println(file.createNewFile());
        //创建一级目录
        File file1 = new File("F:\\Learing\\The_third_week1");
        System.out.println(file1.mkdir());
        //创建多级文件夹
        File file2 = new File("F:\\Learing\\The_third_week\\Test");
        System.out.println(file2.mkdirs());
        //删除文件 只能删除非空文件夹
        System.out.println(file1.delete());
        System.out.println(file2.delete());
        System.out.println(file.delete());
    }
}

1.3 File类的遍历方法

image-20220730103238509

listFiles方法注意事项:

  • 当调用者不存在时,返回null。
  • 当调用者是一个文件时,返回null。
  • 当调用者是一个空文件夹时,返回一个长度为0的数组。
  • 当调用者是一个有内容的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回。
  • 当调用者是一个有隐藏文件的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回,包含隐藏内容。
package com.day09.FileDemo;

import java.io.File;

public class FileDemo04 {
    public static void main(String[] args) {
        //返回file名称数组
        File file = new File("F:\\Learing\\The_third_week");
        String[] lists = file.list();
        for (String list : lists) {
            System.out.println(list);
        }
        /*. idea
            pom.xml
            src
            target
            xxx*/
        //返回文件对象
        File[] files = file.listFiles();
        for (File file1 : files) {
            System.out.println(file1);
        }
 /*
    *   F:\Learing\The_third_week\.idea
        F:\Learing\The_third_week\pom.xml
        F:\Learing\The_third_week\src
        F:\Learing\The_third_week\target
        F:\Learing\The_third_week\xxx
    * */
        //当调用者不存在返回Null
        File file2 = new File("F:\\Learing\\The_third_week1");
        File[] files1 = file2.listFiles();
        System.out.println(files1);//null

        //当调用者是文件时,返回null
        File file3 = new File("F:\\Learing\\The_third_week\\xxx");
        File[] files2 = file3.listFiles();
        System.out.println(files2);//null

        //当调用者时空文件,返回空数组
        File file1 = new File("F:\\Learing\\The_third_week\\src\\main\\java\\com\\day10");
        File[] files3 = file1.listFiles();
        for (File file4 : files3) {
            System.out.println(file4);
        }
    }
}

2 方法递归

2.1 规律化递归

  • 方法直接调用自己或者间接调用自己的形式称为方法递归( recursion)。
  • 递归做为一种算法在程序设计语言中广泛应用。

递归解决问题的思路:

  • 把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。

递归算法三要素大体可以总结为:

  • 递归的公式: f(n)= f(n-1)* n。
  • 递归的终结点: f(1)。
  • 递归的方向必须走向终结点。

需求一:求阶乘!

首先求出数学公式:

$$ f(1) = 1; f(2) = 1*2; f(3)=1*2*3; f(4)=1*2*3*4;...... $$

故可推出公式:

$$ f(x)=f(x-1)*x $$

递归的终结点:

$$ f(1) =1 $$

package com.day09.recursionDemo;

public class recursionDemo01 {
    public static void main(String[] args) {
        int i = factorial(3);
        System.out.println(i);
    }
    //求某个数的阶乘
    public static int factorial(int num){
        if (num==1){
            return 1;
        }else {
            return factorial(num-1)*num;
        }
    }
}

使用debug运行情况:

debug中的方法调用是通过下面该图1,2,3,4步进行调用的。

image-20220730171535882

需求二:求1~n的和

首先求出数学公式:

$$ f(1) = 1; f(2) = 1+2; f(3)=1+2+3; f(4)=1+2+3+4;...... $$

故可推出公式:

$$ f(x)=f(x-1)+x $$

递归的终结点:

$$ f(1) =1 $$

package com.day09.recursionDemo;

public class recursionDemo01 {
    public static void main(String[] args) {
        int i = factorial(6);
        System.out.println(i);
    }
    //求1~n的和
    public static int factorial(int num){
        if (num==1){
            return 1;
        }else {
            return factorial(num-1)+num;
        }
    }
}
//21

需求三:猴子吃桃问题

首先需要判断出公式:

$$ f(x) - f(x)/2 -1 = f(x+1) $$

公式两边同乘2,化简即可得到:

$$ f(x) = 2f(x+1) + 2 $$

递归的终结点:

$$ f(10) = 1 $$

package com.day09.recursionDemo;

public class Monkey_eating_peach {
    public static void main(String[] args) {
        System.out.println("猴子第一天摘了"+num(1)+"个桃。");
    }
    public static int num(int i){
        if (i == 10){
            return 1;
        }else
            return 2 * num(i+1) + 2;
    }
}
//猴子第一天摘了1534个桃。

2.2 非规律化递归

需求一:在D盘寻找:copytranslator.exe 文件

package com.day09.recursionDemo;

import java.io.File;

public class Find_files_recursively {
    //在D盘寻找:copytranslator.exe
    public static void main(String[] args) {
        searchFiles(new File("D:/"),"copytranslator.exe");
    }
    public static void searchFiles(File dir,String filename){
        //判断传进来的目录是否是一级目录
        if (dir != null && dir.isDirectory()){
            File[] files = dir.listFiles();
            if (files != null && files.length>0){
                for (File file : files) {
                    //看是否是文件
                    if (file.isFile()){
                        if (file.getName().contains(filename)){
                            System.out.println("找到目标文件"+file.getAbsolutePath());
                        }
                    }else {
                        searchFiles(file,filename);
                    }
                }
            }
        }else {
            System.out.println("对不起,传入的文件地址不正确。");
        }
    }
}

需求二:啤酒2元1瓶,4个盖子可以换一瓶,2个空瓶可以换一瓶,请问10元钱可以喝多少瓶酒,剩余多少空瓶和盖子。

package com.day09.recursionDemo;

import java.util.Arrays;

public class RecursionDemo02 {

    // 定义一个静态的成员变量用于存储可以买的酒数量
    public static int totalNumber; // 总数量
    public static int lastBottleNumber; // 记录每次剩余的瓶子个数
    public static int lastCoverNumber; // 记录每次剩余的盖子个数


    public static void main(String[] args) {
        // 1、拿钱买酒
        buy(10);
        System.out.println("总数:" + totalNumber);
        System.out.println("剩余盖子数:" + lastCoverNumber);
        System.out.println("剩余瓶子数:" + lastBottleNumber);
    }

    public static void buy(int money){
        // 2、看可以立马买多少瓶
        int buyNumber = money / 2; // 5
        totalNumber += buyNumber;

        // 3、把盖子 和瓶子换算成钱
        // 统计本轮总的盖子数  和 瓶子数
        int coverNumber = lastCoverNumber + buyNumber;
        int bottleNumber = lastBottleNumber + buyNumber;

        // 统计可以换算的钱
        int allMoney = 0;
        if(coverNumber >= 4){
            allMoney += (coverNumber / 4) * 2;
        }
        lastCoverNumber = coverNumber % 4;

        if(bottleNumber >= 2){
            allMoney += (bottleNumber / 2) * 2;
        }
        lastBottleNumber = bottleNumber % 2;

        if(allMoney >= 2){
            buy(allMoney);
        }

        Integer[] arr2 = new Integer[]{11, 22, 33};
        Arrays.sort(arr2);
    }
}

3 字符集

ASCII字符集:

  • ASCII(American Standard Code for Information Interchange,美国信息交换标准代码):包括了数字、英文、符号。
  • ASCII使用1个字节存储一 个字符,一个字节是8位,总共可以表示128个字符信息,对于英文,数字来说是够用的。

GBK:

  • window系统默认的码表。 兼容ASCII码表,也包含了几万个汉字,并支持繁体汉字以及部分日韩文字。
  • 注意: GBK是中国的码表,一个中文以两个字节的形式存储。但不包含世界上所有国家的文字。

Unicode码表:

  • unicode (又称统一码、万国码、单一码)是计算机科学领域里的一项业界字符编码标准。
  • 容纳世界上大多数国家的所有常见文字和符号。
  • 由于Unicode会先通过UTF-8, UTF-16, 以及UTF-32的编码成二进制后再存储到计算机,其中最为常见的就是
    UTF-8。
  • Unicode是万国码,以UTF-8编码后一个中文一般以三个字节的形式存储。
  • UTF-8也要兼容ASCII编码表。
  • 技术人员都应该使用UTF-8的字符集编码。
  • 编码前和编码后的字符集需要一致, 否则会出现中文乱码。

image-20220730202014725

package com.day09.character_set;

import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

public class Test {
    public static void main(String[] args) throws UnsupportedEncodingException {
        //编码:把文字转成字节
        String name = "努力少年!123";
        byte[] bytes = name.getBytes();
        System.out.println(bytes.length);
        String string = Arrays.toString(bytes);
        System.out.println(string);
        //18
        //[-27, -118, -86, -27, -118, -101, -27, -80, -111, -27, -71, -76, -17, -68, -127, 49, 50, 51]
        byte[] bytes1 = name.getBytes("GBK");
        System.out.println(bytes1.length);
        String string1 = Arrays.toString(bytes1);
        System.out.println(string1);
        //13
        //[-59, -84, -63, -90, -55, -39, -60, -22, -93, -95, 49, 50, 51]

        //解码:把字节转换成对应的中文
        String s = new String(bytes);
        System.out.println(s);
        String s1 = new String(bytes1, "GBK");
        System.out.println(s1);
    }
}

总结:

字符串常见的字符底层组成是什么样的?

  • 英文和数字等在任何国家的字符集中都占1个字节。
  • GBK字符中一个中文字符占2个字节。
  • UTF-8编码中一个中文一般占3个字节。

4 字节流

IO流概述:

  • I表示intput,是数据从硬盘文件读入到内存的过程,称之输入,负责读。
  • O表示output,是内存程序的数据从内存到写出到硬盘文件的过程,称之输出,负责写。

IO流分类:

  • 字节输入流:以内存为基准,来自磁盘文件/网络中的数据以字节的形式读入到内存中去的流称为字节输入流。
  • 字节输出流:以内存为基准,把内存中的数据以字节写出到磁盘文件或者网络中去的流称为字节输出流。
  • 字符输入流:以内存为基准,来自磁盘文件/网络中的数据以字符的形式读入到内存中去的流称为字符输入流。
  • 字符输出流:以内存为基准,把内存中的数据以字符写出到磁盘文件或者网络介质中去的流称为字符输出流。

IO流体系:

image-20220731222018669

4.1 FileInputStream字节输入流

构造器描述
FileInputStream(File file)通过打开与实际文件的连接来创建 FileInputStream ,该文件由文件系统中的 File对象 file命名。
FileInputStream(String name)通过打开与实际文件的连接来创建 FileInputStream ,该文件由文件系统中的路径名 name命名。
变量和类型方法描述
voidclose()关闭此文件输入流并释放与该流关联的所有系统资源。
intread()从此输入流中读取一个字节的数据。
intread(byte[] b)从此输入流 b.length最多 b.length字节的数据读 b.length字节数组。
intread(byte[] b, int off, int len)从此输入流 len最多 len字节的数据读入一个字节数组。
package com.day09.IODemo;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class FileInputStreamDemo {
    public static void main(String[] args) throws IOException {
        //创建一个字节输入流 经典多态写法
        InputStream inputStream = new FileInputStream("yyy");
        //读取一个字节并返回
        int read = inputStream.read();
        System.out.println((char) read);//a
        System.out.println((char) inputStream.read());//b
        System.out.println((char) inputStream.read());//c
        System.out.println((char) inputStream.read());//d
        System.out.println((char) inputStream.read());//1
        System.out.println(inputStream.read());//-1
        inputStream.close();
        //即-1代表读取结束
        //使用循环读取文件中的所有字节
        InputStream inputStream1 = new FileInputStream("yyy");
        int b;
        while ((b=inputStream1.read())!= -1){
            System.out.println((char) b);
        }
        inputStream1.close();
    }
}

总结:

每次读取一个字节存在什么问题?

  • 性能较慢。
  • 读取中文字符输出无法避免乱码问题。
package com.day09.IODemo;

import java.io.*;

public class FileInputStreamDemo03 {
    public static void main(String[] args) throws IOException {
        InputStream inputStream = new FileInputStream(new File("yyy"));
        //使用字节数组来接收 每三个字节一块进行接收
        byte[] bytes = new byte[3];
        int read = inputStream.read(bytes);
        String s = new String(bytes);
        System.out.println(s);//abc
        //使用字节数组来接收
        byte[] bytes1 = new byte[3];
        int read1 = inputStream.read(bytes1);
        String s1 = new String(bytes1);
        System.out.println(s1);//d1 这里出现了乱码现象
        inputStream.close();
        //使用循环进行读取
        InputStream inputStream1 = new FileInputStream(new File("yyy"));
        byte[] bytes2 = new byte[3];
        int led; //  记录每次读取的字节数
        while ((led = inputStream1.read(bytes2))!= -1){
            System.out.print(new String(bytes2,0,led));
        }
        //abcd1
         inputStream.close();
    }
}

总结:

每次读取一个字节数组存在什么问题?

  • 读取的性能得到了提升。
  • 读取中文字符输出无法避免乱码问题。
package com.day09.IODemo;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;

public class FileInputStreamDemo04 {
    //使用字节输入流一次性读取完全部字节。从而解决乱码问题
    public static void main(String[] args) throws Exception {
        File file = new File("xxx");
        InputStream inputStream = new FileInputStream(file);
        //2.定义一个字节数组于文件的大小一致
        byte[] bytes = new byte[(int) file.length()];
        int read = inputStream.read(bytes);
        System.out.println(new String(bytes));
        inputStream.close();
        //JDK9之后出现的新API
        InputStream inputStream1 = new FileInputStream(file);
        byte[] bytes1 = inputStream1.readAllBytes();
        System.out.println(new String(bytes1));
        inputStream.close();
    }
}

总结:

如何使用字节输入流读取中文内容输出不乱码呢?

  • 一次性读取完全部字节。
  • 可以定义与文件一样大的字节数组读取,也可以使用官方API。

4.2 FileOutputStream字节输出流

构造器描述
FileOutputStream(File file)创建文件输出流以写入由指定的 File对象表示的文件。
FileOutputStream(File file, boolean append)创建文件输出流以写入由指定的 File对象表示的文件。
FileOutputStream(String name)创建文件输出流以写入具有指定名称的文件。
FileOutputStream(String name, boolean append)创建文件输出流以写入具有指定名称的文件。
变量和类型方法描述
voidclose()关闭此文件输出流并释放与此流关联的所有系统资源。
voidwrite(byte[] b)将指定字节数组中的 b.length字节写入此文件输出流。
voidwrite(byte[] b, int off, int len)将从偏移量 off开始的指定字节数组中的 len字节写入此文件输出流。
voidwrite(int b)将指定的字节写入此文件输出流。
package com.day09.IODemo;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;

public class FileOutputStreamDemo {
    public static void main(String[] args) throws Exception {
        //经典多态写法
        OutputStream outputStream = new FileOutputStream("1.txt",true);//写true实现管道追加,即文件不进行覆盖,而是进行添加字节
        //写入一个字节数组
        byte[] bytes = "你好啊。我叫zhenxi,很高兴认识你。".getBytes();
        outputStream.write(bytes);
        //写入一个字节进去
        outputStream.write('a');
        outputStream.write('b');
        //写数据必须要刷新数据
        outputStream.flush();
        //关闭流自动刷新数据
        outputStream.close();
    }
}

4.3 经典案例:文件拷贝

package com.day09.IODemo;

import java.io.*;

public class File_copy {
    public static void main(String[] args) throws Exception {
        InputStream inputStream = new FileInputStream(new File("C:\\Users\\77339\\Desktop\\最近任务\\javakfscss_jb51\\java开发手册-嵩山版\\java开发手册-嵩山版.pdf"));
        byte[] bytes = inputStream.readAllBytes();
        OutputStream outputStream = new FileOutputStream(new File("xx.pdf"));
        outputStream.write(bytes);
        outputStream.close();
        inputStream.close();
    }
}

5 资源释放

try-catch-finally

  • finally:在异常处理时提供finally块来执行所有清除操作,比如说IO流中的释放资源。
  • 特点:被finally控制的语句最终一定会执行,除非JVM退出。
  • 异常处理标准格式: try...catch...finally
package com.day09.IODemo;

import java.io.*;

public class Try_finally {
    public static void main(String[] args) {
        InputStream is = null;
        OutputStream os = null;
        try {
            is = new FileInputStream(new File("D:\\杂物\\本科杂物\\毕设\\系统演示视频.mp4"));
            os = new FileOutputStream("111.mp4");
            byte[] bytes = is.readAllBytes();
            os.write(bytes);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }
}

6 字符流

6.1 FileReader字符输入流

构造器描述
FileReader(File file)使用平台 FileReader ,在 File读取时创建一个新的 FileReader
FileReader(File file, Charset charset)创建一个新的FileReader ,给出File读取和charset 。
FileReader(String fileName)使用平台 default charset创建一个新的 FileReader ,给定要读取的文件的名称 。
FileReader(String fileName, Charset charset)给定要读取的文件的名称和FileReader ,创建一个新的FileReader
变量和类型方法描述
StringgetEncoding()返回此流使用的字符编码的名称。
intread()读一个字符。
intread(char[] cbuf, int offset, int length)将字符读入数组的一部分。
booleanready()判断此流是否可以读取。
package com.day09.IODemo;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

public class FileReaderDemo01 {
    public static void main(String[] args) throws IOException {
        Reader fr = new FileReader("xxx");
        //读取一个字符
        int read = fr.read();
        System.out.println((char) read); //你
        //使用循环进行读取字符
        int led;
        while ((led = fr.read())!=-1){
            System.out.print((char) led);
        }
        fr.close();
        //使用字符数组进行读取字符
        Reader fr1 = new FileReader("1.txt");
        char[] chars = new char[1024];
        int len;
        while ((len =fr1.read(chars))!=-1){
            String s = new String(chars, 0, len);
            System.out.println(s);
        }
        fr1.close();
    }
}

6.2 FileWriter字符输出流

构造器描述
FileWriter(File file)File写一个 FileWriter ,使用平台的 default charset
FileWriter(File file, boolean append)在给出要写入的 FileWriter下构造 File ,并使用平台的 default charset构造一个布尔值,指示是否附加写入的数据。
FileWriter(File file, Charset charset)构造一个FileWriter给予File编写和charset。
FileWriter(File file, Charset charset, boolean append)构造FileWriter给出File写入, charset和一个布尔值,指示是否附加写入的数据。
FileWriter(String fileName)构造一个 FileWriter给出文件名,使用平台的 default charset
FileWriter(String fileName, boolean append)使用平台的 default charset构造一个 FileWriter给定一个文件名和一个布尔值,指示是否附加写入的数据。
FileWriter(String fileName, Charset charset)构造一个FileWriter给出文件名和charset。
FileWriter(String fileName, Charset charset, boolean append)构造一个FileWriter给定一个文件名, charset和一个布尔值,指示是否附加写入的数据。
变量和类型方法描述
voidflush()刷新流。
StringgetEncoding()返回此流使用的字符编码的名称。
voidwrite(char[] cbuf, int off, int len)写一个字符数组的一部分。
voidwrite(int c)写一个字符。
voidwrite(String str, int off, int len)写一个字符串的一部分。
package com.day09.IODemo;

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class FileWriterDemo {
    public static void main(String[] args) throws Exception {
        Writer fw = new FileWriter("222.txt",true);
        //写入一个字符
        fw.write(11);
        fw.write('a');
        fw.write("你");
        //写入一个字符数组进去
        char[] chars = "你好,我是zhenxi。".toCharArray();
        fw.write(chars);
        //直接写入字符串
        fw.write("加油!继续努力!");
        fw.close();
    }
}

学习记录10


1 缓冲流

缓冲流概述

  • 缓冲流也称为高效流、或者高级流。之前学习的字节流可以称为原始流。
  • 作用:缓冲流自带缓冲区、可以提高原始字节流、字符流读写数据的性能。

缓冲流的作用

缓冲流自带缓冲区、可以提高原始字节流、字符流读写数据的性能。

缓冲流分类

  • 字节缓冲流

    字节缓冲输入流:BufferedInputStream

    字节缓冲输出流:BufferedOutputStream

  • 字符缓冲流

    字符缓冲输入流:BufferedReader

    字符缓冲输出流:BufferedWriter

image-20220731174147227

1.1 字节缓冲流

构造器描述
BufferedInputStream(InputStream in)创建一个 BufferedInputStream并保存其参数,即输入流 in ,供以后使用。
BufferedInputStream(InputStream in, int size)创建具有指定缓冲区大小的 BufferedInputStream ,并保存其参数(输入流 in )供以后使用。
构造器描述
BufferedOutputStream(OutputStream out)创建新的缓冲输出流以将数据写入指定的基础输出流。
BufferedOutputStream(OutputStream out, int size)创建新的缓冲输出流,以使用指定的缓冲区大小将数据写入指定的基础输出流。

需求一:使用字节缓冲流实现文件复制。

package com.day10.Buffered;

import java.io.*;

public class BufferedDemo {
    public static void main(String[] args) throws Exception {
        InputStream inputStream = new FileInputStream(new File("D:\\Java\\Java文档\\java开发手册-嵩山版\\java开发手册-嵩山版.pdf"));
        BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
        byte[] bytes = bufferedInputStream.readAllBytes();
        OutputStream outputStream = new FileOutputStream(new File("xx.pdf"));
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
        bufferedOutputStream.write(bytes);
        bufferedOutputStream.close();
        inputStream.close();
    }
}

需求二:分别使用低级字节流和高级字节缓冲流拷贝大视频,记录耗时。

分析:

①使用低级的字节流按照一个一个字节的形式复制文件。

②使用低级的字节流按照一个一个字节数组的形式复制文件。

③使用高级的缓冲字节流按照一个一个字节的形式复制文件。

④使用高级的缓冲字节流按照一个一个字节数组的形式复制文件。

package com.day10.Buffered;

import java.io.*;

public class BufferedDemo02 {
    public static final String FILE_PATH = "D:\\杂物\\本科杂物\\毕设\\系统演示视频.mp4";

    public static void main(String[] args) {
           // copy1(); //几乎没动静
            copy2(); //1s左右
            copy3();//6s左右
            copy4();//1s以下

    }
    //使用高级缓冲流采用字符数组进行读取
    private static void copy4() {
        long l = System.currentTimeMillis();
        BufferedInputStream bufferedInputStream = null;
        BufferedOutputStream bufferedOutputStream = null;
        try {
            InputStream inputStream = new FileInputStream(FILE_PATH);
            bufferedInputStream = new BufferedInputStream(inputStream);
            OutputStream outputStream = new FileOutputStream("copy1.mp4");
            bufferedOutputStream = new BufferedOutputStream(outputStream);
            byte[] bytes = bufferedInputStream.readAllBytes();
            bufferedOutputStream.write(bytes);
        }
        catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                bufferedInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                bufferedOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        long l1 = System.currentTimeMillis();
        System.out.println("copy4所消耗的时间为:"+(l1-l)/1000);
    }

    //使用高级缓冲流一个字节一个读取
    private static void copy3() {
        long l = System.currentTimeMillis();
        BufferedInputStream bufferedInputStream = null;
        BufferedOutputStream bufferedOutputStream = null;
        try {
            InputStream inputStream = new FileInputStream(FILE_PATH);
            bufferedInputStream = new BufferedInputStream(inputStream);
            OutputStream outputStream = new FileOutputStream("copy1.mp4");
            bufferedOutputStream = new BufferedOutputStream(outputStream);
            int len;
            while ((len = bufferedInputStream.read())!= -1){
                try {
                    bufferedOutputStream.write(len);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                bufferedInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                bufferedOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        long l1 = System.currentTimeMillis();
        System.out.println("copy3所消耗的时间为:"+(l1-l)/1000);
    }


    //使用低级流的字节数组
    private static void copy2() {
        long l = System.currentTimeMillis();
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            inputStream = new FileInputStream(FILE_PATH);
            outputStream = new FileOutputStream("copy02.mp4");
            byte[] bytes = inputStream.readAllBytes();
            outputStream.write(bytes);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        long l1 = System.currentTimeMillis();
        System.out.println("copy2所消耗的时间为:"+(l1-l)/1000);
    }
    //使用低阶字节流 一个一个读
    private static void copy1() {
        long l = System.currentTimeMillis();
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
             inputStream = new FileInputStream(FILE_PATH);
             outputStream = new FileOutputStream("copy1.mp4");
            int len;
            while ((len = inputStream.read())!= -1){
                try {
                    outputStream.write(len);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        long l1 = System.currentTimeMillis();
        System.out.println("copy1所消耗的时间为:"+(l1-l)/1000);
    }
}

1.2 字符缓冲流

构造器描述
BufferedReader(Reader in)创建使用默认大小的输入缓冲区的缓冲字符输入流。
BufferedReader(Reader in, int sz)创建使用指定大小的输入缓冲区的缓冲字符输入流。
变量和类型方法描述
Stream<String>lines()返回 Stream ,其元素是从此 BufferedReader读取的行。
intread()读一个字符。
intread(char[] cbuf, int off, int len)将字符读入数组的一部分。
StringreadLine()读一行文字。
booleanready()判断此流是否可以读取。
voidreset()将流重置为最新标记。
longskip(long n)跳过字符。
package com.day10.Buffered;

import java.io.*;

public class BufferedReaderDemo {
    public static void main(String[] args) {
        try {
            Reader reader = new FileReader("222.txt");
            BufferedReader bufferedReader = new BufferedReader(reader);
            //FileReader经典方法
            /*char[] chars = new char[1024];
            int len;
            while ((len = bufferedReader.read(chars))!=-1){
                System.out.println(new String(chars, 0, len));
            }*/
            //readLine方法
            String line;
            while ((line = bufferedReader.readLine())!=null){
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
构造器描述
BufferedWriter(Writer out)创建使用默认大小的输出缓冲区的缓冲字符输出流。
BufferedWriter(Writer out, int sz)创建一个使用给定大小的输出缓冲区的新缓冲字符输出流。
变量和类型方法描述
voidflush()刷新流。
voidnewLine()写一个行分隔符。
voidwrite(char[] cbuf, int off, int len)写一个字符数组的一部分。
voidwrite(int c)写一个字符。
voidwrite(String s, int off, int len)写一个字符串的一部分。
package com.day10.Buffered;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class BufferedWriterDemo {
    public static void main(String[] args) throws IOException {
        Writer fw = new FileWriter("222.txt",true);
        BufferedWriter bw = new BufferedWriter(fw);
        //写入一个字符
        bw.write(98);
        bw.write('a');
        bw.write("你");
        bw.newLine();
        //写入一个字符数组进去
        char[] chars = "你好,我是zhenxi。".toCharArray();
        bw.write(chars);
        bw.newLine();
        //直接写入字符串
        bw.write("加油!继续努力!");
        bw.close();
        bw.newLine();
    }
}

需求:

把《出师表》的文章顺序进行恢复到一个新文件中。

分析:

①定义一个缓存字符输入流管道与源文件接通。
②定义一个List集合存储读取的每行数据。
③定义一个循环按照行读取数据,存入到List集合中去。
④对List集合中的每行数据按照首字符编号升序排序。
⑤定义一个缓存字符输出管道与目标文件接通。

package com.day10.Buffered;

import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class BufferTest {
    public static void main(String[] args) {
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            Reader fileReader = new FileReader("csb.txt");
            br = new BufferedReader(fileReader);
            bw = new BufferedWriter(new FileWriter("new_csb.txt"));
            List<String> list = new ArrayList<>();
            String line;
            while ((line = br.readLine())!= null){
                list.add(line);
            }
            System.out.println(list);
            Collections.sort(list);
            System.out.println(list);
            for (String s : list) {
                bw.write(s);
                bw.newLine();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                bw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

2 转换流

问题引出:当使用输入流读取文件时默认读取UTF-8的编码。若读取一个GBK的编码则会乱码。

package com.day10.transform_stream;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

public class CharSetDemo {
    public static void main(String[] args) {
        try {
            Reader reader = new FileReader("C:\\Users\\77339\\Desktop\\111.txt");
            BufferedReader bufferedReader = new BufferedReader(reader);
            String line;
            while ((line = bufferedReader.readLine())!=null){
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
/***
运行结果:
����������������GBK�ı��롣
/
//文件内容:啊啊啊啊啊,这是GBK的编码。

InputStreamReader输入转换流:

构造器描述
InputStreamReader(InputStream in, String charsetName)创建一个使用指定charset的InputStreamReader。
变量和类型方法描述
StringgetEncoding()返回此流使用的字符编码的名称。
intread()读一个字符。
intread(char[] cbuf, int offset, int length)将字符读入数组的一部分。
booleanready()判断此流是否可以读取。
package com.day10.transform_stream;

import java.io.*;

public class CharSetDemo02 {
    public static void main(String[] args) {
        try {
            FileInputStream fi = new FileInputStream("C:\\Users\\77339\\Desktop\\111.txt");
            //使用转换流
            InputStreamReader inputStreamReader = new InputStreamReader(fi,"GBK");
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

            String line;
            while ((line = bufferedReader.readLine())!=null){
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

OutputStreamWriter输出转换流:

构造器描述
OutputStreamWriter(OutputStream out)创建使用默认字符编码的OutputStreamWriter。
OutputStreamWriter(OutputStream out, String charsetName)创建使用指定charset的OutputStreamWriter。
OutputStreamWriter(OutputStream out, CharsetEncoder enc)创建使用给定charset编码器的OutputStreamWriter。
变量和类型方法描述
voidflush()刷新流。
StringgetEncoding()返回此流使用的字符编码的名称。
voidwrite(char[] cbuf, int off, int len)写一个字符数组的一部分。
voidwrite(int c)写一个字符。
voidwrite(String str, int off, int len)写一个字符串的一部分。
package com.day10.transform_stream;

import java.io.*;

public class CharSetDemo03 {
    public static void main(String[] args) throws IOException {
        OutputStream outputStream = new FileOutputStream("charsetdemo03.txt");
        Writer writer = new OutputStreamWriter(outputStream, "GBK");
        BufferedWriter bw = new BufferedWriter(writer);
        //写入一个字符
        bw.write(98);
        bw.write('a');
        bw.write("你");
        bw.newLine();
        //写入一个字符数组进去
        char[] chars = "你好,我是zhenxi。".toCharArray();
        bw.write(chars);
        bw.newLine();
        //直接写入字符串
        bw.write("加油!继续努力!");
        bw.close();
    }
}

3 序列化

3.1 对象序列化

  • 作用:以内存为基准,把内存中的对象存储到磁盘文件中去,称为对象序列化。
  • 使用到的流是对象字节输出流: ObjectOutputStream。
变量构造器描述
publicObjectOutputStream(OutputStream out)创建一个写入指定OutputStream的ObjectOutputStream。
变量和类型方法描述
voidclose()关闭流。
voidflush()刷新流。
voidwrite(byte[] buf)写一个字节数组。
voidwrite(byte[] buf, int off, int len)写一个子字节数组。
voidwriteObject(Object obj)将指定的对象写入ObjectOutputStream。

案例:序列下面student类对象。

package com.day10.Serialization;

import java.io.Serializable;
//对象要进行序列化,必须要实现该接口
public class Student implements Serializable {
    private String name;
    private String password;
    private int id;
    private char sex;

    public Student() {
    }

    public Student(String name, String password, int id, char sex) {
        this.name = name;
        this.password = password;
        this.id = id;
        this.sex = sex;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public char getSex() {
        return sex;
    }

    public void setSex(char sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", password='" + password + '\'' +
                ", id=" + id +
                ", sex=" + sex +
                '}';
    }
}
package com.day10.Serialization;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

public class SerializationDemo {
    public static void main(String[] args) throws Exception {
        Student student = new Student("zhenxi", "2022956775", 775, '男');
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Serialization.txt"));
        oos.writeObject(student);
        oos.close();
    }
}

3.2 对象反序列化

  • 使用到的流是对象字节输入流: ObjectInputStream。
  • 作用:以内存为基准,把存储到磁盘文件中去的对象数据恢复成内存中的对象,称为对象反序列化。
变量构造器描述
publicObjectInputStream(InputStream in)创建一个从指定的InputStream读取的ObjectInputStream。
变量和类型方法描述
voidclose()关闭输入流。
intread()读取一个字节的数据。
intread(byte[] buf, int off, int len)读入一个字节数组。
ObjectreadObject()从ObjectInputStream中读取一个对象。
package com.day10.Serialization;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.ObjectInputStream;

public class SerializationDemo02 {
    public static void main(String[] args) throws Exception {
        //反序列化
        ObjectInputStream oit = new ObjectInputStream(new FileInputStream("Serialization.txt"));
        Student student = (Student)oit.readObject();
        System.out.println(student);
    }
}
/**
Student{name='zhenxi', password='2022956775', id=775, sex=男}
*/

4 打印流

作用:打印流可以实现方便、高效的打印数据到文件中去。打印流一般是指: PrintStream, PrintWriter两个类。

构造器描述
PrintStream(File file)使用指定的文件创建没有自动行刷新的新打印流。
PrintStream(File file, String csn)使用指定的文件和字符集创建一个没有自动行刷新的新打印流。
PrintStream(OutputStream out)创建新的打印流。
PrintStream(String fileName)使用指定的文件名创建没有自动行刷新的新打印流。
PrintStream(String fileName, String csn)使用指定的文件名和字符集创建一个没有自动行刷新的新打印流。
package com.day10.PrintStream;

import java.io.FileNotFoundException;
import java.io.PrintStream;

public class PrintStreamDemo {
    public static void main(String[] args) throws FileNotFoundException {
        PrintStream printStream = new PrintStream("printStream.txt");
        printStream.println("打印流");
        printStream.println(111);
        printStream.println('a');
        printStream.println("包含了缓冲流");
        printStream.close();
    }
}

PrintWriter类:

构造器描述
PrintWriter(File file)使用指定的文件创建一个没有自动行刷新的新PrintWriter。
PrintWriter(File file, String csn)使用指定的文件和字符集创建一个没有自动行刷新的新PrintWriter。
PrintWriter(OutputStream out)从现有的OutputStream创建一个没有自动行刷新的新PrintWriter。
PrintWriter(Writer out)创建一个新的PrintWriter,没有自动行刷新。
PrintWriter(String fileName)使用指定的文件名创建一个没有自动行刷新的新PrintWriter。
PrintWriter(String fileName, String csn)使用指定的文件名和字符集创建一个没有自动行刷新的新PrintWriter。
package com.day10.PrintStream;

import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.io.PrintWriter;

public class PrintWriterDemo {
    public static void main(String[] args) throws FileNotFoundException {
        PrintWriter printWriter = new PrintWriter("printWriter.txt");
        printWriter.println("printWriter打印流");
        printWriter.println(111);
        printWriter.println('a');
        printWriter.println("包含了缓冲流");
        printWriter.close();
    }
}

PrintStream和PrintWriter的区别:

  • 打印数据功能上是一模一样的, 都是使用方便,性能高效(核心优势)。
  • PrintStream继承自字节输出流OutputStream,支持写字节数据的方法。
  • PrintWriter继承自字符输出流Writer,支持写字符数据出去。

5 Properties结合IO流

  • Properties代表的是一个属性文件, 可以把自己对象中的键值对信息存入到一个属性文件中去。
  • 属性文件:后缀是.properties结尾的文件,里面的内容都是key=value,后续做系统配置信息的。

Properties类API

变量和类型方法描述
StringgetProperty(String key)在此属性列表中搜索具有指定键的属性。
StringgetProperty(String key, String defaultValue)在此属性列表中搜索具有指定键的属性。
voidlist(PrintStream out)将此属性列表打印到指定的输出流。
voidlist(PrintWriter out)将此属性列表打印到指定的输出流。
voidload(InputStream inStream)从输入字节流中读取属性列表(键和元素对)。
voidload(Reader reader)以简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。
Enumeration<?>propertyNames()返回此属性列表中所有键的枚举,如果尚未从主属性列表中找到相同名称的键,则包括默认属性列表中的不同键。
ObjectsetProperty(String key, String value)调用 Hashtable方法 put
voidstore(OutputStream out, String comments)将此 Properties表中的此属性列表(键和元素对)以适合使用 load(InputStream)方法加载到 Properties表的格式写入输出流。
voidstore(Writer writer, String comments)将此 Properties表中的此属性列表(键和元素对)以适合使用 load(Reader)方法的格式写入输出字符流。
Set<String>stringPropertyNames()从此属性列表返回一组不可修改的键,其中键及其对应的值是字符串,如果尚未从主属性列表中找到相同名称的键,则包括默认属性列表中的不同键。

需求:使用Properties把键值对信息存入到属性文件之中。如何读取Properties文件。

package com.day10.propertiesDemo;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;

public class PropertiesDemo01 {
    public static void main(String[] args) throws IOException {
        Properties properties = new Properties();
        properties.setProperty("admin","1234");
        properties.setProperty("liuchang","141");
        properties.setProperty("save","11234");
        properties.setProperty("admin2","123412");
        properties.setProperty("admin3","123431");
        System.out.println(properties);

        //保存properties
        properties.store(new FileWriter("properties.properties"),"测试properties的保存功能。");

        //如何读取Properties文件信息
        Properties properties1 = new Properties();
        properties1.load(new FileReader("properties.properties"));
        System.out.println(properties1);
        //取数据
        String admin = properties1.getProperty("admin");
        System.out.println(admin);
    }
}

生成的properties文件

#\u6D4B\u8BD5properties\u7684\u4FDD\u5B58\u529F\u80FD\u3002
#Mon Aug 01 15:32:06 CST 2022
liuchang=141
save=11234
admin=1234
admin2=123412
admin3=123431

6 IO框架

  • commons-io是apache开源基金组织提供的一组有关I0操作的类库,可以提高I0功能开发的效率。
  • commons-io工具包提供了很多有关io操作的类。有两个主要的类FileUtils, l0Utils。

引入jar包:

        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>

image-20220801154524153

package com.day10.party;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;

public class Test1 {
    public static void main(String[] args) throws IOException {
        //工具类,直接用静态方法
        String s = FileUtils.readFileToString(new File("222.txt"), "UTF-8");
        System.out.println(s);
        //复制文件
        FileUtils.copyFile(new File("222.txt"),new File("221.txt"));
    }
}

学习记录11


1 多线程的创建方式

  • Java是通过java.lang.Thread类来代表线程的。
  • 按照面向对象的思想,Thread类应该提供了实现多线程的方式。

1.1 多线程的实现方案一:继承Thread类

①定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法。

②创建MyThread类的对象。

③调用线程对象的start()方法启动线程(启动后还是执行run方法的)。

package com.day11.ThreadDemo;

public class ThreadDemo01 {
    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("主线程:"+i);
        }
    }

}
class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("子线程:"+i);
        }
    }
}

方式一:优缺点:

  • 优点:编码简单。
  • 缺点:线程类已经继承Thread,无法继承其他类,不利于扩展。

总结:

  • 为什么不直接调用了run方法,而是调用start启动线程。

    直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。只有调用start方法才是启动一个新的线程执行。

  • 要把主线程任务放在子线程之前了。

    这样主线程一直是先跑完的,相当于是一个单线程的效果了。

1.2 多线程的实现方案二:实现Runnable接口

  • ①定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法。
  • ②创建MyRunnable任务对象。
  • ③把MyRunnable任务对象交给Thread处理。
package com.day11.ThreadDemo;

public class ThreadDemo02 {
    public static void main(String[] args) {
        Runnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("主线程"+i);
        }
    }
}

class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("子线程"+i);
        }
    }
}

①可以创建Runnable的匿名内部类对象。

②交给Thread处理 。

③调用线程对象的start()启动线程。

package com.day11.ThreadDemo;

public class ThreadDemo2 {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("子线程"+i);
                }
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("子线程2"+i);
                }
            }
        }).start();


        for (int i = 0; i < 10; i++) {
            System.out.println("主线程"+i);
        }
    }
}

方式二:优缺点

  • 优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。
  • 缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的。

总结:前2种线程创建方式都存在一个问题

  • 他们重写的run方法均不能直接返回结果。
  • 不适合需要返回线程执行结果的业务场景。

1.3 多线程的实现方案三:利用Callable、FutureTask接口实现。

①得到任务对象:定义类实现Callable接口,重写call方法,封装要做的事情。用FutureTask把Callable对象封装成线程任务对象。

②把线程任务对象交给Thread处理。

③调用Thread的start方法启动线程,执行任务。

Callable重要API:接口,没有构造方法,需要实现call方法

变量和类型方法描述
Vcall()计算结果,如果无法执行,则抛出异常

FutureTask重要API:

构造器描述
FutureTask(Callable<V> callable)创建一个 FutureTask ,在运行时将执行给定的 Callable
变量和类型方法描述
Vget()如果需要等待计算完成,然后检索其结果。
package com.day11.ThreadDemo;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadDemo03 {
    public static void main(String[] args) {
        Callable<String> callable = new MyCallable(100);
        FutureTask<String> futureTask = new FutureTask<>(callable);
        Thread thread = new Thread(futureTask);
        thread.start();

        Callable<String> callable1 = new MyCallable(200);
        FutureTask<String> futureTask1 = new FutureTask<>(callable1);
        Thread thread1 = new Thread(futureTask1);
        thread1.start();

        try {
            String s = futureTask.get();
            System.out.println("第一个子线程结果"+s);
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            String s1 = futureTask1.get();
            System.out.println("第二个子线程结果"+s1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class MyCallable implements Callable<String>{
    private int n;

    public MyCallable(int n) {
        this.n = n;
    }

    @Override
    public String call() throws Exception {
        int sum =0;
        for (int i = 0; i < n; i++) {
            sum += i;
        }
        return "子线程运行结果"+sum;
    }
}

/**
第一个子线程结果子线程运行结果4950
第二个子线程结果子线程运行结果19900
*/

方式三优缺点:

  • 优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。
  • 可以在线程执行完毕后去获取线程执行的结果。
  • 缺点:编码复杂一点。

1.4 三种方式对比

image-20220803092218959

2 Thread类常用方法

构造器描述
Thread()分配新的 Thread对象。
Thread(Runnable target)分配新的 Thread对象。
Thread(Runnable target, String name)分配新的 Thread对象。
Thread(String name)分配新的 Thread对象。
变量和类型方法描述
static ThreadcurrentThread()返回对当前正在执行的线程对象的引用。
longgetId()返回此Thread的标识符。
StringgetName()返回此线程的名称。
intgetPriority()返回此线程的优先级。
Thread.StategetState()返回此线程的状态。
ThreadGroupgetThreadGroup()返回此线程所属的线程组。
voidinterrupt()中断此线程。
static booleaninterrupted()测试当前线程是否已被中断。
booleanisAlive()测试此线程是否存活。
booleanisDaemon()测试此线程是否为守护程序线程。
voidjoin()等待这个线程死亡。
voidjoin(long millis)此线程最多等待 millis毫秒。
voidjoin(long millis, int nanos)此线程最多等待 millis毫秒加上 nanos纳秒。
voidrun()如果此线程是使用单独的Runnable运行对象构造的,则调用该Runnable对象的run方法; 否则,此方法不执行任何操作并返回。
voidsetName(String name)将此线程的名称更改为等于参数 name
voidsetPriority(int newPriority)更改此线程的优先级。
static voidsleep(long millis)导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和调度程序的精度和准确性。
static voidsleep(long millis, int nanos)导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数加上指定的纳秒数,具体取决于系统定时器和调度程序的精度和准确性。
voidstart()导致此线程开始执行; Java虚拟机调用此线程的run方法。
StringtoString()返回此线程的字符串表示形式,包括线程的名称,优先级和线程组。
package com.day11.ThreadDemo;

public class ThreadDemo04 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new MyThread1();
        thread.setName("子线程1");
        thread.start();
        System.out.println(thread.getId());
        System.out.println(thread.getName());

        Thread thread1 = new MyThread1("子线程2");
        thread1.start();
        System.out.println(thread1.getName());
        Thread.sleep(2);

        Thread thread2 = Thread.currentThread();
        System.out.println(thread2.getName());
    }
}
class  MyThread1 extends Thread{
    public MyThread1(String name) {
        super(name);
    }

    public MyThread1() {
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("子线程启动"+i);
        }
    }
}

3 线程安全

线程安全问题出现的原因?

  • 存在多线程并发。
  • 同时访问共享资源。
  • 存在修改共享资源。

需求:

小明和小红是一对夫妻,他们有一个共同的账户,余额是10万元,模拟2人同时去取钱10万。

分析:
①需要提供一个账户类,创建一账户对象代表2个人的共享账户。
②需要定义一个线程类,线程类可以处理账户对象。
③创建2个线程对象,传入同一个账户对象。
④启动2个线程,去同一个账户对象中取钱10万。

package com.day11.ThreadDemo;

public class Account {
    private String cardId;
    private double money; // 账户的余额

    public Account(){

    }

    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }

    /**
       小明 小红
     */
    public void drawMoney(double money) {
        // 0、先获取是谁来取钱,线程的名字就是人名
        String name = Thread.currentThread().getName();
        // 1、判断账户是否够钱
        if(this.money >= money){
            // 2、取钱
            System.out.println(name + "来取钱成功,吐出:" + money);
            // 3、更新余额
            this.money -= money;
            System.out.println(name + "取钱后剩余:" + this.money);
        }else {
            // 4、余额不足
            System.out.println(name +"来取钱,余额不足!");
        }

    }

    public String getCardId() {
        return cardId;
    }

    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

}
package com.day11.ThreadDemo;

public class DrawThread extends Thread{
    private Account account;

    public DrawThread(Account account,String name) {
        super(name);
        this.account = account;
    }

    @Override
    public void run() {
        account.drawMoney(10000);
    }

}
package com.day11.ThreadDemo;

public class ThreadDemo05 {
    public static void main(String[] args) {
        Account account = new Account("66666", 10000);
        new DrawThread(account,"小明").start();
        new DrawThread(account,"小红").start();
    }
}
/**
运行结果:
小明来取钱成功,吐出:10000.0
小红来取钱成功,吐出:10000.0
小明取钱后剩余:0.0
小红取钱后剩余:-10000.0
*/

4 线程同步

  • 为了解决线程安全问题。让多个线程实现先后依次访问共享资源,这样就解决了安全问题。
  • 加锁,把共享资源进行上锁,每次只能一个线程进入访问完毕以后解锁,然后其他线程才能进来。

4.1 同步代码块

  • 作用:把出现线程安全问题的核心代码给上锁。
  • 原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行。
  • 建议使用共享资源作为锁对象。
  • 对于实例方法建议使用this作为锁对象。
  • 对于静态方法建议使用字节码(类名.class) 对象作为锁对象。
public void drawMoney(double money) {
        // 0、先获取是谁来取钱,线程的名字就是人名
        String name = Thread.currentThread().getName();
        // 1、判断账户是否够钱
    //使用synchronized同步代码块,为进程上锁
        synchronized (this) {
            if(this.money >= money){
                // 2、取钱
                System.out.println(name + "来取钱成功,吐出:" + money);
                // 3、更新余额
                this.money -= money;
                System.out.println(name + "取钱后剩余:" + this.money);
            }else {
                // 4、余额不足
                System.out.println(name +"来取钱,余额不足!");
            }
        }
}
/**
运行结果:
小明来取钱成功,吐出:10000.0
小明取钱后剩余:0.0
小红来取钱,余额不足!
*/

4.2 同步方法

  • 作用:把出现线程安全问题的核心方法给上锁。
  • 原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。
    public synchronized void drawMoney(double money) {
        // 0、先获取是谁来取钱,线程的名字就是人名
        String name = Thread.currentThread().getName();
        // 1、判断账户是否够钱
        if(this.money >= money){
            // 2、取钱
            System.out.println(name + "来取钱成功,吐出:" + money);
            // 3、更新余额
            this.money -= money;
            System.out.println(name + "取钱后剩余:" + this.money);
        }else {
            // 4、余额不足
            System.out.println(name +"来取钱,余额不足!");
        }
    }

同步方法底层原理

  • 同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
  • 如果方法是实例方法:同步方法默认用this作为的锁对象。但是代码要高度面向对象!
  • 如果方法是静态方法:同步方法默认用类名.class作为的锁对象。

4.3 Lock锁

  • 为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,更加灵活、方便。
  • Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。
  • Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来构建Lock锁对象。
变量和类型方法描述
voidlock()获得锁。
voidunlock()释放锁定。
package com.day11.ThreadDemo;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Account {
    private String cardId;
    private double money; // 账户的余额
    private final Lock lock = new ReentrantLock();

    public Account(){

    }

    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }

    /**
     小明 小红
     */
    public  void drawMoney(double money) {
        // 0、先获取是谁来取钱,线程的名字就是人名
        String name = Thread.currentThread().getName();
        //上锁操作
        lock.lock();
        // 1、判断账户是否够钱
        try {
            if(this.money >= money){
                // 2、取钱
                System.out.println(name + "来取钱成功,吐出:" + money);
                // 3、更新余额
                this.money -= money;
                System.out.println(name + "取钱后剩余:" + this.money);
            }else {
                // 4、余额不足
                System.out.println(name +"来取钱,余额不足!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    public String getCardId() {
        return cardId;
    }

    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

}

5 线程池

5.1 线程池的概述

  • 不使用线程池的问题:如果用户每发起一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的,这样会严重影响系统的性能。
  • 线程池就是一个可以复用线程的技术。
  • JDK 5.0起提供了代表线程池的接口: ExecutorService

方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象。

构造器描述
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)使用给定的初始参数创建新的 ThreadPoolExecutor
  • 参数一:指定线程池的线程数量(核心线程):corePoolSize 不能小于0
  • 参数二:指定线程池可支持的最大线程数:maximumPoolSize 最大数量>=核心线程数量
  • 参数三:指定临时线程的最大存活时间: keepAliveTime 不能小于0
  • 参数四:指定存活时间的单位(秒、分、时、天): unit 时间单位
  • 参数五:指定任务队列: workQueue 不能为null
  • 参数六:指定用哪个线程工厂创建线程: threadFactory 不能为null
  • 参数七:指定线程忙,任务满的时候,新任务来了怎么办: handler 不能为null
  • 临时线程什么时候创建啊?

    新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。

  • 什么时候会开始拒绝任务?

    核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝。

    image-20220804113615827

        ExecutorService pool = new ThreadPoolExecutor(3, 5 ,
                6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5) , Executors.defaultThreadFactory(),
               new ThreadPoolExecutor.AbortPolicy() );

方式二:使用Executors (线程池的工具类)调用方法返回不同特点的线程池对象。

变量和类型方法描述
static ExecutorServicenewCachedThreadPool()创建一个根据需要创建新线程的线程池,但在它们可用时将重用以前构造的线程。
static ExecutorServicenewCachedThreadPool(ThreadFactory threadFactory)创建一个根据需要创建新线程的线程池,但在它们可用时将重用以前构造的线程,并在需要时使用提供的ThreadFactory创建新线程。
static ExecutorServicenewFixedThreadPool(int nThreads)创建一个线程池,该池重用在共享的无界队列中运行的固定数量的线程。
package com.day11.d8_threadpool;

import java.util.concurrent.*;

/**
    目标:使用Executors的工具方法直接得到一个线程池对象。
 */
public class ThreadPoolDemo3 {
    public static void main(String[] args) throws Exception {
        // 1、创建固定线程数据的线程池
        ExecutorService pool = Executors.newFixedThreadPool(3);

        pool.execute(new MyRunnable());
        pool.execute(new MyRunnable());
        pool.execute(new MyRunnable());
        pool.execute(new MyRunnable()); // 已经没有多余线程了
    }
}

5.2 线程池处理Runnable任务

Execute类方法:

变量和类型方法描述
voidexecute(Runnable command)在将来的某个时间执行给定的命令。
package com.day11.d8_threadpool;

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "输出了:HelloWorld ==> "  + i);
        }
        try {
            System.out.println(Thread.currentThread().getName() + "本任务与线程绑定了,线程进入休眠了~~~");
            Thread.sleep(10000000);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
package com.day11.d8_threadpool;

import java.util.concurrent.*;

/**
    目标:自定义一个线程池对象,并测试其特性。
 */
public class ThreadPoolDemo1 {
    public static void main(String[] args) {
        // 1、创建线程池对象
        /**
         public ThreadPoolExecutor(int corePoolSize,
                                 int maximumPoolSize,
                                 long keepAliveTime,
                                 TimeUnit unit,
                                 BlockingQueue<Runnable> workQueue,
                                 ThreadFactory threadFactory,
                                 RejectedExecutionHandler handler)
         */
        ExecutorService pool = new ThreadPoolExecutor(3, 5 ,
                6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5) , Executors.defaultThreadFactory(),
               new ThreadPoolExecutor.AbortPolicy() );

        // 2、给任务线程池处理。
        Runnable target = new MyRunnable();
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);

        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);

        // 创建临时线程
        pool.execute(target);
        pool.execute(target);
//        // 不创建,拒绝策略被触发!!!
//        pool.execute(target);

        // 关闭线程池(开发中一般不会使用)。
        // pool.shutdownNow(); // 立即关闭,即使任务没有完成,会丢失任务的!
        pool.shutdown(); // 会等待全部任务执行完毕之后再关闭(建议使用的)
    }
}

5.3 线程池处理Callable任务

变量和类型方法描述
<T> Future<T>submit(Callable<T> task)提交值返回任务以执行并返回表示任务的挂起结果的Future。
package com.day11.d8_threadpool;

import java.util.concurrent.Callable;

/**
    1、定义一个任务类 实现Callable接口  应该申明线程任务执行完毕后的结果的数据类型
 */
public class MyCallable implements Callable<String>{
    private int n;
    public MyCallable(int n) {
        this.n = n;
    }

    /**
       2、重写call方法(任务方法)
     */
    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= n ; i++) {
            sum += i;
        }
        return Thread.currentThread().getName()
                + "执行 1-" + n+ "的和,结果是:" + sum;
    }
}
package com.day11.d8_threadpool;

import java.util.concurrent.*;

/**
    目标:自定义一个线程池对象,并测试其特性。
 */
public class ThreadPoolDemo2 {
    public static void main(String[] args) throws Exception {
        // 1、创建线程池对象
        /**
         public ThreadPoolExecutor(int corePoolSize,
                                 int maximumPoolSize,
                                 long keepAliveTime,
                                 TimeUnit unit,
                                 BlockingQueue<Runnable> workQueue,
                                 ThreadFactory threadFactory,
                                 RejectedExecutionHandler handler)
         */
        ExecutorService pool = new ThreadPoolExecutor(3, 5 ,
                6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5) , Executors.defaultThreadFactory(),
               new ThreadPoolExecutor.AbortPolicy() );

        // 2、给任务线程池处理。
        Future<String> f1 = pool.submit(new MyCallable(100));
        Future<String> f2 = pool.submit(new MyCallable(200));
        Future<String> f3 = pool.submit(new MyCallable(300));
        Future<String> f4 = pool.submit(new MyCallable(400));
        Future<String> f5 = pool.submit(new MyCallable(500));

        System.out.println(f1.get());
        System.out.println(f2.get());
        System.out.println(f3.get());
        System.out.println(f4.get());
        System.out.println(f5.get());
    }
}

6 定时器

  • 定时器是一种控制任务延时调用,或者周期调用的技术。
  • 作用:闹钟、定时邮件发送。

定时器的实现方式:

方式一:Timer

构造器描述
Timer()创建一个新计时器。
Timer(String name)创建一个新的计时器,其关联的线程具有指定的名称。
变量和类型方法描述
voidcancel()终止此计时器,丢弃当前计划的任何任务。
intpurge()从此计时器的任务队列中删除所有已取消的任务。
voidschedule(TimerTask task, long delay)在指定的延迟后安排指定的任务执行。
voidschedule(TimerTask task, long delay, long period)在指定 的延迟之后开始,为重复的 固定延迟执行安排指定的任务。
voidschedule(TimerTask task, Date time)计划在指定时间执行指定的任务。

Timer定时器的特点和存在的问题

  • Timer是单线程,处理多个任务按照顺序执行,存在延时与设置定时器的时间有出入。
  • 可能因为其中的某个任务的异常使Timer线程死掉,从而影响后续任务执行。
package com.day11.d9_timer;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

/**
    目标:Timer定时器的使用和了解。
 */
public class TimerDemo1 {
    public static void main(String[] args) {
        // 1、创建Timer定时器
        Timer timer = new Timer();  // 定时器本身就是一个单线程。
        // 2、调用方法,处理定时任务
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "执行AAA~~~" + new Date());}
        }, 0, 2000);
        
        //这里会出现报错,从而发生计时器C不能成功的进行。
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "执行BB~~~"+ new Date());
                System.out.println(10/0);
            }
        }, 0, 2000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "执行CCC~~~"+ new Date());
            }
        }, 0, 3000);
    }
}

方式二:ScheduledExecutorService

变量和类型方法描述
ScheduledFuture<?>scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)提交定期操作,该操作在给定的初始延迟后首先启用,随后在给定的时间段内启用; 也就是说,执行将在initialDelay之后开始,然后是initialDelay + period ,然后是initialDelay + 2 * period ,依此类推。
package com.day11.d9_timer;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
    目标:Timer定时器的使用和了解。
 */
public class TimerDemo2 {
    public static void main(String[] args) {
        // 1、创建ScheduledExecutorService线程池,做定时器
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);

        // 2、开启定时任务
        pool.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "执行输出:AAA  ==》 " + new Date());
                try {
                    Thread.sleep(100000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, 0, 2, TimeUnit.SECONDS);


        pool.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "执行输出:BBB  ==》 " + new Date());
                System.out.println(10 / 0);
            }
        }, 0, 2, TimeUnit.SECONDS);


        pool.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "执行输出:CCC  ==》 " + new Date());
            }
        }, 0, 2, TimeUnit.SECONDS);

    }
}

7 并发,并行

并发与并行:正在运行的程序(软件)就是一个独立的进程,线程是属于进程的,多个线程其实是并发与并行同时进行的。

并发的理解:

  • CPU同时处理线程的数量有限。
  • CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。

并行的理解:

在同一个时刻上,同时有多个线程在被CPU处理并执行。


学习记录12


1 网络通信

实现网络编程关键的三要素:

  • IP地址:设备在网络中的地址,是唯一的标识。
  • 端口:应用程序在设备中唯一的标识。
  • 协议:数据在网络中传输的规则,常见的协议有UDP协议和TCP协议。

1.1 IP地址InetAddress类

变量和类型方法描述
booleanequals(Object obj)将此对象与指定的对象进行比较。
byte[]getAddress()返回此 InetAddress对象的原始IP地址。
static InetAddress[]getAllByName(String host)根据主机的名称,根据系统上配置的名称服务返回其IP地址数组。
static InetAddressgetByAddress(byte[] addr)给定原始IP地址返回 InetAddress对象。
static InetAddressgetByName(String host)根据主机名称确定主机的IP地址。
StringgetHostAddress()返回文本表示中的IP地址字符串。
StringgetHostName()获取此IP地址的主机名。
static InetAddressgetLocalHost()返回本地主机的地址。
booleanisReachable(int timeout)测试该地址是否可达。
package com.day12.d1_inetAddress;
import java.net.InetAddress;

public class InetAddressDemo01 {
    public static void main(String[] args) throws Exception {
        // 1.获取本机地址对象。
        InetAddress ip1 = InetAddress.getLocalHost();
        System.out.println(ip1.getHostName());
        System.out.println(ip1.getHostAddress());

        // 2.获取域名ip对象
        InetAddress ip2 = InetAddress.getByName("www.baidu.com");
        System.out.println(ip2.getHostName());
        System.out.println(ip2.getHostAddress());

        // 3.获取公网IP对象。
        InetAddress ip3 = InetAddress.getByName("112.80.248.76");
        System.out.println(ip3.getHostName());
        System.out.println(ip3.getHostAddress());

        // 4.判断是否能通: ping  5s之前测试是否可通
        System.out.println(ip3.isReachable(5000));
    }
}
/**
LAPTOP-59ARH7U0
192.168.0.196
www.baidu.com
39.156.66.14
112.80.248.76
112.80.248.76
true
*/

1.2 端口号

端口号:标识正在计算机设备上运行的进程(程序),被规定为一个16位的二进制,范围是0~65535。

端口类型:

  • 周知端口: 0~1023,被预先定义的知名应用占用(如: HTTP占用80,FTP占用21)。
  • 注册端口: 1024~49151,分配给用户进程或某些应用程序。( 如: Tomcat占用8080,MySQL占用3306)
  • 动态端口: 49152到65535, 之所以称为动态端口,是因为它-般不固定分配某种进程,而是动态分配。

注意:我们自己开发的程序选择注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错。

1.3 通信协议

传输层的2个常见协议:

  • TCP(Transmission Control Protocol):传输控制协议。
  • UDP(User Datagram Protocol):用户数据报协议。

TCP协议特点:

  • 使用TCP协议,必须双方先建立连接,它是一种面向连接的可靠通信协议。
  • 传输前,采用“三次握手”方式建立连接,所以是可靠的。
  • 在连接中可进行大数据量的传输。
  • 连接、发送数据都需要确认,且传输完毕后,还需释放已建立的连接,通信效率较低。

TCP协议通信场景:

  • 对信息安全要求较高的场景,例如:文件下载、金融等数据通信。

UDP协议:

  • UDP是一种无连接、不可靠传输的协议。
  • 将数据源IP、目的地IP和端口封装成数据包,不需要建立连接。
  • 每个数据包的大小限制在64KB内。
  • 发送不管对方是否准备好,接收方收到也不确认,故是不可靠的。
  • 可以广播发送,发送数据结束时无需释放资源,开销小,速度快。

UDP协议通信场景:

  • 语音通话,视频会话等。

2 UDP通信

DatagramPacket:数据包对象

构造器描述
DatagramPacket(byte[] buf, int length)构造 DatagramPacket用于接收长度为 length数据包。
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)构造一个数据报包,用于将长度为 length且偏移量为 ioffset的数据包发送到指定主机上的指定端口号。

DatagramSocket:发送端和接收端对象

构造器描述
DatagramSocket()构造一个数据报套接字并将其绑定到本地主机上的任何可用端口。
DatagramSocket(int port)构造一个数据报套接字并将其绑定到本地主机上的指定端口。
变量和类型方法描述
voidreceive(DatagramPacket p)从此套接字接收数据报包。
voidsend(DatagramPacket p)从此套接字发送数据报包。

案例一:使用UDP实现:一对一发送

客户端:

package com.day12.d2_udp1;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

/**
  发送端  一发 一收
 */
public class ClientDemo1 {
    public static void main(String[] args) throws Exception {
        System.out.println("=====客户端启动======");

        // 1、创建发送端对象:发送端自带默认的端口号(人)
        DatagramSocket socket = new DatagramSocket(6666);

        // 2、创建一个数据包对象封装数据(韭菜盘子)
        /**
         public DatagramPacket(byte buf[], int length,
         InetAddress address, int port)
         参数一:封装要发送的数据(韭菜)
         参数二:发送数据的大小
         参数三:服务端的主机IP地址
         参数四:服务端的端口
         */
        byte[] buffer = "我是一颗快乐的韭菜,你愿意吃吗?".getBytes();
        DatagramPacket packet = new DatagramPacket( buffer, buffer.length,
                InetAddress.getLocalHost() , 8888);

        // 3、发送数据出去
        socket.send(packet);

        socket.close();
    }
}

服务器端:

package com.day12.d2_udp1;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

/**
  接收端
 */
public class ServerDemo2 {
    public static void main(String[] args) throws Exception {
        System.out.println("=====服务端启动======");
        // 1、创建接收端对象:注册端口(人)
        DatagramSocket socket = new DatagramSocket(8888);

        // 2、创建一个数据包对象接收数据(韭菜盘子)
        byte[] buffer = new byte[1024 * 64];
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

        // 3、等待接收数据。
        socket.receive(packet);

        // 4、取出数据即可
        // 读取多少倒出多少
        int len = packet.getLength();
        String rs = new String(buffer,0, len);
        System.out.println("收到了:" + rs);

        // 获取发送端的ip和端口
        String ip  =packet.getSocketAddress().toString();
        System.out.println("对方地址:" + ip);

        int port  = packet.getPort();
        System.out.println("对方端口:" + port);

        socket.close();
    }
}

运行结果:

注意:先启动服务端后启动客户端!

=====服务端启动======
收到了:我是一颗快乐的韭菜,你愿意吃吗?
对方地址:/192.168.0.196:6666
对方端口:6666

案例二:多发多收

客户端:

package com.day12.d3_udp2;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.util.Scanner;

/**
  发送端  多发 多收
 */
public class ClientDemo1 {
    public static void main(String[] args) throws Exception {
        System.out.println("=====客户端启动======");

        // 1、创建发送端对象:发送端自带默认的端口号(人)
        DatagramSocket socket = new DatagramSocket(7777);
        

        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("请说:");
            String msg = sc.nextLine();

            if("exit".equals(msg)){
                System.out.println("离线成功!");
                socket.close();
                break;
            }

            // 2、创建一个数据包对象封装数据(韭菜盘子)
            byte[] buffer = msg.getBytes();
            DatagramPacket packet = new DatagramPacket( buffer, buffer.length,
                    InetAddress.getLocalHost() , 8888);

            // 3、发送数据出去
            socket.send(packet);
        }

    }
}

服务器端:

package com.day12.d3_udp2;

import java.net.DatagramPacket;
import java.net.DatagramSocket;

/**
  接收端
 */
public class ServerDemo2 {
    public static void main(String[] args) throws Exception {
        System.out.println("=====服务端启动======");
        // 1、创建接收端对象:注册端口(人)
        DatagramSocket socket = new DatagramSocket(8888);

        // 2、创建一个数据包对象接收数据(韭菜盘子)
        byte[] buffer = new byte[1024 * 64];
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

        while (true) {
            // 3、等待接收数据。
            socket.receive(packet);
            // 4、取出数据即可
            // 读取多少倒出多少
            int len = packet.getLength();
            String rs = new String(buffer,0, len);
            System.out.println("收到了来自:" + packet.getAddress() +", 对方端口是" + packet.getPort() +"的消息:" + rs);
        }
    }
}

运行结果:

注意:先启动服务端后启动客户端!

image-20220804143445242

=====客户端启动======
请说:
你好
请说:
我是zhenxi
请说:
exit
离线成功!

Process finished with exit code 0
=====客户端启动======
请说:
你好
请说:
我是流畅
请说:
exit
离线成功!

Process finished with exit code 0
=====服务端启动======
收到了来自:/192.168.0.196, 对方端口是7777的消息:你好
收到了来自:/192.168.0.196, 对方端口是6666的消息:你好
收到了来自:/192.168.0.196, 对方端口是7777的消息:我是zhenxi
收到了来自:/192.168.0.196, 对方端口是6666的消息:我是流畅

案例三:UDP如何实现广播

使用广播地址:255.255.255.255

具体操作:

①发送端发送的数据包的目的地写的是广播地址、且指定端口。(255.255.255.255,9999)

②本机所在网段的其他主机的程序只要匹配端口成功即就可以收到消息了。(9999)

package com.day12.d4_upd3;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;

public class ClientDemo1 {
    public static void main(String[] args) throws Exception {
        System.out.println("=====客户端启动======");

        // 1、创建发送端对象:发送端自带默认的端口号(人)
        DatagramSocket socket = new DatagramSocket();
        

        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("请说:");
            String msg = sc.nextLine();

            if("exit".equals(msg)){
                System.out.println("离线成功!");
                socket.close();
                break;
            }

            // 2、创建一个数据包对象封装数据(韭菜盘子)
            byte[] buffer = msg.getBytes();
            // 注意:只要目的地IP是 255.255.255.255 这个消息将以广播的形式对外发送
            DatagramPacket packet = new DatagramPacket( buffer, buffer.length,
                    InetAddress.getByName("255.255.255.255") , 8888);

                    // 3、发送数据出去
            socket.send(packet);
        }

    }
}

3 TCP通信

TCP相关类:Socket类

构造器描述
Socket()创建一个未连接的套接字,系统默认类型为SocketImpl。
Socket(String host, int port)创建流套接字并将其连接到指定主机上的指定端口号。
变量和类型方法描述
InputStreamgetInputStream()返回此套接字的输入流。
OutputStreamgetOutputStream()返回此套接字的输出流。

ServerSocket(服务端)

构造器描述
ServerSocket()创建未绑定的服务器套接字。
ServerSocket(int port)创建绑定到指定端口的服务器套接字。
变量和类型方法描述
Socketaccept()侦听对此套接字的连接并接受它。

需求一:

①创建客户端的Socket对象,请求与服务端的连接。
②使用socket对象调用getOutputStream()方法得到字节输出流。
③使用字节输出流完成数据的发送。
④释放资源:关闭socket管道。

客户端:

package com.day12.d5_socket1;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;

public class ClientDemo1 {
    public static void main(String[] args) {
        try {
            System.out.println("====客户端启动===");
            // 1、创建Socket通信管道请求有服务端的连接
            // public Socket(String host, int port)
            // 参数一:服务端的IP地址
            // 参数二:服务端的端口
            Socket socket = new Socket("127.0.0.1", 7777);

            // 2、从socket通信管道中得到一个字节输出流 负责发送数据
            OutputStream os = socket.getOutputStream();

            // 3、把低级的字节流包装成打印流
            PrintStream ps = new PrintStream(os);

            // 4、发送消息
            ps.println("TCP的客户端.");
            ps.flush();

            // 关闭资源。
            // socket.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

服务端:

package com.day12.d5_socket1;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerDemo2 {
    public static void main(String[] args) {
        try {
            System.out.println("===服务端启动成功===");
            // 1、注册端口
            ServerSocket serverSocket = new ServerSocket(7777);
            // 2、必须调用accept方法:等待接收客户端的Socket连接请求,建立Socket通信管道
            Socket socket = serverSocket.accept();
            // 3、从socket通信管道中得到一个字节输入流
            InputStream is = socket.getInputStream();
            // 4、把字节输入流包装成缓冲字符输入流进行消息的接收
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            // 5、按照行读取消息
            String msg;
            if ((msg = br.readLine()) != null){
                System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

需求二:多发多收消息

客户端:

package com.day12.d6_socket2;

import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

/**
  实现多发和多收
 */
public class ClientDemo1 {
    public static void main(String[] args) {
        try {
            System.out.println("====客户端启动===");
            // 1、创建Socket通信管道请求有服务端的连接
            // public Socket(String host, int port)
            // 参数一:服务端的IP地址
            // 参数二:服务端的端口
            Socket socket = new Socket("127.0.0.1", 7777);

            // 2、从socket通信管道中得到一个字节输出流 负责发送数据
            OutputStream os = socket.getOutputStream();

            // 3、把低级的字节流包装成打印流
            PrintStream ps = new PrintStream(os);

            Scanner sc =  new Scanner(System.in);
            while (true) {
                System.out.println("请说:");
                String msg = sc.nextLine();
                // 4、发送消息
                ps.println(msg);
                ps.flush();
            }

            // 关闭资源。
            // socket.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

服务端:

package com.day12.d6_socket2;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;


public class ServerDemo2 {
    public static void main(String[] args) {
        try {
            System.out.println("===服务端启动成功===");
            // 1、注册端口
            ServerSocket serverSocket = new ServerSocket(7777);
            while (true) {
                // 2、必须调用accept方法:等待接收客户端的Socket连接请求,建立Socket通信管道
                Socket socket = serverSocket.accept();
                // 3、从socket通信管道中得到一个字节输入流
                InputStream is = socket.getInputStream();
                // 4、把字节输入流包装成缓冲字符输入流进行消息的接收
                BufferedReader br = new BufferedReader(new InputStreamReader(is));
                // 5、按照行读取消息
                String msg;
                while ((msg = br.readLine()) != null){
                    System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

注意:本案例实现了多发多收,那么是否可以同时接收多个客户端的消息?

  • 不可以的。TCP连接是一对一的!
  • 因为服务端现在只有一个线程,只能与一个客户端进行通信。

需求三:同时处理多个客户端消息

客户端:

package com.day12.d7_socket3;

import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

/**
    目标:实现服务端可以同时处理多个客户端的消息。
 */
public class ClientDemo1 {
    public static void main(String[] args) {
        try {
            System.out.println("====客户端启动===");
            // 1、创建Socket通信管道请求有服务端的连接
            // public Socket(String host, int port)
            // 参数一:服务端的IP地址
            // 参数二:服务端的端口
            Socket socket = new Socket("127.0.0.1", 7777);

            // 2、从socket通信管道中得到一个字节输出流 负责发送数据
            OutputStream os = socket.getOutputStream();

            // 3、把低级的字节流包装成打印流
            PrintStream ps = new PrintStream(os);

            Scanner sc =  new Scanner(System.in);
            while (true) {
                System.out.println("请说:");
                String msg = sc.nextLine();
                // 4、发送消息
                ps.println(msg);
                ps.flush();
            }

            // 关闭资源。
            // socket.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

服务端:

package com.day12.d7_socket3;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

/**
   目标:实现服务端可以同时处理多个客户端的消息。
 */
public class ServerDemo2 {
    public static void main(String[] args) {
        try {
            System.out.println("===服务端启动成功===");
            // 1、注册端口
            ServerSocket serverSocket = new ServerSocket(7777);
            // a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
            while (true) {
                // 2、每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息
                Socket socket = serverSocket.accept();
                System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!");
                // 3、开始创建独立线程处理socket
                new ServerReaderThread(socket).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

创建的线程:

package com.day12.d7_socket3;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;

public class ServerReaderThread extends Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            // 从socket通信管道中得到一个字节输入流
            InputStream is = socket.getInputStream();
            // 把字节输入流包装成缓冲字符输入流进行消息的接收
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            // 按照行读取消息
            String msg;
            while ((msg = br.readLine()) != null){
                System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
            }
        } catch (Exception e) {
            System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
        }
    }
}

需求四:引入线程池处理多个客户端消息

客户端:

package com.day12.d8_socket4;

import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

/**
    使用线程池优化:实现通信。
 */
public class ClientDemo1 {
    public static void main(String[] args) {
        try {
            System.out.println("====客户端启动===");
            // 1、创建Socket通信管道请求有服务端的连接
            // public Socket(String host, int port)
            // 参数一:服务端的IP地址
            // 参数二:服务端的端口
            Socket socket = new Socket("127.0.0.1", 6666);

            // 2、从socket通信管道中得到一个字节输出流 负责发送数据
            OutputStream os = socket.getOutputStream();

            // 3、把低级的字节流包装成打印流
            PrintStream ps = new PrintStream(os);

            Scanner sc =  new Scanner(System.in);
            while (true) {
                System.out.println("请说:");
                String msg = sc.nextLine();
                // 4、发送消息
                ps.println(msg);
                ps.flush();
            }
            // 关闭资源。
            // socket.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

服务端:

package com.day12.d8_socket4;


import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;

/**
   目标:实现服务端可以同时处理多个客户端的消息。
 */
public class ServerDemo2 {

    // 使用静态变量记住一个线程池对象
    private static ExecutorService pool = new ThreadPoolExecutor(300,
            1500, 6, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(2)
    , Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

    public static void main(String[] args) {
        try {
            System.out.println("===服务端启动成功===");
            // 1、注册端口
            ServerSocket serverSocket = new ServerSocket(6666);
            // a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
            while (true) {
                // 2、每接收到一个客户端的Socket管道,
                Socket socket = serverSocket.accept();
                System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!");

                // 任务对象负责读取消息。
                Runnable target = new ServerReaderRunnable(socket);
                pool.execute(target);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

线程任务:

package com.day12.d8_socket4;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;

public class ServerReaderRunnable implements Runnable{
    private Socket socket;
    public ServerReaderRunnable(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            // 从socket通信管道中得到一个字节输入流
            InputStream is = socket.getInputStream();
            // 把字节输入流包装成缓冲字符输入流进行消息的接收
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            // 按照行读取消息
            String msg;
            while ((msg = br.readLine()) != null){
                System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
            }
        } catch (Exception e) {
            System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
        }
    }
}

学习总结2

在这一周的学习中,主要学习了logback日志框架的使用、IO流相关知识、线程相关知识、网络通信相关知识等等。在该周的学习过程中没有遇到什么困难,但是对于前几周所学习的知识有些遗忘,于是回头又复习了一遍上两周所记录的笔记,对Java SE的基础知识掌握的越发牢固。该周学习进程有一点慢,下周需要在加快速度的同时也要保证质量。在完成本周课程的基础上,我将继续深入学习Java语言及面向对象技术。希望通过这次学习,能够让自己的编程能力有所提升,继续努力坚持!

0

评论

博主关闭了所有页面的评论