JavaImprove--Lesson05--Arrays,对象排序,Lambda表达式,方法引用简化Lambda表达式

发布时间 2024-01-09 11:28:15作者: 回忆也交给时间

一.Arrays

用来操作数组的一个工具类

在Java中,没有内置的"Arrays工具类",但有一个名为java.util.Arrays的类,它包含了一些用于操作数组的静态方法。这个类主要用于数组的排序、搜索和复制

toString(类型[] arr):打印数组中的内容

int[] arr={20,30,5,8,9};
//public static String toString (类型[] arr)
//返回数组中的内容
System.out.println(Arrays.toString(arr)); //[20, 30, 5, 8, 9]
copyOfRange (类型[], 起始索引, 结束索引):复制范围内的数组,范围是左闭右开
//public static int[] copyOfRange(类型[], 起始索引, 结束索引) 左闭右开
//拷贝指定范围的数组,并返回一个新的数组
int[] copyArray = Arrays.copyOfRange(arr, 1, 3);//拷贝下标为 1,2号元素,左闭右开
System.out.println(Arrays.toString(copyArray)); //[30, 5]
copyOf(类型[], int newLength):复制指定长度的数组
//public static copyOf(类型[], int newLength)复制指定长度的数组
//长度大于原数组,补齐0,小于则复制0下标开始的一部分元素
int[] copyArr = Arrays.copyOf(arr, 10);
System.out.println(Arrays.toString(copyArr));//[20, 30, 5, 8, 9, 0, 0, 0, 0, 0]
setAll(double[] arr, IntToDoubleFunction generator):将数组中的数据改为新数据又存进去;传入参数为要改变的数组,以及一个改变的匿名函数(怎么改变原数组的规则)
 double[] ar = {32.0,15.4,89.3,52.1};
 //public static setAll(double[] arr, IntToDoubleFunction generator)
 //将数组中的数据改为新数据又存进去
 Arrays.setAll(ar, new IntToDoubleFunction() {
     //更改原始数据的方法
     @Override
     public double applyAsDouble(int value) {
         //value是传入数组的下标,一次取值,分别为0,1,2.....
         return ar[value]*10; //将数组中的每个元素扩大 10 倍
     }
 });
 System.out.println(Arrays.toString(ar)); //[320.0, 154.0, 893.0, 521.0]
sort (类型[] arr):对数组排序,默认升序
//public static void sort (类型[] arr)
//对数组排序,默认升序
Arrays.sort(ar);
System.out.println(Arrays.toString(ar)); //[154.0, 320.0, 521.0, 893.0]

 二.对象排序

对数组的基本类型元素排序是很简单的事情,我们在初学编程语言就了解了一些排序算法,比如,冒泡,插入,选择,快速排序等算法,十大基础排序算法

简单是因为,基本数据类型的元素值单一,可以直接通过值的大小进行升序或者降序排序

而当待排序的元素是对象的时候,似乎就不那么简单了

主要是基于对象的比较有很多方式可以比较,如对象在堆空间的地址大小,对象之间的各个属性值大小,这些都是可以比较的方式,所以我们在为同一类对象进行排序的时候一定要制定对应比较规则

制定比较规则的方式有两种:

一.让对象类实现Comparable接口,重写ComparaTo方法,自己制定比较规则

二.使用Arrays类的sort方法,创建Comparator比较器接口的匿名内部类对象,再制定比较规则

实现Comparable接口

Comparable 接口是 Java 中的一个标准接口,用于定义对象之间的自然顺序。当一个类实现了 Comparable 接口后,就意味着这个类的对象可以根据某个指定的属性进行排序。

要实现 Comparable 接口,类需要实现 compareTo 方法。这个方法接受另一个对象作为参数,并返回一个整数值,表示当前对象与参数对象的大小关系。返回值的具体含义如下:

  • 如果返回值为负整数,则表示当前对象小于参数对象。
  • 如果返回值为零,则表示当前对象等于参数对象。
  • 如果返回值为正整数,则表示当前对象大于参数对象

实现Comparable接口,重写ComparaTo方法:

