Skip to content

一、复习

1、枚举(JDK1.5 引入的)

枚举是一种特殊的类型,它的对象是固定,有限的几个常量对象。在枚举类的外面只能通过 “枚举类名.常量对象名” 的方式来选一个对象,而不是像普通类一样直接 new。

语法格式:

java
【修饰符】 enum  枚举类名{
    常量对象名, 常量对象名, 常量对象名;  //常量对象必须在第一个语句列出来

    【修饰符】 final 数据类型 变量名;  //final是建议加。如果加了final,无参构造要么没有,要么在无参构造中必须显式的给它赋值。

    //private通常省略,因为枚举类的构造器一定是private
    private 枚举类名(){

    }
    private 枚举类名(形参列表){

    }

    //....
}
  • 枚举类的直接父类固定是 java.lang.Enum
  • 枚举类没有子类,因为它的构造器私有化,子类无法调用
  • switch-case 从 JDK1.5 开始,支持枚举类型

枚举类的一些方法:

  • String name():获取枚举常量对象名
  • int ordinal():获取枚举常量对象的下标
  • static 枚举类型[] values():得到所有的枚举常量对象
  • static 枚举类型 valueOf("常量对象名"):根据名称获取具体的枚举常量对象
  • Enum 类的 toString 方法已经重写过了,默认返回的是枚举常量对象名

2、密封类

语法格式:

java
【其他修饰符】 sealed class 密封类名  permits 子类们{

}

密封类的子类是受限制的,只能是 permits 后面列出来的类,才有资格直接继承密封类。子类如果继承了密封类,那么子类必须是 3 种情况之一:

  • 继续密封 sealed
  • 直接终止 final
  • 恢复普通 non-sealed

3、记录类

语法格式:

java
【修饰符】 record 记录类名(数据类型 实例变量名, 数据类型 实例变量名){

}
  • 记录类的实例变量只能在 记录类名后面的()中声明,它们都是 final 的
  • 记录类会自动生成 get 方法、toString 方法、hashCode 和 equals 方法。所有实例变量没有 set 方法。
  • 记录类的构造器就这一个全参构造
  • 关于静态变量等成员正常声明即可

4、JUnit5 的注解

  • @Test:标记在单元测试方法上,可以作为 Java 测试程序入口。
    • 单元测试方法必须是非 private、void、()、非 staic,所在的类只有 1 个非 private 的无参构造器
  • @BeforeEach:它标记的方法会在每一个@Test 方法执行之前运行
  • @AfterEach:它标记的方法会在每一个@Test 方法执行之后运行
    • @BeforeEach、@AfterEach 标记的方法也是非 static
  • @BeforeAll:它标记的方法会所有@BeforeEach、@Test、@AfterEach 的方法之前运行,且只运行 1 次
  • @AfterAll:它标记的方法会所有@BeforeEach、@Test、@AfterEach 的方法之后运行,且只运行 1 次
    • @BeforeAll 和@AfterAll 标记的方法必须是 static

5、lombok

  • @Data:根据本类声明的实例变量列表来生成 getter/setter、toString 方法、hashCode 和 equals 方法
  • @NoArgsConstructor:无参构造
  • @AllArgsConstructor:全参构造
  • @RequiredArgsConstructor:为@NonNull 标记的实例变量初始化,它的参数是@NotNull 标记的实例变量
  • @NonNull:标记在实例变量上面,表示这个属性是必填项
  • @ToString:单独生成 toString 方法,如果要内部调用 super.toString() ,需要@ToString(callSuper=true),同理 hashCode 和 equals 方法也是一样的
  • @Builder:它标记的类需要用建造者模式来创建对象
    • 类名.builder().本类属性名(属性名).本类属性名(属性值).build()
  • @SuperBuilder:它标记的父类和子类也是用建造者模式来创建对象
    • 子类名.builder().父属性名(属性名).子属性名(属性值).build()

6、核心类库中的基础注解

  • @Override:用于标记重写的方法
  • @Deprecated:标记已过时的 xx
  • @SuppressWarnings:用于抑制警告
  • @FunctinalInterface:用于标记某个接口有且只有 1 个抽象需要被重写

7、数学计算的类型

  • Math 类:都是静态方法

    • abs 方法:求绝对值
    • pow 方法:求幂次方
    • sqrt 方法:求平方根
    • ceil:向上取整
    • floor:向下取整
    • round:四舍五入,近似值
    • random:产生[0,1)的随机值
    • max:求最大值
    • min:求最小值
    • PI:圆周率
    • .......
  • Random 类:都是非静态方法

    • nextDouble:产生[0,1)的随机值

    • nextInt

      • nextInt():产生一个 int 范围的任意值
      • nextInt(边界):产生[0, 边界)的 int 值
      • nextInt(边界 1,边界 2):产生[边界 1, 边界 2)的 int 值
    • nextBoolean:产生 true 或 false

    • ......

  • BigInteger、BigDecimal:任意大小和精度的整数和小数

    • 通过对象调用 add、subtract、mutiply、divide 等方法完成计算

