08. 面向对象高级(7)_代码块_内部类

代码块

在 Java 中,代码块是指用花括号{}括起来的一段代码。根据代码块的声明位置和修饰符的不同,主要分为静态代码块和实例代码块。

静态代码块

  1. 定义:静态代码块使用 static 关键字修饰,位于类的内部但方法的外部。
  2. 作用:静态代码块在类加载的时候优先执行,并且只执行一次,通常用于初始化静态变量。
  3. 示例
public class CodeDemo1 {
    // 静态变量
    private static String[] arr = new String[]{"张三","李四","王五"};
    public static void main(String[] args) {
        System.out.println("main方法");
    }
    //静态代码块:有静态修饰符修饰,在类加载的时候优先执行,只执行一次
    //作用:静态代码块一般用于初始化静态变量
    static {
        System.out.println("静态代码块执行了!");
        // 初始化静态变量
        //优点:在代码块中,可以保证静态变量的初始化只执行一次,减少内存消耗
        System.out.println(Arrays.toString(arr));
    }
}
  1. 使用方法

当类被加载时,静态代码块会自动执行。例如在上述代码中,当 CodeDemo1 类被加载时,静态代码块会输出 “静态代码块执行了!” 并打印静态数组 arr 的内容。

实例代码块

  1. 定义:实例代码块没有使用 static 关键字修饰,同样位于类的内部但方法的外部。
  2. 作用:实例代码块在每次创建对象时都会执行,并且在构造方法之前执行,主要用于初始化对象的实例变量。
  3. 示例
{
    System.out.println("实例代码块执行了!");
    // 初始化实例变量
    name = "小红";
}
  1. 使用方法

每次创建 CodeDemo2 类的对象时,实例代码块都会执行。例如在 main 方法中:

public class CodeDemo2 {
    // 实例变量
    private String name;
    //实例代码块: 在构造方法之前执行,每次创建对象的时候都会执行一次
    //基本作用:初始化对象的实例变量
    {
        System.out.println("实例代码块执行了!");
        // 初始化实例变量
        name = "小红";
    }
    public static void main(String[] args) {
        System.out.println("main方法 执行了!");
        new CodeDemo2();
        new CodeDemo2();

    }
}

这里创建了两个 CodeDemo2 对象,实例代码块会执行两次。

内部类

成员内部类

就成员内部类是定义在另一个类的内部,并且没有使用 static 修饰的类。它属于外部类的对象,与外部类的实例成员处于同一级别。

成员内部类对象的创建

在 Java 中,创建成员内部类的对象需要先创建外部类的对象,然后通过外部类的对象来创建内部类的对象。其语法格式如下:

外部类名.内部类名 对象名 = 外部类对象.new 内部类对象();

以下是 InnerClassDemo1.java 文件中创建 Outer 类的成员内部类 Inner 对象的示例:

Outer.Inner inner = new Outer().new Inner();

在这个示例中,我们先创建了 Outer 类的对象 new Outer(),然后通过该对象创建了 Inner 类的对象 new Inner(),并将其赋值给 inner 变量。

成员内部类实例方法访问其他成员的特点

  1. 访问外部类的静态成员

成员内部类中可以直接访问外部类的静态成员,无需通过外部类的对象。以下是 Outer.java 文件中 Inner 类的 show 方法直接访问外部类 Outer 的静态成员 name 的示例:

public void show() {
    // 成员内部类中可以直接访问外部类的静态成员
    System.out.println(name);
}
  1. 访问外部类的实例成员

成员内部类中也可以直接访问外部类的实例成员,同样无需通过外部类的对象。以下是 Outer 类的 Inner 类的 show 方法直接访问外部类 Outer 的实例成员 age 的示例:

public void show() {
    // 成员内部类中可以直接访问外部类的实例成员
    System.out.println(age);
}
  1. 获取当前寄生的外部类对象

在成员内部类的实例方法中,可以使用 外部类名.this 来引用当前寄生的外部类对象。以下是 Outer 类的 Inner 类的 show 方法使用 Outer.this 输出当前外部类的对象的示例:

public void show() {
    // 外部类的对象
    System.out.println(Outer.this);
}

示例代码解析

package nuyoah.innerclass;

public class InnerClassDemo1 {
    public static void main(String[] args) {
        People.Inner inner1 = new People().new Inner();
        inner1.show();
    }
}
class People {
    private int age = 20;
    public class Inner {
        private int age = 10;
        public void show() {
            int age = 30;
            System.out.println(age);//就近原则: 30
            System.out.println(this.age);//10
            System.out.println(People.this.age);//20
        }
    }
}
  1. 变量定义
    People 类的 age 变量:这是外部类 People 的实例变量,初始值为 20。
    Inner 类的 age 变量:这是内部类 Inner 的实例变量,初始值为 10。
    show 方法中的 age 变量:这是 show 方法的局部变量,初始值为 30。
  2. 变量访问规则
    System.out.println(age);:在 Java 里,当访问一个变量时,会遵循“就近原则”,也就是优先访问离当前代码最近的变量。在 show 方法里,最近的 age 变量是局部变量,其值为 30,所以输出为 30
    System.out.println(this.age);this 关键字在 Java 中代表当前对象的引用。在内部类 Innershow 方法里,this 指的是 Inner 类的当前对象,所以 this.age 访问的是 Inner 类的实例变量 age,其值为 10,因此输出为 10
    System.out.println(People.this.age);People.this 表示外部类 People 的当前对象引用。通过 People.this.age 可以访问外部类 People 的实例变量 age,其值为 20,所以输出为 20
  3. 本示例 考察了 Java 中变量作用域和内部类访问外部类成员的规则。在 Java 里,变量访问遵循“就近原则”,可以使用 this 关键字访问当前类的实例变量,使用外部类名.this访问外部类的实例变量。

总结

成员内部类提供了一种紧密关联外部类的方式,使得内部类可以方便地访问外部类的成员。通过先创建外部类对象,再创建内部类对象的方式,我们可以灵活地使用成员内部类。同时,外部类名.this 这种引用方式让我们能够明确地操作外部类的对象。在实际编程中,合理运用成员内部类可以提高代码的封装性和可维护性。

静态内部类

静态内部类是指在一个类的内部使用 static 关键字修饰的类。它是外部类的一个静态成员,与外部类的实例无关。

静态内部类对象的创建

创建静态内部类的对象不需要先创建外部类的对象,可直接通过 外部类名.内部类名 的方式创建。语法格式如下:

外部类名.内部类名 对象名 = new 外部类名.内部类名();

在InnerClassDemo2 中创建创建 Outer2 类的静态内部类 Inner2 对象的代码如下:

Outer2.Inner2 oi = new Outer2.Inner2();

静态内部类的特点

  • 独立于外部类实例:静态内部类不依赖于外部类的实例,可以直接创建对象。
  • 只能访问外部类的静态成员:静态内部类只能直接访问外部类的静态成员,不能直接访问外部类的非静态成员。

静态内部类访问外部类成员的规则及原因

静态内部类可以直接访问外部类的静态成员

静态成员属于类本身,而不是类的某个实例。静态内部类也是类的静态成员,它和外部类的静态成员都属于类级别,在类加载时就已经存在于内存中。因此,静态内部类可以直接访问外部类的静态成员。

如在 Inner2 类的 method 方法直接访问了外部类 Outer2 的静态成员 name

public static class Inner2 {
    public void method() {
        System.out.println("method");
        // 静态内部类可以直接访问外部类的静态成员
        System.out.println(name); 
    }
}

静态内部类不能直接访问外部类的非静态成员

非静态成员属于类的实例,只有在创建外部类的实例后才会分配内存。而静态内部类不依赖于外部类的实例,在没有外部类实例的情况下,非静态成员是不存在的。因此,静态内部类不能直接访问外部类的非静态成员。

如果在 Inner2 类的 method 方法中尝试直接访问外部类 Outer2 的非静态成员 age,会导致编译错误:

