java8

2.行为参数化传递代码

2.1 演变过程

2.1.1 筛选绿色苹果

public class Client {
    public static List<Apple> filterGreenApples(List<Apple> repository){
        List<Apple> result = new ArrayList<>();
        for(Apple apple:repository){
            if("green".equals(apple.getColor())){
                result.add(apple);
            }
        }
        return result;
    }
}

改进:筛选红色苹果

2.1.2 颜色作为参数

public class Client {
    public static List<Apple> filterGreenApples(List<Apple> repository,String color){
        List<Apple> result = new ArrayList<>();
        for(Apple apple:repository){
            if(color.equals(apple.getColor())){
                result.add(apple);
            }
        }
        return result;
    }
}

增加:筛选重量

public class Client {
    public static List<Apple> filterGreenApples(List<Apple> repository,int weight){
        List<Apple> result = new ArrayList<>();
        for(Apple apple:repository){
            if(weight < apple.getWeight()){
                result.add(apple);
            }
        }
        return result;
    }
}

改进:筛选两个属性

2.1.3 筛选两个属性

    /**
     * @param repo 所有苹果
     * @param weight 
     * @param color
     * @param flag true筛选颜色,false晒讯重量
     * @return
     */
    public static List<Apple> filter(List<Apple> repo,
            int weight,String color,boolean flag){
        List<Apple> result = new ArrayList<>();
        for(Apple apple:repo){
            if(flag && apple.getColor().equals(color) 
                    || !flag && apple.getWeight()>weight){
                result.add(apple);
            }
        }
        return result;      
    }

改进:行为参数化

2.1.4 根据抽象条件筛选

需要根据Apple的某些属性来返回一个boolean值,我们将它称为谓词(也就是一个返回boolean值的函数),可定义一个接口来对选择标准建模

public interface ApplePredicate {
    boolean test(Apple apple);
}

然后可以用多个该接口的实例来表示多个标准

 //筛选重量
public class AppleWeightPredicate implements ApplePredicate { 
    @Override
    public boolean test(Apple apple) { 
        return apple.getWeight() > 150;
    } 
}
//筛选颜色
public class AppleColorPredicate implements ApplePredicate{
    @Override
    public boolean test(Apple apple) { 
        return "green".equals(apple.getColor());
    } 
}

根据抽象条件筛选


    public static List<Apple> filter(List<Apple> repo,
            ApplePredicate predicate){
        List<Apple> result = new ArrayList<>();
        for(Apple apple:repo){ 
            if(predicate.test(apple)){
                result.add(apple);
            }
        }
        return result;      
    }

多种行为一个参数

public class AppleRedHeavyPredicate implements ApplePredicate { 
    @Override
    public boolean test(Apple apple) {
         
        return "red".equals(apple.getColor()) 
                && apple.getWeight() > 150;
    } 
}
//筛选出红色且重量大于150的苹果
filter(repository,new AppleRedHeavyPredicate());

2.1.5 使用匿名类

        List<Apple> redApples = filter(repo,new ApplePredicate() { 
            @Override
            public boolean test(Apple apple) { 
                return "red".equals(apple.getColor());
            }
        });

    public static List<Apple> filter(List<Apple> repo,
            ApplePredicate predicate){
        List<Apple> result = new ArrayList<>();
        for(Apple apple:repo){ 
            if(predicate.test(apple)){
                result.add(apple);
            }
        }
        return result;      
    }

2.1.6 使用Lambda表达式

List<Apple> greenApples = 
        filter( repo,(Apple apple)->"green".equals(apple.getColor()) );

2.1.7 类型抽象化

public static  <T> List<T> filter(List<T> repo, ApplePredicate<T> predicate){
    List<T> result = new ArrayList<>();
    for(T t:repo){ 
        if(predicate.test(t)){
            result.add(t);
        }
    }
    return result;      
}

public interface ApplePredicate<T> {
    boolean test(T t);
} 

2.2 实例

list.sort(new Comparator<Apple>() { 
    @Override
    public int compare(Apple o1, Apple o2) { 
        return o1.getWeight().compareTo(o2.getWeight()) ;
    } 
});

list.sort((Apple o1, Apple o2) -> o1.getWeight().compareTo(o2.getWeight()));
Thread t = new Thread(new Runnable() {
    
    @Override
    public void run() { 
        System.out.println("run");
    }
});

t = new Thread(()->System.out.println("run"));

3. Lambda表达式

3.1 入门

  • 参数列表
  • 箭头
  • 主体
//String类型的参数并隐形的返回一个int
(String s) -> s.length()
//Apple类型的参数并返回一个boolean
(Apple a) -> a.getWeight() > 150
//两个int参数,没有返回值,
(int x,int y) -> {
    System.out.println("Result = " + x+y);
}
//没有参数返回一个int
() -> 42
//两个apple类型的参数,返回int
(Apple a,Apple b) -> a.getWeight().compareTo(b.getWeight())
//没有参数,返回一个apple
() -> new Apple()

3.2 Lambda使用场景

3.2.1 函数式接口

只定义一个抽象方法的接口
例:下面哪个是函数式接口

public interface Adder{
    int add(int a,int b);
}

public interface SmartAdder extends Adder{
    double add(double a,double b);
}

public interface Nothing{
}

