Java入门

文章目录


img

Java SE

黑马程序员

private、protect、default的区别

  • private:只能被自己访问和修改。
  • protected:自身,子类及同一个包中类可以访问。
  • default(默认):同一包中的类可以访问,声明时没有加修饰符,认为是friendly。

this的用法

  • this只能调用本类的成员方法或成员变量
//静态当中不能用this关键字
//this代表当前对象,而静态方法通过类名称调用
package com.xpzt.day07.demo05;

public class Zi extends Fu {

    int num = 20;

    public Zi() {
        this(1);
        System.out.println("无参构造方法");
    }

    public Zi(int num) {
        this(1, 2);
        System.out.println("一个参数构造方法");
    }

    public Zi(int num1, int num2) {
        //这里有一个隐含的super();
        System.out.println("两个参数构造方法");
    }

    public void method() {
        int num = 10;
        System.out.println(num);
        System.out.println(super.num);//11
        super.methodB();//父类
        methodB();//子类
        this.methodB();//子类 -- 就是为了容易看出是子类的成员方法
        System.out.println(this.num);   //this是调用本类成员变量或方法
    }
    @Override
    public void methodB() {
        super.methodB();
        System.out.println("子类方法B执行!");
    }

}

继承extends及覆盖重写Override

  • 子类可以继承父类的成员方法及成员变量并在父类的基础上加以延伸
public class NewPhone extends Phone {

    @Override
    public void show() {
        super.show();//继续用父类的
        System.out.println("姓名");
        System.out.println("归属地");
    }

}

super的用法

  • 子类可以用super调用父类的成员方法或成员变量
public class Zi extends Fu {

    int num = 10;

    public void method() {
        int num = 20;
        System.out.println(num);
        //this是指本类的成员变量
        System.out.println(this.num);

        System.out.println(super.num);

    }

}

接口interface及implements

  • 接口是默认使用抽象方法的,使用其他类实现接口类
public class MyInterfaceAbstractImpl implements MyInterfaceAbstract {
    @Override
    public void methodAbs1() {
        System.out.println("这是第一个方法");
    }

    @Override
    public void methodAbs2() {
        System.out.println("这是第二个方法");

    }

    @Override
    public void methodAbs3() {
        System.out.println("这是第三个方法");

    }

    @Override
    public void methodAbs4() {
        System.out.println("这是第四个方法");

    }
}

static的用法

static修饰成员变量

  • 静态变量的数据被本类中所有实例对象所共享
  • 如果该静态变量的访问权限高于private,则该静态变量可通过“类名.变量名”直接访问

static 修饰成员方法

  • 在类中,被static修饰的方法称作静态方法。同静态变量一样,如果该静态方法的访问权限高于private,则该静态方法可通过“类名.方法名/接口名.方法名”直接调用,而不需要创建对象调用

多态

  • 使用多态的写法,左侧父类的引用,指向了右侧子类的对象

  • 成员变量:编译看左边,运行看左边

  • 成员方法:编译看左边,运行看右边

public class Fu {
    int num = 10;

    public void showNum() {
        System.out.println(this.num);
    }

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

    public void methodFu(){
        System.out.println("父类特有方法");
    }
}

public class Zi extends Fu {
    int num = 20;

    @Override
    public void showNum() {
        System.out.println(this.num);
    }

    @Override
    public void method(){
        System.out.println("父类方法");
    }

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

public class Demo01MultiField {

    public static void main(String[] args) {


        //成员变量:编译看左边,运行看左边
        //成员方法:编译看左边,运行看右边
        Fu obj = new Zi();
        System.out.println(obj.num);//父类 -- 看等号左边是谁,优先用谁,没有则向上找

        obj.showNum();///父类 -- 看等号右边是谁,优先用谁,没有则向上找
    }
}

public class Demo02MultiMethod {

    public static void main(String[] args) {

        //成员变量:编译看左边,运行看左边
        //成员方法:编译看左边,运行看右边
        Fu obj=new Zi();//多态
        obj.method();//父子若有,优先用子类
        obj.methodFu();//子类没有,父类有,向上找到父类
    }
}

向上转型和向下转型

public class Demo01Main {

    public static void main(String[] args) {

        //对象的向上转型就是,父类引用指向子类对象
        Animal animal = new Cat();
        animal.eat();

        //向下转型 -- “还原”动作
        Cat cat = (Cat) animal;
        cat.catchMouse();

        //错误向下转型写法
        //本来new的是猫,现在非要当成狗
        //ClassCastException
//        Dog dog = (Dog) animal;
    }
}

instanceof用法

  • 向下转型一定要进行instanceof判断,否则可能发生类转换异常
public class Demo02Instanceof {

    public static void main(String[] args) {

        Animal animal = new Cat();
        animal.eat();


        //向下转型一定要进行instanceof判断,否则可能发生类转换异常
        if (animal instanceof Cat) {
            Cat cat = (Cat) animal;
            cat.catchMouse();
        }

        if (animal instanceof Dog) {
            Dog dog = (Dog) animal;
            dog.watchHouse();
        }

        giveAPet(new Dog());
    }

    public static void giveAPet(Animal animal) {
        //判断父类引用animal本来是不是Cat
        if (animal instanceof Cat) {
            Cat cat = (Cat) animal;
            cat.catchMouse();
        }
        //判断父类引用animal本来是不是Dog
        if (animal instanceof Dog) {
            Dog dog = (Dog) animal;
            dog.watchHouse();
        }
    }
}

接口可作为方法的参数

public interface USC {
    public abstract void open();//打开设备

    public abstract void close();//关闭设备
}

//鼠标就是一个USC设备
public class Mouse implements USC {
    @Override
    public void open() {
        System.out.println("打开鼠标");
    }

    @Override
    public void close() {
        System.out.println("关闭鼠标");

    }

    public void click() {
        System.out.println("点击鼠标");
    }
}

//键盘就是一个USC设备
public class KeyBoard implements USC {

    @Override
    public void open() {
        System.out.println("打开键盘");
    }

    @Override
    public void close() {
        System.out.println("关闭键盘");
    }

    public void type() {
        System.out.println("键盘输入");
    }
}

public class Computer {
    public void powerOn() {
        System.out.println("笔记本开机");
    }

    public void powerOff() {
        System.out.println("笔记本关机");
    }

    //使用USC设备的方法, 使用接口作为方法的参数
    public void useDevice(USC usc) {//USC usc=new Mouse()/new KeyBoard()
        //成员变量:编译看左边,运行看左边
        //成员方法:编译看左边,运行看右边
        usc.open();//打开设备
        if (usc instanceof Mouse) {  //先判断
            Mouse mouse = (Mouse) usc;//向下转型
            mouse.click();
        } else if (usc instanceof KeyBoard) {//先判断
            KeyBoard keyBoard = (KeyBoard) usc;//向下转型
            keyBoard.type();
        }
        usc.close();//关闭设备
    }
}

public class Computer {
    public void powerOn() {
        System.out.println("笔记本开机");
    }

    public void powerOff() {
        System.out.println("笔记本关机");
    }

    //使用USC设备的方法, 使用接口作为方法的参数
    public void useDevice(US usc) {//USC usc=new Mouse()/new KeyBoard()
        //成员变量:编译看左边,运行看左边
        //成员方法:编译看左边,运行看右边
        usc.open();//打开设备
        if (usc instanceof Mouse) {  //先判断
            Mouse mouse = (Mouse) usc;//向下转型
            mouse.click();
        } else if (usc instanceof KeyBoard) {//先判断
            KeyBoard keyBoard = (KeyBoard) usc;//向下转型
            keyBoard.type();
        }
        usc.close();//关闭设备
    }
}

final的用法

  • final关键字修饰类, 修饰字段, 修饰方法,修饰局部变量,修饰方法的形参
  • final修饰类,表示最终类, 不能被继承,不能作为父类存在
  • final修饰方法,不能被覆盖(重写)
  • final修饰局部变量, 一旦初始化就不能再修改, 注意final修饰引用类型变量,是指这个变量不能再指向其他对象 , 可以修改它的字段值
  • final修饰方法形参, 在方法体中不能修改final参数的值
public class Demo01Final {

    public static void main(String[] args) {

        int num1 = 10;
        System.out.println(num1);
        final int num2 = 20;
        System.out.println(num2);
        //错误写法,"一次赋值,终生不变"
        //num2 = 10;

        //正确写法,只要保证有唯一一次赋值就行
        final int num3;
        num3 = 11;

        //对于基本类型来说,不可改变的是变量的数据不可改变
        //对于引用类型来说,不可改变的是变量的地址不可改变 new了之后地址就改变
        Student stu1 = new Student("赵丽颖");
        System.out.println(stu1.getName());
        System.out.println(stu1);
        stu1 = new Student("霍建华");
        System.out.println(stu1.getName());
        System.out.println(stu1);

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

        final Student stu2 = new Student("李浩");
        System.out.println(stu2.getName());
        //不能改变的是地址,但是内容可以改变
        //stu2 = new Student("张思");
        stu2.setName("李浩浩浩");
        System.out.println(stu2.getName());
    }
}

导包import

package com.xpzt.day12.demo02;

public class MyClass {

    public int num = 10;

    public void method() {
        System.out.println(num);
    }
}

package com.xpzt.day12.demo02;

public class MyAnother {

    public void anotherMethod() {
        System.out.println((new MyClass()).num);
    }
}

package com.xpzt.day12.demo02.sub;
//导包MyClass
import com.xpzt.day12.demo02.MyClass;

public class MySon extends MyClass {
    public void methodSon() {
        System.out.println(super.num);
    }
}

package com.xpzt.day12.demo02.sub;
//导包MyClass
import com.xpzt.day12.demo02.MyClass;

public class Stranger {

    public void methodStranger() {
        System.out.println(new MyClass().num);
    }
}

内部类和外部类

public class Body {//外部类

    public class Heart {//成员内部类

        //内部类的方法
        public void heat() {
            System.out.println("心脏跳动,滴滴滴");
            System.out.println("我叫:" + name);
        }
    }

    //外部类的成员方法
    private String name;

    //外部类的方法
    public void method() {
        System.out.println("外部类方法");
        //使用匿名对象
        new Heart().heat();
    }

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

    public String getName() {
        return name;
    }

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

  • 内部类和外部类成员变量出现重名,则引用外部类成员变量格式:外部类.this.外部成员变量名
//如果出现了重名 现象,那么格式是 :外部类.this.外部成员变量名
public class Outer {

    int num = 30;//外部成员变量

    public class Inner {

        int num = 20;//内部成员变量

        public void method() {
            int num = 10;//内部类方法的局部变量
            System.out.println(num);//局部变量,就近原则
            System.out.println(this.num);//内部类的成员变量
            System.out.println(Outer.this.num);//外部类的成员变量

        }
    }
}

  • 通过外部类名称.内部类名称 对象名 = new 外部类名称().new内部类名称() 调用内部类方法
public class Body {//外部类

    public class Heart {//成员内部类

        //内部类的方法
        public void heat() {
            System.out.println("心脏跳动,滴滴滴");
            System.out.println("我叫:" + name);
        }
    }

    //外部类的成员方法
    private String name;

    //外部类的方法
    public void method() {
        System.out.println("外部类方法");
        //使用匿名对象
        new Heart().heat();
    }

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

    public String getName() {
        return name;
    }

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

public class Demo01InnerClass {

    public static void main(String[] args) {
        Body body = new Body("陈浩");//外部类对象
        //通过外部类对象,调用内部类方法,里面简介在使用内部类Heart
        body.method();
        System.out.println("=============");

        //使用公式写:
        //外部类名称.内部类名称 对象名 = new 外部类名称().new内部类名称();
        Body.Heart heart = new Body("猪比").new Heart();
        heart.heat();
    }
}

局部内部类

  • 局部内部类,如果希望访问所在方法的局部变量,那么这个局部变量必须是{有效final的}
  • 加final关键字或者不加final关键字就赋值唯一一次
public class MyOuter {

    public void methodOuter() {
        /*final*/int num;
        num = 10;
        class Inner {
            public void methodInner() {
                System.out.println(num);
            }
        }
    }
}

匿名内部类

  • 格式:new interface(){ 成员函数 / 覆盖重写成员函数 }
  • 匿名内部类.方法(既是匿名内部类,又是匿名对象)
public class DemoMain {

    public static void main(String[] args) {

        //使用匿名内部类
        MyInterface objA = new MyInterface() {
            @Override
            public void method1() {
                System.out.println("匿名内部类覆盖重写了接口抽象方法 111-A");
            }

            @Override
            public void method2() {
                System.out.println("匿名内部类覆盖重写了接口抽象方法 222-A");
            }
        };
        objA.method1();
        objA.method2();

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

        //匿名内部类.方法(既是匿名内部类,又是匿名对象)
        new MyInterface() {
            @Override
            public void method1() {
                System.out.println("匿名内部类覆盖重写了接口抽象方法 111-A");
            }

            @Override
            public void method2() {
                System.out.println("匿名内部类覆盖重写了接口抽象方法 222-A");
            }
        }.method1();

        //使用了匿名内部类,省略了对象名称,也是匿名对象
        new MyInterface() {
            @Override
            public void method1() {
                System.out.println("匿名内部类覆盖重写了接口抽象方法 111-B");
            }

            @Override
            public void method2() {
                System.out.println("匿名内部类覆盖重写了接口抽象方法 222-B");
            }
        }.method2();
    }
}

toString

  • 看一个类是否重写了toString,直接打印这个类的对象即可,如果没有重写toString方法,那么打印的是对象的地址值
public class Demo01ToString {

    public static void main(String[] args) {
        Person person = new Person();
        person.setName("李白");
        person.setAge(20);

        //看一个类是否重写了toString,直接打印这个类的对象即可,如果没有重写toString方法,那么打印的是对象的地址值
        String p = person.toString();
        System.out.println(person);
        System.out.println(p);

    }
}

equals

  • 基本数据类型:比较的是值
  • 引用数据类型:比较的是两个对象的地址值
import java.util.Objects;

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

    public String getName() {
        return name;
    }

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

    //覆盖重写toString方法
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    //Object类的equals方法,默认比较是两个对象的地址值,没有意义
    //覆盖重写equals方法,比较两个对象的属性

    @Override
    public boolean equals(Object obj) {
        //增加一个判断,如果是this本身,直接返回true,提高程序效率
        if (obj == this) {
            return true;
        }

        //增加一个判断,如果传递参数是null,返回false,提高程序效率
        if (obj == null) {
            return false;
        }

        //增加一个判断防止类转换异常
        if (obj instanceof Person) {
            // 使用向下转型,将obj强转成Person类
            Person p = (Person) obj;
            //比较两个对象的属性
            boolean b = this.name == p.name && this.age == p.age;
            return b;
        }
        return false;
    }

//    @Override
//    public boolean equals(Object o) {
//        if (
//    //getClass() != o.getClass()使用反射技术,判断o是不是Person类型  -- 等效于 obj instanceof Person
//     )
//        if (o == null || getClass() != o.getClass()) return false;
//        Person person = (Person) o;
//    //使用Object类的equals方法,对两个对象进行比较,可以防止空指针异常
//        return age == person.age && Objects.equals(name, person.name);
//    }

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

    public int getAge() {
        return age;
    }

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

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

    public Person() {
    }
}


public class Demo02Equals {

    public static void main(String[] args) {

        Object p1 = new Person("陈浩", 20);
        System.out.println(p1);
        Object p2 = new Person("猪比", 22);
        System.out.println(p2);

        //equals
        //基本数据类型:比较的是值
        //引用数据类型:比较的是两个对象的地址值
        boolean b1 = p1.equals(p2);
        System.out.println(b1);
        p1 = p2;
        boolean b2 = p1.equals(p2);
        System.out.println(b2);
    }
}

Data类及其方法

import java.util.Date;

public class Demo02Date {

    public static void main(String[] args) {
        demo01();
        demo02();
        demo03();

    }

    //long getTime() 把日期转换成毫秒值
    //相当于System.currentTimeMillis() -- 获取当前系统时间到1970年08:00:00经历了多少毫秒
    private static void demo03() {
        Date date=new Date();
        long time = date.getTime();
        System.out.println(time);
    }

    //Date类带参数构造方法
    //Date()传递毫秒值,把毫秒值转换为Date日期
    private static void demo02() {
        Date date = new Date(1608131750052L);
        System.out.println(date);
    }

    //Date类的空参数构造方法
    private static void demo01() {
        Date date = new Date();
        System.out.println(date);//获取当前的日期和时间
    }
}

DataFormat类及其SimpleDataFormat子类

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

/*java.text.DateFormat是日期/时间格式化子类的抽象类
作用:
    格式化(日期->文本),解析(文本->日期)

成员方法:
    String format(Date date) 按照指定格式,把Date日期,格式化为符合模式的字符串
    Date parse(String source) 把符合模式的字符串,解析为Date日期
DateFormat是一个抽象类,无法直接创建对象,可以用DateFormat类的子类SimpleDateFormat

java.text.SimpleDateFormat extends DateFormat

构造方法:
    SimpleDateFormat(String pattern)
        用给定的模式和默认语言环境的日期格式构造SimpleDateFormat。
模式:区分大小写
        y  年
        M  月
        d  天
        H  时
        m  分
        s  秒
* */
public class Demo01DateFormat {

    public static void main(String[] args) throws ParseException {

        demo01();
        Demo02();
    }


    /*public Date parse(String source) throws ParseException
    * parse声明了一个异常叫ParseException
    * 如果字符串和构造方法的模式不一样,那么程序就会抛出异常*/
    private static void Demo02() throws ParseException {
        DateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
        //Date parse(String source) 把符合模式的字符串,解析为Date日期
        Date date = dateFormat.parse("2020年12月17日 11:27:42");
        System.out.println(date);
    }

    private static void demo01() {
        DateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
        Date date = new Date();
        String time = dateFormat.format(date);
        System.out.println(date);
        System.out.println(time);

    }
}

测试

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

/*使用日期时间相关的API,计算一个人出生了多少天


 * */
public class Demo02Test {

    public static void main(String[] args) throws ParseException {
        //1.使用Scanner获取出生日期
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入出生日期,格式为yyyy-MM-dd:");
        String birthdayDateString = sc.next();
        //2.使用DateFormat类的parse方法,把字符串的出生日期,解析为Date格式的出生日期
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
        Date birthdayDate = df.parse(birthdayDateString);
        //3.把Date格式的出生日期转换成毫秒值
        long birdayDateTime = birthdayDate.getTime();
        //4.获取当前日期,转换成毫秒值
        long todayTime = new Date().getTime();
        //5.毫秒值相减
        long time = todayTime - birdayDateTime;
        System.out.println("你活了"+time/1000/60/60/24+"天");


    }
}

Calendar类及其方法

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

/*
 * Calendar类的常用成员方法
 *   public int get(int field):返回给定日历字段的值
 *   public void set(int field,int value):将给定的日历段设置成给定值
 *   public abstract void add(int field, int amount): 根据日历规则,为给定的日历字段添加或删去指定的时间量
 *   public Date getTime(): 返回一个表示此Calendar时间值(从历元到现在的毫秒偏移量)的Date对象
 * 成员方法的参数:
 *   int field :日历类的字段,可以使用Calendar类的静态成员变量获取 即Calendar.静态成员变量名
 *       public static final int YEAR=1;     年
 *       public static final int MONTH=2;    月
 *       public static final int DATE=5;     月中的某一天
 *       public static final int DAY_OF_MONTH=5;   月中的某一天
 *       public static final int HOUR=10;     时
 *       public static final int MINUTE=12;   分
 *       public static final int SECOND=13;   秒
 *
 * */
public class Demo02Calendar {

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

    /*
    *  public Date getTime(): 返回一个表示此Calendar时间值(从历元到现在的毫秒偏移量)的Date对象
    * 把日历对象,转换为日期对象
    * */
    private static void demo04() {
        //使用getInstance()获取Calendar的子类对象
        Calendar c = Calendar.getInstance();
        Date time = c.getTime();
        System.out.println(time);
    }

    /*
    *  public abstract void add(int field, int amount): 根据日历规则,为给定的日历字段添加或删去指定的时间量
       把指定的字段的值增加\减少指定的值
       * 参数:
       * int field:传递指定的日历字段(YEAR、MONTH...)
       * int amount:增加\减少指定的值
       *        正数:增加
       *        负数:减少
     * */
    private static void demo03() {
        //使用getInstance()获取Calendar的子类对象
        Calendar c = Calendar.getInstance();

        //把年增加两年
        c.add(Calendar.YEAR, 2);
        //把天减少三天
        c.add(Calendar.DATE, -3);
        int year = c.get(Calendar.YEAR);
        System.out.println(year);
        int date = c.get(Calendar.DATE);
        System.out.println(date);

    }

    /*
     *   public void set(int field,int value):将给定的日历段设置成给定值
          参数:
          * int field:传递指定的日历字段(YEAR、MONTH...)
          * int value:给指定字段设置的值
     * */
    private static void demo02() {

        //使用getInstance()获取Calendar的子类对象
        Calendar c = Calendar.getInstance();
        //设置年为9999年
        c.set(Calendar.YEAR, 9999);
        //同时设置年月日,可以使用set的重载方法
        c.set(888, 8, 8);
        int year = c.get(Calendar.YEAR);
        System.out.println(year);
        int month = c.get(Calendar.MONTH);
        System.out.println(month);
        int date = c.get(Calendar.DATE);
        System.out.println(date);


    }

    /*
    * *   public int get(int field):返回给定日历字段的值
          参数:传递指定的日历字段(YEAR、MONTH...)
          返回值:日历字段代表具体的值
     * */
    private static void demo01() {
        Calendar c = Calendar.getInstance();
        int year = c.get(Calendar.YEAR);
        System.out.println(year);
        int month = c.get(Calendar.MONTH);//西方的月份0-11
        System.out.println(month);
    }
}

System类及其方法

import java.util.Arrays;

/*
 * java.lang.System类中提供了大量的静态方法,可以获取与系统相关的信息或系统级操作,在System类的API文档中,常用的方法有:
 *   public static long currentTimeMillis():返回以毫秒为单位的当前时间
 *   public static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length):将数组中指定的数据拷贝到另一个数组中
 * */
public class Demo01System {
    public static void main(String[] args) {
        demo02();
    }

    /*
    * public static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length):将数组中指定的数据拷贝到另一个数组中
    *参数:
        src - 源数组。
        srcPos - 源数组中的起始位置(起始索引)。
        dest - 目标数组。
        destPos - 目标数据中的起始位置。
        length - 要复制的数组元素的数量。
    练习:
    * 将src数组中的前3个元素,复制到dest数组的前三个位置上
    *   复制元素前:
    *       src数组元素[1,2,3,4,5],dest数组元素[6,7,8,9,10]
    *   复制元素后:
    *       src数组元素[1,2,3,4,5],dest数组元素[1,2,3,9,10]
    *
    * */
    private static void demo02() {
        int[] src = {1, 2, 3, 4, 5};
        int[] dest = {6, 7, 8, 9, 10};
        System.out.println("dest复制前:" + Arrays.toString(dest));
        //使用System类中的arraycopy方法把源数组的前三个元素复制到目标数组的前三个位置上
        System.arraycopy(src, 0, dest, 0, 3);
        System.out.println("dest复制后:" + Arrays.toString(dest));

    }

    /*
    public static long currentTimeMillis():返回以毫秒为单位的当前时间
    用来测试程序的效率
    验证for循环打印1-9999所需要的的时间(毫秒)
    * */
    private static void demo01() {
        //起始时间
        long s = System.currentTimeMillis();
        for (int i = 1; i <= 9999; i++) {
            System.out.println(i);
        }
        //结束时间
        long e = System.currentTimeMillis();
        System.out.println("for循环打印1-9999所需要的的时间为:" + (e - s) + "毫秒");
    }
}

StringBuilder及其方法

  • StringBuilder叫字符串缓存区对象,使用时可以不断添加字符、字符串等已有内容。 需要使用时调用toString()获得对象里的所有字符串。
    • 作用: 简化字符串常量池的个数,节省内存
/*
 * java.lang.StringBuilder类:字符串缓冲区,可以提高使用字符串的效率
 *   构造方法:
 *       StringBuilder()构造一个不带任何字符的字符串生成器,其初始容量为16个字符
 *       StringBuilder(String str)构造一个字符串生成器,并初始化为指定的字符串内容
 * */
public class Demo01StringBuilder {

    public static void main(String[] args) {
        //空参数构造方法
        StringBuilder bu1 = new StringBuilder();
        //bu1.append("abc");
        System.out.println("bu1:" + bu1);
        //带参数(字符串)构造方法
        StringBuilder bu2 = new StringBuilder("abc");
        System.out.println("bu2:" + bu2);
    }

}

/*
 * StringBuilder的常用方法:
 *   public StringBuilder append(...)添加任意数据类型的字符串形式,并返回对象自身
 * */
public class Demo02StringBuilder {

    public static void main(String[] args) {
        //创建StringBuilder对象
        StringBuilder bu = new StringBuilder("abc");
        //append返回的是this,调用方法的对象bu,this==bu
        StringBuilder bu1 = bu.append("def"); //把bu的地址赋值给了bu1

        System.out.println(bu);//abcdef
        System.out.println(bu1);//abcdef
        System.out.println(bu1 == bu);//比较的是地址  true


        //使用append无需接收返回值
        //链式编程:返回值是一个对象,可以继续调用方法
        bu.append("ghi").append("jkl").append("jkl");
        System.out.println(bu);
    }
}
  • StringBuilder和String可以互换
/*
 * StringBuilder和String可以互换
 *   String->StringBuilder:可以使用StringBuilder的构造方法
 *       StringBuilder(String str)构造一个字符串生成器,并初始化为指定的字符串内容
 *   StringBuilder->String:可以使用StringBuilder的toString方法
 *          public String toString();将当前StringBuilder对象转换为String对象
 * */
public class Demo03StringBuilder {

    public static void main(String[] args) {
        //String->StringBuilder
        String str = "abc";
        System.out.println("str:"+str);
        StringBuilder str1 = new StringBuilder(str);
        //往StringBuilder中添加数据
        str1.append("def");
        System.out.println("str1:"+str1);

        //StringBuilder->String
        String str2 = str1.toString();
        System.out.println("str2:"+str2);
    }
}

包装类Integer及其方法

/*
装箱:把基本类型的数据,包装到包装类中(基本数据类型->包装类)
    构造方法:
        Integer(int value)构造一个新分配的Integer对象,它表示指定的int值
        Integer(String s)构造一个新分配的String对象,它表示String参数所指示的int值
            传递的字符串,必须是基本类型的字符串,否则会抛出异常 "100"正确   "a"抛出异常
     静态方法:
        static Integer valueOf(int i)返回一个int值的Integer实例
        static Integer valueOf(String s)返回保存指定的String的值的Integer对象
拆箱:在包装类中取出基本类型的数据(包装类->基本类型的数据)
     成员方法:
        int intValue()以int类型返回该Integer的值

 * */

public class Demo01Integer {

    public static void main(String[] args) {
        //装箱:把基本类型的数据,包装到包装类中(基本数据类型->包装类)
        //构造方法
        Integer in1 = new Integer(1);//方法上有横线,说明方法过时了
        System.out.println(in1);// 1 --  重写了toString方法(对象输出的不是地址)
        Integer in2 = new Integer("10");//传递的字符串,必须是基本类型的字符串,否则会抛出异常    "100"正确   "a"抛出异常
        System.out.println(in2);

        //静态方法
        Integer in3 = Integer.valueOf(1);
        System.out.println(in3);
        Integer in4 = Integer.valueOf("10");
        System.out.println(in4);

        //拆箱:在包装类中取出基本类型的数据(包装类->基本类型的数据)
        //成员方法
        int i = in4.intValue();
        System.out.println(i);
    }
}
  • 自动装箱和自动拆箱:基本类型的数据和包装类之间可以自动的相互转换
import java.util.ArrayList;

/*
自动装箱和自动拆箱:基本类型的数据和包装类之间可以自动的相互转换
 * */
public class Demo02Integer {