public static class Inner2 {
    public void method() {
        // 以下代码会报错,因为静态内部类不能直接访问外部类的非静态成员
        // System.out.println(age); 
    }
}

**如果要访问外部类的非静态成员,静态内部类需要先创建外部类的实例,然后通过该实例来访问。**例如:

public static class Inner2 {
    public void method() {
        Outer2 outer = new Outer2();
        System.out.println(outer.age); 
    }
}

局部内部类(了解)

局部内部类是定义在在方法中、代码块中、构造器等执行体中。

匿名内部类

匿名内部类是一种没有显式类名的内部类,它通常用于创建一个只需要使用一次的类。其书写格式如下:

接口名称 对象名 = new 接口名称() {
    // 覆盖重写接口中的所有抽象方法
    @Override
    public void 方法名() {
        // 方法实现
    }
};

或者,如果是继承一个抽象类:

抽象类名 对象名 = new 抽象类名() {
    // 覆盖重写抽象类中的所有抽象方法
    @Override
    public void 方法名() {
        // 方法实现
    }
};

使用匿名内部类实现 Animal 抽象类的示例如下:

Animal cat = new Animal() {
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
};

匿名内部类的特点

  • 没有显式类名:匿名内部类没有自己的类名,它是在创建对象的同时定义的。
  • 一次性使用:通常用于只需要使用一次的场景,避免了创建一个单独的类。
  • 本质是对象:匿名内部类本质上是一个实现了该接口或者继承了该类的子类匿名对象。
  • 编译后有类名:虽然在代码中没有显式类名,但编译后会生成一个类名,格式为 外部类名$编号.class

匿名内部类的基本作用

  • 简化代码:当只需要使用一次某个接口或抽象类的实现时,使用匿名内部类可以避免创建一个单独的类,从而简化代码。
  • 提高代码的可读性:在某些情况下,使用匿名内部类可以使代码更加简洁明了,特别是在处理事件监听器等场景中。

例如,在 Java 的 GUI 编程中,经常使用匿名内部类来实现事件监听器:

button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        // 处理按钮点击事件
        System.out.println("按钮被点击了");
    }
});

匿名内部类在开发中的常见形式

通常作为一个对象参数传输给方法。

案例分析

需求:学生,老师要参加游泳比赛。

package nuyoah.innerclass3;

public class Test2 {
    public static void main(String[] args) {
        //目标:搞清楚匿名内部类的使用形式(语法):通常可以做为一个对象参数传输给方法使用。
        //需求:学生,老师都要参加游泳比赛。
/*
        //原始方法
        //1. 学生类
        Student s1 = new Student();
        //2. 老师类
        Teacher t1 = new Teacher();
        //3. 调用方法
        start(s1);
        start(t1);
*/
        //匿名内部类
        //对象回调
        Swin s1 = new Swin() {
            @Override
            public void Swinming() {
                System.out.println("学生参加游泳比赛");
            }
        };
        start(s1);
        System.out.println("-----------------------");
        start(new Swin() {
            @Override
            public void Swinming() {
                System.out.println("老师参加游泳比赛");
            }
        });

    }

    //设计一个方法,接收学生、老师开始比赛
    public static void start(Swin s) {
        System.out.println("开始比赛");
        s.Swinming();
        System.out.println("比赛结束");
    }
}

/*
//学生类
class Student implements Swin {
    @Override
    public void Swinming() {
        System.out.println("学生参加游泳比赛");
    }
}
//老师类
class Teacher implements Swin {
    @Override
    public void Swinming() {
        System.out.println("老师参加游泳比赛");
    }
}
*/

//定义一个接口
interface Swin {
    void Swinming();
}

在 Test2.java 文件中,start 方法接收一个 Swin 接口类型的参数,而匿名内部类可以创建一个实现了 Swin 接口的对象,这个对象可以直接作为参数传递给 start 方法。

例如

start(new Swin() {
    @Override
    public void Swinming() {
        System.out.println("老师参加游泳比赛");
    }
});