只有Adder是函数式接口,SmartAdder不是函数式接口,因为它有两个抽象方法,Nothing不是函数式接口,因为它没有抽象方法。

Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数接口的实例

class test  
{
    public static void main (String[] args) throws java.lang.Exception
    {
        Runnable ra = new Runnable(){
            @Override
            public void run(){
                System.out.println("run1");
            }
        };
        
        Runnable rb = () -> System.out.println("run2");
        process(ra);
        process(rb);
        //lambda方式
        process(() -> System.out.println("run3")); 
    }
    public static void process(Runnable r){
        r.run();
    }
}

3.2.2 函数描述符

3.3环绕执行模式

//1.原函数
public static String processFile() throws IOException{
    try(BufferedReader br = 
            new BufferedReader(new FileReader("data.txt"))){
        return br.readLine();
    }
}

//2.使用函数式接口
    public interface Processor{
        String process(BufferedReader b) throws IOException;
    }
    
    public static String processFile(Processor processor) throws IOException{
 
    }
    
    //3.使用函数式接口
public static String processFile(Processor processor) throws IOException{
    try(BufferedReader br = 
            new BufferedReader(new FileReader("data.txt"))){
        //接口处理
        return processor.process(br);
    }
}

String oneline = processFile(
        (BufferedReader br)->br.readLine());
String twoLine = processFile(
        (BufferedReader br)->br.readLine() + br.readLine());

3.4 使用函数式接口

java8的java.util.function包中引入了几个新的函数式接口,有Predicate,Consumer,Function

3.4.1 Predicate

Predicate<T>内部定义了一个test的抽象方法,返回boolean,

@FunctionalInterface
public interface Predicate<T>{
    boolean test(T t);
}

使用示例:

List<String> noEmpty = filter(all,(String s)->!s.isEmpty());

public static  <T> List<T> filter(List<T> repo, Predicate<T> predicate){
    List<T> result = new ArrayList<>();
    for(T t:repo){ 
        if(predicate.test(t)){
            result.add(t);
        }
    }
    return result;      
}

3.4.2 Consumer

定义了一个accept方法,接收泛型T对象,没有返回,如果要访问T对象并进行某些操作,就可以使用这个接口

@FunctionalInterface
public interface Consumer<T>{
    void accept(T t);
}
forEach(Arrays.asList(1,2,3,4,5),
        (Integer i)->System.out.println(i));    

public static <T> void forEach(List<T> list,Consumer<T> c){
    for(T t:list){
        c.accept(t);
    }
}

3.4.3 Function

定义了一个叫apply的抽象方法,接收一个泛型T,返回一个泛型R

@FunctionalInterface
public interface Function<T,R>{
    R apply(T t);
}

使用示例:

 
map(Arrays.asList(1,2,3,4,5),
        (Integer i)-> i +"");   
public static <T,R> List<R> map(List<T> list,Function<T,R> func){
    List<R> result = new ArrayList<>();
    for(T t:list){
        result.add(func.apply(t));
    }
    return result;
}   

3.5 关于类型

3.5.1 类型检查

//调用,那么使用lambda的上下文是什么呢
List<Apple> heavier = 
    filter(repo,(Apple a) -> a.getWeight()>150);
//上下文:目标类型是Predicate<Apple>;T目标类型是Apple
//Predicate这个函数式接口的抽象方法是什么?
filter(List<Apple> repo,Predicate<Apple> predicate);

//抽象方法:接收一个Apple类型的参数返回一个boolean
//与调用者的描述符匹配
boolean test(Apple apple);

3.5.2 类型推断

// 没有类型推断
List<Apple> greena = filter(null, 
        (Apple a) -> "green".equals(a.getColor()));
// 类型推断
List<Apple> greenb = filter(null, 
        a -> "green".equals(a.getColor()));
// 没有类型推断
Comparator<Apple> ca = 
        (Apple a, Apple b) -> a.getWeight().compareTo(b.getWeight());
// 有类型推断
Comparator<Apple> cb =
        (a, b) -> a.getWeight().compareTo(b.getWeight());

3.5.3 使用局部变量

Lambda可以没有限制的捕获实例变量或者静态变量,但是对于局部变量必须显示的声明为final或者事实上的final。

3.6 复合

3.6.1 比较器复合

3.6.2 谓词复合

3.6.3 函数复合

4 流入门

4.1 什么是流

流是javaapi新成员,允许以声明方式处理集合

实验数据

public class Dish {
    
    public enum Type{MEAT,FISH,OTHER}
    
    private final String name;
    private final boolean vegetarian;
    private final int calories;
    private final Type type;
    
    public Dish(String name,boolean vegetarian,int calories,Type type){
        this.name = name;
        this.vegetarian = vegetarian;
        this.calories = calories;
        this.type = type;
    }

    public String getName() {
        return name;
    }

    public boolean isVegetarian() {
        return vegetarian;
    }

    public int getCalories() {
        return calories;
    }

    public Type getType() {
        return type;
    }

    @Override
    public String toString() {
        return  name;
    } 
}

//菜单:菜的集合
        List<Dish> menu = Arrays.asList( 
                new Dish("Pork",false,800,Dish.Type.MEAT),
                new Dish("Beef",false,700,Dish.Type.MEAT),
                new Dish("Chicken",false,400,Dish.Type.MEAT),
                new Dish("Fresh Fries",true,530,Dish.Type.OTHER),
                new Dish("Rice",true,350,Dish.Type.OTHER),
                new Dish("Season Fruit",true,120,Dish.Type.OTHER),
                new Dish("Pissa",true,550,Dish.Type.OTHER),
                new Dish("prawns",false,300,Dish.Type.FISH),
                new Dish("Salmon",false,450,Dish.Type.FISH) 
                );

