java 8 函数式编程整理

参考:

《Java 8 函数式编程》[英] Richard Warburton 著

https://blog.csdn.net/icarusliu/article/details/79495534

http://www.runoob.com/java/java8-functional-interfaces.html

https://blog.csdn.net/f641385712/article/details/81513796

1 基本概念

1.1 函数式接口的概念

函数式接口是只有一个抽象方法的接口,用作Lambda表达式的类型。

通常函数式接口需要使用@FunctionalInterface注释,该注释会强制 javac 检查一个接口是否符合函数接口的标准。如果该注释添加给一个枚举类型或另一个注释,或者接口包含不止一个抽象方法javac 就会报错。重构代码时,使用它能很容易发现问题。

1.2 函数式接口的定义

可以按照下面的方式定义一个函数式接口:

@FunctionalInterface
public interface GreetingService {
    void sayMessage(String message);
}

可以使用Lambda表达式来表示一个函数式接口的一个实现:

GreetingService greetingService = message -> System.out.println("Hello " + message);

注意:Lambda表达式的内容就是该函数接口中唯一的抽象方法的实现,因此通过Lambda表达是实现了函数接口,就可以通过调用该方法来使用。

greetingService.sayMessage("Tom");

2 常用函数式接口

2.1 总览

JDK 1.8新增加一个包java.util.function,这个包下面定义了很多函数式接口:

序号接口描述
1BiConsumer<T,U>代表了一个接受两个输入参数的操作,并且不返回任何结果。
2BiFunction<T,U,R>代表了一个接受两个输入参数的方法,并且返回一个结果。
3BinaryOperator<T>代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果。
4BiPredicate<T,U>代表了一个两个参数的boolean值方法。
5BooleanSupplier代表了boolean值结果的提供方。
6Consumer<T>代表了接受一个输入参数并且无返回的操作。
7DoubleBinaryOperator代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。
8DoubleConsumer代表一个接受double值参数的操作,并且不返回结果。
9DoubleFunction<R>代表接受一个double值参数的方法,并且返回结果。
10DoublePredicate代表一个拥有double值参数的boolean值方法。
11DoubleSupplier代表一个double值结构的提供方。
12DoubleToIntFunction接受一个double类型输入,返回一个int类型结果。
13DoubleToLongFunction接受一个double类型输入,返回一个long类型结果。
14DoubleUnaryOperator接受一个参数同为类型double,返回值类型也为double。
15Function<T,R>接受一个输入参数,返回一个结果。
16IntBinaryOperator接受两个参数同为类型int,返回值类型也为int 。
17IntConsumer接受一个int类型的输入参数,无返回值。
18IntFunction<R>接受一个int类型输入参数,返回一个结果。
19IntPredicate:接受一个int输入参数,返回一个布尔值的结果。
20IntSupplier无参数,返回一个int类型结果。
21IntToDoubleFunction接受一个int类型输入,返回一个double类型结果。
22IntToLongFunction接受一个int类型输入,返回一个long类型结果。
23IntUnaryOperator接受一个参数同为类型int,返回值类型也为int。
24LongBinaryOperator接受两个参数同为类型long,返回值类型也为long。
25LongConsumer接受一个long类型的输入参数,无返回值。
26LongFunction<R>接受一个long类型输入参数,返回一个结果。
27LongPredicateR接受一个long输入参数,返回一个布尔值类型结果。
28LongSupplier无参数,返回一个结果long类型的值。
29LongToDoubleFunction接受一个long类型输入,返回一个double类型结果。
30LongToIntFunction接受一个long类型输入,返回一个int类型结果。
31LongUnaryOperator接受一个参数同为类型long,返回值类型也为long。
32ObjDoubleConsumer<T>接受一个object类型和一个double类型的输入参数,无返回值。
33ObjIntConsumer<T>接受一个object类型和一个int类型的输入参数,无返回值。
34ObjLongConsumer<T>接受一个object类型和一个long类型的输入参数,无返回值。
35Predicate<T>接受一个输入参数,返回一个布尔值结果。
36Supplier<T>无参数,返回一个结果。
37ToDoubleBiFunction<T,U>接受两个输入参数,返回一个double类型结果。
38ToDoubleFunction<T>接受一个输入参数,返回一个double类型结果。
39ToIntBiFunction<T,U>接受两个输入参数,返回一个int类型结果。
40ToIntFunction<T>接受一个输入参数,返回一个int类型结果。
41ToLongBiFunction<T,U>接受两个输入参数,返回一个long类型结果。
42ToLongFunction<T>接受一个输入参数,返回一个long类型结果。
43UnaryOperator<T>接受一个参数为类型T,返回值类型也为T。