这里直接在调用 start 方法时创建了一个匿名内部类对象,该对象实现了 Swin 接口的 Swinming 方法,然后将这个对象作为参数传递给 start 方法。

两种方法的差别

传统方法

//原始方法
//1. 学生类
Student s1 = new Student();
//2. 老师类
Teacher t1 = new Teacher();
//3. 调用方法
start(s1);
start(t1);

优点

  • 代码结构清晰:定义了明确的 Student 和 Teacher 类,代码的可读性和可维护性较高,便于后续的扩展和修改。
  • 可复用性强:Student 和 Teacher 类可以在其他地方重复使用。

缺点

  • 代码冗余:如果只是临时使用一次实现 Swin 接口的功能,创建专门的类会增加代码量。
  • 灵活性差:对于一些简单的、只需要使用一次的功能,创建专门的类显得过于繁琐。

匿名内部类方法

//匿名内部类
//对象回调
Swin s1 = new Swin() {
    @Override
    public void Swinming() {
        System.out.println("学生参加游泳比赛");
    }
};
start(s1);
System.out.println("-----------------------");
start(new Swin() {
    @Override
    public void Swinming() {
        System.out.println("老师参加游泳比赛");
    }
});

优点

  • 代码简洁:无需创建专门的类,直接在需要使用的地方定义并实现接口,减少了代码量。
  • 灵活性高:适合临时使用的场景,能够快速实现接口的功能。

缺点

  • 可读性差:如果匿名内部类的代码比较复杂,会降低代码的可读性。
  • 可复用性低:匿名内部类只能使用一次,无法在其他地方复用。

匿名内部类在开发中的真实使用场景示例

调用别人提供的方法实现需求时,这个方法正好可以让我们传输一个匿名内部类对象给其使用。

需求:创建一个登录窗口,窗口上只有一个登录按钮

public class Test3 {
    public static void main(String[] args) {
        //目标:搞清楚几个匿名内部类的使用场景。
        //需求:创建一个登录窗口,窗口上只有一个登录按钮
        JFrame jf = new JFrame("登录窗口");
        jf.setSize(400, 300);
        jf.setLocationRelativeTo(null);
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel jp = new JPanel();
        jf.add(jp);
        JButton jbtn = new JButton("登录");
        jp.add(jbtn);
        //Java要求必须给这个按钮添加一个点击事件监听器对象,这样就可以监听用户的点击操作,就可以做出反应。
        //开发中不是我们要主动去写匿名内部类,而是用别人的功能的时候,别人可以让我们写一个匿名内部类,我们才会写!|
        jbtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("登录按钮被点击了");
            }
        });
        jf.setVisible(true);
    }
}

应用场景分析

  1. 简化事件处理代码
    在 Java 的 GUI 编程中,常常需要为组件(如按钮、菜单等)添加事件监听器。通常情况下,事件监听器是通过实现特定的接口(如 ActionListener)来完成的。如果为每个事件监听器都创建一个单独的类,会导致类的数量增多,代码结构变得复杂。而使用匿名内部类可以在创建监听器对象的同时实现接口方法,避免了创建大量的类,使代码更加简洁。

在上述代码中,ActionListener 是一个接口,用于处理按钮的点击事件。通过匿名内部类,我们可以直接在 addActionListener 方法中创建一个实现了 ActionListener 接口的对象,并实现 actionPerformed 方法。这样,当按钮被点击时,actionPerformed 方法就会被调用,输出相应的信息。

  1. 一次性使用的对象
    匿名内部类通常用于创建只需要使用一次的对象。在这个例子中,按钮的点击事件监听器只在这个按钮的生命周期内有效,不需要在其他地方复用。因此,使用匿名内部类可以避免创建一个专门的类来实现这个监听器,减少了代码的冗余。
  2. 即时实现接口
    匿名内部类允许我们在需要的时候即时实现一个接口。在这个例子中,我们在调用 addActionListener 方法时,直接创建了一个实现了 ActionListener 接口的对象,而不需要提前定义一个类来实现这个接口。这种方式使得代码更加灵活,能够根据实际需求动态地实现接口。

