Skip to content

如何快速创建子类对象 ​

我们在遇到接口或者抽象类类型的变量, 我们需要给该类型变量赋值一个对象, 但是我们深知, 接口和抽象类是不能直接实例化对象的, 只能创建接口和抽象类的子类对象或者实现类对象才行, 那么除了传统的定义一个类继承抽象类或者实现接口之外还有其他方案简化子类对象的创建吗? 答案是有的, Java还提供了以下几种方式:

  • 匿名内部类
  • lambda
  • 方法引用

1.1.匿名内部类 ​

提示

本质: 创建了已知类或者已知接口的子类对象(实现类也叫子类)。

语法格式:

java
new 已知类型 | 已知接口(对参数赋值){
    对方法进行重写
}

例如:

java
interface A {
    void test();
}

public class Demo1 {
    public static void main(String[] args) {
        // 采用匿名内部类创建对象
        A a = new A() {
            @Override
            public void test() {
                System.out.println("test");
            }
        };
    }
}

注意

  1. 匿名内部类也是类, 会在磁盘中形成物理*.class*文件
  2. 匿名内部类本质创建一个已知类或者已知接口的子类对象
  3. 当我们重写的方法超过4个时候不建议使用匿名内部类
  4. 当我们重写方法过于复杂, 内容比较多, 也不建议使用内部类
  5. 当子类对象很多地方都要使用的时候, 不建议使用匿名内部类,复用性很差

案例 : 按照规则对订单进行排序:

java
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
class Order {
    String nickname;
    String category;
    double amount;
    int count;
}

// 有多少个排序规则就需要写多少个实现类(根本写不完)
class AmountComparator implements Comparator<Order> {

    @Override
    public int compare(Order o1, Order o2) {
        return Double.compare(o1.getAmount(), o2.getAmount());
    }
}

class CountComparator implements Comparator<Order> {

    @Override
    public int compare(Order o1, Order o2) {
        return o2.getCount() - o1.getCount();
    }
}

/**
 * 对订单列表按照订单金额进行排序
 */
public class Demo2 {
    public static void main(String[] args) {
        List<Order> orderList = new ArrayList<>();
        orderList.add(new Order("小王", "咖啡", 10, 1));
        orderList.add(new Order("小张", "咖啡", 10, 2));
        orderList.add(new Order("小李", "中国茶", 15, 3));
        orderList.add(new Order("小赵", "奶茶", 20, 20));
        orderList.add(new Order("小孙", "咖啡", 10, 5));
        orderList.add(new Order("小周", "中国茶", 15, 1));
        orderList.add(new Order("小吴", "奶茶", 20, 2));
        orderList.add(new Order("小郑", "咖啡", 10, 3));
        orderList.add(new Order("小王", "咖啡", 10, 4));
        orderList.add(new Order("小张", "中国茶", 15, 5));
        orderList.add(new Order("小李", "咖啡", 10, 6));

        // 对订单列表按照金额进行排序
        // orderList.sort(new AmountComparator()); 
        orderList.sort(new Comparator<Order>() {
            @Override
            public int compare(Order o1, Order o2) {
                return Double.compare(o1.getCount(), o2.getCount());
            }
        });

        orderList.forEach(order -> System.out.println(order));
    }
}

1.2.Lambda ​

针对上面代码中的问题,我们使用Lambda表达式进行代码的简化操作。

Lambda表达式是JDK 8开始新增的一种语法形式,

作用: 用于简化函数式接口匿名内部类对象的创建

函数式接口: 指的是有且仅有一个抽象方法的接口(可以使用@FunctionalInterface注解来检查),例如:

java
@FunctionalInterface
public interface A{
    void method();
}

Lambda完整语法表达式:

java
(抽象方法参数列表) -> {
    对唯一的抽象方法的重写
}

基本使用如下:

java
// 函数式接口 : 有且仅有一个抽象方法的接口就是函数式接口
@FunctionalInterface // 检查当前接口是否符合规则
interface InterA {
    void show();
}

public class Demo1 {

    public static void main(String[] args) {
        // Lambda表达式简化匿名内部类书写
        InterA a = () -> {
            System.out.println("Lambda表达式重写的show方法执行了....");
        };

        // 使用对象调用方法
        a.show();
    }
}

温馨提示

  1. 参数类型可以省略不写
  2. 如果只有一个参数,小括号()也可以省略
  3. 如果方法体代码只有一行代码,可以省略花括号{}不写,同时要省略分号!此时,如果这行代码是return语句,也必须去掉return不写

简化之后的Lambda表达式的书写:

java
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
class Order {
    String nickname;
    String category;
    double amount;
    int count;
}

/**
 * 对订单列表按照订单金额进行排序
 */
