java复习回顾

复习回顾

day01-匿名对象 , 继承 , 抽象类

今日目标

  • 面向对象回顾
  • 匿名对象介绍
  • 面向对象特征 - 继承
  • 抽象类的使用
  • 模板设计模式

1 面向对象回顾

  • 面向对象的核心思想是什么 ?
    • 用代码来模拟现实生活中的事物 , 比如学生类表示学生事物 , 对象表示的就是具体的学生 , 有了类就可以描述万千世界所有的事物了
  • 现有的类还是先有的对象 ?
    • 对象是根据类创建出来的 , 所有现有的类 , 再有对象
  • Java类的创建 ?
    • 类名建议使用大驼峰命名法 , 每个单词首字母需要大写
    • Java规定 , 一个Java文件中可以定义多个类 , 但是只能有一个类使用public修饰 , public修饰的类名需要和java文件名保持一致
    • 按照规范 , 实际工作之时 , 建议一个Java文件中只能有一个类
  • 类中的组成成分 ?
    • 1 成员变量
    • 2 成员方法
    • 3 构造方法
    • 4 内部类(后面学)
    • 5 代码块(后面学)
    image-20210329163755741
  • 创建对象所使用的关键字 ?
    • new关键字
  • 创建对象的格式 ?
    • 类名 对象名 = new 类名(参数列表);
  • 调用对象的成员 ?
    • 调用成员变量 : 对象名.成员变量名
    • 调用成员方法 : 对象名.成员方法名(参数)
  • 定义构造方法的格式 ?
    • 修饰符 类名(参数){ … }
  • 定义构造方法的特点 ?
    • 没有返回值 , 连void都没有
    • 方法名与类名相同
    • 构造方法可以进行重载定义
  • 构造方法的作用 ?
    • 空参构造 : 初始化对象的数据为默认值
    • 有参构造 : 初始化对象时 , 为对象的属性赋值
  • 面向对象三大特征是什么 ?
    • 封装 , 继承 , 多态
  • 封装的思想及其作用 ?
    • 思想 : 把实现细节隐藏 , 对外提供公共的访问方式
    • 作用 : 提高代码的安全性 , 提高代码的复用性
  • 封装的步骤 ?
    • 把成员变量进行private修饰
    • 提供对应的setter和getter方法
  • this关键字的含义 ?
    • this代表的是本类对象的引用
    • 在构造方法中 , this代表的是new的对象
    • 在成员方法中 , this代表的是调用此方法的对象
  • this关键字的作用 ?
    • 主要用于区分局部变量和成员变量同名的问题

2 匿名对象介绍

  • 什么是匿名对象 ?
    • 没有变量接收的对象 , 称作为匿名对象
  • 匿名对象的使用 ?
    • 直接使用匿名对象调用方法
    • 匿名对象作为方法传参
    • 匿名对象作为方法的返回值
  • 代码实现

    package com.itheima.anonymous;
    
    /*
        匿名对象 :
            没有对象名接收的对象 , 称作为匿名对象
            有对象名接收的对象 , 称作有名对象
    
        注意 : 一般使用一次的对象 , 我们可以采用匿名对象
    
        匿名对象的使用 :
            1 使用匿名对象直接调用方法
            2 使用匿名对象作为方法的传参
            3 使用匿名对象作为方法的返回值
     */
    public class Demo1 {
        public static void main(String[] args) {
    
    //        Student s = new Student();
    //        s.study();
    //        1 使用匿名对象直接调用方法
            new Student().study();
    
    
    //        Student s2 = new Student();
    //        useStudent(s2);
    //        2 使用匿名对象作为方法的传参
            useStudent(new Student());
        }
    
        public static void useStudent(Student s) {
            s.study();
        }
    
        public static Student getStudent() {
    //        Student s = new Student();
    //        return s;
            // 3 使用匿名对象作为方法的返回值
            return new Student();
        }
    }
    class Student {
        public void study() {
            System.out.println("学生学习...");
        }
    }
    
  • 代码实践:注册案例

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    package com.itheima.anonymous;
    
    import java.util.ArrayList;
    import java.util.Scanner;
    
    /*
        需求 :
            1 创建用户(User)对象 , 对象数据采用键盘录入而来
            2 用户(User)包含的属性 :
                用户名   (username)
                手机号码 (phonNumber)
                登录密码 (password)
                确认密码 (confirm)
                电子邮箱 (email)
                性别     (sex)
                出生日期 (birthday)
            3 如果登录密码和确认密码不一致 , 重新输入
            4 把用户(User)对象 ,添加到ArrayList集合中 , 打印集合对象即可
     */
    public class Demo2 {
        public static void main(String[] args) {
            // 创建集合对象
            ArrayList<User> list = new ArrayList<>();
    
            // 创建键盘录入对象 , 录入用户的数据
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入用户名:");
            String username = sc.nextLine();
            System.out.println("请输入手机号码:");
            String phoneNumber = sc.nextLine();
            System.out.println("请输入登录密码:");
            String password = sc.nextLine();
            System.out.println("请输入确认密码:");
            // 如果登录密码 , 和确认密码不一致 , 需要重新输入确认密码
            String confirm;
            while (true) {
                confirm = sc.nextLine();
                if (password.equals(confirm)) {
                    break;
                } else {
                    System.out.println("您输入的登录密码和确认密码不一致, 请重新输入...");
                }
            }
            System.out.println("请输入电子邮箱:");
            String email = sc.nextLine();
            System.out.println("请输入性别:");
            String sex = sc.nextLine();
            System.out.println("请输入出生日期:");
            String birthday = sc.nextLine();
    
            // 把用户对象添加到集合中
            list.add(new User(username, phoneNumber, password, confirm, email, sex, birthday));
    
            // 遍历集合
            for (int i = 0; i < list.size(); i++) {
                // 获取集合中的用户对象
                User u = list.get(i);
                System.out.println(u.getUsername() + "---" + u.getPhonNumber() + "---" + u.getPassword() 
                + "---" + u.getConfirm()  + "---" + u.getEmail() + "---" + u.getSex() + "---" + u.getBirthday() );
            }
        }
    }
    
    package com.itheima.anonymous;
    // 用户类
    public class User {
        private String username;
        private String phoneNumber;
        private String password;
        private String confirm;
        private String email;
        private String sex;
        private String birthday;
    
    
        public User() {
        }
    
        public User(String username, String phonNumber, String password, String confirm, String email, String sex, String birthday) {
            this.username = username;
            this.phoneNumber = phonNumber;
            this.password = password;
            this.confirm = confirm;
            this.email = email;
            this.sex = sex;
            this.birthday = birthday;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPhonNumber() {
            return phoneNumber;
        }
    
        public void setPhonNumber(String phonNumber) {
            this.phoneNumber = phonNumber;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public String getConfirm() {
            return confirm;
        }
    
        public void setConfirm(String confirm) {
            this.confirm = confirm;
        }
    
        public String getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    
        public String getSex() {
            return sex;
        }
    
        public void setSex(String sex) {
            this.sex = sex;
        }
    
        public String getBirthday() {
            return birthday;
        }
    
        public void setBirthday(String birthday) {
            this.birthday = birthday;
        }
    }
    

3 继承

3.1 为什么学习继承 ?
  • 继承是将多个类的相同属性和行为抽取到单独一个类中,那么多个类无需再定义这些共性属性和行为,只要继承这个单独类即可继承这些属性和行为了
  • 多个类称为子类(派生类),单独的这个类称为父类(基类 或超类)
3.2 继承的格式 ?
  • 使用关键字extends进行连接子类与父类
  • 举例 : public class Student extends People{ … }
3.3 继承的好处 ?
  • 提高代码的复用性
  • 提高代码的维护性
  • 让类与类产生了关系(继承关系) , 是多态的前提
package com.itheima.extends_demo;
/*

    学生类 : 姓名(name) , 课程名称(course) , 所在班级(className)  , 查看课表(lookForm) , 填写反馈数据(write)
    老师类 : 姓名(name) , 课程名称(course) , 部门名称(department) , 查看课表(lookForm) , 发布试题(release)

    设计 : 把学生类 和 老师类的共性内容抽取到一个单独的类中(Person),存储共性内容
    父类 : 姓名(name) , 课程名称(course) ,  查看课表(lookForm)

 */
public class ExtendsDemo1 {
    public static void main(String[] args) {
        Student s = new Student();

        s.setName("张三");
        s.setCourse("Java");
        s.setClassName("三年二班");

        s.lookForm();
        s.write();
    }
}
package com.itheima.extends_demo;
/*
    此类定义的是子类的共性成员
 */
public class Person {
    private String name;
    private String course;

    public String getName() {
        return name;
    }

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

    public String getCourse() {
        return course;
    }

    public void setCourse(String course) {
        this.course = course;
    }

    public void lookForm(){
        System.out.println("查看课表");
    }

}

package com.itheima.extends_demo;
/*
    子类只需要定义自己特有的成员 , 共性的成员需要抽取到父类中
 */
public class Student extends Person{
    private String className;

    public String getClassName() {
        return className;
    }

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

    public void write(){
        System.out.println("填写反馈数据");
    }
}

package com.itheima.extends_demo;

/*
    子类只需要定义自己特有的成员 , 共性的成员需要抽取到父类中
 */
public class Teacher extends Person {

    private String department;

    public String getDepartment() {
        return department;
    }

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

    public void release() {
        System.out.println("发布试题....");
    }
}

3.5 继承后,子类对象内存图解

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3.6 继承的特点
  • Java只支持单继承 , 不支持多继承 , 但是可以多层继承
    • 简单理解 : 一个儿子只能有一个亲爹
  • 为什么不支持多继承 ?
    • 因为一个子类如果继承多个父类 , 父类有相同的方法声明, 子类会产生继承冲突 , 所以不允许
3.7 继承中成员的发访问特点
  • 成员变量
    • 访问特点-就近原则 : 局部有访问局部的 , 局部没有访问本类成员的 , 本类成员没有访问父类非私有成员
  • 成员方法
    • 访问特点-就近原则 : 子类有调用子类的 , 子类没有调用父类的
  • 构造方法
    • 子类所有的构造方法都会默认去访问父类的空参数构造方法
      • 原因 : 因为子类在初始化时 , 可能会用到父类的数据 , 所以通过访问父类的构造 , 先给父类进行初始化
    • 如果进行初始化呢 ?
      • 每个构造方法中默认第一条语句都会有一个super()
    • 如果父类没有空参数构造 , 那么子类如果进行给父类初始化 ?
      • 子类可以通过super(…)访问父类的有参数构造方法
      • 子类通过this(…)访问子类的有参构造 , 在通过有参构造区访问父类的有参构造 , 不推荐
    • 注意事项 :
      • super(…) 和 this(…) 因为二者都需要放在构造方法的第一条可执行语句, 所以二者不能共存
3.8 方法重写
  • 什么是方法重写 ?
    • 子类和父类出现了一模一样的方法的声明(方法名 , 参数列表)
  • 为什么要学习方法重写 ?
    • 当子类需要使用父类的功能 , 但是父类的功能又满足不了子类 , 那么子类需要重写 , 这样既可以使用父类的功能 ,也可以增加新的功能
  • 如果进行方法重写 ?
    • 子类和父类的方法声明一样 , 方法体中的内容重新定义
  • Override注解是做什么的,有什么用?
    • @Override是放在重写后的方法上,作为重写是否正确的校验注解,加上该注解后如果重写错误,编译阶段会出现错误提示。建议重写方法都加@Override注解,代码安全,优雅!
  • 方法重写的注意事项 ?
    • 私有的方法无法重写
    • 重写的方法与被重写的方法 , 名字 , 参数列表需要保持一致
    • 子类重写父类方法时,子类方法访问权限必须大于或者等于父类方法权限 (暂时了解 :缺省 < protected < public)
      • 一般保持一致即可
3.9 this和super关键字的区别
  • this : 代表的是本类的对象
  • super : 代表的是父类数据存储空间(可以看做成父类的对象)
  • 使用 :
    • 调用变量 :
      • this.变量名 : 访问本类的成员变量
      • super.变量名 : 访问父类的成员变量
    • 调用方法 :
      • this.方法名(…) : 访问本类的成员方法
      • super.方法名(…): 访问父类的成员方法
    • 调用构造 :
      • this.构造方法名(…) : 访问本类的构造方法
      • super.构造方法名(…) : 访问父类的构造方法

4 抽象类

4.1 抽象类 :
  • 抽象类其实就是为抽象方法提供存活的空间 , 需要在类的前面加上上abstract关键字进行修饰
  • 抽象类的作用主要是规范子类必须实现某种规则
4.2 抽象方法 :
  • 一个方法要么有方法体 , 要么是一个抽象方法
4.3 抽象类的注意事项 :
  • 抽象方法和抽象类必须使用abstract关键字进行修饰
  • 抽象类中可以抽象方法 , 也可以有非抽象方法 , 抽象方法必须存在抽象类中
    • 抽象方法的作用 : 让子类必须实现此功能
    • 非抽象方法的作用 : 让子类去继承此功能
  • 抽象类不能实例化
  • 抽象类的子类
    • 要么是一个抽象类
    • 要么重写所有的抽象方法
/*
    注意事项
        1 抽象方法和抽象类必须用关键字 abstract
        2 抽象类中可以有抽象方法 , 也可由非抽象方法
          但是抽象方法必须存在抽象类中
          非抽象方法 : 让子类去继承 , 提高代码的复用性
          抽象方法 : 让子类必须完成某些功能(规范)
        3 抽象类不能进行实例化(不能创建对象)
        4 抽象的子类
            要么重写所有的抽象方法
            要么这个子类是一个抽象类

        抽象类虽然不能创建对象 , 但是存在构造方法
        构造方法存在的意义 : 让子类去通过super访问 , 从而给抽象类中的私有变量赋值
 */
public class AnimalTest {
    public static void main(String[] args) {
        // 抽象类不能进行实例化(不能创建对象)
        // Animal a = new Animal();
    }
}
// 抽象类案例
public abstract class Animal {
    private String breed;
    private String color;

    public Animal() {
    }

    public Animal(String breed, String color) {
        this.breed = breed;
        this.color = color;
    }

    public String getBreed() {
        return breed;
    }

    public void setBreed(String breed) {
        this.breed = breed;
    }

    public String getColor() {
        return color;
    }

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

    // 抽象方法
    public abstract void eat();

    public void drink(){
        System.out.println("喝水....");
    }
}

public class Dog extends Animal{
    public Dog(){
    }

    public Dog(String breed , String color){// breed = "边牧" ,color = "黑白"
        super(breed , color);
    }
    @Override
    public void eat() {
        System.out.println("狗吃骨头!");
    }
}
/*
    需求:
    定义猫类(Cat)和狗类(Dog)

	猫类成员方法:eat(猫吃鱼)drink(喝水…)
	狗类成员方法:eat(狗吃肉)drink(喝水…)

	向上抽取父类 :
	    Animal类 : 品种 , 颜色 , eat();  , drink(){喝水…}

 */
public class AnimalTest {
    public static void main(String[] args) {
        // 空参构造 + set
//        Dog d1 = new Dog();
//        d1.setBreed("哈士奇");
//        d1.setColor("黑白相间");
//        System.out.println(d1.getBreed() + "---" + d1.getColor());
//        d1.drink();
//        d1.eat();

        // 全参构造
        Dog d2 = new Dog("边牧" , "黑白");
        System.out.println(d2.getBreed() + "---" + d2.getColor());
    }
}

5 模板设计模式

5.1 设计模式
  • 一套良好的编码风格 , 经过众多的开发人员不断的测试总结而来
5.2 模板设计模式
  • 可以把抽象类 , 看做一个模板 , 非抽象方法理解为模板的通过格式 , 抽象方法是使用者具体完成的业务逻辑

  • 模板已经定义好了通用的结构 , 使用者只要关心自己需要的那部分实现的功能即可

public abstract class Template {

    public void write() {
        System.out.println("<<我的爸爸>>");

        // 正文
        body();

        System.out.println("啊~这就是我的爸爸.");
    }

    public abstract void body();
}

public class Tom extends Template {
    @Override
    public void body() {
        System.out.println("那是一个秋天 , 风儿那么缠绵 ,记忆中,那天爸爸骑车送我放学回家 , " +
                "我的脚卡在了自行车链中 , 爸爸蹬不动, 他就站起来蹬!"
        );
    }
}

public class Test {
    public static void main(String[] args) {
        Tom tom = new Tom();
        tom.write();
    }
}

今日内容

1 关键字

2 代码块

3 接口

4 枚举

1 Java中的关键字

1.1 static关键字

  • static关键字 :
    • 静态的意思 , 可以修饰变量 , 也可以修饰方法 , 被static修饰的成员 , 我们叫做静态成员
  • static特点 :
    • 静态成员被所类的所有对象共享
    • 随着类的加载而加载 , 优先于对象存在
    • 可以通过对象调用 , 也可以通过类名调用 , 建议使用类名
  • public class Student {
        String name;
        int age;
        static String school;
    
        public void show() {
            System.out.println(name + "---" + age + "---" + school);
        }
    }
    /*
        static关键字的特点 :
            1 静态成员被该类的所有对象的进行共享
            2 静态成员可以通过类名调用 , 也可以通过对象进行调用 , 推荐使用类名
            3 静态成员随着类的加载而加载 , 优先于对象存在
     */
    public class StudentTest {
        public static void main(String[] args) {
            Student.school = "传智专修学院";
    
            Student s = new Student();
            s.name = "张三";
            s.age = 23;
            s.show();
    
            Student s2 = new Student();
            s2.show();
    
        }
    }
    
  • static内存图解

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • static关键字的注意事项
    • 静态方法中只能调用静态成员
    • 非静态方法中可以调用任何成员
    • 静态方法中不能存在this关键字

1.2 final关键字

  • final关键字 : 翻译中文代表最终的意思 , 是java的一个关键字 也是一个修饰符 , 可以修饰类 , 可以修饰方法 , 也可以修饰变量

  • final关键字修饰的特点

    • final修饰的类 : 不能被继承 , 没有子类(太监类)
    • fina修饰的方法 : 不能被重写
    • final修饰的变量
      • 基本数据类型 : 值不能改变
      • 引用数据类型 : 地址不可发生改变 , 对象的属性可改变
      • 注意 :
        • 被final修饰的变量 , 我们叫做自定义常量 , 命名规范 : 每个字母需要大写 , 多个单词之间用下划线分割
        • final修饰成员变量需要注意初始化时机的问题 , 1) 直接赋值 , 2) 在构造方法执行完毕前赋值
  • package com.itheima.final_demo;
    
    /*
        final的特点
            final修饰的类 , 不能被继承, 也就是没有子类
            final修饰的方法 , 不能被重写
            final修饰的变量
                基本数据类型 : 值不可以发生改变
                引用数据类型 : 地址不可发生改变 , 对象的内容可以发生改变
    
            注意 :
                1 被final修饰的变量 , 我们叫做自定义常量 , 命名规范 : 每个字母需要大写 , 多个单词之间用下划线分割
                2 final修饰成员变量需要注意初始化时机的问题
                  1) 直接赋值
                  2) 在构造方法执行完毕前赋值
     */
    public class FinalDemo1 {
        public static void main(String[] args) {
            // final修饰的基本数据类型变量 , 值不能被修改
    //        final int num = 10;
    //        num = 20;
    //        System.out.println(num);
    
            final int[] arr = {1, 2, 3, 4, 5};
            // final修饰的引用数据类型 , 地址不可改发生改变
            // arr = new int[3];
            // final修饰的引用数据类型 , 对象中的内容可以发生改变
            arr[0] = 100;
    
        }
    }
    
    // final修饰的类 , 不能被继承, 也就是没有子类
    //final class Person {
    //
    //}
    
    class Person {
        // final修饰的方法 , 不能被重写
        public final void eat() {
    
        }
    }
    
    class Student extends Person {
        // final修饰成员变量需要注意初始化时机的问题
        // 要么直接赋值 , 要么在构造方法执行完毕前赋值
        // final int num = 10;
        final int num;
    
        public Student() {
            num = 100;
        }
    
        //    @Override
    //    public void eat() {
    //        super.eat();
    //    }
    }
    
    

1.3 Java中的权限修饰符

  • public – protected – 默认的 – private

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    package com.itheima.permissions_demo1;
    
    /*
        public -- protected -- 默认的 -- private
     */
    public class Fu {
        public void method1() {
        }
    
        protected void method2() {
        }
    
        void method3() {
        }
    
        private void method4() {
        }
    
        // 同一个包中同一个类
        public void show(){
            method1();
            method2();
            method3();
            method4();
        }
    
    }
    ===================================================
    
    package com.itheima.permissions_demo1;
    
    public class Test {
        // 同一个包中 , 无关类
        public void show(){
            Fu f = new Fu();
            f.method1();
            f.method2();
            f.method3();
            // f.method4();
        }
    }
    ====================================================
    package com.itheima.permissions_demo1;
    
    public class Zi extends Fu {
        // 用一个包中 , 有子类父关系
        public void show(){
            method1();
            method2();
            method3();
            // method4();
        }
    }
    
    
    package com.itheima.permissions_demo2;
    
    import com.itheima.permissions_demo1.Fu;
    
    public class Test {
        // 不同包中 , 无关类
        public void show(){
            Fu f = new Fu();
            f.method1();
            // f.method2();
            // f.method3();
            // f.method4();
        }
    }
    
    ======================================
        
    package com.itheima.permissions_demo2;
    
    import com.itheima.permissions_demo1.Fu;
    
    public class Zi extends Fu {
        // 不同包中 , 有子类父关系
        public void show(){
            method1();
            method2();
            // method3();
            // method4();
        }
    }
    
    

2 代码块

2.1 构造代码块

  • 构造方法块 : 用一对大括号表示, 定义在类中方法外
  • 执行时机 : 每次构造方法执行前, 都会执行构造代码块
  • 作用 : 抽取构造方法中共性内容
package com.itheima.code_block;
/*
    构造代码块
 */
public class Student {
    final int NUM;

    {
        NUM = 10;
        System.out.println("构造代码块...");
    }

    public Student() {
        System.out.println("空参构造...");
    }

    public Student(int a) {
        System.out.println("有参构造...");
    }
}

class StudentTest {
    public static void main(String[] args) {
        Student s = new Student();
        Student s2 = new Student(10);


    }
}

2.2 静态代码块

  • 静态代码块 : 在一对大括号前加上static关键字 , 定义在类中方法外

  • 执行时机 : 随着类的加载而加载 , 只加载一次

  • 作用 : 一般用于给静态成员初始化

    package com.itheima.code_block.static_demo;
    /*
      静态代码块 :
          1 定义的位置 : 在一对大括号前加上static , 定义在类中方法外
          2 执行时机 ; 随着类的加载而执行, 只加载一次
          3 可以给类中的静态成员进行初始化数据
     */
    public class Test {
      public static void main(String[] args) {
          new Student();
          new Student(10);
      }
    }
    

class Student {
static {
System.out.println(“静态代码块”);
}

public Student() {
    System.out.println("空参构造");
}

public Student(int a) {
    System.out.println("有参构造");
}

}

### 2.3 局部代码块

- 成员代码块 : 用一对大括号表 , 可以定义在任何的局部位置 , 方法中居多
- 执行时机 : 正常执行(从上往下依次执行)
- 控制变量的局部变量的声明周期