注:开发中不是我们要主动去写匿名内部类,而是用别人的功能的时候,别人可以让我们写一个匿名内部类,我们才会写!

使用comparator接口的匿名内部类实现对数组进行排序

需求:完成给数组排序,理解其中匿名内部类的用法

package nuyoah.innerclass3;

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

public class Test4 {
    public static void main(String[] args) {
        //目标:完成给数组排序,理解其中匿名内部类的用法。
        //准备一个学生类型的数组,存放6个学生对象。 姓名 年龄 性别 身高
        Student[] students = new Student[6];
        students[0] = new Student("张三丰", 100, "男", 180);
        students[1] = new Student("周芷若", 18, "女", 170);
        students[2] = new Student("赵敏", 19, "女", 160);
        students[3] = new Student("小昭", 17, "女", 150);
        students[4] = new Student("灭绝师太", 90, "女", 180);
        students[5] = new Student("张无忌", 18, "男", 170);
        //遍历数组
        System.out.println("排序前:");
        for (int i = 0; i < students.length; i++) {
            System.out.println(students[i]);
        }
        //需求:按照学生的年龄进行升序排序。
        //public static void sort(T[] a, Comparator<T> c)
        //                       数组       比较器(指定排序的规则)
        Arrays.sort(students, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                //o1表示当前要比较的元素
                //o2表示已经比较过的元素
                //指定比较规则:
                // 如果o1的年龄大于o2的年龄,返回正数,
                // 如果o1的年龄小于o2的年龄,返回负数,
                // 如果o1的年龄等于o2的年龄,返回0
                //sort方法会调用匿名内部类对象的compare方法,对数组中的学生对象进行两两比较,从而实现排序
                int age1 = o1.getAge();
                int age2 = o2.getAge();
                return age1 - age2;
            }
        });
        //遍历排序后数组
        System.out.println("------------------");
        System.out.println("排序后:");
        for (int i = 0; i < students.length; i++) {
            System.out.println(students[i]);
        }
    }
}

匿名内部类的应用场景及代码分析

Test4.java 文件中,我们的目标是对 Student 类型的数组进行排序,使用了 Arrays.sort 方法,并且通过匿名内部类来指定排序规则

Student.java 定义了 Student 类,包含学生的姓名、年龄、性别和身高信息。

Test4.java文件中,以下代码使用了匿名内部类:

// ... 已有代码 ...
// 需求:按照学生的年龄进行升序排序。
// public static void sort(T[] a, Comparator<T> c)
//                        数组       比较器(指定排序的规则)
Arrays.sort(students, new Comparator<Student>() {
    @Override
    public int compare(Student o1, Student o2) {
        // o1 表示当前要比较的元素
        // o2 表示已经比较过的元素
        // 指定比较规则:
        // 如果 o1 的年龄大于 o2 的年龄,返回正数,
        // 如果 o1 的年龄小于 o2 的年龄,返回负数,
        // 如果 o1 的年龄等于 o2 的年龄,返回 0
        // sort 方法会调用匿名内部类对象的 compare 方法,对数组中的学生对象进行两两比较,从而实现排序
        int age1 = o1.getAge();
        int age2 = o2.getAge();
        return age1 - age2;
    }
});
// ... 已有代码 ...

详细解释

  1. 匿名内部类的作用

Arrays.sort 方法可以对数组进行排序,但是它需要一个 Comparator 对象来指定排序规则。Comparator 是一个接口,我们需要实现其 compare 方法来定义比较逻辑。

在这个案例中,我们使用匿名内部类实现了Comparator<Student>接口,为 Arrays.sort 方法提供了排序规则。通过匿名内部类,我们可以在不创建一个单独的类来实现 Comparator 接口的情况下,直接定义比较逻辑。

  1. 匿名内部类的语法
new Comparator<Student>() {
    @Override
    public int compare(Student o1, Student o2) {
        // 比较逻辑
    }
}

这里new Comparator()表示创建一个实现了 Comparator 接口的对象,后面的{ ... }是匿名内部类的主体,在其中我们重写了 compare 方法。

  1. compare 方法的实现
    compare 方法中,我们定义了比较规则:

