0
点赞
收藏
分享

微信扫一扫

【零基础 快速学Java】韩顺平 零基础30天学会Java--- 异常、泛型、IO(2024JavaReview)


异常(Exception)

异常介绍(Java语言中,将程序执行中发生的不正常情况称为“异常”(开发过程中的语法错误和逻辑错误不是异常))

【零基础 快速学Java】韩顺平 零基础30天学会Java--- 异常、泛型、IO(2024JavaReview)_System

Error(错误)(Java虚拟机无法解决的严重问题)

Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError[栈溢出]和OOM(out ofmemory),Error 是严重错误,程序会崩溃。

Exception(可以使用针对性的代码进行处理)

其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如空指针访问,试图读取不存在的文件,网络连接中断等等,Exception 分为两大类:运行时异常[程序运行时,发生的异常]和编译时异常[编程时,编译器检查出的异常]。

运行时异常(程序运行时,发生的异常)
  1. NullPointerException空指针异常
  2. ArithmeticException数学运算异常
  3. ArrayIndexOutOfBoundsException数组下标越界异常
  4. ClassCastException类型转换异常
  5. NumberFormatException数字格式不正确异常
编译异常(编译器检查出的异常)

编译异常是指在编译期间,就必须处理的异常,否则代码不能通过编译。

SQLException //操作数据库时,查询表可能发生异常IOException //操作文件时,发生的异常EOFException // 操作文件,到文件末尾,发生异常 FileNotFoundException //当操作一个不存在的文件时,发生异常
ClassNotFoundException //加载类,而该类不存在时,异常
llegalArguementException //参数异常

异常处理

try-catch-finally(自行处理)(系统将异常封装成Exception 对象e,传递给catch)

程序员在代码中捕获发生的异常,自行处理

【零基础 快速学Java】韩顺平 零基础30天学会Java--- 异常、泛型、IO(2024JavaReview)_java_02

  1. 可以有多个catch语句,捕获不同的异常(进行不同的业务处理),要求父类异常在后,子类异常在前,比如(Exception 在后,NullPointerException 在前),如果发生异常,只会匹配一个catch
  2. 可以进行 try-finally 配合使用,这种用法相当于没有捕获异常,因此程序会直接崩掉/退出。应用场景,就是执行一段代码,不管是否发生异常,都必须执行某个业务逻辑
finally代码块一定会执行(会覆盖catch里面的返回值)

public class Exception01 {
    public static int method() {
        try {
            String[] names = new String[3];  // 修正了数组定义
            if (names[1] != null && names[1].equals("tom")) {  // NullPointerException
                System.out.println(names[1]);
            } else {
                names[3] = "hspedu";  // ArrayIndexOutOfBoundsException
            }
            return 1;
        } catch (ArrayIndexOutOfBoundsException e) {
            return 2;  // 捕获数组越界异常
        } catch (NullPointerException e) {
            return 3;  // 捕获空指针异常
        } finally {
            return 4;  // finally块会覆盖try/catch中的返回值
        }
    }

    public static void main(String[] args) {
        System.out.println(method());  // 输出4
    }
}

throws(交给调用者(方法)来处理,最顶级的处理者就是JVM)(在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类)(程序中如果没有处理,则默认的异常处理方式是throws)(子类重写父类的方法时,所抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出的异常的类型的子类型

将发生的异常抛出,交给调用者(方法)来处理,最顶级的处理者就是JVM

【零基础 快速学Java】韩顺平 零基础30天学会Java--- 异常、泛型、IO(2024JavaReview)_System_03

自定义异常(当程序中出现了某些“错误”,但该错误信息并没有在Throwable子类中描述处理,这个时候可以自己设计异常类,用于描述该错误信息)

  1. 定义类:自定义异常类名(程序员自己写)继承Exception或RuntimeException
  2. 如果继承Exception,属于编译异常
  3. 如果继承RuntimeException,属于运行异常(一般来说,继承RuntimeException)

public class CustomException {
    public static void main(String[] args) {
        int age = 180;
        // 要求范围在 18 – 120 之间,否则抛出一个自定义异常
        if (!(age >= 18 && age <= 120)) {
            // 通过构造器设置信息,抛出自定义异常
            throw new AgeException("年龄需要在 18~120 之间");
        }
        System.out.println("你的年龄范围正确.");
    }
}

// 自定义一个异常
// 1. 自定义异常通常继承 RuntimeException
// 2. 这样自定义异常成为运行时异常,可以使用默认的处理机制
class AgeException extends RuntimeException {
    // 构造器接收一个错误信息,并传递给父类
    public AgeException(String message) {
        super(message);
    }
}

throws(异常处理的一种方式,在方法声明处后面跟异常类型 )和throw(手动生成异常对象的关键字,在方法体中后面跟异常对象) 的区别

练习题

try catch和finally的执行顺序

泛(广泛)型(类型)(在给泛型指定具体类型后,可以传入该类型或者其子类类型)(在实际开发中,我们往往简写(编译器会进行类型推断),ArrayList<Integer> list3 = new ArrayList<>();)(传入的必须是引用类型,默认是Object类型)(泛型可以有多个)

泛型的理解和好处

看一个需求

1)请编写程序,在ArrayList 中,添加3个Dog对象
2)Dog对象含有name 和 age,并输出name 和 age(要求使用getXxx())

使用传统方法(不能对加入到集合 ArrayList中的数据类型进行约束(不安全))(影响效率)

  1. 不能对加入到集合 ArrayList中的数据类型进行约束(不安全)
  2. 遍历的时候,需要进行类型转换,如果集合中的数据量较大,对效率有影响

this.age = age;
}


public String getName() { return name;
}


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


public int getAge() { return age;
}


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

使用泛型(使用泛型 ArrayList,类型不匹配时编译器会报错,提高了安全性)

  1. 编译时,检查添加元素的类型,提高了安全性
  2. 减少了类型转换的次数,提高效率[说明]
  • 不使用泛型Dog -加入->0bject -取出->Dog //放入到ArrayList 会先转成 Object,在取出时,还需要转换成Dog
  • 使用泛型
    Dog -> Dog -> Dog // 放入时,和取出时,不需要类型转换,提高效率
  1. 不再提示编译警告

import java.util.ArrayList;

