参考:
《Java 8 函数式编程》[英] Richard Warburton 著
https://blog.csdn.net/icarusliu/article/details/79495534
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,这个包下面定义了很多函数式接口:
| 序号 | 接口 | 描述 |
|---|---|---|
| 1 | BiConsumer<T,U> | 代表了一个接受两个输入参数的操作,并且不返回任何结果。 |
| 2 | BiFunction<T,U,R> | 代表了一个接受两个输入参数的方法,并且返回一个结果。 |
| 3 | BinaryOperator<T> | 代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果。 |
| 4 | BiPredicate<T,U> | 代表了一个两个参数的boolean值方法。 |
| 5 | BooleanSupplier | 代表了boolean值结果的提供方。 |
| 6 | Consumer<T> | 代表了接受一个输入参数并且无返回的操作。 |
| 7 | DoubleBinaryOperator | 代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。 |
| 8 | DoubleConsumer | 代表一个接受double值参数的操作,并且不返回结果。 |
| 9 | DoubleFunction<R> | 代表接受一个double值参数的方法,并且返回结果。 |
| 10 | DoublePredicate | 代表一个拥有double值参数的boolean值方法。 |
| 11 | DoubleSupplier | 代表一个double值结构的提供方。 |
| 12 | DoubleToIntFunction | 接受一个double类型输入,返回一个int类型结果。 |
| 13 | DoubleToLongFunction | 接受一个double类型输入,返回一个long类型结果。 |
| 14 | DoubleUnaryOperator | 接受一个参数同为类型double,返回值类型也为double。 |
| 15 | Function<T,R> | 接受一个输入参数,返回一个结果。 |
| 16 | IntBinaryOperator | 接受两个参数同为类型int,返回值类型也为int 。 |
| 17 | IntConsumer | 接受一个int类型的输入参数,无返回值。 |
| 18 | IntFunction<R> | 接受一个int类型输入参数,返回一个结果。 |
| 19 | IntPredicate | :接受一个int输入参数,返回一个布尔值的结果。 |
| 20 | IntSupplier | 无参数,返回一个int类型结果。 |
| 21 | IntToDoubleFunction | 接受一个int类型输入,返回一个double类型结果。 |
| 22 | IntToLongFunction | 接受一个int类型输入,返回一个long类型结果。 |
| 23 | IntUnaryOperator | 接受一个参数同为类型int,返回值类型也为int。 |
| 24 | LongBinaryOperator | 接受两个参数同为类型long,返回值类型也为long。 |
| 25 | LongConsumer | 接受一个long类型的输入参数,无返回值。 |
| 26 | LongFunction<R> | 接受一个long类型输入参数,返回一个结果。 |
| 27 | LongPredicate | R接受一个long输入参数,返回一个布尔值类型结果。 |
| 28 | LongSupplier | 无参数,返回一个结果long类型的值。 |
| 29 | LongToDoubleFunction | 接受一个long类型输入,返回一个double类型结果。 |
| 30 | LongToIntFunction | 接受一个long类型输入,返回一个int类型结果。 |
| 31 | LongUnaryOperator | 接受一个参数同为类型long,返回值类型也为long。 |
| 32 | ObjDoubleConsumer<T> | 接受一个object类型和一个double类型的输入参数,无返回值。 |
| 33 | ObjIntConsumer<T> | 接受一个object类型和一个int类型的输入参数,无返回值。 |
| 34 | ObjLongConsumer<T> | 接受一个object类型和一个long类型的输入参数,无返回值。 |
| 35 | Predicate<T> | 接受一个输入参数,返回一个布尔值结果。 |
| 36 | Supplier<T> | 无参数,返回一个结果。 |
| 37 | ToDoubleBiFunction<T,U> | 接受两个输入参数,返回一个double类型结果。 |
| 38 | ToDoubleFunction<T> | 接受一个输入参数,返回一个double类型结果。 |
| 39 | ToIntBiFunction<T,U> | 接受两个输入参数,返回一个int类型结果。 |
| 40 | ToIntFunction<T> | 接受一个输入参数,返回一个int类型结果。 |
| 41 | ToLongBiFunction<T,U> | 接受两个输入参数,返回一个long类型结果。 |
| 42 | ToLongFunction<T> | 接受一个输入参数,返回一个long类型结果。 |
| 43 | UnaryOperator<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,函数有时候也叫算子。包含UnaryOperator和BinaryOperator,分别对应一元算子和二元算子。
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 为int、long、double三种基本数值型提供了对应的专用流IntStream、LongStream、DoubleStream。
栗子:
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 + '\'' +
'}';
}
}
— 未完待续