    public static void main(String[] args) {
        /*自动装箱:直接把int类型的整数赋值给包装类
        Integer in = 1;就相当于Integer in = new Integer(1);
         * */
        Integer in = 1;

        /*自动拆箱:in是包装类,无法直接参与运算,可以自动转换为基本数据类型,再进行计算
        in + 2;就相当于 in.intValue()+2=3
        in=in.intValue()+2又是一个自动装箱
         * */
        in = in + 2;

        ArrayList<Integer> list = new ArrayList<>();
        /*ArrayList集合无法直接存储整数,可以存储Integer包装类*/
        list.add(1);//-->自动装箱 list.add(new Integer(1));
        int a = list.get(0);//-->自动拆箱 list.get(0).intValue;
        System.out.println(list);
    }
}
基本数据类型                       			包装类

byte                                         Byte           

boolean                            			 Boolean     

short                       				 Short          

char                                		 Character   

int                                			 Integer        

long                                		 Long          

float                                        Float           

double                                       Double   
  • 包装类基本类型与字符串之间转换
/*
 * 基本类型与字符串之间的转换
 * 基本类型->字符串(String)
 *   1.基本类型的值+""    最简单的方法(工作中常用)
 *   2.包装类的静态方法toString(int i)不是Object类的toString() 重载
 *       static String toString(int i)返回一个表示指定整数的String对象
 *   3.String类的静态方法ValueOf(参数)
 *       static String valueOf(int i)返回int参数的字符串表示形式
 * 字符串(String)->基本类型
 *   使用包装类的静态方法parseXXX("字符串");
 *       Integer类:static int parseInt(String s)
 *       Double类:static double parseDouble(String s)

 * */
public class Demo03Integer {

    public static void main(String[] args) {
        //基本类型->字符串(String)
        int i1 = 100;
        String s1 = i1 + "";
        System.out.println(i1 + 200);//100200

        String s2 = Integer.toString(100);//返回一个表示指定整数的String对象
        System.out.println(s2 + 200);//100200

        String s3 = String.valueOf(100);//返回int参数的字符串表示形式
        System.out.println(s3 + 200);//100200

        //字符串(String)->基本类型
        int i2 = Integer.parseInt(s1);
        System.out.println(i2 + 10);

        /*int a=Integer.parseInt("a");
        System.out.println(a);*/
        double a = Double.parseDouble("1.1");
        System.out.println(a);

    }
}

Collection接口及其方法

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

/*
* java.util.Collection接口
*   所有单列集合最顶层的接口,里边定义了所有单列集合共性的方法
*   任意的单列集合都可以使用Collection接口中的方法
*
* 共性的方法:
     boolean add(E e)确保此 collection 包含指定的元素(可选操作)。
     boolean remove(Object o)从此 collection 中移除指定元素的单个实例,如果存在的话(可选操作)。
     void clear()移除此 collection 中的所有元素(可选操作)。
     boolean contains(Object o)如果此 collection 包含指定的元素,则返回 true。
     boolean isEmpty()如果此 collection 不包含元素,则返回 true。
     int size()返回此 collection 中的元素数。
     Object[] toArray()返回包含此 collection 中所有元素的数组。

* */
class Demo01Collection {

    public static void main(String[] args) {
        //创建集合对象,使用多态
        Collection<String> coll = new ArrayList<>();
        System.out.println(coll);//重写了toString方法

        /* boolean add(E e)确保此 collection 包含指定的元素(可选操作)。
         * 返回值是一个boolean值,一般我都返回true,所以不用接收*/

        coll.add("陈浩");
        coll.add("猪比");
        coll.add("狗江");
        coll.add("骚彪");
        System.out.println(coll);

        /* boolean remove(Object o)从此 collection 中移除指定元素的单个实例,如果存在的话(可选操作)。
         返回值是一个boolean值,集合中存在元素,删除元素,返回true
                             集合中不存在元素,删除失败,返回false
         * */

        boolean b1 = coll.remove("陈浩");
        System.out.println(b1);
        boolean b2 = coll.remove("shab");
        System.out.println(b2);

        /*boolean contains(Object o)如果此 collection 包含指定的元素,则返回 true。
         * 包含返回true
         * 不包含返回false*/

        boolean b3 = coll.contains("陈浩");
        System.out.println(b3);
        boolean b4 = coll.contains("陈");
        System.out.println(b4);


        /*boolean isEmpty()如果此 collection 不包含元素,则返回 true。
         * 集合为空返回true,集合不为空返回false*/

        boolean b5 = coll.isEmpty();
        System.out.println(b5);


        /*int size()返回此 collection 中的元素数。
         * 返回集合中元素个数*/

        int size = coll.size();
        System.out.println(size);


        /*Object[] toArray()返回包含此 collection 中所有元素的数组。
         * 把集合中的元素存储到数组中*/

        Object[] obj = coll.toArray();
        for (int i = 0; i < obj.length; i++) {
            System.out.println(obj[i]);
        }


        /*void clear()移除此 collection 中的所有元素(可选操作)。
        * 但是不删除集合,集合还存在*/

        coll.clear();
        System.out.println(coll);
        boolean b6 = coll.isEmpty();
        System.out.println(b6);

    }
}

迭代器Iterator

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

/*
 *
 * java.util.Iterator接口:迭代器(对集合进行遍历)
 * 有两个常用方法:
 *   boolean hasNext()如果仍有元素可以迭代,则返回true,没有返回false
 *       判断集合中还有没有下一个元素,如果有返回true,没有返回false
 *   E next()返回迭代的下一个元素
 *       取出集合下一个元素
 * Iterator迭代器,是一个接口,我们无法直接使用,需要使用Iterator接口的实现类对象
 *   获取实现类的方式比较特殊,Collection接口有一个方法叫iterator(),这个方法返回的就是迭代器的实现类对象
 *   Iterator<E> iterator()返回在此Collection的元素上进行迭代的迭代器
 *
 * 迭代器的使用步骤(重点):
 *   1.使用集合中的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收(多态)
 *   2.使用Iterator接口中的方法hasNext()判断还有没有下一个元素
 *   3.使用Iterator接口中的方法next()取出下一个元素
 * */
public class Demo01Iterator {

    public static void main(String[] args) {
        Collection<String> coll = new ArrayList<>();

        coll.add("陈浩");
        coll.add("陈bb");
        coll.add("陈cc");
        coll.add("陈dd");
        coll.add("陈ee");

        /*1.使用集合中的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收(多态)
         * 注意:
         *   Iterator<E>也是有泛型的,迭代器的泛型跟着集合走
         * */
        //接口                    实现类对象
        Iterator<String> it = coll.iterator();//多态

        /*2.使用Iterator接口中的方法hasNext()判断还有没有下一个元素*/
        while (it.hasNext()) {
            //3.使用Iterator接口中的方法next()取出下一个元素
            String str = it.next();
            System.out.println(str);
        }

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

        for (Iterator<String> it2 = coll.iterator(); it2.hasNext();/*每次自动向后移一位*/) {
            String str2 = it2.next();
            System.out.println(str2);
        }
    }
}

增强for循环

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

/*
 * 增强for循环:底层使用的也是迭代器,使用for循环的格式,简化了迭代器的书写
 * 是JDK1.5之后出现的新特性
 * Collection<E>extends Iterable<E>:所有单列集合都可以使用增强for循环
 * public interface Iterable<T>实现这个类接口允许对象成为"foreach"语句的目标
 *
 * 增强for循环:用来遍历集合和数组
 *
 * 格式:
 *       for(集合/数组的数据类型 变量名:集合名/数组名){
 *           sout(变量名);
 *       }
 * */
public class Demo03Foreach {

    public static void main(String[] args) {
        demo01();
        System.out.println("======================");
        demo02();
    }

    //使用增强for循环遍历集合
    private static void demo02() {
        /*Collection<E>extends Iterable<E>:所有单列集合都可以使用增强for循环
         * public interface Iterable<T>实现这个类接口允许对象成为"foreach"语句的目标*/
       // ArrayList<String> list = new ArrayList<>();
        Collection<String> list = new ArrayList<>();
        list.add("陈浩");
        list.add("陈aa");
        list.add("陈bb");
        list.add("陈cc");
        list.add("陈dd");
        for (String s : list) {
            System.out.println(s);
        }
    }

    //使用增强for循环遍历数组
    private static void demo01() {
        int[] arr = {1, 2, 3, 4, 6};
        for (int i : arr) {
            System.out.println(i);
        }
    }
}

泛型

  • 就是类似ArrayList集合的<>
import java.util.ArrayList;
import java.util.Iterator;

public class Demo01Generic {

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

    /*
     *  创建集合对象时,不使用泛型
     * 好处:
     *  1.避免了类型转换的麻烦,存储的是什么类型,取出的就是什么类型
     *  2.把运行期异常(代码运行之后的异常),提升到了编译器(编译时报错)
     * 弊端:
     *  泛型是什么类型,只能存储什么类型的数据
     * */
    private static void show02() {
        ArrayList<String> list = new ArrayList<>();
        list.add("abc");
//      list.add(1);//java: 不兼容的类型: int无法转换为java.lang.String

        //使用迭代器遍历list集合
        Iterator<String> it = list.iterator();
        while (it.hasNext()) {
            String s = it.next();
            System.out.println(s+"->"+s.length());
        }

    }

    /*
     * 创建集合对象时,不使用泛型
     * 好处:
     *       集合不使用泛型,默认的是Object类型,可以存储任何形式的数据
     * 弊端:
     *       不安全,会引发异常
     * */
    private static void show01() {
        ArrayList list = new ArrayList();
        list.add("abc");
        list.add(1);
        list.add(8.8);

        //使用迭代器遍历list集合
        //获取迭代器

        Iterator it = list.iterator();
        while (it.hasNext()) {
            //取出元素也是Object类型
            Object obj = it.next();
            System.out.println(obj);

            //想要String类特有的方法,length获取字符串长度;不能使用  多态Object obj="abc";
            //需要向下转型
            String s = (String) obj;
            //会抛出ClassCastException类型转换异常,不能把Integer类型转换为String类型
            System.out.println(s.length());
        }

    }
}

泛型类

import java.util.ArrayList;

/*
 * 定义一个含有泛型的类(可以叫集合),模拟ArrayList集合
 * 泛型是一个未知的数据类型,当我们不确定是什么数据类型的时候,可以使用泛型
 * 泛型可以接收任意数据类型,可以使用Integer、String,Student...
 * 创建对象的时候确定泛型的数据类型
 * */
public class GenericClass<E> {

    private E name;

    public E getName() {
        return name;
    }

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

public class Demo02GenericClass {

    public static void main(String[] args) {

        //不写泛型默认为Object类型
        GenericClass<String> gc = new GenericClass<>();
        gc.setName("sb");
        System.out.println(gc.getName());

        //创建GenericClass对象,泛型使用Integer类型
        GenericClass<Integer> gc2 = new GenericClass<>();
        gc2.setName(1);
        System.out.println(gc2.getName());


    }
}
  • 定义含有泛型的方法:泛型定义在方法的修饰符和返回值类型之间
/*
 * 定义含有泛型的方法:泛型定义在方法的修饰符和返回值类型之间
 *
 * 格式:
 *   修饰符 <泛型> 返回值类型 方法名(参数列表(使用泛型)){
 *           方法体;
 *       }
 *
 * 含有泛型的方法,在调用方法的时候确定泛型的数据类型
 * 传递什么类型的参数,泛型就是什么类型
 * */
public class GenericMethod {
    //定义一个含有泛型的方法
    public <M> void method01(M m) {
        System.out.println(m);
    }

    //定义一个含有泛型的静态方法
    public static <S> void method02(S s){
        System.out.println(s);
    }
}

/*
 * 测试含有泛型的方法
 * */
public class Demo03GenericMethod {
    public static void main(String[] args) {
        //创建GenericMethod对象
        GenericMethod gc = new GenericMethod();

        /*
         * 调用含有泛型的方法method01
         * 传递什么类型,泛型就是什么类型
         * */
        gc.method01(11);
        gc.method01("sb");
        gc.method01(1.1);
        gc.method01(true);

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

        GenericMethod.method02("静态方法");
        GenericMethod.method02(1.1);
    }
}

泛型接口

/*
 * 定义含有泛型的接口
 * */
public interface GenericInterface<I> {
    public abstract void method(I i);
}
  • 含有泛型的接口,第一种使用方式:定义接口的实现类,实现接口,指定接口的泛型
/*
 * 含有泛型的接口,第一种使用方式:定义接口的实现类,实现接口,指定接口的泛型
 * public interface Iterator<E> {
 *       E next();
 * }
 * Scanner类实现了Iterator接口,并在指定接口的泛型为String,使用重写的next方法的泛型默认为String
 * public final class Scanner implements Iterator<String>{
 *       public String next();
 * }
 * */
public class GenericInterfaceImpl1 implements GenericInterface<String> {
    @Override
    public void method(String s) {
        System.out.println(s);
    }
}

  • 含有泛型接口的第二种使用方式:接口使用什么类型,实现类就使用什么类型,类跟着接口走
/*
 * 含有泛型接口的第二种使用方式:接口使用什么类型,实现类就使用什么类型,类跟着接口走
 * 就相当于定义了一个含有泛型的类,创建对象的时候确定泛型的类型
 * public interface List<E>{
 *   boolean add(E e);
 *   E get(int index);
 * }
 * public class ArrayList<E> implements list<E>{
 *   public boolean add(E e){}
 *   public E get(int index){}
 * }
 * */
public class GenericInterfaceImpl2<I> implements GenericInterface<I> {
    @Override
    public void method(I i) {
        System.out.println(i);
    }
}

测试

public class Demo04GenericInterface {

    public static void main(String[] args) {
        //创建GenericImplement1对象
        GenericInterface<String> gi = new GenericInterfaceImpl1();//多态
        gi.method("abc");

        //创建GenericImplement2对象
        GenericInterfaceImpl2<String> gi2 = new GenericInterfaceImpl2<>();
        gi2.method("def");

        System.out.println("====================");
        GenericInterfaceImpl2<Double> gi3 = new GenericInterfaceImpl2<>();
        gi3.method(2.2);
        System.out.println("====================");
        GenericInterfaceImpl2<Integer> gi4 = new GenericInterfaceImpl2<>();
        gi4.method(5);
    }
}

通配符 ? 的用法

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

/*
 * 泛型的通配符:
 *       ?:代表任意的数据类型
 * 使用方式:
 *       不能创建对象使用
 *       只能作为方法的参数使用
 * */
public class Demo05Generic {

    public static void main(String[] args) {
        ArrayList<Integer> list01 = new ArrayList<>();
        list01.add(1);
        list01.add(2);

        ArrayList<String> list02 = new ArrayList<>();
        list02.add("abc");
        list02.add("def");

        printArray(list01);
        printArray(list02);

    }

    /*
     * 定义一个方法,能遍历所有类型的ArrayList集合
     * 这时候我们不知道ArrayList集合使用什么数据类型类型,可以使用泛型的通配符?来接收数据类型
     * 注意:
     *      泛型是没有继承概念的
     * */

    public static void printArray(ArrayList<?> list) {
        //使用迭代器遍历集合
        Iterator<?> it = list.iterator();
        while (it.hasNext()) {
            //it.next()方法,取出的元素是Object,可以接收任意的数据类型
            Object obj = it.next();
            System.out.println(obj);
        }
    }
}

泛型的上限限定和泛型的下限限定

  • 泛型的上限限定:? extends E 代表使用的泛型只能是E的子类或者本身
  • 泛型的下限限定:? super E 代表使用的泛型只能是E的父类或者本身
import java.util.ArrayList;
import java.util.Collection;

/*
 * 泛型的上限限定:? extends E  代表使用的泛型只能是E的子类或者本身
 * 泛型的下限限定:? super E    代表使用的泛型只能是E的父类或者本身
 * */
public class Demo06Generic {

    public static void main(String[] args) {
        ArrayList<Integer> list1 = new ArrayList<>();
        ArrayList<String> list2 = new ArrayList<>();
        ArrayList<Number> list3 = new ArrayList<>();
        ArrayList<Object> list4 = new ArrayList<>();

        getElement1(list1);
//        getElement1(list2);//报错
        getElement1(list3);
//        getElement1(list4);//报错

//        getElement2(list1);//报错
//        getElement2(list2);//报错
        getElement2(list3);
        getElement2(list4);

    }

    /*
     * 类与类之间的继承关系
     * Integer extends Number extends Object
     * String extends Object
     * */

    //泛型的上限:此时的泛型必须是Number类型的子类或Number类型
    public static void getElement1(ArrayList<? extends Number> coll){}
    //泛型的下限:此时的泛型必须是Number类型的父类或Number类型
    public static void getElement2(Collection<? super Number> coll){}
}

斗地主小游戏

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

/*
 * 斗地主综合案例
 *   1.准备牌
 *   2.洗牌
 *   3.发牌
 *   4.看牌
 * */
public class Test {
    public static void main(String[] args) {

        //准备牌
        //定义一个存储54张牌的ArrayList集合,泛型使用String
        ArrayList<String> poker = new ArrayList<>();
        //定义两个数组,一个存储牌的花色,一个存储牌的序号
        String[] colors = {"♥", "♠", "♣", "♦"};
        String[] numbers = {"2", "A", "K", "Q", "J", "10", "9", "8", "7", "6", "5", "4", "3"};
        //先把大小王存储到poker集合
        poker.add("大王");
        poker.add("小王");
        //循环嵌套遍历两个数组
        for (String number : numbers) {
            for (String color : colors) {
                poker.add(color + number);
            }
        }
//        System.out.println(poker);

        //洗牌
        /*
         * 使用Collections工具类中的方法
         * static void shuffle(list<?> list ) 使用随机默认源对指定列表进行打印
         * */

        Collections.shuffle(poker);
//        System.out.println(poker);

        //发牌
        //先创建四个集合
        //3个玩家集合
        ArrayList<String> play01 = new ArrayList<>();
        ArrayList<String> play02 = new ArrayList<>();
        ArrayList<String> play03 = new ArrayList<>();
        //一个底牌集合
        ArrayList<String> dipai = new ArrayList<>();
        for (int i = 0; i < poker.size(); i++) {
            //i>=51时发牌给底牌
            //从索引%3给三个玩家轮流发牌
            //注意,先判断i>=52,否则牌就发没了
            String p = poker.get(i);//获取牌
            if (i >= 51) {
                //底牌
                dipai.add(p);
            } else if (i % 3 == 0) {
                //给玩家1发牌
                play01.add(p);
            } else if (i % 3 == 1) {
                //给玩家2发牌
                play02.add(p);
            } else if (i % 3 == 2) {
                //给玩家3发牌
                play03.add(p);
            }
        }

        //看牌
        System.out.println("周星驰"+play01);
        System.out.println("刘德华"+play02);
        System.out.println("周润发"+play03);
        System.out.println("底牌"+dipai);
    }

}

List集合及其方法

/*
 * java.util.List接口extends Collection接口
 * List接口的特点:
 *       1.有序的集合,存储元素和取出元素顺序一致
 *       2.有索引,包含了一些有索引的方法
 *       3.允许存储重复的元素
 *
 * 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):用指定元素替换集合中指定位置的元素,返回值是更新前的元素
 * 注意:
 *      操作索引的时候,一定要防止索引越界异常
 *      IndexOutOfBoundsException:索引越界异常,集合会报
 *      ArrayIndexOutOfBoundsException:数组索引越界异常
 *      StringIndexOutOfBoundsException:字符串索引越界异常
 * */
public class Demo01List {
    public static void main(String[] args) {
        //创建一个List集合对象,多态
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        System.out.println(list);//打印的不是地址,重写了toString方法
        /*
        public void add(int index,E element):将指定的元素,添加到集合中的指定位置上
        */
        list.add(4, "e");
        System.out.println(list);

        /*
         * public E get(int index):返回集合中指定位置的元素
         * */
        for (String s : list) {
            System.out.println(s);
        }

        /*
         * public E remove(int index):移除列表中指定位置的元素,返回的是被移除的元素
         * */
        String removeE = list.remove(3);
        System.out.println("被移除的元素:" + removeE);
        System.out.println(list);

        /*
         * public E set(int index,E element):用指定元素替换集合中指定位置的元素,返回值是更新前的元素
         * */
        String setE = list.set(3, "D");
        System.out.println("被替换的是" + setE);
        System.out.println(list);

//        String r = list.get(5);//IndexOutOfBoundsException
//        System.out.println(r);

    }
}

LinkedList集合及其方法

import java.util.LinkedList;

/*
 * java.util.LinkedList集合 implements List集合
 * linkedList集合的特点:
 *   1.底层是一个链表结构,查询慢,增删快
 *   2.里面包含了大量首尾元素的方法
 *   注意:使用linkedList集合特有的方法,不能使用多态
 *
 *       public void addFirst(E e):将指定元素插入此列表的开头
 *       public void addLast(E e):将指定元素添加到此列表的末尾
 *       public void push(E e):将元素推入此列表表示的堆栈
 *
 *       public E getFirst():返回此列表的第一个元素
 *       public E getLast():返回此列表的最后一个元素
 *
 *       public E removeFirst():移除并返回此列表第一个元素
 *       public E removeLast():移除并返回此列表最后一个元素
 *       public E pop():从此列表所表示的堆栈弹出第一个元素
 *
 *       public boolean isEmpty():如果列表不包含元素,则返回true
 *
 * */
public class Demo01LinkedList {

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

    /*
     *       public E removeFirst():移除并返回此列表第一个元素
     *       public E removeLast():移除并返回此列表最后一个元素
     *       public E pop():从此列表所表示的堆栈弹出第一个元素  相当于removeFirst()
     * */
    private static void show03() {
        //创建LinkedList集合对象
        LinkedList<String> linked = new LinkedList<>();
        //往集合中添加元素
        linked.add("a");
        linked.add("b");
        linked.add("c");

        //public E removeFirst():移除并返回此列表第一个元素
        //String first = linked.removeFirst();
        String first = linked.pop();
        System.out.println("被移除的第一个元素是:" + first);
        System.out.println(linked);

        //public E removeLast():移除并返回此列表最后一个元素
        String last = linked.removeLast();
        System.out.println("被移除的最后一个元素是:" + last);
        System.out.println(linked);

    }

    /*
     *       public E getFirst():返回此列表的第一个元素
     *       public E getLast():返回此列表的最后一个元素
     * */
    private static void show02() {
        //创建LinkedList集合对象
        LinkedList<String> linked = new LinkedList<>();
        //往集合中添加元素
        linked.add("a");
        linked.add("b");
        linked.add("c");

//        linked.clear();
        if (!linked.isEmpty()) {
            //public E getFirst():返回此列表的第一个元素
            String first = linked.getFirst();
            System.out.println(first);
            System.out.println(linked);

            //public E getLast():返回此列表的最后一个元素
            String last = linked.getLast();
            System.out.println(last);
            System.out.println(linked);
        }

    }

    /*
     *       public void addFirst(E e):将指定元素插入此列表的开头
     *       public void addLast(E e):将指定元素添加到此列表的末尾
     *       public void push(E e):将元素推入此列表表示的堆栈
     * */
    public static void show01() {
        //创建LinkedList集合对象
        LinkedList<String> linked = new LinkedList<>();
        //往集合中添加元素
        linked.add("a");
        linked.add("b");
        linked.add("c");

        System.out.println(linked);
        //public void addFirst(E e):将指定元素插入此列表的开头
        //linked.addFirst("www");
        linked.push("www");
        System.out.println(linked);

        //public void addLast(E e):将指定元素添加到此列表的末尾 等效于add(E e)
        linked.addLast("com");
        System.out.println(linked);


    }
}

Set集合及其方法

HashSet集合及其方法

/*
 * java.util.Set接口 extends Collection接口
 * Set接口的特点:
 *   1.不允许存储重复元素
 *   2.没有索引,没有带索引的方法,也不能使用普通的for循环遍历
 *
 * java.util.HashSet接口 implements Set接口
 * HashSet接口特点:
 *   1.不允许存储重复元素
 *   2.没有索引,没有带索引的方法,也不能使用普通的for循环遍历
 *   3.是一个无序的集合,存储元素和取出元素顺序可能不一致
 *   4.底层是一个哈希表(查询速的非常快)
 * */
public class Demo02Set {

    public static void main(String[] args) {

        Set<Integer> hasSet = new HashSet<>();
        //使用add方法往集合中添加元素
        hasSet.add(1);
        hasSet.add(2);
        hasSet.add(3);
        hasSet.add(1);

        //使用迭代器遍历hasSet集合
        Iterator<Integer> it = hasSet.iterator();
        while (it.hasNext()) {
            Integer i = it.next();
            System.out.println(i);
        }
        System.out.println("=====================");
        //使用增强for循环遍历hasSet集合
        for (Integer i : hasSet) {
            System.out.println(i);
        }
    }
}

哈希值
/*
 * 哈希值:是一个十进制的整数,由系统随机给出(就是对象的地址值,是一个逻辑地址,是模拟出来的得到的地址,不是数据实际存储的物理地址)
 *   在Object类有一个方法,可以获取对象的哈希值
 *   int hashCode() 返回该对象的哈希码值
 *   hashCode方法的源码
 *       public native int hashCode();
 *       native:代表该方法调用的是本地操作系统的方法
 * */
public class Demo03HashCode {

    public static void main(String[] args) {
        //Person类继承了Object类,所以可以使用Object类的hashCode方法
        Person p1 = new Person();
        int h1 = p1.hashCode();
        System.out.println(h1);

        Person p2 = new Person();
        int h2 = p2.hashCode();
        System.out.println(h2);

        /*
         * toString方法的源码:
         *       return getClass().getName()+"@"+Integer.toHexString(hashCode());
         * */
        System.out.println(p1);
        System.out.println(p2);

        /*
        * String 类的哈希值
        *       String类重写了Object类的hashCode方法
        * */

        String s1=new String("abc");
        String s2=new String("abc");
        int i1 = s1.hashCode();
        int i2 = s2.hashCode();
        System.out.println(i1);
        System.out.println(i2);

        System.out.println("重地".hashCode());//1179395
        System.out.println("通话".hashCode());//1179395

    }

}

  • Set集合不允许存储重复元素的原理
/*
 * Set集合不允许存储重复元素的原理
 * Set集合在调用add方法时,add方法会调用元素的hashCode方法和equals方法,判断元素是否重复
 * */
public class Demo04HashSetSaveString {