2.1 Predicate<T>

Predicate中文意思是“断言”,即判断的意思,判断某个东西是否满足某种条件; 因此它包含一个抽象的boolean test(T t)方法,根据输入值来做逻辑判断,其结果为true或者false

该接口中还包含三个默认方法:

  • and —— “与”操作。
  • or —— “或”操作。
  • negate —— “非”操作。

栗子:

	/**
     * 测试 Predicate
     */
    @Test
    public void testPredicate() {
        Predicate<String> predicate1 = s -> s.equals("test");
        Predicate<String> predicate2 = s -> s.startsWith("t");

        /**
         * Predicate测试,返回true或者false
         */
        System.out.println(predicate1.test("test")); // true
        System.out.println(predicate1.test("taa")); // false
        System.out.println(predicate2.test("taa")); // true


        /**
         * and: 针对同一个输入,多个Predicate均返回true时,结果为true
         */
        System.out.println(predicate1.and(predicate2).test("test")); // true
        System.out.println(predicate1.and(predicate2).test("tes")); // false

        /**
         * or: 针对同一个输入,多个Predicate中只要有一个返回true,结果为true
         */
        System.out.println(predicate1.or(predicate2).test("test")); // true
        System.out.println(predicate1.or(predicate2).test("tes")); // true

        /**
         * negate: 用于对原来的Predicate取反
         */
        System.out.println(predicate1.negate().test("test")); // false
    }

2.2 Function<T, R>

Function的含义是“函数”,它包含一个抽象的R apply(T t)方法,获得一个输入值,返回一个输出值;其中T为输入值的类型,R为输出值的类型。

该接口还包含两个默认方法:

  • compose —— 先执行compose指定的函数,并将返回值作为输入执行当前函数。
  • andThen —— 表示顺序执行函数,前一个函数的输出作为后一个函数的输入。

栗子:

	/**
     * 测试 Function
     */
	@Test
    public void testFunction() {
        // 定义两个Function
        Function<Integer, Integer> f = (s) -> s + 1;
        Function<Integer, Integer> g = (s) -> s * 2;

        /**
         * Function测试:获取一个输入,返回一个输出
         */
        System.out.println(f.apply(2));  // 3
        System.out.println(g.apply(2));  // 4

        /**
         * compose测试:先执行g,后执行f
         */
        System.out.println(f.compose(g).apply(2));  // 5

        /**
         * andThen测试:顺序执行,先执行否,后执行g
         */
        System.out.println(f.andThen(g).apply(2));  // 6
    }

2.3 Supplier<T>

Supplier的含义是“供应商;供应者”,它没有输入,只有输出,可以理解为“生产者”。它只有一个抽象的T get() 方法,表示产生一个结果,其中T表示产生的结果的类型。

栗子:

	/**
     * 测试 Supplier
     */
    @Test
    public void testSupplier() {
        // 定义一个生成Person对象的Supplier
        Supplier<Person> personSupplier = Person::new;

        /**
         * Supplier测试:生成对象
         */
        Person person1 = personSupplier.get();
        Person person2 = personSupplier.get();
        System.out.println(person1 == person2);  // false
    }

2.4 Consumer<T>

Consumer 的含义是“消费者”,它只有输入,没有输出。它有一个抽象的void accept(T t)方法,接受一个类型为T的输入值,没有输出。

改接口还包含一个默认方法:

  • andThen —— 表示顺序执行函数。

