Java方法引用与排序算法精讲

3.2 特定类型的方法引用

各位小伙伴,我们继续学习特定类型的方法引用。在学习之前还是需要给大家说明一下,这种特定类型的方法引用是没有什么道理的,只是语法的一种约定,遇到这种场景,就可以这样用。

复制代码
Java约定:
    如果某个Lambda表达式里只是调用一个实例方法,并且前面参数列表中的第一个参数是作为方法的主调,后面的所有参数都是作为该实例方法的入参的,则此时就可以使用特定类型的方法引用。
格式:
    类型::方法名
复制代码
public class Test2 {
    public static void main(String[] args) {
        String[] names = {"boby", "angela", "Andy" ,"dlei", "caocao", "Babo", "jack", "Cici"};
        
        // 要求忽略首字符大小写进行排序。
        Arrays.sort(names, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                // 制定比较规则。o1 = "Andy"  o2 = "angela"
                return o1.compareToIgnoreCase(o2);
            }
        });
        
        //lambda表达式写法
        Arrays.sort(names, ( o1,  o2) -> o1.compareToIgnoreCase(o2) );
        
        //特定类型的方法引用!
        Arrays.sort(names, String::compareToIgnoreCase);
​
        System.out.println(Arrays.toString(names));
    }
}

3.3 构造器引用

各位小伙伴,我们学习最后一种方法引用的形式,叫做构造器引用。还是先说明一下,构造器引用在实际开发中应用的并不多,目前还没有找到构造器的应用场景。所以大家在学习的时候,也只是关注语法就可以了。

复制代码
Java约定:
    如果某个Lambda表达式里只是在创建对象,并且前后参数情况一致,就可以使用构造器引用。
格式:
    类名::new

现在,我们准备一个JavaBean类,Car类