    public static void main(String[] args) {
        //创建hashSet集合对象
        HashSet<String> set = new HashSet<>();
        String s1 = new String("abc");
        String s2 = new String("abc");
        set.add(s1);
        /*
         *add方法会调用s1的hashCode方法,计算字符串"abc"的哈希值,是96354,
         * 在集合中找到没有96354这个哈希值的元素,发现没有
         * 就会把s1存储集合中
         * */
        set.add(s2);
        /*
         * add方法会调用s1的hashCode方法,计算字符串"abc"的哈希值,是96354,
         * 在集合中找到有96354这个哈希值的元素,发现有(哈希冲突)
         * s2会调用equals方法和哈希值相同的元素进行比较 s2.equals(s1),返回true
         * 两个元素的哈希值相同,equals方法返回true,认定两个元素相同
         * 就不会把s2存储到集合中
         * */
        set.add("重地");
        /*
         * add方法会调用s1的hashCode方法,计算字符串"重地"的哈希值,是1179395,
         * 在集合中找到没有1179395这个哈希值的元素,发现没有
         * 就会把"重地"存储集合中
         * */
        set.add("通话");
        /*
         add方法会调用s1的hashCode方法,计算字符串"通话"的哈希值,是1179395,
         * 在集合中找到有1179395这个哈希值的元素,发现有(哈希冲突)
         * "通话"会调用equals方法和哈希值相同的元素进行比较 "通话".equals("重地"),返回false (String对比)
         * 两个元素的哈希值相同,equals方法返回false,认定两个元素不相
         * 就会把"通话"存储到集合中
         * */
        set.add("abc");
        System.out.println(set);


    }
}

小测试
import java.util.Objects;

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

    public String getName() {
        return name;
    }

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

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

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

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

    public int getAge() {
        return age;
    }

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

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

    public People() {
    }
}


import java.util.HashSet;

/*
 * HashSet自定义类型元素
 *
 * Set集合报错元素唯一:
 *   存储的元素(String,Integer.. Student),必须重写hashCode方法和equals方法
 *
 * 要求:
 *   同名同年龄,视为同一个人,只能存储一次
 * */
public class Demo05HashSetSavePeople {

    public static void main(String[] args) {
        //创建HashSet集合存储People
        HashSet<People> set = new HashSet<>();
        People p1 = new People("陈浩", 11);
        People p2 = new People("陈浩", 11);
        People p3 = new People("陈浩", 12);
        System.out.println(p1.hashCode());
        System.out.println(p2.hashCode());
        System.out.println(p1 == p2);//比较的是地址
        System.out.println(p1.equals(p2));
        set.add(p1);
        set.add(p2);
        set.add(p3);
        System.out.println(set);
    }
}

LinkedHashSet集合及其方法

import java.util.HashSet;
import java.util.LinkedHashSet;

/*
 * java.util.LinkedHashSet集合 extends HashSet集合
 * LinkedHashSet集合特点
 *       底层是一个哈希表(数组+链表/红黑树)+链表:多了一条链表(记录元素存储顺序),保证元素有序
 * */
public class Demo06LinkedHashSet {

    public static void main(String[] args) {
        HashSet<String >set=new HashSet<>();
        set.add("www");
        set.add("abc");
        set.add("abc");
        set.add("xpzt");
        set.add("com");
        System.out.println(set);//无序,不允许重复

        LinkedHashSet<String > linked=new LinkedHashSet<>();
        linked.add("www");
        linked.add("abc");
        linked.add("abc");
        linked.add("xpzt");
        linked.add("com");
        System.out.println(linked);//有序,不允许重复

    }
}

可变参数

  • 当方法的参数列表类型已经确定,但是参数的个数不确定,就可以使用可变参数

  • 可变参数的注意事项

    1. 一个方法的参数列表,只能有一个可变参数
    2. 如果方法的参数有多个,那么可变参数必须写在参数列表的末尾
  • 使用格式:

    • 修饰符 返回值类型 方法名(数据类型…变量名){}

      //使用格式
      public static void method(String str,Object...obj){
      
      }
      
/*
 * 可变参数:是JDK1.5之后出现的新特性
 * 使用前提:
 *   当方法的参数列表类型已经确定,但是参数的个数不确定,就可以使用可变参数
 * 使用格式:
 *   修饰符 返回值类型 方法名(数据类型...变量名){}
 * 可变参数的原理:
 *   可变参数的底层是一个数组,根据传递的参数不同,会创建不同长度的数组,来存储这些参数
 *   传递的参数个数,可以是0个(不传递)或者1,2..多个
 *
 * */
public class DemoVarArgs {

    public static void main(String[] args) {
//        int i = add();
        int i = add(10, 20, 30, 40, 50, 60, 70, 80, 90, 100);
        System.out.println(i);

        method("abc",3.4,3,1,3,4,5,6,7,4);
    }

    /*
    * 可变参数的注意事项:
    *   1.一个方法的参数列表,只能有一个可变参数
    *   2.如果方法的参数有多个,那么可变参数必须写在参数列表的末尾
    * */
//    public static void method(String s,double b,int i,int...arr){
//    }

    /*
    * 可变参数的特殊写法(终级写法)
    * */

    public static void method(String str,Object...obj){

    }

    /*
     * 定义计算(0-n)整数和的方法
     * 已知:计算整数的和,数据类型已经确定int
     * 但是参数的个数不确定,不知道要计算几个整数的和,就可以使用可变参数
     * add();就会创建一个长度为0的数组,new int[0]
     * add(10, 20, 30, 40, 50, 60, 70, 80, 90, 100);就会创建一个长度为10的数组,new int[]{10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
     * */

    public static int add(int... arr) {
//        System.out.println(arr);
//        System.out.println(arr.length);
        //定义一个初始化的变量,记录累加求和
        int sum = 0;
        //遍历数组
        for (int i : arr) {
            sum += i;
        }
        return sum;
    }
}

Collection集合工具类及其方法

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

/*
 * java.util.Collections是集合工具类,用来对集合进行操作,部分方法如下:
 *   public static <T> boolean addAll(Collection<T> c,T... element):往集合中添加一些元素
 *   public static shuffle(List<?> list)打乱顺序:打乱集合顺序
 * */
public class Demo01Collections {
    public static void main(String[] args) {
        ArrayList<String> list=new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("d");
        list.add("c");
        System.out.println(list);

        /*
        *  public static <T> boolean addAll(Collection<T> c,T... element):往集合中添加一些元素
        * */
        Collections.addAll(list,"3","4","2");
        System.out.println(list);

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

    }

}

Comparable接口

  • 自己(this)和别人(参数)比较,自己需要实现Comparable接口,重写比较的CompareTo方法

  • Comparable接口的排序规则:定义比较的规则

    • 自己(this)-参数:升序
    • 参数-自己(this):降序
  • Comparable:自己(this)和别人(参数)比较,自己需要实现Comparable接口,重写比较的CompareTo方法

  • Comparator:相当于第三方的裁判,比较两个

public class Person implements Comparable<Person> {
    private String name;
    private int 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 "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

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

    public Person() {
    }

    //重写排序规则
    @Override
    public int compareTo(Person o) {
//        return 0;//认为元素都是相同的
        //自定义比较的规则,比较两个人的年龄(this,参数Person)
//        return this.getAge()-o.getAge();//升序
        return o.getAge() - this.getAge();//降序

    }
}

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

/*
 * java.util.Collections是集合工具类,用来对集合进行操作,部分方法如下:
 *       public static<T> void sort(List<T> list):将集合中的元素按照默认规则排序
 *
 * 注意:
 *      sort(List<T> list)使用前提
 *      被排序的集合里边存储的元素,必须实现Comparable,重写接口中的方法compareTo定义排序的规则
 * Comparable接口的排序规则:
 *      自己(this)-参数:升序
 * */
public class Demo02Sort {
    public static void main(String[] args) {
        ArrayList<Integer> list01 = new ArrayList<>();
        list01.add(1);
        list01.add(3);
        list01.add(2);
        System.out.println(list01);

        /*
         * public static<T> void sort(list<T> list):将集合中的元素按照默认规则排序
         * */
        Collections.sort(list01);
        System.out.println(list01);

        ArrayList<String> list02 = new ArrayList<>();
        list02.add("a");
        list02.add("c");
        list02.add("b");
        System.out.println(list02);
        Collections.sort(list02);
        System.out.println(list02);

        ArrayList<Person> list03 = new ArrayList<>();
        list03.add(new Person("陈浩", 11));
        list03.add(new Person("cc", 15));
        list03.add(new Person("陈bb", 4));
        System.out.println(list03);
        Collections.sort(list03);
        System.out.println(list03);
    }
}

小测试
/*
 * java.util.Collections是集合工具类,用来对集合进行操作,部分方法如下:
 *       public static <T> void sort (List<T> list,Comparator<? super T>):将集合中元素按照指定规则排序
 *
 * Comparator和Comparable的区别
 *      Comparable:自己(this)和别人(参数)比较,自己需要实现Comparable接口,重写比较的CompareTo方法
 *      Comparator:相当于第三方的裁判,比较两个
 *
 * Comparator比较规则:
 *      o1-o2:升序
 * */
public class Demo03Sort {

    public static void main(String[] args) {
        ArrayList<Integer> list01 = new ArrayList<>();
        list01.add(1);
        list01.add(3);
        list01.add(2);
        System.out.println(list01);
        Collections.sort(list01, new Comparator<Integer>() {
            //重写比较的规则
            @Override
            public int compare(Integer o1, Integer o2) {
//                return 0;
                return o1 - o2;
            }
        });
        System.out.println(list01);

        ArrayList<Student> list02 = new ArrayList<>();
        list02.add(new Student("a陈浩", 14));
        list02.add(new Student("陈bb", 25));
        list02.add(new Student("陈dd", 1));
        list02.add(new Student("b陈dd", 14));
//        Collections.sort(list02, new Comparator<Student>() {
//            @Override
//            public int compare(Student o1, Student o2) {
                return 0;
//                //按照年龄升序
//                return o1.getAge() - o2.getAge();
//            }
//        });

        //拓展
        Collections.sort(list02, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
//                return 0;
                //按照年龄升序
                int result = o1.getAge() - o2.getAge();
                //如果两个人的年龄相同,再使用姓名的第一个字进行比较
                if (result == 0) {
                    return o1.getName().charAt(0) - o2.getName().charAt(0);
                }
                return result;
            }
        });
        System.out.println(list02);
    }

}

Map集合及其方法

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

/*
 * java.util.Map<k,v>集合
 * Map集合的特点
 *   1.Map集合是一个双列集合,一个元素包含两个值(一个key,一个value)
 *   2.Map集合中的元素,key和value的数据类型可以相同,也可以不同
 *   3.Map集合中的元素,key是不允许重复的,value可以重复
 *   4.Map集合中的元素,key和value是一一对应的
 * java.util.HashMap<k,v>集合 implements Map<k,v>接口
 * HashMap集合的特点:
 *   1.HashMap集合的底层是哈希表:查询速度非常快
 *       JDK1.8之前:数组+单向链表
 *       JDK1.8之后:数组+单向链表/红黑树(链表长度超过8):提高查询速度
 *   2.HashMap集合是一个无序集合,存储元素和取出元素的顺序有可能不一致
 * java.util.LinkedHashMap<k,v>集合 extends HashMap<k,v>集合
 * LinkedHashMap集合的特点:
 *   1.LinkedHashMap集合底层是哈希表+链表(保证迭代的顺序)
 *   2.LinkedHashMap集合是一个有序的集合,存储元素和取出元素的顺序是一致的
 * */
public class Demo01Map {

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

    /*
     * boolean containsKey(Object key):判断集合中是否包含指定的键
     * 包含返回true,不包含返回false
     * */
    private static void show04() {
        //创建Map集合对象
        Map<String, Integer> map = new HashMap<>();
        map.put("陈浩", 11);
        map.put("陈bb", 1);
        map.put("cc", 11);
        System.out.println(map.containsKey("cc"));//true
        System.out.println(map.containsKey("c"));//false
    }

    /*
     * public V get(Object key):根据指定的键 在Map集合中获取对应的值
     * 返回值v:
     *       key存在,v返回对应的value的值
     *       key不存在,v返回null
     * */
    private static void show03() {
        //创建Map集合对象
        Map<String, Integer> map = new HashMap<>();
        map.put("陈浩", 11);
        map.put("陈bb", 1);
        map.put("cc", 11);
        Integer v1 = map.get("陈浩");
        System.out.println("v1:" + v1);
        Integer v2 = map.get("陈浩sb");
        System.out.println("v2:" + v2);
    }

    /*
     * public V remove(Object key):把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值
     * 返回值v:
     *       key存在,v返回被删除的值
     *       key不存在,v返回null
     * */
    private static void show02() {
        //创建Map集合对象
        Map<String, Integer> map = new HashMap<>();
        map.put("陈浩", 11);
        map.put("陈bb", 1);
        map.put("cc", 11);
        System.out.println(map);
        Integer v1 = map.remove("陈bb");
        System.out.println(v1);
        System.out.println(map);
        System.out.println("===========");

        //int v2 = map.remove("陈cb");//自动拆箱  -  NullPointerException
        //所以尽量使用包装类
        Integer v2 = map.remove("陈cb");
        System.out.println(v2);
        System.out.println(map);

    }

    /*
     * public V put(K key,V value):把指定的键与指定的值添加到Map集合中
     *   返回值:v
     *       存储键值对的时候:key不重复,返回值v是null
     *       存储键值对的时候:key重复,会使用新的value替换map中重复的value,返回被替换的value
     * */
    private static void show01() {
        //创建Map集合对象,多态
        Map<String, String> map = new HashMap<>();
        String v1 = map.put("陈浩", "abc");
        System.out.println("v1:" + v1);
        String v2 = map.put("陈浩", "abcd");
        System.out.println("v2:" + v2);
        System.out.println(map);

        map.put("陈浩sb", "abcd");
        System.out.println(map);


    }
}

Map集合的遍历方式

第一种遍历方式:通过键找值(keySet)的方法
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/*
 * Map集合的第一种遍历方式:通过键找值的方法
 * Map集合中的方法:
 *       Set<K> keySet() 返回此映射中包含的键的Set视图
 * 实验步骤:
 *       1.使用Map集合中的方法keySet(),把Map集合所有的key取出来,存储到一个Set集合中
 *       2.遍历set集合,获取Map集合中的每一个key
 *       3.通过Map集合中的方法get(key),通过key找到value
 * */
public class Demo02KeySet {

    public static void main(String[] args) {
        //创建Map集合对象
        Map<String, Integer> map = new HashMap<>();
        map.put("陈浩", 11);
        map.put("陈bb", 1);
        map.put("cc", 11);
        // 1.使用Map集合中的方法keySet(),把Map集合所有的key取出来,存储到一个Set集合中
        Set<String> set = map.keySet();
        //2.遍历set集合,获取Map集合中的每一个key
        //使用迭代器
        Iterator<String> it = set.iterator();
        while (it.hasNext()) {
            //3.通过Map集合中的方法get(key),通过key找到value
            String key = it.next();
            Integer value = map.get(key);
            System.out.println(key + "=" + value);
        }
        System.out.println("=========");
        //使用增强for循环
        for(String key:/*set*/map.keySet()){
            //3.通过Map集合中的方法get(key),通过key找到value
            Integer value = map.get(key);
            System.out.println(key + "=" + value);
        }
    }

}

第二种遍历方式:使用Entry对象遍历
/*
 * Map集合遍历的第二种方式:使用Entry对象遍历
 *
 * Map集合中的方法:
 *       Set <Map.Entry<K,V>> entrySet()返回映射中包含的映射关系的Set视图
 *
 * 实现步骤:
 *       1.使用Map集合中的方法entrySet(),把Map集合中多个Entry对象取出来,存储到一个Set集合中
 *       2.遍历Set集合,获取每一个Entry对象
 *       3.使用Entry对象中的方法getKey()和getValue()获取键与值
 * */
public class Demo03EntrySet {
    public static void main(String[] args) {
        //创建Map集合对象
        Map<String, Integer> map = new HashMap<>();
        map.put("陈浩", 11);
        map.put("陈bb", 1);
        map.put("cc", 11);
        //1.使用Map集合中的方法entrySet(),把Map集合中多个Entry对象取出来,存储到一个Set集合中
        Set<Map.Entry<String, Integer>> set = map.entrySet();
        // 2.遍历Set集合,获取每一个Entry对象
        //使用迭代器遍历set集合
        Iterator<Map.Entry<String, Integer>> it = set.iterator();
        while (it.hasNext()) {
            Map.Entry<String, Integer> entry = it.next();
            //3.使用Entry对象中的方法getKey()和getValue()获取键与值
            String key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println(key + "=" + value);
        }
        System.out.println("===================");
        for (Map.Entry<String, Integer> entry : set) {
            //3.使用Entry对象中的方法getKey()和getValue()获取键与值
            String key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println(key + "=" + value);
        }
    }
}

  • Map集合保证key是唯一
import java.util.Objects;

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

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

    public Person() {
    }

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

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", 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;
    }
}
import java.util.Objects;

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

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

    public Person() {
    }

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

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", 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;
    }
}

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

/*
 * HashMap存储自定义类型键值
 * Map集合保证key是唯一的
 *   作为key的元素,必须重写hashCode方法和equals方法,以保证key唯一
 * */
public class Demo01HashMapSavePerson {
    public static void main(String[] args) {
        show01();
    }

    /*
     *  HashMap存储自定义类型键值
     * key:People类型
     *      People类就必须重写了hashCode方法和equals方法,以保证key唯一
     * value:String类型
     *      可以重复
     * */
    private static void show02() {
        //创建HashMap集合
        HashMap<Person, String> map = new HashMap<>();
        map.put(new Person("t", 11), "A");
        map.put(new Person("x", 12), "C");
        map.put(new Person("p", 11), "E");
        map.put(new Person("t", 11), "C");
        //使用keySet加增强for循环遍历Map集合
        Set<Person> set = map.keySet();
        for (Person key : set) {
            String value = map.get(key);
            System.out.println(key+"-->"+value);
        }
    }

    /*
     *  HashMap存储自定义类型键值
     * key:String类型
     *      String类重写了hashCode方法和equals方法,可以保证key唯一
     * value:Person类型
     *       value可以重复(同名同年龄的人视为同一个人)
     * */
    private static void show01() {
        //创建HashMap集合
        HashMap<String, Person> map = new HashMap<>();
        map.put("北京", new Person("张三", 18));
        map.put("上海", new Person("李四", 19));
        map.put("广州", new Person("王五", 20));
        map.put("北京", new Person("赵六", 18));

        //使用keySet加增强for循环遍历Map集合
        Set<String> set = map.keySet();
        for (String key : set) {
            Person value = map.get(key);
            System.out.println(key + "-->" + value);
        }
    }
}



LinkedHashMap集合

  • Map接口的哈希表和链接列表实现,具有可预知的迭代顺序
import java.util.HashMap;
import java.util.LinkedHashMap;

/*
 * java.util.LinkedHashMap<K,V> extends hashMap<K,V>
 * Map接口的哈希表和链接列表实现,具有可预知的迭代顺序
 * 底层原理:
 *   哈希表+链表(记录元素的顺序)
 * */
public class Demo02LinkedHashSetMap {
    public static void main(String[] args) {
        //创建HashMap对象
        HashMap<String, String> map = new HashMap<>();
        map.put("a", "a");
        map.put("c", "c");
        map.put("b", "b");
        map.put("a", "d");
        System.out.println(map);//key不允许重复 无序

        //创建LinkedHashMap对象
        LinkedHashMap<String, String> linked = new LinkedHashMap<>();
        linked.put("a", "a");
        linked.put("c", "c");
        linked.put("b", "b");
        linked.put("a", "d");
        System.out.println(linked);//key不允许重复 有序

    }

}

Hashtable集合

  • Hashtable:底层也是一个哈希表,是一个线程安全的集合,是单线程集合,速度慢
  • HashMap:底层是一个哈希表,是一个线程不安全的集合,是多线程集合,速度快
  • HashMap集合(之前所学的所有集合):可以存储null值,null键
  • Hashtable集合,不可以存储null值,null键
import java.util.HashMap;
import java.util.Hashtable;

/*
 * java.util.Hashtable<K,V>集合 implements Map<K,V>集合
 *
 * Hashtable:底层也是一个哈希表,是一个线程安全的集合,是单线程集合,速度慢
 * HashMap:底层是一个哈希表,是一个线程不安全的集合,是多线程集合,速度快
 *
 * HashMap集合(之前所学的所有集合):可以存储null值,null键
 * Hashtable集合,不可以存储null值,null键
 *
 *Hashtable集合和Vector集合一样,在jdk1.2之后被更先进的集合(HashMap,ArrayList)取代了
 * Hashtable的子类Properties仍然活跃在历史舞台
 * Properties集合是唯一一个和IO流相结合的集合
 * */
public class Demo03Hashtable {
    public static void main(String[] args) {
        HashMap<String, String> map = new HashMap<>();
        map.put(null, "a");
        map.put("a", null);
        map.put(null, null);
        System.out.println(map);

        Hashtable<String, String> table = new Hashtable<>();
//        table.put(null, "a");//NullPointerException
//        table.put("a", null);//NullPointerException
//        table.put(null, null);//NullPointerException
        System.out.println(table);

    }

}

小测试

import java.util.HashMap;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;

/*
 * 练习:
 *   计算一个字符串中每个字符出现的次数
 *
 * 分析:
 *   1.使用Scanner获取用户输入的字符串
 *   2.创建Map集合,key是字符串中的字符,value是字符的个数
 *   3.遍历字符串,获取每一个字符
 *   4.使用获取到的字符,去Map集合判断key是否存在
 *       key存在:
 *           通过字符(key),获取value(字符个数)
 *           value++
 *           put(key,value)把新的value存储到Map集合中
 *       key不存在:
 *           put(key,1)
 * 5.遍历Map集合,输出结果
 * */
public class Demo04MapTest {
    public static void main(String[] args) {
        //1.使用Scanner获取用户输入的字符串
        Scanner sc=new Scanner(System.in);
        System.out.println("请输入字符串:");
        String str = sc.next();
        //2.创建Map集合,key是字符串中的字符,value是字符的个数
        HashMap<Character,Integer> map=new HashMap<>();
        Set<Character> set = map.keySet();
        //3.遍历字符串,获取每一个字符
        for(char key:str.toCharArray()){

            //4.使用获取到的字符,去Map集合判断key是否存在


            if(map.containsKey(key)){/*key存在*/
                //通过字符(key),获取value(字符个数)
                Integer value = map.get(key);
                value++;
                // put(key,value)把新的value存储到Map集合中
                map.put(key,value);
            }else{/*key不存在*/
                map.put(key,1);
            }
        }

        // 5.遍历Map集合,输出结果
        for (Character key : set) {
            Integer value = map.get(key);
            System.out.println(key+"="+value);
        }
    }

}

JDK9新特性

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

/*
 * JDK9新特性:
 *   List接口,Set接口,Map接口:里边增加了一些静态方法of,可以给集合一次性添加多个元素
 *   static <E> List<E> of(E...elements)
 * 使用前提:
 *       当集合中存储的元素的个数已经确定了,不再改变时使用
 * 注意:
 *   1.of方法只适用于List接口,Set接口,Map接口,不适用于接口的实现类
 *   2.of方法的返回值是一个不能改变的集合,集合不能再使用add方法,put方法添加元素,会抛出异常
 *   3.Set接口和Map接口在调用of方法时,不能有重复元素,否则会抛出异常
 * */
public class Demo01JDk9 {
    public static void main(String[] args) {
        List<String> list=List.of("a","b","c","a");
        System.out.println(list);
//        list.add("w");//UnsupportedOperationException

//        Set<String> set=Set.of("a","b","c","a");//IllegalArgumentException
        Set<String> set=Set.of("a","b","c");
        System.out.println(set);

//        Map<String,Integer> map=Map.of("a",1,"b",2,"c",3,"a",1);//IllegalArgumentException
        Map<String,Integer> map=Map.of("a",1,"b",2,"c",3);
//        map.put("a",1);UnsupportedOperationException
        System.out.println(map);
    }

}

斗地主综合案例:有序版本

import java.util.*;

/*
 * 斗地主综合案例:有序版本
 * 1.准备牌
 * 2.洗牌
 * 3.发牌
 * 4.排序
 * 5.看牌
 * */
public class DouDiZhu {
    public static void main(String[] args) {
        //准备牌
        //创建一个Map集合,存储牌的索引和组装好的牌
        Map<Integer,String> poker=new HashMap<>();
        //创建一个List集合用来存储牌的索引
        List<Integer> pokerIndex=new ArrayList<>();
        //定义两个集合,分别存储牌的花色和序号
        List<String> colors=List.of("♣","♥","♠","♦");
        List<String> nums=List.of("2","A","K","Q","J","10","9","8","7","6","5","4","3");
        //索引序号
        int index=0;
        //先把大王小王存储到poker集合中
        poker.put(index,"大王");
        pokerIndex.add(index);
        index++;
        poker.put(index,"小王");
        pokerIndex.add(index);
        index++;
        //组装牌
        for (String num : nums) {
            for (String color : colors) {
                poker.put(index,color+num);
                pokerIndex.add(index);
                index++;
            }
        }

        //洗牌
        Collections.shuffle(pokerIndex);
        //发牌
        //创建四个集合,分别存储玩家和底牌的牌的索引
        List<Integer> play01=new ArrayList<>();
        List<Integer> play02=new ArrayList<>();
        List<Integer> play03=new ArrayList<>();
        List<Integer> diPai=new ArrayList<>();
        for (int i = 0; i < pokerIndex.size(); i++) {
            Integer in = pokerIndex.get(i);//将乱牌的索引分发
            if(i>=51){
                //发底牌
                diPai.add(in);
            }else if(i%3==0){
                //玩家1
                play01.add(in);
            }else if(i%3==1){
                //玩家2
                play02.add(i);
            }else if(i%3==2){
                //玩家3
                play03.add(in);
            }
        }

        //排序
        Collections.sort(play01);
        Collections.sort(play02);
        Collections.sort(play03);
        Collections.sort(diPai);

        /*
        * 定义一个看牌的方法,提高代码的复用率
        * 参数:
        *   String name:玩家名称
        *   Map<Integer,String> map:存储牌的集合
        *   List<Integer> list:存储玩家和底牌的集合
        * 查表法:
        *   遍历玩家或底牌,获取排队索引
        *   根据索引,去Map集合找到对应的牌
        * */

        //看牌
        lookPoker("陈浩:",poker,play01);
        lookPoker("猪比:",poker,play02);
        lookPoker("狗比:",poker,play03);
        lookPoker("底牌:",poker,diPai);

//        System.out.println(poker);

    }
    public static void lookPoker(String name,Map<Integer,String> map,List<Integer> list){
        System.out.print(name+"");

        //遍历玩家牌或底牌集合
        /*for (int i = 0; i < list.size(); i++) {
            String value = map.get(i);//根据索引获取牌
            System.out.print(value+" ");
        }*/

        for (Integer key : list) {
            String value = map.get(key);//根据索引获取牌
            System.out.print(value+" ");
        }
        //打印一组牌后换行
        System.out.println();
    }
}

Throwable类

  • Exception:编译期异常,进行编译(写代码)java程序出现的问题
  • RuntimeException:运行期异常,java程序运行期间出现的问题
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/*
 * java.lang.Throwable类:是java语言中所有错误或异常的超类
 *   Exception:编译期异常,进行编译(写代码)java程序出现的问题
 *   RuntimeException:运行期异常,java程序运行期间出现的问题
 *       异常就相当于程序得了一个小毛病(感冒,发烧),把异常处理掉,程序就可以继续运行,(吃点药,继续革命工作)
 *   Error:错误
 *       错误就相当于程序得了一个无法治愈的病(艾滋),必须修改源代码,程序才能继续运行
 * */
public class Demo01Exception {