二、包装类

2.1 为什么要有包装类

Java 是面向对象的语言,但是它里面延续了 C 语言的 8 种基本数据类型,它们不是对象。Java 绝大部分都是面向对象,很多语法、新特性、API 都是为对象设计的,那么 8 种基本数据类型就与它们不兼容,格格不入。8 种基本数据类型有自己的优点:简洁、明了、有丰富的运算符支持。Java 为了既能保留 8 种基本数据类型的优势,又能与对象“兼容”,所以为这 8 种基本数据类型设计了对应的包装类,可以实现基本数据类型与对象之间自由切换。

2.2 包装类型有哪些

基本数据类型包装类(类名首字母大写)
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

2.3 装箱与拆箱

装箱:把基本数据类型转为包装类的对象

拆箱:把包装类的对象转为基本数据类型的值

JDK5 之后,装箱和拆箱可以自动完成,称为自动装箱、自动拆箱,使得整个过程是无感的。

java
    @Test
    public void test1(){
        //自动装箱与拆箱
        Integer i = 1;//自动装箱。 左边是Integer(引用数据类型),右边的1是int(基本数据类型)
        Integer j = new Integer(2);//明显是对象
        int b = j;//自动拆箱。  左边是int(基本数据类型),右边j是Integer类型的对象(引用数据类型)

        System.out.println("i = " + i);
        System.out.println("b = " + b);
    }

2.4 包装类有一些常用的方法

1、将字符串转为基本数据类型的值

  • 包装类名.parseXxx
java
    @Test
    public void test2(){
        String str1 = "123";
//        int num = str1;//错误。  类型完全不兼容
        int num = Integer.parseInt(str1);

        System.out.println(num + 1);//124

        String str2 = "3.14";
        double pi = Double.parseDouble(str2);
        System.out.println(pi + 1);

        String str3 = "true";
        boolean b = Boolean.parseBoolean(str3);
        if(b){
            System.out.println("条件成立");
        }

        String str4 = "hello";
        int i = Integer.parseInt(str4);//java.lang.NumberFormatException
        System.out.println("i = " + i);
    }

2、获取每种类型的最大值、最小值

  • 包装类.MAX_VALUE
  • 包装类.MIN_VALUE
java
   @Test
    public void test3(){
        System.out.println("int最大值:" + Integer.MAX_VALUE);
        System.out.println("int最小值:" + Integer.MIN_VALUE);

        System.out.println("long最大值:" + Long.MAX_VALUE);
        System.out.println("long最小值:" + Long.MIN_VALUE);
    }

3、比较两个数据值的大小

  • 包装类名.compare(值1,值2) :当值 1>值 2 时,返回正整数;当值 1<值 2,返回负整数;当值 1=值 2,返回 0.
java
    @Test
    public void test4(){
        double a = 5.3;
        double b = 4.6;

        //要求比较a,b的大小,且当a>b,结果是整整数,a<b,结果是负整数,a=b,结果是0
       /* int result;
        if(a>b){
            result = 1;
        }else if(a<b){
            result= -1;
        }else{
            result = 0;
        }
        System.out.println("result = " + result);*/

        int result = Double.compare(a,b);
        System.out.println("result = " + result);
    }

    @Test
    public void test5(){
        char c1 = '尚';
        char c2 = '硅';

        int result = Character.compare(c1,c2);
        System.out.println("result = " + result);
    }

2.5 包装类的一些特点

1、包装类对象不可变

java
package com.atguigu.wrapper;

import org.junit.jupiter.api.Test;

public class TestWrapper2 {
    @Test
    public void test1(){
        int a = 1;
        Integer b = 1;
        int[] c = {1};

        change(a,b,c);

        System.out.println("a = " + a);//1
        System.out.println("b = " + b);//1
        System.out.println("c[0] = " + c[0]);//2
    }

    /*
    int a是基本数据类型,实参给形参的是数据值副本,形参修改与实参无关。
    Integer b是引用数据类型,实参给形参的是地址值的副本。但是因为包装类对象不可变。
        一旦要修改包装类对象的值,那么就会得到新对象
    int[] c也是引用数据类型,实参给形参的是地址值的副本。形参数组修改了元素,等级于实参数组修改了元素。
            如果形参数组指向1个新数组,那么新数组的元素修改与实参无关。
     */
    public void change(int a, Integer b, int[] c){
        a++;
        b++; //  b = new Integer(b+1)
//        c = new int[5];
        c[0]++;
    }
}