public class Student implements Comparable<Student>{
    private String name;
    private double grade;
    private int age;
    public Student(String name, double grade, int age) {
        this.name = name;
        this.grade = grade;
        this.age = age;
    }
    @Override
    public int compareTo(Student o) {
        return 0;
    }
}

 

Comparable接口有一个泛型需要传入,直接传入当前类或接口就好了

重写的CompareTo接口就需要了解的是它的执行过程:

再启动这个方法的时候,是一个对象调用此方法和另一个对象比较,调用者就是This对象,另一个比较对象就是Student o代表的

所以两两相比较,就是this 和 o 之间的比较,比较完了,下一组又会重新刷洗 this和o所代表的对象

比如:我们需要比较年龄,年龄小的对象在前面,年龄大的在后面,注意上面的返回值约定

重写的compareTo()方法:

 @Override
 public int compareTo(Student o) {
     //约定
     //如果 this 大于 o 对象的属性值,则返回正整数
     //如果 this 小于 o 对象的属性值,则返回负整数
     //如果 this 等于 o 对象的属性值,则返回0
     if(this.age>o.age){
         return 1;
     }else if (this.age<o.age){
         return -1;
     }
     return 0;
 }

 

测试类:

public static void main(String[] args) {
    Student[] st=new Student[4];
    st[0]=new Student("李华",65,18);
    st[1]=new Student("王明",95,19);
    st[2]=new Student("张静",35,16);
    st[3]=new Student("李丹",85,21);
    Arrays.sort(st);
    System.out.println(Arrays.toString(st));
    //[Student{name='张静', grade=35.0, age=16}, 
    // Student{name='李华', grade=65.0, age=18}, 
    // Student{name='王明', grade=95.0, age=19}, 
    // Student{name='李丹', grade=85.0, age=21}]
}

 

Arrays类的sort方法,创建Comparator匿名内部类

Comparator 是 Java 中的一个接口,用于定义对象的比较逻辑。通过实现这个接口,我们可以自定义对象的排序方式。

compare(Object o1, Object o2): 这个方法用于比较两个对象。返回值与 Comparable 接口中的 compareTo 方法类似,表示 o1 和 o2 的大小关系。

它也需要遵守约定和上面compareTo方法的约定是一样的

  • 如果返回值为负整数,则表示当前对象小于参数对象。
  • 如果返回值为零,则表示当前对象等于参数对象。
  • 如果返回值为正整数,则表示当前对象大于参数对象
Arrays.sort(st, new Comparator<Student>() {
    @Override
    public int compare(Student o1, Student o2) {
        return 0;
    }
});

 

如上,compare方法的两两对象比较就是o1,o2互相比较,它的约定和上面的一样

重写 compare方法:按照成绩升序排序

//按照成绩排序
Arrays.sort(st, new Comparator<Student>() {
    @Override
    public int compare(Student o1, Student o2) {
        if(o1.getGrade()>o2.getGrade()){
            return 1;
        }else if (o1.getGrade()<o2.getGrade()){
            return -1;
        }
        return 0;
    }
});

 

Main函数展示结果:

System.out.println(Arrays.toString(st));
//[Student{name='张静', grade=35.0, age=16},
// Student{name='李华', grade=65.0, age=18},
// Student{name='李丹', grade=85.0, age=21},
// Student{name='王明', grade=95.0, age=19}]

 

要想逆序也很简单,只需要把大于改小于,把小于改大于就好了:

//按照成绩排序
Arrays.sort(st, new Comparator<Student>() {
    @Override
    public int compare(Student o1, Student o2) {
        if(o1.getGrade()<o2.getGrade()){
            return 1;
        }else if (o1.getGrade()>o2.getGrade()){
            return -1;
        }
        return 0;
    }
});

 

三.Lambda表达式

Lambda表达式是JDK8开始新增的一种语法形式;作用:用于简化匿名内部类的写法

格式:

(被重写方法的形参列表) - >{

方法体;

}

匿名内部类的写法:

抽象类

public class Animal {
    public static void main(String[] args) {
        //匿名内部类
        Dog dog = new Dog() {
            @Override
            void eat() {
                System.out.println("Dog eat~~");
            }
        };
    }
}
//抽象类
abstract class Dog{
    //抽象方法
    abstract void eat();
}

 