    public static void main(String[] args) /*throws ParseException*/ {
        //Exception:编译期异常,进行编译(写代码)java程序出现的问题
        SimpleDateFormat sdf=new SimpleDateFormat("yy-MM-dd");//用来格式化日期
        Date date = null;//把字符串格式的日期,解析为Date格式的日期
        try {
            date = sdf.parse("1999-1114");//把字符串格式的日期,解析为Date格式的日期
        } catch (ParseException e) {
            e.printStackTrace();
        }
        System.out.println(date);

        //RuntimeException运行期异常,java程序运行期间出现的问题
        int[] arr={1,2,3};
        try {
            //可能出现异常的代码
            System.out.println(arr[3]);
        }catch (Exception e){
            //异常的处理逻辑
            System.out.println(e);
        }

        /*
        * Error:错误
        * OutOfMemoryError: Java heap space
        * 内存溢出的错误,创建的数组太大了,超出了JVM分配的内存
        * */

        int[] arr1=new int[1024*1024*1024];
        //必须修改代码,创建的数组小一点
        int[] arr2=new int[1024*1024];
        System.out.println("后续代码");
    }
}

throw关键字用法

/*
 * throw关键字
 * 作用:
 *   可以使用throw关键字在指定的方法中抛出异常
 * 使用格式:
 *   throw new xxxException("异常产生的原因");
 * 注意:
 *   1.throw关键字必须写在方法的内部
 *   2.throw关键字后边new的对象必须是Exception或者Exception的子类对象
 *   3.throw关键字抛出的异常对象,我们就必须处理这个异常对象
 *       throw关键字后边创建的是RuntimeException或者是RuntimeException的子类对象,我们可以不处理,默认交给JVM处理(打印异常对象,中断程序)
 *       throw关键字后边创建的是编译异常(写代码时出错),我们就必须处理这个异常,要么throws,要么try..catch
 * */
public class Demo02Throw {
    /*
     * 定义一个方法获取数组指定索引处的元素
     * 参数:
     *   int[] arr
     *   int index
     * 以后(工作中)我们必须对方法传地过来的参数进行合法性校验
     * 如果参数不合法,那么我们就必须使用抛出异常的方式,告知方法的调用者,传递的参数有问题
     * */

    public static int getElement(int[] arr, int index) {
        /*
         * 我们可以对传递过来的参数数组,进行合法性校验
         * 如果数组arr为null
         * 那么我们抛出空指针异常,告知方法的调用者“传递数组的值为null”
         * */
        if (arr == null) {
            throw new NullPointerException("传递数组的值为null");
        }

        /*
         * 我们可以对传递过来的参数index进行合法性校验
         * 如果index的范围不在数组的索引范围内
         * 那么我们就抛出数组索引异常,告知方法的调用者"传递到索引超出了数组的使用范围"
         * */
        if(index<0||index>arr.length-1){
            throw new ArrayIndexOutOfBoundsException("传递到索引超出了数组的使用范围");
        }
        int ele = arr[index];
        return ele;
    }

    public static void main(String[] args) {
//        int[] arr =null;
        int[] arr ={1,2,3,4};
        getElement(arr, 4);
    }

}
  • Objects类中的静态方法requireNonNUll:查看指定引用对象是不是null
import java.util.Objects;

/*
 * Objects类中的静态方法
 * public static <T> T requireNonNUll(T obj):查看指定引用对象是不是null
 * 源码:
 *       public static <T> T requireNonNUll(T obj){
 *           if(obj == null)
 *               throw new NullPointerException();
 *           return obj;
 *       }
 * */
public class Demo03Objects {
    public static void method(Object obj) {
        //对传递过来的参数进行合法性判断,判断是否为null
        /*if (obj == null) {
            throw new NullPointerException("传递的对象的值是null");
        }*/
        //Objects.requireNonNull(obj);
        Objects.requireNonNull(obj,"传递的对象的值是null");
    }

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

异常处理

  • throws关键字:异常处理的第一种方式,交给别人处理
import java.io.FileNotFoundException;
import java.io.IOException;

/*
 * throws关键字:异常处理的第一种方式,交给别人处理
 * 作用:
 *   当方法内部抛出异常对象时,那么我们就必须处理这个异常对象
 *   可以使用throws关键字处理异常对象,会把异常对象声明抛出给调用方法的调用者处理(自己不处理,交给别人处理),最终交给JVM处理-->中断处理
 * 使用格式:
 *   修饰符 返回值类型 方法名(参数列表) throws AAAException,BBBException...{
 *       throw new AAAException("产生原因");
 *       throw new BBBException("产生原因");
 *       ...
 *   }
 * 注意:
 *   1.throws关键字必须写在方法生命才
 *   2.throws关键字后边声明的异常必须是Exception或者是Exception的子类
 *   3.方法的内部如果抛出了多个异常对象,那么throws后边必须也声明多个异常
 *       如果抛出的多个异常对象有子父类关系,那么直接声明父类异常即可
 *   4.调用了一个声明抛出异常的方法,我们就必须的处理这个声明的异常
 *       要么使用throws关键字声明抛出,交给方法的调用者处理,最终交给JVM
 *       要么使用try...catch自己处理异常
 * */
public class Demo04Throws {
    /*
     * FileNotFoundException extends IOException extends Exception
     * 如果抛出的多个异常对象有子父类关系,那么直接声明父类异常即可
     * */
    public static void main(String[] args) throws Exception/*IOException*/ /*FileNotFoundException ,IOException*/ {
        readFile("c:\\b.txt");
    }

    /*
     * 定义一个方法,对传递的文件进行合法行判断
     * 如果路径不是"c:\\a.txt",那么我们就抛出文教找不到异常对象,告知方法的调用者
     * 注意:
     *  FileNotFoundException是编译异常,抛出了编译异常,就必须处理这个异常
     *  可以使用throws继续声明抛出FileNotFoundException这个异常对象,让方法的调用者处理
     * */
    private static void readFile(String fileName) throws Exception/*IOException*/ /*FileNotFoundException ,IOException*/ {
        /*
         * 如果传递的路径,不是.txt结尾
         * 那么我们就抛出IO异常对象,告知方法的调用者,文件的后缀名不对
         * */
        if (!fileName.endsWith(".txt")) {
            throw new IOException("文件的后缀名不对");
        }

        /*
         * 如果路径不是"c:\\a.txt",那么我们就抛出文教找不到异常对象,告知方法的调用者
         * */
        if (!fileName.equals("c:\\a.txt")) {
            throw new FileNotFoundException("传递的文件不是c:\\a.txt");
        }

        System.out.println("路径没有问题,读取文件");
    }


}

  • try…catch:异常处理的第二种方式,自己处理异常
import java.io.IOException;

/*
 * try...catch:异常处理的第二种方式,自己处理异常‘
 * 格式:
 *   try{
 *       可能产生异常的代码
 *   }catch(定义一个处理异常的变量,用来接收try中抛出的异常对象){
 *       异常的处理逻辑,抛出异常对象之后,怎么处理异常对象
 *       一般在工作中,会把异常的信息记录到一个日志中
 *   }
 *   ...
 *   catch(异常类名 变量名){
 *
 *   }
 * 注意:
 *   1.try可能会抛出多个异常对象,那么就可以使用多个catch来处理这些对象
 *   2.如果try中产生了异常,那么就会执行catch中的异常处理逻辑,执行完毕catch的处理逻辑,继续执行try...catch之后的代码
 *     如果try中没有产生异常,那么就不会执行catch中的异常处理逻辑,执行完try中的代码,继续执行try...catch之后的代码
 * */
public class Demo05TryCatch {

    public static void main(String[] args) {
        try {
            //可能产生异常的代码
            readFile("c:\\a.tt");
//            readFile("c:\\a.txt");
        } catch (Exception e) {//try中抛出什么异常对象,catch就用什么异常变量,用来接收这个异常对象
            //异常的处理逻辑,抛出异常对象之后,怎么处理异常对象
            //System.out.println("catch - 传递的文件不是.txt");

            /*
            * Throwable类中定义了3个异常处理的方法
            *   String getMessage() 返回此 throwable的简短描述
            *   String toString() 返回此 throwable的详细消息字符串
            *   void printStackTrace() JVM打印异常对象,默认此方法,打印的异常信息是最全面的
            * */

            //System.out.println(e.getMessage());;//文件的后缀名不对
            //System.out.println(e.toString());//java.io.IOException: 文件的后缀名不对
            //System.out.println(e);//java.io.IOException: 文件的后缀名不对

            /*
            * java.io.IOException: 文件的后缀名不对
                at com.xpzt.day18.demo01.Demo05TryCatch.readFile(Demo05TryCatch.java:54)
                at com.xpzt.day18.demo01.Demo05TryCatch.main(Demo05TryCatch.java:28)
            * */
            e.printStackTrace();
        }
        System.out.println("后续代码");
    }

    private static void readFile(String fileName) throws Exception/*IOException*/ /*FileNotFoundException ,IOException*/ {
        /*
         * 如果传递的路径,不是.txt结尾
         * 那么我们就抛出IO异常对象,告知方法的调用者,文件的后缀名不对
         * */
        if (!fileName.endsWith(".txt")) {
            throw new IOException("文件的后缀名不对");
        }
        System.out.println("路径没有问题,读取文件");
    }
}

  • finally代码块
import java.io.IOException;

/*
 * finally代码块
 * 格式:
 *   try{
 *       可能产生异常的代码
 *   }catch(定义一个处理异常的变量,用来接收try中抛出的异常对象){
 *       异常的处理逻辑,抛出异常对象之后,怎么处理异常对象
 *       一般在工作中,会把异常的信息记录到一个日志中
 *   }
 *   ...
 *   catch(异常类名 变量名){
 *
 *   }finally{
 *      无论是否出现异常都会执行
 *   }
 * 注意:
 *      1.finally不能单独使用,必须喝try一起用
 *      2.finally一般用于资源释放(资源回收),无论程序是否出现异常,最终都要释放(IO)
 * */
public class Demo06TryCatchFinally {
    public static void main(String[] args) {
        try {
            //可能出现异常的代码
            readFile("c:\\a.tx");
        } catch (Exception e) {
            //异常处理逻辑
            e.printStackTrace();
        }finally {
            //无论是否出现异常,都会执行
            System.out.println("资源释放");
        }
    }

    private static void readFile(String fileName) throws Exception/*IOException*/ /*FileNotFoundException ,IOException*/ {
        /*
         * 如果传递的路径,不是.txt结尾
         * 那么我们就抛出IO异常对象,告知方法的调用者,文件的后缀名不对
         * */
        if (!fileName.endsWith(".txt")) {
            throw new IOException("文件的后缀名不对");
        }
        System.out.println("路径没有问题,读取文件");
    }

}

  • 如果finally有return语句,永远返回finally中的结果,尽量避免该情况
/*
 * 如果finally有return语句,永远返回finally中的结果,尽量避免该情况
 * */
public class Demo02Exception {

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

    //定义一个方法,返回变量a的值
    public static int getA() {
        int a = 10;
        try {
            return a;
        } catch (Exception e) {
            System.out.println(e);
        } finally {
            //一定会执行的代码
            a = 100;
            return a;
        }
    }

}

异常的注意事项
  • 多个异常分别处理
  • 多个异常一次捕获,多次处理
    • 一个try多个catch注意事项:catch里边定义的异常变量,如果有子父类关系,那么子类的异常变量必须写在上边,否则就会报错
      • ArrayIndexOutOfBoundsException extends IndexOutOfBoundsException
  • 多个异常一次捕获,一次处理
import java.util.List;

/*
 * 异常的注意事项
 * */
public class Demo01Exception {

    public static void main(String[] args) {
        /*
         * 多个异常使用捕获又该如何处理呢?
         * 1.多个异常分别处理。
         * 2.多个异常一次捕获,多次处理
         * 3.多个异常一次捕获,一次处理
         * */

        //1.多个异常分别处理。
        /*try{
            int[] arr={1,2,3};
            System.out.println(arr[3]);//ArrayIndexOutOfBoundsException
        }catch (ArrayIndexOutOfBoundsException e){
            System.out.println(e);
        }
        try {
            List<Integer> list = List.of(1, 2, 3);
            System.out.println(list.get(3));//IndexOutOfBoundsException
        }catch (IndexOutOfBoundsException e){
            System.out.println(e);
        }*/

        //2.多个异常一次捕获,多次处理
        /*try {
            int[] arr = {1, 2, 3};
            //System.out.println(arr[3]);//ArrayIndexOutOfBoundsException
            List<Integer> list = List.of(1, 2, 3);
            System.out.println(list.get(3));//IndexOutOfBoundsException
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println(e);
        } catch (IndexOutOfBoundsException e) {
            System.out.println(e);
        }*/
        /*
         * 一个try多个catch注意事项:
         *   catch里边定义的异常变量,如果有子父类关系,那么子类的异常变量必须写在上边,否则就会报错
         *       ArrayIndexOutOfBoundsException extends IndexOutOfBoundsException
         * */

        //3.多个异常一次捕获,一次处理
        /*try {
            int[] arr = {1, 2, 3};
            //System.out.println(arr[3]);//ArrayIndexOutOfBoundsException
            List<Integer> list = List.of(1, 2, 3);
            System.out.println(list.get(3));//IndexOutOfBoundsException
        } catch (Exception e) {
            System.out.println(e);
        }*/

        //运行时异常被抛出可以不处理,即不捕获也不声明抛出
        //默认个虚拟机处理,终止程序,什么时候不抛出异常了,再来继续执行程序
        int[] arr = {1, 2, 3};
        System.out.println(arr[3]);//ArrayIndexOutOfBoundsException
        List<Integer> list = List.of(1, 2, 3);
        System.out.println(list.get(3));//IndexOutOfBoundsException
        System.out.println("后续代码");
    }
}

子父类的异常
  • 如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者是不抛出
  • 父类没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出
  • 注意:父类异常什么样子,子类异常就什么样子
/*
 * 子父类的异常:
 *   如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者是不抛出
 *   父类没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出
 * 注意:
 *   父类异常什么样子,子类异常就什么样子
 * */
public class Fu {
    public void show01() throws NullPointerException, ClassCastException {}
    public void show02() throws IndexOutOfBoundsException {}
    public void show03() throws IndexOutOfBoundsException {}
    public void show04(){}
}

class Zi extends Fu{
    //子类重写父类方法时,抛出和父类相同的异常
    @Override
    public void show01() throws NullPointerException, ClassCastException {}
    //子类重写父类方法时,抛出父类异常的子类
    public void show02() throws ArrayIndexOutOfBoundsException{}
    //子类重写父类方法时,不抛出异常
    @Override
    public void show03(){}

    /*
    * 父类没有抛出异常,子类重写父类该方法时也不可抛出异常。
    * 此时子类产生该异常,只能捕获处理,不能声明抛出
    * */

    //public void show04() throws Exception{}

    //此时子类产生异常,只能捕获处理,不能声明抛出

    public void show04(){
        try {
            throw new Exception("编译器异常");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
自定义异常类
  • java提供的异常类,不够我们使用,需要自己定义一些异常类
/*
 * 自定义异常类:
 *       java提供的异常类,不够我们使用,需要自己定义一些异常类
 * 格式:
 *       public class XXXException extends Exception | RuntimeException{
 *           添加一个空参数的构造方法
 *           添加一个带异常信息的构造方法
 *       }
 * 注意:
 *       1.自定义异常类一般都是Exception结尾,说明该类是个异常类
 *       2.自定义异常类,必须得继承Exception或者RuntimeException
 *           继承Exception:那么自定义的异常类就是一个编译期异常,如果方法内部抛出了编译期异常,就必须处理这个异常,要么throws,要么try...catch
 *           继承RuntimeException:那么自定义的异常类就是一个运行期异常,无需处理,交给虚拟机处理(中断处理)
 * */
public class RegisterException extends RuntimeException/*Exception*/{
    //添加一个空参数的构造方法

    public RegisterException() {
        super();
    }

    /*
    * 添加一个带异常信息的构造方法
    * 查看源码发现,所有的异常类都会有一个带异常信息的构造方法,方法内部会调用父类带异常信息的构造方法,让父类来处理这个异常信息
    * */
    public RegisterException(String message) {
        super(message);
    }

}

小测试
import java.util.Scanner;

/*
 * 要求:我们模拟注册操作,如果用户名存在,则抛出异常并提示:亲,该用户名已经被注册。
 *
 * 分析:
 *       1.使用数组保存已经注册过的用户名(数据库)
 *       2.使用Scanner获取用户输入的注册的用户名(前端,页面)
 *       3.定义一个方法,对用户输入的注册的用户名进行判断
 *           遍历存储已经注册过的用户名的数组,获取每一个用户名
 *           使用获取到的用户名和用户输入的用户名比较
 *               true:
 *                   用户名已存在,抛出RegisterException异常,告知用户"亲,该用户名已被注册";
 *               false:
 *                   继续遍历比较
 *           如果循环结束了,还没有找到重复的用户名,提示用户"恭喜你,注册成功"
 * */
public class Demo01RegisterException {
    //1.使用数组保存已经注册过的用户名(数据库)
    static String[] userNames = {"张三", "李四", "王五"};

    public static void main(String[] args) /*throws RegisterException*/ {
        //2.使用Scanner获取用户输入的注册的用户名(前端,页面)
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入要注册的用户名:");
        String name = sc.next();
        checkUserName(name);
    }

    //3.定义一个方法,对用户输入的注册的用户名进行判断
    private static void checkUserName(String username) /*throws RegisterException*/ {
        //遍历存储已经注册过的用户名的数组,获取每一个用户名
        for (String name : userNames) {
            //使用获取到的用户名和用户输入的用户名比较
            if (name.equals(username)) {
                //true:用户名已存在,抛出RegisterException异常,告知用户"亲,该用户名已被注册";
                try {
                    throw new RegisterException("亲,该用户名已被注册");
                } catch (RegisterException e) {
                    e.printStackTrace();
                    return;//结束方法
                }
            }
        }
        //如果循环结束了,还没有找到重复的用户名,提示用户"恭喜你,注册成功"
        System.out.println("恭喜你,注册成功");
    }

}

多线程Thread

创建多线程的第一种方式:创建Thread类的子类

/*
 * 1.创建一个Thread类的子类
 * */
public class MyThread extends Thread {
    //2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开辟线程要做什么?)

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("run:" + i);
        }
    }

}

/*
 * 创建多线程的第一种方式:创建Thread类的子类
 * java.lang.Thread类:是描述线程的类,我们想要实现多线程程序,就必须继承Thread类
 *   1.创建一个Thread类的子类
 *   2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开辟线程要做什么?)
 *   3.创建Thread类的子类对象
 *   4.调用Thread类的方法start方法,开启新的线程,执行run方法。
 *       void start() 使用该线程开始执行,java虚拟机调用该线程的run方法
 *       结果是两个线程并发地进行;当前线程(main线程)和另一个线程(创建的新线程,执行其run方法)
 *       多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再启动。
 * java程序属于抢占式调度,哪个线程的优先级高,那个线程优先执行,同一个优先级别,随机才选择一个执行
 * */
public class Demo01MyThread {
    public static void main(String[] args) {
        //3.创建Thread类的子类对象
        MyThread mt = new MyThread();
        //4.调用Thread类的方法start方法,开启新的线程,执行run方法。
        mt.start();
//        new MyThread().start();
        //主线程会继续执行主方法中的代码
        for (int i = 0; i < 20; i++) {
            System.out.println("main:" + i);
        }
    }
}

获取线程的名称

/*
 * 获取线程的名称:
 *   1.使用Thread中的方法getName()
 *       String getName()返回该线程名称
 *   2.可以先获取当前正在执行的线程,使用线程中的方法getName()获取线程的名称
 *       static Thread currentThread() 返回当前正在执行的线程对象的引用
 * */

//定义一个Thread类的子类
public class MyThread extends Thread {
    //重写Thread类中的run方法,设置线程任务

    @Override
    public void run() {
        /*//1.使用Thread中的方法getName()
        String name = getName();
        System.out.println(name);*/

        /*
         * 2.可以先获取当前正在执行的线程,使用线程中的方法getName()获取线程的名称
         *       static Thread currentThread() 返回当前正在执行的线程对象的引用
         * */
        /*Thread t = Thread.currentThread();
        System.out.println(t);

        String name = t.getName();
        System.out.println(name);*/

        //链式编程
        System.out.println(Thread.currentThread().getName());
    }
}

/*
 * 线程的名称:
 *   主线程:main
 *   新线程:Thread-0,Thread-1,Thread-2
 * */
public class Demo01GetThreadName {
    public static void main(String[] args) {
        //创建Thread的子类对象
        MyThread mt = new MyThread();
        //调用start方法,开启新线程,执行run方法
        mt.start();
        new MyThread().start();
        new MyThread().start();
        //链式编程
        System.out.println(Thread.currentThread().getName());
    }

}


设置线程的名称

/*
 *设置线程的名称:(了解)
 *   1.使用Thread类中的方法setName(名字)
 *       void setName(String name) 改变线程名称,使之与参数name相同
 *   2.创建一个参数的构造方法,参数传递现成的名称;调用父类的带参构造方法,把线程的名称传递给父类,让父类(Thread)给子线程起一个名字
 *       Thread(String name) 分配新的Thread对象
 * */
public class MyThread extends Thread {

    /*
     * 2.创建一个参数的构造方法,参数传递现成的名称;调用父类的带参构造方法,把线程的名称传递给父类,让父类(Thread)给子线程起一个名字
     * */
    public MyThread() {
    }

    public MyThread(String name) {
        super(name);//把线程的名称传递给父类,让父类(Thread)给子线程起一个名字
    }

    @Override
    public void run() {
        //获取线程的名称
        System.out.println(Thread.currentThread().getName());
    }
}

public class Demo01SetThreadName {
    public static void main(String[] args) {
        //开启多线程
        MyThread mt=new MyThread();
        //1.使用Thread类中的方法setName(名字)
        mt.setName("小强");
        mt.start();

        开启多线程
        new MyThread("陈浩").start();

    }
}

sleep方法

  • 使当前正在执行的线程以毫秒数暂停(暂时停止执行)
/*
 * public static void sleep(long millis):使当前正在执行的线程以毫秒数暂停(暂时停止执行)
 * 毫秒数结束之后,线程继续执行
 * */
public class Demo01Sleep {
    public static void main(String[] args) {
        //模拟秒表
        for (int i = 0; i < 50; i++) {
            System.out.println(i);
            //使用Thread类的sleep方法让程序睡眠1秒
            try {
                Thread.sleep(1000);//暂停一秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

创建多线程的第二种方式:实现Runnable接口

  • Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为run的无参方法
  • 实现类Runnable接口创建多线程程序的好处:
    • 避免了单继承的局限性:一个类只能继承一个类(一个类只能有一个亲爹),类继承了Thread类就不能继承其他的类,实现了Runnable接口,还可以继承其它的类,实现其他的接口
    • 增强了程序的扩展性,降低了程序的耦合性(解耦)
    • 实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
    • 实现类中,重写了run方法:用来设置线程任务
    • 创建Thread类对象,调用start方法:用来开启线程
//1.创建一个Runnable接口的实现类
public class RunnableImpl implements Runnable {
    //2.在实现类中重写Runnable接口的run方法,设置线程任务
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}

//1.创建一个Runnable接口的实现类
public class RunnableImpl2 implements Runnable {
    //2.在实现类中重写Runnable接口的run方法,设置线程任务
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("HelloWorld"+ i);
        }
    }
}

/*
 * 创建多线程的第二种方式:实现Runnable接口
 * java.lang.Runnable
 *       Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为run的无参方法
 * java.lang.Thread类的构造方法
 *       Thread(Runnable target) 分配新的Thread对象
 *       Thread(Runnable target,String name) 分配新的Thread对象
 *
 * 实现步骤:
 *   1.创建一个Runnable接口的实现类
 *   2.在实现类中重写Runnable接口的run方法,设置线程任务
 *   3.创建一个Runnable接口的实现类对象
 *   4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
 *   5.调用Thread类中的start方法,开启新线程执行run方法
 *
 * 实现类Runnable接口创建多线程程序的好处:
 *  1.避免了单继承的局限性
 *      一个类只能继承一个类(一个类只能有一个亲爹),类继承了Thread类就不能继承其他的类
 *      实现了Runnable接口,还可以继承其它的类,实现其他的接口
 *  2.增强了程序的扩展性,降低了程序的耦合性(解耦)
 *      实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
 *      实现类中,重写了run方法:用来设置线程任务
 *      创建Thread类对象,调用start方法:用来开启线程
 * */
public class Demo01Runnable {
    public static void main(String[] args) {
        //3.创建一个Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        //Thread t = new Thread(run);//打印线程名称
        Thread t = new Thread(new RunnableImpl2());//打印HelloWorld
        //5.调用Thread类中的start方法,开启新线程执行run方法
        t.start();
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }

    }
}

匿名内部类方式实现线程的创建

/*
 * 匿名内部类方式实现线程的创建
 *
 * 匿名:没有名字
 * 内部类:写在其他类内部的类
 *
 * 匿名内部类作用:简化代码
 *   把子类继承父类,重写父类的方法,创建子类对象合一步完成
 *   把实现类实现类接口,重写接口中的方法,创建实现类对象合一步成
 *
 * 匿名内部类的最终产物:子类/实现类对象,而没有类这个名字
 *
 * 格式:
 *   new 父类/接口{
 *       重写父类/接口中的方法;
 *   };
 * */
public class Demo01InnerClassThread {
    public static void main(String[] args) {
        //线程的父类是Thread
        //new Thread().start();
        new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName() + "-->" + "ch");
                }
            }
        }.start();//开启线程

        //线程的接口Runnable
        //Runnable r=new RunnableImpl();//多态
        Runnable r = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName() + "-->" + "zb");
                }
            }
        };
        new Thread(r).start();//开启线程

        //简化接口的方式
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName() + "-->" + "gb");
                }
            }
        }).start();
    }
}

主线程

  • 主线程:执行主(main)方法的线程
  • 单线程程序:java程序中只有一个线程
  • 执行main方法开始,从上往下依次执行
public class Person {
    private String name;

    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(name + "-->" + i);
        }
    }

    public Person() {
    }

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

    public String getName() {
        return name;
    }

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

/*
 * 主线程:执行主(main)方法的线程
 *
 * 单线程程序:java程序中只有一个线程
 * 执行main方法开始,从上往下依次执行
 *
 * JVM执行main方法,main方法会计入到栈内存
 * JVM会找操作系统开辟一条main方法通向cpu的执行路径
 * cpu就可以通过这个线程来执行main方法
 * 而这个路径有一个名字
 * 叫main(主)线程
 * */
public class Demo01MainThread {
    public static void main(String[] args) {
        Person p1 = new Person("ch");
        p1.run();

        System.out.println(0 / 0);//ArithmeticException: / by zero  -- 因为是单线程,后面程序不再执行

        Person p2 = new Person("zb");
        p2.run();
    }

}

Runable接口演示实例1(有bug)

  • 卖票案例出现了线程安全问题:卖出了不存在的票和重复的票
/*
 * 实现卖票案例
 * */
public class RunnableImpl implements Runnable {
    //定义一个多线程共享的票源
    private int ticket = 100;

    //设置线程任务:卖票
    @Override
    public void run() {
        //使用死循环,让买票操作循环执行
        while (true) {
            //先判断票是否存在
            if (ticket > 0) {
                //提高线程安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //票存在,卖票 ticket--
                System.out.println(Thread.currentThread().getName() + "-->" + "正在卖第" + ticket + "张票");
                ticket--;
            }
        }
    }
}

/*
 * 模拟买票案例
 * 创建3个线程,同时开启,对共享的票进行出售
 * */
public class Demo01Ticket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        //创建3个线程,同时开启,对共享的票进行出售
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }
}