​```java
package com.itheima.code_block.local_demo;
/*
  局部代码块 :
      1 位置 : 可以定义任何的局部的位置 , 方法中居多
      2 执行时机 : 正常执行(从上往下依次执行)
      3 作用 : 控制变量的生命周期 , 变量在使用完毕, 及时释放内存
*/
public class Test {
  public static void main(String[] args) {
      int num1 = 10;
      System.out.println(num1);

      // 局部代码块
      {
          int num2 = 20;
          System.out.println(num1);
          System.out.println(num2);
      }

      System.out.println(num1);
//        System.out.println(num2);// 作用域不够 , 报错

  }
}

3 接口

3.1 接口的介绍

  • 接口 : 如果一个类中都是抽象方法 , 那么这个类应该是定义规则的类 , 我们应该把此类定义成接口,接口是一种引用数据类型
  • 作用 :
    • 用于定义规则
    • 程序的扩展性

3.2 接口的定义和特点

  • 定义接口的关键字使用interface
    • public interface 接口名{ … }
  • 类与接口之间的关系是实现关系 , 用关键字implements进行连接
    • public class 类名 implements 接口名 { … }
  • 接口不能实例化
  • 接口的子类我们叫做实现类
    • 要么重写接口中所有的抽象方法
    • 要么实现类是一个抽象类
  • 注意 : 类与接口的关系是实现关系 , 一个类可以实现多个接口,调用逗号分隔 , 还可以继承一个类的同时 , 实现多个接口
package com.itheima.interface_demo;

public interface Inter {
    public abstract void show();
    public abstract void method();
}

interface Inter2 {

}
package com.itheima.interface_demo;

public class InterImpl extends Object implements Inter ,Inter2{
    @Override
    public void show() {
    }

    @Override
    public void method() {
    }
}

package com.itheima.interface_demo;

/*
    1 定义接口的关键字使用interface
      public interface 接口名{ ... }
    2 类与接口的关系是实现关系 , 使用implements进行连接
      public class 类名 implements 接口名{ ... }
    3 接口不能实例化(不能创建对象)
    4 接口的子类,我们叫做实现类
        要么重写接口中所有的抽象方法
        要么这个实现类是一个抽象类

    注意 : 类与接口的关系是实现关系 , 一个类可以实现多个接口
    还可以继承一个类的同时 , 实现多个接口

 */
public class InterfaceDemo1 {
    public static void main(String[] args) {
        // 接口不能实例化(不能创建对象)
        // Inter inter = new Inter();

    }
}

3.3 接口的成员特点

  • 成员变量 : 都是常量 , 默认修饰符 public static final
  • 构造方法 : 没有构造方法
  • 成员方法 : 只能是抽象方法 , 默认修饰符public abstract
    • 关于JDK8和JDK9版本对接口增加了一些方法新特性
package com.itheima.interface_demo;

public interface Inter {
    // 是一个常量 , 默认修饰符 public static final
    public static final int num = 10;
    
    // 不存在构造方法
    // public Inter(){}

    public abstract void show();
    public abstract void method();
}

interface Inter2 {

}
public class InterfaceDemo1 {
    public static void main(String[] args) {
        // 接口不能实例化(不能创建对象)
        // Inter inter = new Inter();

        // 成员变量被static修饰
        System.out.println(Inter.num);
        // 是一个常量 , 只能赋值一次
        // Inter.num = 20;
    }
}

3.4 接口的案例

package com.itheima.interface_demo.interface_test;

public interface player {
    public abstract void play();
    public abstract void pause();
    public abstract void stop();
}

package com.itheima.interface_demo.interface_test;

public class MP3 implements player {
    @Override
    public void play() {
        System.out.println("MP3开始播放音乐");
    }

    @Override
    public void pause() {
        System.out.println("MP3开始暂停");
    }

    @Override
    public void stop() {
        System.out.println("MP3开始关闭音乐");
    }
}

package com.itheima.interface_demo.interface_test;

public class MP4 implements player {
    @Override
    public void play() {
        System.out.println("MP4播放音乐");
    }

    @Override
    public void pause() {
        System.out.println("MP4暂停音乐");
    }

    @Override
    public void stop() {
        System.out.println("MP4关闭音乐");
    }
}

package com.itheima.interface_demo.interface_test;
/*
   需求 :
   1  创建一个播放接口 player
   2  定义三个抽象方法 , 播放(play),暂停(pause),停止(stop)
   3  定义MP3类,MAP4类,手机类,实现接口,重写抽象方法

 */
public class PlayerTest {
    public static void main(String[] args) {
        MP3 mp3 = new MP3();
        mp3.play();
        mp3.pause();
        mp3.stop();
        System.out.println("=============");
        MP4 mp4 = new MP4();
        mp4.play();
        mp4.pause();
        mp4.stop();
    }
}

3.5 接口中成员方法的特点

  • DK8版本之前 : 只能是抽象方法
  • JDK8版本 :
    • 默认方法
      • JDK8可以在接口中定义非抽象方法(带有方法体的方法) , 需要使用default进行修饰 , 其实就是默认方法
      • 作用 : 解决接口升级的问题
      • 格式 : public default 返回值类型 方法名(参数) { … }
      • 注意事项 :
        • 默认方法不是抽象方法 , 可以继承 , 也可以重写 , 重写需要去掉default关键字
        • public 可以省略 , default不能省略
        • 如果实现类实现了多个接口 , 有相同的方法声明 , 那么实现类必须重写该方法
    • 静态方法
      • JDK8可以在接口中定义静态方法 , 也是有方法体的方法
      • 作用 : 方便调用此功能
      • 格式 : public static 返回值类型 方法名(参数){ … }
      • 注意事项
        • 静态方法只能通过接口名调用 , 不能通过实现类名字和对象调用
        • public可以省略 , static不能省略
  • JDK9版本 :
    • 私有方法
      • JDK9中新增私有方法
      • 作用 : 抽取默认方法中共性内容
      • 格式 : private 返回值类型 方法名(参数){ … }
      • 注意 : 要想抽取静态方法中的共性内容 , 需要对私有方法加上关键字static

4 枚举

  • 枚举 : 当一个变量有几种固定可能的取值时,就可以将它定义为枚举类型

  • 作用 : 更贴切 , 加见名之意

  • 定义 : public enum 枚举名{ … }

  • 使用 : 通过枚举类型名直接引用枚举项即可,例如Sex.BOY、Sex.GIRL
    注意 : 每个枚举项都相当于枚举的对象

  • 案例代码

    package com.itheima.enum_demo;
    
    public enum Sex {
        // 每一个枚举项都是枚举的对象
        GIRL("女孩"), BOY("男孩"), YAO;
    
        // 成员变量
        private String name;
    
        // 空参构造方法
        private Sex() {
        }
    
        // 有参构造方法
        private Sex(String name) {
            this.name = name;
        }
    
        // 成员方法
        public String getName() {
            return name;
        }
    }
    
    class Test {
        public static void main(String[] args) {
            System.out.println(Sex.GIRL.getName());
            System.out.println(Sex.BOY.getName());
            System.out.println(Sex.YAO.getName());
        }
    }
    
  • 枚举的本质 :

    • 枚举其实本质上是一个类,每一个枚举项是本枚举类类型的一个对象。我们可以使用JDK提供的反编译命令,将枚举的字节码进行反编译查看
      • 枚举本质上就是最终类
      • 枚举项,就是枚举类的对象,而且是静态的常量。
      • 有私有的构造方法
      • 静态代码块
      • 继承了lang包下的Enum
  • 组成部分 :

    • 构造器,成员方法,成员变量等 , 但是定义的成分一定要在枚举项之后,而且最后一个枚举项必须要有分号结束。

day03-多态 ,内部类,常用API

今日目标

  • 多态
  • 内部类
  • 常用API

1 多态

1.1 面向对象三大特征 ?

  • 封装 , 继承 , 多态

1.2 什么是多态 ?

  • 一个对象在不同时刻体现出来的不同形态

  • 举例 : 一只猫对象

    • 我们可以说猫就是猫 : Cat cat = new Cat();
    • 我们也可以说猫是动物 : Animal cat = new Cat();
    • 这里对象在不同时刻,体现出来的不同形态 , 我们就可以理解为多态

1.3 多态的前提

  • 有继承/实现关系
  • 有方法重写
  • 父类的引用指向子类的对象
package com.itheima.polymorphism_demo;
/*
    多态的三个前提条件
        1 需要有继承/实现关系
        2 需要有方法重写
        3 父类的引用指向子类的对象
 */
public class AnimalTest {
    public static void main(String[] args) {
        // 3 父类的引用指向子类的对象
        // 多态形式对象
        Animal a = new Cat();
    }
}

class Animal{
    public void eat(){
        System.out.println("吃东西");
    }
}

class Cat extends Animal{
    @Override
    public void eat() {
        System.out.println("猫吃鱼....");
    }
}

1.4 多态的成员访问特点

  • 构造方法 : 和继承一样 , 子类通过super()访问父类的构造方法
  • 成员变量 : 编译看左边(父类) , 执行看左边(父类)
  • 成员方法 : 编译看左边(父类) , 执行看右边(子类)
package com.itheima.polymorphism_demo;

/*
    多态的成员访问特点 :
        1 构造方法 : 和继承一样 , 都是通过super()访问父类的构造方法
        2 成员变量 : 编译看左边(父类) , 执行看左边(父类)
        3 成员方法 : 编译看左边(父类) , 执行看右边(子类) , 注意 , 如果执行时
            1) 子类没有回动态去找父类中的方法
            2) 子类的特有方法无法进行调用(多态的缺点)
 */
public class MemberTest {
    public static void main(String[] args) {
        // 父类的引用指向子类的对象
        Fu f = new Zi();

        // 多态对象调用成员变量
        System.out.println(f.num);

        // 多态对新乡调用调用成员方法
        f.show();

        // 多态对象不能调用子类特有的方法
        // f.show2();
    }
}

class Fu {
    int num = 100;

    public void show() {
        System.out.println("父类的show方法");
    }
}

class Zi extends Fu {
    int num = 10;

    public void show() {
        System.out.println("子类的show方法");
    }

    public void show2(){
        System.out.println("子类特有的方法");
    }
}

1.5 多态的优缺点

  • 优点 : 提高代码的扩展性
  • 缺点 : 不能调用子类特有的功能
package com.itheima.polymorphism_test;

public abstract class Animal {
    private String breed;
    private String color;

    public Animal() {
    }

    public Animal(String breed, String color) {
        this.breed = breed;
        this.color = color;
    }

    public String getBreed() {
        return breed;
    }

    public void setBreed(String breed) {
        this.breed = breed;
    }

    public String getColor() {
        return color;
    }

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

    public abstract void eat();
}

package com.itheima.polymorphism_test;

public class Cat extends Animal {
    public Cat() {
    }

    public Cat(String breed, String color) {
        super(breed, color);
    }

    @Override
    public void eat() {
        System.out.println("猫吃鱼...");
    }

    public void catchMouse() {
        System.out.println("抓老鼠...");
    }
}

package com.itheima.polymorphism_test;

public class Dog extends Animal {
    public Dog() {
    }

    public Dog(String breed, String color) {
        super(breed, color);
    }

    @Override
    public void eat() {
        System.out.println("狗吃骨头!");
    }

    public void lookDoor(){
        System.out.println("狗看门...");
    }
}

package com.itheima.polymorphism_test;

public class Pig extends Animal {
    public Pig() {
    }

    public Pig(String breed, String color) {
        super(breed, color);
    }

    @Override
    public void eat() {
        System.out.println("猪拱白菜...");
    }

    public void sleep() {
        System.out.println("一直再睡...");
    }
}

package com.itheima.polymorphism_test;

/*
    如果方法的参数是一个类的话 , 那么调用此方法需要传入此类的对象 , 或者子类对象

    多态的好处 :
        提高代码的扩展性 , 灵活性
    多态的缺点:
        不能调用子类的特有功能
 */
public class AnimalTest {
    public static void main(String[] args) {
        useAnimal(new Cat());

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

        useAnimal(new Dog());

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

        useAnimal(new Pig());
    }

    public static void useAnimal(Animal a){// Animal a = new Dog()
        a.eat();
        // 多态不能访问子类特有的功能
        // 如果解决 ?

        // 向下转型
        if(a instanceof Cat) {
            Cat cat = (Cat) a;
            cat.catchMouse();
        }
        if(a instanceof Dog) {
            Dog dog = (Dog) a;
            dog.lookDoor();
        }

        if(a instanceof Pig) {
            ((Pig) a).sleep();
        }
    }

//    // 定义一个使用猫类的方法
//    public static void useAnimal(Cat c) {// Cat c = new Cat();
//        c.eat();
//        c.catchMouse();
//    }
//
//    // 定义一个使用狗类的方法
//    public static void useAnimal(Dog d) {// Dog d = new Dog();
//        d.eat();
//        d.lookDoor();
//    }
//
//    // 定义一个使用猪类的方法
//    public static void useAnimal(Pig pig) {
//        pig.eat();
//        pig.sleep();
//    }
}

1.6 多态的转型

  • 向上转型 : 把子类类型数据转成父类类型数据 Animal a = new Cat();
  • 向下转型 : 把父类类型数据转成子类类型数据 Cat cat = (Cat)a;

1.7 多态的转型注意

  • 如果被转的对象 , 对应的实际类型和目标类型不是同一种数据类型 , 那么转换时会出现ClassCastException异常

  • 异常代码如下
    public static void main(String[] args) {
        Animal a = new Cat();
        useAnimal(a);
    }
    public static void useAnimal(Animal a) {
        Dog d = (Dog) a;
        d.eat();
    }
    
    

1.8 解决转型安全隐患

  • 使用关键字 instanceof
  • 作用 : 判断一个对象是否属于一种引用数据类型
  • 格式 : 对象名 instanceof 引用数据类型
    • 通俗的理解:判断关键字左边的变量,是否是右边的类型,返回boolean类型结果

2 内部类

2.1 内部类的分类

  • 什么是内部类 ?
    • 一个A类 中 定义一个B类 , 那么B类就属于A类的内部类 , A类就属于B类的外部类

      外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 什么时候使用内部类 ?
    • 多个事物之间有包含关系, 可以使用内部类
  • 内部类分类 ?
    • 成员内部类
    • 局部内部类
    • 匿名内部类

2.2 成员内部类

  • 定义的位置 : 类中方法外

  • 创建成员内部类对象格式 : 外部类名.内部类名 对象名 = new 外部类名().new 内部类名(参数);

    package com.itheima.innerclass_demo.member_innerclass;
    
    // 外部类
    public class Person {
        // 成员内部类
        public class Heart {
            // 频率变量
            private int rate;
            // 跳动方法
            public void beats() {
                System.out.println("咚咚咚!");
            }
        }
    }
    
    class Test {
        public static void main(String[] args) {
            // 创建内部类对象
            Person.Heart heart = new Person().new Heart();
            // 调用内部类中的方法
            heart.beats();
        }
    }
    
  • 成员内部类访问外部类的成员
    • 在内部类中有代表外部类对象的格式 : 外部类名的.this , 私有的也可以访问
    • 外部类要想访问内部类成员 , 需要创建内部类对象
    package com.itheima.innerclass_demo.member_innerclass;
    
    public class Person {
        private String name = "张三";
        private int num = 10;
        
        // 成员内部类
        public class Heart {
            int num = 100;
            // 频率
            private int rate;
            // 跳动
            public void beats() {
                System.out.println("咚咚咚!");
            }
            // 调用外部类的成员
            public void show(){
                int num = 1000;
                System.out.println(Person.this.name);
                System.out.println(num);// 1000 就近原则
                System.out.println(this.num);// 100
                System.out.println(Person.this.num);// 10
    
            }
        }
    }
    
    class Test {
        public static void main(String[] args) {
            Person.Heart heart = new Person().new Heart();
            heart.beats();
            heart.show();
        }
    }
    

2.3 匿名内部类

  • 匿名内部类 : 没有名字的类 , 一次性产品
  • 使用场景 : 直接调用方法 , 作为方法的传参 , 返回值类型
  • 好处 : 简化代码 , 快速实现接口或者抽象的抽象方法
  • 格式 :
    • new 类名/接口名(){ 重写抽象方法 } 注意 : 此处创建的是子类对象!!!
  • 使用方式 :
    • 直接调用方法
    • 作为方法的参数传递
    • 作为方法的返回值类型
//接口
interface Flyable {
    void fly();
}
// 直接调用方法
Flyable f1 = new Flyable() {
    @Override
    public void fly() {
        System.out.println("不知道什么在飞.....");
    }
};
f1.fly();
// 作为方法的参数传递
showFlyable(
    new Flyable() {
        @Override
        public void fly() {
            System.out.println("不知道什么在飞3333");
        }
    }
);

public static void showFlyable(Flyable flyable) {
	flyable.fly();
}
// 作为方法的返回值类型
public static Flyable getFlyable() {
	return new Flyable() {
        @Override
        public void fly() {
            System.out.println("3333333333333");
        }
    };
}
package com.itheima.innerclass_demo.anonymous_innerclass;

/*
    1 如果方法的参数是一个类的话 , 调用此方法需要传入此类的对象或者此类的子类对象
    2 如果方法的返回值类型是一个类的话 , 需要返回此类的对象 , 或者此类的子类对象

     3 如果方法的参数是一个接口的话 , 调用此方法需要传入此接口的实现类对象
     4 如果方法的返回值类型是一个接口的话 , 需要返回此接口的实现类对象


     匿名内部类 : 代表的就是子类对象!!!
        new 类名/接口名(){
            重写抽象类或者接口中的抽象方法
        };

     使用方向 :
        1 调用方法
        2 作为方法参数传递
        3 作为方法的返回值
 */
public interface Swim {
    public abstract void swimming();
}

class Test {
    public static void main(String[] args) {
//        // 子类对象!!!
        //  1 调用方法
//       new Swim() {
//            @Override
//            public void swimming() {
//                System.out.println("匿名内部类 , 重写了接口中的抽象方法...");
//            }
//        }.swimming();


//        //   2 作为方法参数传递
//        useSwim(new Swim() {
//            @Override
//            public void swimming() {
//                System.out.println("匿名内部类 , 重写了接口中的抽象方法...");
//            }
//        });

//        // 3 作为方法的返回值
//        Swim s = getSwim();
//        s.swimming();
    }


    public static Swim getSwim() {
        return new Swim() {
            @Override
            public void swimming() {
                System.out.println("匿名内部类 , 重写了接口中的抽象方法...");
            }
        };
    }


    /*
        Swim swim = new Swim() {
            @Override
            public void swimming() {
                System.out.println("匿名内部类 , 重写了接口中的抽象方法...");
            }
        };
     */
    public static void useSwim(Swim swim) {
        swim.swimming();
    }
}

3 API

3.1 Object类

  • 概述 : 类Object是类层次结构的根,每个类都把Object作为超类。 所有对象(包括数组)都实现了这个类的方法
  • 方法 : public String toString()
    • 如果一个类没有重写toString方法 , 那么打印此类的对象 , 打印的是此对象的地址值
    • 如果一个类重写了toString方法 , 那么打印此类的对象 , 打印的是此对象的属性值
    • 好处 : 把对象转成字符串 , 快速查看一个对象的属性值
    • 执行时机 : 打印对象时会默认调用toString方法
  • 方法 : public boolean equals()
    • 如果一个类没有重写equals方法 , 那么比较此类的对象 . 比较的是地址值
    • 如果一个类重写了equals方法 . 那么比较此类的对象 , 比较的是属性值是否相等
    • 好处 : 可以比较对象的内容

3.2 Objects类

  • Objects是JDK1.7新增的一个对象工具类,里面都是静态方法可以用来操作对象。比如对象的比较,计算对象的hash值,判断对手是否为空…比如里面的equals方法,可以避免空指针异常
public static boolean equals(Object a, Object b):判断两个对象是否相等
    
public static boolean equals(Object a, Object b) {
    return (a == b) || (a != null && a.equals(b));
}
a.equals(b) :如果a是null值,肯定会空指针
Objects.equals(a,b);:如果a是null,不会导致空指针异常

3.2 Date类

  • 概述 : java.util.Date 表示特定的瞬间,精确到毫秒
  • 构造方法 :
    • public Date(): 创建的对象,表示的是当前计算机系统的时间
    • public Date(long time): Date对象 = 1970/1/1 0:0:0 + time毫秒值
  • 成员方法 :
    • public long getTime(): 返回毫秒值 = 当前Date代表的时间 - 1970/1/1 0:0:0
    • public void setTime(long t): Date对象 = 1970/1/1 0:0:0 + time毫秒值
package com.itheima.api_demo.date_demo;

import java.util.Date;

/*
    Date类 : 代表的是一个瞬间 , 精确到毫秒

    构造方法 :
        public Date() : 代表的是当前系统时间
        public Date(long date) : Date对象 = 1970/1/1 0:0:0 + long类型的毫秒值

    成员方法 :
        public void setTime(long date) : Date对象 = 1970/1/1 0:0:0 + long类型的毫秒值
        public long getTime() : 返回的是毫秒值 = Date代表的时间 - 1970/1/1 0:0:0
 */
public class DateDemo {
    public static void main(String[] args) {
        //  public Date() : 代表的是当前系统时间
//        Date d = new Date();
//        System.out.println(d);

        //  public Date(long date) : Date对象 = 1970/1/1 0:0:0 + long类型的毫秒值
//        Date d2 = new Date(1000L * 60 * 60 * 24); // 1970/1/1 0:0:0 + 一天的毫秒值
//        System.out.println(d2);

        Date d = new Date();
        // public void setTime(long date) : Date对象 = 1970/1/1 0:0:0 + long类型的毫秒值
        // d.setTime(1000L * 60 * 60 * 24);
        System.out.println(d);

        // public long getTime() : 返回的是毫秒值 = Date代表的时间 - 1970/1/1 0:0:0
        // System.out.println(d.getTime());

    }
}

3.3 DateFormat类

  • 概述 : 主要用于操作日期格式的一个类

  • 作用 :

    • 格式化 : Date --> String
    • 解析 : String --> Date
  • 构造方法 :

    • SimpleDateFormat(String pattern) 给定日期模板创建日期格式化对象

      image-20210402212755339
  • 成员方法 :

    • public String format ( Date d ):格式化,将日期对象格式化为字符串
    • public Date parse ( String s ):解析,将字符串解析为日期对象
package com.itheima.api_demo.dateformat_demo;

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

/*
    SimpleDateFormat类 :
        "2021年04月03日 16:48:10"  ---> Date
        Date(Sat Apr 03 16:41:38 CST 2021) --> "2021年04月03日 16:48:10"

        作用 :
            格式化 : Date --> String
            解析   : String --> Date

        构造方法 :
            public SimpleDateFormat(String pattern) : pattern : 字符串类型的日期模板

        成员方法 ;
            public final String format(Date date) : 接收一个Date对象返回指定模式的字符串
            public Date parse(String source) : 接收一个字符串  , 返回一个Date对象



        1 获取当前的日期对象,使用格式:yyyy-MM-dd HH:mm:ss 来表示,例如:2020-10-31 17:00:00【格式化】

        2 将字符串的 2020年10月31日  17:00:00,转换为日期Date对象。【解析】

 */
public class SimpleDateFormatDemo {
    public static void main(String[] args) throws ParseException {
        // 解析   : String --> Date
        String strDate = "2020年10月31日 17:00:00";
        // 注意 : 解析时 , SimpleDateFormat的参数(日期模板) , 必须和要解析字符串的模式匹配
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
        //  public Date parse(String source) : 接收一个字符串  , 返回一个Date对象
        Date date = sdf.parse(strDate);
        System.out.println(date); // Sat Oct 31 17:00:00 CST 2020
    }

    private static void method() {
        // 格式化 : Date --> String
        // 获取当前系统时间
        Date date = new Date();
        // System.out.println(date);// Sat Apr 03 16:53:35 CST 2021

        // public SimpleDateFormat(String pattern) : pattern : 字符串类型的日期模板
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        // public final String format(Date date) : 接收一个Date对象返回指定模式的字符串
        String strDate = sdf.format(date);

        System.out.println(strDate);
    }
}

3.4 Calendar类

  • 概述 :
    • java.util.Calendar类表示日历,内含有各种时间信息,以及获取,计算时间的方法。
    • Calendar本身是一个抽象类,可以通过Calendar提供的静态方法getInstance日历子类对象
  • Calendar常用方法 :
    方法名说明
    public static Calendar getInstance()获取一个它的子类GregorianCalendar对象。
    public int get(int field)获取指定日历字段的时间值。
    public void set(int field,int value)设置指定字段的时间值
    public void add(int field,int amount)为某个字段增加/减少指定的值
  • get,set,add方法参数中都有一个整数类型的参数field,field用来区分当前要获取或者操作的日期时间数据的。field数据的值使用Calender中定义的整数常量来表示

    • Calendar.YEAR : 年
    • Calendar.MONTH :月
    • Calendar.DAY_OF_MONTH:月中的周
    • Calendar.HOUR:小时
    • Calendar.MINUTE:分钟,
    • Calendar.SECOND:秒
    • Calendar.DAY_OF_WEEK:星期
  • 星期对应的关系

    ​ 字段时间值 : 1 2 3 … 7
    ​ 真正的时间 : SUNDAY MONDAY TUESDAY … SATURDAY

  • 月份对应的关系

    ​ 字段时间值 : 0 1 … 11
    ​ 正真的时间 : JANUARY FEBRUARY … DECEMBER

package com.itheima.api_demo.calendar_demo;
/*

    月份对应的关系
    字段时间值     :       0           1           ....        11
    正真的时间     :     JANUARY     FEBRUARY      ....      DECEMBER


 */
import java.util.Calendar;
/*
    成员方法 :
        public int get(int field)	获取指定日历字段的时间值。
        public void set(int field,int value)	设置指定字段的时间值
        public void add(int field,int amount)	为某个字段增加/减少指定的值
 */
public class CalendarDemo {
    public static void main(String[] args) {
        // 获取Calendar对象 , rightNow对象
        Calendar rightNow = Calendar.getInstance();

        // public void set(int field , int value)	设置指定字段的时间值
        // rightNow.set(Calendar.YEAR , 2024);
        // rightNow.set(Calendar.MONTH , 5);
        // rightNow.set(Calendar.DAY_OF_MONTH, 10);

        // public void add(int field,int amount)	为某个字段增加/减少指定的值
        // rightNow.add(Calendar.DAY_OF_MONTH , -3);

        //  public int get(int field)	 : 获取指定日历字段的时间值。
        int year = rightNow.get(Calendar.YEAR);
        int month = rightNow.get(Calendar.MONTH);
        int day = rightNow.get(Calendar.DAY_OF_MONTH);


        System.out.println(year); // 2021
        System.out.println(month);// 3
        System.out.println(day);  // 3
    }
}
package com.itheima.api_demo.calendar_demo;

import java.util.Calendar;

/*
    1 写代码使用get方法,将年,月,日,时,分,秒,周获取出来
    特别注意获取月份,和星期有以下特点:
    直接获取的月份数据是从0开始的, 0表示1月,.....11表示12月
    周的获取,从周日开始计算,1就是周日,2就是周一 ......7就是周六


星期对应的关系
     字段时间值  :   1           2            3          ...        7
     真正的时间  : SUNDAY        MONDAY      TUESDAY     ...        SATURDAY

月份对应的关系
    字段时间值     :       0           1           ....        11
    正真的时间     :     JANUARY     FEBRUARY      ....      DECEMBER

 */
public class Test1 {
    public static void main(String[] args) {
        // 拿到当前时间
        Calendar now = Calendar.getInstance();

        System.out.println(now.get(Calendar.YEAR));
        System.out.println(now.get(Calendar.MONTH));
        System.out.println(now.get(Calendar.DAY_OF_MONTH));
        System.out.println(now.get(Calendar.HOUR));
        System.out.println(now.get(Calendar.MINUTE));
        System.out.println(now.get(Calendar.SECOND));

        int week = now.get(Calendar.DAY_OF_WEEK);// 7
        System.out.println(getWeek(week));// 字段值
    }

    public static String getWeek(int field){
        String[] str = { "" , "SUNDAY" , "MONDAY" , "TUESDAY" , "WEDNESDAY" , "THURSDAY" , "FRIDAY" , "SATURDAY"};
        return str[field];
    }
}
package com.itheima.api_demo.calendar_demo;

import java.util.Calendar;

/*
    2 写代码实现,获取2022年10月1日是星期几?
    参考思路:
    直接获取日历对象,得到的是当前系统的日历时间信息。
    获取日历对象后,要重新设置日期
    再获取星期数据

 */
public class Test2 {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.YEAR, 2022);
        cal.set(Calendar.MONTH, 9);
        cal.set(Calendar.DAY_OF_MONTH, 1);

        int week = cal.get(Calendar.DAY_OF_WEEK);
        System.out.println(getWeek(week));// 字段值
    }

    public static String getWeek(int field){
        String[] str = { "" , "SUNDAY" , "MONDAY" , "TUESDAY" , "WEDNESDAY" , "THURSDAY" , "FRIDAY" , "SATURDAY"};
        return str[field];
    }
}

package com.itheima.api_demo.calendar_demo;

import java.util.Calendar;

/*
    3 计算10000天之后的年月日

    参考思路:
    先获取当前日历对象
    再调用add方法,指定DATE或者DAY_OF_MONTH,添加10000天
    再获取日历的年,月,日

 */
public class Test3 {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.DAY_OF_MONTH, 10000);


        System.out.println(cal.get(Calendar.YEAR));
        System.out.println(cal.get(Calendar.MONTH));
        System.out.println(cal.get(Calendar.DAY_OF_MONTH));
    }
}

3.5 Math类

  • 概述 : Math包含执行基本数字运算的方法,如基本指数,对数,平方根和三角函数。所提供的都是静态方法,可以直接调用

  • 常用方法 :

    public static int abs(int a)获取参数a的绝对值:
    public static double ceil(double a)向上取整
    public static double floor(double a)向下取整
    public static double pow(double a, double b)获取a的b次幂
    public static long round(double a)四舍五入取整

3.6 System类

  • System类包含几个有用的类字段和方法。 它不能被实例化

  • 常用方法 :

    方法名说明
    public static void exit(int status)终止当前运行的 Java 虚拟机,非零表示异常终止
    public static long currentTimeMillis()返回当前时间(以毫秒为单位)

day04-常用API,正则表达式,Collection集合

今日目标

  • BigInteger类
  • BigDecimal类
  • Arrays类
  • 包装类
  • String类的常用方法
  • 正则表达式
  • Collection集合

1 BigInteger类

1.1 概述

  • 概述 : java.math.BigInteger类是一个引用数据类型 , 可以用于计算一些大的整数 , 当超出基本数据类型数据范围的整数运算时就可以使用BigInteger了。

1.2 构造方法

  • 构造方法 : 可以将整数的字符串 . 转成BigInteger类型的对象

1.3 成员方法

  • 方法声明描述
    public BigInteger add (BigInteger value)超大整数加法运算
    public BigInteger subtract (BigInteger value)超大整数减法运算
    public BigInteger multiply (BigInteger value)超大整数乘法运算
    public BigInteger divide (BigInteger value)超大整数除法运算,除不尽取整数部分
package com.itheima.api_demo.biginteger_demo;

import java.math.BigInteger;

/*
    构造方法 :
        BigInteger(String value)	可以将整数的字符串,转换为BigInteger对象
    成员方法 :
        public BigInteger add (BigInteger value)	    超大整数加法运算
        public BigInteger subtract (BigInteger value)	超大整数减法运算
        public BigInteger multiply (BigInteger value)	超大整数乘法运算
        public BigInteger divide (BigInteger value)	超大整数除法运算,除不尽取整数部分

 */
public class BigIntegerDemo {
    public static void main(String[] args) {
        // 获取大整数对象
        BigInteger bigInteger1 = new BigInteger("2147483648");
        // 获取大整数对象
        BigInteger bigInteger2 = new BigInteger("2");
        // public BigInteger add (BigInteger value)	    超大整数加法运算
        BigInteger add = bigInteger1.add(bigInteger2);
        System.out.println(add);

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

        // public BigInteger subtract (BigInteger value)	超大整数减法运算
        BigInteger subtract = bigInteger1.subtract(bigInteger2);
        System.out.println(subtract);

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

        // public BigInteger multiply (BigInteger value)	超大整数乘法运算
        BigInteger multiply = bigInteger1.multiply(bigInteger2);
        System.out.println(multiply);

        System.out.println("=============");
        // public BigInteger divide (BigInteger value)	超大整数除法运算,除不尽取整数部分
        BigInteger divide = bigInteger1.divide(bigInteger2);
        System.out.println(divide);
    }
}

2 BigDecimal类

2.1 概述

  • 概述 : java.math.BigDecimal可以对大浮点数进行运算,保证运算的准确性。float,double 他们在存储及运算的时候,会导致数据精度的丢失。如果要保证运算的准确性,就需要使用BigDecimal。

2.2 构造方法

  • 构造方法 :
    • public BigDecimal(String val) : 将 BigDecimal 的字符串表示形式转换为 BigDecimal

2.3 成员方法

  • 成员方法 :

    • 方法声明描述
      public BigDecimal add(BigDecimal value)加法运算
      public BigDecimal subtract(BigDecimal value)减法运算
      public BigDecimal multiply(BigDecimal value)乘法运算
      public BigDecimal divide(BigDecimal value)除法运算(除不尽会有异常)
      public BigDecimal divide(BigDecimal divisor, int roundingMode)除法运算(除不尽,使用该方法)参数说明:scale 精确位数,roundingMode取舍模式 BigDecimal.ROUND_HALF_UP 四舍五入 BigDecimal.ROUND_FLOOR 去尾法 BigDecimal.ROUND_UP 进一法
package com.itheima.api_demo.bigdecimal_demo;

import java.math.BigDecimal;

/*
    构造方法 :
        public BigDecimal(String val)	将 BigDecimal 的字符串表示形式转换为 BigDecimal
    成员方法 :
        public BigDecimal add(BigDecimal value)	加法运算
        public BigDecimal subtract(BigDecimal value)	减法运算
        public BigDecimal multiply(BigDecimal value)	乘法运算
        public BigDecimal divide(BigDecimal value)	除法运算(除不尽会有异常)
        public BigDecimal divide(BigDecimal value, int scale, RoundingMode roundingMode)	除法运算(除不尽,使用该方法)
        参数说明:
        scale 精确位数,
        roundingMode取舍模式
                   BigDecimal.ROUND_HALF_UP 四舍五入
                   BigDecimal.ROUND_FLOOR 去尾法
                   BigDecimal.ROUND_UP  进一法
 */
public class BigDecimalDemo {
    public static void main(String[] args) {
        BigDecimal bigDecimal1 = new BigDecimal("0.1");
        BigDecimal bigDecimal2 = new BigDecimal("0.2");

        // public BigDecimal add(BigDecimal value)	加法运算
        BigDecimal add = bigDecimal1.add(bigDecimal2);
        System.out.println(add);

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

        // public BigDecimal subtract(BigDecimal value)	减法运算
        BigDecimal subtract = bigDecimal1.subtract(bigDecimal2);
        System.out.println(subtract);

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

        // public BigDecimal multiply(BigDecimal value)	乘法运算
        BigDecimal multiply = bigDecimal1.multiply(bigDecimal2);
        System.out.println(multiply);

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

        // public BigDecimal divide(BigDecimal value)	除法运算(除不尽会有异常)
        // BigDecimal divide = bigDecimal1.divide(bigDecimal2);
        // System.out.println(divide);

        /*
            public BigDecimal divide(BigDecimal divisor, int roundingMode)	除法运算(除不尽,使用该方法)
            参数说明:
            scale 精确位数,
            roundingMode : 取舍模式
                       BigDecimal.ROUND_HALF_UP 四舍五入
                       BigDecimal.ROUND_FLOOR 去尾法
                       BigDecimal.ROUND_UP  进一法
        */

        // BigDecimal divide = bigDecimal1.divide(bigDecimal2, 3, BigDecimal.ROUND_HALF_UP);
        // BigDecimal divide = bigDecimal1.divide(bigDecimal2, 3, BigDecimal.ROUND_FLOOR);
        // BigDecimal divide = bigDecimal1.divide(bigDecimal2, 3, BigDecimal.ROUND_UP);
        // System.out.println(divide);

    }
}

3 Arrays类

3.1 概述

  • 概述 : java.util.Arrays是数组的工具类,里面有很多静态的方法用来对数组进行操作(如排序和搜索),还包含一个静态工厂,可以将数组转换为List集合(后面会讲到集合知识

3.2 构造方法

  • 构造方法 : private Arrays(){}

  • public static void sort(int[] a)按照数字顺序排列指定的数组
    public static String toString(int[] a)返回指定数组的内容的字符串表示形式
package com.itheima.api_demo.arrays_demo;

import java.util.Arrays;
import java.util.Random;

/*
    1 随机生成10个[0,100]之间的整数,放到整数数组中,按照从小到大的顺序排序并打印元素内容。
 */
public class ArraysDemo {
    public static void main(String[] args) {
        // 创建数组
        int[] arr = new int[10];

        // 创建随机数对象
        Random r = new Random();

        // 采用随机数给数组的每一个元素赋值
        for (int i = 0; i < arr.length; i++) {
            arr[i] = r.nextInt(101);
        }

        // 对数组进行排序
        Arrays.sort(arr);

        // 把数组转成指定格式的字符串
        System.out.println(Arrays.toString(arr));
    }
}

4 包装类

4.1 概述

  • 概述 :
    • Java中基本数据类型对应的引用数据类型

4.2 包装类的作用

  • 包装类的作用 :
    • 基本数据类型 , 没有变量 , 没有方法 , 包装类就是让基本数据类型拥有变量和属性 , 实现对象化交互
    • 基本数据类型和字符串之间的转换

4.3 基本数据类型和包装类对应

  • 基本数据类型和包装类的对应关系
    基本数据类型包装类型
    byteByte
    shortShort
    intInteger
    longLong
    floatFloat
    doubleDouble
    charCharacter
    booleanBoolean

4.4 自动装箱和自动拆箱

  • 自动转型和自动拆箱

    • 自动装箱和拆箱是JDK1.5开始的
    • 自动装箱 : 基本数据类型自动转成对应的包装类类型
    • 自动拆箱 : 包装类类型自动转成对应的基本数据类型
    Integer i1 = 10;
    int i2 = i1;
    

4.5 基本数据类型和字符串之间的转换

  • 使用包装类, 对基本数据类型和字符串之间的转换

    • 在开发过程中数据在不同平台之间传输时都以字符串形式存在的,有些数据表示的是数值含义,如果要用于计算我们就需要将其转换基本数据类型.

    • 基本数据类型–> String

      • 直接在数值后加一个空字符串
      • 通过String类静态方法valueOf()
    • String --> 基本数据类型

      • public static byte parseByte(String s):将字符串参数转换为对应的byte基本类型。
        public static short parseShort(String s):将字符串参数转换为对应的short基本类型。
        public static int parseInt(String s):将字符串参数转换为对应的int基本类型。
        public static long parseLong(String s):将字符串参数转换为对应的long基本类型。
        public static float parseFloat(String s):将字符串参数转换为对应的float基本类型。
        public static double parseDouble(String s):将字符串参数转换为对应的double基本类型。
        public static boolean parseBoolean(String s):将字符串参数转换为对应的boolean基本类型。
  • 注意事项 :

    • 包装类对象的初始值为null(是一个对象)

    • Java中除了float和double的其他基本数据类型,都有常量池

      • 整数类型:[-128,127]值在常量池
      • 字符类型:[0,127]对应的字符在常量池
      • 布尔类型:true,false在常量池
    • 在常量池中的数据 , 会进行共享使用 , 不在常量池中的数据会创建一个新的对象

5 String类的常用方法

5.1 常用方法

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

package com.itheima.api_demo.string_demo;

/*
    已知字符串,完成需求
    String str = "I Love Java, I Love Heima";
    判断是否存在  “Java”
    判断是否以Heima字符串结尾
    判断是否以Java开头
    判断 Java在字符串中的第一次出现位置
    判断  itcast 所在的位置

 */
public class Test {
    public static void main(String[] args) {
        String str = "I Love Java, I Love Heima";

        // 判断是否存在  “Java”
        System.out.println(str.contains("Java"));// true

        // 判断是否以Heima字符串结尾
        System.out.println(str.endsWith("Heima"));// true

        // 判断是否以Java开头
        System.out.println(str.startsWith("Java"));// false

        // 判断 Java在字符串中的第一次出现位置
        System.out.println(str.indexOf("Java"));// 7

        // 判断  itcast 所在的位置
        System.out.println(str.indexOf("itcast"));// -1
    }
}

package com.itheima.api_demo.string_demo;

/*
    已知字符串,完成右侧需求
    String str = "I Love Java, I Love Heima";
    需求:
    1.将所有 Love 替换为 Like ,打印替换后的新字符串
    2.截取字符串 "I Love Heima"
    3.截取字符串 "Java"

 */
public class Test2 {
    public static void main(String[] args) {
        String str = "I Love Java, I Love Heima";

        // 1.将所有 Love 替换为 Like ,打印替换后的新字符串
        System.out.println(str.replace("Love", "Like"));
        // 2.截取字符串 "I Love Heima"
        System.out.println(str.substring(13));
        // 3.截取字符串 "Java"
        System.out.println(str.substring(7 , 11));

    }
}

package com.itheima.api_demo.string_demo;

/*
    已知字符串,完成右侧需求
    String str = "I Love Java, I Love Heima";
    需求:
    1 计算字符 a 出现的次数(要求使用toCharArray)
    2 计算字符 a 出现的次数(要求使用charAt)
    3 将字符串中所有英文字母变成小写
    4 将字符串中所有英文字母变成大写
 */
public class Test3 {
    public static void main(String[] args) {
        String str = "I Love Java, I Love Heima";

//        1 计算字符 a 出现的次数(要求使用toCharArray)
        int count1 = 0;
        char[] chars = str.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            if (chars[i] == 'a') {
                count1++;
            }
        }
        System.out.println("字符a出现了" + count1 + "次");


//        2 计算字符 a 出现的次数(要求使用charAt)
        int count2 = 0;
        for (int i = 0; i < str.length(); i++) {
            char ch = str.charAt(i);
            if(ch == 'a'){
                count2++;
            }
        }
        System.out.println("字符a出现了" + count2 + "次");

//        3 将字符串中所有英文字母变成小写
        String s1 = str.toLowerCase();
        System.out.println(s1);

//        4 将字符串中所有英文字母变成大写
        String s2 = str.toUpperCase();
        System.out.println(s2);
    }
}

6 正则表达式

6.1 概述 :

  • 正则表达式通常用来校验,检查字符串是否符合规则的

6.2 体验正则表达式

package com.itheima.regex_demo;

import java.util.Scanner;

/*
    设计程序让用户输入一个QQ号码,验证QQ号的合法性:
    1. QQ号码必须是5--15位长度
    2. 而且首位不能为0
    3. 而且必须全部是数字

 */
public class Test1 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        System.out.println("请输入您的qq号码:");
        String qq = sc.nextLine();

        System.out.println(checkQQ2(qq));

    }

    private static boolean checkQQ(String qq) {
//        1. QQ号码必须是5--15位长度
        if (qq.length() < 5 || qq.length() > 15) {
            return false;
        }
//       2 . 而且首位不能为0
        if (qq.charAt(0) == '0') {
            return false;
        }

//        2. 而且必须全部是数字
        for (int i = 0; i < qq.length(); i++) {
            char ch = qq.charAt(i);
            if (ch < '0' || ch > '9') {
                return false;
            }
        }

        return true;
    }

    // 正则表达式改进
    private static boolean checkQQ2(String qq) {
        return qq.matches("[1-9][0-9]{4,14}");
    }
}

6.3 正则表达式的语法

  • boolean matches(正则表达式) :如果匹配正则表达式就返回true,否则返回false
    • boolean matches(正则表达式) :如果匹配正则表达式就返回true,否则返回false
  • 字符类
    • [abc] :代表a或者b,或者c字符中的一个。
    • [^abc]:代表除a,b,c以外的任何字符。
    • [a-z] :代表a-z的所有小写字符中的一个。
    • [A-Z] :代表A-Z的所有大写字符中的一个。
    • [0-9] :代表0-9之间的某一个数字字符。
    • [a-zA-Z0-9]:代表a-z或者A-Z或者0-9之间的任意一个字符。
    • [a-dm-p]:a 到 d 或 m 到 p之间的任意一个字符
package com.itheima.regex_demo;
/*
    字符类 : 方括号被用于指定字符
    [abc] :代表a或者b,或者c字符中的一个。
    [^abc]:代表除a,b,c以外的任何字符。
    [a-z] :代表a-z的所有小写字符中的一个。
    [A-Z] :代表A-Z的所有大写字符中的一个。
    [0-9] :代表0-9之间的某一个数字字符。
    [a-zA-Z0-9]:代表a-z或者A-Z或者0-9之间的任意一个字符。
    [a-dm-p]:a 到 d 或 m 到 p之间的任意一个字符

    需求 :
    1 验证str是否以h开头,以d结尾,中间是a,e,i,o,u中某个字符
    2 验证str是否以h开头,以d结尾,中间不是a,e,i,o,u中的某个字符
    3 验证str是否a-z的任何一个小写字符开头,后跟ad
    4 验证str是否以a-d或者m-p之间某个字符开头,后跟ad
    注意: boolean  matches(正则表达式) :如果匹配正则表达式就返回true,否则返回false
 */
public class RegexDemo {
    public static void main(String[] args) {
//        1 验证str是否以h开头,以d结尾,中间是a,e,i,o,u中某个字符
        System.out.println("had".matches("h[aeiou]d"));

//        2 验证str是否以h开头,以d结尾,中间不是a,e,i,o,u中的某个字符
        System.out.println("hwd".matches("h[^aeiou]d"));

//        3 验证str是否a-z的任何一个小写字符开头,后跟ad
        System.out.println("aad".matches("[a-z]ad"));

//        4 验证str是否以a-d或者m-p之间某个字符开头,后跟ad
        System.out.println("bad".matches("[a-dm-p]ad"));

    }
}
  • 逻辑运算符
    • && :并且
    • | :或者
package com.itheima.regex_demo;
/*
    逻辑运算符 :
        1 && : 并且
        2 |  : 或者

    需求 :
        1 要求字符串是除a、e、i、o、u外的其它小写字符开头,后跟ad
        2 要求字符串是aeiou中的某个字符开头,后跟ad
 */
public class RegexDemo2 {
    public static void main(String[] args) {
//        1 要求字符串是除a、e、i、o、u外的其它小写字符开头,后跟ad
        System.out.println("vad".matches("[a-z&&[^aeiou]]ad"));
//        2 要求字符串是aeiou中的某个字符开头,后跟ad
        System.out.println("aad".matches("[a|e|i|o|u]ad"));
    }
}

  • 预定义字符类
    • “.” : 匹配任何字符。
    • “\d”:任何数字[0-9]的简写;
    • “\D”:任何非数字[^0-9]的简写;
    • “\s” : 空白字符:[ \t\n\x0B\f\r] 的简写
    • “\S” : 非空白字符:[^\s] 的简写
    • “\w” :单词字符:[a-zA-Z_0-9]的简写
    • “\W”:非单词字符:[^\w]
package com.itheima.regex_demo;
/*
    预定义字符 : 简化字符类的书写

    "."  :匹配任何字符。
    "\d" :任何数字[0-9]的简写
    "\D" :任何非数字[^0-9]的简写
    "\s" :空白字符:[\t\n\x0B\f\r] 的简写
    "\S" :非空白字符:[^\s] 的简写
    "\w" :单词字符:[a-zA-Z_0-9]的简写
    "\W" :非单词字符:[^\w]

    需求 :
       1 验证str是否3位数字
       2 验证手机号:1开头,第二位:3/5/8,剩下9位都是0-9的数字
       3 验证字符串是否以h开头,以d结尾,中间是任何字符

 */
public class RegexDemo3 {
    public static void main(String[] args) {
//        1 验证str是否3位数字
        System.out.println("123".matches("\\d\\d\\d"));

//        2 验证手机号:1开头,第二位:3/5/8,剩下9位都是0-9的数字 )
        System.out.println("15188888888".matches("1[358]\\d\\d\\d\\d\\d\\d\\d\\d\\d"));

//        3 验证字符串是否以h开头,以d结尾,中间是任何字符
        System.out.println("had".matches("h.d"));
    }
}

  • 数量词
    • X? : 0次或1次
    • X* : 0次到多次
    • X+ : 1次或多次
    • X{n} : 恰好n次
    • X{n,} : 至少n次
    • X{n,m}: n到m次(n和m都是包含的)
package com.itheima.regex_demo;

/*
    数量词 :
        - X?    : 0次或1次
        - X*    : 0次到多次
        - X+    : 1次或多次
        - X{n}  : 恰好n次
        - X{n,} : 至少n次
        - X{n,m}: n到m次(n和m都是包含的)

    需求 :
      1 验证str是否3位数字
      2 验证str是否是多位(大于等于1次)数字
      3 验证str是否是手机号 ( 1开头,第二位:3/5/8,剩下9位都是0-9的数字)
      4 验证qq号码:1).5--15位;2).全部是数字;3).第一位不是0

 */
public class RegexDemo4 {
    public static void main(String[] args) {
//        1 验证str是否3位数字
        System.out.println("123".matches("\\d{3}"));

//        2 验证str是否是多位(大于等于1次)数字
        System.out.println("123456".matches("\\d+"));

//        3 验证str是否是手机号 ( 1开头,第二位:3/5/8,剩下9位都是0-9的数字)
        System.out.println("15188888888".matches("1[358]\\d{9}"));

//        4 验证qq号码:1).5--15位;2).全部是数字;3).第一位不是0
        System.out.println("122103987".matches("[1-9]\\d{4,14}"));
    }
}

  • 分组括号 :
    • 将要重复使用的正则用小括号括起来,当做一个小组看待
package com.itheima.regex_demo;
/*
    分组括号 : 将要重复使用的正则用小括号括起来,当做一个小组看待
    需求 :  window秘钥 , 分为5组,每组之间使用 - 隔开 , 每组由5位A-Z或者0-9的字符组成 , 最后一组没有 -
    举例 :
        xxxxx-xxxxx-xxxxx-xxxxx-xxxxx
        DG8FV-B9TKY-FRT9J-99899-XPQ4G
    分析:
        前四组其一  :DG8FV-    正则:[A-Z0-9]{5}-
        最后一组    :XPQ4G     正则:[A-Z0-9]{5}

    结果 : ([A-Z0-9]{5}-){4}[A-Z0-9]{5}

 */
public class RegexDemo5 {
    public static void main(String[] args) {
        System.out.println("DG8FV-B9TKY-FRT9J-99899-XPQ4G".matches("([A-Z0-9]{5}-){4}[A-Z0-9]{5}"));
    }
}

  • 字符串中常用含有正则表达式的方法
    • public String[] split ( String regex ) 可以将当前字符串中匹配regex正则表达式的符号作为"分隔符"来切割字符串。
    • public String replaceAll ( String regex , String newStr ) 可以将当前字符串中匹配regex正则表达式的字符串替换为newStr。
package com.itheima.regex_demo;

import java.util.Arrays;

/*

    1 字符串中常用含有正则表达式的方法
        public String[] split ( String regex ) 可以将当前字符串中匹配regex正则表达式的符号作为"分隔符"来切割字符串。
        public String replaceAll ( String regex , String newStr ) 可以将当前字符串中匹配regex正则表达式的字符串替换为newStr。

    需求:
        1 将以下字符串按照数字进行切割
        String str1 = "aa123bb234cc909dd";

        2 将下面字符串中的"数字"替换为"*“a
        String str2 = "我卡里有100000元,我告诉你卡的密码是123456,要保密哦";

 */
public class RegexDemo6 {
    public static void main(String[] args) {
        // 1 将以下字符串按照数字进行切割
        String str1 =  "aa123bb234cc909dd";
        String[] strs = str1.split("\\d+");
        System.out.println(Arrays.toString(strs));

        // 2 将下面字符串中的"数字"替换为"*“a
        String str2 = "我卡里有100000元,我告诉你卡的密码是123456,要保密哦";
        System.out.println(str2.replaceAll("\\d+" , "*"));
    }
}

7 引用数据类型使用

7.1 使用方式

- 基本数据类型可以当做方法的参数,返回值及成员变量使用,传递或者保存的是数据值。
- 引用数据类型也可以当做方法的参数,返回值及字段使用,传递或者保存的是对象的引用(地址)。
- 特别要注意,如果是抽象类或者接口那么传递或者保存的就是子类对象的地址引用了
package com.itheima.datatype_demo;


public interface Player {
    public abstract void play();
}

abstract class Mp3Player implements Player {

}

class IPod extends Mp3Player {
    @Override
    public void play() {
        System.out.println("IPod播放音乐...");
    }
}


package com.itheima.datatype_demo;

/*
   1 分别定义含有Play,Mp3Player , Ipod参数的方法,并调用传入实参进行测试

   2  定义一个学生类,里面定义含有Player, Mp3Player,Ipod类型的成员变量
      创建学生对象并给成员变量赋值

 */
public class PlayerTest {
    public static void main(String[] args) {
        show1(new IPod());
        show2(new IPod());
        show2(new IPod());

        Student s = new Student();
        s.setPlayer(new IPod());
        s.setMp3Player(new IPod());
        s.setiPod(new IPod());
    }

    public static void show1(Player player) {// 方法的参数是一个接口 , 接收实现类对象
        player.play();
    }

    public static void show2(Mp3Player player) {// 方法的参数是一个抽象类 , 接收子类对象
        player.play();
    }

    public static void show3(Mp3Player player) {// 方法的参数是具体的类 , 接收此类对象或者子类对象
        player.play();
    }
}

/*
    定义一个学生类,里面定义含有Player, Mp3Player,Ipod类型的成员变量
    创建学生对象并给成员变量赋值
 */
class Student {
    private Player player;   // 接口的类型的成员变量 , 保存的是实现类对象地址
    private Mp3Player mp3Player; // 实现类的类型的成员变量 , 保存的是子类对象地址
    private IPod iPod; // 具体类类型的成员变量 , 保存的是此类对象或者此类的子类对象地址


    public Player getPlayer() {
        return player;
    }

    public void setPlayer(Player player) {
        this.player = player;
    }

    public Mp3Player getMp3Player() {
        return mp3Player;
    }

    public void setMp3Player(Mp3Player mp3Player) {
        this.mp3Player = mp3Player;
    }

    public IPod getiPod() {
        return iPod;
    }

    public void setiPod(IPod iPod) {
        this.iPod = iPod;
    }
}

8 Collection集合

8.1 集合和数组的区别

  • 长度区别
    • 数组 : 长度固定
    • 集合 : 长度可变
  • 存储数据类型
    • 数组 : 可以存储基本数据类型, 也可以存储引用数据类型
    • 集合 : 只能存储引用数据类型 , 要想存储基本数据类型 , 需要存储对应的包装类类型

8.2 集合的体系

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

8.3 Collection集合常用的方法

  • 在学习集合体系,一般先学习顶层接口。学习了顶层接口的方法,子类型继承而来的方法,就可以不用重复学习。
    • public boolean add(E e): 把给定的对象添加到当前集合中 。
    • public void clear() :清空集合中所有的元素。
    • public boolean remove(E e): 把给定的对象在当前集合中删除。
    • public boolean contains(Object obj): 判断当前集合中是否包含给定的对象。
    • public boolean isEmpty(): 判断当前集合是否为空。
    • public int size(): 返回集合中元素的个数。
    • public Object[] toArray(): 把集合中的元素,存储到数组中

8.4 迭代器

  • 概述 :
    • 迭代器就是对Iterator的称呼 , 专门用来对Collection集合进行遍历使用的。学习迭代器的目的就是为了遍历集合
  • 注意 :
    • 只有继承/实现Iterable接口才具有迭代的能力
    • Collection接口继承了Iterable接口所以 , Collection及其子类都具备迭代的能力
  • 获取迭代器对象的方式
    • 通过Collection集合对象调用Iterable接口中的iterator方法 , 就可以获取迭代器对象
  • Iterator(迭代器)中的方法
    booleanhasNext()如果迭代具有更多元素,则返回 true 。
    Enext()返回迭代中的下一个元素。
    default voidremove()从底层集合中删除此迭代器返回的最后一个元素(可选操作)。
  • 迭代器的注意事项
    • 当迭代器迭代元素完成后,不能继续next获取元素,否则会报:NoSuchElementException
    • 当迭代器在使用的过程中,不能使用集合对象直接增删元素。会导致报错ConcurrentModificationException。如果要删除可以使用迭代器来删除

8.5 增强for循环

  • 增强for循环(foreach),专门用来遍历集合或者数组,底层实现使用迭代器

  • 定义格式 :

    for(元素的类型 变量名 : 数组/单列集合 ){
        变量代表的就是集合或者数组的元素
    }
    

    day05 泛型,数据结构,List,Set

今日目标

  • 泛型使用
  • 数据结构
  • List
  • Set

1 泛型

1.1 泛型的介绍

  • 泛型是一种类型参数,专门用来保存类型用的
    • 最早接触泛型是在ArrayList,这个E就是所谓的泛型了。使用ArrayList时,只要给E指定某一个类型,里面所有用到泛型的地方都会被指定对应的类型

1.2 使用泛型的好处

  • 不用泛型带来的问题
    • 集合若不指定泛型,默认就是Object。存储的元素类型自动提升为Object类型。获取元素时得到的都是Object,若要调用特有方法需要转型,给我们编程带来麻烦.
  • 使用泛型带来的好处
    • 可以在编译时就对类型做判断,避免不必要的类型转换操作,精简代码,也避免了因为类型转换导致的代码异常
 //泛型没有指定类型,默认就是Object
ArrayList list = new ArrayList();
list.add("Hello");
list.add("World");
list.add(100);
list.add(false);
//集合中的数据就比较混乱,会给获取数据带来麻烦
for (Object obj : list) {
    String str = (String) obj;
    //当遍历到非String类型数据,就会报异常出错
    System.out.println(str + "长度为:" + str.length());
}

1.3 泛型的注意事项

  • 泛型在代码运行时,泛型会被擦除。后面学习反射的时候,可以实现在代码运行的过程中添加其他类型的数据到集合

    • 泛型只在编译时期限定数据的类型 , 在运行时期会被擦除

1.4 自定义泛型类

  • 当一个类定义其属性的时候,不确定具体是什么类型时,就可以使用泛型表示该属性的类型

  • 定义的格式
    • 在类型名后面加上一对尖括号,里面定义泛型。一般使用一个英文大写字母表示,如果有多个泛型使用逗号分隔
    • public class 类名<泛型名>{ … }
    举例 : 
    public class Student<X,Y>{  
        X  xObj; 
    } 
    
  • 泛型的确定
    • 当创建此泛型类是 , 确定泛型类中泛型的具体数据类型
  • 练习
package com.itheima.genericity_demo.genericity_class;

import java.time.Period;

/*
    需求 : 定义一个人类,定义一个属性表示爱好,但是具体爱好是什么不清楚,可能是游泳,乒乓,篮球。
 */
public class GenericityDemo {
    public static void main(String[] args) {
        Person<BasketBall> person = new Person<>();
        person.setHobby(new BasketBall());

        Person<Swim> person2 = new Person<>();
        person2.setHobby(new Swim());

        Person person3 = new Person<>();// 如果没有指定泛型 , 那么默认使用Object数据类型
    }
}

class Person<H> {
    // 定义属性表达爱好
    private H hobby;


    public H getHobby() {
        return hobby;
    }

    public void setHobby(H hobby) {
        this.hobby = hobby;
    }
}

class Swim {
}

class PingPang {
}

class BasketBall {
}

1.3 自定义泛型接口

  • 当定义接口时,内部方法中其参数类型,返回值类型不确定时,就可以使用泛型替代了。

  • 定义泛型接口
    • 在接口后面加一对尖括号 , 尖括号中定义泛型 , 一般使用大写字母表示, 多个泛型用逗号分隔
    • public interface<泛型名> { … }
    • 举例 :
    public interface Collection<E>{
    	public boolean add(E e);
    } 
    
    
  • 泛型的确定
    • 实现类去指定泛型接口的泛型
    • 实现了不去指定泛型接口的泛型 , 进行延续泛型 , 回到泛型类的使用
package com.itheima.genericity_demo.genericity_interface;
/*
    需求:
    模拟一个Collection接口,表示集合,集合操作的数据不确定。
    定义一个接口MyCollection具体表示。

 */
// 泛型接口
public interface MyCollection<E> {
    // 添加功能
    public abstract void add(E e);
    // 删除功能
    public abstract void remove(E e);
}

// 指定泛型的第一种方式 : 让实现类去指定接口的泛型
class MyCollectionImpl1 implements MyCollection<String>{
    @Override
    public void add(String s) {

    }
    @Override
    public void remove(String s) {

    }
}
// 指定泛型的第二种方式 : 实现类不确定泛型,延续泛型,回到泛型类的使用
class MyCollectionImpl2<E> implements MyCollection<E>{

    @Override
    public void add(E a) {

    }

    @Override
    public void remove(E a) {

    }
}

1.4 自定义泛型方法

  • 当定义方法时,方法中参数类型,返回值类型不确定时,就可以使用泛型替代了

  • 泛型方法的定义
    • 可以在方法的返回值类型前定义泛型
    • 格式 : public <泛型名> 返回值类型 方法名(参数列表){ … }
    • 举例 : public void show(T t) { … }
  • 泛型的确定
    • 当调用一个泛型方法 , 传入的参数是什么类型, 那么泛型就会被确定
  • 练习
    package com.itheima.genericity_demo.genericity_method;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    
    public class Test {
        public static void main(String[] args) {
            // Collection集合中 : public <T> T[] toArray(T[] a) : 把集合中的内容存储到一个数组中 , 进行返回
            ArrayList<String> list = new ArrayList<>();
            list.add("abc");
            list.add("ads");
            list.add("qwe");
            String[] array = list.toArray(new String[list.size()]);
            System.out.println(Arrays.toString(array));
        }
    
        // 接收一个集合 , 往集合中添加三个待指定类型的元素
        public static <X> void addElement(ArrayList<X> list, X x1, X x2, X x3) {
            list.add(x1);
            list.add(x2);
            list.add(x3);
        }
    }
    

1.5 通配符

  • 当我们对泛型的类型确定不了,而是表达的可以是任意类型,可以使用泛型通配符给定

    符号就是一个问号:? 表示任意类型,用来给泛型指定的一种通配值。如下

public static void shuffle(List<?> list){
   //…
} 

说明:该方法时来自工具类Collections中的一个方法,用来对存储任意类型数据的List集合进行乱序
  • 泛型通配符结合集合使用

    • 泛型通配符搭配集合使用一般在方法的参数中比较常见。在集合中泛型是不支持多态的,如果为了匹配任意类型,我们就会使用泛型通配符了。
    • 方法中的参数是一个集合,集合如果携带了通配符,要特别注意如下
      • 集合的类型会提升为Object类型
      • 方法中的参数是一个集合,集合如果携带了通配符,那么此集合不能进行添加和修改操作 , 可以删除和获取
    package com.itheima.genericity_demo;
    
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class Demo {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<>();
            list.add("abc");
            list.add("asd");
            list.add("qwe");
            // 方法的参数是一个集合 , 集合的泛型是一个通配符 , 可以接受任意类型元素的集合
            show(list);
        }
    
        public static void show(List<?> list) {
            // 如果集合的泛型是一个通配符 , 那么集合中元素以Object类型存在
            Object o = list.get(0);
    
            // 如果集合的泛型是一个通配符 , 那么此集合不能进行添加和修改操作 , 可以删除和获取
            // list.add(??);
    
            // 删除可以
            list.remove(0);
            // 获取元素可以
            for (Object o1 : list) {
                System.out.println(o1);
            }
        }
    }
    
    package com.itheima.genericity_demo;
    
    import java.util.ArrayList;
    
    /*
        已知存在继承体系:Integer继承Number,Number继承Object。
        定义一个方法,方法的参数是一个ArrayList。
        要求可以接收ArrayList<Integer>,ArrayList<Number>,ArrayList<Object>,ArrayList<String>这些类型的数据。
    
        结论 : 具体类型的集合,不支持多态 , 要想接收任意类型集合 , 需要使通配符集合
     */
    public class Test1 {
        public static void main(String[] args) {
            ArrayList<Integer> list1 = new ArrayList<>();
            ArrayList<Number> list2 = new ArrayList<>();
            ArrayList<String> list3 = new ArrayList<>();
            ArrayList<Object> list4 = new ArrayList<>();
    
            useList5(list1);
            useList5(list2);
            useList5(list3);
            useList5(list4);
        }
    
        // 此方法只能接收存储Integer类型数据的集合
        public static void useList1(ArrayList<Integer> list) {
    
        }
    
        // 此方法只能接收存储Number类型数据的集合
        public static void useList2(ArrayList<Number> list) {
    
        }
    
        // 此方法只能接收存储String类型数据的集合
        public static void useList3(ArrayList<String> list) {
    
        }
    
        // 此方法只能接收存储Object类型数据的集合
        public static void useList4(ArrayList<Object> list) {
    
        }
    
        public static void useList5(ArrayList<?> list) {
    
        }
    
    }
    

1.6 受限泛型

  • 受限泛型是指,在使用通配符的过程中 , 对泛型做了约束,给泛型指定类型时,只能是某个类型父类型或者子类型

  • 分类 :

    • 泛型的下限 :
      • <? super 类型> //只能是某一类型,及其父类型,其他类型不支持
    • 泛型的上限 :
      • <? extends 类型> //只能是某一个类型,及其子类型,其他类型不支持
    package com.itheima.genericity_demo.wildcard_demo;
    
    import java.util.ArrayList;
    
    /*
      wildcardCharacter
    
      基于上一个知识点,定义方法
        show1方法,参数只接收元素类型是Number或者其父类型的集合
        show2方法,参数只接收元素类型是Number或者其子类型的集合
    
     */
    public class Test2 {
        public static void main(String[] args) {
            ArrayList<Integer> list1 = new ArrayList<>();
            ArrayList<Number> list2 = new ArrayList<>();
            ArrayList<Object> list3 = new ArrayList<>();
    
            show1(list3);
            show1(list2);
    
            show2(list2);
            show2(list1);
        }
        // 此方法可以接受集合中存储的是Number或者Number的父类型 , 下限泛型
        public static void show1(ArrayList<? super Number> list) {
    
        }
        // 此方法可以接受集合中存储的是Number或者Number的子类型 , 上限泛型
        public static void show2(ArrayList<? extends Number> list) {
    
        }
    }
    

2 数据结构

  • 栈结构 : 先进后出

  • 队列结构 : 先进先出

  • 数组结构 : 查询快 , 增删慢

  • 链表结构 : 查询慢 , 增删快

  • 二叉树

    • 二叉树 : 每个节点最多有两个子节点

    • 二茬查找树 : 每个节点的左子节点比当前节点小 , 右子节点比当前节点大

    • 二茬平衡树 : 在查找树的基础上, 每个节点左右子树的高度不超过1

    • 红黑树 :

      • 每一个节点或是红色的,或者是黑色的

      • 根节点必须是黑色

      • 如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的

      • 不能出现两个红色节点相连的情况

      • 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点

      • 添加元素 :

        外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 哈希表结构 :

    • 哈希值:是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
    • Object类中有一个方法可以获取对象的哈希值
      public int hashCode():返回对象的哈希码值
    • 对象的哈希值特点
      • 同一个对象多次调用hashCode()方法返回的哈希值是相同的
      • 默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同

3 List集合

  • List集合是Collection集合子类型,继承了所有Collection中功能,同时List增加了带索引的功能

  • 特点 :
    • 元素的存取是有序的【有序】
    • 元素具备索引 【有索引】
    • 元素可以重复存储【可重复】
  • 常见的子类
    • ArrayList:底层结构就是数组【查询快,增删慢】
    • Vector:底层结构也是数组(线程安全,同步安全的,低效,用的就少)
    • LinkedList:底层是链表结构(双向链表)【查询慢,增删快】
  • List中常用的方法
    • public void add(int index, E element): 将指定的元素,添加到该集合中的指定位置上。
    • public E get(int index):返回集合中指定位置的元素
    • public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。\
    • public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回值的更新前的元素
  • LinkedList类
    • LinkedList底层结构是双向链表。每个节点有三个部分的数据,一个是保存元素数据,一个是保存前一个节点的地址,一个是保存后一个节点的地址。可以双向查询,效率会比单向链表高。
    • LinkedList特有方法
      • public void addFirst(E e):将指定元素插入此列表的开头。
      • public void addLast(E e):将指定元素添加到此列表的结尾。
      • public E getFirst():返回此列表的第一个元素。
      • public E getLast():返回此列表的最后一个元素。
      • public E removeFirst():移除并返回此列表的第一个元素。
      • public E removeLast():移除并返回此列表的最后一个元素。

4 Set集合

  • Set集合也是Collection集合的子类型,没有特有方法。Set比Collection定义更严谨
  • 特点 :
    • 元素不能保证插入和取出顺序(无序)
    • 元素是没有索引的(无索引)
    • 元素唯一(元素唯一)
  • Set常用子类
    • HashSet:底层由HashMap,底层结构哈希表结构。
      去重,无索引,无序。
      哈希表结构的集合,操作效率会非常高。
    • LinkedHashSet:底层结构链表加哈希表结构。
      具有哈希表表结构的特点,也具有链表的特点。
    • TreeSet:底层是有TreeMap,底层数据结构 红黑树。
      去重,让存入的元素具有排序(升序排序)

day06-排序查找算法,Map集合,集合嵌套,斗地主案例

今日目标 :

  • TreeSet集合(重点)
  • 排序算法(理解)
  • 查找算法(理解)
  • Map集合(重点)
  • 集合嵌套(理解)
  • 斗地主案例(理解)

1 TreeSet集合

1.1 集合体系

  • Collection

    • List接口

      • ArrayList类
      • LinkedList类
    • Set接口

      • HashSet集合

      • TreeSet集合

1.2 TreeSet特点

  • 不包含重复元素的集合[元素唯一]
  • 没有带索引的方法[无索引]
  • 可以按照指定的规则进行排序[可以排序]

1.3 TreeSet集合的练习

package com.itheima.treeset_demo;

import java.util.Iterator;
import java.util.TreeSet;

/*
      1 TreeSet集合练习
            存储Integer类型的整数,并遍历
 */
public class TreeSetDemo {
    public static void main(String[] args) {
        TreeSet<Integer> ts = new TreeSet<>();
        ts.add(10);
        ts.add(10);
        ts.add(20);
        ts.add(10);
        ts.add(30);

        // 迭代器
        Iterator<Integer> it = ts.iterator();
        while(it.hasNext()){
            Integer s = it.next();
            System.out.println(s);
        }

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

        // 增强for
        for (Integer t : ts) {
            System.out.println(t);
        }
    }
}

  • 如果TreeSet存储自定义对象 , 需要对自定义类进行指定排序规则

    下列代码没有指定排序规则 , 所以运行会报出错误

package com.itheima.treeset_demo;

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

    public Student() {
    }

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

    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;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
=============================================================
    
    
package com.itheima.treeset_demo;

import java.util.Iterator;
import java.util.TreeSet;

/*
      TreeSet集合练习
         存储学生对象,并遍历

 */
public class TreeSetDemo2 {
    public static void main(String[] args) {
        TreeSet<Student> ts = new TreeSet<>();

        Student s1 = new Student("dilireba", 19);
        Student s2 = new Student("gulinazha", 20);
        Student s3 = new Student("maerzhaha", 18);

        ts.add(s1);
        ts.add(s2);
        ts.add(s3);

        System.out.println(ts);
    }
}

1.4 排序规则

1.4.1 自然排序
  • 使用步骤
    • 使用空参构造创建TreeSet集合对象
    • 存储元素所在的类需要实现Comparable接口
    • 重写Comparable接口中的抽象方法 compareTo方法,指定排序规则
  • compareTo方法如何指定排序规则 :
    • 此方法如果返回的是小于0 , 代表的是当前元素较小 , 需要存放在左边
    • 此方法如果返回的是大于0 , 代表的是当前元素较大, 需要存放在右边
    • 此方法如果返回的是0 , 代表的是当前元素在集合中已经存在 , 不存储
  • 练习 : 存储学生对象, 按照年龄的升序排序,并遍历
    package com.itheima.treeset_demo;
    
    public class Student implements Comparable<Student>{
        private String name;
        private int age;
    
        public Student() {
        }
    
        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        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;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    
        /*
    
         */
        @Override
        public int compareTo(Student o) {
            /*
                this : 当前要添加的元素
                o    : 集合中已经存在的元素
    
                如果方法返回值为负数 : 当前元素放左边
                如果方法的返回值为正数  : 当前元素放右边
                如果方法的返回值为0  : 说明当前元素在集合中存在,不存储
             */
            int result = this.age - o.age;
            return result;
        }
    }
    =====================================================
        
    package com.itheima.treeset_demo;
    import java.util.Iterator;
    import java.util.TreeSet;
    
    /*
          TreeSet集合练习
             存储学生对象,并遍历
    
     */
    public class TreeSetDemo2 {
        public static void main(String[] args) {
            TreeSet<Student> ts = new TreeSet<>();
    
            Student s1 = new Student("dilireba", 19);
            Student s2 = new Student("gulinazha", 20);
            Student s3 = new Student("maerzhaha", 18);
    
            ts.add(s1);
            ts.add(s2);
            ts.add(s3);
    
            System.out.println(ts);
        }
    }
    
    • 对刚才的案例进行改进 , 如果年龄相等 , 那么对学生的名字在字典的顺序排序
    package com.itheima.treeset_demo;
    
    public class Student implements Comparable<Student> {
        private String name;
        private int age;
    
        public Student() {
        }
    
        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        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;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    
        /*
    
         */
        @Override
        public int compareTo(Student o) {
            /*
                this : 当前要添加的元素
                o    : 集合中已经存在的元素
    
                如果方法返回值为负数 : 当前元素放左边
                如果方法的返回值为正数  : 当前元素放右边
                如果方法的返回值为0  : 说明当前元素在集合中存在,不存储
             */
            int result = this.age - o.age;
            // 如果年龄相等 , 那么按照名字进行排序
            return result == 0 ? this.name.compareTo(o.name) : age;
        }
    }
    
    =============================================================
        
        
    package com.itheima.treeset_demo;
    
    import java.util.Iterator;
    import java.util.TreeSet;
    
    /*
          TreeSet集合练习
             存储学生对象, 按照年龄的升序排序,并遍历
    
     */
    public class TreeSetDemo2 {
        public static void main(String[] args) {
            TreeSet<Student> ts = new TreeSet<>();
    
            Student s1 = new Student("dilireba", 19);
            Student s2 = new Student("gulinazha", 20);
            Student s3 = new Student("maerzhaha", 18);
            Student s4 = new Student("ouyangnanan", 18);
    
            ts.add(s1);
            ts.add(s2);
            ts.add(s3);
            ts.add(s4);
    
            System.out.println(ts);
        }
    }
    
    
1.4.2 比较器排序
  • 使用步骤
    • TreeSet的带参构造方法使用的是 “比较器排序” 对元素进行排序的
    • 比较器排序,就是让TreeSet集合构造方法接收Comparator接口的实现类对象
    • 重写Comparator接口中的 compare(T o1,T o2)方法 , 指定排序规则
    • 注意 : o1代表的是当前往集合中添加的元素 , o2代表的是集合中已经存在的元素,排序原理与自然排序相同!
  • 排序规则
    • 排序原理与自然排序相同!
  • 练习
package com.itheima.treeset_demo.comparator_demo;

public class Teacher  {
    private String name;
    private int age;

    public Teacher() {
    }

    public Teacher(String name, int age) {
        this.name = name;
        this.age = age;
    }

    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;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
======================================
    
package com.itheima.treeset_demo.comparator_demo;

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

/*
      TreeSet集合练习
         存储学生对象, 按照年龄的升序排序,并遍历

 */
public class TreeSetDemo2 {
    public static void main(String[] args) {
        TreeSet<Teacher> ts = new TreeSet<>(new ComparatorImpl());

        Teacher s1 = new Teacher("dilireba", 19);
        Teacher s2 = new Teacher("gulinazha", 20);
        Teacher s3 = new Teacher("maerzhaha", 18);
        Teacher s4 = new Teacher("ouyangnanan", 18);

        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);

        System.out.println(ts);
    }
}

// 比较器排序
class ComparatorImpl implements Comparator<Teacher> {
    @Override
    public int compare(Teacher o1, Teacher o2) {
        int result = o1.getAge() - o2.getAge();
        return result == 0 ? o1.getName().compareTo(o2.getName()) : result;
    }
}
1.4.3 两种排序的区别
  • 自然排序:自定义类实现Comparable接口,重写compareTo方法,根据返回值进行排序。

  • 比较器排序:创建TreeSet对象的时候传递Comparator的实现类对象,重写compare方法,根据返回值进行排序。

  • 如果Java提供好的类已经定义了自然排序排序规则 , 那么我们可以使用比较器排序进行替换

  • 注意 : 如果自然排序和比较器排序都存在 , 那么会使用比较器排序

  • 两种方式中,关于返回值的规则:

    • 如果返回值为负数,表示当前存入的元素是较小值,存左边
    • 如果返回值为0,表示当前存入的元素跟集合中元素重复了,不存
    • 如果返回值为正数,表示当前存入的元素是较大值,存右边

2 Collections单列集合工具类

  • Collections工具类介绍
    • java.util.Collections 是集合的工具类,里面提供了静态方法来操作集合,乱序,排序…

2.1 shuffle方法

  • public static void shuffle(List<?> list) 对集合中的元素进行打乱顺序。

  • 集合中元素类型可以任意类型

    package com.itheima.collections_demo;
    
    import java.util.ArrayList;
    import java.util.Collections;
    
    /*
        Collections类 : 操作单列集合的工具类
            public static void shuffle(List<?> list) 对集合中的元素进行打乱顺序
            1 乱序只能对List集合进行乱序
            2 集合中元素类型可以任意类型
    
            需求 : 定义一个List集合,里面存储若干整数。对集合进行乱序
     */
    public class ShuffleDemo {
        public static void main(String[] args) {
            ArrayList<Integer> list = new ArrayList<>();
            list.add(10);
            list.add(30);
            list.add(50);
            list.add(40);
            list.add(20);
    
            Collections.shuffle(list);
    
            System.out.println(list);// 打印集合中的元素
        }
    }
    
    

2.2 sort方法

  • public static void sort (List list): 对集合中的元素自然排序

    • 该方法只能对List集合进行排序
    • 从方法中泛型分析可知,集合中元素类型必须是Comparable的子类型
    package com.itheima.collections_demo;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.Comparator;
    
    /*
        Collections类 : 单列集合的工具类
            sort方法是一个重载的方法,可以实现自然排序及比较器排序。
            要特别注意的是sort方法只能对List集合进行排序,方法如下:
    
            public static <T extends Comparable> void sort (List<T> list)
    
            练习:定义List集合,存储若干整数,进行排序
     */
    public class SortDemo1 {
        public static void main(String[] args) {
            ArrayList<Integer> list = new ArrayList<>();
            list.add(3);
            list.add(2);
            list.add(4);
            list.add(1);
    
            // 使用此方法 , 需要集合中存储的元素实现Comparable接口
            Collections.sort(list);
    
            System.out.println(list);
        }
    }
    
  • public static void sort (List list, Comparator<? super T> c)

    • 方法只能对List集合排序

    • 对元素的类型没有要求

    • 需要定义一个比较器Comparator (规则和之前TreeSet时一样)

    • 使用场景:

      • List集合中的元素类型不具备自然排序能力(元素类型没有实现结果 Comparable)
      • List集合中的元素类型具备自然排序能力,但是排序规则不是当前所需要的。
      package com.itheima.collections_demo;
      
      import java.util.ArrayList;
      import java.util.Collections;
      import java.util.Comparator;
      
      /*
          Collections类 : 单列集合的工具类
              sort方法是一个重载的方法,可以实现自然排序及比较器排序。
              要特别注意的是sort方法只能对List集合进行排序,方法如下:
      
              public static <T extends Comparable> void sort (List<T> list) : 只能对集合中的元素自然排序
      
              需求1:定义一个List集合,存储若干整数,要求对集合进行降序排序
              分析:整数类型Integer具备自然排序能力,但是题目要求降序排序
      
              需求2:定义一个学生类,属性有姓名,年龄。创建若干对象放到List集合中。要求对List集合中学生对象进行年龄的升序排序。
              分析:自定义类型默认是没有自然排序能力的,我们使用自定义比较器方式排序。
      
      
       */
      public class SortDemo2 {
          public static void main(String[] args) {
      
              /*
                  需求2:定义一个学生类,属性有姓名,年龄。创建若干对象放到List集合中。要求对List集合中学生对象进行年龄的升序排序。
                  分析:自定义类型默认是没有自然排序能力的,我们使用自定义比较器方式排序
              */
              ArrayList<Student> list = new ArrayList<>();
              list.add(new Student("lisi", 24));
              list.add(new Student("zhangsan", 23));
      
              Collections.sort(list, new Comparator<Student>() {
                  @Override
                  public int compare(Student o1, Student o2) {
                      return o1.getAge() - o2.getAge();
                  }
              });
              System.out.println(list);
          }
      
          /*
              需求1:定义一个List集合,存储若干整数,要求对集合进行降序排序
              分析:整数类型Integer具备自然排序能力,但是题目要求降序排序
           */
          private static void method1() {
              ArrayList<Integer> list = new ArrayList<>();
              list.add(3);
              list.add(2);
              list.add(4);
              list.add(1);
      
              // 使用此方法 , 需要集合中存储的元素实现Comparable接口
              Collections.sort(list, new Comparator<Integer>() {
                  @Override
                  public int compare(Integer o1, Integer o2) {
                      return o2 - o1;
                  }
              });
      
              System.out.println(list);
          }
      }
      

3 可变参数

3.1 可变参数介绍

  • 在 JDK5 中提供了可变参数,允许在调用方法时传入任意个参数。可变参数原理就是一个数组形式存在
  • 格式 : 修饰符 返回值类型 方法名(数据类型… 变量名) { }
  • 举例 : public void show(int … num) : 表达式该方法可以接收任意个整数值 , 原理 : 其实就是一个int类型的数组

3.2 可变参数注意

  • 可变参数只能作为方法的最后一个参数,但其前面可以有或没有任何其他参数。
  • 可变参数本质上是数组,不能作为方法的重载。如果同时出现相同类型的数组和可变参数方法,是不能编译通过的。

3.3 可变参数的使用

  • 调用可变参数方法,可以给出零到任意多个参数,编译器会将可变参数转化为一个数组,也可以直接传递一个数组。
    方法内部使用时直接当做数组使用即可
package com.itheima.changevariable_demo;

/*
    1 什么是可变参数
        JDK5中,允许在调用方法时传入任意个参数。可变参数原理就是一个数组形式存在
        格式 : 修饰符 返回值类型 方法名(数据类型… 变量名) {  }
        举例 : public void show(int... num){}

    2 可变参数注意 :
        1) 可变参数只能作为方法的最后一个参数,但其前面可以有或没有任何其他参数。
        2) 可变参数本质上是数组,不能作为方法的重载。如果同时出现相同类型的数组和可变参数方法,是不能编译通过的。

    3 可变参数的使用 :
        调用可变参数的方法 , 可以传入0个到任意个数据 , 编译器会将可变参数转换成一个数组 , 也可以直接传递一个数组
        方法中把可变参数当做一个数组去使用即可


    练习:定义方法可以求任意个整数的和

 */
public class VariableDemo1 {
    public static void main(String[] args) {
        sum();
        sum(1, 2);
        sum(1, 2, 3, 4);
    }
    
    public static void sum(int... num) {// 方法的参数是一个int类型的可变参数
        int sum = 0;// 定义求和变量
        for (int i : num) {
            sum += i;
        }
        System.out.println(sum);
    }
}

3.4 addAll方法

  • static boolean addAll(Collection<? super T> c , T… elements) : 添加任意多个数据到集合中

    • 该方法就是一个含有可变参数的方法,使用时可以传入任意多个实参,实用方便!

    • 分析 : Collection<? super T> , ? 可以是 T 的类型或者父类型 , 反过来 , T是?类型或者子类型

      那么当你确定?的类型,也就是集合的类型 , 就可以往集合中添加此类型或者子类型

    package com.itheima.changevariable_demo;
    
    import java.util.ArrayList;
    import java.util.Collections;
    
    /*
        Collections的addAll方法
            static <T> boolean  addAll(Collection<? super T> c , T... elements) : 添加任意多个数据到集合中
            分析: 该方法就是一个含有可变参数的方法,使用时可以传入任意多个实参,实用方便!
    
            练习:创建一个List集合,使用addAll方法传入若干字符串
    
     */
    public class VariableDemo2 {
        public static void main(String[] args) {
            ArrayList<Integer> list = new ArrayList<>();
            Collections.addAll(list, 10, 20, 30, 40);
            System.out.println(list);// [10, 20, 30, 40]
        }
    }
    
    

4 排序,查找算法

4.1 冒泡排序

  • 冒泡排序 : 将一组数据按照从小到大的顺序进行排序
  • 冒泡排序原理 : 相邻元素两两作比较 , 大的元素往后放
package com.itheima.arraysort_demo.bubblesort_demo;

import java.util.Arrays;

/*
    冒泡排序 : 将一组数据按照从小到大的顺序进行排序
    冒泡排序的原理 : 相邻元素两两作比较 , 大的元素往后放

    需求 : 将数组中的元素 {3,5,2,1,4} 进行升序排序
 */
public class SortDemo {
    public static void main(String[] args) {
        int[] arr = {3, 5, 2, 1, 4};

//        // 第一轮排序
//        for (int i = 0; i < arr.length - 1; i++) {
//            if (arr[i] > arr[i + 1]) {
//                int temp = arr[i];
//                arr[i] = arr[i + 1];
//                arr[i + 1] = temp;
//            }
//        }
//
//        System.out.println("第一轮排序:" + Arrays.toString(arr));
//
//
//        // 第二轮排序
//        for (int i = 0; i < arr.length - 2; i++) {
//            if (arr[i] > arr[i + 1]) {
//                int temp = arr[i];
//                arr[i] = arr[i + 1];
//                arr[i + 1] = temp;
//            }
//        }
//
//        System.out.println("第二轮排序:" + Arrays.toString(arr));
//
//
//        // 第三轮排序
//        for (int i = 0; i < arr.length - 2; i++) {
//            if (arr[i] > arr[i + 1]) {
//                int temp = arr[i];
//                arr[i] = arr[i + 1];
//                arr[i + 1] = temp;
//            }
//        }
//
//        System.out.println("第三轮排序:" + Arrays.toString(arr));
//
//
//        // 第四轮排序
//        for (int i = 0; i < arr.length - 2; i++) {
//            if (arr[i] > arr[i + 1]) {
//                int temp = arr[i];
//                arr[i] = arr[i + 1];
//                arr[i + 1] = temp;
//            }
//        }
//
//        System.out.println("第四轮排序:" + Arrays.toString(arr));

		// 优化代码
        for (int j = 0; j < arr.length - 1; j++) {// 比较的轮次
            // 每轮相邻元素比较的次数
            for (int i = 0; i < arr.length - 1 - j; i++) {
                if (arr[i] > arr[i + 1]) {
                    int temp = arr[i];
                    arr[i] = arr[i + 1];
                    arr[i + 1] = temp;
                }
            }
            System.out.println("第" + (j + 1) + "轮排序:" + Arrays.toString(arr));
        }
    }
}

4.2 选择排序

  • 选择排序原理 : 它的工作原理是每一次从待排序的数据元素中选出最小的一个元素,存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到已排序序列的末尾。以此类推,直到全部待排序的数据元素排完。
  • 注意事项 :
    • 有n个元素,那么就要比较n-1轮次。
    • 每一趟中都会选出一个最值元素,较前一趟少比较一次
package com.itheima.arraysort_demo.selectionsort_demo;
/*
    选择排序工作原理 :
        它的工作原理是每一次从待排序的数据元素中选出最小的一个元素,
        存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到已排序序列的末尾。
        以此类推,直到全部待排序的数据元素排完。

    注意 :
        1 有n个元素,那么就要比较n-1趟。
        2 每一趟中都会选出一个最值元素,较前一趟少比较一次
 */

import java.util.Arrays;

public class SortDemo {
    public static void main(String[] args) {
        int[] arr = {4, 1, 5, 3, 2};

        // 遍历数组
        for (int i = 0; i < arr.length - 1; i++) {
            // 记录当前元素和其之后所有元素的最小值索引
            int minIndex = i;

            int min = arr[i];
            for (int j = i; j < arr.length; j++) {
                if (arr[j] < min) {
                    minIndex = j; //  把当前最小值的索引赋值给minIndex
                    min = arr[j];// 替换最小值
                }
            }

            if (i != minIndex) {
                int temp = arr[i];
                arr[i] = arr[minIndex];
                arr[minIndex] = temp;
            }
        }

        System.out.println(Arrays.toString(arr));
    }
}

4.3 二分查找

  • 原理 : 每次去掉一般的查找范围
  • 前提 : 数组必须有序
package com.itheima.arraysort_demo.binarysearch_demo;

/*
    二分查找 :
        原理 : 每次去掉一般的查找范围
        前提 : 数组必须有序
        步骤 :
            1,定义两个变量,表示要查找的范围。默认min = 0 , max = 最大索引
            2,循环查找,但是min <= max
            3,计算出mid的值
            4,判断mid位置的元素是否为要查找的元素,如果是直接返回对应索引
            5,如果要查找的值在mid的左半边,那么min值不变,max = mid -1.继续下次循环查找
            6,如果要查找的值在mid的右半边,那么max值不变,min = mid + 1.继续下次循环查找
            7,当 min > max 时,表示要查找的元素在数组中不存在,返回-1.
 */
public class BinarySearchDemo {
    public static void main(String[] args) {

        int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

        int i = binarySearch(arr, 8);
        System.out.println(i);

    }

    public static int binarySearch(int[] arr, int num) {
        // 定义两个变量,表示要查找的范围。默认min = 0 , max = 最大索引
        int max = arr.length - 1;
        int min = 0;

        // 2,循环查找,但是min <= max
        while (min <= max) {
            // 3,计算出mid的值
            int mid = (min + max) / 2;

            if (arr[mid] == num) {
                //  4,判断mid位置的元素是否为要查找的元素,如果是直接返回对应索引
                return mid;
            } else if (arr[mid] > num) {
                // 5,如果要查找的值在mid的左半边,那么min值不变,max = mid -1.继续下次循环查找
                max = mid - 1;
            } else if (arr[mid] < num) {
                // 6,如果要查找的值在mid的右半边,那么max值不变,min = mid + 1.继续下次循环查找
                min = mid + 1;
            }
        }

        return -1;
    }
}

5 Map集合

5.1 Map集合的介绍

  • java.util.Map<K,V> 集合,里面保存的数据是成对存在的,称之为双列集合。存储的数据,我们称为键值对。 之前所学的Collection集合中元素单个单个存在的,称为单列集合

5.2 特点

  • Map<K,V> K:键的数据类型;V:值的数据类型

  • 特点 :

    • 键不能重复,值可以重复
    • 键和值是 一 一 对应的,通过键可以找到对应的值
    • (键 + 值) 一起是一个整体 我们称之为“键值对” 或者 “键值对对象”,在Java中叫做“Entry对象”
  • 使用场景

    • 凡是要表示一一对应的数据时就可以Map集合
      • 举例 : 学生的学号和姓名 — (itheima001 小智)
      • 举例 : 夫妻的关系 ---- (王宝强 马蓉 ) (谢霆锋 张柏芝)

5.3 常用实现类

  • HashMap:
    • 此前的HashSet底层实现就是HashMap完成的,HashSet保存的元素其实就是HashMap集合中保存的键,底层结构是哈希表结构,具有键唯一,无序,特点。
  • LinkedHashMap:
    • 底层结构是有链表和哈希表结构,去重,有序
  • TreeMap:
    • 底层是有红黑树,去重,通过键排序

5.4 常用的方法

  • public V put(K key, V value): 把指定的键与指定的值添加到Map集合中。
  • public V remove(Object key): 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。
  • public V get(Object key) 根据指定的键,在Map集合中获取对应的值。
  • public Set keySet(): 获取Map集合中所有的键,存储到Set集合中。
  • public boolean containKey(Object key):判断该集合中是否有此键。
package com.itheima.map_demo.mapmethod_demo;

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

/*
    Map中常用方法 :
        public V put(K key, V value):  把指定的键与指定的值添加到Map集合中
        public V remove(Object key): 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值
        public V get(Object key) 根据指定的键,在Map集合中获取对应的值
        public Set<K> keySet(): 获取Map集合中所有的键,存储到Set集合中
        public boolean containKey(Object key): 判断该集合中是否有此键。

     需求 : 将以下数据保存到Map集合中 , 进行测试以上方法
        “文章“       "马伊琍
        “谢霆锋”     “王菲”
        “李亚鹏”     “王菲”

 */
public class MapDemo {
    public static void main(String[] args) {
        //  创建双列集合对象
        Map<String, String> hm = new HashMap<>();

        // 添加元素
        // public V put(K key, V value):  把指定的键与指定的值添加到Map集合中
        hm.put("文章", "马伊琍");
        hm.put("谢霆锋", "王菲");
        hm.put("李亚鹏", "王菲");

        // public V remove(Object key): 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值
        // System.out.println(hm.remove("谢霆锋"));

        // public V get(Object key) 根据指定的键,在Map集合中获取对应的值
        // System.out.println(hm.get("李亚鹏"));

        // public Set<K> keySet(): 获取Map集合中所有的键,存储到Set集合中
//        Set<String> set = hm.keySet();
//        for (String key : set) {
//            System.out.println(key);
//        }

        // public boolean containKey(Object key): 判断该集合中是否有此键。
        // System.out.println(hm.containsKey("李亚鹏"));

        System.out.println(hm);// 打印集合 , 打印的是集合中的元素
    }
}

5.5 Map集合的遍历

  • 第一种方式 : 键找值
    package com.itheima.map_demo.map_test;
    

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

/*
创建Map集合对象 , 往集合中添加以下四对元素 , 使用键找值遍历集合

周瑜 -- 小乔
孙策 -- 大乔
刘备 -- 孙尚香
诸葛亮 -- 黄月英

*/
public class MapTest1 {
public static void main(String[] args) {
// 创建集合对象
Map<String, String> hm = new HashMap<>();

    // 添加元素
    hm.put("周瑜", "小乔");
    hm.put("孙策", "大乔");
    hm.put("刘备", "孙尚香");
    hm.put("诸葛亮", "黄月英");

    // 获取健集合
    Set<String> set = hm.keySet();
    
    // 遍历健集合 , 通过键找值
    for (String key : set) {
        String value = hm.get(key);
        System.out.println(key + "---" + value);
    }
}

}

- 第二种方式  : 获取键值对对象 , 在找到键和值

​```java
package com.itheima.map_demo.map_test;

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