2、部分包装类对象可以被共享

正因为包装类对象不可变,所以包装类对象可以被安全的共享。如果要被共享,这些对象会被缓存起来。

因为不是所有包装类对象都是很常用的,如果把所有 包装类的对象都缓存起来,内存压力太大了,所以它只缓存部分常用的对象。

包装类数据范围缓存对象范围
Byte[-128, 127][-128, 127]
Short[-32768, 32767][-128, 127]
Integer[Integer.MIN_VALUE, Integer.MAX_VALUE][-128, 127]
Long[Long.MIN_VALUE, Long.MAX_VALUE][-128, 127]
Float[Float.MIN_VALUE, Float.MAX_VALUE]
Double[Double.MIN_VALUE, Double.MAX_VALUE]
Character[0, 65535][0,127]
Booleantrue,falsetrue,false
java
package com.atguigu.wrapper;

import org.junit.jupiter.api.Test;

public class TestWrapper3 {
    @Test
    public void test1(){
        int a = 1;
        int b = 1;
        System.out.println(a == b);//true  比较数据值

        Integer i = 1;
        Integer j = 1;
        System.out.println(i == j);//true   比较地址值
        //1在Integer的缓存对象范围内,i和j指向同一个对象
    }

    @Test
    public void test2(){
        int a = 200;
        int b = 200;
        System.out.println(a == b);//true  比较数据值

        Integer i = 200;
        Integer j = 200;
        System.out.println(i == j);//false   比较地址值
        //200不在Integer的缓存对象范围内,i和j指向不同对象
    }

    @Test
    public void test3(){

        int a = 1;
        int b = 1;
        System.out.println(a == b);//true  比较数据值

        Integer i = new Integer(1);
        Integer j = new Integer(1);
        System.out.println(i == j);//false   比较地址值
        //这里明确用new,不用缓存对象
        //只有自动装箱 或  包装类.valueOf方法得到包装类的对象,才会用缓存对象
    }

    @Test
    public void test4(){

        int a = 1;
        int b = 1;
        System.out.println(a == b);//true  比较数据值

        Integer i = Integer.valueOf(1);
        Integer j = Integer.valueOf(1);
        System.out.println(i == j);//true   比较地址值
        //这里明确用new,不用缓存对象
        //只有自动装箱 或  包装类.valueOf方法得到包装类的对象,才会用缓存对象
    }
}

3、包装类对象除了一种情况计算,其余都拆箱计算

  • 只有== 或 != 的左右两边都是包装类对象,不会拆箱的
  • 其他计算都拆箱
java
package com.atguigu.wrapper;

import org.junit.jupiter.api.Test;

public class TestWrapper4 {
    @Test
    public void test1(){
        Integer i = 200;
        Integer j = 200;
        System.out.println(i == j);//比较地址值
        //当== 或 != 的左右两边都是包装类对象,不会拆箱的

        Double d = 1.0;
//        System.out.println(i == d);//报错。 比较地址值  Java中两种不同类型且不是父子类关系的对象是不能比较地址
    }

    @Test
    public void test2(){
        Integer i = 200;
        Integer j = 250;
        System.out.println(i + j);//拆箱计算
        System.out.println(i > j);//拆箱比较

        Double d = 230.0;
        System.out.println(i + d);//拆箱计算
        System.out.println(i > d);//拆箱比较
    }

    @Test
    public void test3(){
        Integer i = 200;
        double d = 200.0;
        System.out.println(i==d);//拆箱比较
        //==左边不全是包装类
    }
}

2.6 类型转换问题特别说明

java
package com.atguigu.wrapper;

import org.junit.jupiter.api.Test;

public class TestWrapper5 {
    @Test
    public void test1(){
        int a = 1;
        Integer b = a;

        int[] arr = {1};
        //Integer[] nums = arr;//错误
        //int <->  Integer可以自动装箱与拆箱
        //int[] <-> Integer不可以 自动装箱与拆箱,也不可以自由转换
    }

    @Test
    public void test2(){
        //关于自动装箱与拆箱,只能 基本数据类型 与 自己对应的包装类之间自动装箱与拆箱
        Integer i = 1;
        Double b = 1.0;

       // Double d = 1;//错误  ,右边1是int,与Double不是对应的包装类
        Double d = 1D;//数字中D结尾代表是double类型。F结尾代表float类型。L结尾代表long类型。
    }
}

三、比较器

3.1 自然比较接口:Comparable(优先)