复制代码
public class Car {
    private String name;
    private double price;
​
    public Car() {
​
    }
​
    public Car(String name, double price) {
        this.name = name;
        this.price = price;
    }
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    public double getPrice() {
        return price;
    }
​
    public void setPrice(double price) {
        this.price = price;
    }
​
    @Override
    public String toString() {
        return "Car{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}

因为方法引用是基于Lamdba表达式简化的,所以也要按照Lamdba表达式的使用前提来用,需要一个函数式接口,接口中代码的返回值类型是Car类型

复制代码
interface CreateCar{
    Car create(String name, double price);
}

最后,再准备一个测试类,在测试类中创建CreateCar接口的实现类对象,先用匿名内部类创建、再用Lambda表达式创建,最后改用方法引用创建。同学们只关注格式就可以,不要去想为什么(语法就是这么设计的)。

复制代码
public class Test3 {
    public static void main(String[] args) {
        // 1、创建这个接口的匿名内部类对象。
        CreateCar cc1 = new CreateCar(){
            @Override
            public Car create(String name, double price) {
                return new Car(name, price);
            }
        };
        //2、使用匿名内部类改进
        CreateCar cc2 = (name,  price) -> new Car(name, price);
​
        //3、使用方法引用改进:构造器引用
        CreateCar cc3 = Car::new;
        
        //注意:以上是创建CreateCar接口实现类对象的几种形式而已,语法一步一步简化。
        
        //4、对象调用方法
        Car car = cc3.create("奔驰", 49.9);
        System.out.println(car);
    }
}

四、常见算法

4.1 认识算法

接下来,我们认识一下什么是算法。算法其实是解决某个实际问题的过程和方法。比如百度地图给你规划路径,计算最优路径的过程就需要用到算法。再比如你在抖音上刷视频时,它会根据你的喜好给你推荐你喜欢看的视频,这里也需要用到算法。

我们为什么要学习算法呢?主要目的是训练我们的编程思维,还有就是面试的时候,面试官也喜欢问一下算法的问题来考察你的技术水平。最后一点,学习算法是成为一个高级程序员的必经之路。

当然我们现在并不会学习非常复杂的算法,万丈高楼平地起,我们现在只需要学习几种常见的基础算法就可以了。而且Java语言本身就内置了一些基础算法给我们使用,实际上自己也不会去写这些算法。

4.2 选择排序

接下来,我们学习一种算法叫排序算法,它可以价格无序的整数,排列成从小到大的形式(升序),或者从大到小的形式(降序)

排序算法有很多种,我们这里只学习比较简单的两种,一种是冒泡排序,一种是选择排序。学习算法我们先要搞清楚算法的流程,然后再去"推敲"如何写代码。(注意,我这里用的是推敲,也就是说算法这样的代码并不是一次成型的,是需要反复修改才能写好的)。

由于冒泡排序在基础阶段已经学习过了,接下来我们学习了另一种排序方法,叫做选择排序。按照刚才给大家介绍的算法的学习方式。先要搞清楚算法的流程,再去推敲代码怎么写。

所以我们先分析选择排序算法的流程:选择排序的核心思路是,每一轮选定一个固定的元素,和其他的每一个元素进行比较;经过几轮比较之后,每一个元素都能比较到了。

接下来,按照选择排序的流程编写代码

复制代码
public class Test1 {
    public static void main(String[] args) {
        // 1. 准备好一个数组
        int[] arr = {5, 1, 3, 2};
        //           0  1  2  3
​
        // 2. 控制选择几轮
        for (int i = 0; i < arr.length - 1; i++) {
            /**
             *    轮数          比较次数
             * i = 0 第一轮    j = 1 2 3
             * i = 1 第二轮    j = 2 3
             * i = 2 第三轮    j = 3
             */
            // 3. 控制每次比较的次数
            for (int j = i + 1; j < arr.length; j++) {
                // 判断当前位置是否大于后面位置处的元素值, 若大于则交换
                if (arr[i] > arr[j]){
                    int temp = arr[i];
                    arr[i] = arr[j];
                    arr[j] = temp;
                }
            }
        }
        System.out.println(Arrays.toString(arr));
    }
}

4.3 查找算法

接下来,我们学习一个查找算法叫做二分查找。在学习二分查找之前,我们先来说一下基本查找,从基本查找的弊端,我们再引入二分查找,这样我们的学习也会更加丝滑一下。

**先聊一聊基本查找:**假设我们要查找的元素是81,如果是基本查找的话,只能从0索引开始一个一个往后找,但是如果元素比较多,你要查找的元素比较靠后的话,这样查找的此处就比较多。性能比较差。

再讲二分查找 :二分查找的主要特点是,每次查找能排除一半元素,这样效率明显提高。但是二分查找要求比较苛刻,它要求元素必须是有序的,否则不能进行二分查找。

  • 二分查找的核心思路
复制代码
第1步:先定义两个变量,分别记录开始索引(left)和结束索引(right)
第2步:计算中间位置的索引,mid = (left + right) / 2;
第3步:每次查找中间mid位置的元素,和目标元素key进行比较
        如果中间位置元素比目标元素小,那就说明mid前面的元素都比目标元素小
            此时:left = mid + 1
        如果中间位置元素比目标元素大,那说明mid后面的元素都比目标元素大
            此时:right = mid - 1
        如果中间位置元素和目标元素相等,那说明mid就是我们要找的位置
            此时:把mid返回       
注意:一搬查找一次肯定是不够的,所以需要把第1步和第2步循环来做,只到left>end就结束,如果最后还没有找到目标元素,就返回-1.
复制代码
/**
 * 目标:掌握二分查找算法。
 */
public class Test2 {
    public static void main(String[] args) {
        // 准备好一个数组
        int[] arr = {7, 23, 79, 81, 103, 127, 131, 147};
​
        // 调用已存在的结果
        System.out.println(arr[binarySearch(arr, 79)]);
​
        // 调用不存在的结果   返回值是-1
        System.out.println(binarySearch(arr,190));
​
    }
​
    public static int binarySearch(int[] arr, int data){
        // 1. 定义两个变量,一个站在左边位置,一个站在右边位置
        int left = 0;
        int right = arr.length - 1;
​
        // 2. 定义一个循环控制折半
        while (left <= right){
            // 3. 每次折半,都算出中间位置处的索引
            int middle = (left + right) / 2;
            // 4. 判断当前要找的元素值,与中间位置处的元素值的大小情况
            if (data < arr[middle]){
                // 往左边找,截止位置(右边位置) = 中间位置 - 1
                right = middle - 1;
            } else if (data > arr[middle]) {
                // 往右边找,起始位置(左边位置) = 中间位置 + 1
                left = middle + 1;
            } else {
                // 中间位置处的元素值,正好等于我们要找的元素值
                return middle;
            }
        }
        return -1; // -1特殊结果,就代表没有找到数据!数组中不存在该数据!
    }
}

五、异常

5.1 认识异常

接下来,我们学习一下异常,学习异常有利于我们处理程序中可能出现的问题。

我先带着同学们认识一下,什么是异常?

我们阅读下面的代码,通过这段代码来认识异常。 我们调用一个方法时,经常一部小心就出异常了,然后在控制台打印一些异常信息。其实打印的这些异常信息,就叫做异常,说白了就是程序出现的问题。

那肯定有同学就纳闷了,我写代码天天出异常,我知道这是异常啊!我们这里学习异常,其实是为了告诉你异常是怎么产生的?只有你知道异常是如何产生的,才能避免出现异常。以及产生异常之后如何处理。

因为写代码时经常会出现问题,Java的设计者们早就为我们写好了很多个异常类,来描述不同场景下的问题。而有些类是有共性的所以就有了异常的继承体系

复制代码
    Error:代表的系统级别错误(属于严重问题),也就是说系统一旦出现问题,sun公司会把这些问题封装成Error对象给出来,说白了,Error是给sun公司自己用的,不是给我们程序员用的,因此我们开发人员不用管它。
​
    Exception:叫异常,它代表的才是我们程序可能出现的问题,所以,我们程序员通常会用Exception以及它的孩子来封装程序出现的问题。
Exception分为两类:编译时异常、运行时异常:
1. 运行时异常:RuntimeException及其子类,编译阶段不会出现错误提醒,运行时出现的异常(如:数组索引越界异常、除零异常)
2. 编译时异常:编译阶段就会出现错误提醒的。(如:日期解析异常)

先来演示一个运行时异常产生

复制代码
public class ExcpetionTest2 {
    public static void main(String[] args) {
        // System.out.println(1/0); // 除零异常
​
        // 定义数组
        int[] arr = {11,22,33};
        //5是一个不存在的索引,所以此时产生ArrayIndexOutOfBoundsExcpetion
        System.out.println(arr[5]);
    }
}

下图是API中对ArrayIndexOutOfBoundsExcpetion类的继承体系,以及告诉我们它在什么情况下产生。

再来演示一个编译时异常

我们在调用SimpleDateFormat对象的parse方法时,要求传递的参数必须和指定的日期格式一致,否则就会出现异常。 Java比较贴心,它为了更加强烈的提醒方法的调用者,设计了编译时异常,它把异常的提醒提前了,你调用方法是否真的有问题,只要可能有问题就给你报出异常提示(红色波浪线)。

编译时异常的目的:意思就是告诉你,你小子注意了!!,这里小心点容易出错,仔细检查一下

有人说,我检查过了,我确认我的代码没问题,为了让它不报错,继续将代码写下去。我们这里有两种解决方案。

  • 第一种:使用throws在方法上声明,意思就是告诉下一个调用者,这里面可能有异常啊,你调用时注意一下。
复制代码
/**
 * 目标:认识异常。
 */
public class ExcpetionTest3 {
    public static void main(String[] args) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d = sdf.parse("2029-11-11 10:24");
        System.out.println(d);
    }
}
  • 第二种:使用try...catch语句块异常进行处理。
复制代码
public class ExcpetionTest4 {
    public static void main(String[] args) {
        try { // 试图/尝试
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date d = sdf.parse("2029-11-11 10:24");
            System.out.println(d);
        } catch (ParseException e) { // 捕捉异常
            // throw new RuntimeException(e); // 抛给运行时异常
            e.printStackTrace(); // 打印异常
        }
    }
}

好了,关于什么是异常,我们就先认识到这里。

5.2 自定义异常

同学们经过刚才的学习已经认识了什么是异常了,但是Java无法为这个世界上的全部问题都提供异常类,如果企业自己的某种问题,想通过异常来表示,那就需要自己来定义异常类了。

自定义异常的种类

我们通过一个实际场景,来给大家演示自定义异常。

需求:写一个saveAge(int age)方法,在方法中对参数age进行判断,如果age<0或者>=150就认为年龄不合法,如果年龄不合法,就给调用者抛出一个年龄非法异常。

分析:Java的API中是没有年龄非法这个异常的,所以我们可以自定义一个异常类,用来表示年龄非法异常,然后在方法中抛出自定义异常即可。