@SuppressWarnings({"all"})
public class Generic02 {
    public static void main(String[] args) {

        // 使用泛型来解决类型安全问题
        // 1. ArrayList<Dog> 表示集合中的元素是 Dog 类型
        // 2. 添加不符合类型的元素会在编译时报错
        // 3. 遍历时可以直接取出 Dog 类型,而不是 Object

        ArrayList<Dog> arrayList = new ArrayList<>();  // 使用泛型 ArrayList<Dog>

        arrayList.add(new Dog("旺财", 10));
        arrayList.add(new Dog("发财", 1));
        arrayList.add(new Dog("小黄", 5));

        // 编译器会报错,因为类型不匹配
//         arrayList.add(new Cat("招财猫", 8));

        // 输出每只狗的名字和年龄
        System.out.println("===使用泛型====");
        for (Dog dog : arrayList) {
            System.out.println(dog.getName() + " - " + dog.getAge());
        }
    }
}

/**
 * Dog 类
 */
class Dog {
    private String name;
    private int age;

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

/**
 * Cat 类
 */
class Cat {
    private String name;
    private int age;

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

泛型的语法

泛型的声明

interface 接口{} 和 class 类<K,V>{}
//比如:List , ArrayList
说明:

  1. 其中,T,K,V不代表值,而是表示类型。
  2. 任意字母都可以。常用T表示,是Type的缩写

泛型的实例化

要在类名后面指定类型参数的值(类型)。
如:

  1. List<String> strList = new ArrayList();[举例说明]
  2. Iterator<Customer> iterator = customers.iterator();

泛型的使用

  1. interface List{},public class HashSet{}… 等等说明:T,E只能是引用类型 看看下面语句是否正确?:

List<Integer> list = new ArrayList<Integer>();
 //OKList<int> list2 = new ArrayList<int>();//错误

  1. 在给泛型指定具体类型后,可以传入该类型或者其子类类型
  2. 泛型使用形式(在实际开发中,我们往往简写,/编译器会进行类型推断, 推荐使用下面写法)
    List list1 = new ArrayList();
    List list2 = new ArrayList<>();[说明:]
  3. 如果我们这样写 List list3 = new ArrayList();默认给它的 泛型是[E就是 Object]

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

/**
 * @创建人 wdl
 * @创建时间 2024/9/14
 * @描述
 */
@SuppressWarnings({"all"})
public class GenericDetail {
    public static void main(String[] args) {
        // 1. 给泛型指向数据类型时,要求是引用类型,不能是基本数据类型
        List<Integer> list = new ArrayList<Integer>(); // OK
        // List<int> list2 = new ArrayList<int>(); // 错误,基本类型不允许

        // 2. 说明
        // 因为 E 指定了 A 类型, 构造器传入了 new A()
        // 在给泛型指定具体类型后,可以传入该类型或者其子类类型
        Pig<A> aPig = new Pig<A>(new A());
        aPig.f();

        Pig<A> aPig2 = new Pig<A>(new B()); // B 是 A 的子类
        aPig2.f();

        // 3. 泛型的使用形式
        ArrayList<Integer> list1 = new ArrayList<Integer>();
        List<Integer> list2 = new ArrayList<Integer>();

        // 在实际开发中,我们往往简写,编译器会进行类型推断
        ArrayList<Integer> list3 = new ArrayList<>();
        List<Integer> list4 = new ArrayList<>();
        ArrayList<Pig> pigs = new ArrayList<>(); // 这里 Pig 没有泛型类型指定,默认为 Object

        // 4. 如果是这样写 泛型默认是 Object
        ArrayList arrayList = new ArrayList(); // 等价于 ArrayList<Object> arrayList = new ArrayList<Object>();
        /*
        public boolean add(Object e) {
        ensureCapacityInternal(size + 1); // Increments modCount!!
        elementData[size++] = e;
        return true;
        }
        */
        
        // 创建一个 Tiger 对象
        Tiger tiger = new Tiger(); // 没有指定泛型类型,默认为 Object
    }
}

class Tiger<E> { // 类 E 是泛型类型
    E e;

    public Tiger() {
    }

    public Tiger(E e) {
        this.e = e;
    }
}

class A {
}

class B extends A {
}

class Pig<E> { // Pig 使用泛型
    E e;

    public Pig(E e) {
        this.e = e;
    }

    public void f() {
        System.out.println(e.getClass()); // 输出泛型对象的运行时类型
    }
}

/**
 * @创建人 wdl
 * @创建时间 2024/9/14
 * @描述
 */
import java.util.*;

/**
 * @author 韩顺平
 * @version 1.0
 */
@SuppressWarnings({"all"})
public class GenericExercise02 {

    public static void main(String[] args) {

        ArrayList<Employee> employees = new ArrayList<>();
        employees.add(new Employee("tom", 20000, new MyDate(1980, 12, 11)));
        employees.add(new Employee("jack", 12000, new MyDate(2001, 12, 12)));
        employees.add(new Employee("tom", 50000, new MyDate(1980, 12, 10)));

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

        // 定制排序
        employees.sort(new Comparator<Employee>() {
            //没有泛型的时候需要这样写
//            @Override
//            public int compare(Object o1, Object o2) {
//                return 0;
//            }

            @Override
            public int compare(Employee emp1, Employee emp2) {
                // 先按照 name 排序,如果 name 相同,则按生日日期的先后排序。【即:定制排序】
                // 先对传入的参数进行验证
                if (!(emp1 instanceof Employee && emp2 instanceof Employee)) {
                    System.out.println("类型不正确..");
                    return 0;
                }
                // 比较 name
                int i = emp1.getName().compareTo(emp2.getName());
                if (i != 0) {
                    return i;
                }

                // 下面是对 birthday 的比较,因此,我们最好把这个比较,放在 MyDate 类完成
                // 封装后,将来可维护性和复用性,就大大增强.
                return emp1.getBirthday().compareTo(emp2.getBirthday());
            }
        });

        System.out.println("==对雇员进行排序==");
        System.out.println(employees);

    }
}

/**
 * 定义 Employee 类
 * 1) 该类包含:private 成员变量 name,sal,birthday,其中 birthday 为 MyDate 类的对象;
 * 2) 为每一个属性定义 getter, setter 方法;
 * 3) 重写 toString 方法输出 name, sal, birthday
 */
class Employee {
    private String name;
    private double sal;
    private MyDate birthday;

    public Employee(String name, double sal, MyDate birthday) {
        this.name = name;
        this.sal = sal;
        this.birthday = birthday;
    }

    public String getName() {
        return name;
    }

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

    public double getSal() {
        return sal;
    }

    public void setSal(double sal) {
        this.sal = sal;
    }

    public MyDate getBirthday() {
        return birthday;
    }

    public void setBirthday(MyDate birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "Employee{name='" + name + '\'' + ", sal=" + sal + ", birthday=" + birthday + '}';
    }
}

/**
 * 4) MyDate 类包含: private 成员变量 month,day,year;并为每一个属性定义 getter, setter 方法;
 */
class MyDate implements Comparable<MyDate> {
    private int year;
    private int month;
    private int day;

