我们可以把Lambda表达式理解为一段可以传递的代码。使用它可以写出更简介、更灵活的代码。
实质是对编码风格的改变。并不会影响代码执行的结果。简单来说,你还是你,只是换了一件新衣服、更帅了。
前面讲的 ProxyFactory 类
public class ProxyFactory {
/** 调用此方法,返回一个代理类对象 **/
public static Object getProxyInstance(Object obj) {
// 使用 Proxy 的 静态方法 newProxyInstance 得到代理类对象
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(), (proxy, method, args) -> method.invoke(obj, args));
}
}
里面就使用了 Lambda 表达式。
再来看一个例子:一个Runnable 的匿名实现类给r1,通过r1调用方法。
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("我爱北京");
}
};
r1.run();
使用lambda表达式改造
Runnable r1 = () -> System.out.println("我爱北京");
r1.run();
再来看看一个例子:
Comparator com = new Comparator() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
com.compare(12, 21);
使用lambda表达式改造
Comparator com = (o1, o2)-> Integer.compare(o1, o2);
com.compare(12, 21);
这就完了?再次升级 称为方法引用的写法。
// 方法引用的写法
Comparator com = Integer::compare;
com.compare(12, 21);
Lambda 表达式实质是方法的简写。但是又不仅仅是这样。它还是一个对象。
比如:
Runnable r1 = () -> System.out.println("我爱北京");
可以被 Runnable 类型接收,说明这个表达式是一个 Runnable 实现类对象!!!
上面展示的几个例子基本对Lambda表达式有了初步了解。
它的基本格式是括号加箭头加大括号。()->{}
实际上这个形式是方法的简写。()里面是方法的参数,{}里面直接是方法体。
上面都是以接口为例的,这就要求这个接口里面只有一个抽象方法。因为没有方法名,多个的话就不知道是指哪一个了。
因为只有一个,所有Lambda表达式自然就可以省略方法名。
根据方法的多种情况,lambda表达式也就有多种形式。
方法没有 参数,没有返回值
()-> {方法体}
就像上面的 Runable 的例子。又因为 方法体只有 1条语句,因此 {} 也可以省略。
有参数,无返回值
(类型 p)->{方法体}
变量名p可以随便取。有几个参数就写几个
实例:
Consumer con1 = (p)->System.out.println(p);
con1.accept("哈哈");
还是因为函数体只有一条语句,省略了{},而且 还省略了参数p的类型。因为这个类型可以通过 左边 Consumer的泛型推断得出。
我们发现,参数只有一个,还可以省略()
Consumer con1 = o ->System.out.println(o);
con1.accept("哈哈");
有参数,有返回值
Comparator com = (a, b)->{ return a.compareTo(b);};
int compare = com.compare(90, 9);
System.out.println(compare);
由于泛型的存在,可以省略参数的类型,而且如果只有一条语句,那么 return 和 {} 都是可以省略的。
Comparator com = (a, b)-> a.compareTo(b);
int compare = com.compare(90, 9);
System.out.println(compare);
下面这种写法是 多个参数,有返回值的情况。也是一条语句可以省略 return 和 {}
(proxy, method, args) -> method.invoke(obj, args)
如果是多个参数,多条语句,看下面的写法。
(proxy, method, args) -> {
GeneralMethod humanUtil = new GeneralMethod();
humanUtil.method1();
Object invoke = method.invoke(obj, args);
humanUtil.method2();
return invoke;
}
就是 ruturn 和 {} 都不能省略了而已。
无参数,有返回值
还是一样的。只是参数使用 ()
总结:
标准格式 (参数类型 变量名)-> {方法体}
如果参数类型可以推断,那么类型可以省略
如果只有一个参数,那么()可以省略
如果方法体只有一条语句,这条语句不是return语句,{}可以省略
如果方法体 只有一条语句,这条语句是 return 语句,return 与{} 都可以省略。而且要省略必须都省略。
其他情况就是 按照标准格式写了。
形式上,一条语句的情况如果都省略了,是一样的。实际上,省略的东西是不一样的。带return的语句不仅省略了 {},还要省略return
上面的例子应该可以发现,上面时候用Lambda表达式呢? 一般是一个接口写它的匿名实现类时。这个接口有一个特点,只有一个抽象方法。
这种只有1个抽象方法的接口叫做函数式接口。
lambda表示式其实就是函数式接口的匿名实现类的 写法优化,是一个匿名实现类对象。
// 这是一个lambda表达式,是Comparator接口的一个匿名实现类对象。直接作为参数就是 匿名实现类的匿名对象
(a, b)-> a.compareTo(b);
之所以要求只能有一个抽象方法(可以有默认方法、静态方法 等非抽象方法),是因为lambda没有名字,多个抽象方法就不知道重写的是哪个抽象方法了。只有一个的话,就很明确了。
JAVA 提供了一个注解 @FunctionalInterface 放在接口上面,用来检查这个接口是否是一个函数式接口。
根据 方法的参数与返回值的有无,可以有4中类型的方法。因此,也就有四种类型的函数式接口。
无参数、无返回值 Runnable
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
无参数、有返回值 Supplier 一般叫做供给型,不消费参数,还返回东西
@FunctionalInterface
public interface Supplier {
T get();
}
有参数、无返回值 Consumer 一般叫做消费型,消费参数,不返回东西
@FunctionalInterface
public interface Consumer {
void accept(T t);
default Consumer andThen(Consumer super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
有参数、有返回值
Function 函数型、即消费参数、又提供返回值
Predicate 断定型、根据参数返回真假。
@FunctionalInterface
public interface Function {
R apply(T t);
default Function compose(Function super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default Function andThen(Function super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static Function identity() {
return t -> t;
}
}
@FunctionalInterface
public interface Predicate {
boolean test(T t);
default Predicate and(Predicate super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate negate() {
return (t) -> !test(t);
}
default Predicate or(Predicate super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static Predicate isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
Function 接口比较通用,有参数,有返回值;而Predicate 虽然也是有参数,有返回值,但是返回值类型必须是 Boolean型。
这是几个基本的 函数式接口。还有其他的一些编写。比如多个参数什么的。
如何需要定义函数式接口,符号上述类型的,基本可以不用定义了,直接使用就是了。
使用举例:
1、Consumer 接口
// 定义一个 使用 Consumer 接口作为参数的 函数
public void happyTime(double money, Consumer con){
con.accept(money);
}
使用
@Test
public void test1() throws Exception {
happyTime(400, a-> System.out.println("消费啊,快乐啊"+ a));
}
// 输出
消费啊,快乐啊400.0
2、Predicate 接口 例子
// 字符串过滤函数,使用 Predicate 作为一个参数
public List filterString(List list, Predicate pre){
ArrayList arrayList = new ArrayList<>();
for (String s : list) {
// 当判断为 true 时,添加到新的 List里面
if (pre.test(s)){
arrayList.add(s);
}
}
return arrayList;
}
使用:
// 待过滤的 list
List list = Arrays.asList("aaa", "bbb", "ccc", "ddd", "zzz", "eee");
// 调用 filterString 函数, 第二个参数是 Predicate 接口的实现类对象。使用lambda表达式写。
// Predicate 接口属于 只有1个参数,返回值是 Boolean类型。
// 所有 ()可以省略, 返回语句 只有一条 turn 和{} 也可以省略。
// 这里的规则是 如果 参数比“ccc“ 大就返回 true 否则返回false
List ggg = filterString(list, str -> str.compareTo("ccc") > 0);
System.out.println(ggg);
// 输出
[ddd, zzz, eee]
上图是其他的一些函数式节点,主要就是 参数个数、返回值这里变来变去的。没什么意思。
当要传递给 Lambda体的操作已经有实现的方法了,就可以使用方法引用。
方法引用就是lambda表达式,只不过这个表达式不再使用 ()-{} 这种形式,而是直接利用现有的逻辑。通过方法的名称来指向这部分逻辑。
从上面lambda表达式的学习来看,就知道这个方法主要是根据 方法的 参数以及返回值来的。所以,被引用的方法能够用来替换表达式,就必须和原来的函数式接口的抽象方法具有 相同的参数以及相同的返回值。
格式: 使用操作费 "::" 双冒号将 类名(或者对象)与方法名隔开。
左边是 调用者、右边是 方法名。类也是Class类的一个对象。
例如有如下三种主要情况
对象::实例方法名
类::静态方法名
类::实例方法名
例子1:
// 普通的 lambda 表达式
Consumer con = str-> System.out.println(str);
con.accept("哈哈哈");
// 方法引用
Consumer con1 = System.out::println;
con.accept("哈哈哈");
可以看到 System.out::println 替换了 str-> System.out.println(str)
原因是 str-> System.out.println(str) 这一部分逻辑,已经在 System.out 这个类 即 PrintStream 里面定义了 println 方法,这个方法的参数与返回值也是 一个参数,没有返回值。与Consumer 接口的 accept 方法一致。
public void println(Object x) {
String s = String.valueOf(x);
synchronized (this) {
print(s);
newLine();
}
}
为什么可以使用方法引用呢?
因为lambda表达式本质上是要实现 抽象方法,写方法体。而现在已经有函数实现了,就用现成的不久行了。
再看一个例子2:
// 普通 lambda表达式
Comparator com1 = (t1, t2) -> Integer.compare(t1, t2);
int compare1 = com1.compare(10, 20);
System.out.println(compare1);
// 方法引用 Integer类里面的compare 方法的参数与返回值类型 都与 Comparator接口的抽象方法 compare 一致。可以使用
// Integer类的compare方法 里面的逻辑。
Comparator com2 = Integer::compare;
int compare2 = com1.compare(10, 20);
System.out.println(compare2);
再来例子3:
// Comparator 中的 int compare(T t1, T t2)
// String 中的 int t1.compareTo(t2)
Comparator com1 = (s1, s2) -> s1.compareTo(s2);
com1.compare("abc", "abd");
Comparator com2 = String::compareTo;
com2.compare("abc", "abd");
不是说 String 类的 compareTo 方法的参数返回值要与 compare一样吗?为什么这样写也可以呢?
如果 第一个参数 是 方法的调用者,那么是可以使用 方法引用的。这就出现了 类名::实例方法的写法。
例子4:
// BiPredicate 中的 boolean test(T t, U u)
// String 中的 boolean equals(Object anObject)
BiPredicate com1 = (s1, s2) ->s1.equals(s2);
com1.test("abc", "abd");
// 方法引用
BiPredicate com2 = String::equals;
com2.test("abc", "abd");
这个例子也是,第一个参数是方法的调用者。使用方法引,在 :: 之前要写第一个参数的类型。看起来就像是类名调用实例方法。
例子5:
// BiPredicate 中的 R apply(T t);
// Person 中的 String getName()
Function f1 = e -> e.getName();
System.out.println(f1.apply(new Person("aa", 11)));
// 方法引用
Function f2 = Person::getName;
System.out.println(f1.apply(new Person("aa", 11)));
这里也是 被引用的方法getName的调用者是 第一个参数Person。
例子1:
// 普通 Lambda 表达式
Supplier per = ()-> new FullPerson();
FullPerson person = per.get();
// 构造器引用
Supplier per1 = FullPerson::new;
FullPerson fullPerson = per1.get();
例子2:
// 普通lambda表达式
Function func = (a)-> new FullPerson(a);
FullPerson apply = func.apply(12);
System.out.println(apply);
// 构造器引用
Function func1 = FullPerson::new;
FullPerson apply1 = func1.apply(12);
System.out.println(apply1);
这里面构造器引用是 因为构造器的参数和返回值与 函数式接口的抽象方法一致。
依然符合上面方法引用的原则。
例子1:
// 普通lambda表达式
Function funcArr = (num)-> new String[num];
String[] apply = funcArr.apply(20);
System.out.println(apply.length);
// 数组引用
Function funcArr1 = String[]::new;
String[] apply1 = funcArr1.apply(20);
System.out.println(apply1.length);
把数组看成是一个特殊的类,那么数组引用和构造器引用就一样了。
Stream 流 用于处理集合的关键抽象概念。它可以指定你希望对集合进行的操作。可以执行非常复杂的查找、过滤和映射数据等操作。
工作中用的很多,也非常简单。
Stream API是对集合进行处理的。
// 这是Collection接口中的默认方法 stream; 返回值是 Stream 接口的实现类对象。
default Stream stream() {
return StreamSupport.stream(spliterator(), false);
}
Stream 是一个接口,继承了 BaseStream 接口和 AutoCloseable 接口。接口是可以多继承的。
学习Strem、就是学习Stream 接口中的方法是怎么用的。
Stream 自己不会存储元素
不会改变源对象,会返回一个持有结果的 新Stream对象
操作是延迟执行的,这意味着会等待需要结果的时候才执行。
Stream的操作有三个步骤。
1、创建
从集合中获取一个Stream对象。
2、中间操作
过滤、映射、排序、去重 等待
3、终止操作
产生结果,不能再继续使用。
1、从集合创建,我们知道,Collection中 有stream 默认方法,因此,集合都可以调用这个方法获取Stream对象。
List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
// 顺序流[安装元素顺序来取数据]
Stream stream = list.stream();
// 并行流[不按顺序,多个一起]
Stream integerStream = list.parallelStream();
2、Arrays 类的静态方法 stream,获取Stream对象
IntStream stream1 = Arrays.stream(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 0});
3、通过Stream接口自己的 静态方法of 创建
List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
Stream.of(1,2,3,4,5,6);
Stream.of(list);
of 有2个重载方法,一个接收 数组。另一个接收 一个泛型,意味着很多类型都可以接收。
4、创建无限流, 一般用来造数据
Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);
4.1 、iterate方法
public static Stream iterate(final T seed, final UnaryOperator f) {
Objects.requireNonNull(f);
final Iterator iterator = new Iterator() {
@SuppressWarnings("unchecked")
T t = (T) Streams.NONE;
@Override
public boolean hasNext() {
return true;
}
@Override
public T next() {
return t = (t == Streams.NONE) ? seed : f.apply(t);
}
};
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
iterator,
Spliterator.ORDERED | Spliterator.IMMUTABLE), false);
}
UnaryOperator 接口,继承了Function,里面有一个静态方法identity,由于继承了Function,还有一个抽象方法 apply
但是继承的时候,泛型是 Function
@FunctionalInterface
public interface UnaryOperator extends Function {
static UnaryOperator identity() {
return t -> t;
}
}
2、generate 方法
// 产生10个随机数
Stream.generate(Math::random).limit(10).forEach(System.out::println);
generate 方法,接收一个 Supplier 函数式接口对象,返回的是一个流
public static Stream generate(Supplier s) {
Objects.requireNonNull(s);
return StreamSupport.stream(
new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);
}
Stream
List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
// 获取元素值大于5的元素
list.stream().filter(item-> item>5).forEach(System.out::println);
// 输出
6
7
8
9
Stream
List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
// 限制3个
list.stream().limit(3).forEach(System.out::println);
// 输出
1
2
3
Stream
List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
// 跳过前6个
list.stream().skip(6).forEach(System.out::println);
// 输出
7
8
9
0
Stream
List list = Arrays.asList(1, 3, 3, 6, 7, 8, 8);
list.stream().distinct().forEach(System.out::println);
// 输出
1
3
6
7
8
每一个元素都会应用这个对象里的逻辑。
List list = Arrays.asList(1, 3, 3, 6, 7, 8, 8);
// 每一个元素是 item, 应用逻辑 item+item,最后每个元素变成 item+item
list.stream().map(item->item+item).limit(3).forEach(System.out::println);
// 输出
2
6
6
将流中的每一个值都换成另一个流,然后将所有流连接成一个流。如果一个集合的元素还是一个集合,就可以使用这个方法比较方法。
List list = Arrays.asList("aa","bb","cc","dd");
有这么一个方法:
public class TestMain {
// 接收一个字符串,将每个字符变成一个集合的元素,最后返回这个集合的流
// 比如字符串 "abc", 变成List,['a','b','b'], 再返回这个list的流
public static Stream getStream(String str){
ArrayList list = new ArrayList<>();
for(Character c : str.toCharArray()){
list.add(c);
}
return list.stream();
}
}
当我们使用map时:
List list = Arrays.asList("aa","bb","cc","dd");
Stream> stream = list.stream().map(TestMain::getStream);
stream.forEach(s->{ s.forEach(System.out::println);});
// 输出
a
a
b
b
c
c
d
d
map操作对每个元素都执行一下上面的getStream方法,得到一个 嵌套流Stream
如果使用flatMap 就没有这么麻烦了。
List list = Arrays.asList("aa","bb","cc","dd");
Stream stream = list.stream().flatMap(TestMain::getStream);
stream.forEach(System.out::println);
// 输出
a
a
b
b
c
c
d
d
可以看到 flatMap 得到的Stream
java中排序就是前面讲到的 2个接口 Comparable 和 Comparator
因此排序也有2个重载的方法
Stream
List list = Arrays.asList("aa","dd","cc","bb");
list.stream().sorted().forEach(System.out::println);
// 输出
aa
bb
cc
dd
Stream
List list = Arrays.asList("aa","dd","cc","bb");
list.stream().sorted((s1,s2)-> -s1.compareTo(s2)).forEach(System.out::println);
// 输出
dd
cc
bb
aa
allMatch、anyMatch、noneMatch
boolean allMatch(Predicate super T> predicate) 检查是否匹配所有元素
List list = Arrays.asList("aa","dd","cc","bb");
// 判断元素是否都等于 dd
boolean dd = list.stream().allMatch(item -> item.equals("dd"));
System.out.println(dd); // false
boolean anyMatch(Predicate super T> predicate) 判断是否能够匹配到至少一个元素
List list = Arrays.asList("aa","dd","cc","bb");
// 判断是否至少有1个元素等于 dd
boolean dd = list.stream().anyMatch(item -> item.equals("dd"));
System.out.println(dd); // true
boolean noneMatch(Predicate super T> predicate) 判断是否全部不匹配
List list = Arrays.asList("aa","dd","cc","bb");
// 判断是否 没有一个元素 等于 dd
boolean dd = list.stream().noneMatch(item -> item.equals("dd"));
System.out.println(dd); // false
findFirst、findAny
Optional
List list = Arrays.asList("aa","dd","cc","bb");
Optional first = list.stream().findFirst();
System.out.println(first);
// 输出
Optional[aa]
Optional
List list = Arrays.asList("aa","dd","cc","bb");
// 串行流
Optional first = list.stream().findAny();
// 并行流
Optional any = list.parallelStream().findAny();
System.out.println(first); //Optional[aa]
System.out.println(any); // Optional[cc]
min、max、count、
long count() 统计有多少元素,一般配合过滤操作使用
List list = Arrays.asList("aa","dd","cc","bb");
long count = list.stream().filter(item->item.equals("cc")).count();
System.out.println(count); // 1
Optional
List list = Arrays.asList("aa","dd","cc","bb");
Optional max = list.stream().max(String::compareTo);
System.out.println(max);
// Optional[dd]
Optional
List list = Arrays.asList("aa","dd","cc","bb");
Optional mix = list.stream().mix(String::compareTo);
System.out.println(mix);
// Optional[aa]
forEach 迭代遍历,内部迭代
void forEach(Consumer super T> action) 前面已经多次使用了forEach, 接收一个 Consumer 实现类对象。这是一个迭代方法
List list = Arrays.asList("aa","dd","cc","bb");
list.stream().forEach(System.out::println);
// 输出
aa
dd
cc
bb
T reduce(T identity, BinaryOperator
Optional
U reduce(U identity,BiFunction accumulator, BinaryOperator combiner)
规约有上面3个重载的方法。
方法1:T reduce(T identity, BinaryOperator
看源码:
public interface BinaryOperator extends BiFunction {省略其他}
可以看到,继承了BiFunction接口,而且三个泛型都是一样的。
看使用
List list = Arrays.asList("aa","dd","cc","bb");
String reduce = list.stream().reduce("", (s1, s2) -> s1 + s2);
System.out.println(reduce);
// 输出
aaddccbb
再看看一个例子
List list = Arrays.asList(1,2,3,4,5,6,7,8,9);
Integer reduce = list.stream().reduce(0, Integer::sum);
System.out.println(reduce);
方法2、Optional
List list = Arrays.asList(1,2,3,4,5,6,7,8,9);
Optional reduce = list.stream().reduce(Integer::sum);
System.out.println(reduce);
// Optional[45]
方法3、 U reduce(U identity,BiFunction accumulator, BinaryOperator combiner)
这个方法的解释参考:[五]java函数式编程归约reduce概念原理 stream reduce方法详解 reduce三个参数的reduce方法如何使用 - noteless - 博客园
第一个参数,依然是初始值
第二个参数是 计算逻辑
第三个参数是 多个结果的合并方式
大体就是 第三个参数是多个子任务的结果联合方式。在串行流中第三个参数没有什么用。但是在并行流中,第一个参数都会参与每一个线程的运算。这时,联合方式就有作用了。
List list = Arrays.asList(1,2,3,4,5,6,7,8,9);
Integer reduce = list.stream().reduce(1, Integer::sum, Integer::sum);
Integer reduce2 = list.parallelStream().reduce(1, Integer::sum, Integer::sum);
System.out.println(reduce); // 46
System.out.println(reduce2); // 54
看了上面的文章你应该会理解为什么结果会不一样,因为初始值参与了每一个线程的计算。即多计算了几次。
List list = Arrays.asList(1,2,3,4,5,6,7,8,9);
Integer reduce = list.stream().reduce(8, Integer::sum, Integer::sum);
Integer reduce2 = list.parallelStream().reduce(8, Integer::sum, (n1, n2)-> n1+n2 - 8);
System.out.println(reduce); // 53
System.out.println(reduce2); // 53
解决方法是 要么初值为0,要么在联合的时候把多加的初值减掉。上面的代码就是采用减掉多加的初值。
这个collect方法 接收3个参数 第一个参数 是一个提供者函数是接口、第二个是 BiConsumer
这个方法用的较少,此处略过!
Collectors 工具类提供了很多静态方法,可以产生 Collector 实现类对象,方便的收集 List、Set、Map等
List list = Arrays.asList(1,2,3,4,5,6,7,8,9);
// Collectors.toList() 获取Collector 实现类对象,收集为 List
List collect = list.stream().filter(item -> item > 5).collect(Collectors.toList());
System.out.println(collect);
// [6, 7, 8, 9]
Collectors还有 toSet、toMap、toCollection 等方法
这个类主要是为了解决空指针异常,防止空指针异常污染代码。
Optional类是一个容器类。可以保存类型T的值,或者保存null,表示这个值不存在。
这是一个可以为null的容器对象,如果值存在,则isPresent方法会返回true、调用get方法会返回该对象。
否则返回false。
有一个Cat 类
public class Cat implements Comparable, Serializable {
public static final long serialVersionUID = 1L;
private Car car;
public int age;
public String type = "cat";
@Override
public int hashCode() {
int result = age;
result = 31 * result + (type != null ? type.hashCode() : 0);
return result;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Cat)) {
return false;
}
Cat cat = (Cat) o;
if (age != cat.age) {
return false;
}
return Objects.equals(type, cat.type);
}
@Override
public int compareTo(Object o) {
if ( !(o instanceof Cat)){
throw new RuntimeException("必须传入相同类型的对象");
}
Cat cat = (Cat) o;
if (this == cat){
return 0;
}
return Integer.compare(this.age, cat.age);
}
public Cat(int age, String type) {
this.age = age;
this.type = type;
}
public Cat() {
}
@Override
public String toString() {
return "Cat{" +
"age=" + age +
", type='" + type + '\'' +
'}';
}
}
再来一个类,Cat 作为 属性
public class BigCat {
private Cat cat;
public BigCat(Cat cat) {
this.cat = cat;
}
public BigCat() {
}
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
@Override
public String toString() {
return "BigCat{" +
"cat=" + cat +
'}';
}
}
使用:
Cat cat = new Cat();
// 使用of方法创建一个Optional实例, 参数不能为空
Optional cat1 = Optional.of(cat);
// 使用empty方法创建一个空的实例
Optional
用法例子:
以前这样写: 会报空指针异常!
@Test
public void test1() throws Exception {
BigCat bigCat = new BigCat();
Integer catAge = getCatAge(bigCat);
System.out.println(catAge);
}
public Integer getCatAge(BigCat bigCat){
return bigCat.getCat().getAge();
}
一般是这样进行改进的:
@Test
public void test1() throws Exception {
BigCat bigCat = new BigCat();
Integer catAge = getCatAgeModify1(bigCat);
System.out.println(catAge);
}
public Integer getCatAgeModify1(BigCat bigCat){
if (Objects.nonNull(bigCat) && Objects.nonNull(bigCat.getCat())){
return bigCat.getCat().getAge();
}
return -1;
}
但是有了Optional ,我们可以这样玩:
public Integer getCatAgeModify1(BigCat bigCat) {
Optional bigCatOpt = Optional.ofNullable(bigCat);
// orElse 返回的对象一定非空
BigCat notNullBigCat = bigCatOpt.orElse(new BigCat());
Cat cat = notNullBigCat.getCat();
// 有2层,还要再包装一下
Optional optional = Optional.ofNullable(cat);
Cat notNullCat = optional.orElse(new Cat());
return notNullCat.getAge();
}
测试:
Integer catAge = getCatAgeModify1(null);
System.out.println(catAge);
// 0
就算放入null,也不会报错了。下面测试正常情况
BigCat bigCat = new BigCat();
bigCat.setCat(new Cat());
bigCat.getCat().setAge(15);
Integer catAge = getCatAgeModify1(bigCat);
System.out.println(catAge); // 15
如果得到一个Optional对象,如果获取里面的值呢?
可以使用get方法。但是需要保证一定要有值。可以先使用 isPresent 先判断一下再调用get。