Java 中对象是不能直接用>,<等运算符比较大小的,那么两个对象比较大小通常需要调用方法来比较大小。因为担心各自取的方法名不一致,为了统一标准,Java 规定凡是对象要比较大小,那么对象所在的类都要实现 java.lang.Comparable 接口,重写 int compareTo(Object obj)方法。

核心类库中已经定义好的接口及其抽象方法:

java
public interface Comparable<T> { //今天先忽略<T>,这个等泛型部分讲解
     public abstract int compareTo(T o);
}

案例 1

java
package com.atguigu.compare;

import lombok.*;

@Data
@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
public class Student implements Comparable{
    @NonNull
    private String name;
    @NonNull
    private int score;
    @NonNull
    private double weight;
    private int height;

    //在compareTo方法中要说明如果比较两个学生对象的大小
    @Override
    public int compareTo(Object obj) {
        //例如:我们这里想要按照成绩比较大小
        //当我们在测试类中 s1.compareTo(s2)
        //比较this和obj对象,this就是s1, obj就是 s2
        //因为compareTo方法的形参是Object类型,所以实参s2就发生了向上转型,obj就失去了调用score等成员的能力
        //所以要向下转型
        Student other = (Student) obj;//向下转型
        return this.score - other.score;
    }
}
java
    @Test
    public void test1(){
        Student s1 = new Student("张三",86,120);
        Student s2 = new Student("李四",96,110);
//        System.out.println(s1 > s2);//错误

        int result = s1.compareTo(s2);
        System.out.println("result = " + result);//返回值是负数,代表s1<s2
    }

案例 2

java
package com.atguigu.compare;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Circle implements Comparable{
    private double radius;

    @Override
    public int compareTo(Object o) {
       //这里比较两个圆对象的半径大小
        /*if(this.radius>((Circle)o).radius){
            return 1;
        }else if(this.radius>((Circle)o).radius){
            return -1;
        }else{
            return 0;
        }*/

        return Double.compare(this.radius, ((Circle)o).radius);
    }
}
java
    @Test
    public void test2(){
        Circle c1 = new Circle(2.6);
        Circle c2 = new Circle(2.5);
        int result = c1.compareTo(c2);
        System.out.println("result = " + result);
        //返回值是正数,代表c1>c2
    }

3.2 定制比较接口:Comparator(备胎)

如果 Comparable 接口不能满足我们的新需求,就需要找 java.util.Comparator 接口:

java
@FunctionalInterface
public interface Comparator<T> {//今天先忽略<T>,这个等泛型部分讲解
    int compare(T o1, T o2);
}

案例 1

java
package com.atguigu.compare;

import java.util.Comparator;

public class WeightComparator implements Comparator {

    //我想用它来比较两个Student对象的体重大小
    @Override
    public int compare(Object o1, Object o2) {
        Student s1 = (Student) o1;
        Student s2 = (Student) o2;
        return Double.compare(s1.getWeight(), s2.getWeight());
    }
}
java
    @Test
    public void test1(){
        Student s1 = new Student("张三",86,120);
        Student s2 = new Student("李四",96,110);

        //希望这里按照体重比较大小
        WeightComparator weightComparator = new WeightComparator();
        int result = weightComparator.compare(s1, s2);
        System.out.println("result = " + result);
        //结果是正数,代表s1>s2
    }

    @Test
    public void test1_2(){
        Student s1 = new Student("张三",86,120);
        Student s2 = new Student("李四",96,110);

        //希望这里按照体重比较大小
      //  WeightComparator weightComparator = new WeightComparator();
        Comparator weightComparator = new Comparator(){
            //我想用它来比较两个Student对象的体重大小
            @Override
            public int compare(Object o1, Object o2) {
                Student s1 = (Student) o1;
                Student s2 = (Student) o2;
                return Double.compare(s1.getWeight(), s2.getWeight());
            }
        };


        int result = weightComparator.compare(s1, s2);
        System.out.println("result = " + result);
        //结果是正数,代表s1>s2
    }

案例 2

java
package com.atguigu.compare;

import java.util.Comparator;

public class HeightComparator implements Comparator {

    //我想用它来比较两个Student对象的身高
    @Override
    public int compare(Object o1, Object o2) {
        Student s1 = (Student) o1;
        Student s2 = (Student) o2;
        return Integer.compare(s1.getHeight(), s2.getHeight());
    }
}
java
 @Test
    public void test2(){
        Student s1 = new Student("张三",86,120,178);
        Student s2 = new Student("李四",96,110,180);

        //希望这里按照身高比较大小
        HeightComparator heightComparator = new HeightComparator();
        int result = heightComparator.compare(s1, s2);
        System.out.println("result = " + result);
        //结果是负数,代表s1<s2
    }


    @Test
    public void test2_2(){
        Student s1 = new Student("张三",86,120,178);
        Student s2 = new Student("李四",96,110,180);

        //希望这里按照身高比较大小
//        HeightComparator heightComparator = new HeightComparator();
        Comparator heightComparator = new Comparator(){
            //我想用它来比较两个Student对象的身高
            @Override
            public int compare(Object o1, Object o2) {
                Student s1 = (Student) o1;
                Student s2 = (Student) o2;
                return Integer.compare(s1.getHeight(), s2.getHeight());
            }
        };

        int result = heightComparator.compare(s1, s2);
        System.out.println("result = " + result);
        //结果是负数,代表s1<s2
    }