4.2 流简介

java8中的集合支持一个新的stream方法,它会返回一个流
流的定义:从支持数据操作的源生成的元素序列
流水线:很多流操作本身会返回一个流,这样多个操作就可以连接起来,形成一个流水线。

示例:

List<String> threeHighCalories = 
        menu.stream()//获取流
        .filter((Dish d) -> d.getCalories()>300)//筛选
        .map((Dish d) -> d.getName())//映射
        .limit(3)//限制
        .collect(toList());//import static java.util.stream.Collectors.toList;
//[Pork, Beef, Chicken]
System.out.println(threeHighCalories);
  • filter:接收lambda,从流中排除某些元素
  • map,接收lambda,将元素转成其他形式或者提取信息
  • limit,截断流,使元素不超过给定数量
  • collect,将流转换为其他形式

4.3 流与集合

4.3.1 流只能消费一次

流只能遍历一次,遍历完之后,就说这个流已经被消费了,可以从原始数据那里再获取一次流来重新遍历一遍。

4.3.2 流是内部迭代

4.4 流操作

4.4.1 中间操作

中间操作会返回一个流

List<String> threelowCalories = 
        menu.stream()//获取流
        .filter((Dish d) -> {
            System.out.println("[Filtering]======" + d.getName());
            return d.getCalories() < 400;
        })//筛选
        .map((Dish d) ->{
            System.out.println("[mapping]:---> " + d.getName());
            return d.getName();
        })//映射
        .limit(3)//限制
        .collect(toList());

执行过程
[Filtering]======Pork
[Filtering]======Beef
[Filtering]======Chicken
[Filtering]======Fresh Fries
[Filtering]======Rice
[mapping]:—> Rice
[Filtering]======SeasonFruit
[mapping]:—> SeasonFruit
[Filtering]======Pissa
[Filtering]======prawns
[mapping]:—> prawns
[Rice, SeasonFruit, prawns]

中间操作

操作       类型    返回类型     操作参数        函数描述符
filter     中间    Stream<T>    Predicate<T>    T -> boolean
map        中间    Stream<R>    Function<T,R>   T -> R
limit      中间    Stream<T>      
sorted     中间    Stream<T>    Comparator<T>   (T,T) -> int
distinct   中间    Stream<T>   

4.4.2 终端操作

终端操作生成结果。

  • forEach是一个返回void的终端操作,它会对源中每道菜运用一个lambda
menu.forEach((Dish d)->System.out.println(d));

终端操作

操作       类型     目的
forEach    终端     用lambda消费流中的元素,该操作返回void
count      终端     返回流中的数据个数,返回long
collect    终端     把流规约成集合如list,map甚至是integer

5.使用流

5.1 筛选和切片

5.1.1 用谓词筛选

filter方法接收一个谓词(放回boolean的函数)作为参数,并返回所有符合谓词的元素的流

menu.
    stream().
    filter(Dish::isVegetarian).//谓词筛选
    collect(toList());

5.1.2 筛选各异的元素

distinct会根据元素的hashCode和equals方法返回一个元素各异的流
筛选出列表中所有的偶数并确保没有重复

nums.
    stream().
    filter((Integer i) -> i%2 == 0).
    distinct().
    forEach(System.out::println);// 2,4

5.1.3 截断流

limit返回一个不超过给定长度的流

5.1.4 跳过元素

skip(n),返回一个扔掉了前n个元素的流,如果流中元素不超过n,则返回一个空流,limit和skip是互补的

跳过超过300卡路里的头两道菜

menu.
    stream().
    filter(d -> d.getCalories()>300).
    skip(2).
    collect(toList());

5.2 映射

5.2.1 对流中每一个元素应用函数

map方法接收一个函数作为参数,该函数被应用到每个元素上,并将其映射成一个新的元素(注意 : 这里的映射是创建一个新的,而不是修改原来的)

//这里map方法的输出类型是Stream<String>
menu.
    stream().
    map(Dish::getName).
    collect(toList());
List<String> words = 
                Arrays.asList("java8","lambda","in","action");
List<Integer> wordLens = 
                words.stream().map(String::length).collect(toList());

打印每道菜的名字有多长

menu.
    stream().
    map(Dish::getName).
    map(String::length).
    forEach(System.out::println);

5.2.2 流的扁平化

flatMap:把流中每个值都换成另一个流,然后把所有的流连成一个流

5.3 查找和匹配

allMatch,anyMatch,noneMatch,findFirst,findAny

5.3.1 检查谓词至少陪陪一个元素

anyMatch:“流中是否有一个元素能匹配给定的谓词”

//是否有一个适合素食者
if(menu.stream().anyMatch(Dish::isVegetarian)){
    System.out.println("this nemu is vegetarian friedly");
}

思考:如果查找是否包含,不用再外部遍历去做了

5.3.2 检查谓词是否匹配所有元素

allMatch:查看流中元素是否都能匹配给定的谓词

//流中每道菜的卡路里是否小于1000
boolean isHealty = 
    menu.stream().allMatch(d -> d.getCalories()<1000);

