Java程序员

JDK8特性与使用手记

2019-03-26  本文已影响0人  androidjp

新特性总览

Lambda表达式

Stream

打平之后,直接操作一个数组就好。

Optional

函数式编程(Stream+Lambda)

函数式编程 VS 命令式编程

  • 命令式编程关注怎么做,而函数式编程关注做什么
  • 函数式编程 可读性强,但运行速度不见得更快。

例子

1. for循环取数组最小值

int[] nums = {1,3,-1,6,-20};
int min = Integer.MAX_VALUE;
for (int i:nums) {
    if(i < min) {
        min = i;
    }
}

变成

int min2 = IntStream.of(nums).parallel().min().getAsInt();

2. 接口的实现/匿名内部类转Lambda

/// 接口实现
Object target = new Runnable() {
    @Override
    public void run() {
        System.out.println("新建一个线程");
    }
};
new Thread((Runnable) target).start();

/// 匿名内部类
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("BBB");
    }
}).start();

变成

Object target2 = (Runnable)() -> System.out.println("新建一个线程2");
Runnable target3 = () -> System.out.println("新建一个线程3");
System.out.println("target2 == target3 :" + (target2 == target3)); // false
new Thread((Runnable) target2).start();

new Thread(() -> System.out.println("BBB")).start()

3. Lambda创建自定义接口的实例对象

必备条件:

  1. 该接口中只能有一个抽象方法
  2. 在接口上加上@FunctionalInterface注解(可选:为了编译器的校验,有这个注解的接口,当存在多个抽象方法时,是会编译报错的。)

JDK8 中,接口中可以定义静态方法和默认方法。

@FunctionalInterface
interface Interface1 {
    int doubleNum(int i);

    default int add(int x, int y) {
        return x + y;
    }

    static int sub(int x, int y) {
        return x - y;
    }
}

@FunctionalInterface
interface Interface2 {
    int doubleNum(int i);

    default int add(int x, int y) {
        return x + y;
    }
}

@FunctionalInterface
interface Interface3 extends Interface1, Interface2 {
    @Override
    default int add(int x, int y) {
        return Interface1.super.add(x, y);
    }
}


@Test
public void test_lambda_1() {
    Interface1 i1 = (i) -> i * 2;
    System.out.println("Interface1.sub(10, 3): " + Interface1.sub(10, 3));
    System.out.println("i1.add(3,7):" + i1.add(3, 7));
    System.out.println("i1.doubleNum(20):" + i1.doubleNum(20));

    Interface2 i2 = i -> i * 2;
    Interface3 i3 = (int i) -> i * 2;
    Interface3 i4 = (int i) -> {
        System.out.println(".....");
        return i * 2;
    };
}

4. Lambda与Function

String cityName= "HongKong";
int stateCode=237;
String street = "东岸村黄皮树下街1号";

String locationID = "";

Function<String, String> locationIDBuilder = locId -> locId + cityName; // Step 1
locationID = locationIDBuilder
        .andThen(locId -> locId + ",区号:" + stateCode) // Step 2
        .andThen(locId -> locId+",街道:" + street).apply(locationID); // Step 3
System.out.println("locationID:" + locationID);

JDK 1.8 API包含了很多内建的函数式接口,在老Java中常用到的比如Comparator或者Runnable接口,这些接口都增加了@FunctionalInterface注解以便能用在lambda上

name type description
Consumer Consumer< T > 接收T对象,不返回值
Predicate Predicate< T > 接收T对象并返回boolean
Function Function< T, R > 接收T对象,返回R对象
Supplier Supplier< T > 提供T对象(例如工厂),不接收值
UnaryOperator UnaryOperator 接收T对象,返回T对象
BinaryOperator BinaryOperator 接收两个T对象,返回T对象

Lambda 与 设计模式

策略模式

interface ValidationStrategy {
    boolean execute(String s);
}

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

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

static class Validator {
    private final ValidationStrategy validationStrategy;

    public Validator(ValidationStrategy validationStrategy) {
        this.validationStrategy = validationStrategy;
    }

    public boolean validate(String s) {
        return validationStrategy.execute(s);
    }
}