接口类

public class Animal {
    public static void main(String[] args) {
        //匿名内部类
       Cat cat = new Cat() {
           @Override
           public void eat() {
               System.out.println("cat eat ~~");
           }
       };
    }
}
//接口
interface Cat{
    void eat();
}

 

今天我们要学习的Lambda表达式可以简化匿名内部类的写法,但是,需要注意的是不是所有的匿名内部类都可以简化写法

抽象类的匿名内部类是不能简化的

只能简化函数式接口方法的匿名内部类

简化Cat匿名内部类的写法:

public class Animal {
    public static void main(String[] args) {
        //Lambda表达式简化
       Cat cat = () ->{
           System.out.println("cat eat ~~");
       };
    }
}
//接口
interface Cat{
     void eat();
}

 

什么是函数式接口呢?

有且仅有一个抽象方法的接口

同时如果我们看到一些接口上有@FunctionalInterface注解的接口就一定是函数式接口

@FunctionalInterface
interface Cat{
     void eat();
}

 

此注解会抑制接口只能有一个抽象方法

重写Arrays.sort()方法

原方法体:

Student[] st=new Student[4];
st[0]=new Student("李华",65,18);
st[1]=new Student("王明",95.2,19);
st[2]=new Student("张静",35,16);
st[3]=new Student("李丹",95.1,21);
//按照成绩排序
Arrays.sort(st, new Comparator<Student>() {
    @Override
    public int compare(Student o1, Student o2) {
        return Double.compare(o1.getGrade(),o2.getGrade());
    }
});
System.out.println(Arrays.toString(st));

 

我们可以看看Comparator接口是不是函数式接口:

 有注解,妥妥的函数式接口,所以可以使用Lambda表达式进行加简化Arrays.sort()方法

Arrays.sort(st,(Student o1, Student o2) -> {
    return Double.compare(o1.getGrade(),o2.getGrade());
});

 

上面的写法式最原始的Lambda表达式的写法,后面的Java开发又更新了很多新的简化规则,还能继续简化:

规则:

  1. 参数类型可以省略不写
  2. 只有一个参数时,括号()可以不写
  3. Lambda的主体方法中只有一句话时,可以省略大括号{ } 还有分号;如果这一行代码是return 语句,return 也可以不写

满足 1,2的接口:

public class Animal {
    public static void main(String[] args) {
        //Lambda表达式简化
       Cat cat = vale ->{
           System.out.println("cat eat ~~");
       };
    }
}
//接口
@FunctionalInterface
interface Cat{
     void eat(int value);
}

 

满足1,3的接口:

//原Lambda
Arrays.sort(st,(Student o1, Student o2) -> {
    return Double.compare(o1.getGrade(),o2.getGrade());
});
//继续简化
Arrays.sort(st,(o1, o2) -> Double.compare(o1.getGrade(),o2.getGrade()));

 

 

满足1,2,3的接口:

public class Animal {
    public static void main(String[] args) {
        //Lambda表达式简化
       Cat cat = vale -> System.out.println("cat eat ~~");
    }
}
//接口
@FunctionalInterface
interface Cat{
     void eat(int value);
}

 四.方法引用简化Lambda表达式

对于Lambda表达式我们知道,是用来简化函数式接口的匿名函数构造的

而方法引用可以进一步简化Lambda表达式

静态方法引用

类名::静态方法

如果某个Lambda表达式只是调用一个静态方法,并且前后参数一致,就可以使用静态方法的引用

原Lambda表达式:

public class StaticMethodUseDemo {
    public static void main(String[] args) {
        int[] arr={10,20,30,40,50};
        ToArr ta = (array,number) -> {
          return  ctrlArray(array,number);//对数组元素加 10
        };
        int[] addArray = ta.ctrlArray(arr,10);
        System.out.println(Arrays.toString(addArray)); //[20, 30, 40, 50, 60]
    }
    //对数据元素进行加减
    public static int[] ctrlArray(int[] arr,int number){
        int[] temp = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            temp[i]=arr[i]+number;
        }
        return temp;
    }
}
@FunctionalInterface
interface ToArr{
    int[] ctrlArray(int[] arr,int number);
}

 

 