栗子:

	/**
     * 测试 Consumer
     */
    @Test
    public void testConsumer() {
        /**
         * 定义两个Consumer
         */
        Consumer<String> consumer1 = (str) -> System.out.println("consumer1: " + str);
        Consumer<String> consumer2 = (str) -> System.out.println("consumer2: " + str);

        /**
         * Consumer测试:消费对象
         */
        consumer1.accept("haha");
        consumer2.accept("wowo");

        /**
         * andThen测试:顺序执行多个consumer
         */
        consumer1.andThen(consumer2).andThen(consumer1).accept("test");
    }

2.5 Operator

Operator其实就是Function,函数有时候也叫算子。包含UnaryOperatorBinaryOperator,分别对应一元算子二元算子

  • BinaryOperator<T> - 定义一个二元算子,输入的两个参数都为T类型,返回值也是T类型。
  • UnaryOperator<T> - 定义一个一元算子,输入唯一参数的类型为T类型,返回值也是T类型。
  • DoubleBinaryOperator - 定义一个二元算子,输入的两个参数、返回值的类型都是double
  • DoubleUnaryOperator - 定义一个一元算子,输入只有一个参数,类型为double,返回值类型为double
  • IntBinaryOperator - 定义一个二元算子,输入的两个参数、返回值的类型都是int
  • IntUnaryOperator - 定义一个一元算子,输入只有一个参数,类型为int,返回值类型为int
  • LongBinaryOperator - 定义一个二元算子,输入的两个参数、返回值的类型都是long
  • LongUnaryOperator - 定义一个一元算子,输入只有一个参数,类型为long,返回值类型为long

栗子:

	/**
     * 测试 Operator
     */
    @Test
    public void testOperator() {
        // 定义一个一元算子
        UnaryOperator<Integer> unaryOperator = x -> x + 10;
        // 定义一个二元算子
        BinaryOperator<Integer> binaryOperator = (x, y) -> x * y;

        /**
         * 测试算子
         */
        System.out.println(unaryOperator.apply(5));  // 15
        System.out.println(binaryOperator.apply(2, 3));  // 6
    }

3 Stream API

Stream API提供了一堆操作的集合。

流的操作类型分为两类:

  • Intermediate(中间操作):一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。

    map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered

  • Terminal(终止操作):一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。

    forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

还有一种操作被称为 short-circuiting(短路操作)。用以指:

  • 对于一个 intermediate 操作,如果它接受的是一个无限流,它可以返回一个有限的新 Stream
  • 对于一个 terminal 操作,如果它接受的是一个无限流,但能在有限的时间计算出结果。

anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit

下面汇总了Stream的所有操作:

中间操作(Intermediate Operations)

操作函数说明
unordered()返回一个不关心顺序的Stream对象。
filter()过滤元素。
map()元素一对一转换。
mapToInt()元素一对一转换,返回一个IntStream。
mapToLong()元素一对一转换,返回一个LongStream。
mapToDouble()元素一对一转换,返回一个DoubleStream。
flatMap()元素一对多转换。
flatMapToInt()元素一对多转换,返回一个IntStream。
flatMapToLong()元素一对多转换,返回一个LongStream。
flatMapToDouble()元素一对多转换,返回一个DoubleStream。
peek()生成一个含原Stream所有元素的新Stream,使用传入的Consumer消费流中对象。
distinct()去重。
sorted()排序。
limit()获取有限个元素。
skip()跳过前n个元素,返回包含剩下元素的流。
takeWhile()如果Stream是有序的(Ordered),那么返回最长命中序列(符合传入的Predicate的最长命中序列)组成的Stream;如果是无序的,那么返回的是所有符合传入的Predicate的元素序列组成的Stream。
dropWhile()与takeWhile相反,如果是有序的,返回除最长命中序列外的所有元素组成的Stream;如果是无序的,返回所有未命中的元素组成的Stream。
sequential()返回一个相等的串行的Stream对象,如果原Stream对象已经是串行就可能会返回原对象。
parallel()返回一个相等的并行的Stream对象,如果原Stream对象已经是并行的就会返回原对象。
onClose()返回一个相等的Steam对象,同时新的Stream对象在执行Close方法时会调用传入的Runnable对象。
close()关闭Stream对象。