正常来说,要new一些策略来当参数传。

IsNumeric isNumeric = new IsNumeric();
IsAllLowerCase isAllLowerCase = new IsAllLowerCase();

Validator validatorA = new Validator(isNumeric);
Validator validatorB = new Validator(isAllLowerCase);

使用lambda,让new尽量少出现在code中。

Validator validatorA = new Validator(s -> s.matches("\\d+"));
Validator validatorB = new Validator(s -> s.matches("[a-z]+"));

模板模式(抽象类的应用)

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

    abstract void makeCustomerHappy(Customer customer);

    static class Customer {}

    static class Database {
        static Customer getCustomerWithId(int id) {
            return new Customer();
        }
    }
}
...
AbstractOnlineBank bank = new AbstractOnlineBank() {
    @Override
    void makeCustomerHappy(Customer customer) {
        System.out.println("Hello!");
    }
};
bank.processCustomer(1);
bank.processCustomer(2);

用了Lambda,抽象方法都用不着了

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

    static class Customer {}

    static class Database {
        static Customer getCustomerWithId(int id) {
            return new Customer();
        }
    }
}
...
AbstractOnlineBank bank = new AbstractOnlineBank();
bank.processCustomer(1, customer -> System.out.println("Hello"));
bank.processCustomer(2, customer -> System.out.println("Hi"));

观察者模式

interface Observer{
        void inform(String tweet);
    }

private static class NYTimes implements Observer {

    @Override
    public void inform(String tweet) {
        if (tweet != null && tweet.contains("money")) {
            System.out.println("Breaking news in NY!" + tweet);
        }
    }
}

private static class Guardian implements Observer {

    @Override
    public void inform(String tweet) {
        if (tweet != null && tweet.contains("queen")) {
            System.out.println("Yet another news in London... " + tweet);
        }
    }
}

private static class LeMonde implements Observer {

    @Override
    public void inform(String tweet) {
        if(tweet != null && tweet.contains("wine")){
            System.out.println("Today cheese, wine and news! " + tweet);
        }
    }
}

interface Subject {
    void registerObserver(Observer o);

    void notifyObserver(String tweet);
}

private static class Feed implements Subject {
    private final List<Observer> observers = new ArrayList<>();

    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void notifyObserver(String tweet) {
        observers.forEach(o -> o.inform(tweet));
    }
}

好的,看到了有一个观察者接口,并且只有一个方法inform,那么,我们是不是就可以少声明这几个实现类呢?

Feed feedLambda = new Feed();
feedLambda.registerObserver((String tweet) -> {
    if (tweet != null && tweet.contains("money")) {
        System.out.println("Breaking news in NY!" + tweet);
    }
});

feedLambda.registerObserver((String tweet) -> {
    if (tweet != null && tweet.contains("queen")) {
        System.out.println("Yet another news in London... " + tweet);
    }
});

feedLambda.notifyObserver("Money money money, give me money!");

责任链模式

private static abstract class AbstractProcessingObject<T> {
    protected AbstractProcessingObject<T> successor;

    public void setSuccessor(AbstractProcessingObject<T> successor) {
        this.successor = successor;
    }

    public T handle(T input) {
        T r = handleWork(input);
        if (successor != null) {
            return successor.handle(r);
        }
        return r;
    }

    protected abstract T handleWork(T input);
}

一看到,又是抽象类加不同的实现,那就想到是不是可以用匿名函数实现,而这里,我们使用UnaryOperator

// 流程A
UnaryOperator<String> headerProcessing = (String text) -> "From Raoul, Mario and Alan: " + text;
// 流程B
UnaryOperator<String> spellCheckerProcessing = (String text) -> text.replaceAll("labda", "lambda");
// A --> B
Function<String, String> pipeline = headerProcessing.andThen(spellCheckerProcessing);
String result2 = pipeline.apply("Aren't labdas really sexy?!!");

工厂模式

private interface Product {
}

private static class ProductFactory {
    public static Product createProduct(String name) {
        switch (name) {
            case "loan":
                return new Loan();
            case "stock":
                return new Stock();
            case "bond":
                return new Bond();
            default:
                throw new RuntimeException("No such product " + name);
        }
    }
}