  • 先写一个异常类AgeIllegalException(这是自己取的名字,名字取得很奈斯),继承
复制代码
// 必须让这个类继承自Exception,才能成为一个编译时异常类。
public class AgeIllegalRuntimeException extends RuntimeException{
    public AgeIllegalRuntimeException() {
    }
​
    public AgeIllegalRuntimeException(String message) {
        super(message);
    }
}
  • 再写一个测试类,在测试类中定义一个saveAge(int age)方法,对age判断如果年龄不在0~150之间,就抛出一个AgeIllegalException异常对象给调用者。
复制代码
public class ExcpetionTest5 {
    public static void main(String[] args) {
        // 需求:保存一个合法的年龄
        try {
            saveAge(300);
            System.out.println("底层执行是成功的!");
        } catch (Exception e) {
            // 打印异常信息
            e.printStackTrace();
            System.out.println("底层执行是出现bug的!");
        }
    }
​
    public static void saveAge(int age){
        if (age > 0 && age < 150){
            System.out.println("年龄被成功保存: " + age);
        } else {
            // 用一个异常对象封装这个问题
            // throw 抛出去这个异常对象
            throw new AgeIllegalRuntimeException("/age is illegal, your age is " + age);
        }
    }
}

再来一个编译时异常

复制代码
// 必须让这个类继承自Exception,才能成为一个编译时异常类。
public class AgeIllegalException extends Exception{
    public AgeIllegalException() {
    }
​
    public AgeIllegalException(String message) {
        super(message);
    }
}
​
public class ExcpetionTest6 {
    public static void main(String[] args) {
        // 需求:保存一个合法的年龄
        try {
            saveAge(300);
            System.out.println("编译异常底层执行是成功的!");
        } catch (Exception e) {
            // 打印异常信息
            e.printStackTrace();
            System.out.println("编译异常底层执行是出现bug的!");
        }
    }
​
    public static void saveAge(int age) throws AgeIllegalException {
        if (age > 0 && age < 150){
            System.out.println("年龄被成功保存: " + age);
        } else {
            // 用一个异常对象封装这个问题
            // throw 抛出去这个异常对象
            throw new AgeIllegalException("/age is illegal, your age is " + age);
        }
    }
}
  • 注意咯,自定义异常可以是编译时异常,也可以是运行时异常
复制代码
1.如果自定义异常类继承Excpetion,则是编译时异常。
    特点:方法中抛出的是编译时异常,必须在方法上使用throws声明,强制调用者处理。
    
2.如果自定义异常类继承RuntimeException,则运行时异常。
    特点:方法中抛出的是运行时异常,不需要在方法上用throws声明。

5.3 异常处理

同学们,通过前面两小节的学习,我们已经认识了什么是异常,以及异常的产生过程。接下来就需要告诉同学们,出现异常该如何处理了。

比如有如下的场景:A调用B,B调用C;C中有异常产生抛给B,B中有异常产生又抛给A;异常到了A这里就不建议再抛出了,因为最终抛出被JVM处理程序就会异常终止,并且给用户看异常信息,用户也看不懂,体验很不好。

此时比较好的做法就是:

  1. 将异常捕获,将比较友好的信息显示给用户看;

  2. 尝试重新执行,看是是否能修复这个问题。

我们看一个代码,main方法调用test1方法,test1方法调用test2方法,test1和test2方法中都有异常。

  • 第一种处理方式是,在main方法中对异常进行try...catch捕获处理了,给出友好提示。
复制代码
public class ExcpetionTest7 {
    public static void main(String[] args) {
        try {
            test1();
        } catch (FileNotFoundException e) {
            System.out.println("您访问的图片不存在...");
            e.printStackTrace(); // 打印出这个异常对象的信息。记录下来。
        } catch (ParseException e) {
            System.out.println("您解析的日期有误...");
            e.printStackTrace(); // 打印出这个异常对象的信息。记录下来。
        }
    }
​
    public static void test1() throws FileNotFoundException, ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d = sdf.parse("2029-11-11 10:24:00");
        System.out.println(d);
        test2();
    }
​
    public static void test2() throws FileNotFoundException {
        // 读取文件的。
        InputStream inputStream = new FileInputStream("D:/meinv.png");
    }
}

简化形式:

复制代码
public class ExcpetionTest7_2 {
    public static void main(String[] args) {
        try {
            test1();
        } catch (Exception e) {
            System.out.println("您当前的操作有误...");
            e.printStackTrace(); // 打印出这个异常对象的信息。记录下来。
        }
    }
​
    public static void test1() throws Exception {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d = sdf.parse("2029-11-11 10:24:00");
        System.out.println(d);
        test2();
    }
​
    public static void test2() throws Exception {
        // 读取文件的。
        InputStream inputStream = new FileInputStream("D:/meinv.png");
    }
}
  • 第二种处理方式是:在main方法中对异常进行捕获,并尝试修复