结束操作(Terminal Operations)

操作函数说明
forEach()对所有元素进行迭代处理,无返回值。
forEachOrdered()按Stream的Encounter所决定的序列进行迭代处理,无返回值。
toArray()返回所有元素的数组。
reduce()使用一个初始的值,和六中的元素接连做二元运算,每次计算的结果和下一个元素一起做二元运算。
collect()汇聚元素。
max()返回所有元素中最大值的Optional对象;如果Stream中无任何元素,那么返回的Optional对象为Empty。
min()返回所有元素中最小值的Optional对象;如果Stream中无任何元素,那么返回的Optional对象为Empty。
count()所有元素个数。
anyMatch()只要其中有一个元素满足传入的Predicate时返回True,否则返回False。
allMatch()所有元素均满足传入的Predicate时返回True,否则False。
noneMatch()所有元素均不满足传入的Predicate时返回True,否则False。
findFirst()返回第一个元素的Optioanl对象;如果无元素返回的是空的Optional; 如果Stream是无序的,那么任何元素都可能被返回。
findAny()返回任意一个元素的Optional对象,如果无元素返回的是空的Optioanl。
iterator()返回Stream中所有对象的迭代器。
spliterator()返回对所有对象进行的spliterator对象。
isParallel()判断是否当前Stream对象是并行的。

Java 8 为intlongdouble三种基本数值型提供了对应的专用流IntStreamLongStreamDoubleStream

栗子:

3.1 Stream对象的创建

  • a. 创建空的Stream对象

    Stream stream = Stream.empty();
    
  • b. 通过集合类中的stream()或者parallelStream()方法创建

    List<String> list = Arrays.asList("a", "b", "c", "d");
    Stream listStream = list.stream();                   //获取串行的Stream对象
    Stream parallelListStream = list.parallelStream();   //获取并行的Stream对象
    
  • c. 由数组创建流

    int[] array = {1, 2, 3, 4};
    IntStream intStream = Arrays.stream(array);
    
  • d. 由值创建流

    Stream<Integer> integerStream = Stream.of(1,2,3,4);
    Stream<String> stringStream = Stream.of("123", "test");
    
  • e. 通过方法创建流

    • iterate方法,每一个值与前一个值有关。

      Stream<Integer> s = Stream.iterate(1, n -> n + 2).limit(10);
      s.forEach(System.out::println);
      
    • generate方法,每一个值都是独立生成的,与前一个值无关。

      Stream.generate(Math::random).limit(10).forEach(System.out::println);
      
    • 通过concat连接两个流。

      Stream.concat(Stream.of(1,2,3,4), Stream.of(5,6,7,8)).forEach(System.out::println);
      

3.2 Stream对象中的常用方法

3.2.1 filter —— 过滤元素

函数申明:

Stream<T> filter(Predicate<? super T> predicate);

栗子:

Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa");
//查找所有包含t的元素并进行打印
s.filter(n -> n.contains("t")).forEach(System.out::println);

3.2.2 map —— 元素一对一转换

函数申明:

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

栗子:

Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa");
// 为每个字符串加上".txt"后缀
s.map(n -> n.concat(".txt")).forEach(System.out::println);

3.2.3 flatMap —— 元素一对多转换

对原Stream中的所有元素使用传入的Function进行处理,每个元素经过处理后生成一个多个元素的Stream对象,然后将返回的所有Stream对象中的所有元素组合成一个统一的Stream并返回。

函数申明:

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

栗子:

Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa");
// 对一个String类型的Stream进行处理,将每一个元素拆分成单个字母,并打印
s.flatMap(str -> Stream.of(str.split(""))).forEach(System.out::println);

3.3 Collectors —— 分组、排序

3.3.1 分组

(1)按类型分组原始数据
场景描述:按城市名称对雇员分组

Map<String, List<Employee>> employeesByCity = employeeList.stream()
                .collect(groupingBy(Employee::getCity));