static private class Loan implements Product {
}

static private class Stock implements Product {
}

static private class Bond implements Product {
}

简单情况下,可以用Supplier去实现引用方法式的构造器调用,并且减少switch。

private static class ProductFactory {
    private static final Map<String, Supplier<Product>> map = new HashMap<>();

    static {
        map.put("loan", Loan::new);
        map.put("stock", Stock::new);
        map.put("bond", Bond::new);
    }

    public static Product createProduct(String name) {
        Supplier<Product> productSupplier = map.get(name);
        if (productSupplier != null) {
            return productSupplier.get();
        }

        throw new RuntimeException("No such product " + name);
    }
}

当然,如果工厂方法 createProduct 需要接收多个传递给产品构造方法的参数,这种方式的扩展性不是很好。

默认方法

Jdk8开始支持的东西

Jdk8中的接口支持在声明方法的同时提供实现。

作用

解决继承链上的冲突的规则

public interface A {
    default void hello() {
        System.out.println("Hello from A");
    }
}
public interface B extends A {
    default void hello() {
        System.out.println("Hello from B");
    }
}
public class C implements A, B {
    public static void main(String[] args) {
        // 猜猜打印的是什么?
        new C().hello();
    }
}

以上问题,就是菱形继承问题,如果父子接口有同名的default方法,那么,以上代码编译不通过。
我们需要重写这个方法:

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

    @Override
    public void hello() {
       A.super.hello();
    }

    OR

    @Override
    public void hello() {
       B.super.hello();
    }

    OR

    @Override
    public void hello() {
       System.out.println("Hello from C!");
    }
}

组合式异步编程

RecursiveTask(JDK 1.7)

例子:实现一个100000个自然数的求和。

class SumTask extends RecursiveTask<Long> {
public static final int Flag = 50;
long[] arr;
int start;
int end;

public SumTask(long[] arr, int start, int end) {
    this.arr = arr;
    this.start = start;
    this.end = end;
}

public SumTask(long[] arr) {
    this.arr = arr;
    this.start = 0;
    this.end = arr.length;
}

@Override
protected Long compute() {
    // 如果不能进行更小粒度的任务分配
    int length = end - start;
    if (length <= Flag) {
        return processSequentially();
    }
    //分治
    int middle = (start + end) / 2;
    SumTask sumTaskOne = new SumTask(arr, start, middle);
    SumTask sumTaskTwo = new SumTask(arr, middle, end);
    invokeAll(sumTaskOne, sumTaskTwo);
    Long join1 = sumTaskOne.join();
    Long join2 = sumTaskTwo.join();
    return join1 + join2;
}

// 小任务具体是做什么
private long processSequentially() {
    long sum = 0;
    for (int i = start; i < end; i++) {
        sum += arr[i];
    }
    return sum;
}
}

@Test
public void test() {
  long[] arr = new long[1000];
  for (int i = 0; i < arr.length; i++) {
      arr[i] = (long) (Math.random() * 10 + 1);
  }
  // 线程池(since 1.7)
  ForkJoinPool forkJoinPool = new ForkJoinPool(5);
  ForkJoinTask<Long> forkJoinTask = new SumTask(arr);
  long result = forkJoinPool.invoke(forkJoinTask);
  System.out.println(result);
}

Spliterator(可分迭代器 JDK 1.8)

(一)背景

(二)例子

例子:一个自定义的并行迭代器,用于处理单词数量统计

// 首先,我们有一个英文句子,我们要统计它的单词数量
public static final String SENTENCE =
        " Nel   mezzo del cammin  di nostra  vita " +
                "mi  ritrovai in una  selva oscura" +
                " che la  dritta via era   smarrita ";

CompletableFuture(Jdk 1.8)

(一)一个商店商品报价的例子

普通的方法去写一个同步的计算报价的方法:

private static class Shop {
    private final String name;
    private final Random random;

    public Shop(String name) {
        this.name = name;
        random = new Random(name.charAt(0) * name.charAt(1) * name.charAt(2));
    }

    public double getPrice(String product) {
        return calculatePrice(product);
    }
    