public class Demo2 {
    public static void main(String[] args) {
        List<Order> orderList = new ArrayList<>();
        orderList.add(new Order("小王", "咖啡", 10, 1));
        orderList.add(new Order("小张", "咖啡", 10, 2));
        orderList.add(new Order("小李", "中国茶", 15, 3));
        orderList.add(new Order("小赵", "奶茶", 20, 20));
        orderList.add(new Order("小孙", "咖啡", 10, 5));
        orderList.add(new Order("小周", "中国茶", 15, 1));
        orderList.add(new Order("小吴", "奶茶", 20, 2));
        orderList.add(new Order("小郑", "咖啡", 10, 3));
        orderList.add(new Order("小王", "咖啡", 10, 4));
        orderList.add(new Order("小张", "中国茶", 15, 5));
        orderList.add(new Order("小李", "咖啡", 10, 6));

        // 对订单列表按照金额进行排序
        orderList.sort((o1, o2) -> Double.compare(o1.getCount(), o2.getCount()));

        orderList.forEach(order -> System.out.println(order));

        System.out.println("------------------------------");
        // 根据条件删除订单
        orderList.removeIf(order -> order.getCount() <= 2);

        orderList.forEach(order -> System.out.println(order));
    }
}

1.3.方法引用 ​

当 Lambda 体只是调用一个已有方法,参数匹配,就可以用方法引用。是在特定的情况下对Lambda方法的进一步简化。

1.3.1.方法引用1->对象::实例方法 ​

已有对象,调用它的实例方法。语法如下:

Java
// 语法  对象::实例方法
class Printer {
    public void print(String s) {
        System.out.println(s);
    }
}

@FunctionalInterface
interface MyFunc {
    void apply(String str);
}

public class Demo {
    public static void main(String[] args) {
        Printer printer = new Printer();
        // 匿名内部类版本
        MyFunc f1 = new MyFunc() {
            @Override
            public void apply(String str) {
                printer.print(str);
            }
        };

        // Lambda
        MyFunc f2 = str -> printer.print(str);

        // ✅方法引用:对象::实例方法
        MyFunc f3 = printer::print;

        f3.apply("测试文本");
    }
}

1.3.2.方法引用2->类::静态方法 ​

调用类的 static 方法。

Java
// 语法  类::静态方法
class Util {
    public static void show(String s) {
        System.out.println(s);
    }
}

// 匿名内部类
MyFunc f1 = new MyFunc() {
    @Override
    public void apply(String str) {
        Util.show(str);
    }
};
// Lambda
MyFunc f2 = str -> Util.show(str);
// ✅方法引用:类名::静态方法
MyFunc f3 = Util::show;

1.3.3.方法引用3->类::实例方法(特殊) ​

第一个参数作为方法调用者,后面参数作为方法参数。

java
@FunctionalInterface
interface TestFunc {
    void run(String s);
}

// 匿名内部类
TestFunc f1 = new TestFunc(){
    @Override
    public void run(String s){
        s.toUpperCase();
    }
};
// Lambda
TestFunc f2 = s -> s.toUpperCase();
// ✅方法引用:类::实例方法
TestFunc f3 = String::toUpperCase;

1.3.4.方法引用4->类::new 构造器引用 ​

引用构造方法,相当于 new 对象。

java
@FunctionalInterface
interface PersonFactory {
    Person create(String name);
}

class Person {
    String name;
    public Person(String name) {
        this.name = name;
    }
}

public class Demo {
    public static void main(String[] args) {
        // 匿名内部类
        PersonFactory f1 = new PersonFactory() {
            @Override
            public Person create(String name) {
                return new Person(name);
            }
        };
        // Lambda
        PersonFactory f2 = name -> new Person(name);
        // ✅构造器方法引用:类名::new
        PersonFactory f3 = Person::new;

        Person p = f3.create("李四");
    }
}

1.3.5.集合遍历的经典例子 ​

java
List<String> list = Arrays.asList("A","B","C");

// 匿名内部类
list.forEach(new Consumer<String>() {
    @Override
    public void accept(String s) {
        System.out.println(s);
    }
});

// Lambda
list.forEach(s -> System.out.println(s));

// 方法引用
list.forEach(System.out::println);

请注意!!

匿名内部类 和 方法引用的区别

  1. 匿名内部类:可以实现任意接口 / 继承类,可以有多个抽象方法,可以写多个方法、变量、代码块。不要求函数式接口。
  2. 方法引用:只能用于函数式接口(只有一个抽象方法),本质是代替匿名内部类的简写形式,不能写额外代码,只能复用已存在的方法。

1.3.6.什么时候不能使用方法引用 ​

Lambda 内部不是单纯调用一个方法,有额外逻辑时,不能转方法引用:

java
import java.util.Arrays;
import java.util.List;

public class Demo3 {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("A", "B", "C");

        // Lambda:内部有额外拼接逻辑,不能直接简化为方法引用
        list.forEach(s -> {
            String str = s + "!";
            System.out.println(str);
        });
        // 正常情况下简化之后的方法引用应该是这样的:
        list.forEach(System.out::println);
    }
}

一句话总结:匿名内部类是完整实现;Lambda 简化匿名内部类;当 Lambda 只是调用现成方法,参数匹配,就可以用方法引用进一步简写。