只包含一个未实现方法的接口,称作“函数式接口”,最好加上注解 @FunctionalInterface
这一类接口的匿名内部类的对象,可以用lambda表达式代替。
->符号,左边是要实现的方法的参数列表,右边是代码块。如果只有一句return xxx,那么可以同时省掉代码块的大括号边界和return关键字。
强调:
C++可以将一个函数定义作为参数类型和返回值类型,并将函数指针作为实际参数和返回值
java8将“函数式接口”作为参数类型和返回值类型,并将一个实现了这个接口的类作为实际参数和返回值
@FunctionalInterface
interface FunInterface {
String getString(String input);
}
public class A_01 {
public static void main(String[] args) {
FunInterface fa = (String a) -> "Yes, it is " + a; //Lambda表达式
// FunInterface fa = a -> (String a) -> { return "Yes, it is " + a;}; 代码块如果比较复杂,用大括号括起来
// FunInterface fa = a -> "Yes, it is " + a; 甚至可以更简单地写成这样
FunInterface fb = new FunInterface() { //匿名内部类
@Override
public String getString(String input) {
return "No, it is not " + input;
}
};
System.out.println(fa.getString("java8!"));
System.out.println(fb.getString("java8!"));
}
}
应当传入一个“函数式接口”的对象作为参数的地方,可以选择传入:
一个实现了此接口的匿名内部类对象
一个lambda表达式
一个::表达式,这个表达式代表了一个方法,可以是
对象名::非static方法
类名::非static方法
类名::static方法
this::本对象中的方法
super::父类对象中的方法
类名::new (这是调用某个类的构造器,构造器其实就是一个生成对象的static方法)
@FunctionalInterface
interface ExampleInterface {
int getInt(int input);
}
class Abc {
static int fun(int a) {
return a + 200;
}
int fun2(int a) {
return a + 300;
}
}
public class A_02 {
static ExampleInterface face;
static void setFace(ExampleInterface inputFace) {
face = inputFace;
}
public static void main(String[] args) {
ExampleInterface f = (int a) -> a + 100;
setFace((int b) -> b - 100);
System.out.println(face.getInt(0));
setFace(f);
System.out.println(face.getInt(0));
setFace(Abc::fun);
System.out.println(face.getInt(0));
Abc bbb = new Abc();
setFace(bbb::fun2);
System.out.println(face.getInt(0));
}
}
java8新增的接口特性,比较颠覆对“面向对象”的认知。
接口中可以有default方法,default方法已经被实现了。
在接口的实现类中:
如果不重写default方法,则使用interface定义中的方法实现代码
如果重写default方法,则使用实现类中的方法实现代码
这么做,可以给以往的接口中增加方法,不然接口增加了某方法,所有已有的实现类都无法编译通过了……
interface ExampleInterface {
int aaa(int input);
default int bbb(int input) {
return input + 500;
}
}
public class B_02 implements ExampleInterface {
@Override
public int aaa(int input) {
return input + 1000;
}
// @Override
// public int bbb(int input) {
// return input - 1000;
// }
public static void main(String[] args) {
B_02 obj = new B_02();
System.out.println(obj.aaa(0));
System.out.println(obj.bbb(0));
}
}
接口中可以有static方法,这一类方法必须在接口定义中实现。
可以直接通过接口来调用这个方法。
interface SimpleInterface {
int aaa(int input);
static int bbb(int input) {
return input + 1000;
}
}
public class B_01 {
public static void main(String[] args) {
System.out.println(SimpleInterface.bbb(0));
}
}
先给一个直观的例子,看看stream又起来有多么方便吧。
要统计一个String列表中符合某个条件的元素的数量,本来我们要写这么一段:
List words = Arrays.asList("abc", "defh", "ghiko");
int count = 0;
for (String word : words) {
if (word.length() >= 4)
++count;
}
但是使用stream之后,我们使用这么一句就行了:
List words = Arrays.asList("abc", "defh", "ghiko");
long count2 = words.stream().filter(w -> w.length() >= 4).count();
可以把stream看多“集合”或者“数组”的一个抽象,我们可以对stream做过滤、聚合、统计,并把stream还原称为一个集合或者数组。
stream并不是“集合”本身:
stream不会存储元素
stream不会改变集合对象本身
stream可能被延迟执行
注意,一个stream不能多次生成最终对象;复用stream对象时,应当十分谨慎。
创建stream对象有若干种方法:
集合类,调用 stream() 方法
数组,调用 Stream.of() 静态方法
List list = Arrays.asList(1, 2, 3);
Stream a = list.stream();
Integer[] array = {1, 2, 3};
Stream b = Stream.of(array);
Stream c = Stream.of(1, 2, 3);
也可以创建含有无限多元素的stream
Stream.generate() 静态方法,接收一个无参的方法(其实是Supplier<T>对象)作为参数
Stream.iterate() 静态方法,第一个参数是“种子”,第二个参数是一个x->y类型的方法,用于根据种子产生下一个值
Stream d = Stream.generate(() -> 1234);
Stream e = Stream.generate(Math::random);
Stream f = Stream.iterate(Integer.MIN_VALUE, n -> (n + 1) / 10);
使用map方法时,对stream中的所有元素逐一调用某个方法,转换成另一个对象,并将返回值收集到一个新的stream中。
List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
Stream sstream = list.stream().map(Integer::toBinaryString);
filter方法的参数,是一个返回值为boolean类型的方法。作为此方法的参数返回true的对象,是符合要求的对象,被放入到新的流之中。
limit返回一个只包含n个元素的新stream。
skip返回一个丢弃了前n个元素的新stream。
List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);\
Stream moreThan5 = list.stream().filter(n -> n > 5);
Stream limit = list.stream().limit(4);
Stream skip = list.stream().skip(5);
Optional
以下的一些函数会以Optional
Optional
创建一个Optional |
Optional<Integer> op = Optional.empty(); |
得到对象中的值 如果没有,会抛出NoSuchElementException |
int i = op.get(); |
检查其中是否有值 |
boolean b = op.isPresent(); |
如果其中有值,就对其中的值执行某个方法 |
op.ifPresent(v -> System.out.println(v * 10)); |
如果其中没有值,想返回些别的…… |
op = Optional.empty(); |
一个SQL查询,可以用MAX,MIN,COUNT,SUM等函数得到单个的值。stream也有类似的聚合操作方法。
感觉可以大致分为几类:最大最小值,加和,数量,是否有满足条件的元素,返回一个满足某条件的元素……
List list = Arrays.asList("ab", "cde", "fegr", "opfsg", "dgasdfa");
Optional op = list.stream().filter(v -> v.length() > 4).findFirst();
boolean b = list.stream().anyMatch(v -> v.endsWith("sg"));
List la = Arrays.asList(1, 3, 4, 5, 3, 2);
Optional i = la.stream().max((aa, ba) -> aa - ba);
一个stream做完各种转换之后,可以将其恢复成为数组或者集合。
生成数组:使用toArray方法,传入一个数组的构造方法作为参数,否则会返回Object[]类型。
List list = Arrays.asList(1, 2, 4, 3, 8, 5, 6, 9, 0, 4);
Object[] arr = list.stream().map(n -> "int:" + n).toArray();
String[] arrr = list.stream().map(n -> "int:" + n).toArray(String[]::new);
生成List:使用collect方法,注意参数
List list = Arrays.asList(1, 2, 4, 3, 8, 5, 6, 9, 0, 4);
List ll = list.stream().map(n -> "int:" + n).collect(Collectors.toList());
生成Map:使用collect方法,使用不一样的参数。
解释一下toMap的三个参数。第一个是map的键;第二个是map的值;第三个是有重复键时的解决方法。
List list = Arrays.asList(1, 2, 4, 3, 8, 5, 6, 9, 0, 4);
Stream ss = list.stream().map(n -> "int:" + n);
Map mm = ss.collect(Collectors.toMap(n -> n
, n -> n.substring(2)
, (existvalue, newvalue) -> newvalue));
生成指定实现的集合,有两种方式。一种是使用Collectors.toCollection,参数是集合的构造方法;的第二种是三个参数,分别给出构造方法、添加方法、批量添加方法。
List list = Arrays.asList(1, 2, 4, 3, 8, 5, 6, 9, 0, 4);
LinkedList lll = list.stream().map(n -> "int:" + n).collect(Collectors.toCollection(LinkedList::new)); //指定使用LinkedList
HashSet hhh = list.stream().map(n -> "int:" + n).collect(HashSet::new, HashSet::add, HashSet::addAll); //指定使用HashSet
其行为很像SQL中的GROUP BY操作。
重点关注示例中的Collectors.groupingBy方法,带的参数决定了生成的Map的key/value的类型。不带参数时,默认为对象的List;带参数时,可以是set等别的集合,也可以是一个计算得到的值。
static class Demo {
public Demo(int aa, int bb) {
a = aa;
b = bb;
}
private int a;
private int b;
public int getA() {
return a;
}
public int getB() {
return b;
}
@Override
public String toString() {
return String.format("{a:%d, b:%d}", a, b);
}
}
public static void main(String[] args) {
List demoList = new ArrayList<>();
demoList.add(new Demo(1, 12));
demoList.add(new Demo(1, 13));
demoList.add(new Demo(2, 22));
demoList.add(new Demo(2, 23));
demoList.add(new Demo(3, 33));
demoList.add(new Demo(3, 34));
Map> map = demoList.stream()
.collect(Collectors.groupingBy(Demo::getA));
System.out.println(map);
Map> map2 = demoList.stream()
.collect(Collectors.groupingBy(Demo::getA, Collectors.toSet()));
System.out.println(map2);
Map map3 = demoList.stream()
.collect(Collectors.groupingBy(Demo::getA, Collectors.averagingInt(Demo::getB)));
System.out.println(map3);
}
第一大节已经给出了“函数式接口”的定义,就是只有一个未实现的方法,且用@FunctionalInterface注解标注的接口。
上面的例子中,各种接受一个lambda表达式、::表达式作为参数的地方,其实际的参数类型,实际就是函数式接口。
以下是书中总结的函数式接口列表
接口定义(只写了未实现的方法,忽略了static和default方法) |
描述 |
---|---|
@FunctionalInterface |
提供一个T类型的值 |
@FunctionalInterface |
处理一个T类型的值 |
*/ |
处理两个类型,分别是T和U的值 |
@FunctionalInterface |
输入一个类型T,得到boolean值 |
@FunctionalInterface @FunctionalInterface @FunctionalInterface
|
输入一个类型T,得到int/long/double值 |
@FunctionalInterface @FunctionalInterface @FunctionalInterface |
输入一个int/double/long值,产生一个R类型的值 |
@FunctionalInterface } |
输入T类型值,返回R类型值 |
@FunctionalInterface |
输入T、U类型值,得到R类型值 |
@FunctionalInterface |
由T类型得到T类型 |
@FunctionalInterface } |
由两个T类型得到T类型 |