noneMatch:确保流中没有任何元素与给定的谓词匹配

boolean isHealty = 
    menu.stream().noneMatch(d -> d.getCalories()>=1000);

5.3.2 查找任意元素

findAny将返回流中的任意元素

Optional<Dish> dish =
        menu.stream().filter(Dish::isVegetarian).findAny();

Optional是一个容器类,代表一个值存在或不存在

  • isPresent:如果Optional有值返回true,否则为false
  • T get():值存在返回值,不存在抛异常
  • orElse(T other):只存在返回,否则返回一个默认值

5.3.3 查找第一个元素

找出第一个平方能被3整除的数

Optional<Integer> firstNum = 
                        nums.stream().map(x -> x*x).
                        filter(x -> x%3 == 0).findFirst();

5.4 归约

reduce操作

5.4.1 元素求和

List<Integer> nums = Arrays.asList(1,2,3,4,5,6);
//累加
int sum = nums.stream().reduce(0, (a,b) -> a+b);
//累乘
int product = nums.stream().reduce(1, (a,b) -> a*b);
//没有初始值,返回optional,(流中没有任何元素)
Optional<Integer> sumnum = nums.stream().reduce((a,b) -> a+b);

reduce接收两个参数
一个初始值,这里是0
一个lambda(BinaryOperator<T>)
流程:首先,0作为lambda的第一个参数a,从流中获取第二个参数b,然后累加,在用累加值和流中下一个元素调用lambda,产生新的累加。

5.4.2

最大值和最小值
reduce的两个参数
一个初始值
一个lambda来把两个流元素结合起来产生一个新值

Optional<Integer> max = nums.stream().reduce(Integer::max);
Optional<Integer> max2 = nums.stream().reduce((x,y) -> x>y?x:y);
Optional<Integer> min = nums.stream().reduce(Integer::min);
Optional<Integer> min2 = nums.stream().reduce((x,y) -> x<y?x:y);

统计菜单里有多少菜

menu.stream().map(d->1).reduce(0,(a,b) -> a+b);

5.5 实例

实验数据

Trader raoul = new Trader("Raoul","Cambridge");
Trader mario = new Trader("Mario","Milan");
Trader alan = new Trader("Alan","Cambridge");
Trader brian = new Trader("Brian","Cambridge");

List<Transaction> transactions = Arrays.asList(
        new Transaction(brian, 2011, 300),
        new Transaction(raoul, 2012, 1000),
        new Transaction(raoul, 2011, 400), 
        new Transaction(mario, 2012, 710),
        new Transaction(mario, 2012, 700),
        new Transaction(alan, 2012, 950)
        );


//交易员
public class Trader {
    private final String name;
    private final String city;
    
    public Trader(String name,String city){
        this.name = name;
        this.city = city;
    }

    public String getName() {
        return name;
    }

    public String getCity() {
        return city;
    }

    @Override
    public String toString() {
        return "Trader:" + this.name + " in " + this.city;
    } 
}


//交易
public class Transaction {
    private final Trader trader;
    private final int year;
    private final int value;
    
    public Transaction(Trader trader,int year,int value){
        this.trader = trader;
        this.year = year;
        this.value = value;
    }

    public Trader getTrader() {
        return trader;
    }

    public int getYear() {
        return year;
    }

    public int getValue() {
        return value;
    }

    @Override
    public String toString() {
        return "Transaction [trader=" + trader + ", year=" + year + ", value=" + value + "]";
    }  
}

问题

1.找出2011年发生的所有交易,并按照交易额排序
2.交易员在哪些不同的城市工作过
3.查找所有来自剑桥的交易员,并按字母顺序排序
4.返回所有交易员的姓名字符串,并按字母排序
5.有没有交易员在米兰工作?
6.打印生活在剑桥的交易员的所有交易额
7.所有交易中,交易额最高的是多少
8.找出交易额最小的交易

答案

transactions.stream().
    filter((Transaction transaction) -> transaction.getYear() == 2011).
    sorted(comparing(Transaction::getValue)).
    collect(toList());
transactions.stream()
    .map((Transaction transaction) -> transaction.getTrader().getCity())
    .distinct()
    .collect(toList());
transactions.stream()
    .map((Transaction transaction) -> transaction.getTrader())
    .filter((Trader trader) -> trader.getName().equals("Cambridge"))
    .distinct()
    .sorted(Comparator.comparing(Trader::getName))
    .collect(toList());
transactions.stream()
    .map((Transaction transaction) -> transaction.getTrader().getName()) 
    .distinct()
    .sorted( )
    .reduce("",(a,b)->a+b);

transactions.stream()
    .anyMatch(transaction -> 
    transaction.getTrader().getCity().equals("Milan"));
transactions.stream().
    filter(t -> t.getTrader().getCity().equals("Cambridge")).
    forEach(System.out::println);
transactions.stream().
    map(Transaction::getValue).reduce(Integer::max);
transactions.stream().
    reduce((t1,t2)-> t1.getValue()<t2.getValue()?t1:t2);

5.6 构建流

从值序列,数组,文件来创建流

5.6.1 由值创建流

可以使用Stream.of方法显示的创建一个流
使用Stream.empty创建一个空流

Stream<String> stream = Stream.of("java8","lambda","in","action");
Stream<String> emptyStream = Stream.empty();

5.6.2 由数组创建流