注意Lambda表达式部分,只调用了一个静态方法:

        ToArr ta = (array,number) -> {
          return  ctrlArray(array,number);//对数组元素加 10
        };

 

 

它是满足方法引用的,因为它只调用了一个静态方法,且前后的参数形式都是一样的,所以还可以继续简化,类名::静态方法名

ToArr ta = StaticMethodUseDemo::ctrlArray;//对数组元素加 10

 

如上代码,以类名::方法名的方式和上面的Lambda表达式未简化的是一样的

实例方法的引用

对象名::实例方法

如果Lambda表达式中只调用一个实例方法,且前后参数形式一致,就可以使用实例方法的引用

原Lambda表达式:

public class StaticMethodUseDemo {
    public static void main(String[] args) {
        int[] arr={10,20,30,40,50};
        methodClass methodClass = new methodClass();
        ToArr ta = (array,number) ->{
          return methodClass.ctrlArray(arr,number);
        };
        int[] addArray = ta.ctrlArray(arr,10);//对数组元素加 10
        System.out.println(Arrays.toString(addArray)); //[20, 30, 40, 50, 60]
    }
}
//方法类
class methodClass{
    //对数据元素进行加减
    //实例方法非静态
    public  int[] ctrlArray(int[] arr,int number){
        int[] temp = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            temp[i]=arr[i]+number;
        }
        return temp;
    }
}
@FunctionalInterface
interface ToArr{
    int[] ctrlArray(int[] arr,int number);
}

 

我们可以观察一下Lambda表达式的部分:

        methodClass methodClass = new methodClass();
        ToArr ta = (array,number) ->{
          return methodClass.ctrlArray(arr,number);
        };

它只使用了一个实例方法,且参数前后都是一致的,所以可以使用方法引用简化Lambda表达式:

methodClass methodClass = new methodClass();
ToArr ta = methodClass::ctrlArray;

 

 

特定类型的方法引用

类型::方法

如果某个Lambda表达式只调用一个实例方法,且第一个参数作为方法的主调,后面的参数都作为该实例方法的入参,就可以使用特定类型的方法的引用

示例 (a,b,c) -> a.method(b,c ) 或者 (a,b) -> a . method(b)

原Lambda表达式:

public static void main(String[] args) {
 String[] names = {"andy","Bob","Alice","jack","desk"};//按名字排序
    //不区分大小写
    Arrays.sort(names, (o1,o2)-> {
        return o1.compareToIgnoreCase(o2); //此方法是String自带的,可以不区分字符大小写进行比较
    });
    System.out.println(Arrays.toString(names));
    //[Alice, andy, Bob, desk, jack]
}

 

 

我们注意看Lambda表达式:

Arrays.sort(names, (o1,o2)-> {
    return o1.compareToIgnoreCase(o2); //此方法是String自带的,可以不区分字符大小写进行比较
});

 

符合Lambda表达式只有一个实例方法,并且第一个参数为主调,后面的参数作为方法入参,可以继续简化:

Arrays.sort(names, String::compareToIgnoreCase); //此方法是String自带的,可以不区分字符大小写进行比较

 

构造器引用

类名::new

当某个Lambda表达式正在创建对象的时候,且前后参数一致,就可以使用构造器引用

开发中是使用的比较少,因为没有人会写一个函数式接口目的只是为了创建一个对象

public class StaticMethodUseDemo {
    public static void main(String[] args) {
        //创建一个methodClass对象
        ToArr ta = () ->{
          return new methodClass();  
        };
        methodClass creat = ta.creat();
    }
}
//方法类
class methodClass{
  
}
@FunctionalInterface
interface ToArr{
    methodClass creat();
}

 

主要看Lambda表达式:

ToArr ta = () ->{
  return new methodClass();  
};

 

Lambda的主体是创建一个对象,且参数都一致(没有参数),所以可以使用方法引用继续优化:

ToArr ta = methodClass::new;

 

总结:

Lambda表达式是蒸滴C,能简化成这样,真是没谁了