(2)分组的同时将原始类型的对象转换成另一个类型的对象
场景描述:按城市名分组,每组只保存雇员的姓名

Map<String, List<String>> employeeNamesByCity = employeeList.stream()
                .collect(groupingBy(Employee::getCity, mapping(Employee::getName, toList())));

场景描述:按城市名分组,并且将Employee类型转换为Person类型

Map<String, List<Person>> employeeToPersonByCity = employeeList.stream()
                .collect(groupingBy(Employee::getCity, mapping(employee -> {
                    // 转换成Person
                    Person person = new Person();
                    person.setName(employee.getName());
                    return person;
                }, toList())));

(3)分组并统计
场景描述:分组后,统计每组的雇员个数

Map<String, Long> countEmployeesByCity = employeeList.stream()
                .collect(groupingBy(Employee::getCity, counting()));

场景描述:分组后,统计每组雇员的平均销量

Map<String, Double> averageSalesByCity = employeeList.stream()
                .collect(groupingBy(Employee::getCity, averagingDouble(Employee::getSales)));

场景描述:分组后,统计每组最大销售额
注意:最好使用toMap,代码更简洁

Map<String, Double> maxSalesByCity = employeeList.stream()
                .collect(toMap(Employee::getCity, Employee::getSales, Double::max));

3.3.2 排序

(1)单字段排序
场景描述:将雇员按照销售额sales排序,默认升序

List<Employee> orderBySalesAsc = employeeList.stream()
                .sorted(comparing(Employee::getSales))
                .collect(toList());

场景描述:将雇员按照销售额sales排序,降序

List<Employee> orderBySalesDesc = employeeList.stream()
                .sorted(comparing(Employee::getSales).reversed())
                .collect(toList());

场景描述:将雇员按照销售额sales排序,另一种降序的使用方式

List<Employee> orderBySalesDesc2 = employeeList.stream()
                .sorted(comparing(Employee::getSales, reverseOrder()))
                .collect(toList());

(2)多字段排序
场景描述:将雇员按照销售额sales降序、年龄age升序排序

List<Employee> orderBySalesDescAndAgeAsc = employeeList.stream()
                .sorted(comparing(Employee::getSales, reverseOrder()).thenComparing(Employee::getAge))
                .collect(toList());

3.3.3 完整代码

package com.tao.springstarter.test;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static java.util.stream.Collectors.*;
import static java.util.Comparator.*;