    public MyDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public int getMonth() {
        return month;
    }

    public void setMonth(int month) {
        this.month = month;
    }

    public int getDay() {
        return day;
    }

    public void setDay(int day) {
        this.day = day;
    }

    @Override
    public int compareTo(MyDate other) {
        // 比较 year
        int yearDiff = this.year - other.year;
        if (yearDiff != 0) {
            return yearDiff;
        }

        // 比较 month
        int monthDiff = this.month - other.month;
        if (monthDiff != 0) {
            return monthDiff;
        }

        // 比较 day
        return this.day - other.day;
    }

    @Override
    public String toString() {
        return "MyDate{year=" + year + ", month=" + month + ", day=" + day + '}';
    }
}

自定义泛型

自定义泛型类(类型是在创建对象时确定的)

class 类名<T, R...>{//...表示可以有多个泛型
	成员
}

  1. 普通成员可以使用泛型(属性、方法),但使用泛型变量不能初始化(因为对象没有创建的时候不确定其具体的类型)
  2. 泛型类的类型,是在创建对象时确定的(静态方法/静态变量/静态代码块 中不能使用类的泛型)(因为创建对象时,需要指定确定类型), 如果在创建对象时,没有指定类型,默认为Object

import java.util.Arrays;

/**
 * @创建人 wdl
 * @创建时间 2024/9/14
 * @描述
 */
@SuppressWarnings({"all"})
public class CustomGeneric_ {
    public static void main(String[] args) {
        // T=Double, R=String, M=Integer
        Tiger<Double, String, Integer> g = new Tiger<>("john");
        g.setT(10.9); // OK
        // g.setT("yy"); // 错误,类型不对
        System.out.println(g);

        Tiger g2 = new Tiger("john~~"); // OK T=Object, R=Object, M=Object
        g2.setT("yy"); // OK ,因为 T=Object,"yy"=String 是 Object 子类
//        g2.setT(1);
        System.out.println("g2=" + g2);
    }
}

// 解读
// 1. Tiger 后面泛型,所以我们把 Tiger 就称为自定义泛型类
// 2. T, R, M 泛型的标识符, 一般是单个大写字母
// 3. 泛型标识符可以有多个.
// 4. 普通成员可以使用泛型 (属性、方法)
// 5. 使用泛型的数组,不能初始化
// 6. 静态方法中不能使用类的泛型
class Tiger<T, R, M> {
    String name;
    R r; // 属性使用到泛型
    M m;
    T t;
    // 因为数组在 new 时无法确定 T 的类型,无法在内存开空间
    T[] ts;

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

    public Tiger(R r, M m, T t) { // 构造器使用泛型
        this.r = r;
        this.m = m;
        this.t = t;
    }

    public Tiger(String name, R r, M m, T t) { // 构造器使用泛型
        this.name = name;
        this.r = r;
        this.m = m;
        this.t = t;
    }

    // 因为静态是和类相关的,在类加载时,对象还没有创建
    // 所以,如果静态方法和静态属性使用了泛型,JVM 就无法完成初始化
    // static R r2;
    // public static void m1(M m) {
    //
    // }

    // 方法使用泛型
    public String getName() {
        return name;
    }

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

    public R getR() {
        return r;
    }

    public void setR(R r) { // 方法使用到泛型
        this.r = r;
    }

    public M getM() { // 返回类型可以使用泛型
        return m;
    }

    public void setM(M m) {
        this.m = m;
    }

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }

    @Override
    public String toString() {
        return "Tiger{" +
                "name='" + name + '\'' +
                ", r=" + r +
                ", m=" + m +
                ", t=" + t +
                ", ts=" + Arrays.toString(ts) +
                '}';
    }
}

自定义泛型接口(类型是在继承接口或者实现接口时确定的)

interface 接口名<T, R...>{
}

  1. 接口中,静态…也不能使用泛型(这个和泛型类规定一样)
  2. 泛型接口的类型,在继承接口或者实现接口时确定
  3. 没有指定类型,默认为Object

/**
 * @创建人 wdl
 * @创建时间 2024/9/14
 * @描述
 */
public class CustomInterfaceGeneric {
    public static void main(String[] args) {
        // 测试 AA 实现类
        AA aa = new AA();
        System.out.println(aa.get("test")); // null
        aa.hi(3.14);
        aa.run(1.0, 2.0, "A", "B");

        // 测试 BB 实现类
        BB bb = new BB();
        System.out.println(bb.get(10)); // null
        bb.hi(4.5f);
        bb.run(1.1f, 2.2f, 3, 4);

        // 测试 CC 实现类
        CC cc = new CC();
        System.out.println(cc.get("object")); // null
        cc.hi("hello");
        cc.run(1, 2, "u1", "u2");
    }
}

/**
 * 泛型接口使用的说明
 * 1. 接口中,静态成员也不能使用泛型
 * 2. 泛型接口的类型, 在继承接口或者实现接口时确定
 * 3. 没有指定类型,默认为 Object
 */

// 在继承接口时指定泛型接口的类型
interface IA extends IUsb<String, Double> {}

// 当我们去实现 IA 接口时,因为 IA 在继承 IUsb 接口时,指定了 U 为 String,R 为 Double
// 在实现 IUsb 接口的方法时,使用 String 替换 U,使用 Double 替换 R
class AA implements IA {
    @Override
    public Double get(String s) {
        System.out.println("AA.get() called with: " + s);
        return null;
    }

    @Override
    public void hi(Double aDouble) {
        System.out.println("AA.hi() called with: " + aDouble);
    }

    @Override
    public void run(Double r1, Double r2, String u1, String u2) {
        System.out.println("AA.run() called with: " + r1 + ", " + r2 + ", " + u1 + ", " + u2);
    }
}

// 实现接口时,直接指定泛型接口的类型
// 给 U 指定 Integer 给 R 指定 Float
// 所以,当我们实现 IUsb 方法时,会使用 Integer 替换 U,使用 Float 替换 R
class BB implements IUsb<Integer, Float> {
    @Override
    public Float get(Integer integer) {
        System.out.println("BB.get() called with: " + integer);
        return null;
    }

    @Override
    public void hi(Float aFloat) {
        System.out.println("BB.hi() called with: " + aFloat);
    }

    @Override
    public void run(Float r1, Float r2, Integer u1, Integer u2) {
        System.out.println("BB.run() called with: " + r1 + ", " + r2 + ", " + u1 + ", " + u2);
    }
}

// 没有指定类型,默认为 Object
// 等价于 class CC implements IUsb<Object, Object>
class CC implements IUsb<Object, Object> {
    @Override
    public Object get(Object o) {
        System.out.println("CC.get() called with: " + o);
        return null;
    }