Arrays.stream方法从数组Chaun创建一个流,它接收一个数组作为参数

int[] nums = {1,2,3,4,5,6};
Arrays.stream(nums);

5.6.3 由文件创建流

Files.lines方法会将文件中的各行构建成一个流

long uniqueWords = 0;
try(Stream<String> lines = 
            Files.lines(Paths.get("data.txt"))){
    uniqueWords = lines.
            flatMap(line->Arrays.stream(line.split(" "))).
            distinct().
            count();
}
catch(Exception e){}

5.6.4 由函数创建无限流

Stream.iterate和Stream.generate生成无限流,一般使用limit来限制

迭代

//从0开始生成并打印10个偶数
Stream.
    iterate(0, n -> n+2).
    limit(10).
    forEach(System.out::println);

iterate操作时顺序的,因为结果取决于上一次应用

Stream.
    iterate(new int[]{0,1}, t->new int[]{t[1],t[0]+t[1]});

生成

Stream.
    generate(Math::random).
    limit(5).forEach(System.out::println);

6.用流收集数据

6.1 收集器

Collector会对元素应用一个转换函数,并将结果累积在一个数据结构中

6.2 归约和汇总

//统计菜单里有多少菜
long howmany = menu.stream().collect(Collectors.counting());
long howmany2 = menu.stream().count();

6.2.1 查找流中的最值

Collectors.maxBy和Collectors.minBy,这两个收集器接收Comparator作为参数来比较流中的元素。

Comparator<Dish> comparator = 
                Comparator.comparing(Dish::getCalories);
Optional<Dish> mostcal = 
                menu.stream().collect(Collectors.maxBy(comparator));

6.2.2 汇总

summingInt:所有菜的总热量

int total = 
        menu.stream().
        collect(Collectors.summingInt(Dish::getCalories));

averagingInt:求平均

double total = 
        menu.stream().
        collect(Collectors.averagingInt(Dish::getCalories));

summaringzing:返回收集器,通过该操作可以数出个数,并得到总和,平均值,最值

IntSummaryStatistics statics = menu.
                            stream().
                            collect(Collectors.summarizingInt(Dish::getCalories));
//IntSummaryStatistics{count=9, sum=4200, min=120, average=466.666667, max=800}
System.out.println(statics);

6.2.3 连接字符串

joining方法返回的收集器会把对流中的每一个对象应用toString方法得到的所有字符串连成一个字符串。
joining方法内部使用了StringBuilder。

String shortMenu = menu.stream().map(Dish::getName).collect(Collectors.joining());
System.out.println(shortMenu);
//友好输出
shortMenu = menu.stream().map(Dish::getName).collect(Collectors.joining(", "));
System.out.println(shortMenu);

6.2.4 广义的归约和汇总

Collectors.reducing操作可以将上面的操作一般化

6.3 分组

Collectors.groupBy方法可以方便的分组,groupingBy内部传递了一个Function

Map<Dish.Type,List<Dish>> dishByType =
            menu.stream().collect(Collectors.groupingBy(Dish::getType));            
System.out.println(dishByType);
/*{OTHER=[Fresh Fries, Rice, Season Fruit, Pissa], 
   FISH=[prawns, Salmon], MEAT=[Pork, Beef, Chicken]}*/ 
Map<CaloriLevel,List<Dish>> byCal = menu.stream()
                        .collect(Collectors.groupingBy(dish->{
                                if(dish.getCalories()<= 400) return CaloriLevel.DIET;
                                else if(dish.getCalories()<=700) return CaloriLevel.NORMAL;
                                else return CaloriLevel.FAT; 
                        }));

6.3.1 多级分组

Map<Type, Map<CaloriLevel, List<Dish>>> byCal = menu.stream()
                        .collect(Collectors.groupingBy(Dish::getType,
                                Collectors.groupingBy(dish->{
                                    if(dish.getCalories()<= 400) return CaloriLevel.DIET;
                                    else if(dish.getCalories()<=700) return CaloriLevel.NORMAL;
                                    else return CaloriLevel.FAT; 
                            })));
System.out.println(byCal);
/*{FISH={NORMAL=[Salmon], DIET=[prawns]}, 
 * MEAT={NORMAL=[Beef], DIET=[Chicken], FAT=[Pork]}, 
 * OTHER={NORMAL=[Fresh Fries, Pissa], DIET=[Rice, Season Fruit]}}
 * */

6.3.2 按子组收集数据

Map<Dish.Type,Long> typesCount = 
                        menu.stream().
                        collect(
                                Collectors.groupingBy(Dish::getType,Collectors.counting())
                                );
System.out.println(typesCount);
//{FISH=2, MEAT=3, OTHER=4}

Map<Dish.Type,Optional<Dish>> mostByType = 
                        menu.stream().
                        collect(
                                Collectors.groupingBy(Dish::getType,
                                Collectors.maxBy(Comparator.comparing(Dish::getCalories)))
                                ); 

6.4 分区

true一组,false一组,以一个谓词作为分类函数
比如,素食主义者的朋友要来,于是需要把菜单上的才分开

Map<Boolean,List<Dish>> partitionMenu = 
                    menu.stream().
                    collect(Collectors.partitioningBy(Dish::isVegetarian));
System.out.println(partitionMenu);
/*
 * {false=[Pork, Beef, Chicken, prawns, Salmon], 
 *  true=[Fresh Fries, Rice, Season Fruit, Pissa]}
 * */