public class Test7 {
    public static void main(String[] args) {

        /**
         * 准备:创建数据
         */
        List<Employee> employeeList = generateData();

        /**
         * 应用:数据分组
         * 关键方法:groupingBy、mapping
         */

        /**
         * 类型一:按类型分组原始数据
         * 场景描述:按城市名称对雇员分组
         */
        Map<String, List<Employee>> employeesByCity = employeeList.stream()
                .collect(groupingBy(Employee::getCity));
        System.out.println("=== 场景描述:按城市名称对雇员分组 ===");
        System.out.println(employeesByCity);

        /**
         * 类型二:分组的同时将原始类型的对象转换成另一个类型的对象
         * 场景描述:按城市名分组,每组只保存雇员的姓名
         */
        Map<String, List<String>> employeeNamesByCity = employeeList.stream()
                .collect(groupingBy(Employee::getCity, mapping(Employee::getName, toList())));
        System.out.println("=== 场景描述:按城市名分组,每组只保存雇员的姓名 ===");
        System.out.println(employeeNamesByCity);

        /**
         * 场景描述:按城市名分组,并且将Employee类型转换为Person类型
         */
        Map<String, List<Person>> employeeToPersonByCity = employeeList.stream()
                .collect(groupingBy(Employee::getCity, mapping(employee -> {
                    // 转换成Person
                    Person person = new Person();
                    person.setName(employee.getName());
                    return person;
                }, toList())));
        System.out.println("=== 场景描述:按城市名分组,并且将Employee类型转换为Person类型 ===");
        System.out.println(employeeToPersonByCity);

        /**
         * 类型三:分组并统计
         * 场景描述:分组后,统计每组的雇员个数
         */
        Map<String, Long> countEmployeesByCity = employeeList.stream()
                .collect(groupingBy(Employee::getCity, counting()));
        System.out.println("=== 场景描述:分组后,统计每组的雇员个数 ===");
        System.out.println(countEmployeesByCity);

        /**
         * 场景描述:分组后,统计每组雇员的平均销量
         */
        Map<String, Double> averageSalesByCity = employeeList.stream()
                .collect(groupingBy(Employee::getCity, averagingDouble(Employee::getSales)));
        System.out.println("=== 场景描述:分组后,统计每组雇员的平均销量 ===");
        System.out.println(averageSalesByCity);

        /**
         * 场景描述:分组后,统计每组最大销售额
         * 注意:最好使用toMap,代码更简洁
         */
        Map<String, Double> maxSalesByCity = employeeList.stream()
                .collect(toMap(Employee::getCity, Employee::getSales, Double::max));
        System.out.println("=== 场景描述:分组后,统计每组最大销售额 ===");
        System.out.println(maxSalesByCity);


        /**
         * 应用:数据排序
         * 关键方法:sorted、comparing、reversed、reverseOrder
         */

        /**
         * 类型一:单字段排序
         * 场景描述:将雇员按照销售额sales排序,默认升序
         */
        List<Employee> orderBySalesAsc = employeeList.stream()
                .sorted(comparing(Employee::getSales))
                .collect(toList());
        System.out.println("=== 场景描述:将雇员按照销售额sales排序,默认升序 ===");
        System.out.println(orderBySalesAsc);

        /**
         * 场景描述:将雇员按照销售额sales排序,降序
         */
        List<Employee> orderBySalesDesc = employeeList.stream()
                .sorted(comparing(Employee::getSales).reversed())
                .collect(toList());
        System.out.println("=== 场景描述:将雇员按照销售额sales排序,降序 ===");
        System.out.println(orderBySalesDesc);

        /**
         * 场景描述:将雇员按照销售额sales排序,另一种降序的使用方式
         */
        List<Employee> orderBySalesDesc2 = employeeList.stream()
                .sorted(comparing(Employee::getSales, reverseOrder()))
                .collect(toList());
        System.out.println("=== 场景描述:将雇员按照销售额sales排序,另一种降序的使用方式 ===");
        System.out.println(orderBySalesDesc2);

        /**
         * 类型二:多字段排序
         * 场景描述:将雇员按照销售额sales降序、年龄age升序排序
         */
        List<Employee> orderBySalesDescAndAgeAsc = employeeList.stream()
                .sorted(comparing(Employee::getSales, reverseOrder()).thenComparing(Employee::getAge))
                .collect(toList());
        System.out.println("=== 场景描述:将雇员按照销售额sales降序、年龄age升序排序 ===");
        System.out.println(orderBySalesDescAndAgeAsc);
    }

    /**
     * 创建数据
     *
     * @return
     */
    private static List<Employee> generateData() {
        List<Employee> employeeList = new ArrayList<>();
        employeeList.add(new Employee("Alice", 23, "London", 1200.00));
        employeeList.add(new Employee("Bob", 19, "London", 2000.00));
        employeeList.add(new Employee("Charles", 25, "New York", 1650.00));
        employeeList.add(new Employee("Dorothy", 20, "Hong Kong", 1200.00));
        return employeeList;
    }
}


/**
 * 雇员
 */
class Employee {
    /**
     * 姓名
     */
    private String name;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 所在城市
     */
    private String city;

    /**
     * 销售额
     */
    private Double sales;

    public Employee(String name, Integer age, String city, Double sales) {
        this.name = name;
        this.age = age;
        this.city = city;
        this.sales = sales;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public Double getSales() {
        return sales;
    }

    public void setSales(Double sales) {
        this.sales = sales;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", city='" + city + '\'' +
                ", sales=" + sales +
                '}';
    }
}

/**
 * 人
 */
class Person {
    /**
     * 姓名
     */
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}

— 未完待续


版权声明:本文为hbtj_1216原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。