3.3 对比

例如:学生 Student 要比较大小

ComparableComparator
英语词性形容词名词
谁来实现它要比较大小的对象的类型,那么就是 Student 类实现 Comparable,代表学生对象本身具备可比较大小的功能,compareTo 方法。单独有一个类(有名字的类,也可以是匿名的类)来实现这个接口。这个类的对象用于比较两个对象对象的大小。例如:WeightComparator 类或 HeightComparator 类
比较方式学生对象.compareTo(另一个学生对象)(1)先创建 HeightComparator 或 WeightComparator 的对象,例如对象名是 c
(2)c.compare(学生对象1,学生对象2)
此时 c 相当于是裁判的角色

2.4 进一步的应用

2.4.1 模仿 Arrays 定义一个数组工具类 MyArrays

java
package com.atguigu.compare;

import java.util.Comparator;

public class MyArrays {
    //可以为所有对象数组排序(例如:冒泡排序)
    public static void sort(Object[] arr){
        for(int i=1; i<arr.length; i++){
            for(int j=1; j<=arr.length-i; j++){
                //arr[j-1] 与 arr[j] 比较

//                (Comparable)arr[j-1] //把arr[j-1]元素转为 Comparable
                //因为只有这个类型的实现类对象,才有 compareTo方法
//                ((Comparable)arr[j-1]) 这里加()是因为要先转型,再. 方法
                if(((Comparable)arr[j-1]).compareTo(arr[j])>0){
                    Object temp = arr[j-1];
                    arr[j-1] = arr[j];
                    arr[j] = temp;
                }
            }
        }
    }

    public static void sort(Object[] arr, Comparator c){
        for(int i=1; i<arr.length; i++){
            for(int j=1; j<=arr.length-i; j++){
                //arr[j-1] 与 arr[j] 比较
                if(c.compare(arr[j-1], arr[j]) > 0){
                    Object temp = arr[j-1];
                    arr[j-1] = arr[j];
                    arr[j] = temp;
                }
            }
        }
    }

/*    //可以为所有对象数组排序(例如:冒泡排序)
    public static void sort(Object[] arr){
        for(int i=1; i<arr.length; i++){
            for(int j=1; j<arr.length-i; j++){
                //arr[j-1] 与 arr[j] 比较
                if(arr[j-1] > arr[j]){ //对象不能用>,<运算符直接比较大小
                    Object temp = arr[j-1];
                    arr[j-1] = arr[j];
                    arr[j] = temp;
                }
            }
        }
    }*/
}

测试类

java
package com.atguigu.compare;

import org.junit.jupiter.api.Test;

public class TestMyArrays1 {
    @Test
    public void test1(){
        Student[] arr = new Student[3];
        arr[0] = new Student("张三",89,120,180);
        arr[1] = new Student("李四",96,150,150);
        arr[2] = new Student("王五",85,170,170);

        System.out.println("排序之前:");
        for (Student student : arr) {
            System.out.println(student);
        }
        //排序
        MyArrays.sort(arr);

        System.out.println("按照成绩升序排序之后:");
        for (Student student : arr) {
            System.out.println(student);
        }

        System.out.println("按照体重升序排序之后:");
        WeightComparator weightComparator = new WeightComparator();
        MyArrays.sort(arr, weightComparator);

        for (Student student : arr) {
            System.out.println(student);
        }

        System.out.println("按照身高升序排序之后:");
        HeightComparator heightComparator = new HeightComparator();
        MyArrays.sort(arr, heightComparator);
        for (Student student : arr) {
            System.out.println(student);
        }
    }
}

2.4.2 直接使用 Arrays 工具类

java
package com.atguigu.compare;

import org.junit.jupiter.api.Test;

import java.util.Arrays;