    private double calculatePrice(String product) {
        delay(1000);
        return random.nextDouble()*product.charAt(0) + product.charAt(1);
    }
}
......
@Test
public void test_1() {
    Shop nike = new Shop("nike");
    Shop adidas = new Shop("adidas");
    System.out.println("nike Kobe1 price : "+ nike.getPrice("Kobe1"));
    System.out.println("nike Rose3 price : "+ adidas.getPrice("Rose3"));
}

我们先用CompletableFuture来让计算变成异步。

(二)Future的局限性

看到上面,我们发现,其实,和用Future去handle结果返回,好像差不多。

@Test
public void test_1() throws InterruptedException, ExecutionException {
    ExecutorService executor = Executors.newCachedThreadPool();
    Future<Double> future = executor.submit(this::doSthAsync);
    System.out.println("好,任务起来了,我去干别的先了");
    for (int i = 0; i < 2; i++) {
        System.out.println("主线程正在干活。。");
        Thread.sleep(500L);
    }
    try {
        System.out.println("异步任务返回了: " + future.get(2, TimeUnit.SECONDS));
    } catch (TimeoutException e) {
        System.out.println("异步任务出了异常!!!");
        e.printStackTrace();
    }
}

这里,就要说一下Future局限性了。

  1. 我们很难表述Future结果之间的依赖性。比如这样一个案例:“当长时间计算任务完成时,请将该计算的结果通知到另一个长时间运行的计算任务,这两个计算任务都完成后,将计算的结果与另一个查询操作结果合并”。
  2. 以下场景,Future都难以表述:
    • 将两个异步计算合并为一个——这两个异步计算之间相互独立,同时第二个又依赖于第一个的结果。
    • 等待Future集合中的所有任务都完成。
    • 仅等待Future集合中最快结束的任务完成(有可能因为它们试图通过不同的方式计算同一个值),并返回它的结果。
    • 通过编程方式完成一个Future任务的执行(即以手工设定异步操作结果的方式)。
    • 应对Future的完成事件(即当Future的完成事件发生时会收到通知,并能使用Future计算的结果进行下一步的操作,不只是简单地阻塞等待操作的结果)。

(三)CompetableFuture与Future间的关系

CompetableFuture之于Future,相当于Stream之于Collection

(四)CompetableFuture的一些用法

并发,用并行流还是CompletableFuture?

情况 推荐 原因
如果你进行的是计算密集型的操作,并且没有I/O Stream 实现简单
如果你并行的工作单元还涉及等待I/O的操作(包括网络连接等待) CompletableFuture 高灵活性

新的日期和时间API

背景

LocalDate和LocalTime

相关类:

三个类都实现了各种基本计算方法、parse方法、比较方法, 以及各种静态方法。

LocalDate localDate = LocalDate.of(2018, 11, 25);
int year = localDate.getYear();// 2018
Month month = localDate.getMonth(); // 11
int day = localDate.getDayOfMonth(); // 25
DayOfWeek dow = localDate.getDayOfWeek(); // SUNDAY
int len = localDate.lengthOfMonth(); // 本月总天数: 30
boolean leap = localDate.isLeapYear(); // 是不是闰年: false

LocalDate localDate = LocalDate.now();
int year = localDate.get(ChronoField.YEAR);
int month = localDate.get(ChronoField.MONTH_OF_YEAR);
int day = localDate.get(ChronoField.DAY_OF_MONTH);

LocalTime localTime = LocalTime.now();
int hour = localTime.get(ChronoField.HOUR_OF_DAY);
int minute = localTime.get(ChronoField.MINUTE_OF_HOUR);
int second = localTime.get(ChronoField.SECOND_OF_MINUTE);

LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime); // 2018-11-25T22:10:08.721
System.out.println(localDateTime.atZone(ZoneId.of("GMT"))); // 2018-11-25T22:11:08.778Z[GMT]
System.out.println(localDateTime.atOffset(ZoneOffset.UTC)); // 2018-11-25T22:11:44.362Z

机器的日期和时间格式

从计算机的角度来看,建模时间最自然的格式是表示一个持续时间段上某个点的单一大整型数。