/*
  需求 :  创建Map集合对象 , 往集合中添加以下三对元素
          使用获取Entry对象集合,在找到键和值 遍历集合

      张无忌 -- 赵敏
      张翠山 -- 殷素素
      张三丰 -- 郭芙

*/
public class MapTest2 {
  public static void main(String[] args) {
      // 创建集合对象
      Map<String, String> hm = new HashMap<>();

      // 添加元素
      hm.put("张无忌", "赵敏");
      hm.put("张翠山", "殷素素");
      hm.put("张三丰", "郭芙");

      // 获取键值对对象集合
      Set<Map.Entry<String, String>> set = hm.entrySet();

      // 遍历键值对对象集合 , 获取每一个键值对对象
      for (Map.Entry<String, String> entry : set) {
          // 通过entry对象获取键
          String key = entry.getKey();
          // 通过entry对象获取值
          String value = entry.getValue();
          System.out.println(key + "--" + value);
      }
  }
}

5.6 HashMap集合

  • 注意 : HashMap集合 , 要想保证键唯一 , 那么键所在的类必须重写hashCode和equals方法
package com.itheima.map_demo.map_test;

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

    public Student() {
    }

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

    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;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return name != null ? name.equals(student.name) : student.name == null;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }

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

package com.itheima.map_demo.map_test;
import java.util.HashMap;
import java.util.Set;