    @Override
    public void hi(Object o) {
        System.out.println("CC.hi() called with: " + o);
    }

    @Override
    public void run(Object r1, Object r2, Object u1, Object u2) {
        System.out.println("CC.run() called with: " + r1 + ", " + r2 + ", " + u1 + ", " + u2);
    }
}

// 泛型接口定义
interface IUsb<U, R> {
//    int n ; //
//     U name; //不能这样使用,因为接口里面的变量都是pulbic static final类型的

    // 普通方法中,可以使用接口泛型
    R get(U u);

    void hi(R r);

    void run(R r1, R r2, U u1, U u2);

    // 在 jdk8 中,可以在接口中,使用默认方法, 也是可以使用泛型
    default R method(U u) {
        return null;
    }
}

自定义泛型方法(类型是在泛型方法被调用时确定的)

修饰符 <T,R..>返回类型 方法名(参数列表){
}

  1. 泛型方法,可以定义在普通类中,也可以定义在泛型类
  2. 当泛型方法被调用时,类型会确定
  3. public void eat(E e)},修饰符后没有<T,R…>eat方法不是泛型方法,而是使用了泛型

import java.util.ArrayList;

/**
 * @创建人 wdl
 * @创建时间 2024/9/14
 * @描述
 */
@SuppressWarnings({"all"})
public class CustomMethodGeneric {
    public static void main(String[] args) {
        Car car = new Car();
        car.fly("宝马", 100); // 当调用方法时,传入参数,编译器就会确定类型
        System.out.println("=======");
        car.fly(300, 100.1); // 当调用方法时,传入参数,编译器就会确定类型

        // 测试泛型类 Fish
        // T->String, R->ArrayList
        Fish<String, ArrayList> fish = new Fish<>();
        fish.hello(new ArrayList<>(), 11.3f);
    }
}

// 泛型方法可以定义在普通类中,也可以定义在泛型类中
class Car { // 普通类
    public void run() { // 普通方法
        System.out.println("Car is running...");
    }

    // 泛型方法
    // 1. <T,R> 就是泛型
    // 2. 是提供给 fly 使用的
    public <T, R> void fly(T t, R r) { // 泛型方法
        System.out.println("t type: " + t.getClass().getSimpleName()); // 打印 t 的类型
        System.out.println("r type: " + r.getClass().getSimpleName()); // 打印 r 的类型
    }
}

// 泛型类
class Fish<T, R> { // 泛型类
    public void run() { // 普通方法
        System.out.println("Fish is swimming...");
    }

    // 泛型方法
    public <U, M> void eat(U u, M m) { // 泛型方法
        System.out.println("Eating: " + u + " and " + m);
    }

    // 说明
    // 1. 下面 hi 方法不是泛型方法
    // 2. 是 hi 方法使用了类声明的泛型
    public void hi(T t) {
        System.out.println("Hi: " + t);
    }

    // 泛型方法,可以使用类声明的泛型,也可以使用自己声明的泛型
    public <K> void hello(R r, K k) {
        System.out.println("R type: " + r.getClass().getSimpleName());
        System.out.println("K type: " + k.getClass().getSimpleName());
//        System.out.println("K type: " + k.getClass());
    }
}

泛型的继承和通配符

  1. 泛型不具备继承性List<Object> list = new ArrayList(); // 错误
  2. <?>:支持任意泛型类型
  3. <?extends A>:支持A类以及A类的子类,规定了泛型的上限
  4. <?super A>:支持A类以及A类的父类,不限于直接父类,规定了泛型的下限

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

/**
 * @创建人 wdl
 * @创建时间 2024/9/14
 * @描述
 */
public class GenericExtends {
    public static void main(String[] args) {

        Object o = new String("xx");

        // 泛型没有继承性
//         List<Object> list = new ArrayList<String>();

        // 举例说明下面三个方法的使用
        List<Object> list1 = new ArrayList<>();
        List<String> list2 = new ArrayList<>();
        List<AA> list3 = new ArrayList<>();
        List<BB> list4 = new ArrayList<>();
        List<CC> list5 = new ArrayList<>();

        // 如果是 List<?> c ,可以接受任意的泛型类型
        printCollection1(list1);
        printCollection1(list2);
        printCollection1(list3);
        printCollection1(list4);
        printCollection1(list5);

        // List<? extends AA> c: 表示 上限,可以接受 AA 或者 AA 子类
        // printCollection2(list1);//×
        // printCollection2(list2);//×
        printCollection2(list3);//√
        printCollection2(list4);//√
        printCollection2(list5);//√

        // List<? super AA> c: 支持 AA 类以及 AA 类的父类,不限于直接父类
        printCollection3(list1);//√
        // printCollection3(list2);//×
        printCollection3(list3);//√
        // printCollection3(list4);//×
        // printCollection3(list5);//×

        // 其他排序相关代码可以根据需要在此添加
    }

    // ? extends AA 表示 上限,可以接受 AA 或者 AA 子类
    public static void printCollection2(List<? extends AA> c) {
        for (Object object : c) {
            System.out.println(object);
        }
    }

    // 说明: List<?> 表示 任意的泛型类型都可以接受
    public static void printCollection1(List<?> c) {
        for (Object object : c) { // 通配符,取出时,就是 Object
            System.out.println(object);
        }
    }

    // ? super 子类类名 AA:支持 AA 类以及 AA 类的父类,不限于直接父类,
    // 规定了泛型的下限
    public static void printCollection3(List<? super AA> c) {
        for (Object object : c) {
            System.out.println(object);
        }
    }
}

// 定义 AA, BB, CC 类
class AA {
}

class BB extends AA {
}

class CC extends BB {
}

JUnit

  1. 一个类有很多功能代码需要测试,为了测试,就需要写入到main方法中。如果有多个功能代码测试,就需要来回注销,切换很麻烦,如果可以直接运行一个方法,就方便很多,并且可以给出相关信息,就好了->JUnit
  2. JUnit是一个Java语言的单元测试框架,多数Java的开发环境都已经集成了JUnit作为单元测试的工具

/**
 * @创建人 wdl
 * @创建时间 2024/9/14
 * @描述
 */
// 导入需要的 JUnit 包
import org.junit.Test;

public class JUnitTestExample {

    public static void main(String[] args) {
        // 传统方式手动调用方法(不是通过 JUnit 框架)
        System.out.println("传统方式调用方法:");
        new JUnitTestExample().m1();
        new JUnitTestExample().m2();
    }