复制代码
/**
 * 目标:掌握异常的处理方式:捕获异常,尝试修复。
 */
public class ExcpetionTest8 {
    public static void main(String[] args) {
        // 需求:调用一个方法,让用户输入一个合适的价格返回为止。
        while (true) {
            try {
                System.out.println(getMoney());
                break;
            } catch (Exception e) {
                System.out.println("请您输入合法的数字!!!!!!!!");
            }
        }
    }
​
    public static double getMoney(){
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("请您输入合适的价格: ");
            double money = sc.nextDouble();
            if (money >= 0){
                return money;
            }else{
                System.out.println("您输入的价格是不合适的...");
            }
        }
    }
}

总结:

好了,到此我们关于今天的知识就全部学习完了。

相关推荐
xyq20241 小时前
AppML 案例未来:探索移动应用机器学习的新篇章
开发语言
Mr.朱鹏2 小时前
5.LangChain零基础速通-LCEL链式调用
python·langchain·django·大模型·llm·virtualenv
MZ_ZXD0012 小时前
springboot音乐播放器系统-计算机毕业设计源码76317
java·c语言·c++·spring boot·python·flask·php
qq_283720052 小时前
LangChain 文档切割全攻略:8 大主流切割技术选型 + 实战代码详解
python·langchain·选型·切割
DanCheng-studio2 小时前
网安毕业设计最新项目选题指导
python·毕业设计·毕设
_376271532 小时前
Cgo回调函数中处理 const char- 类型参数的正确方法
jvm·数据库·python
@小柯555m2 小时前
Java八股刷题
java·开发语言·八股
182******20832 小时前
2026新手必看:C语言学到什么程度可以出去找工作
c语言·开发语言
IT猿手2 小时前
光伏模型参数估计:山羊优化算法(Goat Optimization Algorithm, GOA)求解光伏模型参数辨识问题,免费提供完整MATLAB代码链接
开发语言·算法·matlab·智能优化算法·光伏模型参数估计·光伏模型参数辨识·最新群智能算法