Runable接口演示实例2

解决线程安全问题的第一种方案:synchronized同步代码块
  • 注意:
    • 1.通过代码块中的锁对象,可以使用任意的对象
    • 2.但是必须保证多个线程使用的锁对象是同一个
    • 3.锁对象的作用:把同步代码块锁住,只让一个线程在同步代码块中执行
/*
 * 卖票案例出现了线程安全问题
 * 卖出了不存在的票和重复的票
 *
 * 解决线程安全问题的一种方案:使用同步代码块
 * 格式:
 *      synchronized(锁对象){
 *          可能出现安全问题的代码(访问了共享数据的代码)
 *      }
 *
 * 注意:
 *  1.通过代码块中的锁对象,可以使用任意的对象
 *  2.但是必须保证多个线程使用的锁对象是同一个
 *  3.锁对象的作用:
 *      把同步代码块锁住,只让一个线程在同步代码块中执行
 * */
public class RunnableImpl implements Runnable {
    //定义一个多线程共享的票源
    private int ticket = 100;
    //创建一个锁对象
    Object obj = new Object();

    //设置线程任务:卖票
    @Override
    public void run() {
        //使用死循环,让买票操作循环执行
        while (true) {
            //同步代码块
            synchronized (obj) {
                //先判断票是否存在
                if (ticket > 0) {
                    //提高线程安全问题出现的概率,让程序睡眠
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //票存在,卖票 ticket--
                    System.out.println(Thread.currentThread().getName() + "-->" + "正在卖第" + ticket + "张票");
                    ticket--;
                }
            }
        }
    }
}

/*
 * 模拟买票案例
 * 创建3个线程,同时开启,对共享的票进行出售
 * */
public class Demo01Ticket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        //创建3个线程,同时开启,对共享的票进行出售
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }
}

解决线程安全问题的第二种方案:使用同步方法
  • 第一种:普通的同步方法
/*
 * 卖票案例出现了线程安全问题
 * 卖出了不存在的票和重复的票
 *
 * 解决线程安全问题的一种方案:使用同步方法
 * 使用步骤:
 *      1.把访问了共享数据的代码抽取出来,放到一个方法中
 *      2.在方法中添加synchronized修饰符
 *
 * 格式:定义方法的格式
 *      修饰符 synchronized 返回值类型 方法名(参数列表){
 *          可能出现安全问题的代码(访问了共享数据的代码)
 *      }
 *
 * */
public class RunnableImpl implements Runnable {
    //定义一个多线程共享的票源
    private int ticket = 100;

    //设置线程任务:卖票
    @Override
    public void run() {
        System.out.println("this:" + this);//this:com.xpzt.day20.demo03.RunnableImpl@506e1b77
        //使用死循环,让买票操作循环执行
        while (true) {
            payTicket();
        }
    }

    /*
     * 定义一个同步方法
     * 同步方法也会把方法内部的代码锁住
     * 只会让一个线程执行
     * 同步方法 锁对象是谁?
     * 就是实现类对象 new RunnableImpl()
     * 也就是this
     * */
    public /*synchronized*/ void payTicket() {
        /*//先判断票是否存在
        if (ticket > 0) {
            //提高线程安全问题出现的概率,让程序睡眠
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //票存在,卖票 ticket--
            System.out.println(Thread.currentThread().getName() + "-->" + "正在卖第" + ticket + "张票");
            ticket--;*/

        synchronized (this) {
            //先判断票是否存在
            if (ticket > 0) {
                //提高线程安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //票存在,卖票 ticket--
                System.out.println(Thread.currentThread().getName() + "-->" + "正在卖第" + ticket + "张票");
                ticket--;
            }
        }
    }
}

/*
 * 模拟买票案例
 * 创建3个线程,同时开启,对共享的票进行出售
 * */
public class Demo01Ticket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        System.out.println("run:" + run);//run:com.xpzt.day20.demo03.RunnableImpl@506e1b77
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        //创建3个线程,同时开启,对共享的票进行出售
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }
}

  • 第二种:静态的同步方法
/*
 * 卖票案例出现了线程安全问题
 * 卖出了不存在的票和重复的票
 *
 * 解决线程安全问题的一种方案:使用同步方法
 * 使用步骤:
 *      1.把访问了共享数据的代码抽取出来,放到一个方法中
 *      2.在方法中添加synchronized修饰符
 *
 * 格式:定义方法的格式
 *      修饰符 synchronized 返回值类型 方法名(参数列表){
 *          可能出现安全问题的代码(访问了共享数据的代码)
 *      }
 *
 * */
public class RunnableImpl implements Runnable {
    //定义一个多线程共享的票源
    private static int ticket = 100;

    //设置线程任务:卖票
    @Override
    public void run() {
        System.out.println("this:" + this);//this:com.xpzt.day20.demo03.RunnableImpl@506e1b77
        //使用死循环,让买票操作循环执行
        while (true) {
            payTicketStatic();
        }
    }

    /*
     * 静态的同步方法
     * 锁对象是谁?
     * 不能是this
     * this是创建对象后产生的,静态方法优先于对象
     * 静态方法的锁对象是本类的class属性-->class文件对象(反射)
     * */
    public static synchronized void payTicketStatic() {
        synchronized (RunnableImpl.class) {
            //先判断票是否存在
            if (ticket > 0) {
                //提高线程安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //票存在,卖票 ticket--
                System.out.println(Thread.currentThread().getName() + "-->" + "正在卖第" + ticket + "张票");
                ticket--;
            }
        }

    }
}

/*
 * 模拟买票案例
 * 创建3个线程,同时开启,对共享的票进行出售
 * */
public class Demo01Ticket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        System.out.println("run:" + run);//run:com.xpzt.day20.demo03.RunnableImpl@506e1b77
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        //创建3个线程,同时开启,对共享的票进行出售
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }
}

解决线程安全问题的第三种方案:使用lock锁
  • 使用步骤:
    1. 在成员位置创建一个ReentrantLock对象
    2. 在可能会出现安全问题的代码前调用Lock接口中的方法lock()获取锁
    3. 在可能会出现安全问题的代码后调用Lock接口中的方法unlock()释放锁
import java.util.concurrent.locks.ReentrantLock;

/*
 * 卖票案例出现了线程安全问题
 * 卖出了不存在的票和重复的票
 *
 * 解决线程安全问题的第三种方案:使用lock锁
 * java.util.concurrent.locks.Lock接口
 * Lock 实现提供了比使用 synchronized 方法和语句可获得更广泛的锁定操作。
 * Lock接口中的方法:
 *      void lock() 获取锁
 *      void unlock() 释放锁
 * java.util.concurrent.locks.ReentrantLock implements Lock接口
 *
 * 使用步骤:
 *      1.在成员位置创建一个ReentrantLock对象
 *      2.在可能会出现安全问题的代码前调用Lock接口中的方法lock()获取锁
 *      3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock()释放锁
 * */
public class RunnableImpl implements Runnable {
    //定义一个多线程共享的票源
    private int ticket = 100;
    //1.在成员位置创建一个ReentrantLock对象
    ReentrantLock l = new ReentrantLock();

    //设置线程任务:卖票
    @Override
    public void run() {
        //使用死循环,让买票操作循环执行
        while (true) {
            //2.在可能会出现安全问题的代码前调用Lock接口中的方法lock()获取锁
            l.lock();
            //先判断票是否存在
            if (ticket > 0) {
                //提高线程安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                    //票存在,卖票 ticket--
                    System.out.println(Thread.currentThread().getName() + "-->" + "正在卖第" + ticket + "张票");
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally{
                    //3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock()释放锁
                    l.unlock();//无论程序是否异常,都会把锁释放
                }


            }
        }
    }
}

/*
 * 模拟买票案例
 * 创建3个线程,同时开启,对共享的票进行出售
 * */
public class Demo01Ticket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        //创建3个线程,同时开启,对共享的票进行出售
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }
}

synchronized同步与互斥实例

/*
 * 资源类:包子类
 * 设置包子的属性:
 *       皮
 *       馅
 *       包子的状态:有 true, 没有 false
 * */
public class BaoZi {
    //皮
    String pi;
    //馅
    String xian;
    //包子的状态:有 true, 没有 false  设置初始值为没有 false
    boolean flag = false;
}

/*
 * 生产者(包子铺)类:是一个线程类,可以继承Thread
 * 设置线程任务(run):生产包子
 * 对包子的状态进行判断:
 * true:有包子
 *   包子铺调用wait方法进入等待状态
 * false:没有包子
 *   包子铺生产包子
 *   增加一些趣味性:交替生产两种包子
 *       有两种状态(i%2==0)
 *   包子铺生产好了包子
 *   修改包子的状态为true有
 *   唤醒吃货线程,让吃货线程吃包子
 *
 * 注意:
 *   包子铺线程和吃货线程关系-->通信关系(互斥)
 *   必须同时使用同步技术保证两个线程只能有一个线程在执行
 *   锁对象必须唯一,可以使用包子对象作为锁对象
 *   包子铺类和吃货类就需要把包子对象作为参数传递进来
 *       1.需要在成员位置创建一个包子变量
 *       2.使用带参的构造方法,为这个包子变量赋值
 * */
public class BaoZiPu extends Thread {
    //1.需要在成员位置创建一个包子变量
    private BaoZi bz;

    //2.使用带参的构造方法,为这个包子变量赋值
    public BaoZiPu(BaoZi bz) {
        this.bz = bz;
    }

    //设置线程任务(run):生产包子
    @Override
    public void run() {
        //定义一个变量
        int count = 0;
        //让包子铺一直生产包子
        while (true) {
            //必须同时使用同步技术保证两个线程只能有一个线程在执行
            synchronized (bz) {
                //对包子的状态进行判断
                if (bz.flag == true) {
                    //true:有包子
                    //包子铺调用wait方法进入等待状态
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                //被唤醒之后执行,包子铺生产包子
                //增加一些趣味性:交替生产两种包子
                if (count % 2 == 0) {
                    //生产 薄皮 三鲜馅包子
                    bz.pi = "薄皮";
                    bz.xian = "三鲜馅";
                } else {
                    //生产 冰皮 牛肉大葱包子
                    bz.pi = "冰皮";
                    bz.xian = "牛肉大葱";
                }
                count++;
                System.out.println("包子铺正在生产:" + bz.pi + bz.xian + "包子");
                //生产包子需要3秒钟
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //包子铺生产好了包子
                //修改包子的状态为true有
                bz.flag = true;
                bz.notify();
                //唤醒吃货线程,让吃货线程吃包子
                System.out.println("包子铺已经生产好了:" + bz.pi + bz.xian + "包子,吃货可以开始吃了");
            }
        }
    }
}

/*
 * 消费者(吃货)类:是一个线程类,可以继承Thread类
 * 设置线程任务(run):吃包子
 * 对包子的状态进行判断
 * false:没有包子
 *   吃货调用wait方法进入等待状态
 * true:有包子
 *   吃货吃包子
 *   吃货吃完包子
 *   修改包子的状态为false 没有
 *   吃货唤醒包子铺线程,生产包子
 * */
public class ChiHuo extends Thread {
    //1.需要在成员位置创建一个包子变量
    private BaoZi bz;

    //2.使用带参的构造方法,为这个包子变量赋值
    public ChiHuo(BaoZi bz) {
        this.bz = bz;
    }

    //设置线程任务(run):吃包子
    @Override
    public void run() {
        while (true) {
            //必须同时使用同步技术保证两个线程只能有一个线程在执行
            synchronized (bz) {
                //对包子的状态进行判断
                if (bz.flag == false) {
                    //false:没有包子
                    //吃货调用wait方法进入等待状态
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                //被唤醒之后执行的代码,吃包子
                System.out.println("吃货正在吃:" + bz.pi + bz.xian + "包子");

                //吃包子需要3秒钟
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //吃货吃完包子
                //修改包子的状态为false 没有
                bz.flag = false;
                //吃货唤醒包子铺线程,生产包子
                bz.notify();
                System.out.println("吃货已经把:" + bz.pi + bz.xian + "包子吃完了,包子铺开始生产包子");
            }
        }
    }
}

/*
 *测试类:
 * 包含main方法,程序执行的入口,启动程序
 * 创建包子对象
 * 创建包子铺程序,开启,生产包子
 * 创建吃货线程,开启,吃包子
 * */
public class Demo {
    public static void main(String[] args) {
        //创建包子对象
        BaoZi bz = new BaoZi();
        //创建包子铺程序,开启,生产包子
        new BaoZiPu(bz).start();
        //创建吃货线程,开启,吃包子
        new ChiHuo(bz).start();
    }
}

等待唤醒案例:线程之间的通信

Object类中的wait和notify方法及notifyAll方法

/*
 * 等待唤醒案例:线程之间的通信
 *   创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
 *   创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子
 *
 * 注意:
 *   顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个执行
 *   同步使用的锁对象必须保证唯一
 *   只有锁对象才能调用wait方法和notify方法
 *
 * Object类中的方法
 * void wait()
 *          在其他线程调用此对象的notify()方法或notifyAll()方法前,导致当前线程等待
 * void notify()
 *          唤醒在此对象监视器上等待的单个线程
 *          会继续执行wait方法之后的代码
 * */
public class Demo01WaitAndNotify {
    public static void main(String[] args) {
        //创建锁对象,保证唯一
        Object obj = new Object();
        //创建一个顾客线程(消费者)
        new Thread() {
            @Override
            public void run() {
                //一直等着买包子
                while (true) {
                    //保证等待和唤醒只能有一个在执行,需要使用同步技术
                    synchronized (obj) {
                        System.out.println("告知老板要的包子的种类和数量");

                        try {
                            obj.wait();//调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }//不能使用throws的原因是当前方法的父类方法没有抛出异常,所以子类重写方法不能抛出异常
                        //唤醒之后执行的代码
                        System.out.println("包子已经做好了,开吃");
                        System.out.println("==============================");
                    }

                }
            }
        }.start();

        //创建一个老板线程(生产者)
        new Thread() {
            @Override
            public void run() {
                //一直做包子
                while (true) {
                    //花了5秒做包子
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    //保证等待和唤醒只能有一个在执行,需要使用同步技术
                    synchronized (obj) {
                        System.out.println("老板5秒之后做好包子,告知顾客,可以吃包子了");
                        //做好包子之后,调用notify方法,唤醒顾客吃包子
                        obj.notify();

                    }
                }
            }
        }.start();
    }
}

/*
* 进入到TimeWaiting(计时等待)有两种方式
*   1.使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
*   2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态
* 唤醒的方法:
*   void notify() 唤醒在此对象监视器上等待的单个线程
*   void notifyAll() 唤醒在此对象监视器上等待的所有线程
* */
public class Demo02WaitAndNotify {
    public static void main(String[] args) {
        //创建锁对象,保证唯一
        Object obj = new Object();
        //创建一个顾客线程(消费者)
        new Thread() {
            @Override
            public void run() {
                //一直等着买包子
                while (true) {
                    //保证等待和唤醒只能有一个在执行,需要使用同步技术
                    synchronized (obj) {
                        System.out.println("顾客1告知老板要的包子的种类和数量");
                        //调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
                        try {
                            obj.wait(/*5000*/);//wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }//不能使用throws的原因是当前方法的父类方法没有抛出异常,所以子类重写方法不能抛出异常
                        //唤醒之后执行的代码
                        System.out.println("包子已经做好了,顾客1开吃");
                        System.out.println("==============================");
                    }

                }
            }
        }.start();

        //创建一个顾客线程(消费者)
        new Thread() {
            @Override
            public void run() {
                //一直等着买包子
                while (true) {
                    //保证等待和唤醒只能有一个在执行,需要使用同步技术
                    synchronized (obj) {
                        System.out.println("顾客2告知老板要的包子的种类和数量");
                        //调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
                        try {
                            obj.wait(/*5000*/);//wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }//不能使用throws的原因是当前方法的父类方法没有抛出异常,所以子类重写方法不能抛出异常
                        //唤醒之后执行的代码
                        System.out.println("包子已经做好了,顾客2开吃");
                        System.out.println("==============================");
                    }

                }
            }
        }.start();

        //创建一个老板线程(生产者)
        new Thread() {
            @Override
            public void run() {
                //一直做包子
                while (true) {
                    //花了5秒做包子
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    //保证等待和唤醒只能有一个在执行,需要使用同步技术
                    synchronized (obj) {
                        System.out.println("老板5秒之后做好包子,告知顾客,可以吃包子了");
                        //做好包子之后,调用notify方法,唤醒顾客吃包子
                        //obj.notify();//如果有多个等待线程,随机唤醒一个
                        obj.notifyAll();//唤醒所有等待的线程
                    }
                }
            }
        }.start();
    }
}

线程池

/*
 * 2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
 * */
public class RunnableImpl implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"开始新线程任务");
    }
}

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/*
 * 线程池:JDK1.5之后提供的
 * java.util.concurrent.Executors:线程池的工厂类,用来生成线程池
 * Executors类中的静态方法:
 *       static ExecutorService newFixedThreadPool(int nThread) 创建一个可重用固定线程数的线程池
 *       参数:
 *           int nThread:创建线程池包含的线程数量
 *       返回值:
 *           ExecutorService接口,返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收(面向接口编程)
 * java.util.concurrent.ExecutorService:线程接口
 *       用来从线程池获取线程,调用start(),执行线程任务
 *           submit(Runnable task) 提交一个Runnable任务用于执行
 *       关闭/销毁线程池的方法:
 *           void shutdown()
 *
 * 线程池的使用步骤:
 *       1.使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定数量的线程池
 *       2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
 *       3.调用ExecutorService中的方法submit方法,传递线程任务(实现类),开启线程,执行run方法
 *       4.调用ExecutorService中的方法shutdown方法销毁线程池(不推荐使用)
 * */
public class Demo01ThreadPool {
    public static void main(String[] args) {
        //1.使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定数量的线程池
        ExecutorService es = Executors.newFixedThreadPool(6);
        //3.调用ExecutorService中的方法submit方法,传递线程任务(实现类),开启线程,执行run方法
        es.submit(new RunnableImpl());//pool-1-thread-2开始新线程任务
        es.submit(new RunnableImpl());//pool-1-thread-2开始新线程任务
        es.submit(new RunnableImpl());//pool-1-thread-1开始新线程任务
        //4.调用ExecutorService中的方法shutdown方法销毁线程池(不推荐使用)
        es.shutdown();
        //es.submit(new RunnableImpl());//抛异常,线程池都没有了,就不能获取线程了  RejectedExecutionException
    }
}

Lambda表达式

  • Lambda的使用前提:

    • 特别注意
      • 使用Lambda必须具有接口,且要求 接口中有且仅有一个抽象方法
      • 无论是JDK内置的Runnable、Comparator接口还是自定义的接口,只有当接口的抽象方法唯一时,才可以使用Lambda
      • 使用Lambda必须具有上下文推断也就是方法的参数或局部变量必须为Lambda对应的接口类型,才能使用该接口的实例
    • 备注:有且仅有一个抽象方法的接口,称为"函数式接口"
  • 格式:(参数列表) -> {一些重写的代码};

    • Lambda表达式:是可推导,可以省略
      • 凡是根据上下文推导出来的内容,都可以省略书写
      • 可以省略的内容:
        • (参数列表):括号中参数列表的数据类型,可以省略不写
        • (参数列表):括号中的参数只有一个,那么类型和()都可以省略
        • {一些代码}:如果{}中的代码只有一行,无论是否有返回值,都可以省略({},return,分号)
      • 注意:要省略({},return,分号)必须一起省略
/*
 * Lambda表达式的标准格式:
 *   由三部分组成:
 *       a.一些参数
 *       b.一个箭头
 *       c.一段代码
 *   格式:
 *       (参数列表) -> {一些重写的代码};
 *   解释说明格式:
 *       ():接口中抽象方法的参数列表,没有参数,就空着;有参数就写出参数,多个参数使用逗号隔开
 *       ->:传递的意思,把参数传给方法体()
 *       {}:重写接口的抽象方法的方法体
 * */
public class Demo01Lambda {
    public static void main(String[] args) {
        //使用匿名内部类,使用多线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "新线程创建了");
            }
        }).start();

        //使用Lambda表达式,使用多线程
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "新线程创建了");
        }
        ).start();

        //优化省略Lambda
        new Thread(() ->System.out.println(Thread.currentThread().getName() + "新线程创建了")).start();
    }
}

小测试

实例1
/*
 * 给定一个厨子Cook接口,内含唯一的抽象方法makeFood
 * */
public interface Cook {
    //定义无参数、无返回值的方法makeFood
    /*public abstract */void makeFood();
}

/*
 * 需求:
 *   给定一个厨子Cook接口,内含唯一的抽象方法makeFood,且无参数、无返回值。
 *   使用Lambda的标准格式调用invokeCook方法,打印输出"吃饭啦!"字样
 * */
public class Demo01Lambda {
    public static void main(String[] args) {
        //调用invokeCook方法,参数是Cook接口,传递Cook接口的匿名内部类对象
        invokeCook(new Cook() {
            @Override
            public void makeFood() {
                System.out.println("吃饭啦!");
            }
        });

        //使用Lambda表达式,简化匿名内部类的书写
        invokeCook(() -> {
            System.out.println("吃饭啦!");
        });

        //优化省略Lambda
        invokeCook(() -> System.out.println("吃饭啦!"));
    }

    /*
     * 定义一个方法,参数传递Cook接口,方法内部调用Cook接口中的方法makeFood
     * */
    public static void invokeCook(Cook cook) {
        cook.makeFood();
    }
}

实例2
public class Person {
    private String name;
    private int age;

    public Person() {
    }

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", 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;
    }
}

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

/*
 * Lambda表达式有参数有返回值的练习
 * 需求:
 *       使用数组存储多个Person对象
 *       对数组中的Person对象使用Arrays的sort方法通过年龄升序排序
 * */
public class Demo01Arrays {
    public static void main(String[] args) {
        //使用数组存储多个Person对象
        Person[] arr = {
                new Person("陈浩", 111),
                new Person("猪比", 141),
                new Person("狗比", 61)
        };
        //对数组中的Person对象使用Arrays的sort方法通过年龄升序(前边-后边)排序
        /*Arrays.sort(arr, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge() - o2.getAge();
            }
        });//sort的第二个参数相当于排序格式
        */
        //使用Lambda表达式,简化匿名内部类
        Arrays.sort(arr, (Person o1, Person o2) -> {
            return o1.getAge() - o2.getAge();
        });

        //优化省略Lambda
        Arrays.sort(arr, (o1, o2) -> o1.getAge() - o2.getAge());

        //遍历数组
        for (Person person : arr) {
            System.out.println(person);
        }
    }
}

实例3
/*
 * 给定一个计算器Calculator接口,内含抽象方法calc可以将两个int数值相加得到和值
 * */
public interface Calculator {
    //定义计算两个int整数和方法并返回结果
    int calc(int a,int b);
}

/*
 * Lambda表达式有参数有返回值的练习
 * 需求:
 *   给定一个计算器Calculator接口,内含抽象方法calc可以将两个int数值相加得到和值
 *   使用Lambda的标准格式调用invokeCalc方法,完成120加130的相加计算
 * */
public class Demo02Calculator {
    public static void main(String[] args) {
        //调用invokeCalc方法,方法参数一个接口,可以使用匿名内部类
        invokeCalc(10, 20, new Calculator() {
            @Override
            public int calc(int a, int b) {
                return a + b;
            }
        });

        //使用Lambda表达式,简化匿名内部类的书写
        invokeCalc(120, 130, (int a, int b) -> {
            return a + b;
        });

        //优化省略Lambda
        invokeCalc(120, 130, (a, b) -> a + b);
    }

    /*
     * 定义一个方法
     * 参数传递两个int类型的整数
     * 传输传递Calculator接口方法内部调用Calculator中的方法calc计算两个整数的和
     * */
    public static void invokeCalc(int a, int b, Calculator c) {
        int sum = c.calc(a, b);
        System.out.println(sum);
    }
}

File类及其方法

  • File类是一个与系统无关的类,任何的操作系统都可以使用这个类的方法
  • java把电脑中的文件和文件夹(目录)封装成了一个File类,我们可以使用File类对文件和文件夹进行操作
import java.io.File;

/*
 * java.io.File类
 * 文件和目录路径的抽象表示形式
 * java把电脑中的文件和文件夹(目录)封装成了一个File类,我们可以使用File类对文件和文件夹进行操作
 * 我们可以使用File类的方法
 *       创建一个文件/文件夹
 *       删除文件/文件夹
 *       获取文件/文件夹
 *       判断文件/文件夹是否存在
 *       对文件夹进行遍历
 *       获取文件的大小
 * File类是一个与系统无关的类,任何的操作系统都可以使用这个类的方法
 *
 * 重点:记住这三个单词
 *       file:文件
 *       directory:文件夹/目录
 *       path:路径
 *
 * */
public class Demo01File {
    public static void
    main(String[] args) {

        /*
         * static String pathSeparator 与系统有关的路径分隔符,为了方便,它被表示为一个字符串。
         * static char pathSeparatorChar 与系统有关的路径分隔符。
         * static String separator 与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。
         * static char separatorChar 与系统有关的默认名称分隔符。
         *
         * 操作路径:路径不能写死了
         * D:\IdeaProjects\basic-code  windows
         * D:/IdeaProjects/basic-code  linux
         * "D:"+File.separator+"IdeaProjects"+File.separator+"basic-code"  通用
         * */
        String pathSeparator = File.pathSeparator;
        System.out.println(pathSeparator);//路径分隔符 Windows:分号;  Linux/Mac:冒号:
        String separator = File.separator;
        System.out.println(separator);//文件名称分隔符 Windows:反斜杠\  Linux/Mac:正斜杠/

    }
}

  • File(File parent,String child) 根据parent抽象路径和child路径名字符串创建一个新的File类
  • 参数:把路径分成了两部分
    • String parent:父路径
    • String child:子路径
    • 好处:
    • 父路径和子路径,可以单独书写,使用起来非常灵活;父路径和子路径都可以变化
    • 父路径是File类型,可以使用File类的方法对路径进行一些操作,再使用路径创建对象
import java.io.File;

/*
 * 路径:
 *   绝对路径:是一个完整的路径
 *       以盘符(C:,D:)开始的路径
 *           D:\\a.txt
 *           D:\\IdeaProjects\\basic-code
 *           D:\\demo\\b.txt
 *   相对路径:是一个简化的路径
 *       相对的是当前项目的根目录(D:\\IdeaProjects\\basic-code)
 *       如果使用当前项目的根目录,路径可以简化书写
 *       D:\\IdeaProjects\\basic-code\\123.txt -->简化为 123.txt(可以省略项目的根目录)
 *   注意:
 *       1.路径不区分大小写
 *       2.路径的文件名称分隔符windows使用反斜杠,反斜杠是转义字符,两个反斜杠代表一个普通的反斜杠
 * */
public class Demo02File {
    public static void main(String[] args) {
        /*
         * File类的构造方法
         * */
        //show01();
        show02("c://", "a.txt");//Mac c:/a.txt
        //show02("d:\\", "a.txt");//d:\a.txt
//        show03();

    }

    /*
     * File(File parent,String child) 根据parent抽象路径和child路径名字符串创建一个新的File类
     * 参数:把路径分成了两部分
     *   String parent:父路径
     *   String child:子路径
     * 好处:
     *   父路径和子路径,可以单独书写,使用起来非常灵活;父路径和子路径都可以变化
     *   父路径是File类型,可以使用File类的方法对路径进行一些操作,再使用路径创建对象
     * */
    private static void show03() {
        File parent = new File("c:\\");
        File file = new File(parent, "hello.java");//父路径是File类型,可以使用File类的方法对路径进行一些操作,再使用路径创建对象
        System.out.println(file);//c:\hello.java

    }