/*
    HashMap类 :
        存储数据,每位学生(姓名,年龄)都有自己的家庭住址。
        学生和地址有对应关系,将学生对象和家庭住址存储到map集合中。学生作为键, 家庭住址作为值。
        要求:学生姓名相同并且年龄相同视为同一名学生,不能重复存储
 */
public class HashMapTest {
    public static void main(String[] args) {
        // 学生作为键, 家庭住址作为值。
        HashMap<Student, String> hm = new HashMap<>();

        hm.put(new Student("迪丽热巴", 18) , "新疆");
        hm.put(new Student("迪丽热巴", 18) , "中国");

        Set<Student> set = hm.keySet();
        for (Student key : set) {
            String value = hm.get(key);
            System.out.println(key + "--" + value);
        }
    }
}

5.7 LinkedHashMap集合

  • LinkedHashMap类 , 在最底层采用的数据结构 : 是链表+哈希表。
  • 特点 :
    • 元素唯一
    • 元素有序
package com.itheima.map_demo.map_test;

import java.util.LinkedHashMap;
import java.util.Set;

/*
    LinkedHashMap类 , 在最底层采用的数据结构 : 是链表+哈希表。
    特点 :
        1 元素唯一
        2 有序

    需求 : 创建LinkedHashMap对象 , 添加元素进行验证 元素唯一 , 有序的特点
 */
