Predicate
主要功能是验证数据
//源码 public interface Predicate<T> { /** * 制定检查模板 */ boolean test(T t); /** * 得到相反结果 */ default Predicate<T> negate() { return (t) -> !test(t); //相当于new了一个Predicate对象并重写了其test方法,如此可以进行链式书写 } …… } //调用 Predicate<String> predicate = (s) -> s.length() > 0; //重写test predicate.test("foo"); // true predicate.negate().test("foo"); // negate返回一个Predicate对象,调用这个对象的test方法;false Predicate<Boolean> nonNull = Objects::nonNull; //重写test Predicate<Boolean> isNull = Objects::isNull; Predicate<String> isEmpty = String::isEmpty; Predicate<String> isNotEmpty = isEmpty.negate();
Consumer
主要功能是更改输入参数的内部状态
//源码 public interface Consumer<T> { void accept(T t); /** * 检测入参是否为空并返回一个实现了方法的Consumer对象 * 用于链式书写:consumer.andThen().accept() */ default Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; //既对调用andThen的对象应用accept方法,也对传入andThen的参数应用accept方法 } } //调用 User user=new User("zm"); //接收一个参数 Consumer<User> userConsumer=User1 -> User1.setName("zmChange"); userConsumer.accpet(user); System.out.println(user.getName());
Predicate和Consumer综合应用
- 通过一个学生例子:Student类包含姓名、分数和费用,每个学生科根据分数获得不同程度的费用折扣
Student类:
public class Student { String firstName; String lastName; Double grade; Double feeDiscount = 0.0; Double baseFee = 2000.0; public Student(String firstName, String lastName, Double grade) { this.firstName = firstName; this.lastName = lastName; this.grade = grade; } public void printFee(){ Double newFee = baseFee - ((baseFee * feeDiscount)/100); System.out.println("The fee after discount: " + newFee); } }
用一个Predicate接口判断输入的Student对象是否拥有费用打折的资格,然后使用Consumer接口更新Student对象的折扣:
public class PredicateConsumerDemo{ public static Student updateStudentFee(Student student, Predicate<Student> predicate, Consumer<Student> consumer){ if(predicate.test(student)){ consumer.accept(student); } return student; } }
调用updateStudentFee()
Stream stream=Stream.of("a","b","c"); //构造stream String[] strArray=new String[]{"a","b","c"}; stream=Arrays.stream(strArray); //底层也是构造stream List<String> list=Arrays.asList(strArray); //底层是new ArrayList<>(strArray); stream=list.stream(); //底层也是stream IntStream.of(new int[]{1,2,3}).forEach(System.out::println); Student student=new Student("Ashok","Kumar",9.5); student=PredicateConsumerDemo.updateStudentFee(student, stu -> stu.grade > 8.5, stu -> stu.feeDiscount = 30.0); student.printFee(); //The fee after discount:1400.0
Supplier
主要是返回一个实例,相当于工厂
//实例化一个String对象,值为hello world Supplier<String> supplier=() -> "hello world"; System.out.println(supplier.get()); //实例化一个对象 Supplier<Student> student=() -> new Student(); student.get(); //获取此对象
Function
- 主要功能是对输入的参数进行计算并返回结果
源码:
public interface Function<T, R> { //指定入参、返回值的类型 /** * 对输入的参数进行某种计算并返回定义接口时指定的类型 */ R apply(T t); /** * 返回一个Function对象,重写其apply方法为:先执行before的apply方法再执行调用者的apply方法 */ default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); } /** * 先执行调用者的apply方法再执行after的apply方法 */ default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); } /** * static:静态方法;<T>:泛型方法; * Function<T,T>:返回值为一个Function对象, * 此对象被重写的apply方法入参和返回值类型均为T * 功能:返回一个Function对象,其apply方法为传入什么返回什么 */ static <T> Function<T, T> identity() { return t -> t; } }
调用
Function<Integer,Integer> name=e -> e*2; Function<Integer,Integer> square=e -> e*e; int value=name.andThen(square).apply(3); System.out.println(value); //36 value=name.compose(square).apply(3); //18 System.out.println(value); String identity=(String)Function.identity().apply("hello"); System.out.println(identity); //hello
BiFunction
- BiFunction与Function的区别就是传入两个参数,依旧返回一个参数(比如传入两个字符串连接成一个返回)
调用
Function<String,Integer> myFunction=str -> str.length(); BiFunction<String,String,String> myBiFunction=(str1,str2) -> str1+str2; int len=myFunction.apply("hello"); System.out.println(len); //5 String str=myBiFunction.apply("hello","world"); System.out.println(str); //hello world
Comparator
- 主要功能是比较任意类型数据,多用于排序、分组
源码:(不是很懂为什么可以有两个抽象方法,但是要实现Comparator接必须实现compare(),equals()随意
public interface Comparator<T> { /** * 比较两个参数,数字一般相减,字符串一般用compareTo() * 返回1,0,-1,以该接口为入参之一的Collection.sort()分别进行升序、不处理、降序处理 */ int compare(T o1, T o2); /** * 判断调用者是否和入参相等,同Object.equals() */ boolean equals(Object obj); /** * 还有一些默认函数,不常用 */ }
排序应用
public class FormulaImpl{ //首先定义一个狗狗的类 class Dog{ public int age; public String name; public Dog(int age,String name){ super(); this.age=age; this.name=name; } @Override public String toString() { return "Dog{" + "age=" + age + ", name='" + name + '\'' + '}'; } } //然后用Comparator来排序 public static void main(String[] args) { //这里注意静态方法里不能有非静态的成员,一定要先实例外部类再实例内部类 List<Dog> list=new ArrayList<>(); list.add(new FormulaImpl().new Dog(1,"a")); list.add(new FormulaImpl().new Dog(2,"b")); list.add(new FormulaImpl().new Dog(3,"c")); //按照年龄降序 Comparator<Dog> comparator=(obj1,obj2) -> obj2.age-obj1.age; //升序:obj1.age-obj2.age Collections.sort(list,comparator); System.out.println("按照年龄降序:"+list); //[Dog{age=3, name='c'}, Dog{age=2, name='b'}, Dog{age=1, name='a'}] //按照字母顺序升序 comparator=(obj1,obj2) -> obj1.name.compareTo(obj2.name); //[Dog{age=1, name='a'}, Dog{age=2, name='b'}, Dog{age=3, name='c'}] Collections.sort(list,comparator); System.out.println("按照字母顺序升序:"+list); } }
分组应用
public class FormulaImpl{ //首先定义一个苹果类 class Apple { public String color; public int weight; public Apple(String color, int weight) { super(); this.color = color; this.weight = weight; } @Override public String toString() { return "Apple [color=" + color + ", weight=" + weight + "]"; } } /** * 分组:入参为数据源和比较规则,比如吧相同颜色或者同一重量级的分到一组 * 返回组别的集合 */ public static <T> List<List<T>> divider(Collection<T> datas, Comparator<? super T> c) { //result是所有组的集合,每个组又单独是一个集合 List<List<T>> result = new ArrayList<List<T>>(); //result.size()=0 //开始分组:遍历每个苹果 for (T t : datas) { //设置flag boolean isSameGroup = false; //同已有组的任一成员比较,相等即进组;第一个苹果肯定是不用进此循环直接到下一步的 for (int j = 0; j < result.size(); j++) { if (c.compare(t, result.get(j).get(0)) == 0) { isSameGroup = true; result.get(j).add(t); break; } } //不是前面任何组的,单独开组 if (!isSameGroup) { List<T> innerList = new ArrayList<T>(); //以下两行看起来好像是开了新组但是却没把苹果放进去,其实到了遍历集合的时候自然就进去了 result.add(innerList); innerList.add(t); } } return result; } //调用 public static void main(String[] args) { List<Apple> list=new ArrayList<>(); list.add(new FormulaImpl().new Apple("红",200)); list.add(new FormulaImpl().new Apple("红",100)); list.add(new FormulaImpl().new Apple("绿",201)); list.add(new FormulaImpl().new Apple("绿",101)); list.add(new FormulaImpl().new Apple("黄",102)); list.add(new FormulaImpl().new Apple("黄",202)); Comparator<Apple> comparator=(obj1,obj2) -> obj1.color.compareTo(obj2.color); List<List<Apple>> byColor=divider(list,comparator); System.out.println("按照颜色分组:"+byColor); comparator=(obj1,obj2) -> (obj1.weight/100 == obj2.weight/100) ? 0 : -1; List<List<Apple>> byWeight=divider(list,comparator); System.out.println("按照重量分组:"+byWeight); } }
- 从这个例子中我们可以看出,当一个数组需要按照不同的规则排序的时候,我们可以定义不同的比较器传入排序方法中。
Stream
- 主要功能是对集合转换成的数据流进行过滤、转换、计算等操作
- 除了Stream,平行的还有IntStream、LongStream、DoubleStream三种数值型Stream,当然我们也可以用Stream
来代替,但是boxing和unboxing会很耗时。 - Map接口不支持Stream
源码:
public interface Stream<T> extends BaseStream<T, Stream<T>> { /** * 返回符合判断条件的流对象 */ Stream<T> filter(Predicate<? super T> predicate); /** * 返回经过计算的流对象 */ <R> Stream<R> map(Function<? super T, ? extends R> mapper); /** * 返回经过计算的流对象(该计算的结果为int型) */ IntStream mapToInt(ToIntFunction<? super T> mapper); /** * 返回经过计算的流对象,这个流对象的每一个元素都是流 */ <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper); /** * 返回经过计算的int型流对象,这个流对象的每一个元素都是int型的流 */ IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper); /** * 去重 */ Stream<T> distinct(); /** * 元素有得比较则按默认规则排序,否则抛异常 */ Stream<T> sorted(); /** * 按指定规则排序 */ Stream<T> sorted(Comparator<? super T> comparator); /** * 对流的所有元素进行同一个操作 */ void forEach(Consumer<? super T> action); /** * 遍历流的元素,与forEach的区别是它返回的是Stream对象,可以继续进行链式操作 */ Stream<T> peek(Consumer<? super T> action); /** * 提供一个起始值identity,然后依照运算规则BinaryOperator和所有的元素组合 * 字符串的拼接、数值的sum、min、max、average都是特殊的reduce * 例如:Stream的sum就相当于 * Integer sum=integers.reduce(0,(a,b) -> a+b); * 或Integer sum=integers.reduce(0,Integer::sum); */ T reduce(T identity, BinaryOperator<T> accumulator); /** * 还有很多方法 */ }
流的构造:
//用数组构造 Stream stream=Stream.of("a","b","c"); //构造stream String[] strArray=new String[]{"a","b","c"}; stream=Arrays.stream(strArray); //底层也是构造stream //用集合构造 List<String> list=Arrays.asList(strArray); //底层是new ArrayList<>(strArray); stream=list.stream(); //底层也是构造stream
数值流的构造
IntStream.of(new int[]{1,2,3});
流转换为其他数据结构
//转换成数组 Stream stream=Stream.of("a","b","c"); String[] strArray= (String[]) stream.toArray(String[]::new); //a,b,c //转换成集合 stream=Stream.of("a","b","c"); //Stream只能使用一次,用完即关闭,需重开 List<String> list1= (List<String>) stream.collect(Collectors.toList()); //a,b,c stream=Stream.of("a","b","c"); List<String> list2= (List<String>) stream.collect(Collectors.toCollection(ArrayList::new)); //a,b,c //转换成String stream=Stream.of("a","b","c"); String str=stream.collect(Collectors.joining()).toString(); //abc
调用map()
//转换大写 List<String> list=new ArrayList<>(); list.add("a"); list.add("b"); list.add("c"); List<String> output=list.stream() .map(String::toUpperCase) .collect(Collectors.toList()); System.out.println(output); //平方数 List<Integer> nums=Arrays.asList(1,2,3,4); List<Integer> squareNums=nums.stream() .map(n -> n*n) .collect(Collectors.toList()); System.out.println(squareNums);
flapMap()
//flatMap把Stream中的层级结构扁平化,直接将最底层元素抽取出来构成Stream Stream<List<Integer>> inputStream=Stream.of(Arrays.asList(1), Arrays.asList(2,3), Arrays.asList(4,5,6)); Stream<Integer> outputStream=inputStream.flatMap(midList -> midList.stream()); List<Integer> list2=outputStream.collect(Collectors.toList()); //Stream不能直接打印,打印出来是一串看不懂的类标志 System.out.println(list2); //[1, 2, 3, 4, 5, 6]
peek()
List<String>list=Stream.of("one","two","three","four").filter(e -> e.length()>3).peek(e -> System.out.println("Filter value:"+e)) .map(String::toUpperCase).peek(e -> System.out.println("Mapped value:"+e)) .collect(Collectors.toList()); //一定要转换成List才会打印内容出来
reduce():有起始值的话一定返回确定的类型,否则可能由于元素不够而判断不出类型返回Optional
//字符串连接 String concat=Stream.of("A","B","C","D").reduce("",String::concat); //ABCD //求最小值 double min=Stream.of(-1.5,1.0,-3.0,-2.0).reduce(Double.MAX_VALUE,Double::min); //-3.0 //求和 int sum=Stream.of(1,2,3,4).reduce(0,Integer::sum); //10 //过滤+字符串连接 concat=Stream.of("a","B","c","D","e","f").filter(x -> x.compareTo("Z")>0) .reduce("",String::concat);
limit()&skip()
//Person类 class Person{ public int no; //序号 private String name; public Person(int no, String name) { this.no = no; this.name = name; } //注意:getName方法有打印操作 public String getName(){ System.out.println(name); return name; } } //塞数据 List<Person> persons=new ArrayList<>(); for (int i=0;i<11;i++){ //塞11个 Person person=new FormulaImpl().new Person(i,"name"+i); persons.add(person); } //调用方法 List<String> personList=persons.stream() .map(Person::getName) .limit(10) //map管道被限制到只打印前10个 .skip(3) //personList先被限制到前十又被跳过前三 .collect(Collectors.toList()); //第一次打印:输出0~10 //第二次打印 System.out.println(personList); //输出3~10
limit()&skip()对sorted()无作用
List<Person> persons=new ArrayList<>(); for (int i=0;i<5;i++){ Person person=new FormulaImpl().new Person(i,"name"+i); persons.add(person); } List<Person> personList=persons.stream() .sorted((p1,p2) -> p1.getName().compareTo(p2.getName())) //不受限制,打印5个 .limit(2) .collect(Collectors.toList()); System.out.println(personList); //输出2个
打印结果:
name1 name0 name2 name1 name3 name2 name4 name3 [com.java1234.tool.FormulaImpl$Person@18769467, com.java1234.tool.FormulaImpl$Person@46ee7fe8]
- 上面的打印结果说明sorted的底层是两个两个元素拿出来比较的
max()&min()
//找出最长一行的长度 BufferedReader br = new BufferedReader(new FileReader("c:\\SUService.log")); int longest = br.lines(). //br.lines()得到的是一个Stream对象 mapToInt(String::length). max(). getAsInt(); br.close();
distinct()
//找出全文的单词,转小写,去重,并排序 BufferedReader br=new BufferedReader(new FileReader("d:\\test.txt")); List<String> words=br.lines().flatMap(line -> Stream.of(line.split(" "))) .filter(word -> word.length()>0) .map(String::toLowerCase) .distinct() .sorted() .collect(Collectors.toList()); br.close();
allMatch()&anyMatch()&noneMatch():Stream中全部、任一、无一元素符合传入的Predicate,返回true
在Person类中加个年龄属性和获取年龄的方法,做以下测试
List<Person> persons = new ArrayList(); persons.add(new Person(1, "name" + 1, 10)); persons.add(new Person(2, "name" + 2, 21)); persons.add(new Person(3, "name" + 3, 34)); persons.add(new Person(4, "name" + 4, 6)); persons.add(new Person(5, "name" + 5, 55)); boolean isAllAdult = persons.stream(). allMatch(p -> p.getAge() > 18); //false boolean isThereAnyChild = persons.stream(). anyMatch(p -> p.getAge() < 12); //true
count():计数,返回Stream中元素的个数,返回值类型是long;是一个最终操作
long startsWithB = stringCollection.stream() .filter((s) -> s.startsWith("b")) .count();
并行Stream
- Stream有串行和并行两种,串行是在一个线程中依次完成,而并行则是在多个线程上同时执行,可提高效率。
下面我们来创建一个无重复的大容量List
int max=1000000; List<String> list=new ArrayList<>(max); for (int i=0;i<max;i++){ UUID uuid=UUID.randomUUID(); list.add(uuid.toString()); }
串行排序
long t0=System.nanoTime(); list.stream().sorted(); long t1=System.nanoTime(); long millis=TimeUnit.NANOSECONDS.toMillis(t1-t0); System.out.println(String.format("Sequential sort took: %d ms",millis)); //899mx
并行排序,效率提高一倍
t0=System.nanoTime(); list.parallelStream().sorted(); t1=System.nanoTime(); millis=TimeUnit.NANOSECONDS.toMillis(t1-t0); System.out.println(String.format("Parallel sort took: %d ms",millis)); //472ms
参考文章