如果 o1 的年龄大于 o2 的年龄,返回正数。
如果 o1 的年龄小于 o2 的年龄,返回负数。
如果 o1 的年龄等于 o2 的年龄,返回 0。
Arrays.sort 方法会根据 compare 方法的返回值来对数组中的元素进行排序。当返回正数时,o1 会排在 o2 后面;当返回负数时,o1 会排在 o2 前面;当返回 0 时,它们的相对顺序不变。

总结
在这个案例中,匿名内部类的使用使得我们可以简洁地为Arrays.sort方法提供排序规则,避免了创建一个单独的类来实现 Comparator 接口,提高了代码的简洁性和可读性。

函数式编程

什么是函数式编程?有什么好处?

  • 定义:函数式编程(Functional Programming)是一种编程范式,它将计算视为数学函数的求值,并避免改变状态和可变数据。在函数式编程中,函数是第一等公民,可以作为参数传递、返回值或存储在变量中。
  • 好处
    • 简洁性:代码更加简洁,易于阅读和维护。
    • 并行处理:由于函数式编程避免了副作用(即不改变外部状态),因此更容易实现并行处理。
    • 模块化:函数式编程鼓励编写小而独立的函数,这使得代码更易于测试和复用。
    • 减少错误:通过避免可变状态,减少了因共享状态导致的潜在问题。

Lambda 表达式

Lambda表达式是啥?有什么用?怎么写?

public class LambdaDemo1 {
    public static void main(String[] args) {
        //目标:认识Lambda表达式,搞清楚其作用
        Animal a = new Animal() {
            @Override
            public void eat() {
                System.out.println("猫吃鱼");
            }
        };
        a.eat();

       //错误示范:Lambda并不是可以简化全部的匿名内部类,Lambda只能简化函数式接口的匿名内部类,
/*        Animal a1 = () -> {
            System.out.println("猫吃鱼");
        };
        a1.eat();*/

        System.out.println("-----------常规写法------------");
        //常规的写法
        Swim s=new Swim() {
            @Override
            public void swimming() {
                System.out.println("狗刨");
            }
        };
        s.swimming();
        System.out.println("-----------Lambda表达式写法------------");
        //Lambda表达式的原理?上下文推断
        //Lambda表达式的标准格式:
        //(参数列表)->{
        //    方法体
        //}
        Swim s1=()->{
            System.out.println("狗刨");
        };
        s1.swimming();
    }
}

abstract class Animal{
    public abstract void eat();
}

//函数式接口:接口中只有一个抽象方法的接口,称之为函数式接口。
@FunctionalInterface  //注解:声明一个函数式接口
interface Swim{
    void swimming();
}
  • 定义:Lambda表达式是Java 8引入的一种简化匿名内部类的语法糖,用于实现函数式接口的实例化。
  • 作用
    • 简化代码:相比于传统的匿名内部类,Lambda表达式更加简洁。
    • 提高效率:使代码更易读,尤其在集合操作(如Stream API)中非常有用。
  • 写法
(参数列表) -> {
    方法体
}
- **示例**:
Swim s1 = () -> {
    System.out.println("狗刨");
};
s1.swimming();

上述代码中,Swim是一个函数式接口,s1通过Lambda表达式实现了swimming方法。


什么样的接口是函数式接口?怎么确保一个接口必须是函数式接口?

  • 定义:函数式接口是指仅包含一个抽象方法的接口。
    • 示例:Swim接口中只有一个抽象方法swimming(),因此它是函数式接口。
  • 确保方法
    • 使用@FunctionalInterface注解声明接口为函数式接口。
      • 如果接口不符合函数式接口的定义(例如包含多个抽象方法),编译器会报错。
    • 示例
@FunctionalInterface
interface Swim {
    void swimming();
}

在上述代码中,@FunctionalInterface注解确保了Swim接口只能有一个抽象方法。如果尝试添加第二个抽象方法,编译器会抛出错误。