public class LinkedHashMapTest {
    public static void main(String[] args) {
        // 创建集合对象
        LinkedHashMap<Integer, String> linkedHashMap = new LinkedHashMap<>();

        linkedHashMap.put(1, "张三");
        linkedHashMap.put(1, "李四");
        linkedHashMap.put(2, "王五");
        linkedHashMap.put(3, "赵六");

        System.out.println(linkedHashMap);
    }
}

5.8 TreeMap集合

  • TreeMap的底层是红黑树实现的,有排序的能力,键去重。

  • 可以自然排序(键所在的类要实现Comparable)

  • 若自定义类没有自然排序功能,或自然排序功能不满足要求时。可以自定义比较器排序(Comparator)

package com.itheima.map_demo.map_test;
import java.util.TreeMap;

/*
    需求 :
        定义TreeMap集合存储键值对,键使用Integer,值使用String,存储若干键值对,遍历集合观察结果是否有排序。
 */
public class TreeMapTest1 {
    public static void main(String[] args) {
        //  定义TreeMap集合存储键值对,键使用Integer,值使用String
        // 创建集合对象
        TreeMap<Integer, String> treeMap = new TreeMap<>();

        treeMap.put(1, "张三");
        treeMap.put(3, "赵六");
        treeMap.put(2, "王五");

        System.out.println(treeMap);
    }
}

package com.itheima.map_demo.map_test;

import java.util.Comparator;
import java.util.TreeMap;

/*
    需求:创建一个TreeMap集合,键是学生对象(Student),值是籍贯(String)。
    学生属性姓名和年龄, 要求按照年龄进行升序排序并遍历
 */
public class TreeMapTest2 {
    public static void main(String[] args) {
        // 学生作为键, 家庭住址作为值。
        TreeMap<Student, String> tm = new TreeMap<>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o1.getAge() - o2.getAge();
            }
        });

        tm.put(new Student("迪丽热巴", 18), "新疆");
        tm.put(new Student("迪丽热巴", 16), "中国");

        System.out.println(tm);
    }
}

6 集合嵌套

6.1 List嵌套List

package com.itheima.Collection_nested_demo;

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

/*
    使用场景举例:一年级有多个班级,每个班级有多名学生。要求保存每个班级的学生姓名,保存一个年级所有的班级信息

    思路:
    可以使用List集合保存一个班级的学生
    可以使用List集合保存所有班级

    因此我们可以定义集合如下:
    班级:List<String>
        举例 :
            List<String> 三年一班 = {迪丽热巴 , 古力娜扎 ,马尔扎哈 ,欧阳娜娜}
            List<String> 三年二班 = {李小璐 , 白百何 , 马蓉}
            List<String> 三年三班 = {林丹 ,文章, 陈赫}

    年级:List<List<String>>
        举例 :
            List<List<String>> 年级 = {三年一班 , 三年二班 , 三年三班}
 */
public class Test3 {
    public static void main(String[] args) {
        List<String> 三年一班 = new ArrayList<>();
        三年一班.add("迪丽热巴");
        三年一班.add("古力娜扎");
        三年一班.add("马尔扎哈");
        三年一班.add("欧阳娜娜");

        List<String> 三年二班 = new ArrayList<>();
        三年二班.add("李小璐");
        三年二班.add("白百何");
        三年二班.add("马蓉");

        List<String> 三年三班 = new ArrayList<>();
        三年三班.add("林丹");
        三年三班.add("文章");
        三年三班.add("陈赫");

        List<List<String>> 年级 = new ArrayList<>();
        年级.add(三年一班);
        年级.add(三年二班);
        年级.add(三年三班);

        for (List<String> 班级 : 年级) {
            for (String name : 班级) {
                System.out.println(name);
            }
            System.out.println("-----------------");
        }
    }
}

6.2 List嵌套Map

package com.itheima.Collection_nested_demo;

import java.util.*;

/*
    List嵌套Map :

    使用场景举例:一年级有多个班级,每个班级有多名学生。要求保存每个班级的学生姓名,姓名有与之对应的学号,保存一年级所有的班级信息。

    思路:
        1 可以使用Map集合保存一个班级的学生(键是学号,值是名字)
        2 可以使用List集合保存所有班级

    因此我们可以定义集合如下:

    班级:Map<String,String> 键是学号,值是姓名
        举例 :
            Map<String,String> 三年一班 = {it001 = 迪丽热巴 , it002 = 古力娜扎 ,it003 = 马尔扎哈 ,it004 = 欧阳娜娜}
            Map<String,String> 三年二班 = {it001 = 李小璐 , it002 = 白百何 , it003 = 马蓉}
            Map<String,String> 三年三班 = {it001 = 林丹 ,it002 = 文章, it003 = 陈赫}

    年级:List<Map<String,String>>保存每个班级的信息
        举例 :
              List<Map<String,String>> 年级 = {三年一班 , 三年二班 , 三年三班}
 */
public class Test2 {
    public static void main(String[] args) {
        Map<String, String> 三年一班 = new HashMap<>();
        三年一班.put("it001", "迪丽热巴");
        三年一班.put("it002", "古力娜扎");
        三年一班.put("it003", "马尔扎哈");
        三年一班.put("it004", "欧阳娜娜");


        Map<String, String> 三年二班 = new HashMap<>();
        三年二班.put("it001", "李小璐");
        三年二班.put("it002", "白百何");
        三年二班.put("it003", "马蓉");

        Map<String, String> 三年三班 = new HashMap<>();
        三年三班.put("it001", "林丹");
        三年三班.put("it002", "文章");
        三年三班.put("it003", "陈赫");


        List<Map<String, String>> 年级 = new ArrayList<>();
        年级.add(三年一班);
        年级.add(三年二班);
        年级.add(三年三班);

        for (Map<String, String> 班级 : 年级) {
            Set<String> studentId = 班级.keySet();
            for (String id : studentId) {
                String name = 班级.get(id);
                System.out.println(id + "---" + name);
            }
            System.out.println("=================");
        }
    }
}

6.3 Map嵌套Map

package com.itheima.Collection_nested_demo;

import java.util.*;

/*
    Map嵌套Map

    使用场景举例:一个年级有多个班级,每个班级有多名学生。要求保存每个班级的学生姓名,姓名有与之对应的学号,保存一年级所有的班级信息,班级有与之对应的班级名称。

    思路:
    可以使用Map集合保存一个班级的学生(键是学号,值是名字)

    可以使用Map集合保存所有班级(键是班级名称,值是班级集合信息)

    因此我们可以定义集合如下:
    班级: Map<String,String> 键:学号,值:姓名
        举例 :
            Map<String,String> 三年一班 = {it001 = 迪丽热巴 , it002 = 古力娜扎 ,it003 = 马尔扎哈 ,it004 = 欧阳娜娜}
            Map<String,String> 三年二班 = {it001 = 李小璐 , it002 = 白百何 , it003 = 马蓉}
            Map<String,String> 三年三班 = {it001 = 林丹 ,it002 = 文章, it003 = 陈赫}

    年级: Map<String , Map<String,String>> 键:班级名称,值:具体班级信息
        举例:
            Map<String, Map<String,String>> 年级 = {"三年一班" = 三年一班 , "三年二班" = 三年二班 , "三年三班" = 三年三班 }

 */
public class Test3 {
    public static void main(String[] args) {

        Map<String, String> 三年一班 = new HashMap<>();
        三年一班.put("it001", "迪丽热巴");
        三年一班.put("it002", "古力娜扎");
        三年一班.put("it003", "马尔扎哈");
        三年一班.put("it004", "欧阳娜娜");


        Map<String, String> 三年二班 = new HashMap<>();
        三年二班.put("it001", "李小璐");
        三年二班.put("it002", "白百何");
        三年二班.put("it003", "马蓉");

        Map<String, String> 三年三班 = new HashMap<>();
        三年三班.put("it001", "林丹");
        三年三班.put("it002", "文章");
        三年三班.put("it003", "陈赫");


        Map<String, Map<String, String>> 年级 = new HashMap<>();
        年级.put("三年一班", 三年一班);
        年级.put("三年二班", 三年二班);
        年级.put("三年三班", 三年三班);

        Set<String> 班级名字集合 = 年级.keySet();

        for (String 班级名字 : 班级名字集合) {
            Map<String, String> 班级信息 = 年级.get(班级名字);
            Set<String> 学生学号 = 班级信息.keySet();
            for (String 学号 : 学生学号) {
                String 姓名 = 班级信息.get(学号);
                System.out.println("班级名字:" + 班级名字 + " ,学号:" + 学号 + " , 名字:" + 姓名);
            }
            System.out.println("============");
        }

    }
}

7 斗地主案例

package com.itheima.doudizhu;

import java.util.*;

/*
    按照斗地主的规则,完成洗牌发牌的动作。
    要求完成以下功能:
    准备牌:组装54张扑克牌
    洗牌:54张牌顺序打乱
    发牌:三个玩家参与游戏,三人交替摸牌,每人17张牌,最后三张留作底牌。
    看牌:查看三人各自手中的牌(按照牌的大小排序)、底牌

    规则:手中扑克牌从大到小的摆放顺序:大王,小王,2,A,K,Q,J,10,9,8,7,6,5,4,3

 */
public class DouDiZhu {
    public static void main(String[] args) {
        // 准备牌
        // 键我牌的序号 , 值为牌面
        HashMap<Integer, String> pokers = new HashMap<>();

        // 牌的颜色
        String[] colors = {"♠", "♥", "♣", "♦"};
        String[] numbers = {"2", "A", "K", "Q", "J", "10", "9", "8", "7", "6", "5", "4", "3"};
        int count = 2;
        for (String number : numbers) {// 3
            for (String color : colors) {
                // System.out.println(count + " = " + color + number);
                pokers.put(count, color + number);
                count++;
            }
        }
        pokers.put(0, "大王");
        pokers.put(1, "小王");

        System.out.println(pokers);

        // 洗牌
        Set<Integer> set = pokers.keySet();
        // 创建存储编号的list集合
        List<Integer> list = new ArrayList<>();// {10 , 6 , 8 , 20 , 22 , 11 ... }
        // 把set集合中的元素存储到list集合中
        list.addAll(set);
        // 打乱集合中编号
        Collections.shuffle(list);
        System.out.println(list);

        // 发牌
        TreeSet<Integer> 赌神 = new TreeSet<>();
        TreeSet<Integer> 赌圣 = new TreeSet<>();
        TreeSet<Integer> 赌侠 = new TreeSet<>();
        TreeSet<Integer> 底牌 = new TreeSet<>();
        // 遍历牌的编号
        for (int i = 0; i < list.size() - 3; i++) {
            if (i % 3 == 0) {
                赌神.add(list.get(i));
            } else if (i % 3 == 1) {
                赌圣.add(list.get(i));
            } else {
                赌侠.add(list.get(i));
            }
        }

        底牌.add(list.get(51));
        底牌.add(list.get(52));
        底牌.add(list.get(53));
        System.out.println("赌神:" + 赌神);
        System.out.println("赌圣:" + 赌圣);
        System.out.println("赌侠:" + 赌侠);
        System.out.println("底牌:" + 底牌);


        // 看牌
        // 赌神
        lookPoker(pokers, 赌神, "赌神:  ");

        // 赌圣
        lookPoker(pokers, 赌圣, "赌圣:  ");

        // 赌侠
        lookPoker(pokers, 赌侠, "赌侠:  ");

        // 底牌
        System.out.print("底牌:  ");
        for (Integer integer : 底牌) {
            String poker = pokers.get(integer);
            System.out.print(poker + "\t");
        }

    }

    private static void lookPoker(HashMap<Integer, String> pokers, TreeSet<Integer> 赌神, String s) {
        System.out.print(s);
        for (Integer integer : 赌神) {
            String poker = pokers.get(integer);
            System.out.print(poker + "\t");
        }
        System.out.println();
    }
}

```java

package com.itheima.exception_demo;

import sun.java2d.pipe.SpanShapeRenderer;

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

/*
    声明异常—— throws
        格式 : 修饰符   返回值类型  方法名(参数列表) throws 异常类型1 , 异常的类型2...   {  ...  }
        举例 : public void show() throws NullPointerException , ArrayIndexOutOfBoundsException  { .... }
        作用 :
            1 表示告知调用者当前的方法可能会出现某些异常,使用时需要注意哦!
            2 如果当前方法没有出现任何异常, 那么代码会正常执行
            3 如果当前方法中出现了异常 , 会把异常交给本方法调用者处理(甩锅)

    需求 :
        练习 : 定义两个方法一个运行时期异常 , 一个声明编译时期异常 !

    注意 :
        1 编译时异常因为在编译时就会检查,所以必须要写在方法后面进行显示声明
        2 运行时异常因为在运行时才会发生,所以在方法后面可以不写
        3 如果声明多个异常有子父类关系 , 那么只要声明一个父类即可(多态)
 */
public class Exception_Throws {
    public static void main(String[] args) throws ParseException{
        printArr();// 如果此方法出现了异常 , 会交给jvm进行处理
        StringToDate();// 如果此方法出现了异常 , 会交给jvm进行处理
    }

    // 1 告诉调用者 , 此方法可能会出现异常哦
    // 2 如果此方法没有出现异常 , 那么会正常执行
    // 3 如果此方法中出现了异常 , 会把此异常交给调用者处理
    // 注意 : 如果声明的异常是一个运行时期异常 , 那么此声明可以省略
    public static void printArr() /*throws NullPointerException*/ {
        int[] arr = null;
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }

    // 1 告诉调用者 , 此方法可能会出现异常哦
    // 2 如果此方法没有出现异常 , 那么会正常执行
    // 3 如果此方法中出现了异常 , 会把此异常交给调用者处理
    // 注意 : 如果声明的异常 是一个编译时期异常 , 那么在编译时期必须处理 , 要么程序无法执行
    public static void StringToDate() throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = sdf.parse("2000-03-11 12:12:12");
    }
}


# day08-异常,多线程

## 今日目标 : 

- 异常的概述 
- 异常的分类 
- 异常的处理方式
- 自定义异常
- 多线程入门

## 1 异常的概述 

### 1.1 什么是异常?

- 异常就是程序出现了不正常情况 , 程序在执行过程中 , 数据导致程序不正常 , 最终导致了JVM的非正常停止
- 注意 : 语句错误不算在异常体系中

### 1.2 异常的存在的形式

- 异常有类型之分,比如我们之前有接触过的比较熟悉的数组越界异常(ArrayIndexOutOfBoundsException),空指针异常(NullPointerException),类型转换异常(ClassCastException)。当程序中产生异常时,其实就是在异常的位置创建了一个该异常的对象,该对象携带了相关的异常信息。
  - 简单来说 : 异常就是Java中提供的类的对象

### 1.3 程序中异常产生后,是如何处理的

- 程序中一旦产生异常,首先会中断向下执行。异常的传递要根据处理方式(后面章节会讲到)而定,如果没有处理,默认是将异常传递给本方法的调用者。不断往回传递,直到JVM收到该异常,此时程序终止执行

## 2 异常的分类

- 编译时期异常
  -RuntimeException及其子类 :  编译时异常就是在编译的时候可能出现的异常,  编译时期必须处理,否则程序无法执行
- 运行时期异常
  - RuntimeException及其子类 : 运行时异常就是在运行时可能出现的异常,  在编译时期不需要处理

## 3 异常的处理方式

### 3.1 JVM处理异常的方式

- 如果程序出现了问题,我们没有做任何处理,最终JVM会做默认的处理 , 那么JVM是如何处理的呢 ?
  - 把异常的类型 , 原因 , 位置打印在控制台
  - 程序停止执行
- 注意 : 程序中出现了异常 , 会在当前位置创建此异常的对象 , 对象中包含了异常的信息 , 并把此异常交给本方法的调用者处理

- 缺点 : 用户体验不好

### 3.2 手动处理异常方式

#### 3.2.1 声明异常

- 声明异常—— throws

  - 修饰符   返回值类型  方法名(参数列表)  throws 异常类型1 , 异常的类型2...   {  ...  }
  - 举例 : public void show() throws NullPointerException , ArrayIndexOutOfBoundsException  { .... }

- 作用 : 

  - 表示调用当前的方法可能会出现某些异常,使用时需要注意哦!
  - 如果当前方法没有出现任何异常, 那么代码会正常执行
  - 如果当前方法中出现了异常 , 会把异常交给本方法调用者处理(甩锅)

- ```java
  package com.itheima.exception_demo;
  
  import sun.java2d.pipe.SpanShapeRenderer;
  
  import java.text.ParseException;
  import java.text.SimpleDateFormat;
  import java.util.Date;
  
  /*
      声明异常—— throws
          格式 : 修饰符   返回值类型  方法名(参数列表) throws 异常类型1 , 异常的类型2...   {  ...  }
          举例 : public void show() throws NullPointerException , ArrayIndexOutOfBoundsException  { .... }
          作用 :
              1 表示告知调用者当前的方法可能会出现某些异常,使用时需要注意哦!
              2 如果当前方法没有出现任何异常, 那么代码会正常执行
              3 如果当前方法中出现了异常 , 会把异常交给本方法调用者处理(甩锅)
  
      需求 :
          练习 : 定义两个方法一个运行时期异常 , 一个声明编译时期异常 !
  
      注意 :
          1 编译时异常因为在编译时就会检查,所以必须要写在方法后面进行显示声明
          2 运行时异常因为在运行时才会发生,所以在方法后面可以不写
          3 如果声明多个异常有子父类关系 , 那么只要声明一个父类即可(多态)
   */
  public class Exception_Throws {
      public static void main(String[] args) throws ParseException{
          printArr();// 如果此方法出现了异常 , 会交给jvm进行处理
          StringToDate();// 如果此方法出现了异常 , 会交给jvm进行处理
      }
  
      // 1 告诉调用者 , 此方法可能会出现异常哦
      // 2 如果此方法没有出现异常 , 那么会正常执行
      // 3 如果此方法中出现了异常 , 会把此异常交给调用者处理
      // 注意 : 如果声明的异常是一个运行时期异常 , 那么此声明可以省略
      public static void printArr() /*throws NullPointerException*/ {
          int[] arr = null;
          for (int i = 0; i < arr.length; i++) {
              System.out.println(arr[i]);
          }
      }
  
      // 1 告诉调用者 , 此方法可能会出现异常哦
      // 2 如果此方法没有出现异常 , 那么会正常执行
      // 3 如果此方法中出现了异常 , 会把此异常交给调用者处理
      // 注意 : 如果声明的异常 是一个编译时期异常 , 那么在编译时期必须处理 , 要么程序无法执行
      public static void StringToDate() throws ParseException {
          SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
          Date date = sdf.parse("2000-03-11 12:12:12");
      }
  }
  
3.2.2 抛出异常
  • 思考:
    • 以前出现了异常,虚拟机帮我们创建一个异常对象,抛给调用者。但是如果我们需要自己手动创建一个异常对象该如何写?
  • 格式 :
    • 修饰符    返回值类型    方法名(参数列表)  {
          throw new 异常对象();
      }
      
  • 注意 :
    • 抛出异常的格式必须在方法的内部完成
    • 如果手动抛出一个异常,下面的代码无法执行
  • package com.itheima.exception_demo;
    
    /*
        抛出异常演示 :
            格式 :
                修饰符  返回值类型  方法名(参数列表)  {
                    throw new 异常对象();
                }
    
            注意 :
                1 抛出异常的格式必须在方法的内部完成
                2 如果手动抛出一个异常,下面的代码无法执行
     */
    public class Exception_Throw {
        public static void main(String[] args) {
            System.out.println("家里有一个貌美如花的老婆");
            System.out.println("还有一个当官的兄弟");
            System.out.println("自己还有一个买卖");
            System.out.println("这样的生活你要不要?");
    
            // 程序不想往下执行了 ,怎么做 ???
            // 1 自己手动制造出一个异常
            // 2 当前异常也是交给了方法的调用者处理 , 也就是jvm处理
            // 3 下面代码无法执行
            throw new RuntimeException();
    
            // System.out.println("武大郎的标准生活!");
        }
    }
    
    
  • 作用 :
    • 在方法中,当传递的参数有误,没有继续运行下去的意义了,则采取抛出处理,表示让该方法结束运行。
    • 告诉调用者方法中出现的问题原因
    package com.itheima.exception_demo;
    
    /*
        抛出异常存在的意义所在 :
            1 在方法中,当传递的参数有误,没有继续运行下去的意义了,则采取抛出处理,表示让该方法结束运行。
            2 告诉调用者方法中出现了问题
    
        练习   : 定义一个方法 , 方法的参数接收一个数组 , 在方法中遍历数组 .
        需求1  : 如果方法接收的数组为null  , 使用输出语句提示
        需求2  : 如果方法接收的数组为null  , 使用抛出异常解决
        思考   : 两种方式的区别在哪里 ?
     */
    public class Exception_Throw2 {
        public static void main(String[] args) {
            int[] arr = {1, 2, 3, 4, 5};
            arr = null;
            // printArr1(arr);
            printArr2(arr);// 接收方法返回的异常 , 但是此异常有jvm进行处理
        }
    
        // 需求1  : 如果方法接收的数组为null  , 使用输出语句提示
        public static void printArr1(int[] arr) {
            if (arr == null) {
                System.out.println("数组为null");
            } else {
                for (int i = 0; i < arr.length; i++) {
                    System.out.println(arr[i]);
                }
            }
        }
    
        // 需求2  : 如果方法接收的数组为null  , 使用抛出异常解决
        public static void printArr2(int[] arr) {
            if (arr == null) {
                throw new RuntimeException();
            } else {
                for (int i = 0; i < arr.length; i++) {
                    System.out.println(arr[i]);
                }
            }
        }
    }
    

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3.2.3 throws和throw的区别
  • throws :
    • 用在方法声明后面,跟的是异常类名
    • 表示声明异常,告知调用者调用该方法有可能会出现这样的异常
  • throw :
    • 用在方法体内,跟的是异常对象名
    • 表示手动抛出异常对象,告知调用者数据传入有误
3.2.4 捕获异常
  • 捕获处理异常介绍 : try, catch

    • 之前的声明或者抛出都是将异常传递出去,让调用者知道异常信息。
      而捕获处理是本方法内部进行处理 , 能够阻止异常的传递,从而保证程序能够继续往下执行。
  • 捕获异常的格式

    • try {
          try中存放可能会出现问题的代码
              1.代码...
              2.代码...
              3.代码...
      } catch (异常类型 变量名) {
          	4.处理异常方案
             打印异常,获取异常原因记录日志......}
      5.其他代码...
      
  • 执行方式

    • 如果 try 中没有遇到问题,怎么执行?
      • 从上往下依次执行 , catch中不执行
    • 如果 try 中代码2遇到了问题,问题下面的代码还会执行吗?
      • 不会执行,会拿当前异常对象和异常类型匹配,匹配成功执行处理异常代码
    • 如果出现的问题没有被捕获,那么程序如何运行?
      • 如果异常没有捕获到 , 虚拟机会帮助我们处理
  • 多异常捕获处理方案

    • 多个异常,每个异常单独处理

      • try{
            异常1
        }catch(异常1){
        
        } 
        try{
            异常2
        }catch(异常2){
        
        }
        
    • 多个异常,一次捕获,多次处理

      • try{
            异常1
            异常2
        }catch(异常1){
        }catch(异常2){
        }
        
    • 多个异常,异常一次捕获,一次处理

      • try{
            异常1
            异常2
        }catch(Exception e){
        }
        

3.3 Throwable 的成员方法

方法名说明
public String getMessage()返回此 throwable 的详细消息字符串
public String toString()返回此可抛出的简短描述
public void printStackTrace()把异常的错误信息输出在控制台

3.4 异常练习

package com.itheima.exception_demo;

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

/*
    定义一个方法接收一个生日日期字符串(xxxx年xx月xx)
    main方法中让用户输入一个生日日期字符串,调用设计好的方法计算在地球上活了多少天。

    要求:如果解析发生异常,捕获异常,提示用户要重新输入生日日期字符串,直到输入正确的日期为止。
    思考:设计代此码的过程中想想什么时候捕获异常,什么时候声明异常?

 */
public class ExceptionTest {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入生日(xxxx-xx-xx):");
        