    /*
     * File(String parent,String child) 根据parent路径名字符串和child路径名字符串创建一个新的File类
     * 参数:把路径分成了两部分
     *   String parent:父路径
     *   String child:子路径
     * 好处:
     *   父路径和子路径,可以单独书写,使用起来非常灵活;父路径和子路径都可以变化
     * */
    private static void show02(String parent, String child) {
        File file = new File(parent, child);
        System.out.println(file);
    }


    /*
     * File(String pathname) 通过将给定路径名字字符串转换为抽象路径名创建一个新File实例
     * 参数:
     *   String pathname:字符串的路径名称
     *   路径可以是以文件结尾,也可以是以文件夹结尾
     *   路径可以是相对路径,也可以是绝对路径
     *   路径可以是存在,也可以是不存在
     *   创建File对象,只是把字符串路径封装为File对象,不考虑路径的真或假情况
     * */
    private static void show01() {
        File f1 = new File("D:\\IdeaProjects\\basic-code\\a.txt");
        System.out.println(f1);

        File f2 = new File("D:\\IdeaProjects\\basic-code");
        System.out.println(f2);

        File f3 = new File("b.txt");
        System.out.println(f3);
    }
}

File类获取功能的方法

import java.io.File;

/*
 * File类获取功能的方法
 *       public String getAbsolutePath():返回次File的绝对路径名字符串
 *       public String getPath():将此File转换为路径名字符串
 *       public String getName(): 返回由此File表示的文件或目录的名称
 *       public long length():返回由此File表示的文件的长度
 * */
public class Demo03File {
    public static void main(String[] args) {
        show04();
    }

    /*
     * public long length():返回由此File表示的文件的长度
     * 获取的是构造方法指定的文件大小,以字节为单位
     * 注意:
     *       文件夹是没有大小概念的,不能获取文件夹的大小
     *       如果构造方法中给出的路径不存在,那么length方法返回0
     * */
    private static void show04() {
        File f1 = new File("/Users/xp/Downloads/IMG_0659.HEIC");
        long l1 = f1.length();
        System.out.println(l1);//2679652

        File f2 = new File("/Users/xp/Downloads");
        System.out.println(f2.length());//0

        File f3 = new File("/Users/xp");
        System.out.println(f3.length());//4096  文件夹没有大小概念的?
    }

    /*
     * public String getName(): 返回由此File表示的文件或目录的名称
     * 获取的就是构造方法传递路径的结尾部分(文件/文件夹)
     *  */
    private static void show03() {
        File f1 = new File("D://IdeaProjects//basic-code//a.txt");//Mac 是// windows是\\
        String name1 = f1.getName();
        System.out.println(name1);//a.txt

        File f2 = new File("D://IdeaProjects//basic-code");
        String name2 = f2.getName();
        System.out.println(name2);//basic-code
    }

    /*
     * public String getPath():将此File转换为路径名字符串
     * 获取构造方法中传递的路径
     *
     * toString方法调用的就是getPath方法
     * 源码:
     *      public String toString(){
     *          return getPath();
     *      }
     * */
    private static void show02() {
        File f1 = new File("D:\\IdeaProjects\\basic-code\\a.txt");
        File f2 = new File("a.txt");
        String path1 = f1.getPath();
        System.out.println(path1);//D:\IdeaProjects\basic-code\a.txt
        String path2 = f2.getPath();
        System.out.println(path2);//a.txt

        System.out.println(f1);//D:\IdeaProjects\basic-code\a.txt
        System.out.println(f1.toString());//D:\IdeaProjects\basic-code\a.txt
    }

    /*
     * public String getAbsolutePath():返回次File的绝对路径名字符串
     * 获取的构造方法中传递的路径
     * 无论路径是绝对的还是相对的,getAbsolutePath方法返回的都是绝对路径
     * */
    private static void show01() {
        File f1 = new File("D:\\IdeaProjects\\basic-code\\a.txt");
        String absolutePath1 = f1.getAbsolutePath();
        System.out.println(absolutePath1);//D:\IdeaProjects\basic-code\a.txt

        File f2 = new File("a.txt");
        String absolutePath2 = f2.getAbsolutePath();
        System.out.println(absolutePath2);//D:\IdeaProjects\basic-code\day03-code\a.txt
    }
}

File判断功能的方法

import java.io.File;

/*
 * File判断功能的方法
 *       public boolean exists(): 此File表示的文件或目录是否真实存在
 *       public boolean isDirectory(): 此File表示的是否为目录
 *       public boolean isFile(): 此File表示的是否为文件
 * */
public class Demo04File {
    public static void main(String[] args) {
        show02();
    }

    /*
    *  public boolean isDirectory(): 此File表示的是否为目录
    *       用于判断构造方法中给定的路径是以文件夹结尾
    *           是:true
    *           否:false
    *  public boolean isFile(): 此File表示的是否为文件
    *       用于判断构造方法中给定的路径是以文件结尾
     *           是:true
     *           否:false
     * 注意:
     *      电脑的硬盘中的只有文件/文件夹,两个方法互斥
     *      这两个方法的使用前提,路径必须是存在的,否则都返回false
    * */
    private static void show02() {
        File f1 = new File("/Users/xp/Downloads/i4ToolsDownloads/Wallpaper//11");
        //不存在,就没必要获取
        if(f1.exists()){
            System.out.println(f1.isDirectory());//false
            System.out.println(f1.isFile());//false
        }

        File f2 = new File("/Users/xp/Downloads/i4ToolsDownloads/Wallpaper");
        if(f2.exists()){
            System.out.println(f2.isDirectory());//true
            System.out.println(f2.isFile());//false
        }

        File f3 = new File("/Users/xp/Downloads/i4ToolsDownloads/Wallpaper/爱思壁纸_329034.jpg");
        if(f2.exists()){
            System.out.println(f3.isDirectory());//false
            System.out.println(f3.isFile());//true
        }
    }

    /*
     * public boolean exists(): 此File表示的文件或目录是否真实存在
     * 用于判断构造方法中的路径是否存在
     *       存在:true
     *       不存在:false
     * */
    private static void show01() {
        File f1 = new File("/Users/xp/Downloads/i4ToolsDownloads/Wallpaper");
        System.out.println(f1.exists());//true

        File f2 = new File("C:\\Users\\Xpzt\\Pictures\\Screen");
        System.out.println(f2.exists());//false

        File f3 = new File("爱思壁纸_329034.jpg");//相对路径  C:\Users\Xpzt\Pictures\Screenshots
        System.out.println(f3.exists());//true

        File f4=new File("2.png");
        System.out.println(f4.exists());//false
    }
}

File类创建删除功能的方法

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

/*
 * File类创建删除功能的方法
 *       public boolean creatNewFile():当且仅当具有该名称文件尚不存在时,创建一个新的空文件夹
 *       public boolean delete():删除由此File表示的文件或目录
 *       public boolean mkdir():创建此File表示的目录
 *       public boolean mkdirs():创建此File表示的目录,包或任何必须但不存在的父目录
 * */
public class Demo05File {
    public static void main(String[] args) throws IOException {
        show03();
    }

    /*
     * public boolean delete():删除由此File表示的文件或目录
     * 此方法,可以删除构造方法路径中给出的文件/文件夹
     * 返回值:布尔值
     *       true:文件/文件夹删除成功,返回true
     *       false:文件夹中有内容,不会删除 返回false;构造方法中路径不存在 false
     * 注意:
     *       delete方法是直接在硬盘删除文件/文件夹,不走回收站,删除要谨慎
     * */
    private static void show03() {
        File f1 = new File("/Users/xp/Downloads/a");
        boolean b1 = f1.delete();
        System.out.println("b1:" + b1);

        File f2 = new File("/Users/xp/Downloads/11/22/33");
        System.out.println(f2.delete());

        File f3 = new File("/Users/xp/Downloads/关于大纲的解释.jpg");
        System.out.println(f3.delete());

        File f4 = new File("D:\\IdeaProjects\\basic-code\\abc.txt");
        System.out.println(f4.delete());
    }

    /*
     * public boolean mkdir():创建单级空文件夹
     * public boolean mkdirs():既可以创建单级空文件夹,也可以创建多级文件夹
     * 创建文件夹的路径和名称在构造方法中给出(构造方法的参数)
     * 返回值:布尔值
     *       true:文件夹不存在,创建文件夹,返回true
     *       false:文件夹存在,不会创建,返回false;构造方法红给出对路径不存在返回false
     * 注意:
     *       此方法只能创建文件夹,不能创建文件
     * */
    private static void show02() {
        File f1 = new File("/Users/xp/Downloads/a");
        boolean b1 = f1.mkdir();
        System.out.println("b1:" + b1);

        File f2 = new File("/Users/xp/Downloads/11/22/33");
        boolean b2 = f2.mkdirs();
        System.out.println("b2:" + b2);

        File f3 = new File("/Users/xp/Downloads/abc.txt");
        boolean b3 = f3.mkdirs();//看类型是一个文件
        System.out.println("b3:" + b3);

        File f4 = new File("D:\\IdeaProjects\\basic-co\\abc");
        boolean b4 = f4.mkdir();//不会抛出异常,路径不存在,不会创建
        System.out.println("b4:" + b4);

    }

    /*
     * public boolean creatNewFile():当且仅当具有该名称文件尚不存在时,创建一个新的空文件夹
     * 创建文件的路径和名称在构造方法中给出(构造方法的参数)
     * 返回值:布尔值
     *       true:文件不存在
     *       false:文件存在
     * 注意:
     *       1.此方法只能创建文件,不能创建文件夹
     *       2.创建文件的路径必须存在,否则会抛出异常
     * public boolean creatNewFile() throws IOException
     * creatNewFile声明抛出了IOException,我们调用这个方法,就必须的处理这个异常,要么throws,要么try ... catch
     * */
    private static void show01() throws IOException {
        File f1 = new File("/Users/xp/Downloads/1.txt");
        boolean b1 = f1.createNewFile();
        System.out.println(b1);

        File f2 = new File("Downloads/b.txt");
        System.out.println(f2.createNewFile());

        File f3 = new File("Downloads/新建文件夹");
        System.out.println(f3.createNewFile());//不要被名称迷糊,要看类型

        File f4 = new File("Xpz\\c.txt");
        System.out.println(f2.createNewFile());//路径不存在,抛出IOException异常
    }
}

File类遍历(文件夹)目录功能

import java.io.File;

/*
 * File类遍历(文件夹)目录功能
 *       public String[] list():返回一个String数组,表示该File目录中的所有子文件或目录
 *       public File[] listFiles():返回一个File数组,表示该File目录中的所有子文件或目录
 *
 * 注意:
 *       list方法和listFiles方法遍历的是构造方法给出的目录
 *       如果构造方法中给出的目录的路径不存在,会抛出空指针异常
 *       如果构造方法中给出的路径不是一个目录,也会抛出空指针异常
 * */
public class Demo06File {
    public static void main(String[] args) {
        show02();
    }

    /*
     * public File[] listFiles():返回一个File数组,表示该File目录中的所有子文件或目录
     * 遍历构造方法中给出的目录,会获取目录中所有文件/文件夹的名称,把文件夹/文件封装为File对象,多个File对象存储到File数组中
     * */
    private static void show02() {
        File f1 = new File("/Users/xp/Downloads");
        File[] files = f1.listFiles();
        for (File file : files) {
            System.out.println(file);
        }
    }

    /*
     * public String[] list():返回一个String数组,表示该File目录中的所有子文件或目录
     * 遍历构造方法中给出的目录,会获取目录中所有文件/文件夹的名称,把获取到的多个名称存储到一个String类型的数组中
     * */
    private static void show01() {
        //File f1 = new File("D:\\IdeaProjects\\basic-code\\a.txt");//NullPointerException
        //File f1 = new File("D:\\IdeaProjects\\basic-co");//NullPointerException
        File f1 = new File("/Users/xp/Downloads");
        String[] fileName = f1.list();//可以获取到隐藏文件
        for (String s : fileName) {
            System.out.println(s);
        }

    }

}

递归:方法自己调用自己

  • 递归的分类:
    • 递归分为两种:直接递归和间接递归
      • 直接递归称为方法自身调用自己
      • 间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法
  • 注意事项:
    • 递归一定要有条件限制,保证递归能够停止下来,否则会发证栈内存溢出
    • 在递归中虽然有限定条件,但是递归次数不能太多,否则也会发生栈内存溢出
    • 构造方法,禁止递归
  • 递归的使用前提:当调用方法的时候,方法的主体不变,每次调用方法的参数不同,可以使用递归
/*
 * 递归:方法自己调用自己
 *   递归的分类:
 *       递归分为两种:直接递归和间接递归
 *           直接递归称为方法自身调用自己
 *           间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法
 *   注意事项:
 *       递归一定要有条件限制,保证递归能够停止下来,否则会发证栈内存溢出
 *       在递归中虽然有限定条件,但是递归次数不能太多,否则也会发生栈内存溢出
 *       构造方法,禁止递归
 *   递归的使用前提:
 *       当调用方法的时候,方法的主体不变,每次调用方法的参数不同,可以使用递归
 * */
public class Demo01Recursion {
    public static void main(String[] args) {
//        a();
        b(1);
    }

    /*
     * 构造方法,禁止递归
     *       编译报错:构造方法是创建对象使用的,一直递归会导致内存中有无数个对象,直接编译报错
     * */
    public Demo01Recursion() {
        //Demo01Recursion();
    }

    /*
     * 在递归中虽然有限定条件,但是递归次数不能太多,否则也会发生栈内存溢出
     * 10617
     *      Exception in thread "main" java.lang.StackOverflowError
     * */
    private static void b(int i) {
        System.out.println(i);
        if (i == 20000) {
            return;//结束方法
        }
        b(++i);
    }

    /*
     * 递归一定要有条件限制,保证递归能够停止下来,否则会发证栈内存溢出
     * Exception in thread "main" java.lang.StackOverflowError
     *
     * a方法会在栈内存中一直调用a方法,就会导致栈内存中有无数个a方法,方法太多了,超出栈内存的大小,就会导致内存溢出的错误
     *
     * 注意:
     *       当一个方法调用其他方法时,被调用的方法没有执行完毕,当前方法就会一直等待调用的方法执行完毕,才会继续执行
     * */
    private static void a() {
        System.out.println("a方法!");
        a();
    }
}

  • 使用递归计算1-n的和
/*
 * 练习:
 *       使用递归计算1-n的和
 * */
public class Demo02Recursion {
    public static void main(String[] args) {
        int s = sum(3);
        System.out.println(s);
    }

    /*
     * 定义一个方法,使用递归计算1-n之间的和
     * 1+2+3+4+...+n
     * n+(n-1)+(n-2)+...+1
     * 已知:
     *       最大值:n
     *       最小值:1
     * 使用递归必须明确:
     *       1.递归的结束条件
     *           获取到1的时候结束
     *       2.递归的目的
     *           获取下一个被加的数字(n-1)
     * */
    public static int sum(int n) {
        //获取到1的时候结束
        if (n == 1) {
            return 1;
        }
        //获取下一个被加的数字
        return n + sum(n - 1);
    }
}

  • 使用递归计算阶乘
/*
* 练习:
*       使用递归计算阶乘
*       n的阶乘: n! =n * (n-1) *...* 1
* */
public class Demo03Recursion {
    public static void main(String[] args) {
        int jiecheng = jc(5);
        System.out.println(jiecheng);
    }

    /*
    * 定义方法使用递归计算阶乘
    * 5的阶乘:5! = 5 * 4 * 3 * 2 * 1
    * 递归结束的条件
    *       获取到1的时候结束
    * 递归的目的
    *       获取下一个被乘的数字
    * 方法的参数变化
    *       5,4,3,2,1
    * */

    public static int jc(int n){
        //获取到1的时候结束
        if(n==1){
            return 1;
        }
        //获取下一个被乘的数字(n-1)
        return n*jc(n-1);
    }
}

  • 递归打印多级目录
import java.io.File;

/*
 * 练习:
 *   递归打印多级目录
 * 需求:
 *   遍历C:\abc文件夹,及abc文件夹的子文件夹
 *   C:\abc
 *   C:\abc\abc.txt
 *   C:\abc\abc.java
 *   C:\abc\a
 *   C:\abc\a\a.jpg
 *   C:\abc\a\a.java
 *   C:\abc\b
 *   C:\abc\b\b.txt
 *   C:\abc\b\b.java
 * */
public class Demo04Recursion {
    public static void main(String[] args) {
        File file = new File("/Users/xp/Downloads");
        getAllFile(file);
    }

    /*
     * 定义一个方法,参数传递File类型的目录
     * 方法中对目录进行遍历
     * */
    public static void getAllFile(File dir) {
        //打印被遍历的目录名称
        System.out.println(dir);
        File[] files = dir.listFiles();
        for (File f : files) {
            //对遍历得到的File对象f进行判断,判断是否文件夹
            if (f.isDirectory()) {
                //f是一个文件夹,则继续遍历这个文件夹
                //我们发现getAllFile方法就是传递文件夹,遍历文件夹的方法
                //所以我们直接调用getAllFile方法即可:递归(自己调用自己)
                getAllFile(f);
            } else {
                //f是一个文件直接打印
                System.out.println(f);
            }
        }
    }
}

import java.io.File;

/*
 * 练习:
 *   递归打印多级目录
 * 需求:
 *   遍历C:\abc文件夹,及abc文件夹的子文件夹
 *
 *   只要.java结尾的文件
 *
 *   C:\abc
 *   C:\abc\abc.txt
 *   C:\abc\abc.java
 *   C:\abc\a
 *   C:\abc\a\a.jpg
 *   C:\abc\a\a.java
 *   C:\abc\b
 *   C:\abc\b\b.txt
 *   C:\abc\b\b.java
 * */
public class Demo05Recursion {
    public static void main(String[] args) {
        File file = new File("/Users/xp/Downloads");
        getAllFile(file);
    }

    /*
     * 定义一个方法,参数传递File类型的目录
     * 方法中对目录进行遍历
     * */
    public static void getAllFile(File dir) {
        //System.out.println(dir); //打印被遍历的目录名称
        File[] files = dir.listFiles();
        for (File f : files) {
            //对遍历得到的File对象f进行判断,判断是否文件夹
            if (f.isDirectory()) {
                //f是一个文件夹,则继续遍历这个文件夹
                //我们发现getAllFile方法就是传递文件夹,遍历文件夹的方法
                //所以我们直接调用getAllFile方法即可:递归(自己调用自己)
                getAllFile(f);
            } else {
                //f是一个文件直接打印

                /*
                 * C:\abc\abc.java
                 * 只要.java结尾的文件
                 * 1.把File对象f,转换为字符串对象  abc.java
                 * */

                //String name = f.getName();//abc.java
                //String path = f.getPath();//C:\abc\abc.java
                /*String s = f.toString();//C:\abc\abc.java

                //把字符串转换为小写
                String s1 = s.toLowerCase();

                //2.调用String类中的方法endsWith法判断字符串是否以.java结尾
                boolean b = s1.endsWith(".java");

                //3.如果是以.java结尾的文件,则输出
                if (b) {
                    System.out.println(f);
                }*/
                //链式编程
                if(f.toString().toLowerCase().endsWith(".java")){
                    System.out.println(f);
                }
            }
        }
    }
}

过滤器FileFilter类及其方法

import java.io.File;
import java.io.FileFilter;

/*
 * 创建过滤器FileFilter的实现类,重写过滤方法accept,定义过滤规则
 * */
public class FileFilterImpl implements FileFilter {
    @Override
    public boolean accept(File pathname) {
        /*
        * 过滤的规则
        * 在accept方法中,判断File对象是否以.java结尾
        * 是就返回true
        * 否就返回false
        * */

        //如果pathname是一个文件夹,也返回true,继续遍历这个文件夹
        if(pathname.isDirectory()){
            return true;
        }
        return pathname.toString().toLowerCase().endsWith(".java");
    }
}

import java.io.File;

/*
 * 练习:
 *   递归打印多级目录
 * 需求:
 *   只要.java结尾的文件
 *   C:\abc
 *   C:\abc\abc.txt
 *   C:\abc\abc.java
 *   C:\abc\a
 *   C:\abc\a\a.jpg
 *   C:\abc\a\a.java
 *   C:\abc\b
 *   C:\abc\b\b.txt
 *   C:\abc\b\b.java
 *
 * 我们可以使用过滤器来实现
 * 在File类中有两个和listFiles重载的方法,方法的参数传递过去的是过滤器
 * File[] listFiles(FileFilter filter)
 * java.io.FileFilter接口:用于抽象路径名(File类对象)的过滤器
 *      作用:用来过滤文件的方法
 *      抽象方法:用来过滤文件的方法
 *          boolean accept(File pathname) 此时指定抽象路径名是否应该包含在某个路径名列表中
 *          参数:
 *              File pathname:使用listFiles方法遍历目录,得到的每一个文件对象
 *
 * File[] listFiles(FilenameFilter filter)
 * java.io.FilenameFilter接口:实现此接口的类实例可用于过滤器文件名
 *      作用:用来过滤文件名称
 *      抽象方法:用来过滤文件的方法
 *          boolean accept(File dir,String name ) 测试指定文件是否应该包含在某一文件列表中
 *          参数:
 *              File dir:构造方法中传递的被遍历的目录
 *              String name:使用listFiles方法遍历目录,获取每一个文件/文件夹的名称
 * 注意:
 *      两个过滤器接口是没有实现类的,需要我们自己写实现类,重写过滤的方法accept,在方法中自己定义过滤的规则
 * */
public class Demo01Filter {
    public static void main(String[] args) {
        File file = new File("/Users/xp/Downloads");
        getAllFile(file);
    }

    /*
     * 定义一个方法,参数传递File类型的目录
     * 方法中对目录进行遍历
     * */
    public static void getAllFile(File dir) {
        /*//打印被遍历的目录名称
        System.out.println(dir);*/
        File[] files = dir.listFiles(new FileFilterImpl());//传递过滤器对象
        for (File f : files) {
            //对遍历得到的File对象f进行判断,判断是否文件夹
            if (f.isDirectory()) {
                //f是一个文件夹,则继续遍历这个文件夹
                //我们发现getAllFile方法就是传递文件夹,遍历文件夹的方法
                //所以我们直接调用getAllFile方法即可:递归(自己调用自己)
                getAllFile(f);
            } else {
                //f是一个文件直接打印
                System.out.println(f);
            }
        }
    }
}import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;

/*
 * 练习:
 *   递归打印多级目录
 * 需求:
 *   只要.java结尾的文件
 *   C:\abc
 *   C:\abc\abc.txt
 *   C:\abc\abc.java
 *   C:\abc\a
 *   C:\abc\a\a.jpg
 *   C:\abc\a\a.java
 *   C:\abc\b
 *   C:\abc\b\b.txt
 *   C:\abc\b\b.java
 *
 * 我们可以使用过滤器来实现
 * 在File类中有两个和listFiles重载的方法,方法的参数传递过去的是过滤器
 * File[] listFiles(FileFilter filter)
 * java.io.FileFilter接口:用于抽象路径名(File类对象)的过滤器
 *      作用:用来过滤文件的方法
 *      抽象方法:用来过滤文件的方法
 *          boolean accept(File pathname) 此时指定抽象路径名是否应该包含在某个路径名列表中
 *          参数:
 *              File pathname:使用listFiles方法遍历目录,得到的每一个文件对象
 *
 * File[] listFiles(FilenameFilter filter)
 * java.io.FilenameFilter接口:实现此接口的类实例可用于过滤器文件名
 *      作用:用来过滤文件名称
 *      抽象方法:用来过滤文件的方法
 *          boolean accept(File dir,String name ) 测试指定文件是否应该包含在某一文件列表中
 *          参数:
 *              File dir:构造方法中传递的被遍历的目录
 *              String name:使用listFiles方法遍历目录,获取每一个文件/文件夹的名称
 * 注意:
 *      两个过滤器接口是没有实现类的,需要我们自己写实现类,重写过滤的方法accept,在方法中自己定义过滤的规则
 * */
public class Demo02Filter {
    public static void main(String[] args) {
        File file = new File("/Users/xp/Downloads");
        getAllFile(file);
    }

    /*
     * 定义一个方法,参数传递File类型的目录
     * 方法中对目录进行遍历
     * */
    public static void getAllFile(File dir) {
        /*//打印被遍历的目录名称
        System.out.println(dir);*/
        //使用过滤器对象 使用匿名内部类
        /*File[] files = dir.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                //过滤规则,pathname是文件夹或者是.java结尾的文件返回true
                return pathname.isDirectory() || pathname.toString().toLowerCase().endsWith(".java");
            }
        });*/

        //使用Lambda表达式优化匿名内部类(接口中只有一个抽象方法)
        //File[] files = dir.listFiles(pathname -> pathname.isDirectory() || pathname.toString().toLowerCase().endsWith(".java"));

       /* File[] files = dir.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                //过滤规则,pathname是文件夹或者是.java结尾的文件返回true
                return new File(dir, name).isDirectory() || name.toLowerCase().endsWith(".java");
            }
        });*/

        //使用Lambda表达式优化匿名内部类(接口中只有一个抽象方法)
        File[] files = dir.listFiles((d, name) -> new File(d, name).isDirectory() || name.toLowerCase().endsWith(".java"));
        for (File f : files) {
            //对遍历得到的File对象f进行判断,判断是否文件夹
            if (f.isDirectory()) {
                //f是一个文件夹,则继续遍历这个文件夹
                //我们发现getAllFile方法就是传递文件夹,遍历文件夹的方法
                //所以我们直接调用getAllFile方法即可:递归(自己调用自己)
                getAllFile(f);
            } else {
                //f是一个文件直接打印
                System.out.println(f);
            }
        }
    }
}


OutputStream:字节输出流

  • 此抽象类是表示输出字节流的所有类的超类

FileOutputStream:文件字节输出流

  • 作用:把内存中的数据写入到硬盘的文件中
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/*
 * java.io.OutputStream:字节输出流
 *       此抽象类是表示输出字节流的所有类的超类
 *
 * 定义了一些子类共性的成员方法:
 *       public void close(): 关闭此输出流并释放与此流相关的任何系统资源
 *       public void flush(): 刷新此输出流并强制任何缓冲的输出字节被写出
 *       public void write(byte[] b):将b.length字节从指定的字节数组写入此输出流
 *       public void write(byte[] b,int off,int len):从指定的字节数组写入len字节,从偏移量 off开始输出到此输出流
 *       public abstract void write(int b):将指定的字节输出流
 *
 * java.io.FileOutputStream extends OutputStream
 * FileOutputStream:文件字节输出流
 * 作用:把内存中的数据写入到硬盘的文件中
 *
 * 构造方法:
 *      FileOutputStream(String name)创建一个向具有指定名称的文件中写入数据的输出文件流
 *      FileOutputStream(File file)创建一个向指定File对象表示的文件中写入数据的文件输出流
 *      参数:写入数据的目的
 *          String name:目的地是一个文件的路径
 *          File file:目的地是一个文件
 *      构造方法的作用:
 *          1.创建一个FileOutputStream对象
 *          2.会根据构造方法中传递的文件/文件路径,创建一个空的文件
 *          3.会把FileOutputStream对象指向创建好的文件
 *
 * 写入数据的原理:(内存-->硬盘)
 *      java程序-->JVM(java虚拟机)-->OS(操作系统)-->OS调用写数据的方法-->把数据写入到文件中
 *
 * 字节输出流的使用步骤:
 *      1.创建一个FileOutputStream对象,构造方法中传递写入数据的目的地
 *      2.调用FileOutputStream对象中的方法write,把数据写入到内存中
 *      3.释放资源(流使用会占用一定的内存,使用完毕要把内存清空,提高程序效率)
 *
 * 任意文本编辑器(记事本,notepad++,...)
 *      在打开文件的时候,都会查询编码表,把字节转换为字符表示
 * 0-127:查询ASCII码表
 *      97-->a
 * 其他值:查询系统默认码表(中文系统GBK)
 * */