public class TestArrays {
    @Test
    public void test1(){
        Student[] arr = new Student[3];
        arr[0] = new Student("张三",89,120,180);
        arr[1] = new Student("李四",96,150,150);
        arr[2] = new Student("王五",85,170,170);

        System.out.println("排序之前:");
        for (Student student : arr) {
            System.out.println(student);
        }
        //排序
        Arrays.sort(arr);

        System.out.println("按照成绩升序排序之后:");
        for (Student student : arr) {
            System.out.println(student);
        }

        System.out.println("按照体重升序排序之后:");
        WeightComparator weightComparator = new WeightComparator();
        Arrays.sort(arr, weightComparator);

        for (Student student : arr) {
            System.out.println(student);
        }

        System.out.println("按照身高升序排序之后:");
        HeightComparator heightComparator = new HeightComparator();
        Arrays.sort(arr, heightComparator);
        for (Student student : arr) {
            System.out.println(student);
        }


    }
}
java
package com.atguigu.compare;

import org.junit.jupiter.api.Test;

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

public class TestArrays2 {
    @Test
    public void test1(){
        Student[] arr = new Student[3];
        arr[0] = new Student("张三",89,120,180);
        arr[1] = new Student("李四",96,150,150);
        arr[2] = new Student("王五",85,170,170);

        System.out.println("排序之前:");
        for (Student student : arr) {
            System.out.println(student);
        }
        //排序
        Arrays.sort(arr);

        System.out.println("按照成绩升序排序之后:");
        for (Student student : arr) {
            System.out.println(student);
        }

        System.out.println("按照体重升序排序之后:");
       // WeightComparator weightComparator = new WeightComparator();
        Comparator weightComparator = new Comparator(){
            //我想用它来比较两个Student对象的体重大小
            @Override
            public int compare(Object o1, Object o2) {
                Student s1 = (Student) o1;
                Student s2 = (Student) o2;
                return Double.compare(s1.getWeight(), s2.getWeight());
            }
        };

        Arrays.sort(arr, weightComparator);

        for (Student student : arr) {
            System.out.println(student);
        }

        System.out.println("按照身高升序排序之后:");
//        HeightComparator heightComparator = new HeightComparator();
        Comparator heightComparator = new Comparator(){
            //我想用它来比较两个Student对象的身高
            @Override
            public int compare(Object o1, Object o2) {
                Student s1 = (Student) o1;
                Student s2 = (Student) o2;
                return Integer.compare(s1.getHeight(), s2.getHeight());
            }
        };

        Arrays.sort(arr, heightComparator);
        for (Student student : arr) {
            System.out.println(student);
        }

    }
}

四、IO 流

4.1 File 类

File 是文件或文件夹(或目录)的抽象表现形式。每一个 File 的对象代表一个文件或文件夹。这个文件或文件夹可能是在硬盘中存在的,也可能是不存在。

4.1.1 File 类的操作方法系列 1:获取各种属性

  • length():获取文件大小
  • isFile()是文件吗
  • isDirectory():是文件夹吗
  • isHidden():是隐藏的吗
  • exists():存在吗
  • lastModified():最后修改时间
  • getName():获取文件或文件夹的名称
  • getPath():获取文件或文件夹的路径

4.1.2 文件或文件夹的路径问题:

java
package com.atguigu.file;

import org.junit.jupiter.api.Test;

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

public class TestPath {
    @Test
    public void test1(){
        /*
        斜杆的方向: 路径分隔符
        (1)\   早期windows只支持这种,现在windows两种都支持,默认或习惯仍然是 \
            因为Java中\有特殊意义的,代表转义。所以当你要表示普通的\的时候,需要\\进行转义
        (2)/   其他平台包含网址 都习惯用  /
         */

        File f = new File("D:\\atguigu\\javaee\\Java0625\\note");
    }

    @Test
    public void test2() throws IOException {//不处理,直接扔出去,谁爱处理谁处理
        /*
        (1)绝对路径:
            在windows操作系统下,以盘符开头的,例如:D:\atguigu\javaee\Java0625\note
            在其他操作系统下,以 / 开头的,就是绝对路径
         (2)相对路径
             在windows操作系统下,不是以盘符开头的,例如:  javaee\\1.txt
             在其他操作系统下,不是以 / 开头的

          相对谁?
          凡是相对都得参照物。
          如果以 @Test标记的单元测试方法为入口的, 那么它相对路径是相对与当前的module
          如果以main为入口的,那么它相对路径是相对project
         */
        File file = new File("1.txt");
        file.createNewFile();
    }

    public static void main(String[] args) throws IOException {
        File file = new File("2.txt");
        file.createNewFile();
    }

    @Test
    public void test3()throws IOException{
        File file = new File("../../1.txt");
        System.out.println("构造路径:" + file.getPath());//路径:..\..\1.txt
        System.out.println("绝对路径:" + file.getAbsolutePath());
        System.out.println("规范绝对路径:" + file.getCanonicalPath());
    }
}