    // 使用 JUnit 的方式运行测试方法
    @Test
    public void m1() {
        System.out.println("m1 方法被调用");
    }

    @Test
    public void m2() {
        System.out.println("m2 方法被调用");
    }

    @Test
    public void m3() {
        System.out.println("m3 方法被调用");
    }
}

IO流

文件(保存数据的地方)

文件流

【零基础 快速学Java】韩顺平 零基础30天学会Java--- 异常、泛型、IO(2024JavaReview)_泛型_04

创建文件对象相关构造器和方法(先有File对象,然后用其createNewFile方法创建新文件)

new File(String pathname) //根据路径构建一个File对象
new File(File parent,String child) //根据父目录文件+子路径构建
new File(String parent,String child) //根据父目录+子路径构建

【零基础 快速学Java】韩顺平 零基础30天学会Java--- 异常、泛型、IO(2024JavaReview)_System_05

import org.junit.Test;

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

/**
 * @创建人 wdl
 * @创建时间 2024/9/21
 * @描述
 */
public class FileCreate {

    public static void main(String[] args) {
        // 这里可以手动调用测试方法来创建文件
        FileCreate fileCreate = new FileCreate();
        fileCreate.create01();
        fileCreate.create02();
        fileCreate.create03();
    }

    // 方式 1: 使用 new File(String pathname)
    @Test
    public void create01() {
        String filePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news1.txt";
        File file = new File(filePath);

        try {
            if (file.createNewFile()) {
                System.out.println("文件创建成功");
            } else {
                System.out.println("文件已存在");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 方式 2: 使用 new File(File parent, String child)
    // 根据父目录文件+子路径构建
    @Test
    public void create02() {
        File parentFile = new File("D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\");
        String fileName = "news2.txt";

        // 创建文件对象
        File file = new File(parentFile, fileName);

        try {
            if (file.createNewFile()) {
                System.out.println("文件创建成功");
            } else {
                System.out.println("文件已存在");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 方式 3: 使用 new File(String parent, String child)
    // 根据父目录+子路径构建
    @Test
    public void create03() {
        String parentPath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\";
        String fileName = "news4.txt";

        // 创建文件对象
        File file = new File(parentPath, fileName);

        try {
            if (file.createNewFile()) {
                System.out.println("文件创建成功");
            } else {
                System.out.println("文件已存在");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 下面四个都是抽象类
    // InputStream
    // OutputStream
    // Reader // 字符输入流
    // Writer // 字符输出流
}

获取文件的相关信息

getName
getAbsolutePath
getParent
length
exists
isFile
isDirectory

import org.junit.Test;

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

/**
 * @创建人 wdl
 * @创建时间 2024/9/21
 * @描述
 */

public class FileInformation {

    public static void main(String[] args) {
        // 手动调用 info() 方法来查看文件信息
        FileInformation fileInformation = new FileInformation();
        fileInformation.info();
    }

    // 获取文件的信息
    @Test
    public void info() {
        // 先创建文件对象
        File file = new File("D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news1.txt");

        try {
            file.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 调用相应的方法,得到对应信息
        System.out.println("文件名字 = " + file.getName());
        System.out.println("文件绝对路径 = " + file.getAbsolutePath());
        System.out.println("文件父级目录 = " + file.getParent());
        System.out.println("文件大小(字节) = " + file.length());
        System.out.println("文件是否存在 = " + file.exists()); // T
        System.out.println("是不是一个文件 = " + file.isFile()); // T
        System.out.println("是不是一个目录 = " + file.isDirectory()); // F
    }
}

目录的操作和文件删除

mkdir创建一级目录

mkdirs创建多级目录 delete删除空目录或文件

【零基础 快速学Java】韩顺平 零基础30天学会Java--- 异常、泛型、IO(2024JavaReview)_java_06

I/O流原理及流的分类(对于数据的输入/输出操作以”流(stream)”的方式进行)

  • 按操作数据单位不同分为:字节流(8 bit)二进制文件,字符流(按字符)文本文件
  • 按数据流的流向不同分为:输入流,输出流
  • 按流的角色的不同分为:节点流,处理流/包装流

【零基础 快速学Java】韩顺平 零基础30天学会Java--- 异常、泛型、IO(2024JavaReview)_java_07


【零基础 快速学Java】韩顺平 零基础30天学会Java--- 异常、泛型、IO(2024JavaReview)_java_08

FileInputStream(字节流)(其不直接处理字符编码,如果使用它们写入和读取字节数据,而不考虑编码格式,那么就有可能导致乱码(见转换流))

/**
 * @创建人 wdl
 * @创建时间 2024/9/21
 * @描述
 */
import org.junit.Test;

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

public class FileInputStreamExample {
    public static void main(String[] args) {

    }

    /**
     * 演示读取文件...
     * 单个字节的读取,效率比较低
     * -> 使用 read(byte[] b)
     */
    @Test
    public void readFile01() {
        String filePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news1.txt";
        int readData;
        FileInputStream fileInputStream = null;

        try {
            // 创建 FileInputStream 对象,用于读取文件
            fileInputStream = new FileInputStream(filePath);

            // 从该输入流读取一个字节的数据。如果没有输入可用,此方法将阻止。
            // 如果返回-1, 表示读取完毕
            while ((readData = fileInputStream.read()) != -1) {
                System.out.print((char) readData); // 转成 char 显示
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭文件流,释放资源.
            try {
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 使用 read(byte[] b) 读取文件,提高效率
     */
    @Test
    public void readFile02() {
        String filePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news1.txt";
        byte[] buf = new byte[8]; // 一次读取 8 个字节.
        int readLen;
        FileInputStream fileInputStream = null;

        try {
            // 创建 FileInputStream 对象,用于读取文件
            fileInputStream = new FileInputStream(filePath);

            // 从该输入流读取最多 b.length 字节的数据到字节数组。此方法将阻塞,直到某些输入可用。
            // 如果返回-1, 表示读取完毕
            // 如果读取正常, 返回实际读取的字节数
            while ((readLen = fileInputStream.read(buf)) != -1) {
                System.out.print(new String(buf, 0, readLen)); // 显示
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭文件流,释放资源.
            try {
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

FileOutputStream

写文件

【零基础 快速学Java】韩顺平 零基础30天学会Java--- 异常、泛型、IO(2024JavaReview)_List_09

import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStream01 {
    public static void main(String[] args) {
        //创建 FileOutputStream 对象
        String filePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news1.txt";
        FileOutputStream fileOutputStream = null;

        try {
            // 得到 FileOutputStream 对象 对象
            // 老师说明
            // 1. new FileOutputStream(filePath) 创建方式,当写入内容时,会覆盖原来的内容
            // 2. new FileOutputStream(filePath, true) 创建方式,当写入内容时,是追加到文件后面
            fileOutputStream = new FileOutputStream(filePath, true);

            // 写入字符串
            String str = "呜我大,world!";
            // str.getBytes() 可以把 字符串-> 字节数组

            /*
             * write(byte[] b, int off, int len)
             * 将 len 字节从位于偏移量 off 的指定字节数组写入此文件输出流
             */
            fileOutputStream.write(str.getBytes(), 0, 3);

            System.out.println("数据写入成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                // 关闭文件输出流,释放资源
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

复制文件

/**
 * @创建人 wdl
 * @创建时间 2024/9/21
 * @描述
 */
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileCopy {
    public static void main(String[] args) {
        // 完成文件拷贝,将 e:\\Koala.jpg 拷贝到 c:\\
        // 思路分析
        // 1. 创建文件的输入流 , 将文件读入到程序
        // 2. 创建文件的输出流,将读取到的文件数据,写入到指定的文件。
        String srcFilePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news1.txt";
        String destFilePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news2.txt";
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;

        try {
            // 创建文件输入流对象,用于读取源文件
            fileInputStream = new FileInputStream(srcFilePath);
            // 创建文件输出流对象,用于写入目标文件
            fileOutputStream = new FileOutputStream(destFilePath);
            // 定义一个字节数组,提高读取效率
            byte[] buf = new byte[1024];
            int readLen;
            while ((readLen = fileInputStream.read(buf)) != -1) {
                // 读取到后,就写入到文件通过 fileOutputStream
                // 即,是一边读,一边写
                fileOutputStream.write(buf, 0, readLen); // 一定要使用这个方法
            }
            System.out.println("拷贝 ok~");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                // 关闭输入流和输出流,释放资源
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

FileReader(字符流)(不会乱码)

【零基础 快速学Java】韩顺平 零基础30天学会Java--- 异常、泛型、IO(2024JavaReview)_开发语言_10

  1. new FileReader(File/String)
  2. read:每次读取单个字符,返回该字符,如果到文件未尾返回-1
  3. read(char[]):批量读取多个字符到数组,返回读取到的字符数,如果到文件未尾返回-1

相关API:

  1. new String(char[]):将char[]转换成String
  2. new String(char[],off,len):将char[]的指定部分转换成String

/**
 * @创建人 wdl
 * @创建时间 2024/9/21
 * @描述
 */
import org.junit.Test;

import java.io.FileReader;
import java.io.IOException;

public class FileReader_ {
    public static void main(String[] args) {

    }

    /**
     * 单个字符读取文件
     */
    @Test
    public void readFile01() {
        String filePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news1.txt";
        FileReader fileReader = null;
        int data = 0;

        // 1. 创建 FileReader 对象
        try {
            fileReader = new FileReader(filePath);
            // 循环读取 使用 read, 单个字符读取
            while ((data = fileReader.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fileReader != null) {
                    fileReader.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 字符数组读取文件
     */
    @Test
    public void readFile02() {
        System.out.println("~~~readFile02 ~~~");
        String filePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news1.txt";
        FileReader fileReader = null;
        int readLen = 0;
        char[] buf = new char[8];

        // 1. 创建 FileReader 对象
        try {
            fileReader = new FileReader(filePath);
            // 循环读取 使用 read(buf), 返回的是实际读取到的字符数
            // 如果返回-1, 说明到文件结束
            while ((readLen = fileReader.read(buf)) != -1) {
                System.out.print(new String(buf, 0, readLen));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fileReader != null) {
                    fileReader.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

FileWriter(FileWriter使用后,必须要关闭(close)或刷新(flush),否则写入不到指定的文件!)

【零基础 快速学Java】韩顺平 零基础30天学会Java--- 异常、泛型、IO(2024JavaReview)_开发语言_11

  1. new FileWriter(File/String):覆盖模式,相当于流的指针在首端
  2. new FileWriter(File/String,true):追加模式,相当于流的指针在尾端
  3. write(int):写入单个字符
  4. write(char[]):写入指定数组
  5. write(char[],off,len):写入指定数组的指定部分
  6. write(string):写入整个字符串7)write(string,off,len):写入字符串的指定部分相关APl:String类:toCharArray:将String转换成char[]

import java.io.FileWriter;

public class FileWriter_ {
    public static void main(String[] args) {
        String filePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news1.txt";
        // 创建 FileWriter 对象
        FileWriter fileWriter = null; // 需要导入 java.io 包
        char[] chars = {'a', 'b', 'c'};
        try {
            fileWriter = new FileWriter(filePath); // 默认是覆盖写入
            // 3) write(int): 写入单个字符
            fileWriter.write('H');
            // 4) write(char[]): 写入指定数组
            fileWriter.write(chars);
            // 5) write(char[], off, len): 写入指定数组的指定部分
            fileWriter.write("教育1111111".toCharArray(), 0, 3);
            // 6) write(String): 写入整个字符串
            fileWriter.write(" 你好北京~");
            fileWriter.write("风雨之后,定见彩虹");
            // 7) write(String, off, len): 写入字符串的指定部分
            fileWriter.write("上海天津", 0, 2);
            // 在数据量大的情况下,可以使用循环操作.
        } catch (java.io.IOException e) { // 需要导入 java.io 包
            e.printStackTrace();
        } finally {
            // 对应 FileWriter , 一定要关闭流,或者 flush 才能真正的把数据写入到文件
            // 看源码就知道原因.
            /*
            看看代码
            private void writeBytes() throws IOException {
                this.bb.flip();
                int var1 = this.bb.limit();
                int var2 = this.bb.position();
                assert var2 <= var1;
                int var3 = var2 <= var1 ? var1 - var2 : 0;
                if (var3 > 0) {
                    if (this.ch != null) {
                        assert this.ch.write(this.bb) == var3 : var3;
                    } else {
                        this.out.write(this.bb.array(), this.bb.arrayOffset() + var2, var3);
                    }
                }
                this.bb.clear();
            }
            */
            try {
                 fileWriter.flush();
                // 关闭文件流,等价 flush() + 关闭
                fileWriter.close();
            } catch (java.io.IOException e) {
                e.printStackTrace();
            }
        }
        System.out.println("程序结束...");
    }
}

节点流(可以从一个特定的数据源读写数据)和处理流(也叫(包装流)是“连接”在已存在的流(节点流或处理流)之上,为程序提供更为强大的读写功能)

  1. 节点流是底层流/低级流,直接跟数据源相接
  2. 处理流(包装流)包装节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出。
  3. 处理流(也叫包装流)对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连[模拟修饰器设计模式]

处理流-BufferedInputStream 和 BufferedOutputStream

【零基础 快速学Java】韩顺平 零基础30天学会Java--- 异常、泛型、IO(2024JavaReview)_List_12


【零基础 快速学Java】韩顺平 零基础30天学会Java--- 异常、泛型、IO(2024JavaReview)_java_13

import java.io.*;

/**
 * @创建人 wdl
 * @创建时间 2024/9/21
 * @描述
 */


public class BufferedCopy02 {
    public static void main(String[] args) {
        // String srcFilePath = "e:\\Koala.jpg";
        // String destFilePath = "e:\\hsp.jpg";
        // String srcFilePath = "e:\\0245_韩顺平零基础学 Java_引出 this.avi";
        // String destFilePath = "e:\\hsp.avi";
        String srcFilePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news1.txt";
        String destFilePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news3.txt";
        // 创建 BufferedOutputStream 对象 BufferedInputStream 对象
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            // 因为 FileInputStream 是 InputStream 子类(向上类型转换)
            bis = new BufferedInputStream(new FileInputStream(srcFilePath));
            bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
            // 循环的读取文件,并写入到 destFilePath
            byte[] buff = new byte[1024];
            int readLen = 0;
            // 当返回 -1 时,就表示文件读取完毕
            while ((readLen = bis.read(buff)) != -1) {
                bos.write(buff, 0, readLen);
            }
            System.out.println("文件拷贝完毕~~~");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭流 , 关闭外层的处理流即可,底层会去关闭节点流
            try {
                if (bis != null) {
                    bis.close();
                }
                if (bos != null) {
                    bos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

处理流-BufferedReader 和 BufferedWriter

import java.io.*;

/**
 * @创建人 wdl
 * @创建时间 2024/9/21
 * @描述
 */


public class BufferedCopy_ {
    public static void main(String[] args) {
        // 说明
        // 1. BufferedReader 和 BufferedWriter 是按字符操作
        // 2. 不要去操作 二进制文件[声音,视频,doc, pdf], 可能造成文件损坏
        // BufferedInputStream
        // BufferedOutputStream
        String srcFilePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news1.txt";
        String destFilePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news4.txt";
        // String srcFilePath = "e:\\0245_韩顺平零基础学 Java_引出 this.avi";
        // String destFilePath = "e:\\a2 韩顺平.avi";
        BufferedReader br = null;
        BufferedWriter bw = null;
        String line;
        try {
            br = new BufferedReader(new FileReader(srcFilePath));
            bw = new BufferedWriter(new FileWriter(destFilePath));
            // 说明: readLine 读取一行内容,但是没有换行
            while ((line = br.readLine()) != null) {
                // 每读取一行,就写入
                bw.write(line);
                // 插入一个换行
                bw.newLine();
            }
            System.out.println("拷贝完毕...");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭流
            try {
                if (br != null) {
                    br.close();
                }
                if (bw != null) {
                    bw.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

对象流-ObjectInputStream (提供 反序列化功能)和 ObjectOutputStream(提供 序列化功能)

  1. 序列化和反序列化序列化就是在保存数据时,但保存数据的值和数据类型
  2. 反序列化就是在恢复数据时,恢复数据的值和数据类型
  3. 需要让某个对象支持序列化机制,则必须让其类是可序列化的(1. 读写顺序要一致;2. 序列化的类中建议添加SerialVersionUlD,为了提高版本的兼容性序列化对象时,默认将里面所有属性都进行序列化,但除了static或transient修饰的成员;3. 序列化对象时,要求里面属性的类型也需要实现序列化接口;4. 序列化具备可继承性,也就是如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化),为了让某个类是可序列化的,该类必须实现如下两个接口之一:
  • Serializable //这是一个标记接口,没有方法(Java 的序列化机制就会自动处理这个类的对象的序列化和反序列化过程)
  • Externalizable // 该接口有方法需要实现,因此我们一般实现上面的 Serializable接口

【零基础 快速学Java】韩顺平 零基础30天学会Java--- 异常、泛型、IO(2024JavaReview)_java_14


【零基础 快速学Java】韩顺平 零基础30天学会Java--- 异常、泛型、IO(2024JavaReview)_System_15

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

// 演示 ObjectOutputStream 的使用, 完成数据的序列化
public class ObjectOutStream_ {
    public static void main(String[] args) throws Exception {
        // 序列化后,保存的文件格式,不是存文本,而是按照他的格式来保存
//        String filePath = "e:\\data.dat";
        String filePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news1.txt";
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
        // 序列化数据
        oos.writeInt(100); // int -> Integer (实现了 Serializable)
        oos.writeBoolean(true); // boolean -> Boolean (实现了 Serializable)
        oos.writeChar('a'); // char -> Character (实现了 Serializable)
        oos.writeDouble(9.5); // double -> Double (实现了 Serializable)
        oos.writeUTF("韩顺平教育"); // String
        // 保存一个 dog 对象
        oos.writeObject(new Dog("旺财", 10, "日本", "白色"));
        oos.close();
        System.out.println("数据保存完毕(序列化形式)");
    }
}

class Dog implements Serializable {
    private String name;
    private int age;
    private String breed;
    private String color;

    public Dog(String name, int age, String breed, String color) {
        this.name = name;
        this.age = age;
        this.breed = breed;
        this.color = color;
    }

    // 可选:添加 getter 和 setter 方法,或者 toString() 方法用于调试

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

/**
 * @创建人 wdl
 * @创建时间 2024/9/21
 * @描述
 */
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;

// 反序列化示例
public class ObjectInputStream_ {
    public static void main(String[] args) throws Exception {
        // 1.创建流对象
        String filePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news1.txt";

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
        // 2.读取,注意顺序
        System.out.println(ois.readInt());
        System.out.println(ois.readBoolean());
        System.out.println(ois.readChar());
        System.out.println(ois.readDouble());
        System.out.println(ois.readUTF());
        System.out.println(ois.readObject()); // 输出 Dog 对象

        // 以下两行代码将导致异常,因为 data.dat 文件中没有足够的对象可供读取
        // System.out.println(ois.readObject());
        // System.out.println(ois.readObject());

        // 3.关闭
        ois.close();
        System.out.println("以反序列化的方式读取(恢复)ok~");
    }
}

标准输入输出流

【零基础 快速学Java】韩顺平 零基础30天学会Java--- 异常、泛型、IO(2024JavaReview)_java_16


【零基础 快速学Java】韩顺平 零基础30天学会Java--- 异常、泛型、IO(2024JavaReview)_java_17

转换流-InputStreamReader 和OutputStreamWriter

问题引入-中文乱码问题

【零基础 快速学Java】韩顺平 零基础30天学会Java--- 异常、泛型、IO(2024JavaReview)_java_18

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

/**
 * @创建人 wdl
 * @创建时间 2024/9/22
 * @描述
 */
public class CodeQuestion {
    public static void main(String[] args) throws IOException {
        // 读取 e:\\a.txt 文件到程序
        // 思路
        // 1. 创建字符输入流 BufferedReader [处理流]
        // 2. 使用 BufferedReader 对象读取 a.txt
        // 3. 默认情况下,读取文件是按照 utf-8 编码
        String filePath = "e:\\a.txt";
        BufferedReader br = new BufferedReader(new FileReader(filePath));
        String s = br.readLine();
        System.out.println("读取到的内容: " + s);
        br.close();
        // InputStreamReader
        // OutputStreamWriter
    }
}

解决办法
  1. InputStreamReader:Reader的子类,可以将InputStream(字节流)包装成(转换)Reader(字符流);OutputStreamWriter:Writer的子类,实现将OutputStream(字节流)包装成Writer(字符流)
  2. 当处理纯文本数据时,使用字符流效率更高并且可以有效解决中文问题,所以建议将字节流转换成字符流
  3. 可以在使用时指定编码格式(比如 utf-8,gbk,gb2312,ISO8859-1 等)

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

public class InputStreamReader_ {
    public static void main(String[] args) throws IOException {
        String filePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news4.txt";
        // 解读
        // 1. 把 FileInputStream 转成 InputStreamReader
        // 2. 指定编码 gbk
        // InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), "gbk");
        // 3. 把 InputStreamReader 传入 BufferedReader
        // BufferedReader br = new BufferedReader(isr);
        // 将 2 和 3 合在一起
        BufferedReader br = new BufferedReader(new InputStreamReader(
                new FileInputStream(filePath), "utf-8"));
        // 4. 读取
        String s = br.readLine();
        System.out.println("读取内容=" + s);
        // 5. 关闭外层流
        br.close();
    }
}

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

public class OutputStreamWriter_ {
    public static void main(String[] args) throws IOException {
        // 1. 创建流对象
        OutputStreamWriter osw =
                new OutputStreamWriter(new FileOutputStream("d:\\a.txt"), "gbk");
        // 第 857页
        // 韩顺平循序渐进学 Java 零基础
        // 2. 写入
        osw.write("hello,韩顺平教育~");
        // 3. 关闭
        osw.close();
        System.out.println("保存成功~");
    }
}

Properties 类

看一个需求

如下一个配置文件 mysql.properties

ip=192.168.0.13
user=root
pwd=12345

请问编程读取 ip 、user 和 pwd 的值是多少

【零基础 快速学Java】韩顺平 零基础30天学会Java--- 异常、泛型、IO(2024JavaReview)_java_19

传统方法

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;

/**
 * @创建人 wdl
 * @创建时间 2024/9/22
 * @描述
 */

public class Properties01 {
    public static void main(String[] args) throws IOException {
        // 获取当前工作目录
        String currentDir = System.getProperty("user.dir");
        System.out.println(currentDir);
        // 读取 mysql.properties 文件,并得到 ip, user 和 pwd
        BufferedReader br = new BufferedReader(new FileReader("MyTest\\\\src\\\\mysql.properties"));
//        BufferedReader br = new BufferedReader(new FileReader("D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\mysql.properties"));
        String line = "";
        while ((line = br.readLine()) != null) { // 循环读取
            String[] split = line.split("=");
            // 如果我们要求指定的 ip 值
            if ("ip".equals(split[0])) {
                System.out.println(split[0] + "值是: " + split[1]);
            }
            System.out.println(Arrays.toString(split));
        }
        br.close();
    }
}

使用Properties类可以方便实现

  1. 专门用于读写配置文件的集合类配置文件的格式:
    键=值
    键=值
  2. 注意:键值对不需要有空格,值不需要用引号引起来。默认类型是String
  3. Properties的常见方法
    load:加载配置文件的键值对到Properties对象
    Iist:将数据显示到指定设备
    getProperty(key):根据键获取值
    setProperty(key,value):设置键值对到Properties对象
    store:将Properties中的键值对存储到配置文件,在idea 中,保存信息到配置文件,如果含有中文,会存储为unicode码

import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;

public class CombinedProperties {

    public static void main(String[] args) throws IOException {
        // 调用 Properties02 的逻辑
        runProperties02();

        // 调用 Properties03 的逻辑
        runProperties03();
    }

    public static void runProperties02() throws IOException {
        //使用 Properties 类来读取 mysql.properties 文件
        //1. 创建 Properties 对象
        Properties properties = new Properties();
        //2. 加载指定配置文件
        properties.load(new FileReader("MyTest\\src\\mysql.properties"));
        //3. 把 k-v 显示控制台
        properties.list(System.out);
        //4. 根据 key 获取对应的值
        String user = properties.getProperty("user");
        String pwd = properties.getProperty("pwd");
        System.out.println("用户名=" + user);
        System.out.println("密码是=" + pwd);
    }

    public static void runProperties03() throws IOException {
        //使用 Properties 类来创建 配置文件, 修改配置文件内容
        Properties properties = new Properties();
        //创建
        //1.如果该文件没有 key 就是创建
        //2.如果该文件有 key ,就是修改
        /*
        Properties 父类是 Hashtable , 底层就是 Hashtable 核心方法
        public synchronized V put(K key, V value) {
            // Make sure the value is not null
            if (value == null) {
                throw new NullPointerException();
            }
            // Makes sure the key is not already in the hashtable. Entry<?,?> tab[] = table;
            int hash = key.hashCode();
            int index = (hash & 0x7FFFFFFF) % tab.length;
            @SuppressWarnings("unchecked")
            Entry<K,V> entry = (Entry<K,V>)tab[index];
            for(; entry != null ; entry = entry.next) {
                if ((entry.hash == hash) && entry.key.equals(key)) {
                    V old = entry.value;
                    entry.value = value;//如果 key 存在,就替换
                    return old;
                }
            }
            addEntry(hash, key, value, index);//如果是新 k, 就 addEntry
            return null;
        }
        */
        properties.setProperty("charset", "utf8");
        properties.setProperty("user", "汤姆");//注意保存时,是中文的 unicode 码值
        properties.setProperty("pwd", "888888");
        //将 k-v 存储文件中即可
        properties.store(new FileOutputStream("MyTest\\src\\mysql2.properties"), null);
        System.out.println("保存配置文件成功~");
    }
}


举报

相关推荐

0 条评论