List<Dish> veges = partitionMenu.get(true);
System.out.println(veges);
//[Fresh Fries, Rice, Season Fruit, Pissa]

上面的代码效果和下面的效果一样

List<Dish> vegets = menu.stream()
                    .filter(Dish::isVegetarian)
                    .collect(toList());

6.4.1 分区的优势(重载函数)

Map<Boolean,Map<Type,List<Dish>>> result = 
                menu.stream().
                collect(
                        Collectors.partitioningBy(Dish::isVegetarian,
                        Collectors.groupingBy(Dish::getType)));

//先按是否素分区再按照是否大于500卡分区

menu.stream().collect(Collectors.partitioningBy(Dish::isVegetarian,
        Collectors.partitioningBy((Dish d)->d.getCalories()>500)));

//计算每个分区项目的数目

menu.stream().collect(Collectors.partitioningBy(Dish::isVegetarian,
        Collectors.counting()));
//{false=5,true=4}

6.5 收集器接口

//T流中要收集的项目的类型
//A是累加器的类型
//R是收集操作得到的对象
public interface Collector<T,A,R> {
    Supplier<A> supplier();
    BiConsumer<A,T> accumulator();
    Function<A,R> finisher();
    BinaryOperator<A> combiner();
    Set<Characteristics> characteristics();
}

前四个方法都会返回一个被collect方法调用的函数,而第五个方法characteristics则提供了一些列特征,也就是一个提示列表,告诉collect方法在执行归约操作的时候可以应用哪些优化

7.并行处理数据

7.1 并行流

可以通过parrallelStrem方法把集合转化成并行流,并行流就是一个把内容分成多个数据块并用不同的线程分别处理每个数据块的流,这样一来就可以把给定的操作的负荷分配给多核处理器的所有内核,让他们都运行起来

7.1.1 顺序流转并行流

顺序流

    public static long sequentialSum(long n){
        return Stream.iterate(1L, i -> i+1)//生成自然数无限流
                .limit(n)//限制
                .reduce(0L, Long::sum);//求和
    }

并行流

    public static long sequentialSum(long n){
        return Stream.iterate(1L, i -> i+1)//生成自然数无限流
                .limit(n)//限制
                .parallel()//将流转成并行流
                .reduce(0L, Long::sum);//求和
    }

parallel会导致归约操作并行执行,并且结果汇总,并行流内部使用了ForkJoinPool,它默认的线程数量就是处理器数量

7.2 分支/合并框架

分支合并框架的目的是以递归的方式将可以并行执行的任务拆解成更小的任务,然后将每个子任务的结果合并起来生成整体的结果

7.2.1 使用RecursiveTask

要把任务提交给线程池,必须创建RecursiveTask<R>的一个子类,其中R是并行任务产生的结果的类型,或者如果任务不返回结果,则是RecursiveAction类型,要使用RecursiveTask,只需要实现它的一个抽象方法compute,

protected abstract R compute();

这个方法同时两个逻辑1.将任务拆分成子任务 2.无法再拆分时,执行任务并生成任务结果的逻辑

    if(任务足够小或者不可分){
        顺序计算该任务
    }else{
        将任务分成两个子任务
        递归调用该方法,拆分每个子任务,等待所有子任务完成。
        合并每个任务的结果。
    }
public class ForkJoinSumCalculator<Long> extends RecursiveTask<Long> {

    private final long[] numbers;//要求和的数组
    //子任务处理的数组的起始和结束位置
    private final int start;
    private final int end;
    
    public static final long THRESHOLD = 10 * 1000;
    
    public ForkJoinSumCalculator(long[] nums){
        this(nums, 0, nums.length);
    }
    
    private ForkJoinSumCalculator(long[] nums,int start,int end){
        this.numbers = nums;
        this.start = start;
        this.end = end;
    }
    
    
    
    @Override
    protected Long compute() {
        int length = end - start;
        if(length <= THRESHOLD){
            return computeSequentially();
        }
        ForkJoinSumCalculator<Long> leftTask = 
                new ForkJoinSumCalculator<>(numbers, start, start + length/2);
        ForkJoinSumCalculator<Long> rightTask = 
                new ForkJoinSumCalculator<>(numbers, start+length/2, end);
        Long rightResult = rightTask.compute();
        Long leftResult = leftTask.join();
        return leftResult + rightResult;
        return null;
    }
    
    private Long computeSequentially(){
        long sum = 0;
        for(int i=start;i<end;i++){
            sum += numbers[i];
        }
        return sum;
    }  

}

7.2.2 工作窃取

理想情况下,划分并行任务的时候如果每个任务都会在相同的时间内完成,那么所有的cpu就同样繁忙,不幸的是,每个任务的执行时间差别很大,可能会导致一个线程还很忙另一个线程就已经空闲了
工作窃取是说,每个线程内部会维护一个双向的链式队列,线程每次从队头取出一个任务执行,如果现在有了空闲线程,那空闲线程会从繁忙线程的队列尾部取出一个任务执行,这个过程一直持续,知道所有任务执行完毕,所有队列都清空。

7.3 Spliterator

Spliterator也用于遍历,但他是为了并行执行而设计的。