        while (true) {
            String birthday = sc.nextLine();
            try {
                method(birthday);
                break;// 如果生日没有问题结束死循环
            } catch (ParseException e) {
                System.out.println("录入生日格式有误!");
            }
        }
    }

    public static void method(String strDate) throws ParseException {
        // 创建日期模板对象
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

        // 解析字符串
        Date date = sdf.parse(strDate);

        // 获取生日到1970/1/1 经历的毫秒值
        long time1 = date.getTime();

        // 当前系统时间到1970/1/1 经历的毫秒值
        Date d2 = new Date();
        long time2 = d2.getTime();


        System.out.println("活了" + (time2 - time1) / (1000L * 60 * 60 * 24) + "天");
    }
}

4 自定义异常

4.1 概述

  • 当JDK中的异常类型,不满足实际的业务需要时。就可以自己定义异常。例如,学生的年龄数据,如果是负数或者数据 超过了150认为是不合法的,就需要抛出异常。JDK中就没有表示年龄的异常,就需要自己定义异常了

4.2 实现步骤

  • 定义异常类
  • 写继承关系
  • 空参构造
  • 带参构造

4.3 自定义异常注意

  • 如果要自定义编译时异常,就继承Exception
  • 如果要自定义运行时异常,就继承RuntimeException

5 多线程入门

5.1 多线程相关的概念

  • 并发与并行
    • 并行:在同一时刻,有多个任务在多个CPU上同时执行。
    • 并发:在同一时刻,有多个任务在单个CPU上交替执行。
  • 进程与线程
    • 进程:就是操作系统中正在运行的一个应用程序。
    • 线程:就是应用程序中做的事情。比如:360软件中的杀毒,扫描木马,清理垃圾。

5.2 什么是多线程

  • 是指从软件或者硬件上实现多个线程并发执行的技术。
    具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能。
  • 好处 : 提高任务的执行性能

5.3 多线程的创建方式

5.3.1 继承Thread方式
  • 基本步骤:

    • 创建一个类继承Thread类。
    • 在类中重写run方法(线程执行的任务放在这里)
    • 创建线程对象,调用线程的start方法开启线程。
    • 执行程序,观察控制台的打印数据的现象
    package com.itheima.thread_demo;
    
    /*
        线程的创建方式1:继承Thread方式
    
        基本步骤 :
            1 创建一个类继承Thread类。
            2 在类中重写run方法(线程执行的任务放在这里)
            3 创建线程对象,调用线程的start方法开启线程。
    
        需求 :
            我们启动一个Java程序,其实默认就存在一个主线程(main方法所在线程)
            接下来,我们在主线程启动一个线程,打印1到100的数字,主线程启动完线程后又打印1到100的数字。
            此时主线程和启动的线程在并发执行,观察控制台打印的结果。
     */
    public class MyThread01 {
        public static void main(String[] args) {
            // 创建线程对象,调用线程的start方法开启线程。
            MyThread mt = new MyThread();
            mt.start();
    
            // main方法中的任务
            for (int i = 1; i <= 100; i++) {
                System.out.println("i:" + i);
            }
        }
    }
    // 创建一个类继承Thread类。
    class MyThread extends Thread {
        // 在类中重写run方法(线程执行的任务放在这里)
        @Override
        public void run() {
            for (int i = 1; i <= 100; i++) {
                System.out.println("i:" + i);
            }
        }
    }
    
5.3.2 实现Runable方式
  • 构造方法

    • public Thread(Runnable target)
    • public Thread(Runnalbe target , String name)
  • 实现步骤

    • 定义任务类实现Runnable,并重写run方法
    • 创建任务对象
    • 使用含有Runnable参数的构造方法,创建线程对象并指定任务。
    • 调用线程的start方法,开启线程
    package com.itheima.thread_demo;
    
    /*
         线程的创建方式2:实现Runnable方式
    
        基本步骤 :
            1 定义任务类实现Runnable,并重写run方法
            2 创建任务对象
            3 使用含有Runnable参数的构造方法,创建线程对象并指定任务。
            4 调用线程的start方法,开启线程
    
        需求 :
            我们启动一个Java程序,其实默认就存在一个主线程(main方法所在线程)
            接下来,我们在主线程启动一个线程,打印1到100的数字,主线程启动完线程后又打印1到100的数字。
            此时主线程和启动的线程在并发执行,观察控制台打印的结果。
     */
    public class MyThread02 {
        public static void main(String[] args) {
            // 创建线程对象,调用线程的start方法开启线程。
            MyRunnable mr = new MyRunnable();
            Thread thread= new Thread(mr);
            thread.start();
    
            // main方法中的任务
            for (int i = 1; i <= 100; i++) {
                System.out.println("i:" + i);
            }
        }
    
    }
    
    // 1 定义任务类实现Runnable,并重写run方法
    class MyRunnable implements Runnable {
        // 在类中重写run方法(线程执行的任务放在这里)
        @Override
        public void run() {
            for (int i = 1; i <= 100; i++) {
                System.out.println("i:" + i);
            }
        }
    }
    
    

5.3.3 Thread类中常用方法

  • String getName():返回此线程的名称

  • Thread类中设置线程的名字

    • void setName(String name):将此线程的名称更改为等于参数 name
    • 通过构造方法也可以设置线程名称
  • public static Thread currentThread():返回对当前正在执行的线程对象的引用

  • public static void sleep(long time):让线程休眠指定的时间,单位为毫秒

  • 线程有两种调度模型

    • 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
      抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程
    • 获取的 CPU 时间片相对多一些
    package com.itheima.thread_demo.thread_method;
    
    /*
        线程有两种调度模型
            1 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
            2 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程
               获取的 CPU 时间片相对多一些
    
            注意 : Java使用的是抢占式调度模型
    
    
            优先级高 , 只是抢夺到cpu执行的概率高而已 , 只是一种概率问题
     */
    public class PriorityDemo {
        public static void main(String[] args) {
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 1000; i++) {
                        System.out.println(Thread.currentThread().getName() + ":" + i);
                    }
                }
            });
            // 优先级最低
            thread1.setPriority(1);
            thread1.start();
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 1000; i++) {
                        System.out.println(Thread.currentThread().getName() + ":" + i);
                    }
                }
            });
            // 优先级最高
            thread2.setPriority(10);
            thread2.start();
        }
    }
    

day10 - Lambda,Stream,File,递归

今日目标

  • Lambda表达式
  • Stream流
  • File类
  • 递归

1 Lambda表达式

1.1 体验Lambda表达式

  • package com.itheima.lambda_demo;
    
    /*
        Lambda表达式体验 :
     */
    public class LambdaDemo {
        public static void main(String[] args) {
            // 匿名内部类方式完成
            goSwimming(new Swimming() {
                @Override
                public void swim() {
                    System.out.println("铁汁 , 我们去游泳吧....");
                }
            });
    
            // lambda表达式的方式完成
            goSwimming(() -> System.out.println("铁汁 , 我们去游泳吧...."));
    
        }
    
        public static void goSwimming(Swimming swimming) {
            swimming.swim();
        }
    
    }
    
    interface Swimming {
        public abstract void swim();
    }
    
  • lambda表达式可以理解为对匿名内部类的一种简化 , 但是本质是有区别的

  • 面向对象思想 :

    • 强调的是用对象去完成某些功能
  • 函数式编程思想 :

    • 强调的是结果 , 而不是怎么去做

1.2 函数式接口

  • 只有一个抽象方法需要重写的接口,函数式接口。函数式接口是允许有其他的非抽象方法的存在例如静态方法,默认方法,私有方法。
  • 为了标识接口是一个函数式接口,可以在接口之上加上一个注解: @FunctionalInterface 以示区别
  • 在JDK中 java.util.function 包中的所有接口都是函数式接口。我们之前学习线程时学习的Runnable也是函数式接口
  • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.3 Lambda表达式的使用

  • 使用前提
    • 必须存在一个接口
    • 接口中有且只有一个抽象方法
  • 格式 : ( 形式参数 ) -> { 代码块 }
    • 形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
    • ->:由英文中画线和大于符号组成,固定写法。代表指向动作
    • 代码块:是我们具体要做的事情,也就是以前我们写的方法体内容

1.4 Lambda表达式的案例

package com.itheima.lambda_test;

/*
    练习1:
        1 编写一个接口(ShowHandler)
        2 在该接口中存在一个抽象方法(show),该方法是无参数无返回值
        3 在测试类(ShowHandlerDemo)中存在一个方法(useShowHandler)
        	方法的的参数是ShowHandler类型的,在方法内部调用了ShowHandler的show方法
 */
public class LambdaTest1 {
    public static void main(String[] args) {
        useShowHandler(() -> {
            System.out.println("我是一个lambda表达式....");
        });
    }

    public static void useShowHandler(ShowHandler showHandler) {
        showHandler.show();
    }
}

interface ShowHandler {
    public abstract void show();
}
package com.itheima.lambda_test;

/*
    1 首先存在一个接口(StringHandler)
    2 在该接口中存在一个抽象方法(printMessage),该方法是有参数无返回值
    3 在测试类(StringHandlerDemo)中存在一个方法(useStringHandler),
        方法的的参数是StringHandler类型的,
        在方法内部调用了StringHandler的printMessage方法
 */
public class LambdaTest2 {
    public static void main(String[] args) {
        useStringHandler((String msg) -> {
            System.out.println(msg);
        });
    }

    public static void useStringHandler(StringHandler stringHandler){
        stringHandler.printMessage("今天天气不错 , 挺风和日丽的...");
    }
}

@FunctionalInterface
interface StringHandler {
    public abstract void printMessage(String msg);
}

package com.itheima.lambda_test;

import java.util.Random;

/*
    1 首先存在一个接口(RandomNumHandler)
    2 在该接口中存在一个抽象方法(getNumber),该方法是无参数但是有返回值
    3 在测试类(RandomNumHandlerDemo)中存在一个方法(useRandomNumHandler),方法的的参数是RandomNumHandler类型的
        在方法内部调用了RandomNumHandler的getNumber方法

 */
public class LambdaTest3 {
    public static void main(String[] args) {
        useRandomNumHandler(() -> {
            return new Random().nextInt(10) + 1;
        });
    }

    public static void useRandomNumHandler(RandomNumHandler randomNumHandler) {
        int number = randomNumHandler.getNumber();
        System.out.println(number);
    }
}

interface RandomNumHandler {
    public abstract int getNumber();
}

package com.itheima.lambda_test;

/*
    1 首先存在一个接口(Calculator)
    2 在该接口中存在一个抽象方法(calc),该方法是有参数也有返回值
    3 在测试类(CalculatorDemo)中存在一个方法(useCalculator)
        方法的的参数是Calculator类型的
        在方法内部调用了Calculator的calc方法

 */
public class LambdaTest4 {
    public static void main(String[] args) {
        useCalculator((int a , int b) -> { return a + b;});
    }

    public static void useCalculator(Calculator calculator) {
        int calc = calculator.calc(10, 20);
        System.out.println(calc);
    }
}

@FunctionalInterface
interface Calculator {
    public abstract int calc(int a, int b);
}

2 Stream流

2.1 Stream的体验

package com.itheima.stream_demo;

import java.util.ArrayList;

/*
    体验Stream流的好处

    需求:按照下面的要求完成集合的创建和遍历

        1 创建一个集合,存储多个字符串元素
            "张无忌" , "张翠山" , "张三丰" , "谢广坤" , "赵四" , "刘能" , "小沈阳" , "张良"
        2 把集合中所有以"张"开头的元素存储到一个新的集合
        3 把"张"开头的集合中的长度为3的元素存储到一个新的集合
        4 遍历上一步得到的集合

 */
public class StreamDemo {
    public static void main(String[] args) {
        // 传统方式完成
        ArrayList<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("张翠山");
        list.add("张三丰");
        list.add("谢广坤");
        list.add("赵四");
        list.add("刘能");
        list.add("小沈阳");
        list.add("张良");

        // 把集合中所有以"张"开头的元素存储到一个新的集合
        ArrayList<String> list2 = new ArrayList<>();
        for (String s : list) {
            if (s.startsWith("张")) {
                list2.add(s);
            }
        }

        // 把"张"开头的集合中的长度为3的元素存储到一个新的集合
        ArrayList<String> list3 = new ArrayList<>();
        for (String s : list2) {
            if (s.length() == 3) {
                list3.add(s);
            }
        }
        // 遍历list3集合
        for (String s : list3) {
            System.out.println(s);
        }

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

        // Stream流的方式
        list.stream().filter(s -> s.startsWith("张") && s.length() == 3).forEach(s -> System.out.println(s));
    }
}

2.2 Stream流的三类方法

  • 获取Stream流
    • 创建一条流水线,并把数据放到流水线上准备进行操作
  • 中间方法
    • 流水线上的操作。
    • 一次操作完毕之后,还可以继续进行其他操作
  • 终结方法
    • 一个Stream流只能有一个终结方法
    • 是流水线上的最后一个操作

2.3 第一类 : 获取方法

package com.itheima.stream_demo;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;

/*
    Stream流中三类方法之一 :  获取方法

    1 单列集合
       可以使用Collection接口中的默认方法stream()生成流
       default Stream<E> stream()
    2 双列集合
        双列集合不能直接获取 , 需要间接的生成流
        可以先通过keySet或者entrySet获取一个Set集合,再获取Stream流
    3 数组
       Arrays中的静态方法stream生成流
 */
public class StreamDemo2 {
    public static void main(String[] args) {
        // 单列集合的获取
        // method1();

        // 双列集合的获取
        // method2();

        // 数组获取
        // method3();

    }

    private static void method3() {
        int[] arr = {1, 2, 3, 4, 5, 6};
        Arrays.stream(arr).forEach(s -> System.out.println(s));
    }

    private static void method2() {
        HashMap<String, String> hm = new HashMap<>();
        hm.put("it001", "曹植");
        hm.put("it002", "曹丕");
        hm.put("it003", "曹熊");
        hm.put("it004", "曹冲");
        hm.put("it005", "曹昂");

        // 获取map集合的健集合 , 在进行输出打印
        hm.keySet().stream().forEach(s -> System.out.println(s));
        // 获取map集合的entry对象 , 在输出打印
        hm.entrySet().stream().forEach(s -> System.out.println(s));
    }

    private static void method1() {
        // 可以使用Collection接口中的默认方法stream()生成流
        ArrayList<String> list = new ArrayList<>();
        list.add("迪丽热巴");
        list.add("古力娜扎");
        list.add("马尔扎哈");
        list.add("欧阳娜娜");
        list.stream().forEach(s -> System.out.println(s));
    }
}

2.4 第二类 : 中间方法

package com.itheima.stream_demo;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.function.Predicate;
import java.util.stream.Stream;

/*
    Stream流中三类方法之一 :  中间方法

    1 Stream<T> filter(Predicate predicate):用于对流中的数据进行过滤
        Predicate接口中的方法 : boolean test(T t):对给定的参数进行判断,返回一个布尔值
    2 Stream<T> limit(long maxSize):截取指定参数个数的数据
    3 Stream<T> skip(long n):跳过指定参数个数的数据
    4 static <T> Stream<T> concat(Stream a, Stream b):合并a和b两个流为一个流
    5 Stream<T> distinct():去除流中重复的元素。依赖(hashCode和equals方法)
    6 Stream<T> sorted () : 将流中元素按照自然排序的规则排序
    7 Stream<T> sorted (Comparator<? super T> comparator) : 将流中元素按照自定义比较器规则排序

 */
public class StreamDemo3 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("张翠山");
        list.add("张三丰");
        list.add("谢广坤");
        list.add("赵四");
        list.add("刘能");
        list.add("小沈阳");
        list.add("张良");
        list.add("张良");
        list.add("张良");
        list.add("张良");

        // Stream<T> limit(long maxSize):截取指定参数个数的数据
        // list.stream().limit(3).forEach(s -> System.out.println(s));

        // Stream<T> skip(long n):跳过指定参数个数的数据
        // list.stream().skip(3).forEach(s-> System.out.println(s));

        // Stream<T> distinct():去除流中重复的元素。依赖(hashCode和equals方法)
        // list.stream().distinct().forEach(s->{System.out.println(s);});
        
    }

    // // Stream<T> sorted (Comparator<? super T> comparator) : 将流中元素按照自定义比较器规则排序
    private static void method3(ArrayList<String> list) {
        list.stream().sorted(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.length() - o2.length();
            }
        }).forEach(s->{
            System.out.println(s);
        });
    }

    // Stream<T> sorted () : 将流中元素按照自然排序的规则排序
    private static void method3() {
        ArrayList<Integer> list2 = new ArrayList<>();
        list2.add(3);
        list2.add(1);
        list2.add(2);
        list2.stream().sorted().forEach(s->{
            System.out.println(s);
        });
    }

    // // static <T> Stream<T> concat(Stream a, Stream b):合并a和b两个流为一个流
    private static void method2(ArrayList<String> list) {
        ArrayList<String> list2 = new ArrayList<>();
        list2.add("迪丽热巴");
        list2.add("古力娜扎");
        list2.add("欧阳娜娜");
        list2.add("马尔扎哈");

        Stream.concat(list.stream(), list2.stream()).forEach(s -> {
            System.out.println(s);
        });
    }

    // Stream<T> filter(Predicate predicate):用于对流中的数据进行过滤
    private static void method1(ArrayList<String> list) {
        // filter方法会获取流中的每一个数据
        // s就代表的是流中的每一个数据
        // 如果返回值为true , 那么代表的是数据留下来
        // 如果返回值的是false , 那么代表的是数据过滤掉
//        list.stream().filter(new Predicate<String>() {
//            @Override
//            public boolean test(String s) {
//                boolean result = s.startsWith("张");
//                return result;
//            }
//        }).forEach(s -> System.out.println(s));

        list.stream().filter(s ->
                s.startsWith("张")
        ).forEach(s -> System.out.println(s));
    }
}

2.5 第三类 : 终结方法

package com.itheima.stream_demo;

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

/*
    Stream流中三类方法之一 :  终结方法
    1 void forEach(Consumer action):对此流的每个元素执行操作
        Consumer接口中的方法 void accept(T t):对给定的参数执行此操作
    2 long count():返回此流中的元素数

 */
public class StreamDemo4 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("张翠山");
        list.add("张三丰");
        list.add("谢广坤");

        // long count():返回此流中的元素数
        long count = list.stream().count();
        System.out.println(count);
    }
    // void forEach(Consumer action):对此流的每个元素执行操作
    private static void method1(ArrayList<String> list) {
        // 把list集合中的元素放在stream流中
        // forEach方法会循环遍历流中的数据
        // 并循环调用accept方法 , 把数据传给s
        // 所以s就代表的是流中的每一个数据
        // 我们只要在accept方法中对数据做业务逻辑处理即可
        list.stream().forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });

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

        list.stream().forEach( (String s) -> {
            System.out.println(s);
        });

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

        list.stream().forEach(  s -> { System.out.println(s); });
    }

}

2.6 Stream流中的收集方法

package com.itheima.stream_demo;

import java.util.ArrayList;

/*
    Stream流的收集操作 : 第一部分

    需求:过滤元素并遍历集合
        定义一个集合,并添加一些整数1,2,3,4,5,6,7,8,9,10
        将集合中的奇数删除,只保留偶数。
        遍历集合得到2,4,6,8,10

    结论:在Stream流中无法直接修改集合,数组等数据源中的数据。
 */
public class StreamDemo5 {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();

        for (int i = 1; i <= 10; i++) {
            list.add(i);
        }

        list.stream().filter(num -> num % 2 == 0).forEach(num -> System.out.println(num));

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

        // 结论:在Stream流中无法直接修改集合,数组中的数据。
        System.out.println(list);
    }
}
  • 结论:在Stream流中无法直接修改集合,数组中的数据
package com.itheima.stream_demo;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/*

    Stream流的收集操作 : 第二部分

    使用Stream流的方式操作完毕之后,我想把流中的数据起来,该怎么办呢?

    Stream流的收集方法
    R collect(Collector collector) : 此方法只负责收集流中的数据 , 创建集合添加数据动作需要依赖于参数

    工具类Collectors提供了具体的收集方式
    public static <T> Collector toList():把元素收集到List集合中
    public static <T> Collector toSet():把元素收集到Set集合中
    public static  Collector toMap(Function keyMapper,Function valueMapper):把元素收集到Map集合中


    需求 :
        定义一个集合,并添加一些整数1,2,3,4,5,6,7,8,9,10
        将集合中的奇数删除,只保留偶数。
        遍历集合得到2,4,6,8,10
 */
public class StreamDemo6 {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
            list.add(i);
        }
        list.add(10);
        list.add(10);
        list.add(10);
        list.add(10);
        list.add(10);

        // collect只负责收集流中的数据
        // Collectors.toList()会负责在底层创建list集合 ,并把数据添加到集合中 , 返回集合对象
        List<Integer> list2 = list.stream().filter(num -> num % 2 == 0 ).collect(Collectors.toList());
        System.out.println(list2);

        Set<Integer> set = list.stream().filter(num -> num % 2 == 0 ).collect(Collectors.toSet());
        System.out.println(set);

    }
}
package com.itheima.stream_demo;

import java.util.ArrayList;
import java.util.Map;
import java.util.stream.Collectors;

/*
    Stream流的收集操作 : 第三部分

    1 创建一个ArrayList集合,并添加以下字符串。字符串中前面是姓名,后面是年龄
        "zhangsan,23"
        "lisi,24"
        "wangwu,25"
    2 保留年龄大于等于24岁的人,并将结果收集到Map集合中,姓名为键,年龄为值

    收集方法 :
    public static  Collector toMap(Function keyMapper  ,   Function valueMapper):把元素收集到Map集合中
 */
public class StreamDemo7 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("zhangsan,23");
        list.add("lisi,24");
        list.add("wangwu,25");

        // collect 只负责收集数据
        // Collectors.toMap负责在底层创建集合对象 , 添加元素
        // toMap方法中的s就是代表的是集合中的每一个元素
        // 第一个参数 : 如何获取map集合中的键
        // 第二个参数 : 如何获取map集合中的值
        Map<String, String> map = list.stream().filter(s -> Integer.parseInt(s.split(",")[1]) > 23).collect(
                Collectors.toMap(
                        (String s) -> {
                            return s.split(",")[0];
                        }
                        ,
                        (String s) -> {
                            return s.split(",")[1];
                        }
                )
        );
        System.out.println(map);
    }
}

3 File类

3.1 File类的介绍

  • java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等操作

3.2 构造方法

package com.itheima.file_demo;

import java.io.File;

/*
    File:它是文件和目录路径名的抽象表示
        文件和目录可以通过File封装成对象
        File封装的对象仅仅是一个路径名。它可以是存在的,也可以是不存在的。
    构造方法 :
        1 File(String pathname)   通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例
        2 File(String parent, String child)   从父路径名字符串和子路径名字符串创建新的 File实例
        3 File(File parent, String child) 从父抽象路径名和子路径名字符串创建新的 File实例
 */
public class FileDemo1 {
    public static void main(String[] args) {
//        1 File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例
        File f1 = new File("D:\\abc.txt");
        System.out.println(f1);
//        2 File(String parent, String child) 从父路径名字符串和子路径名字符串创建新的 File实例
        File f2 = new File("D:\\aaa", "bbb.txt");
        System.out.println(f2);

//        3 File(File parent, String child)   从父抽象路径名和子路径名字符串创建新的 File实例
        File f3 = new File(new File("D:\\aaa"), "bbb.txt");
        System.out.println(f3);
    }
}

3.3 File类的创建功能

package com.itheima.file_demo;


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

/*
    File类的创建功能 :
        1 public boolean createNewFile()    : 创建一个新的空的文件
        2 public boolean mkdir()   : 创建一个单级文件夹
        3 public boolean mkdirs() : 创建一个多级文件夹
 */
public class FileDemo2 {
    public static void main(String[] args) throws IOException {
        File f1 = new File("D:\\a.txt");
        // 1 public boolean createNewFile()     : 创建一个新的空的文件
        System.out.println(f1.createNewFile());

        File f2 = new File("day10_demo\\aaa");
        //  2 public boolean mkdir()   : 创建一个单级文件夹
        System.out.println(f2.mkdir());

        File f3 = new File("day10_demo\\bbb\\ccc");
        // 3 public boolean mkdirs() : 创建一个多级文件夹
        System.out.println(f3.mkdirs());
    }
}

3.4 File类的删除功能

package com.itheima.file_demo;

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

/*
    File类删除功能 :
        public boolean delete()    删除由此抽象路径名表示的文件或目录

        注意 : 
            1 delete方法直接删除不走回收站。
            2 如果删除的是一个文件,直接删除。
            3 如果删除的是一个文件夹,需要先删除文件夹中的内容,最后才能删除文件夹
 */
public class FileDemo3 {

    public static void main(String[] args) throws IOException {
        File f1 = new File("D:\\a.txt");
        // 1 public boolean createNewFile()     : 创建一个新的空的文件
        System.out.println(f1.delete());

        File f2 = new File("day10_demo\\aaa");
        //  2 public boolean mkdir()   : 创建一个单级文件夹
        System.out.println(f2.delete());

        File f3 = new File("day10_demo\\bbb");
        // 3 public boolean mkdirs() : 创建一个多级文件夹
        System.out.println(f3.delete());
    }
}

3.5 File类的判断和获取功能

package com.itheima.file_demo;

import java.io.File;

/*
    File类判断和获取功能
        public boolean isDirectory()   测试此抽象路径名表示的File是否为目录
        public boolean isFile()    测试此抽象路径名表示的File是否为文件
        public boolean exists()    测试此抽象路径名表示的File是否存在
        public String getAbsolutePath()    返回此抽象路径名的绝对路径名字符串
        public String getPath()    将此抽象路径名转换为路径名字符串
        public String getName()    返回由此抽象路径名表示的文件或目录的名称
 */
public class FileDemo4 {
    public static void main(String[] args) {
        File f1 = new File("day10_demo\\aaa");
        File f2 = new File("day10_demo\\a.txt");

//        public boolean isDirectory() 测试此抽象路径名表示的File是否为目录
        System.out.println(f1.isDirectory());
        System.out.println(f2.isDirectory());

//        public boolean isFile()  测试此抽象路径名表示的File是否为文件
        System.out.println(f1.isFile());
        System.out.println(f2.isFile());

//        public boolean exists()  测试此抽象路径名表示的File是否存在
        System.out.println(f1.exists());

//        public String getAbsolutePath()  返回此抽象路径名的绝对路径名字符串
        System.out.println(f1.getAbsolutePath());

//        public String getPath()  将此抽象路径名转换为路径名字符串
        System.out.println(f1.getPath());

//        public String getName()  返回由此抽象路径名表示的文件或目录的名称
        System.out.println(f2.getName());
    }
}

3.6 File类高级获取功能

package com.itheima.file_demo;

import java.io.File;

/*
    File类高级获取功能
        public File[] listFiles()  返回此抽象路径名表示的目录中的文件和目录的File对象数组

    listFiles方法注意事项:
        1 当调用者不存在时,返回null
        2 当调用者是一个文件时,返回null
        3 当调用者是一个空文件夹时,返回一个长度为0的数组
        4 当调用者是一个有内容的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回
        5 当调用者是一个有隐藏文件的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回,包含隐藏内容
 */
public class FileDemo5 {
    public static void main(String[] args) {
        File file = new File("day10_demo\\bbb\\ccc");

        // 返回此抽象路径名表示的目录中的文件和目录的File对象数组
        File[] files = file.listFiles();

        // 遍历File类的数组
        for (File f : files) {
            // 拿到每一个文件的文字
            System.out.println(f.getName());
        }
    }
}
练习 :
package com.itheima.file_demo;

import java.io.File;
import java.util.HashMap;

/*
    练习二 :统计一个文件夹中每种文件的个数并打印。

   打印格式如下:
      txt:3个
      doc:4个
      jpg:6个
      …
 */
public class FileTest2 {
    public static void main(String[] args) {
        File file = new File("day10_demo\\统计文件个数文件夹");

        getFileCount(file);
    }

    public static void getFileCount(File f) {

        HashMap<String, Integer> hm = new HashMap<>();

        // 是文件夹在获取所有的子文件
        if (f.isDirectory()) {
            File[] files = f.listFiles();
            // 遍历数组
            for (File file : files) {
                String fileName = file.getName();
                String name = fileName.split("\\.")[1];
                if (hm.containsKey(name)) {
                    hm.put(name, hm.get(name));
                } else {
                    hm.put(name, 1);
                }
            }
        }

        System.out.println(hm);
    }
}