public class Demo01OutputStream {
    public static void main(String[] args) throws IOException {
        //先创建一个文件
        File f = new File("/Users/xp/Downloads/a");
        f.createNewFile() ;
        // 1.创建一个FileOutputStream对象,构造方法中传递写入数据的目的地
        FileOutputStream fos = new FileOutputStream("/Users/xp/Downloads/a");
        //2.调用FileOutputStream对象中的方法write,把数据写入到内存中
        //public abstract void write(int b):将指定的字节输出刘
        fos.write(97);
        //3.释放资源(流使用会占用一定的内存,使用完毕要把内存清空,提高程序效率)
        fos.close();
    }
}

  • 一次写多个字节的方法
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;

/*
 * 一次写多个字节的方法
 *       public void write(byte[] b):将b.length字节从指定的字节数组写入此输出流
 *       public void write(byte[] b,int off, int len):从指定的字节数组写入len字节,从偏移量off开始输出到此输出流
 * */
public class Demo02OutputStream {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("/Users/xp/Downloads/a");
        /*
         * public void write(byte[] b):将b.length字节从指定的字节数组写入此输出流
         * 一次写多个字节
         *      如果写的第一个字节是正数(0-127),那么显示的时候会查询ASCII
         *      如果写的第一个字节是负数,那么第一个字节和第二个字节,两个字节组成一个中文显示,查询系统默认码表(GBK)
         * */
        byte[] bytes = {65, 66, 67, 68, 69};//ABCDE
        //byte[] bytes = {-65, -66, -67, 68, 69};//烤紻E
        fos.write(bytes);

        /*
         * public void write(byte[] b,int off, int len):把字节数组的一部分写到文件中
         *      int off:数组的开始索引
         *      int len:写几个字节
         * */

        fos.write(bytes, 1, 2);//BC

        /*
        * 写入字符的方法:可以使用String类中的方法,把字符串转换为字节数组
        *       byte[] getBytes():把字符串转换为字节数组
        * */

        byte[] bytes1 = "你好".getBytes();//[-28, -67, -96, -27, -91, -67]
        System.out.println(Arrays.toString(bytes1));
        fos.write(bytes1);

        //释放资源
        fos.close();

    }
}

  • 追加写/续写:使用两个参数的构造方法
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

/*
 * 追加写/续写:使用两个参数的构造方法
 *       FileOutputStream(String name,boolean append) 创建一个向具有指定name的文件中写入数据的输出文件流
 *       FileOutputStream(File file,boolean append) 创建一个向指定File对象表示的文件中写入数据的文件输出流
 *       参数:
 *           String name,File file:写入数据的目的地
 *           boolean append:追加写开关
 *               true:创建对象不会覆盖源文件,继续在文件的末尾追加写数据
 *               false:创建一个新文件,覆盖源文件
 *
 * 写换行:写换行符号
 *      windows:\r\n
 *      linux:/n
 *      mac:\r
 * */
public class Demo03OutputStream {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("/Users/xp/Downloads/a", true);
        for (int i = 1; i <= 10; i++) {
            fos.write("你好".getBytes());
            //换行
            fos.write("\r".getBytes());
        }
        fos.close();
    }
}

InputStream:字节输入流

FileInputStream:文件字节输入流

  • 作用:把硬盘的文件中的数据,读取到内存中使用
import java.io.FileInputStream;
import java.io.IOException;

/*
 * java.io.InputStream:字节输入流
 * 此抽象类是表示字节输入流的所有类的超类
 *
 * 定义了所有子类共性的方法:
 *       abstract int read() 从输入流中读取数据的下一个字节
 *       int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中
 *       void close(): 关闭此输入流与该流关联的所有系统资源
 *
 * java.io.FileInputStream extends InputStream
 * FileInputStream:文件字节输入流
 * 作用:把硬盘的文件中的数据,读取到内存中使用
 *
 * 构造方法:
 *       FileInputStream(String name)
 *       FileInputStream(File file)
 *       参数:读取文件的数据源
 *           String name :文件的路径
 *           File file :文件
 *       构造方法的作用:
 *           1.会创建一个FileInputStream对象
 *           2.会把FileInputStream对象指向构造方法中要读取的文件
 *
 * 读取数据的原理:(硬盘-->内存)
 *       java程序-->JVM-->OS-->OS读取数据的方法-->读取文件
 *
 * 字节输入流的使用步骤(重点):
 *       1.创建FileInputStream对象,构造方法中绑定要读取的数据源
 *       2.使用FileInputStream对象中的方法read,读取文件
 *       3.释放资源
 * */
public class Demo01InputStream {
    public static void main(String[] args) throws IOException {
        //1.创建FileInputStream对象,构造方法中绑定要读取的数据源
        FileInputStream fis = new FileInputStream("/Users/xp/Downloads/a");
        //2.使用FileInputStream对象中的方法read,读取文件
        //abstract int read() 读取文件中的一个字节并返回,读取到文件的末尾返回-1
        /*
        //一次读取一个字节,读完一个字节,指针自动向后移一位(和C语言的fgetc(pf)差不多)
        int len = fis.read();
        System.out.println(len);//97  a
        len = fis.read();
        System.out.println(len);//98  b
        len = fis.read();
        System.out.println(len);//99  c
        len = fis.read();
        System.out.println(len);//-1 文件末尾*/

        /*
         * 发现以上读取文件是一个重复过程,所以可以使用循环优化
         * 不知道文件中有多少个字节,使用while循环
         * while循环结束条件,读取到-1的时候结束
         *
         * 布尔表达式:(len = fis.read()) != -1
         *      1.fis.read():读取一个字节
         *      2.len = fis.read():把读取到的字节赋值给变量len
         *      3.(len = fis.read()) != -1:判断变量len是否不等于-1
         * */
        int len = 0;//记录读取到的字节
        while ((len = fis.read()) != -1) {
            System.out.print((char)len);//abc
        }
        //3.释放资源
        fis.close();
    }

}

  • 字节输入流一次读取多个字节的方法
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;

/*
 * 字节输入流一次读取多个字节的方法:
 *       int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中
 * 明确两件事:
 *       1.方法的参数byte[]的作用?
 *          起到缓冲作用,存储每次读到的字节
 *          数组的长度一般定义为1024(1kb)或者1024的整数倍
 *       2.方法的返回值int是什么?
 *          每次读取的有效字节个数
 *
 * String类的构造方法
 *      String (byte[] bytes): 把字节数组转换为字符串
 *      String (byte[] bytes,int offset,int length) 把字节数组的一部分转换为字符串 offset:数组的开始索引 length:转换的字节个数
 * */
public class Demo02InputStream {
    public static void main(String[] args) throws IOException {
        //1.创建FileInputStream对象,构造方法中绑定要读取的数据源
        FileInputStream fis = new FileInputStream("/Users/xp/Downloads/a");
        //使用FileInputStream对象中的方法read读取文件
        //int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中
        /*byte[] bytes = new byte[2];
        int len = fis.read(bytes);//读取有效的字节个数
        System.out.println(Arrays.toString(bytes));//[65, 66]
        System.out.println(len);//2
        System.out.println(new String(bytes));//AB

        len = fis.read(bytes);//读取有效的字节个数
        System.out.println(len);//2
        System.out.println(new String(bytes));//CD

        len = fis.read(bytes);//读取有效的字节个数
        System.out.println(len);//1
        System.out.println(new String(bytes));//ED

        len = fis.read(bytes);//读取有效的字节个数
        System.out.println(len);//-1
        System.out.println(new String(bytes));//ED*/

        /*
         * 发现以上读取是一个重复的过程,可以使用循环优化
         * 不知道文件有多少字节,所以用while循环
         * while循环结束的条件,读取到-1结束
         * */

        byte[] bytes = new byte[1024];
        int len = 0;//记录每次读取有效字节的个数
        while ((len = fis.read(bytes)) != -1) {
            //String (byte[] bytes,int offset,int length) 把字节数组的一部分转换为字符串 offset:数组的开始索引 length:转换的字节个数
            System.out.println(new String(bytes, 0, len));
            System.out.println(len);
        }

        fis.close();
        //释放资源

    }
}

  • 文件复制练习:一读一写
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/*
 * 文件复制练习:一读一写
 *
 * 明确:
 *       数据源:C:\\1.jpg
 *       数据目的地:d:\\1.jpg
 *
 * 文件复制的步骤:
 *       1.创建一个字节输入流对象,构造方法中绑定要读取的数据源
 *       2.创建一个字节输出流对象,构造方法中绑定要写入的数据源
 *       3.使用字节输入流对象中的方法read文件对象
 *       4.使用字节输出流中的方法write,把读取到的字节写入到目的地的文件中
 *       5.释放资源
 * */
public class Demo01CopyFile {
    public static void main(String[] args) throws IOException {
        long s = System.currentTimeMillis();
        //1.创建一个字节输入流对象,构造方法中绑定要读取的数据源
        FileInputStream fis = new FileInputStream("/Users/xp/Downloads/a");
        //2.创建一个字节输出流对象,构造方法中绑定要写入的数据源
        FileOutputStream fos = new FileOutputStream("/Users/xp/Downloads/b");
        //一次读取一个字节写入一个字节的方式
        //3.使用字节输入流对象中的方法read文件对象
        /*int len=0;
        while((len=fis.read())!=-1){
            //4.使用字节输出流中的方法write,把读取到的字节写入到目的地的文件中
            fos.write(len);
        }
        //5.释放资源(先关写的,后关读的,如果写完了,肯定读完了)
        fis.close();
        fos.close();*/

        //使用数组缓冲读取多个字节,写入多个字节
        byte[] bytes = new byte[1024];
        int len = 0;//每次读取的有效字节个数
        while ((len = fis.read(bytes)) != -1) {
            //4.使用字节输出流中的方法write,把读取到的字节写入到目的地的文件中
            fos.write(bytes, 0, len);
        }
        //5.释放资源(先关写的,后关读的,如果写完了,肯定读完了)
        fis.close();
        fos.close();
        long e = System.currentTimeMillis();
        System.out.println("复制所需时间为:" + (e - s) + "毫秒");
    }

}

Reader:字符输入流

  • 是字符输入流的最顶层的父类,定义了一些共性的成员方法,是一个抽象类

FileReader:文件字符输入流

  • 作用:把硬盘文件中的数据以字符的方式读取到内存中
import java.io.FileReader;
import java.io.IOException;

/*
 * java.io.Reader:字符输入流,是字符输入流的最顶层的父类,定义了一些共性的成员方法,是一个抽象类
 *
 * 共性的成员方法:
 *       int read() 读取单个字符并返回
 *       int read(char[] cbuf) 一次读取多个字符,将字符读入数组
 *       void close() 关闭该留并释放与之关联的所有资源
 *
 * java.io.FileReader extends InputStreamReader extends Reader
 * FileReader:文件字符输入流
 * 作用:把硬盘文件中的数据以字符的方式读取到内存中
 *
 * 构造方法:
 *       FileReader(String filename)
 *       FileReader(File file)
 *       参数:读取文件的数据源
 *           String filename:文件的路径
 *           File file:一个文件
 *       FileRead构造方法的作用:
 *           1.会创建一个FileReader对象
 *           2.会把FileReader对象指向要读取到文件
 *
 * 字符输入流的使用步骤:
 *       1.创建FileReader对象,构造方法中绑定要读取的数据源
 *       2.使用FileReader对象中的方法read读取文件
 *       3.释放资源
 *
 * */
public class Demo01Reader {
    public static void main(String[] args) throws IOException {
        //1.创建FileReader对象,构造方法中绑定要读取的数据源
        FileReader fr = new FileReader("/Users/xp/Downloads/a");
        //2.使用FileReader对象中的方法read读取文件
        //int read() 读取单个字符返回
        /*int len = 0;
        while((len = fr.read()) != -1) {
            System.out.print((char)len);
        }*/

        //int read(char[] cbuf) 一次读取多个字符,将字符读入数组
        char[] cs = new char[1024];//存储到读取到的多个字符
        int len = 0;//记录的是每次读取到的有效字符的个数
        while ((len = fr.read(cs)) != -1) {
            /*
             * String类的构造方法
             * String (char[] value) 把字符数组转换为字符串
             * String (char[] value,int offset,int count ):把字符数组的一部分转换为字符串,offset数组的开始索引,count 转换的个数
             * */
            System.out.println(new String(cs, 0, len));
        }
        //3.释放资源
        fr.close();
    }

}

Writer:字符输出流

  • 是所有字符输出流的最顶层的父类,是一个抽象类

FileWriter:文件字符输出流

  • 作用:把内存中的字符数据写入到文件中
import java.io.FileWriter;
import java.io.IOException;

/*
 * java.io.Writer:字符输出流,是所有字符输出流的最顶层的父类,是一个抽象类
 *
 * 共性的成员方法:
 *       void write(int c) 写入单个字符
 *       void write(char[] cbuf) 写入字符数组
 *       abstract void write(char[] cbuf,int off, int len) 写入字符数组的某一部分,off数组的开始索引,len写的字符个数
 *       void write(String str) 写入字符串
 *       void write(String str,int off,int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数
 *       void flush() 刷新该流的缓冲
 *       void close()关闭此流,但要先刷新它
 *
 * java.io.FileWriter extends OutputSteamWriter extends Writer
 * FileWriter:文件字符输出流
 * 作用:把内存中的字符数据写入到文件中
 *
 * 构造方法:
 *      FileWriter(File file) 根据给定的File对象构造一个FileWriter对象
 *      FileWriter(String filename) 根据给定的文件名构造一个FileWriter对象
 *      参数:写入数据的目的地
 *          String filename:文件的路径
 *          File file:是一个文件
 *      构造方法的作用:
 *      1.会创建一个FileWriter对象
 *      2.会根据构造方法中传递的文件/文件夹的路径,创建文件
 *      3.会把FileWriter对象指向创建好的文件
 *
 * 字符输出流的使用步骤(重点):
 *      1.创建FileWriter对象,构造方法中绑定要写入数据的目的地
 *      2.使用FileWriter中的方法write,把数据写入到内存缓冲区(字符数据转换为字节的过程)
 *      3.使用FileWriter中的方法flush,把内存缓冲区的数据,刷新到文件中
 *      4.释放资源(会先把内存缓冲区的数据刷新到文件中)
 *
 * */
public class Demo01Writer {
    public static void main(String[] args) throws IOException {
        //1.创建FileWriter对象,构造方法中绑定要写入数据的目的地
        FileWriter fw=new FileWriter("/Users/xp/Downloads/a");
        //2.使用FileWriter中的方法write,把数据写入到内存缓冲区(字符数据转换为字节的过程)        重点(文件里面之前的数据会被覆盖)
        //void write(int c) 写入单个字符
        fw.write(97);//a
        //fw.write("abc");
        //3.使用FileWriter中的方法flush,把内存缓冲区的数据,刷新到文件中
        fw.flush();
        //4.释放资源(会先把内存缓冲区的数据刷新到文件中)
        fw.close();
    }

}

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

/*
 *   字符输出流写数据的其他办法:
 *       void write(char[] cbuf) 写入字符数组
 *       abstract void write(char[] cbuf,int off, int len) 写入字符数组的某一部分,off数组的开始索引,len写的字符个数
 *       void write(String str) 写入字符串
 *       void write(String str,int off,int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数
 *
 * */
public class Demo03Writer {
    public static void main(String[] args) throws IOException {
        FileWriter fw=new FileWriter("/Users/xp/Downloads/a");
        //void write(char[] cbuf) 写入字符数组
        char[] cs={'a','b','c','d','e'};
        fw.write(cs);//abcde

        //abstract void write(char[] cbuf,int off, int len) 写入字符数组的某一部分,off数组的开始索引,len写的字符个数
        fw.write(cs,1,3);//bcd

        //void write(String str) 写入字符串
        fw.write("陈浩bbb");//陈浩bbb

        //void write(String str,int off,int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数
        fw.write("猪比aaa",1,3);//比aa

        fw.close();
    }
}

flush方法和close方法的区别
  • flush:刷新缓冲区,流对象可以继续使用
  • close:先刷新缓冲区,然后通知系统释放资源,流对象不能再被使用了
import java.io.FileWriter;
import java.io.IOException;

/*
 * flush方法和close方法的区别
 *       flush:刷新缓冲区,流对象可以继续使用
 *       close:先刷新缓冲区,然后通知系统释放资源,流对象不能再被使用了
 * */
public class Demo02FlushAndClose {
    public static void main(String[] args) throws IOException {
//1.创建FileWriter对象,构造方法中绑定要写入数据的目的地
        FileWriter fw = new FileWriter("/Users/xp/Downloads/a");
        //2.使用FileWriter中的方法write,把数据写入到内存缓冲区(字符数据转换为字节的过程)        重点
        //void write(int c) 写入单个字符
        fw.write(97);//a
        //3.使用FileWriter中的方法flush,把内存缓冲区的数据,刷新到文件中
        fw.flush();

        //刷新流之后可以继续使用
        fw.write(98);

        //4.释放资源(会先把内存缓冲区的数据刷新到文件中)
        fw.close();

        //close方法之后流已经关闭了,已经从内存中消失了,流就不能再使用了
//        fw.write(99);//IOException: Stream closed
    }
}

  • 续写和换行
import java.io.FileWriter;
import java.io.IOException;

/*
 * 续写和换行
 * 续写,追加写:使用两个参数的构造方法
 *       FileWriter(String filename,boolean append)
 *       FileWriter(File file,boolean append)
 *          参数:String filename,File file :写入数据的目的地
 *              boolean append:续写开关 true:不会创建新的文件覆盖源文件,可以续写;false:创建新的文件覆盖源文件
 *      换行:换行符号
 *          windows:\r\n
 *          linux:/n
 *          mac:\r
 *
 * */
public class Demo04Writer {
    public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter("/Users/xp/Downloads/a", true);
        for (int i = 0; i < 10; i++) {
            fw.write("HelloWorld" + i + "\r");
        }
        fw.close();
    }
}

在jdk1.7之前使用try catch finally 处理流中异常
import java.io.FileWriter;
import java.io.IOException;

/*
 * 在jdk1.7之前使用try catch finally 处理流中异常
 * 格式:
 *       try{
 *           可能出现异常的代码
 *       }catch(异常类变量 变量名){
 *           异常的处理逻辑
 *       }finally{
 *           一定会执行的代码
 *           资源释放
 *       }
 * */