tryAdvance:类似于普通的遍历,会按顺序一个个来
strySplit:是一个递归过程,第一步是对一个Spliterator调用trySplit,生成第二个Spliterator,第二步是对两个Spliterator,这样总共就会产生四个,框架不断的对Spliterator调用trySplit,,直到它返回null,表名它处理的数据不能在分割。

8. 重构测试和调试

8.1 从匿名类到lambda表达式的转换时需要注意的情况

第一,匿名类中,this代表的是自身,但在lambda中,它代表的是包含类
第二,在涉及重载的上下文中,lambda表达式可能导致问题

//定义一个接口Task,
public interface Task {
    public void execute();
}

public class Client { 
    public static void doSth(Task task){
        task.execute();
    }
    
    public static void doSth(Runnable r){
        r.run();
    } 
    public static void main(String[] args) {
        // The method doSth(Task) is ambiguous for the type Client
        doSth(()->System.out.println(""));
        //显示的类型转换来指定类型
        doSth((Task)()->System.out.println(""));
        doSth((Runnable)()->System.out.println(""));
        
    } 
}

8.2 用lambda重构设计模式

8.2.1 策略模式:在运行时选择使用哪种方案

  • 一个代表某个算法的接口(策略)
  • 一个或者多个接口的实现
  • 使用策略对象的客户
public interface ValidationStrategy {
    //验证文本的接口
    boolean execute(String s);
}

public class IsAllLower implements ValidationStrategy { 
    @Override
    public boolean execute(String s) { 
        return s.matches("[a-z]+");
    } 
}

public class IsNumberic implements ValidationStrategy { 
    @Override
    public boolean execute(String s) { 
        return s.matches("\\d+");
    } 
}

public class Validator {
    private final ValidationStrategy strategy;
    public Validator(ValidationStrategy strategy){
        this.strategy = strategy;
    }
    public boolean validate(String s){
        return strategy.execute(s);
    }
}

public class Client {

    public static void main(String[] args) {
        //
        Validator numVali = new Validator(new IsNumberic());
        boolean b1 = numVali.validate("aaaa");//false;
        Validator lowerVa = new Validator(new IsAllLower());
        boolean b2 = lowerVa.validate("abcdef");
        //使用lambda表达式:注意对比
        Validator num2 = new Validator((String s)->s.matches("\\d+"));
        Validator low2 = new Validator((String s)->s.matches("[a-z]+"));
    } 
}


8.2.2 模板方法

public abstract class OnlineBanking {
    public void processCustomer(int id){
        Customer c = Database.getCustomerWithId(id);
        makeCustomerHappy(c);
    }
    
    abstract void makeCustomerHappy(Customer c);
}

使用lambda

public abstract class OnlineBankingLambda {
    public void processCustomer(int id,
            Consumer<Customer> makeCustomerHappy){
        Customer c = Database.getCustomerWithId(id);
        makeCustomerHappy.accept(c);
    }  
}


new OnlineBankingLambda().
processCustomer(1223,
        (Customer customer)->System.out.println("make happy"));

9.默认方法

java8的接口支持在声明方法的同时提供实现
通过两种方式可以完成这个操作:第一,java8允许在接口内声明静态方法,第二,java8引入了一个新功能叫默认方法,通过默认方法,可以指定接口方法的默认实现,也就是说接口能提供方法的具体实现。实现接口的类如果不显示的提供该方法的具体实现,就会自动继承默认的实现

向接口添加方法是诸多问题的源头,一旦接口发生变化,实现这些接口的类往往也需要更新,提供新方法的实现才能适配接口的变化,如果对接口以及所有实现类能够完全控制的话当然没有问题,但是实际情况是,很难做到完全控制 ,这就是引入默认方法的目的,它让类可以自动的继承接口的默认实现。此外,默认方法为方法的多继承提供了一种更灵活的机制,类可以从多个接口继承默认方法。

9.1 概述默认方法

public interface Sized {
    int size();
    //默认方法,
    //任何一个实现Sized接口的类都会自动继承isEmpty的实现
    default boolean isEmpty(){
        return size() == 0;
    }
}

9.2 默认方法的使用模式

9.2.1 可选方法

Iterator接口定义了next,hasNext还定义了remove方法,java8之前,由于不会使用remove方法,通常在实现Iterator的时候会把这个方法留白,采用默认方法之后,可以为这种类型的方法提供一个默认实现,这样实体类就无需在自己的实现中显示的提供一个空方法,比如java8中Iterator就为remove方法提供了一个默认实现。通过这种方法,实现Iterator的每一个类就不需要再声明一个空的remove方法了。

public interface Iterator<T> {
    boolean hasNext();
    T next();
    default void remove(){
        //默认实现
    }
}

9.2.2 行为的多继承

就是实现多个有默认方法的接口

9.3 解决冲突的规则

java8的类可以实现多个接口,有可能一个类继承了多个方法而且他们使用的是同样的签名,这种情况下类会选择哪一个函数呢?
如:

public interface A {
    default void hello(){
        System.out.println("from A");
    }
}

public interface B extends A {
    default void hello(){
        System.out.println("from B");
    }
}

public class C implements A, B {
    public static void main(String[] args){
        new C().hello();
    }
}

9.3.1 解决问题的三条规则

  • 类中的方法优先级最高:类或者父类中声明的方法的优先级高于任何声明为默认方法的优先级
  • 如果第一条无法判断,那么子接口的优先级更高
  • 如果还是无法判断,继承了多个接口的类必须显示的选择使用哪一个默认实现