4 递归

  • 概述
    • 递归概述:以编程的角度来看,递归指的是方法定义中调用方法本身的现象
  • 好处
    • 把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解
      小的问题解决 , 大的问题也会逐一进行解决

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 注意
    • 递归出口:否则会出现内存溢出
    • 递归规则:需要找到与原问题相似的规模较小的问题
  • 案例
    • package com.itheima.recursion_demo;
      
      
      /*
              需求:用递归求5的阶乘,并把结果在控制台输出
      
              思路:
                  1 定义一个方法,用于递归求阶乘,参数为一个int类型的变量
                  2 在方法内部判断该变量的值是否是1
                      是:返回1
                      不是:返回n*(n-1)!
                  3 调用方法
                  4 输出结果
      
       */
      public class Demo1 {
          public static void main(String[] args) {
              int result = jc(5);
              System.out.println("5的阶乘是:" + result);
          }
      
          private static int jc(int n) {
              if (n == 1) {
                  return 1;
              }
              return n * jc(n - 1);
          }
      }
      
    • package com.itheima.recursion_demo;
      
      import java.io.File;
      
      /*
          需求 : 使用递归删除计算机中指定的文件夹
      
          删除D盘中的aaa文件夹!
      
       */
      public class Demo2 {
          public static void main(String[] args) {
              File f = new File("D:\\aaa");
              deleteFiles(f);
          }
      
          private static void deleteFiles(File f) {
      
              File[] files = f.listFiles();
      
              for (File file : files) {
                  if (file.isDirectory()) {
                      // 递归
                      deleteFiles(file);
                  } else {
                      // 删除文件
                      file.delete();
                  }
              }
              // 删除当前文件夹
              f.delete();
          }
      }
      

    day12-编码表,字符流,对象流,其他流

今日目标

  • 编码表
  • 字符输出流
  • 字符输入流
  • 字符缓冲流
  • 转换流
  • 对象操作流
  • 打印流
  • 装饰模式
  • commons-iojar包

1 编码表

1.1 思考:

  • 既然字节流可以操作所有文件,那么为什么还要学习字符流 ?
    • 如果使用字节流 , 把文本文件中的内容读取到内存时, 可能会出现乱码
    • 如果使用字节流 , 把中文写入文本文件中 , 也有可能会出现乱码

1.2 编码表介绍

  • 计算机中储存的信息都是用二进制数据表示的;我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果

  • 按照编码表规则,将字符存储到计算机中,称为编码。

  • 按照同样的编码表规则,将存储在计算机中的二进制数据解析显示出来,称为解码 。

  • 编码和解码使用的码表必须一致,否则会导致乱码。

  • 简单理解:
    • 存储一个字符a,首先需在码表中查到对应的数字是97,然后按照转换成二进制的规则进行存储。称为编码
    • 读取的时候,先把二进制解析出来,再转成97,通过97查找早码表中对应的字符是a。称为解码
  • ASCII码表:
    • ASCII(American Standard Code for Information Interchange,美国信息交换标准码表):
    • 包括了数字字符,英文大小写字符和一些常见的标点符号字符。
    • 注意:ASCII码表中是没有中文的。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • GBK码表:
    • window系统默认的码表。兼容ASCII码表,也包含了21003个汉字,并支持繁体汉字以及部分日韩文字
    • 注意:GBK是中国的码表,一个中文以两个字节的形式存储。但不包含世界上所有国家的文字。
  • Unicode码表:
    • 由国际组织ISO 制定,是统一的万国码表,计算机科学领域里的一项业界标准,容纳世界上大多数国家的所有常见文字和符号。
    • 但是因为表示的字符太多,所以Unicode码表中的数字不是直接以二进制的形式存储到计算机的,会先通过UTF-7,UTF-7.5,UTF-8,UTF-16,以及 UTF-32的编码方式再存储到计算机,其中最为常见的就是UTF-8。
    • 注意: Unicode是万国码表,以UTF-8编码后一个中文以三个字节的形式存储

1.3 编码表小结

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.4 字节流读中文出现码的原码

  • 因为字节流一次读一个字节,而不管GBK还是UTF-8一个中文都是多个字节,用字节流每次只能读其中的一部分,所以就会出现乱码问题

2 字符输出流

2.1 字符流输出介绍

  • Writer类
    • 写入字符流的最顶层的类 , 是一个抽象类 ,不能实例化 , 需要使用其子类FileWriter类
  • FileWriter类 : 用来写入字符文件的便捷类

2.2 FileWriter的成员

  • 构造方法 :

    • public FileWriter(File file) : 往指定的File路径中写入数据
    • public FileWriter(String fileName) : 往指定的String路径中写入数据
  • 成员方法 :

    • void write(int c)写一个字符
      void write(char[] cbuf)写入一个字符数组
      void write(char[] cbuf, int off, int len)写入字符数组的一部分
      void write(String str)写一个字符串
      void write(String str, int off, int len)写一个字符串的一部分
    • flush()刷新流,还可以继续写数据
      close()关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据

2.3 FileWriter写数据的步骤

  • 1 创建字符输出流对象
    • 注意事项:
      如果文件不存在,就创建。但是要保证父级路径存在。
      如果文件存在就清空
  • 2 写数据
    • 注意事项:
      写出int类型的整数,实际写出的是整数在码表上对应的字母。
      写出字符串数据,是把字符串本身原样写出。
  • 3 释放资源
    • 注意事项:
      每次使用完流必须要释放资源。
  • package com.itheima.writer_demo;
    
    import java.io.FileWriter;
    import java.io.IOException;
    
    /*
        Writer类 : 写入字符流的最顶层的类 , 是一个抽象类 ,不能实例化
        需要使用其子类FileWriter类
    
        FileWriter类 : 用来写入字符文件的便捷类
        构造方法 :
            public FileWriter(File file) : 往指定的File路径中写入数据
            public FileWriter(String fileName) : 往指定的String路径中写入数据
        成员方法
            void write​(int c)	写一个字符
            void write​(char[] cbuf)	写入一个字符数组
            void write​(char[] cbuf, int off, int len)	写入字符数组的一部分
            void write​(String str)	写一个字符串
            void write​(String str, int off, int len)	写一个字符串的一部分
            flush()	刷新流,还可以继续写数据
            close()	关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据
     */
    public class WriterDemo1 {
        public static void main(String[] args) throws IOException {
            // 创建字符输出流对象
            // 如果文件不存在会创建一个空的文件
            // 如果文件存在 , 会把文件中的内容清空
            FileWriter fw = new FileWriter("day12_demo\\charstream2.txt");
    
            // 写数据
            fw.write('a');
            fw.write('b');
            // 刷新流 , 把流中的数据刷到硬盘中 , 刷新之后可以继续写数据
            // fw.flush();
    
            // 释放资源
            // 关闭流 , 但是会先刷新流
            fw.close();
            // 一旦关闭流无法写数据
            // fw.write('c');
        }
    }
    
    
    package com.itheima.writer_demo;
    
    import java.io.FileWriter;
    import java.io.IOException;
    
    /*
        Writer类 : 写入字符流的最顶层的类 , 是一个抽象类 ,不能实例化
        需要使用其子类FileWriter类
    
        FileWriter类 : 用来写入字符文件的便捷类
        构造方法 :
            public FileWriter(File file) : 往指定的File路径中写入数据
            public FileWriter(String fileName) : 往指定的String路径中写入数据
        成员方法
            void write​(int c)	写一个字符
            void write​(char[] cbuf)	写入一个字符数组
            void write​(char[] cbuf, int off, int len)	写入字符数组的一部分
            void write​(String str)	写一个字符串
            void write​(String str, int off, int len)	写一个字符串的一部分
            flush()	刷新流,还可以继续写数据
            close()	关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据
     */
    public class WriterDemo2 {
        public static void main(String[] args) throws IOException {
            // 创建字符输出流对象
            FileWriter fw = new FileWriter("day12_demo\\charstream2.txt");
    
            // 写数据
    //        void write​(int c)	写一个字符
    //        fw.write('a');
    //        fw.write('b');
    //        fw.write('c');
    
    //        void write​(char[] cbuf)	写入一个字符数组
            char[] chs = {'a', 'b', 'c', 'd', 'e'};
    //        fw.write(chs);
    
    //        void write​(char[] cbuf, int off, int len)	写入字符数组的一部分
    //        fw.write(chs , 2 , 3);
    
    //        void write​(String str)	写一个字符串
    //        fw.write("abcadaasda");
    
    //        void write​(String str, int off, int len)	写一个字符串的一部分
    //        fw.write("abnacna", 3, 2);
    
            // 释放资源
            fw.close();
        }
    }
    
    

2.4 字符输出流练习

  • package com.itheima.writer_demo;
    
    import java.io.FileWriter;
    import java.io.IOException;
    import java.util.Scanner;
    
    /*
        需求 : 将用户键盘录入的用户名和密码保存到本地实现永久化存储。
        要求 : 用户名和密码在文件中各占一行
    
        步骤:
            1 用户键盘录入用户名
            2 创建字符输出流对象
            3 将用户名和密码写到本地文件中
     */
    public class WriterTest {
        public static void main(String[] args) throws IOException {
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入用户名:");
            String username = sc.nextLine();
    
            System.out.println("请输入密码:");
            String password = sc.nextLine();
    
            // 创建字符输出流对象
            FileWriter fw = new FileWriter("day12_demo\\user.txt");
            // 往文件中写入用户名和密码
            fw.write(username);
            // 换行
            fw.write("\r\n");
            fw.write(password);
            // 刷新
            fw.flush();
            // 释放资源
            fw.close();
        }
    }
    
    

3 字符输入流

3.1 字节输入流介绍

  • Reader类 :
    • 读取字符流的最顶层的类 , 是一个抽象类 ,不能实例化
    • 需要使用其子类FileReader类
  • FileReader类 :
    • 用来读取字符文件的便捷类

3.2 FileReader的成员

  • 构造方法 :

    • public FileReader(File file) : 从指定的File路径中读取数据
    • public FileReader(String fileName) : 从指定的String路径中读取数据
  • 成员方法 :

    • int read()一次读一个字符数据
      int read(char[] cbuf)一次读一个字符数组数据
  • package com.itheima.reader_demo;
    
    import java.io.FileNotFoundException;
    import java.io.FileReader;
    import java.io.IOException;
    
    /*
        Reader类 : 读取字符流的最顶层的类 , 是一个抽象类 ,不能实例化
        需要使用其子类FileReader类
    
        FileReader类 : 用来读取字符文件的便捷类
    
        构造方法 :
            public FileReader(File file) : 从指定的File路径中读取数据
            public FileReader(String fileName) : 从指定的String路径中读取数据
    
        成员方法 :
            int read​() : 一次读一个字符数据
            int read​(char[] cbuf)	 : 一次读一个字符数组数据
     */
    public class ReaderDemo1 {
        public static void main(String[] args) throws IOException {
            // 创建字符输入流对象
            FileReader fr = new FileReader("day12_demo\\charstream.txt");
    
            // 一次读一个字符数据
            int ch;
            while ((ch = fr.read()) != -1) {
                System.out.print((char) ch);
            }
    
            // 释放资源
            fr.close();
        }
    }
    
    
  • package com.itheima.reader_demo;
    
    import java.io.FileNotFoundException;
    import java.io.FileReader;
    import java.io.IOException;
    
    /*
        Reader类 : 读取字符流的最顶层的类 , 是一个抽象类 ,不能实例化
        需要使用其子类FileReader类
    
        FileReader类 : 用来读取字符文件的便捷类
    
        构造方法 :
            public FileReader(File file) : 从指定的File路径中读取数据
            public FileReader(String fileName) : 从指定的String路径中读取数据
    
        成员方法 :
            int read​()	一次读一个字符数据
            int read​(char[] cbuf)	一次读一个字符数组数据
     */
    public class ReaderDemo2 {
        public static void main(String[] args) throws IOException {
            // 创建字符输入流对象
            FileReader fr = new FileReader("day12_demo\\charstream.txt");
    
            // 一次读一个字符数组数据
            char[] chs = new char[1024];
            int len;
            while ((len = fr.read(chs)) != -1) {
                System.out.println(new String(chs, 0, len));
            }
            // 释放资源
            fr.close();
        }
    }
    
    

4 字符缓冲流

4.1 字符缓冲流

  • BufferedWriter:可以将数据高效的写出
  • BufferedReader:可以将数据高效的读入到内存
  • 注意 : 字符缓冲流不具备读写功能 , 只提供缓冲区 , 真正读写还是需要依赖于构造接收的基本的字符流
  • 构造方法:
    • public BufferedWriter(Writer out) : 构造方法中需要接收一个基本的字符输出流
    • public BufferedReader(Reader in) : 构造方法中需要接收一个基本的字符输入流
package com.itheima.bufferedstream_demo;

import java.io.*;

/*
    需求 : 使用字符缓冲流复制纯文本文件
    将当日课程资料中的 ‘斗罗大陆.txt’ 复制到当前模块下 'copy.txt'
 */
public class BufferedStreamDemo1 {
    public static void main(String[] args) throws IOException {
        // 创建高效的字符输入流对象
        BufferedReader br = new BufferedReader(new FileReader("D:\\传智播客\\上海-JavaSE进阶面授\\day12【缓冲流、转换流、序列化流、装饰者模式、commons-io工具包】\\资料\\斗罗大陆.txt"));
        // 创建高效的字符输出流对象
        BufferedWriter bw = new BufferedWriter(new FileWriter("day12_demo\\copy.txt"));

        // 一次读写一个字符
//        int ch;
//        while ((ch = br.read()) != -1) {
//            bw.write(ch);
//        }

        // 一次读写一个字符数组
        char[] chs = new char[1024];
        int len;
        while ((len = br.read(chs)) != -1) {
            bw.write(chs, 0, len);
        }
        // 释放资源
        br.close();
        bw.close();
    }
}

4.2 字符缓冲流特有的功能

  • BufferedWriter类
    • void newLine():写一个行分隔符,会根据操作系统的不同,写入不同的行分隔符
  • BufferedReader类
    • public String readLine() :读取文件一行数据, 不包含换行符号 , 读到文件的末尾返回null
  • package com.itheima.bufferedstream_demo;
    
    import java.io.*;
    
    /*
        1 字符缓冲流:
            BufferedWriter:可以将数据高效的写出
            BufferedReader:可以将数据高效的读入到内存
    
        2 字符缓冲流特有功能
            BufferedWriter类
                void newLine​():写一个行分隔符,会根据操作系统的不同,写入不同的行分隔符
            BufferedReader类
                public String readLine​() :读取文件一行数据, 不包含换行符号 ,  读到文件的末尾返回null
    
            远桥之下泛莲舟
            岱岩石上松溪流
            万仞翠山梨亭在
            莫闻空谷声悠悠
     */
    public class BufferedStreamDemo2 {
        public static void main(String[] args) throws IOException {
    
            // 创建高效的字符输出流对象
            BufferedWriter bw = new BufferedWriter(new FileWriter("day12_demo\\abc.txt"));
            // void newLine​():写一个行分隔符,会根据操作系统的不同,写入不同的行分隔符
            bw.write("远桥之下泛莲舟");
            bw.newLine();
            bw.write("岱岩石上松溪流");
            bw.newLine();
            bw.write("万仞翠山梨亭在");
            bw.newLine();
            bw.write("莫闻空谷声悠悠");
            bw.flush();
    
            // 创建高效的字符输入流对象
            BufferedReader br = new BufferedReader(new FileReader("day12_demo\\abc.txt"));
            // public String readLine​() :读取文件一行数据, 不包含换行符号 ,  读到文件的末尾返回null
    //        String s = br.readLine();
    //        System.out.println(s);
    //        s = br.readLine();
    //        System.out.println(s);
    //        s = br.readLine();
    //        System.out.println(s);
    //        s = br.readLine();
    //        System.out.println(s);
    //        System.out.println("============");
    //        s = br.readLine();
    //        System.out.println(s);
    //        s = br.readLine();
    //        System.out.println(s);
    
            // 循环改进
            String line;
            while((line = br.readLine()) != null){
                System.out.println(line);
            }
    
            // 释放资源
            br.close();
            bw.close();
        }
    }
    
    

4.3 字符缓冲流练习

package com.itheima.bufferedstream_demo;

import java.io.*;
import java.util.Arrays;

/*
    需求:读取文件中的数据 : 33 22 11 55 44
    排序后 : 11 22 33 44 55  再次写到本地文件

    步骤 :
        1 创建高效的字符输入流对象
        2 读取文件中的一行数据
        3 将数据按照空格切割
        4 把字符串数组转成int类型数组
        5 对int类型的数组进行排序
        6 创建高效的字符输出流对象
        7 遍历数组,把数组中的数据写入到文件中
        8 释放资源
 */
public class BufferedStreamDemo3 {
    public static void main(String[] args) throws IOException {
        // 1 创建高效的字符输入流对象
        BufferedReader br = new BufferedReader(new FileReader("day12_demo\\sort.txt"));
        // 2 读取文件中的一行数据
        String line = br.readLine();
        // 3 将数据按照空格切割
        String[] strs = line.split(" ");
        // 4 把字符串数组转成int类型数组
        int[] arr = new int[strs.length];
        for (int i = 0; i < strs.length; i++) {
            arr[i] = Integer.parseInt(strs[i]);
        }
        // 5 对int类型的数组进行排序
        Arrays.sort(arr);
        // 6 创建高效的字符输出流对象
        BufferedWriter bw = new BufferedWriter(new FileWriter("day12_demo\\sort.txt"));
        // 7 遍历数组,把数组写入到文件中
        for (int i = 0; i < arr.length; i++) {
            bw.write(arr[i] + " ");
            bw.flush();
        }
        // 8 释放资源
        br.close();
        bw.close();
    }
}

5 转换流

5.1 转换流介绍

  • 转换流就是来进行字节流和字符流之间转换的桥梁

5.2 转换流分类

  • InputStreamReader是从字节流到字符流的桥梁

    • public InputStreamReader(InputStream in) : 创建一个使用默认编码的 InputStreamReader。
    • public InputStreamReader(InputStream in , String charsetName) : 创建使用指定编码的 InputStreamReader。
  • OutputStreamWriter是从字符流到字节流的桥梁

    • public OutputStreamWriter(OutputStream out) : 创建使用默认字符编码的 OutputStreamWriter
    • public OutputStreamWriter(OutputStream out, String charsetName) : 创建使用指定编码的 OutputStreamWriter。
  • 练习
    package com.itheima.conversion_demo;
    
    import java.io.*;
    
    /*
        转换流就是来进行字节流和字符流之间转换的桥梁
    
        InputStreamReader是从字节流到字符流的桥梁
            public InputStreamReader(InputStream in) : 创建一个使用默认编码的 InputStreamReader。
            public InputStreamReader(InputStream in ,  String charsetName) : 创建使用指定编码的 InputStreamReader。
    
        OutputStreamWriter是从字符流到字节流的桥梁
            public OutputStreamWriter(OutputStream out) : 创建使用默认字符编码的 OutputStreamWriter
            public OutputStreamWriter(OutputStream out,  String charsetName) : 创建使用指定编码的 OutputStreamWriter。
    
    
        需求1 : 使用转换流 , 把以下数据按照GBK的编码写入文件 , 在使用GBK的编码读取数据
        数据如下 :
            远桥之下泛莲舟
            岱岩石上松溪流
            万仞翠山梨亭在
            莫闻空谷声悠悠
     */
    public class ConversionDemo2 {
        public static void main(String[] args) throws IOException {
            // 创建转换输出流
            OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("day12_demo\\conversion.txt"), "GBK");
            osw.write("远桥之下泛莲舟");
            osw.write("\r\n");
            osw.write("岱岩石上松溪流");
            osw.write("\r\n");
            osw.write("万仞翠山梨亭在");
            osw.write("\r\n");
            osw.write("莫闻空谷声悠悠");
            osw.write("\r\n");
            osw.close();
    
            // 创建转换输入流
            InputStreamReader isr = new InputStreamReader(new FileInputStream("day12_demo\\conversion.txt"), "GBK");
            int ch;
            while ((ch = isr.read()) != -1) {
                System.out.print((char) ch);
            }
            isr.close();
        }
    }
    
    
    package com.itheima.conversion_demo;
    
    import java.io.*;
    
    /*
        转换流就是来进行字节流和字符流之间转换的桥梁
    
        InputStreamReader是从字节流到字符流的桥梁
            public InputStreamReader(InputStream in) : 创建一个使用默认编码的 InputStreamReader。
            public InputStreamReader(InputStream in ,  String charsetName) : 创建使用指定编码的 InputStreamReader。
    
        OutputStreamWriter是从字符流到字节流的桥梁
            public OutputStreamWriter(OutputStream out) : 创建使用默认字符编码的 OutputStreamWriter
            public OutputStreamWriter(OutputStream out,  String charsetName) : 创建使用指定编码的 OutputStreamWriter。
    
        需求2 :  将模块根目录中GBK编码的文本文件 , 转换为UTF-8编码的文本文件
     */
    public class ConversionDemo2 {
        public static void main(String[] args) throws IOException {
    
            // 创建转换输入流
            InputStreamReader isr = new InputStreamReader(new FileInputStream("day12_demo\\GBK编码的文件.txt"), "GBK");
    
            // 创建转换输出流
            OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("day12_demo\\UTF编码的文件.txt"), "UTF-8");
    
            int ch;
            while ((ch = isr.read()) != -1) {// 以GBK编码进去读取
                osw.write(ch);// 以UTF-8编码进行写入
            }
    
            // 释放资源
            isr.close();
            osw.close();
        }
    }
    
    

6 对象操作流

6.1 对象操作流介绍

  • 可以把对象以字节的形式写到本地文件,直接打开文件,是读不懂的,需要再次用对象操作流读到内存中

6.2 对象操作流的分类

  • ObjectOutputStream :
    • ​ 对象操作输出流(对象序列化流):就是将对象写到本地文件中,或者在网络中传输对象
  • ObjectInputStream :
    • ​ 对象操作输入流(对象反序列化流):把写到本地文件中的对象读到内存中,或者接收网络中传输的对象

6.3 对象操作流的注意事项

  • 注意 : 如果一个类对象想要被序列化 , 那么此类需要实现Serializable接口

    • Serializable接口的含义 :
      • ​ 1 是一个标记性接口 , 里面没有任何抽象方法
      • ​ 2 只要一个类实现了此接口 , 表示此类的对象可以被序列化
  • 用对象序列化流序列化了一个对象后,假如我们修改了对象所属的Javabean类,读取数据会不会出问题呢?

    • 会出问题,会抛出InvalidClassException异常
  • 如果出问题了,如何解决呢?

    • 给对象所属的类加一个serialVersionUID
    • private static final long serialVersionUID = 42L;
  • 如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?

  • 给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程

  • package com.itheima.objectstream_demo;
    
    
    import java.io.Serializable;
    
    /*
        如果此类对象想要被序列化 , 那么此类需要实现Serializable接口
        Serializable接口的含义 :
            是一个标记性接口 , 里面没有任何抽象方法
            只要一个类实现了此接口 , 表示此类的对象可以被序列化
     */
    public class User implements Serializable {
        /*
            问题分析 :
                serialVersionUID : 序列号
                序列号是根据类的信息进行生成的
                如果没有自己给出序列号 , JVM会根据类的信息自动计算一个序列号
                如果改动了类的信息 , 那么JVM会重新计算一个序列号
    
                第一步 : 把对象序列化到本地中 , 序列号为 -4446663370728791812 也会存储到本地中
                第二步 : 我们自己修改了类 , 会重新计算一个新的序列号 2908680347500030933
                第三步 : 当把对象读到内存中时 , 本地中的序列号和类中的序列号不一致就会发生 InvalidClassException异常
    
            解决方案 :
                我们自己手动给出序列号, 不让虚拟机自动生成 , 并且这个值恒久不变
                private static final long serialVersionUID = 值L;
         */
    
        private static final long serialVersionUID = 111L;
    
        private String username;
        private transient String password;
    
        public User() {
        }
    
        public User(String username, String password) {
            this.username = username;
            this.password = password;
        }
    
        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;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "username='" + username + '\'' +
                    ", password='" + password + '\'' +
                    '}';
        }
    }
    

6.4 对象操作流的练习

7 打印流

学习目标
  • 了解打印流的使用
内容讲解
【1】API

PrintStream类 : 打印流

作用 : 就是为了方便记录数据(日志)

1)构造方法

public PrintStream(String filePath) : 构建一个打印流对象,传入接收数据的文件路径

2)方法

public void println(数据)   打印后换行
public void print(数据)     打印不换行
【2】代码实践

创建打印流对象,记录一些数据到指定文件

package com.itheima.printstream_demo;

import java.io.FileNotFoundException;
import java.io.PrintStream;

/*
    PrintStream类 : 打印流

    作用 : 就是为了方便记录数据(日志)

    构造方法 :
        public PrintStream(String filePath) : 构建一个打印流对象,传入接收数据的文件路径
    成员方法 :
        public void println(数据)   打印后换行
        public void print(数据)     打印不换行

    需求 : 创建打印流对象,记录一些数据到指定文件
 */
public class PrintStreamDemo {
    public static void main(String[] args) throws FileNotFoundException {
        // System.out.println("abc");

        //  public PrintStream(String filePath) : 构建一个打印流对象,传入接收数据的文件路径
        PrintStream ps = new PrintStream("day11_demo\\print.txt");

        ps.println(123);
        ps.println(true);
        ps.println("abc");

        ps.close();
    }
}

System.out 对象类型,其实就是PrintStream类型。

可以改变系统输出的流向:System.setOut(打印流)

package com.itheima.printstream_demo;

import java.io.FileNotFoundException;
import java.io.PrintStream;

/*
    PrintStream类 : 打印流

    构造方法 :
        public PrintStream(String filePath) : 构建一个打印流对象,传入接收数据的文件路径
    成员方法 :
        public void println(数据)   打印后换行
        public void print(数据)     打印不换行
    需求 :
        System.out 对象类型,其实就是PrintStream类型。
        可以改变系统输出的流向 : System.setOut(打印流)
 */
public class PrintStreamDemo2 {
    public static void main(String[] args) throws FileNotFoundException {
        System.out.println("数据打印在控制台");

        // 可以改变系统输出的流向
        System.setOut(new PrintStream("day11_demo\\print.txt"));

        System.out.println("数据打印在文件中");
    }
}
内容小结
  • 打印流使用很简单,print/println方法
  • 作用:就是为了方便记录数据(日志)

8 装饰设计模式

  • 设计模式 : 一套良好的编码风格 , 经过众多的开发人员不断的测试总结而来
学习目标
  • 熟悉装饰设计模式的使用
内容讲解
【1】概述
  • 装饰模式指的是在不改变原类, 不使用继承的基础上,动态地扩展一个对象的功能。

  • 不使用继承技术扩展功能, 可以降低耦合

  • 使用原则:
    • 装饰类和被装饰类需要有共同的父类型。
      • 在之前学习过的 BufferedWriter 和 FileWriter 就是装饰设计模式
      • BufferedWriter的父类为Writer
      • FileWriter的父类也是Writer
      • 我们把FileWriter的对象传递到BufferedWriter的构造中 , 那么可以理解为BufferedWriter是装饰类 , FileWriter是被装饰类
      • BufferedWriter对FileWriter的功能做了增强
    • 装饰类的构造要接收被装饰类的对象
      • FileWriter fw = new FileWriter(“路径”);
      • BufferedWriter bw = new BufferedWriter(fw);
    • 在装饰类中把要增强扩展的功能进行扩展
      • BufferedWriter和FileWriter的功能一样, 都具备Writer中写数据的功能
      • 但是BufferedWriter提供了缓冲区 , 相当于对FileWriter功能做了扩展
    • 对于不要增强的功能直接调用
      • 不需要增强的功能直接继承父类的即可
【2】代码实践

已知有接口Star和其子类型LiuDeHua。

public interface Star {
    public abstract void sing();
    public abstract void dance();
}

需求 :在不改变LiuDeHua类,及不使用继承的技术前提下,动态的扩展LiuDeHua的sing功能。

LiuDeHua就是一个被装饰类 , 需要对唱歌的功能进行扩展

思路 :

定义一个装饰类,去装饰增强 LiuDehua类。

步骤:

  • 创建LiuDeHua类并实现接口Star【被装饰类】

  • 定义一个装饰类LiuDeHuaWrapper实现Star 【装饰类】

  • 在装饰类里面定义一个成员变量类型是LiuDeHua,可以使用构造方法进行传入被装饰类对象。

  • 在装饰类中对sing方法进行功能扩展

  • 对dance不做改动

  • 测试类分别创建装饰类的对象和被装饰类的对象。将被装饰类对象刘德华对象设置给装饰类对象

package com.itheima.design_demo;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;

/*
    装饰模式指的是在不改变原类, 不使用继承的基础上,动态地扩展一个对象的功能

    使用原则 :
        1. 装饰类和被装饰类需要有共同的父类型。
        2. 装饰类要传入被装饰类的对象
        3. 在装饰类中把要增强扩展的功能进行扩展
        4. 对于不要增强的功能直接调用


    需求 : 在不改变LiuDeHua类,及不使用继承的技术前提下,动态的扩展LiuDeHua的sing功能。
            LiuDeHua就是一个被装饰类 , 需要对唱歌的功能进行扩展
    步骤:
        1. 创建LiuDeHua类并实现接口Star【被装饰类】
        2. 定义一个装饰类LiuDeHuaWrapper实现Star 【装饰类】
        3. 在装饰类里面定义一个成员变量类型是LiuDeHua,可以使用构造方法进行传入被装饰类对象。
        4. 在装饰类中对sing方法进行功能扩展
        5. 对dance不做改动
        6. 测试类分别创建装饰类的对象和被装饰类的对象。将被装饰类对象刘德华对象设置给装饰类对象
 */