Lambda 表达式的简化

public class LambdaDemo2 {
    public static void main(String[] args) {
        //目标:Lambda表达式简化的匿名内部类
        test1();
        test2();


    }

    public static void test1() {
        //目标:完成给数组排序,理解其中匿名内部类的用法。
        //准备一个学生类型的数组,存放6个学生对象,倚天屠龙记人物角色。 姓名 年龄 性别 身高
        Student[] students = new Student[6];
        students[0] = new Student("张三丰", 100, "男", 180);
        students[1] = new Student("周芷若", 18, "女", 170);
        students[2] = new Student("赵敏", 19, "女", 160);
        students[3] = new Student("小昭", 17, "女", 150);
        students[4] = new Student("灭绝师太", 90, "女", 180);
        students[5] = new Student("张无忌", 18, "男", 170);

        //遍历数组
        System.out.println("排序前:");
        for (int i = 0; i < students.length; i++) {
            System.out.println(students[i]);
        }
        //Lambda表达式完整写法
        Arrays.sort(students, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                //sort方法会调用匿名内部类对象的compare方法,对数组中的学生对象进行两两比较,从而实现排序
                int age1 = o1.getAge();
                int age2 = o2.getAge();
                return age1 - age2;
            }
        });

        //1. lambda表达式简化
       Arrays.sort(students,(Student o1,Student o2)->{
           return o1.getAge()-o2.getAge();
       });
        
        //2. lambda表达式进一步简化——参数类型全部可以省略不写。
        Arrays.sort(students, (o1, o2) -> o1.getAge() - o2.getAge());
        
        //遍历排序后数组
        System.out.println("------------------");
        System.out.println("排序后:");
        for (int i = 0; i < students.length; i++) {
            System.out.println(students[i]);
        }
    }

    public static void test2() {
        //目标:搞清楚几个匿名内部类的使用场景。
        //需求:创建一个登录窗口,窗口上只有一个登录按钮
        JFrame jf = new JFrame("登录窗口");
        jf.setSize(400, 300);
        jf.setLocationRelativeTo(null);
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel jp = new JPanel();
        jf.add(jp);
        JButton jbtn = new JButton("登录");
        jp.add(jbtn);
        
        //Lambda表达式完整写法
        jbtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("登录按钮被点击了");
            }
        });
        
        System.out.println("----------------");
        //1. Lambda表达式简化
        jbtn.addActionListener((ActionEvent e) -> {
            System.out.println("登录按钮被点击了");
        });
        System.out.println("----------------");
        //2. Lambda表达式进一步简化——参数类型全部可以省略不写。
        jbtn.addActionListener(e -> {
            System.out.println("登录按钮被点击了");
        });
        System.out.println("----------------");
        //3. Lambda表达式进一步简化——如果Lambda表达式中只有一行代码,大括号可以不写,同时要省略分号“;”
        jbtn.addActionListener(e -> System.out.println("登录按钮被点击了"));
        jf.setVisible(true);
    }
}

LambdaDemo2.java中,通过不同的场景展示了Lambda表达式的简化方式。以下是详细的解释:


完整形式:参数类型和方法体都明确写出
  • 特点:Lambda表达式的标准格式,包含完整的参数列表和方法体。
  • 示例(来自test1方法):
Arrays.sort(students, (Student o1, Student o2) -> {
    return o1.getAge() - o2.getAge();
});
  • 解释
    • 参数类型Student被显式声明。
    • 方法体用大括号包裹,并以return语句返回结果。

省略参数类型
  • 特点:编译器可以通过上下文推断出参数类型,因此可以省略参数类型声明。
  • 示例(来自test1方法):
Arrays.sort(students, (o1, o2) -> o1.getAge() - o2.getAge());
  • 解释
    • 参数类型Student被省略,编译器根据Comparator<Student>接口的泛型信息自动推断出o1o2的类型为Student
    • 方法体仍然保留大括号和return语句。

进一步简化:单行代码时省略大括号和分号
  • 特点:如果Lambda表达式的方法体只有一行代码,则可以省略大括号、return关键字以及分号。
  • 示例(来自test2方法):