4.1.3 File 类的操做方法系列 2

操作文件或文件夹(创建、删除、重命名)

  • createNewFile():创建新文件
  • mkdir():创建文件夹
  • mkdirs():创建多级文件夹
  • delete():删除文件或空文件夹
  • renameTo(新名字的File对象):重命名文件或文件夹

4.1.4 File 类的操作方法系列 3

获取上级目录或下级文件或目录

  • getParent()
  • getParentFile()
  • list()
  • listFiles()
java
package com.atguigu.file;

import org.junit.jupiter.api.Test;

import java.io.File;

public class TestFile3 {
    @Test
    public void test1(){
        File f = new File("D:\\temp\\img\\dog.jpg");
        String parent = f.getParent();
        System.out.println("parent = " + parent);
        //parent = D:\temp\img

        File parentFile = f.getParentFile();
        System.out.println("parentFile = " + parentFile);
        //parentFile = D:\temp\img
    }

    @Test
    public void test2(){
        //如果要获取下一级,首先File对象得是一个文件夹的File对象,否则没有下一级
         /*File f = new File("D:\\temp\\img\\dog.jpg");
       File[] files = f.listFiles();
        for (File sub : files) {
            System.out.println(sub);
        }*///错误,因为文件没有下一级

        File f = new File("D:\\temp\\img");
        File[] files = f.listFiles();
        for (File sub : files) {
            System.out.println(sub);
        }
    }

    @Test
    public void test3(){
        File f = new File("d:\\temp");
        //看它的下一级,如果下一级是文件夹,继续看下一级
        listAllSubs(f);
    }

    public void listAllSubs(File f){
        if(f.isDirectory()){
            //继续看下一级
            File[] files = f.listFiles();
            for (File sub : files) {
                listAllSubs(sub);//自己调用自己(递归)
            }
        }else if(f.isFile()){
            System.out.println(f);
        }
    }

    @Test
    public void test4(){
        //删除非空文件夹
        File f = new File("d:\\尚硅谷2");//找一个没用的文件夹测试
        forceDelete(f);
    }

    public void forceDelete(File f){
        if(f.isDirectory()){
            //把文件夹的下一级先删除
            File[] files = f.listFiles();
            for (File sub : files) {
                forceDelete(sub);//递归,自己调用自己
            }
        }
        f.delete();//如果是文件或已经清空下一级的空文件夹,可以通过它删除
    }

    @Test
    public void test5(){
        File f = new File("d:\\temp");
        //System.out.println("文件夹大小:" + f.length());//文件夹大小:8192(错误)
        //无法直接获取文件夹的大小

        System.out.println("文件夹大小:" + getTotalLength(f));
    }

    public long getTotalLength(File f){
        if(f.isFile()){
            return f.length();
        }else if(f.isDirectory()){
            //累加这个文件夹下的所有下一级文件或文件夹的总大小
            long sum = 0;
            File[] files = f.listFiles();
            for (File sub : files) {
                //sum += sub的大小;
                sum += getTotalLength(sub);//递归
            }
            return sum;
        }
        return 0;
    }
}

4.1.5 Hutool 工具

java
package com.atguigu.file;

import cn.hutool.core.io.FileUtil;
import org.junit.jupiter.api.Test;

import java.io.File;

public class TestHutool {
    @Test
    public void test1(){
        File f = new File("d:\\shangguigu2");
        FileUtil.del(f);//可以直接删除非空文件夹
    }

    @Test
    public void test2(){
        File f = new File("d:\\temp");
        long size = FileUtil.size(f);
        System.out.println("size = " + size);//size = 725382355
    }

}

4.2 IO 流类

IO 流的作用是用于读或写数据。

  • 读可以从文件读、从网络通道中读、从内存读取...
  • 写也可以把数据写到文件中、发送到网络通道中、写到内存中...

今天主要围绕文件的读和写。

4.2.1 文件 IO 流

文件 IO 流一共有 4 个:

  • FileInputStream:从文件读。文件字节输入流
  • FileOutputStream:把数据写到文件。文件字节输出流。
  • FileReader:从文件读。文件字符输入流。
  • FileWrtier:把数据写到文件。文件字符输出流。

分类方式之一:输入流和输出流

image-20250712164748931

分类方式之二:字节流和字符流

  • 字节流:以字节为单位。一次最少可以读取 1 个字节。1 个字节可能不是一个完整的字符。
    • 如果是非纯文本文件,那么只能使用字节流。
    • 当然纯文本文件也可以用字节流。
  • 字符流:以字符为单位。一次最少必须读取 1 个字符。1 个字符可能是 1-4 个字节。
    • 如果文件是纯文本文件,建议用字符流。

案例 1:把单词写到纯文本文件中