public class Test {
    public static void main(String[] args) throws IOException {
        // 被装饰类对象
        LiuDeHua huaZai = new LiuDeHua();// 0x001

        // 装饰类对象
        LiuDeHuaWrapper liuDeHuaWrapper = new LiuDeHuaWrapper(huaZai);
        liuDeHuaWrapper.sing();
        liuDeHuaWrapper.dance();

//        // 被装饰类对象
//        FileWriter fw = new FileWriter("路径");
//        // 装饰类对象
//        BufferedWriter bw = new BufferedWriter(fw);
    }
}


// 1. 创建LiuDeHua类并实现接口Star【被装饰类】
class LiuDeHua implements Star {

    @Override
    public void sing() {
        System.out.println("唱忘情水...");
    }

    @Override
    public void dance() {
        System.out.println("华仔在跳老年迪斯高..");
    }
}

// 2. 定义一个装饰类LiuDeHuaWrapper实现Star 【装饰类】
class LiuDeHuaWrapper implements Star {
    // 3. 在装饰类里面定义一个成员变量类型是LiuDeHua,可以使用构造方法进行传入被装饰类对象。
    private LiuDeHua huaZai;// 0x001

    public LiuDeHuaWrapper(LiuDeHua huaZai) {// 0x001
        this.huaZai = huaZai;
    }

    @Override
    public void sing() {
        // 4. 在装饰类中对sing方法进行功能扩展
        System.out.print("华仔深情");
        huaZai.sing();
    }

    @Override
    public void dance() {
        // 5. 对dance不做改动
        huaZai.dance();
    }
}


// 明星接口 , 装饰类和被装饰类的父类型
interface Star {
    public abstract void sing(); // 唱歌

    public abstract void dance();// 跳舞
}
内容小结
  1. 装饰类和被装饰类需要有共同的父类型。
  2. 装饰类要传入被装饰类的对象
  3. 在装饰类中把要增强扩展的功能进行扩展
  4. 对于不要增强的功能直接调用

9 commons-io工具包(对文件的拷贝做优化)

学习目标
  • 能够熟悉导入commons-io工具包,并使用
内容讲解
【1】三方库的导入
  1. 下载commons-io相关jar包;http://commons.apache.org/proper/commons-io/

  2. 把commons-io-2.6.jar包复制到指定的Module的lib目录中

  3. 将commons-io-2.6.jar加入到项目中

    右键jar包:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

【2】API

1)org.apache.commons.io.IOUtils类

public static int copy(InputStream in, OutputStream out):
	把input输入流中的内容拷贝到output输出流中,返回拷贝的字节个数(适合文件大小为2GB以下)
    
public static long copyLarge(InputStream in, OutputStream out):
	把input输入流中的内容拷贝到output输出流中,返回拷贝的字节个数(适合文件大小为2GB以上)

package com.itheima.commons_io;

import org.apache.commons.io.IOUtils;

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

/*
    org.apache.commons.io.IOUtils类

    public static int copy(InputStream in, OutputStream out):
	把input输入流中的内容拷贝到output输出流中,返回拷贝的字节个数(适合文件大小为2GB以下)

    public static long copyLarge(InputStream in, OutputStream out):
	把input输入流中的内容拷贝到output输出流中,返回拷贝的字节个数(适合文件大小为2GB以上)
 */
public class Test1 {
    public static void main(String[] args) throws IOException {
        IOUtils.copy(new FileInputStream("D:\\传智播客\\安装包\\好看的图片\\liqin.jpg") , new FileOutputStream("day12_demo\\copy.jpg"));
    }
}

2)org.apache.commons.io.FileUtils

public static void copyFileToDirectory(final File srcFile, final File destFile): 
	复制文件到另外一个目录下。
public static void copyDirectoryToDirectory(File src , File dest ):
	复制src目录到dest位置。

代码实践:

package com.itheima.commons_io;

import org.apache.commons.io.FileUtils;

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

/*
    org.apache.commons.io.FileUtils

    public static void copyFileToDirectory(final File srcFile, final File destFile):
	复制文件到另外一个目录下。

    public static void copyDirectoryToDirectory(File src , File dest ):
	复制src目录到dest目录中。
 */
public class Test2 {
    public static void main(String[] args) throws IOException {
       
    }
}

内容小结

commons-io可以简化IO复制文件的操作。

day13-网络编程

今日目标

  • 网络编程
  • TCP通信
  • Junit单元测试
  • 单例设计模式
  • 多例设计模式
  • 工厂设计模式

1 网络编程

1.1 软件架构

  • C/S结构 :全称为Client/Server结构,是指客户端和服务器结构。常见程序有QQ、迅雷等软件
  • B/S结构 :全称为Browser/Server结构,是指浏览器和服务器结构。常见浏览器有谷歌、火狐等
  • 两种架构各有优势,但是都离不开网络的支持。网络编程 , 就是在一定的协议下,实现两台计算机的通信的程序
  • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.2 什么是网络编程

  • 在网络通信协议下,不同计算机上运行的程序,可以进行数据传输

1.3 网络编程三要素

  • IP地址 : 设备在网络中的地址,是唯一的标识。
  • 端口 : 设备在网络中的地址,是唯一的标识。
  • 数据在网络中传输的规则,常见的协议有UDP协议和TCP协议。

1.4 IP地址

  • IP:全称”互联网协议地址”,也称IP地址。是分配给上网设备的数字标签。常见的IP分类为:ipv4和ipv6

    简单来说 : 就是设备在网络中的唯一标识 , 想要连接哪一台电脑 , 就找到此电脑在网络中的ip地址

  • IP地址常见分类 : ipv4和ipv6

  • 常用命令:

    • ipconfig:查看本机IP地址
    • IP地址:检查网络是否连通
  • 特殊IP地址:

    • 127.0.0.1:是回送地址也称本地回环地址,可以代表本机的IP地址,一般用来测试使用
  • 为了方便我们对IP地址的获取和操作,Java提供了一个类InetAddress 供我们使用
    InetAddress:此类表示Internet协议(IP)地址

    • static InetAddress getByName(String host)在给定主机名的情况下确定主机的 IP 地址
      String getHostName()获取此 IP 地址的主机名
      String getHostAddress()返回 IP 地址字符串(以文本表现形式)。

1.5 端口

  • 端口:应用程序在设备中唯一的标识。

  • 端口号:应用程序的唯一标识方式 , 用两个字节表示的整数,它的取值范围是0~65535。
    其中0~1023之间的端口号用于一些知名的网络服务或者应用。
    我们自己使用1024以上的端口号就可以了。

  • 注意:一个端口号只能被一个应用程序使用。

1.6 通信协议

  • 协议:计算机网络中,连接和通信的规则被称为网络通信协议
  • UDP协议
    • 用户数据报协议(User Datagram Protocol)
    • UDP是面向无连接通信协议。
    • 速度快,有大小限制一次最多发送64K,数据不安全,易丢失数据。
  • TCP协议
    • 传输控制协议 (Transmission Control Protocol)
    • TCP协议是面向连接的通信协议。
    • 速度慢,没有大小限制,数据安全

2 TCP通信

2.1 TCP发送数据

package com.itheima.tcp_demo.demo1;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

/*
    客户端 :

    发送数据的步骤
        1 创建客户端的Socket对象 : Socket(String host, int port) 与指定服务端连接
            参数说明:
            host 表示服务器端的主机名,也可以是服务器端的IP地址,只不过是String类型的
            port 表示服务器端的端口

        2 通获Socket对象取网络中的输出流,写数据
            OutputStream getOutputStream()

        3 释放资源
            void close()

 */
public class ClientDemo {
    public static void main(String[] args) throws IOException {
        // 创建客户端的Socket对象(Socket) 与指定服务端连接
        Socket socket = new Socket("127.0.0.1", 10010);

        // 通获Socket对象取网络中的输出流,写数据
        OutputStream os = socket.getOutputStream();
        os.write("hello".getBytes());

        // while(true){}

        // 释放资源
        os.close();
        socket.close();
    }
}

2.2 TCP接收数据

package com.itheima.tcp_demo.demo1;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

/*
     服务端接收数据 :

    1 创建服务器端的Socket对象 : ServerSocket类
        ServerSocket(int port)  : 构造方法需要绑定一个端口号 , port就是端口号

    2 监听客户端连接,并接受连接,返回一个Socket对象
        Socket accept() : 该方法会一直阻塞直到建立连接

    3 获取网络中的输入流,用来读取客户端发送过来的数据
        InputStream getInputStream()

    4 释放资源 : 服务端一般不会关闭
        void close()
 */
public class ServerDemo {
    public static void main(String[] args) throws IOException {
//        1 创建服务器端的Socket对象 : ServerSocket类
//        ServerSocket(int port)  : 构造方法需要绑定一个端口号 , port就是端口号
        ServerSocket serverSocket = new ServerSocket(10010);

//        2 监听客户端连接,并接受连接,返回一个Socket对象
//        Socket accept() : 该方法会一直阻塞直到建立连接
        Socket socket = serverSocket.accept();
//
//        3 获取网络中的输入流,用来读取客户端发送过来的数据
//        InputStream getInputStream()
        InputStream is = socket.getInputStream();
        int by;
        System.out.println("read方法执行前");
        while ((by = is.read()) != -1) {
            System.out.print((char) by);
        }
        System.out.println("read方法执行后");
    }
}

2.3 TCP通信原理分析

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.4 TCP三次握手

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.5 TCP练习1

package com.itheima.tcp_demo.test1;

import java.io.*;
import java.net.Socket;

/*
    客户端 :

    发送数据的步骤
        1 创建客户端的Socket对象 : Socket(String host, int port) 与指定服务端连接
            参数说明:
            host 表示服务器端的主机名,也可以是服务器端的IP地址,只不过是String类型的
            port 表示服务器端的端口

        2 通获Socket对象取网络中的输出流,写数据
            OutputStream getOutputStream​()

        3 释放资源
            void close​()

 */
public class ClientDemo {
    public static void main(String[] args) throws IOException {
        // 创建客户端的Socket对象(Socket) 与指定服务端连接
        Socket socket = new Socket("127.0.0.1", 10010);

        // 通获Socket对象取网络中的输出流,写数据
        OutputStream os = socket.getOutputStream();
        os.write("hello".getBytes());
        // 像服务端写入结束标记
        socket.shutdownOutput();

        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line = br.readLine();
        System.out.println(line);

        // 释放资源
        br.close();
        os.close();
        socket.close();
    }
}

package com.itheima.tcp_demo.test1;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/*
     服务端接收数据 :

    1 创建服务器端的Socket对象 : ServerSocket类
        ServerSocket​(int port)  : 构造方法需要绑定一个端口号 , port就是端口号

    2 监听客户端连接,并接受连接,返回一个Socket对象
        Socket accept​() : 该方法会一直阻塞直到建立连接

    3 获取网络中的输入流,用来读取客户端发送过来的数据
        InputStream getInputStream​()

    4 释放资源 : 服务端一般不会关闭
        void close​()
 */
public class ServerDemo {
    public static void main(String[] args) throws IOException {
//        1 创建服务器端的Socket对象 : ServerSocket类
//        ServerSocket​(int port)  : 构造方法需要绑定一个端口号 , port就是端口号
        ServerSocket serverSocket = new ServerSocket(10010);

//        2 监听客户端连接,并接受连接,返回一个Socket对象
//        Socket accept​() : 该方法会一直阻塞直到建立连接
        Socket socket = serverSocket.accept();
//
//        3 获取网络中的输入流,用来读取客户端发送过来的数据
//        InputStream getInputStream​()
        InputStream is = socket.getInputStream();
        int by;

        while ((by = is.read()) != -1) {
            System.out.print((char) by);
        }

        BufferedWriter bos = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        bos.write("你谁啊");

        bos.close();
        is.close();
        socket.close();
        serverSocket.close();
    }
}

2.6 TCP练习2

package com.itheima.tcp_demo.test2;

import java.io.*;
import java.net.Socket;

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        // 创建客户端Socket对象
        Socket socket = new Socket("127.0.0.1", 10086);

        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\传智播客\\安装包\\好看的图片\\liqin.jpg"));

        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());

        int by;
        while ((by = bis.read()) != -1) {// 从本地中读一个字节
            bos.write(by);// 往服务器写一个字节
            bos.flush();
        }
        // 写结束标记
        socket.shutdownOutput();

        // 把网络中的字节输入流 , 封装成高效的字符输入流
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//        String line;
//        while ((line = br.readLine()) != null) {
//            System.out.println(line);
//        }
        String msg = br.readLine();// 读到换行才叫读到一行, 所以必须写服务器必须写newLine
        System.out.println(msg);

        // 释放资源
        bis.close();
        socket.close();
    }
}

package com.itheima.tcp_demo.test2;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        // 创建服务端的连接对象
        ServerSocket serverSocket = new ServerSocket(10086);
        Socket socket = null;
        BufferedInputStream bis = null;
        BufferedWriter socketBw = null;

        while (true) {
            // 获取Socket对象
            socket = serverSocket.accept();

            // 获取网络中的字节输入流  在封装成高效的字节输入流对象
            bis = new BufferedInputStream(socket.getInputStream());

            // 创建本地的字节输出流 , 封装成高效的字节输出流
            BufferedOutputStream bw = new BufferedOutputStream(new FileOutputStream("day13_demo\\图片\\a.jpg"));

            int by;
            while ((by = bis.read()) != -1) {
                bw.write(by);
                bw.flush();
            }

            //关闭本地流
            bw.close();

            // 获取网络中的字节输出流 , 封装成高效的字符输出流
            socketBw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            socketBw.write("谢谢你");
            socketBw.newLine();// 必须有换行 , 因为readLine读到换行结束
            socketBw.flush();
        }

        // 释放资源
//        socketBw.close();
//        bis.close();
//        socket.close();
//        serverSocket.close();
    }
}

2.7 TCP练习3

package com.itheima.tcp_demo.test3;

import java.io.*;
import java.net.Socket;

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        // 创建客户端Socket对象
        Socket socket = new Socket("127.0.0.1", 10086);

        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\传智播客\\安装包\\好看的图片\\liqin.jpg"));

        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());

        int by;
        while ((by = bis.read()) != -1) {// 从本地中读一个字节
            bos.write(by);// 往服务器写一个字节
            bos.flush();
        }
        // 写结束标记
        socket.shutdownOutput();

        // 把网络中的字节输入流 , 封装成高效的字符输入流
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//        String line;
//        while ((line = br.readLine()) != null) {
//            System.out.println(line);
//        }
        String msg = br.readLine();// 读到换行才叫读到一行, 所以必须写服务器必须写newLine
        System.out.println(msg);

        // 释放资源
        bis.close();
        socket.close();
    }
}

package com.itheima.tcp_demo.test3;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        // 创建服务端的连接对象
        ServerSocket serverSocket = new ServerSocket(10086);

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        while (true) {
            // 获取Socket对象
            Socket socket = serverSocket.accept();
            executorService.submit(new ServerThread(socket));
        }

        // 释放资源
//        socketBw.close();
//        bis.close();
//        socket.close();
//        serverSocket.close();
    }
}

package com.itheima.tcp_demo.test3;

import javax.management.relation.RoleUnresolved;
import java.io.*;
import java.net.Socket;
import java.util.UUID;

public class ServerThread implements Runnable {
    Socket socket = null;
    BufferedOutputStream bw = null;

    public ServerThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {

        try {
            // 获取网络中的字节输入流  在封装成高效的字节输入流对象
            BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());

            // 创建本地的字节输出流 , 封装成高效的字节输出流
            bw = new BufferedOutputStream(new FileOutputStream("day13_demo\\图片\\" + UUID.randomUUID() + ".jpg"));

            int by;
            while ((by = bis.read()) != -1) {
                bw.write(by);
                bw.flush();
            }
            // 获取网络中的字节输出流 , 封装成高效的字符输出流
            BufferedWriter socketBw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            socketBw.write("谢谢你");
            socketBw.newLine();// 必须有换行 , 因为readLine读到换行结束
            socketBw.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭本地流
            try {
                bw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

3 Junit单元测试

3.1 Junit4单元测试概述

  • 单元测试就是编写测试代码,可以准确、快速地保证程序的正确性,Junit是Java单元测试的框架。

  • JUnit4可以通过注解的方式来标记方法 , 让方法存某种意义 ,常见的注解有:

    • @BeforeClass 全局只会执行一次,而且是第一个运行(标记的方法需要是一个静态无参无返回值方法)
    • @Before 在测试方法运行之前运行(非静态无参无返回值方法)
    • **@Test 测试方法(此方法必须是非静态无参无返回值方法), 主要用于测试的方法 **
    • @After 在测试方法运行之后运行(非静态无参无返回值方法)
    • @AfterClass 全局只会执行一次,而且是最后一个运行(标记的方法需要是一个静态无参无返回值方法)
    • @Ignore 忽略此方法

================================================================================================================

3.2 Junit的基本使用

  • 已知存在需要测试的类Calculator ,这是一个能够简单实现加减乘除、平方、开方的计算器类,然后对这些功能进行单元测试。
// 计算器类
public class Calculator {
    // 静态变量,用于存储运行结果
    private static int result; // 20
  
    // 加法运算
    public void add(int n) {
        result = result + n;
    }
    
    // 减法运算
    public void subtract(int n) {
        // Bug: 正确的应该是 result = result - n
        result = result - 1;  
    }
  
    // 乘法运算
    public void multiply(int n) {
        // 此方法尚未写好
    }         
  
    // 除法运算
    public void divide(int n) {
        result = result / n;
    }
    
    // 平方运算
    public void square(int n) {
        result = n * n;
    }
    
    // 平方根运算
    public void squareRoot(int n) {
        // Bug : 死循环
        for (; ;) ;            
    }
    
    // 将结果清零
    public void clear() {     
        result = 0;
    }
    
    // 返回运算结果
    public int getResult(){
        return result;
    }
}
  • 引入Junit4的jar包到模块中
    • 第一步 : 在模块中新建文件夹lib,拷贝今天资料中junit4的jar包到模块中
      外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    • 第二步 : 选中jar文件 , 右键选择 Add as Library

  • 生成Junit测试框架
    • 使用IDEA能够直接给需要测试的类生成测试框架,如下:

    • 选中本类任何位置右键选择 Generate(Alt+Insert)/ go to 选项 , 选择Test…

      外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    • 选择需要进行测试的方法

    • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    • 在上一步OK后系统会自动生成一个新类CalculatorTest,里面包含一些空的测试用例。

      你只需要将这些测试用例稍作修改即可使用,完整的CalculatorTest代码如下:

      
      
    • 限时测试

      对于那些逻辑很复杂,循环嵌套比较深的程序,很有可能出现死循环,因此一定要采取一些预防措施。限时测试是一个很好的解决方案。我们给这些测试函数设定一个执行时间,超过了这个时间,他们就会被系统强行终止,并且系统还会向你汇报该函数结束的原因是因为超时,这样你就可以发现这些Bug了。要实现这一功能,只需要给@Test标注加一个参数即可,代码如下:

      被测方法:

      public void squareRoot(int n) {
          //Bug : 死循环
          for (; ; ) ;            
      }
      
      测试方法:
      @Test(timeout = 1000)
      // Timeout参数表明了你要设定的时间,单位为毫秒,因此1000就代表1秒。
      public void squareRoot() {
          calculator.squareRoot(4);
          assertEquals(2 , calculator.getResult());
      }
      

3.3 断言

  • 概述 :

​ 预先判断某个条件一定成立,如果条件不成立,则直接报错。

  • 使用 :
    //第一个参数表示期望值
    //第二个参数表示实际值
    // 如果实际值和期望值相同,说明结果正确就测试通过,如果不相同,说明结果是错误的,就会报错
    Assert.assertEquals( 期望值, 实际值);
    Assert.assertEquals("异常原因", 期望值, 实际值);
    
    //例如:
    int result = add(100,200);
    Assert.assertEquals(300, result);  
    
  • 小结 : 如何进行断言
    Assert.assertEquals(期望值,实际值)
    

4 单例设计模式

学习目标

  • 能够使用单例设计模式设计代码

内容讲解

  • 正常情况下一个类可以创建多个对象
public static void main(String[] args) {
	// 正常情况下一个类可以创建多个对象
	Person p1 = new Person();
	Person p2 = new Person();
	Person p3 = new Person();
}
  • 如果说有时一个对象就能搞定的事情 , 非要创建多个对象 , 浪费内存!!!
4.1 单例设计模式的作用
  • 单例模式,是一种常用的软件设计模式。通过单例模式可以保证项目中,应用该模式的这个类只有一个实例。

    即一个类只有一个对象实例。

  • 好处 :可以节省内存,共享数据

4.2 单例设计模式实现步骤
  1. 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
  2. 在该类内部产生一个唯一的实例化对象,并且将其封装为private static 类型的成员变量。
  3. 定义一个静态方法返回这个唯一对象。
4.3 单例设计模式的类型

根据创建对象的时机单例设计模式又分为以下两种:

  1. 饿汉单例设计模式

  2. 懒汉单例设计模式

=============================================================================================

4.4 饿汉单例设计模式
  • 饿汉单例设计模式就是使用类的时候已经将对象创建完毕

    不管以后会不会使用到该实例化对象,先创建了再说。很着急的样子,故被称为“饿汉模式”。

  • 代码如下:

    public class Singleton {
        // 1.将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
        private Singleton() {}
    
        // 2.在该类内部产生一个唯一的实例化对象,并且将其封装为private static 类型的成员变量。
        private static final Singleton instance = new Singleton();
        
        // 3.定义一个静态方法返回这个唯一对象。
        public static Singleton getInstance() {
            return instance;
        }
    }
    
  • 需求:定义一个皇帝类,要求对象只能存在一个。

    package com.itheima.singledesign;
    /*
        需求 : 使用单例模式(饿汉式) , 要求此类只能有一个对象
    
        步骤 :
            1. 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
            2. 在该类内部产生一个唯一的实例化对象,并且将其封装为private static 类型的成员变量。
            3. 定义一个静态方法返回这个唯一对象。
     */
    public class King {
        // 1. 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
        private King(){
    
        }
    
        // 2. 在该类内部产生一个唯一的实例化对象,并且将其封装为private static 类型的成员变量。
        private static final King KING = new King();
    
        // 3. 定义一个静态方法返回这个唯一对象。
        public static King getInstance(){
            return KING;
        }
    }
    

=============================================================================================

4.5 懒汉单例设计模式
  • 懒汉单例设计模式就是调用getInstance()方法时对象才被创建

    也就是说先不急着实例化出对象,等要用的时候才实例化出对象。不着急,故称为“懒汉模式”。

    代码如下:

public class Singleton {

 // 1.将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
private Singleton() {}

// 2.在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型的成员变量。
private static Singleton instance;

// 3.定义一个静态方法返回这个唯一对象。要用的时候才例化出对象

public static synchronized Singleton getInstance() {
    if(instance == null) {
        instance = new Singleton();
    }
    return instance;
}

}


- ##### 注意 :

- 懒汉单例设计模式在多线程环境下可能会实例化出多个对象,不能保证单例的状态,所以加上关键字:synchronized,保证其同步安全。



- 需求:使用懒汉单例 ,改写皇帝类的单例模式

```java
package com.itheima.singledesign;

/*
    需求 : 使用单例模式(懒汉式) , 要求此类只能有一个对象

    步骤 :
        1. 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
        2. 在该类内部定义一个private static修饰的成员变量 . 此变量不需要赋值
        3. 定义一个静态方法返回这个唯一对象。 此方法需要加上synchronized关键字保证在多线程中也只有一个实例对象
 */
public class King2 {
    // 1. 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
    private King2() {
    }

    //  2. 在该类内部定义一个private static修饰的成员变量 . 此变量不需要赋值
    private static King2 king2;

    // 3. 定义一个静态方法返回这个唯一对象。 此方法需要加上synchronized关键字保证在多线程中也只有一个实例对象
    public static synchronized King2 getInstance() {
        if (king2 == null) {
            king2 = new King2();
        }
        return king2;
    }
}
知识小结
  • 单例模式可以保证系统中一个类只有一个对象实例。

  • 实现单例模式的步骤:

    • 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
    • 在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型的成员变量。
    • 定义一个静态方法返回这个唯一对象。

5 多例设计模式

学习目标

  • 能使用多例设计模式设计代码

内容讲解

5.1 多例设计模式的作用
  • 多例模式,是一种常用的设计模式之一。通过多例模式可以保证项目中,应用该模式的类有固定数量的实例

    多例类要自我创建并管理自己的实例,还要向外界提供获取本类实例的方法。

  • 使用场景:线程池

    线程池 = Executors.newFixedThreadPool(3);
    
5.2.实现步骤

​ 1.创建一个类, 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。

​ 2.在类中定义该类被创建对象的总数量

​ 3.在类中定义存放类实例的list集合

​ 4.在类中提供静态代码块,在静态代码块中创建类的实例

​ 5.提供获取类实例的静态方法

5.3.实现代码
  • 某一个学科有固定3位老师,年级中上该课程的老师就是这三位老师其中一位

    要求使用多例模式 ,每次获取的都是这三位老师其中一位

package com.itheima.moreinstance_demo;

import java.util.ArrayList;
import java.util.Random;

/*
    需求  : 某一个学科有固定3位老师,年级中上该课程的老师就是这三位老师其中一位
            要求使用多例模式 ,每次获取的都是这三位老师其中一位

    实现步骤 :
        1.创建一个类,  将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
        2.在类中定义该类被创建对象的总数量
        3.在类中定义存放类实例的list集合
        4.在类中提供静态代码块,在静态代码块中创建类的实例
        5.提供获取类实例的静态方法
 */
public class Teacher {
    // 1.创建一个类,  将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
    private Teacher() {
    }

    // 2.在类中定义该类被创建对象的总数量
    private static int maxCount = 3;

    // 3.在类中定义存放类实例的list集合
    private static ArrayList<Teacher> list = new ArrayList<>();// {teacher1  , teacher2 , teacher3}

    // 4.在类中提供静态代码块,在静态代码块中创建类的实例
    static {
        for (int i = 0; i < maxCount; i++) {
            list.add(new Teacher());
        }
    }

    // 5.提供获取类实例的静态方法
    public static Teacher getInstance() {
        int index = new Random().nextInt(3);// [0 - 2]
        return list.get(index);
    }

}

5.4 小结
  • 多例模式作用 : 可以保证项目中一个类有固定个数的实例, 在实现需求的基础上, 能够提高实例的复用性.

    实现多例模式的步骤 :

    • 创建一个类, 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
    • 在类中定义该类被创建的总数量
    • 在类中定义存放类实例的list集合
    • 在类中提供静态代码块,在静态代码块中创建类的实例
    • 提供获取类实例的静态方法

6 工厂设计模式

学习目标

  • 能够使用工厂设计模式设计代码

内容讲解

6.1 概述
  • 工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。之前我们创建类对象时, 都是使用 new 对象的形式创建, 除new 对象方式以外, 工厂模式也可以创建对象.
6.2 作用
  • 解决类与类之间的耦合问题
6.3案例实践
  • 需求:定义汽车工厂类,生产各种品牌的车

  • 实现步骤

    • 编写一个Car接口, 提供run方法
    • 编写一个Falali类实现Car接口,重写run方法
    • 编写一个Benchi类实现Car接口,重写run方法
    • 提供一个CarFactory(汽车工厂),用于生产汽车对象
    • 定义CarFactoryTest测试汽车工厂

实现代码

package com.itheima.factorydesign_demo;

/*
  - 需求:定义汽车工厂类,生产各种品牌的车

  - 实现步骤
      - 编写一个Car接口, 提供run方法
      - 编写一个Falali类实现Car接口,重写run方法
      - 编写一个Benchi类实现Car接口
      =============================================
      - 提供一个CarFactory(汽车工厂),用于生产汽车对象
      - 定义CarFactoryTest测试汽车工厂
 */
public class CarTest {
    public static void main(String[] args) {
        Car benchi = CarFactory.getInstance(Brand.BENCHI);
        System.out.println(benchi);
    }
}

// 汽车接口
interface Car {
    public abstract void run();
}

// 编写一个Falali类实现Car接口,重写run方法
class Falali implements Car {
    public Falali() {
    }

    @Override
    public void run() {
        System.out.println("法拉利破百需要3秒...");
    }
}

// 编写一个Benchi类实现Car接口
class Benchi implements Car {
    @Override
    public void run() {
        System.out.println("奔驰破百需要5秒...");
    }
}

// 汽车品牌枚举
enum Brand {
    BENCHI, FALALI, BAOMA, BINLI, AODI;
}

// 提供一个CarFactory(汽车工厂),用于生产汽车对象
class CarFactory {
    private CarFactory() {
    }

    public static Car getInstance(Brand brand) {
        switch (brand) {
            case FALALI:
                return new Falali();
            case BENCHI:
                return new Benchi();
            default:
                return null;
        }
    }
}

 提供一个CarFactory(汽车工厂),用于生产汽车对象
//class CarFactory{
//    private CarFactory(){}
//
//    public static Car getInstance(String brand) {
//        if(brand.equals("Falali")){
//            return new Falali(10);
//        }else if(brand.equals("Benchi")) {
//            return new Benchi();
//        }else {
//            return null;
//        }
//    }
//}

知识小结
  • 工厂模式的存在可以改变创建对象的方式,降低类与类之间的耦合问题.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值