jbtn.addActionListener(e -> System.out.println("登录按钮被点击了"));
  • 解释
    • 参数类型ActionEvent被省略,编译器根据ActionListener接口的定义自动推断出e的类型。
    • 方法体只有一行代码System.out.println("登录按钮被点击了"),因此省略了大括号和分号。

总结
简化方式示例代码特点
完整形式(Student o1, Student o2) -> { return o1.getAge() - o2.getAge(); }参数类型和方法体完整写出
省略参数类型(o1, o2) -> o1.getAge() - o2.getAge()编译器自动推断参数类型
单行代码进一步简化e -> System.out.println("登录按钮被点击了")如果方法体只有一行代码,省略大括号、return关键字和分号

方法引用

静态方法的引用

定义

静态方法引用是指当某个Lambda表达式中仅调用一个静态方法,并且“→”前后参数的形式一致时,可以使用静态方法引用。其格式为:类名::静态方法名

示例代码分析(来自 Demo1.java
Arrays.sort(students, (o1, o2) -> Student.compareByAge(o1, o2));
// 简化为:
Arrays.sort(students, Student::compareByAge);
  • 原始代码(o1, o2) -> Student.compareByAge(o1, o2) 表示通过 Student 类的静态方法 compareByAge 对两个学生对象进行比较。
  • 简化后Student::compareByAge 直接引用了 Student 类中的静态方法 compareByAge,编译器会自动将数组中的元素作为方法的参数传递。
使用场景

适用于需要调用某个类的静态方法,并且该方法的参数与上下文一致的情况。


实例方法的引用

定义

实例方法引用是指当某个Lambda表达式中仅通过对象名称调用一个实例方法,并且“→”前后参数的形式一致时,可以使用实例方法引用。其格式为:对象名::实例方法名

示例代码分析(来自 Demo2.java
Student s = new Student();
Arrays.sort(students, (o1, o2) -> s.compareByHeight(o1, o2));
// 简化为:
Arrays.sort(students, s::compareByHeight);
  • 原始代码(o1, o2) -> s.compareByHeight(o1, o2) 表示通过 s 对象的实例方法 compareByHeight 对两个学生对象进行比较。
  • 简化后s::compareByHeight 直接引用了 s 对象的实例方法 compareByHeight,编译器会自动将数组中的元素作为方法的参数传递。
使用场景

适用于需要调用某个对象的实例方法,并且该方法的参数与上下文一致的情况。


特定类型方法的引用

定义

特定类型方法引用是指当某个Lambda表达式中仅调用一个特定类型的实例方法,并且第一个参数作为方法的主调对象,其余参数作为方法的入参时,可以使用特定类型的方法引用。其格式为:类型名称::方法名

示例代码分析(来自 Demo3.java
Arrays.sort(names, (o1, o2) -> o1.compareToIgnoreCase(o2));
// 简化为:
Arrays.sort(names, String::compareToIgnoreCase);
  • 原始代码(o1, o2) -> o1.compareToIgnoreCase(o2) 表示通过字符串对象的 compareToIgnoreCase 方法对两个字符串进行忽略大小写的比较。
  • 简化后String::compareToIgnoreCase 直接引用了 String 类型的实例方法 compareToIgnoreCase,编译器会自动将数组中的元素作为方法的参数传递。
使用场景

适用于需要调用某个特定类型的实例方法,并且该方法的第一个参数作为主调对象,其余参数作为方法入参的情况。


总结

引用类型格式示例代码使用场景
静态方法引用类名::静态方法名Arrays.sort(students, Student::compareByAge);调用某个类的静态方法,且方法参数与上下文一致。
实例方法引用对象名::实例方法名Arrays.sort(students, s::compareByHeight);调用某个对象的实例方法,且方法参数与上下文一致。
特定类型方法引用类型名称::方法名Arrays.sort(names, String::compareToIgnoreCase);调用某个特定类型的实例方法,且第一个参数作为主调对象,其余参数作为入参。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值