java
package com.atguigu.io;

import org.junit.jupiter.api.Test;

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

public class TestFileWriter {
    @Test
    public void test1() throws IOException {
        FileWriter fw = new FileWriter("d:\\1.txt");//覆盖模式

        fw.write("hello");
        fw.write("java");
        fw.flush();

        fw.write("world");
        fw.write("chai");

        fw.close();
        /*
        如果IO流要结束了,那么用close()
        如果IO流后面还要用,只是把前面的数据刷出去,用flush()
         */
    }

    @Test
    public void test2() throws IOException {
        FileWriter fw = new FileWriter("d:\\1.txt", true);//追加模式
        fw.write("尚硅谷");
        fw.write("最最棒");
        fw.close();
    }

    @Test
    public void test3() throws IOException {
        FileWriter fw = new FileWriter("d:\\2.txt");//覆盖模式
        //如果2.txt不存在,会自动创建。如果存在,现在是覆盖或追加。

        fw.write("hello");
        fw.write("java");
        fw.close();
    }

    @Test
    public void test4() throws IOException {
        FileWriter fw = new FileWriter("d:\\尚硅谷\\2.txt");//覆盖模式
        //如果文件夹不存在,不会自动创建,报错
        //java.io.FileNotFoundException: d:\尚硅谷\2.txt (系统找不到指定的路径。)

        fw.write("hello");
        fw.write("java");
        fw.close();
    }

}

案例 2:读取纯文本文件

java
package com.atguigu.io;

import org.junit.jupiter.api.Test;

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

public class TestFileReader {
    @Test
    public void test() throws IOException {
        FileReader fr = new FileReader("d:\\1.txt");
        //以字符为单位读取
        System.out.println(fr.read());//97  a
        System.out.println(fr.read());//98  b
        System.out.println(fr.read());//99  c
        System.out.println(fr.read());//23578 尚

        fr.close();
        /*
        int read():一次读1个字符,返回它的编码值(Unicode编码值)
         */
    }

    @Test
    public void test2() throws IOException {
        FileReader fr = new FileReader("d:\\1.txt");

        char[] arr = new char[5];//数组长度为5。最多一次能读取5个字符
        while(true){
            int len = fr.read(arr);
            if(len == -1){
                break;
            }
            System.out.println(len);
            System.out.println(Arrays.toString(arr));
        }
        /*
        文件共9个字符
        3次,第1次是5个,第1次是4个,第3次是返回-1

         */
    }

    @Test
    public void test3() throws IOException {
        FileReader fr = new FileReader("d:\\1.txt");

        char[] arr = new char[5];//数组长度为5。最多一次能读取5个字符
        while(true){
            int len = fr.read(arr);
            if(len == -1){
                break;
            }
           // System.out.println(Arrays.toString(arr));
            System.out.print(new String(arr,0,len));
            /*
            new String(arr,0,len) 把arr数组中len个字符取出来构建一个字符串
             */
        }
        /*
        文件共9个字符
        3次,
        第1次是5个,[a, b, c, 尚, h]
        第1次是4个,[e, l, l, o, h]
        第3次是返回-1

         */
    }

    @Test
    public void test4() throws IOException {
        FileReader fr = new FileReader("d:\\3.txt");
        //如果读的时候文件不存在,会报错java.io.FileNotFoundException: d:\3.txt (系统找不到指定的文件。)
        System.out.println(fr.read());
        fr.close();
    }
}

案例 3:字节流复制文件

java
package com.atguigu.io;

import org.junit.jupiter.api.Test;

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

public class TestCopy {
    @Test
    public void test1() throws IOException {
//        copy("d:\\temp\\img\\dog.jpg", "d:\\狗.jpg");
//        copy("d:\\1.txt","d:\\3.txt");
        copy("E:\\software\\jdk\\jdk-17_windows-x64_bin.exe","d:\\jdk-17_windows-x64_bin.exe");
    }

    /**
     * 这是一个复制文件的方法。例如:d:\temp\img\dog.jpg文件复制到 d:\狗.jpg。
     * @param srcFile String 原文件的完整路径名
     * @param destFile String 目标文件的完整路径名
     */
    public void copy(String srcFile, String destFile) throws IOException {
        FileInputStream fis = new FileInputStream(srcFile);
        FileOutputStream fos = new FileOutputStream(destFile);

        byte[] arr = new byte[1024*1024];//字节的长度,决定一次转运的字节数量
        while(true){
            int len = fis.read(arr);//装车过程,并返回装了几个字节
            if(len == -1){
                break;
            }
            fos.write(arr, 0, len);//这次装车多少,就运送多少
        }

        fis.close();
        fos.close();
    }
}