public class Demo05Writer {
    public static void main(String[] args) {
        //提高变量fw的作用域,让finally可以使用
        //变量在定义的时候,可以没有值,但是使用的时候必须有值
        //fw = new FileWriter("/Users/xp/Downloads/a", true); 执行失败,fw没有值,fw.close会报错
        FileWriter fw = null;
        try {
            //可能出现异常的代码
            fw = new FileWriter("/Users/xp/Downloads/a", true);
            for (int i = 0; i < 10; i++) {
                fw.write("HelloWorld" + i + "\r");
            }
        } catch (IOException e) {
            //异常的处理逻辑
            e.printStackTrace();
        } finally {
            //一定会执行的代码
            //创建对象失败了,fw的默认值就是null,null是不能调用方法的,会抛出NullPointerException,需要增加一个判断,不是null再把资源释放
            if (fw != null) {
                try {
                    //fw.close方法声明抛出了IOException异常对,所以我们必须得处理这个异常对象,要么throws,要么try catch
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

JDK7新特性
  • 在try后边可以增加一个(),在括号中可以定义流对象那么这个流对象的作用作用域就在try中有效,try中的代码执行完毕,会自动把流对象释放,不用写finally,即不用close();
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/*
 * JDK7新特性
 * 在try后边可以增加一个(),在括号中可以定义流对象
 * 那么这个流对象的作用作用域就在try中有效
 * try中的代码执行完毕,会自动把流对象释放,不用写finally
 * 格式:
 *       try(定义流对象;定义流对象...){
 *           可能出现异常的代码
 *       }catch(异常类变量 变量名){
 *           异常的处理逻辑
 *       }
 * */
public class Demo06JDK7 {
    public static void main(String[] args) {
        try (//1.创建一个字节输入流对象,构造方法中绑定要读取的数据源
             FileInputStream fis = new FileInputStream("/Users/xp/Downloads/a");
             //2.创建一个字节输出流对象,构造方法中绑定要写入的数据源
             FileOutputStream fos = new FileOutputStream("/Users/xp/Downloads/b",true);) {
            //一次读取一个字节写入一个字节的方式
            //3.使用字节输入流对象中的方法read文件对象
            int len = 0;
            while ((len = fis.read()) != -1) {
                //4.使用字节输出流中的方法write,把读取到的字节写入到目的地的文件中
                fos.write(len);
            }
            //5.释放资源(先关写的,后关读的,如果写完了,肯定读完了)
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Properties集合

  • Properties集合是一个唯一和IO流相结合的集合
    • 可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中储存
    • 可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用
  • 属性列表中每个键及其对应的值都是一个字符串
    • Properties集合是一个双列集合,key和value默认都是字符串
import java.io.*;
import java.util.Properties;
import java.util.Set;

/*
 * java.util.Properties集合 extends HashTable<k,v> implements Map<k,v>
 * Properties类表示了一个持久的属性集.Properties可保存在流中或从流中加载.
 * Properties集合是一个唯一和IO流相结合的集合
 *       可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中储存
 *       可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用
 *
 * 属性列表中每个键及其对应的值都是一个字符串
 *       Properties集合是一个双列集合,key和value默认都是字符串
 *
 * */
public class Demo01Properties {
    public static void main(String[] args) throws IOException {
        show03();
    }

    /*
     * 可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用
     * void load(InputStream inStream)
     * void load(Reader reader)
     * 参数:
     *       InputStream inStream:字节输入流,不能读取含有中文的键值对
     *       Reader reader:字符输入流,能读取对象中的方法load读取保存键值对的文件
     * 使用步骤:
     *      1.创建Properties集合对象
     *      2.使用Properties集合对象中的方法load读取保存键值对的文件
     *      3.遍历Properties集合
     * 注意:
     *      1.存储键值对的文件中,键与值默认的连接符号可以使用=,空格(其他符号)
     *      2.存储键值对的文件中,可以使用#进行注释,被注释的键值对不会再被读取
     *      3.存储键值对的文件中,键与值默认都是字符串,不用再加引号
     * */
    private static void show03() throws IOException {
        //1.创建Properties集合对象
        Properties prop = new Properties();
        //2.使用Properties集合对象中的方法load读取保存键值对的文件
        //prop.load(new FileInputStream("D:/Users/xp/Downloads/a"));
        prop.load(new FileReader("/Users/xp/Downloads/b"));
        //3.遍历Properties集合
        for (String key : prop.stringPropertyNames()) {
            String value = prop.getProperty(key);
            System.out.println(key + "=" + value);
        }
    }

    /*
     * 可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
     * void store(OutputStream out,String comments)
     * void store(Writer writer,String comments)
     * 参数:
     *       OutputStream out:字节输出流,不能写中文
     *       Writer writer:字符输出流,可以写中文
     *       String comments:注释,用来解释说明保存的问件是做什么用的
     *                   不能使用中文,会产生乱码,默认是Unicode编码
     *                   一般使用""空字符串
     *
     * 使用步骤:
     *       1.创建Properties集合对象,添加数据
     *       2.创建字符输出流/字节输出流对象,构造方法中绑定要输出的目的地
     *       3.使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
     *       4.释放资源
     * */
    private static void show02() throws IOException {
        //1.创建Properties集合对象,添加数据
        Properties prop = new Properties();
        prop.setProperty("陈浩", "11");
        prop.setProperty("陈bb", "1");
        prop.setProperty("陈cc", "111");
        //2.创建字符输出流/字节输出流对象,构造方法中绑定要输出的目的地
        FileWriter fw = new FileWriter("/Users/xp/Downloads/b");
        //3.使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
        prop.store(fw, "");
        //4.释放资源
        fw.close();
        //prop.store(new FileOutputStream("D:\\IdeaProjects\\basic-code\\prop2.txt"),"");
    }

    /*
     * 使用Properties集合存储数据,遍历取出Properties集合中的数据
     * Properties集合是一个双列集合,key和value默认都是字符串
     * Properties集合有一些操作字符串的特有方法
     *       Object setProperty(String kry,String value) 和调用HashTable的方法put差不多
     *       String getProperty(String key) 通过key找到value值,此方法相当于Mao集合中的get(key)方法
     *       Set<String> stringPropertyNames() 返回此属性列表中的键集,其中该键及其其对应值是字符串,此方法相当于Map集合中的keySet方法
     * */
    private static void show01() {
        //创建Properties集合对象
        Properties prop = new Properties();
        //使用setProperty往集合中添加数据
        prop.setProperty("陈浩", "11");
        prop.setProperty("陈bb", "1");
        prop.setProperty("陈cc", "111");
        //使用stringPropertyNames把Properties集合的键取出,存储到一个set集合中
        Set<String> set = prop.stringPropertyNames();
        //遍历set集合,取出Properties集合的每一个键
        for (String key : set) {
            //使用getProperty方法通过key获取value
            String value = prop.getProperty(key);
            System.out.println(key + "=" + value);
        }
    }

}

BufferedOutputStream:字节缓冲输出流

import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

/*
 * java.io.BufferedOutputStream extends OutputStream
 * BufferedOutputStream:字节缓冲输出流
 *
 * 继承父类的共性成员方法;
 *      public void close(): 关闭此输出流并释放与此流相关联的任何系统资源
 *      public void flush(): 刷新此输出流并强制任何缓冲的输出字节被写出
 *      public void write(byte[] b): 将b.length字节从指定的字节数组写入此输出流
 *      public void write(byte[] b,int off,int len): 从指定的字节数组写入len字节,从偏移量off开始输出到此输出流
 *      public abstract void write(int b): 将指定的字节输出流
 *
 * 构造方法:
 *      BufferedOutputStream(OutputStream out) 创建一个新的缓冲输出流,以将数据的写入指定的底层输出流
 *      BufferedOutputStream(OutputStream out,int size) 创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流
 *      参数:
 *          OutputStream out:字节输出流
 *                  我们可以传递FileOutputStream,缓冲流会给FileOutputStream增加一个缓冲区,提高FileOutputStream的写入效率
 *          int size:指定缓冲流内部缓冲区的大小,不指定默认
 * 使用步骤(重点):
 *      1.创建FileOutputStream对象,构造方法中绑定要输出的目的地
 *      2.创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象,提高FileOutputStream对象效率
 *      3.使用BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区中
 *      4.使用BufferedOutputStream对象中的方法flush,把内部缓冲区的数据,刷新到文件中
 *      5.释放资源(会先调用flush方法刷新数据,第4步可以省略)
 * */
public class Demo01BufferedOutputStream {
    public static void main(String[] args) throws IOException {
        //1.创建FileOutputStream对象,构造方法中绑定要输出的目的地
        FileOutputStream fos = new FileOutputStream("/Users/xp/Downloads/a",true);
        //2.创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象,提高FileOutputStream对象效率
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        //3.使用BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区中
        bos.write("把数据写入到内部缓冲区中".getBytes());
        //5.释放资源(会先调用flush方法刷新数据,第4步可以省略)
        bos.close();
    }
}

BufferedInputStream:字节缓冲输入流

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;

/*
 * java.io.BufferedInputStream extends InputStream
 * BufferedInputStream:字节缓冲输入流
 *
 * 继承自父类的成员方法:
 *       int read()从输入流中读取数据的下一个字节
 *       int read(byte[] b) 从输入流中华读取一定数量的字节,并将其储存在缓冲区数组b中
 *       void close() 关闭此输入流并释放与该流相关联的所有系统资源
 *
 * 构造方法:
 *       BufferedInputStream(InputStream in) 创建一个BufferedInputStream并保存其参数,即输入流in,以便将来使用
 *       BufferedInputStream(InputStream in ,int size) 创建具有指定缓冲区大小的 BufferedInputStream 并保存其参数,即输入流in
 *       参数:
 *           InputStream in:字节输入流
 *                   我们可以传递FileInputStream,缓冲流会给FileInputStream增加一个缓冲区,提高FileInputStream的读取效率
 *           int size:指定缓冲流内部缓冲区的大小,不指定默认
 *
 * 使用步骤:
 *       1.创建FileInputStream对象,构造方法中绑定要读取的数据源
 *       2.创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象的读取效率
 *       3.使用BufferedInputStream对象中的方法read,读取文件
 *       4.释放资源
 * */
public class Demo02BufferedInputStream {
    public static void main(String[] args) throws IOException {
        //1.创建FileInputStream对象,构造方法中绑定要读取的数据源
        FileInputStream fis = new FileInputStream("/Users/xp/Downloads/a");
        //2.创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象的读取效率
        BufferedInputStream bis = new BufferedInputStream(fis);
        //3.使用BufferedInputStream对象中的方法read,读取文件
        /*int len = 0;//记录每次读到的字节
        while ((len = bis.read()) != -1) {
            System.out.println(len);
        }*/

        //int read(byte[] b) 从输入流中华读取一定数量的字节,并将其储存在缓冲区数组b中
        byte[] bytes = new byte[1024];
        int len = 0;//记录每次读到的字节
        while ((len = bis.read(bytes)) != -1) {
            System.out.println(new String(bytes, 0, len));
        }
        //4.释放资源
        bis.close();
    }
}

文件复制练习:一读一写

import java.io.*;

/*
 * 文件复制练习:一读一写
 *
 * 明确:
 *       数据源:c:\\1.jpg
 *       目的地:d:\\jpg
 * 文件复制的步骤:
 *       1.创建字节缓冲输入流对象,构造方法中传递字节输入流
 *       2.创建字节缓冲输出流对象,构造方法中传递字节输出流
 *       3.使用字节缓冲输入流对象中的方法read,读取文件
 *       4.使用字节缓冲输出流中对象的方法write,把读取到的数据写入到内部缓冲区
 *       5.释放资源(会先把缓冲区中的数据,刷新到文件中)
 *
 * 文件的大小 2,679,652 字节
 * 一次读写一个字节:97ms
 * 使用数组缓冲读取多个字节,写入多个字节:34ms
 * */
public class Demo03CopyFile {
    public static void main(String[] args) throws IOException {
        long l = System.currentTimeMillis();
        //1.创建字节缓冲输入流对象,构造方法中传递字节输入流
        BufferedInputStream fis = new BufferedInputStream(new FileInputStream("/Users/xp/Downloads/a"));
        //2.创建字节缓冲输出流对象,构造方法中传递字节输出流
        BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream("/Users/xp/Downloads/b"));
        //3.使用字节缓冲输入流对象中的方法read,读取文件
        int len = 0;//记录每次读取到的字节
        //4.使用字节缓冲输出流中对象的方法write,把读取到的数据写入到内部缓冲区
       /* while ((len = fis.read()) != -1) {
            fos.write(len);
        }*/
        byte[] bytes = new byte[1024];
        while ((len = fis.read(bytes)) != -1) {
            fos.write(bytes, 0, len);
        }
        fis.close();
        fos.close();
        long e = System.currentTimeMillis();
        System.out.println("共耗时" + (e - l) + "毫秒");
    }
}

BufferedWriter:字符缓冲输入流

  • 特定的成员方法:
    • void newLine() 写入一个行分隔符.会根据不同的操作系统,获取不同的行分隔符
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

/*
 * java.io.BufferedWriter extends Writer
 * BufferedWriter:字符缓冲输入流
 *
 * 继承自父类的成员方法:
 *      public void close(): 关闭此输出流并释放与此流相关联的任何系统资源
 *      public void flush(): 刷新此输出流并强制任何缓冲的输出字节被写出
 *      public void write(char[] cbuf): 写入字符数组
 *      public abstract void write(String str,int off,int len): 写入字符串的某一部分,off字符串开始的索引,len写的字符个数
 *      public void write(char[] cbuf,int off,int len): 写入字符数组的某一部分,off字符串开始的索引,len写的字符个数
 *      void write(int c): 写入单个字符
 *      void write(String str) 写入字符串
 *
 * 构造方法:
 *       BufferedWriter(Writer out) 创建一个使用默认大小输出缓冲区的缓冲字符输出流
 *       BufferedWriter(Writer out ,int sz) 创建一个使用给定大小输出缓冲区的新缓冲字符输出流
 *       参数:
 *           Writer out:字符输出流
 *                   我们可以传递FileWriter,缓冲流会给FileWriter增加一个缓冲区,提高FileWriter的写入效率
 *           int sz:指定缓冲流内部缓冲区的大小,不指定默认
 *
 * 特定的成员方法:
 *      void newLine() 写入一个行分隔符.会根据不同的操作系统,获取不同的行分隔符
 *      换行:换行符号
 *      windows:\r\n
 *      linux:/n
 *      mac:\r
 *
 * 使用步骤:
 *       1.创建字符缓冲输出流对象,构造方法中绑定要读取的数据源
 *       2.调用字符缓冲输出流中的方法write,把数据写入到内存缓冲区中
 *       3.使用字符缓冲输出流中的方法flush,把内存缓冲区中的数据,刷新到文件中
 *       4.释放资源
 * */
public class Demo01BufferedWriter {
    public static void main(String[] args) throws IOException {
        //1.创建字符缓冲输出流对象,构造方法中绑定要读取的数据源
        BufferedWriter bw = new BufferedWriter(new FileWriter("/Users/xp/Downloads/a",true));
        //2.调用字符缓冲输出流中的方法write,把数据写入到内存缓冲区中
        for (int i = 0; i < 10; i++) {
            bw.write("陈浩");
            //bw.write("\r");
            bw.newLine();
        }
        //4.释放资源
        bw.close();

    }
}

BufferedReader:字符缓冲输出流

  • 特有的成员方法:
    • String readLine() 读取一个文本行,读取一行数据
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

/*
 * java.io.BufferedReader extends Reader
 *
 * 继承自父类的共性成员方法:
 *       int read() 读取单个字符并返回
 *       int read(char[] cbuf)一次读取多个字节,将字符读入数组
 *       void close() 关闭该流并释放与之关联的所有资源.
 *
 * 构造方法:
 *      BufferedReader(Reader in): 创建一个使用默认大小输入缓冲区的缓冲字符输入流
 *      BufferedReader(Reader in,int sz) 创建一个使用指定大小输入缓冲区的缓冲字符输入流
 *      参数:
 *          Reader in:字符输入流
 *              我们可以传递FileReader,缓冲流会给FileReader增加一个缓冲区,提高FileReader的读取效率
 * 特有的成员方法:
 *      String readLine() 读取一个文本行,读取一行数据
 *           行的终止符号:通过下列字符之一即可认为某行已终止:换行("\n"),回车("\r"),换行加回车("\r\n")
 *      返回值:
 *          包含该行的内容的字符串,不包含任何行终止符,如果已达到流末尾,则返回null
 *
 * 使用步骤:
 *      1.创建字符缓冲输入流对象,构造方法中传递字符输入流
 *      2.使用字符缓冲输入流对象中的方法read/readLine读取文本
 *      3.释放资源
 * */
public class Demo02BufferedReader {
    public static void main(String[] args) throws IOException {
        //1.创建字符缓冲输入流对象,构造方法中传递字符输入流
        BufferedReader br = new BufferedReader(new FileReader("/Users/xp/Downloads/a"));
        //2.使用字符缓冲输入流对象中的方法read/readLine读取文本
        /*
         * 不知道文件中有多少行数据,所以使用while循环
         * while的结束条件,读取到null结束
         * */
        String line;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
        //3.释放资源
        br.close();
    }
}

Split方法

  • 根据分隔符对读取到的文本进行切割,获取行中的序号和文本内容
import java.io.*;
import java.util.HashMap;

/*
 * 练习:
 *   对文本的内容进行排序
 *   按照(1,2,3,4..)顺序排列
 * 分析:
 *   1.创建一个HashMap集合对象,可以:key存储每行文本的序号(1,2,3,4..) value:存储每行的文本
 *   2.创建字符缓冲输入流对象,构造方法中绑定字符输入流
 *   3.创建字符缓冲输出流对象,构造方法中绑定字符输出流
 *   4.使用字符缓冲输入流中的方法readLine,逐行读取文本
 *   5.对读取到的文本进行切割,获取行中的序号和文本内容
 *   6.把切割好的序号和文本内容存储到HashMap集合中(key的序号是有序的,会自动排序1,2,3,4...)
 *   7.遍历HashMap集合,获取每一个键值对
 *   8.把每一个键值对,拼接为一个文本行
 *   9.把拼接好的文本行,使用字符缓冲输出流中的方法write,写入到文件中
 *   10.释放资源
 * */
public class Demo03Test {
    public static void main(String[] args) throws IOException {
        //1.创建一个HashMap集合对象,可以:key存储每行文本的序号(1,2,3,4..) value:存储每行的文本
        HashMap<String, String> map = new HashMap<>();
        //2.创建字符缓冲输入流对象,构造方法中绑定字符输入流
        BufferedReader br = new BufferedReader(new FileReader("/Users/xp/Downloads/a"));
        //3.创建字符缓冲输出流对象,构造方法中绑定字符输出流
        BufferedWriter bw = new BufferedWriter(new FileWriter("/Users/xp/Downloads/a",true));
        //4.使用字符缓冲输入流中的方法readLine,逐行读取文本
        String line;
        while ((line = br.readLine()) != null) {
            //5.对读取到的文本进行切割,获取行中的序号和文本内容
            String[] arr = line.split("\\.");//分隔符
            //6.把切割好的序号和文本内容存储到HashMap集合中(key的序号是有序的,会自动排序1,2,3,4...)
            map.put(arr[0], arr[1]);
        }
        //7.遍历HashMap集合,获取每一个键值对
        for (String key : map.keySet()) {
            String value = map.get(key);
            //8.把每一个键值对,拼接为一个文本行
            //9.把拼接好的文本行,使用字符缓冲输出流中的方法write,写入到文件中
            bw.write(key + "." + value);
            bw.newLine();
        }
        //10.释放资源
        br.close();
        bw.close();
    }
}

OutputStreamWrite:是字符通向字节流的桥梁

  • 可使用指定的 charset 将要写入流中的字符编码成字节.(编码:把能看懂的变成看不懂)
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

/*
 * java.io.OutputStreamWriter extends Writer
 * OutputStreamWrite:是字符通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节.(编码:把能看懂的变成看不懂)
 *
 * 继承父类的共性成员方法:
 *       void write(int c): 写入单个字符
 *       void write(char[] cbuf):写入字符数组
 *       abstract void write(char[] cbuf,int off,int len) 写入字符数组的某一部分,off数组的开始索引,len写的字符个数
 *       void write(String str) 写入字符串
 *       void write(String str,int off,int len) 写入字符串的某一部分,off字符串开始的索引,len写的字符个数
 *       void flush() 刷新该流的缓冲
 *       void close() 关闭该流,但要先刷新它
 * 构造方法:
 *      OutputStreamWriter(OutputStream out) 创建使用默认字符编码的OutputStreamWriter
 *      OutputStreamWriter(OutputStream out,String charsetName) 创建使用指定字符集的 OutputStreamWriter
 *      参数:
 *          OutputStream out :字节输出流,可以用来写转换之后的字节到文件中
 *          String charsetName: 指定的编码表名称,不区分大小写,可以是utf-8/UTF-8,gbk/GBK,..不指定使用默认UTF-8
 * 使用步骤:
 *      1.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称
 *      2.使用OutputStreamWriter对象中的方法write,把字符转换为字节存储到缓冲区中(编码)
 *      3.使用OutputStreamWriter对象中的方法flush,把内存缓冲区中的字节刷新到文件中(使用字节流写字节的过程)
 *      4.释放资源
 *
 * */
public class Demo02OutputStreamWriter {
    public static void main(String[] args) throws IOException {
         write_utf_8();
        //write_gbk();
    }

    /*
     * 使用转换流OutputStreamWriter写GBK格式的文件
     * */
    private static void write_gbk() throws IOException {
        //1.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("/Users/xp/Downloads/b"), "gbk");
        //2.使用OutputStreamWriter对象中的方法write,把字符转换为字节存储到缓冲区中(编码)
        osw.write("你好啊");
        //3.使用OutputStreamWriter对象中的方法flush,把内存缓冲区中的字节刷新到文件中(使用字节流写字节的过程)
        osw.flush();
        //4.释放资源
        osw.close();
    }

    /*
     * 使用转换流OutputStreamWriter写UTF-8格式的文件
     * */
    private static void write_utf_8() throws IOException {
        //1.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("/Users/xp/Downloads/b"), "utf-8");
        //OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\\IdeaProjects\\basic-code\\utf_8.txt"));//默认为UTF-8格式
        //2.使用OutputStreamWriter对象中的方法write,把字符转换为字节存储到缓冲区中(编码)
        osw.write("你好");
        //3.使用OutputStreamWriter对象中的方法flush,把内存缓冲区中的字节刷新到文件中(使用字节流写字节的过程)
        osw.flush();
        //4.释放资源
        osw.close();

    }
}

InputStreamReader: 是字节通向字符流的桥梁

  • 它使用的charset读取字节并将其解码为字符.(解码:把看不懂的变成看得懂的)
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

/*
 * java.io.InputStreamReader extends Reader
 * InputStreamReader: 是字节通向字符流的桥梁:它使用的charset读取字节并将其解码为字符.(解码:把看不懂的变成看得懂的)
 *
 * 继承父类的共性成员方法:
 *       int read() 读取单个字符并返回
 *       int read(char[] cbuf) 一次读取多个字符,将字符读取数组
 *       void close() 关闭该流并释放与之关联的所有资源
 * 构造方法:
 *       InputStreamReader(InputStream in) 创建一个使用默认字符集的 InputStreamReader
 *       InputStreamReader(InputStream in,String charsetName ) 创建使用指定字符集的 InputStreamReader
 *       参数:
 *           InputStream in:字节输入流,用来读取文件中保存的字节
 *           String charsetName: 指定的编码表名称,不区分大小写,可以使utf-8/UTF-8,gbk/GBK,... 不指定默认使用UTF-8
 * 使用步骤:
 *       1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称
 *       2.使用InputStreamReader对象中的方法read读取文件
 *       3.释放资源
 * 注意事项:
 *       构造方法中指定的编码表名称要和文件的编码相同,否则会发生乱码
 * */
public class Demo03InputStreamReader {
    public static void main(String[] args) throws IOException {
//        read_utf_8();
        read_gbk();
    }

    /*
    * 使用InputStreamReader读取GBK格式的文件
    * */
    private static void read_gbk() throws IOException {
        //1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称
        InputStreamReader isr = new InputStreamReader(new FileInputStream("/Users/xp/Downloads/b"), "gbk");
        //2.使用InputStreamReader对象中的方法read读取文件
        int len = 0;
        while ((len = isr.read()) != -1) {
            System.out.print((char) len);
        }
        //3.释放资源
        isr.close();
    }

    /*
     * 使用InputStreamReader读取UTF-8格式的文件
     * */
    private static void read_utf_8() throws IOException {
        //1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称
        InputStreamReader isr = new InputStreamReader(new FileInputStream("/Users/xp/Downloads/b"), "utf-8");
        //2.使用InputStreamReader对象中的方法read读取文件
        int len = 0;
        while ((len = isr.read()) != -1) {
            System.out.print((char) len);
        }
        //3.释放资源
        isr.close();
    }
}

练习:转换文件编码

import java.io.*;

/*
 * 练习:转换文件编码
 *       将GBK编码的文本文件,转换为UTF-8编码的文本文件
 *
 * 分析:
 *       1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称GBK
 *       2.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称UTF-8
 *       3.使用InputStreamReader对象中的方法read读取文件
 *       4.使用OutputStreamWriter对象中的方法write,把读取的数据写入到文件中
 *       5.释放资源
 * */
public class Demo04Test {
    public static void main(String[] args) throws IOException {
        //1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称GBK
        InputStreamReader isr = new InputStreamReader(new FileInputStream("/Users/xp/Downloads/b"),"GBK");
        //2.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称UTF-8
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("/Users/xp/Downloads/b"),"utf-8");
        //3.使用InputStreamReader对象中的方法read读取文件
        int len = 0;
        while ((len = isr.read()) != -1) {
            //4.使用OutputStreamWriter对象中的方法write,把读取的数据写入到文件中
            osw.write(len);
        }
        //5.释放资源
        osw.close();
    }
}

Serializable接口:也叫标记型接口

  • 要进行序列化和反序列化的类必须实现Serializable接口,就会给类添加一个标记
  • 当我们进行序列化和反序列化的时候,就会检测类上是否有这个标记
    • 有:就可以序列化和反序列化
    • 没有:就会抛出 NotSerializableException 异常
  • transient关键字:瞬态关键字
    • 被transient关键字修饰的成员变量,不能被序列化的
import java.io.Serializable;

/*
 * 序列化和反序列化的时候,会抛出 NotSerializableException 没有序列化异常
 * 类通过实现 java.io.Serializable 接口以启用其序列化功能.未实现此接口的类将无法使其任何状态序列化或反序列化
 * Serializable接口也叫标记型接口
 *   要进行序列化和反序列化的类必须实现Serializable接口,就会给类添加一个标记
 *   当我们进行序列化和反序列化的时候,就会检测类上是否有这个标记
 *       有:就可以序列化和反序列化
 *       没有:就会抛出 NotSerializableException 异常
 *
 * 去市场买肉-->肉上有一个蓝色章(检测合格)-->放心购买-->买回来怎么吃随意
 *
 * static关键字:静态关键字
 *       静态优先于非静态加载到内存中(静态优先于对象进入到内存中)
 *       被static修饰的成员变量是不能被序列化的,序列化的都是对象
 *       private static int age;
 *       oos.writeObject(new Person("陈浩", 11));
 *       Person{name='陈浩', age=0}
 *       陈浩=0
 *
 * transient关键字:瞬态关键字
 *       被transient关键字修饰的成员变量,不能被序列化的
 *       private transient int age;
 *       oos.writeObject(new Person("陈浩", 11));
 *       Person{name='陈浩', age=0}
 *       陈浩=0
 *
 * 问题:
 *      每次修改类的定义,都会给class文件生成一个新的序列号
 * 解决方案:
 *      无论是否对类的定义进行修改,都不重新生成新的序列号
 *      可以手动给类添加一个序列号
 * 格式在Serializable接口规定:
 *      可序列化类可以通过声明名为"serialVersionUID"的字段(该字段必须是是静态的(static),最终的(final),和long型的字段)
 *      显式声明其自己的serialVersionUID:
 *      static final long serialVersionUID = 42L; 常量 不可改变
 * */
public class Person implements Serializable {
    private static final long serialVersionUID = 1L;//序列号 常量-不能改变
    private String name;
    //private static int age;
    //private transient int age;
    public int age;

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

    public String getName() {
        return name;
    }

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

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

    public int getAge() {
        return age;
    }

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

    public Person() {
    }
}

ObjectOutputStream: 对象的序列化流

  • 作用:把对象以流的方式写入到文件中保存
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

/*
 * java.io.ObjectOutputStream extends OutputStream
 * ObjectOutputStream: 对象的序列化流
 * 作用:把对象以流的方式写入到文件中保存
 *
 * 构造方法:
 *       ObjectOutputStream(OutputStream out) 创建写入指定 OutputStream 的 ObjectOutputStream
 *       参数:
 *           OutputStream out: 字节输出流
 * 特有的成员方法:
 *       void writeObject(Object obj) 将指定的对象写入ObjectOutputStream
 *
 * 使用步骤:
 *       1.创建ObjectOutputStream对象,构造方法中传递字节输出流
 *       2.使用ObjectOutputStream对象的方法writeObject,把对象写入到文件中
 *       3.释放资源
 * */
public class Demo01ObjectOutputStream {
    public static void main(String[] args) throws IOException {
        //1.创建ObjectOutputStream对象,构造方法中传递字节输出流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("/Users/xp/Downloads/b"));
        //2.使用ObjectOutputStream对象的方法writeObject,把对象写入到文件中
        oos.writeObject(new Person("陈浩", 11));
        //3.释放资源
        oos.close();
    }
}

ObjectInputStream:对象的反序列化流

  • 作用:把文件中保存的对象,以流的方式读取出来
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

/*
 * java.io.ObjectInputStream extends InputStream
 * ObjectInputStream:对象的反序列化流
 * 作用:把文件中保存的对象,以流的方式读取出来
 *
 * 构造方法:
 *       ObjectInputStream(InputStream in) 创建从指定 InputStream 读取的 ObjectInputStream
 *       参数:
 *           InputStream in:字节输入流
 * 特有的成员方法:
 *       Object readObject() 从 ObjectInputStream 读取对象
 *
 * 使用步骤:
 *       1.创建ObjectInputStream对象,构造方法中传递字节输入流
 *       2.使用ObjectInputStream对象中的方法readObject读取保存对象的文件
 *       3.释放资源
 *       4.使用读取出来的对象(打印)
 *
 * readObject方法声明抛出了 ClassNotFoundException (class文件找不到异常)
 * 当不存在对象的class文件时抛出异常
 * 反序列化的前提:
 *      1.类必须实现Serializable
 *      2.必须存在类对应的class文件
 * */
public class Demo02ObjectInputStream {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //1.创建ObjectInputStream对象,构造方法中传递字节输入流
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/Users/xp/Downloads/b"));
        //2.使用ObjectInputStream对象中的方法readObject读取保存对象的文件
        Object o = ois.readObject();
        //3.释放资源
        ois.close();
        //4.使用读取出来的对象(打印)
        System.out.println(o);
        Person p = (Person) o;
        System.out.println(p.getName() + "=" + p.getAge());
    }
}

练习:序列化集合

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

/*
 * 练习:序列化集合
 *       当我们想在文件中保存多个对象时
 *       可以把多个对象存储到一个集合中
 *       对集合进行序列化和反序列化
 * 分析:
 *       1.定义一个存储Person对象的ArrayList集合
 *       2.往ArrayList集合中添加Person对象
 *       3.创建一个序列化流ObjectOutputStream对象
 *       4.使用ObjectOutputStream对象中的方法writeObject,对集合进行序列化
 *       5.创建一个反序列化ObjectInputStream对象
 *       6.使用ObjectInputStream对象中的方法readObject读取文件中保存的集合
 *       7.把Object类型的集合强转为ArrayList类型的集合
 *       8.遍历ArrayList集合
 *       9.释放资源
 * */
public class Demo04Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //1.定义一个存储Person对象的ArrayList集合
        ArrayList<Person> list = new ArrayList<>();
        //2.往ArrayList集合中添加Person对象
        list.add(new Person("陈浩", 11));
        list.add(new Person("陈bb", 111));
        list.add(new Person("陈cc", 1));
        //3.创建一个序列化流ObjectOutputStream对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("/Users/xp/Downloads/b"));
        //4.使用ObjectOutputStream对象中的方法writeObject,对集合进行序列化
        oos.writeObject(list);
        //5.创建一个反序列化ObjectInputStream对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/Users/xp/Downloads/b"));
        //6.使用ObjectInputStream对象中的方法readObject读取文件中保存的集合
        Object o = ois.readObject();
        //7.把Object类型的集合强转为ArrayList类型的集合
        ArrayList<Person> list_02 = (ArrayList<Person>) o;
        //8.遍历ArrayList集合
        for (Person p : list_02) {
            System.out.println(p);
        }
        //9.释放资源
        ois.close();
        oos.close();
    }
}

PrintStream:打印流

  • 为其他输出流添加了功能,使他们能够方便地打印各种数值表示形式
import java.io.FileNotFoundException;
import java.io.PrintStream;

/*
 * java.io.PrintStream:打印流
 *       PrintStream 为其他输出流添加了功能,使他们能够方便地打印各种数值表示形式
 * PrintStream特点:
 *       1.只负责数据的输出,不负责数据的读取
 *       2.与其他输出流不同,PrintStream永远不会抛出 IOException
 *       3.有特有的方法,print,println
 *               void print(任意类型的值)
 *               void println(任意类型的值并换行)
 * 构造方法:
 *       PrintStream(File file): 输出到目的地是一个文件
 *       PrintStream(OutputStream out): 输出到目的地是一个字节输出流
 *       PrintStream(String fileName): 输出到目的地是一个文件路径
 * PrintStream extends OutputStream
 * 继承来自父类的成员方法:
 *      public void close(): 关闭此输出流并释放与此流相关联的任何系统资源
 *      public void flush(): 刷新此输出流并强制任何缓冲的输出字节被写出
 *      public void write(byte[] b): 将b.length字节从指定的字节数组写入此输出流
 *      public void write(byte[] b,int off,int len): 从指定的字节数组写入len字节,从偏移量off开始输出到此输出流
 *      public abstract void write(int b): 将指定的字节输出流
 * 注意:
 *      如果使用继承自父类的write法写数据,那么查看数据的时候会查询编码表 97-a
 *      如果使用自己特有的方法print/println方法写数据,写的数据原样输出 97-97
 * */
public class Demo01PrintStream {
    public static void main(String[] args) throws FileNotFoundException {
        //System.out.println("HelloWorld");

        //创建打印流PrintStream对象,构造方法中绑定要输出的目的地
        PrintStream ps=new PrintStream("/Users/xp/Downloads/b");
        //如果使用继承自父类的write法写数据,那么查看数据的时候会查询编码表 97-a
        ps.write(97);

        //如果使用自己特有的方法print/println方法写数据,写的数据原样输出 97-97
        ps.println(97);
        ps.println('a');
        ps.println("abc");
        ps.println(97.1);
        ps.println(true);

        //释放资源
        ps.close();
    }
}

  • 可以改变输出语句的目的地(打印流的流向)
import java.io.FileNotFoundException;
import java.io.PrintStream;

/*
 * 可以改变输出语句的目的地(打印流的流向)
 * 输出语句,默认在控制台输出
 * 使用System.setOut方法将输出语句的目的地改为参数中传递的打印流的目的地
 *       static void setOut(OutputStream out)
 *           重新分配"标准"输出流
 * */
public class Demo02PrintStream {
    public static void main(String[] args) throws FileNotFoundException {
        System.out.println("我在控制台输出");

        PrintStream ps = new PrintStream("/Users/xp/Downloads/b");
        //把输出语句目的地改变为打印流的目的地
        System.setOut(ps);
        System.out.println("我在打印流输出");
    }
}

评论 38
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Xpccccc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值