Duration/Period

Duration

Period

修改、构造时间

简单来说,API给我们划分了读取和修改两类方法:

下面这些方法都会生成一个新的时间对象,不会修改源对象:

// 2018-11-17
LocalDate date1 = LocalDate.of(2018, 11, 17);
// 2019-11-17
LocalDate date2 = date1.withYear(2019);
// 2019-11-25
LocalDate date3 = date2.withDayOfMonth(25);
// 2019-09-25
LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 9);

特别:用TemporalAdjuster实现复杂操作

利用重写各种withXX方法,并自定义TemporalAdjuster参数,就能实现复杂的时间操作:

// 2018-11-17
LocalDate date1 = LocalDate.of(2018, 11, 17);
// 2018-11-19
LocalDate date2 = date1.with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY));
// 2018-11-30
LocalDate date3 = date2.with(TemporalAdjusters.lastDayOfMonth());

我们看看这个用TemporalAdjuster接口,其实要自定义很简单,因为它只有一个接口:

@FunctionalInterface
public interface TemporalAdjuster {
    Temporal adjustInto(Temporal temporal);
}

Format

// Date 转 String
LocalDate date1 = LocalDate.of(2018, 11, 17);
String s1 = date1.format(DateTimeFormatter.BASIC_ISO_DATE); // 20181117
String s2 = date1.format(DateTimeFormatter.ISO_LOCAL_DATE); // 2018-11-17
// String 转 Date
LocalDate date2 = LocalDate.parse("20181117", DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date3 = LocalDate.parse("2018-11-17", DateTimeFormatter.ISO_LOCAL_DATE);

DateTimeFormatter italianFormatter = DateTimeFormatter.ofPattern("d. MMMM yyyy", Locale.ITALIAN);
LocalDate date5 = LocalDate.of(2018, 11, 16);
// 16. novembre 2018
String formattedDate2 = date5.format(italianFormatter);
// 2018-11-16
LocalDate date6 = LocalDate.parse(formattedDate2, italianFormatter);

DateTimeFormatterBuilder实现细粒度格式化控制:

DateTimeFormatter italianFormatter = new DateTimeFormatterBuilder()
                .appendText(ChronoField.DAY_OF_MONTH)
                .appendLiteral(". ")
                .appendText(ChronoField.MONTH_OF_YEAR)
                .appendLiteral(" ")
                .appendText(ChronoField.YEAR)
                .parseCaseInsensitive()
                .toFormatter(Locale.ITALIAN);

LocalDate now = LocalDate.now();
// 17. novembre 2018
String s1 = now.format(italianFormatter);

处理不同的时区和历法

 // 地区ID都为“{区域}/{城市}”的格式
ZoneId shanghaiZone = ZoneId.of("Asia/Shanghai");

LocalDate date = LocalDate.of(2018, 11, 17);
ZonedDateTime zdt1 = date.atStartOfDay(shanghaiZone);

LocalDateTime dateTime = LocalDateTime.of(2018, 11, 27, 18, 13, 15);
ZonedDateTime zdt2 = dateTime.atZone(shanghaiZone);

Instant instant = Instant.now();
ZonedDateTime zdt3 = instant.atZone(shanghaiZone);


// LocalDateTime 转 Instant
LocalDateTime dateTime2 = LocalDateTime.of(2018, 11, 17, 18, 45);
ZoneOffset newYorkOffset = ZoneOffset.of("-05:00");
Instant instantFromDateTime = dateTime2.toInstant(newYorkOffset);

// 通过反向的方式得到LocalDateTime对象
Instant instant2 = Instant.now();
LocalDateTime timeFromInstant = LocalDateTime.ofInstant(instant2, shanghaiZone);

// OffsetDateTime,它使用ISO-8601的历法系统,以相对于UTC/格林尼治时间的偏差方式表示日期时间。
LocalDateTime dateTime3 = LocalDateTime.of(2018, 11, 17, 18, 45);
OffsetDateTime offsetDateTime = OffsetDateTime.of(dateTime3, newYorkOffset);
上一篇下一篇

猜你喜欢

热点阅读