10.Optional

10.1 为缺失的值建模

public class Insurance {
    private String name;
    public String getName(){
        return name;
    }
}

public class Car {
    private Insurance insurance;
    public Insurance getInsurance(){
        return insurance;
    }
}

public class Person {
    private Car car;
    public Car getCar(){
        return car;
    }
}

public class Client {
    public String getCarInsuranceName(Person person){
        return person.getCar().getInsurance().getName();
    }
}

上面的方法很容易出现NullPointerException

10.1.1 采用防御式检查

public class Client {
    public String getCarInsuranceName(Person person){
        if(person!=null){
            Car car = person.getCar();
            if(car!=null){
                Insurance insurance = car.getInsurance();
                if(insurance!=null){
                    return insurance.getName();
                }
            }
        }
        return "UnKnown";
    }
}

上面的方法代码的结构性很差

public class Client {
    public String getCarInsuranceName(Person person){
        if(person == null) return "unknown";
        Car car = person.getCar();
        if(car == null) return "unknown";
        Insurance insurance = car.getInsurance();
        if(insurance == null) return "unknown";
        return insurance.getName();
    }
}

上面的代码返回点很多,不易维护,另外,很有可能忘记某个属性的检查

10.2 Optional

变量存在时,Optional类是对类的简单封装,变量不存在时,缺失的值会变成一个空的Optional对象,由方法Optional.empty()返回,Optional.empty()方法是一个静态工厂方法,它返回Optional类的特定单一实例
Optional.empty和null的区别,引用null一定会引发异常,使用Optional.empty就没有问题,它是Optional类的一个有效对象

public class Person {
    //人可能有车,也可能没车
    private Optional<Car> car;
    public Optional<Car> getCar(){
        return car;
    }
}

public class Car {
    //车可能有保险,也可能没保险
    private Optional<Insurance> insurance;
    public Optional<Insurance> getInsurance(){
        return insurance;
    }
}

public class Insurance {
    //保险公司必须有名字
    private String name;
    public String getName(){
        return name;
    }
}

10.3应用Optional的几种模式

10.3.1 创建Optional对象

  • 使用Optional.empty创建一个空的Optional对象
  • 使用Optional.of,根据一个非空值创建一个Optional对象(如果那个非空值为空,则直接抛出异常,而不是等待访问时才抛出异常)
  • 可接受null的Optional,使用ofNullable方法可以创建一个允许null值的Optional对象
//如果car是null,得到的Optional对象就是个空对象
Optional<Car> optCar = Optional.ofNullable(car);

10.3.2 使用map从Optional对象中提取和转换值

Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
Optional<String> name = optInsurance.map(Insurance::getName);

10.3.3 使用flatMap链接Optional对象

        Optional<Person> optPerson = Optional.ofNullable(person);
        Optional<String> name = 
                optPerson.map(Person::getCar).
                map(Car::getInsurance).
                map(Insurance::getName()); 

上面这段编译不过,getCar会返回Optional<Car>,这样map就会返回一个Optional<Optional<Car>>

        Optional<Person> optPerson = Optional.ofNullable(person);
        Optional<String> name = 
                optPerson.flatMap(Person::getCar).
                flatMap(Car::getInsurance).
                map(Insurance::getName); 

10.3.3

  • get方法最简单但最不安全,如果变量存在返回该变量否则抛出NoSuchElementException
  • orElse(T other)允许在Optional对象不包含值时提供一个默认值
  • orElseGet(Supplier<? exteds T> other),上一个方法的延迟版,Supplier方法只在Optional对象不包含值的时候才执行调用,如果创建是个耗时操作可以考虑使用这个
  • ifPresent(Consumser<? extends T>),让你能在变量值存在时执行一个作为参数传入的方法,否则就不进行任何操作

10.3.4 两个Optional对象的操作

public Optional<Insurance> nullSafeFind
        (Optional<Car> car,Optional<Person> person){
    if(person.isPresent() && car.isPresent()){
        return Optional.of(insurance);
    }else{
        return Optional.empty();
    }
}

10.3.5 使用filter剔除特定的值

    insurance.filter(x->"abc".equals(x.getName()))
    .ifPresent(x->System.out.println("ok"));

10.4 Optional的使用

Optional<Object> value = 
        Optional.ofNullable(map.get("key"));

11.

Future的使用

public class Client {  
    public static void  main(String[] args){
        ExecutorService executor = Executors.newCachedThreadPool();
        Future<Double> future = executor.submit(new Callable<Double>(){ 
            @Override
            public Double call() throws Exception { 
                return doLongComputation();
            } 
        });
        doSthElse();  
        System.out.println("Before===================");
        try {
            Double result = future.get();
            System.out.println(result);
        } catch ( Exception e) { 
            e.printStackTrace();
        }
        System.out.println("End===================");
    }
    
    private static void doSthElse() { 
            System.out.println("sth else"); 
    }

    private static Double doLongComputation() {
        try {
            Thread.sleep(5000);
            System.out.println("compute>>>>");
        } catch (InterruptedException e) { 
            e.printStackTrace();
        }
        return 3.14;
    }
}

sth else
Before===================
compute>>>>
3.14
End===================

    原文作者:xihe
    原文地址: https://www.jianshu.com/p/627e43e41def
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