Java8新特性

前言:

北京时间2018年9月26日,Oracle官方发布Java 11。既然版本都更新到11了,现在才来学8是不是太晚了?其实不是的,目前应该大部分都还是使用的Java 7和Java 8,这两个应该还是主流。而Java 8 又有一些激动人心的新特性,所以还是值得学习的。Java 8 新特性主要有以下几点:

  • Lambda表达式(重点);
  • 函数式接口;
  • 方法引用与构造器引用;
  • Stream API(重点);
  • 接口中的默认方法与静态方法;
  • 新时间日期API;
  • 其他新特性。

有了以上新特性,Java 8就可以做到:

  • 速度更快;
  • 代码更少(增加了新的语法 Lambda 表达式);
  • 方便操作集合(Stream API)
  • 便于并行;
  • 最大化减少空指针异常 Optional。

接下来一起来了解一下Java 8的这些新特性。


欢迎大家关注我的公众号 javawebkf,目前正在慢慢地将简书文章搬到公众号,以后简书和公众号文章将同步更新,且简书上的付费文章在公众号上将免费。


一、Lambada表达式:

1、什么是lambda?
Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。

2、了解新操作符:
Java 8引入了新的操作符,->,叫箭头操作符或者叫lambda操作符。当使用lambda表达式时就需要使用这个操作符。

3、lambda表达式语法:
箭头操作符将lambda表达式分成了两部分:

  • 左侧:lambda表达式的参数列表(接口中抽象方法的参数列表)
  • 右侧:lambda表达式中所需执行的功能(lambda体,对抽象方法的实现)

语法有如下几种格式:

  • 语法格式一(无参数无返回值): () -> 具体实现
  • 语法格式二(有一个参数无返回值): (x) -> 具体实现 或 x -> 具体实现
  • 语法格式三(有多个参数,有返回值,并且lambda体中有多条语句):(x,y) -> {具体实现}
  • 语法格式四:若方法体只有一条语句,那么大括号和return都可以省略
    注:lambda表达式的参数列表的参数类型可以省略不写,可以进行类型推断。

看几个例子:
例一:

@Test
public void test1(){
     // 实现一个线程
     int num = 0;//jdk1.8以前,这个必须定义为final,下面才能用,1.8后默认就为final
     Runnable runnable = new Runnable() {
         @Override
         public void run() {
             System.out.println("hello world"+ num);
         }
     };
     runnable.run();
}

创建一个线程,重写run方法,在run方法里面打印一句话。我们想要的就是System.out.println("hello world"+ num);这行代码,但是为了实现这行代码,不得不多写了好多行。lambda就可以解决这一点,看看用lambda如何实现:

Runnable runnable1 = () -> System.out.println("hello world"+num);
runnable1.run();

用lambda这样就搞定了。首先还是Runnable runnable1 =,但是不用new了,右边就用lambda实现。我们要使用的是该接口的run方法,run方法不需要参数,所以lambda表达式左边就是(),lambda表达式右边是抽象方法的实现,也就是第一种方式中run方法的方法体写到lambda表达式右边就可以了。

例二:

Comparator<Integer> comparator = new Comparator<Integer>() {
     @Override
     public int compare(Integer o1, Integer o2) {
         return Integer.compare(o1,o2);//就这一行关键代码
     }
};

以前写一个比较器就要像上面那样写,先new比较器类,然后在其compare方法里写核心代码。用lambda实现:

Comparator<Integer> comparator = (x,y) -> Integer.compare(x,y);

compare方法需要两个参数,所以箭头操作符左边写(x,y),右边是compare方法的实现,所以应该写return Integer.compare(o1,o2);,但是根据上面的语法格式四可知,return可以省略,因此就写成了上面那样。

通过这两个例子可以感受到lambda表达式的简洁,但是问题来了:我们说lambda表达式就是一个匿名函数,我们只需要指定参数和lambda体即可,那么它是如何判断重写的是哪个方法呢?比如一个接口中有多个方法,如果使用lambda表达式来写,那么如何判断我们使用的是该接口的哪个方法?其实是不能判断的!通过上面两个例子可以发现,Runnable接口和Comparator接口都是只有一个方法的接口,所以可以使用lambda。

二、函数式接口:

1、什么是函数式接口?
像Runnable和Comparator这样只有一个方法的接口,称为函数式接口。也可以在接口上加上@FunctionalInterface注解,如果编译通过,则该接口就是函数式接口。lambda表达式就需要函数式接口的支持。

2、看一个需求:
需求:需要对两个数进行加减乘除等运算,怎么实现?

  • 传统做法:传统做法中,需要进行几种运算,我们就要写几个方法。一种运算对应一个方法。
  • lambda做法:首先要定义一个函数式接口,接口中只有一个方法,接收两个参数。
@FunctionalInterface
public interface MyInterface {
    public Integer getValue(Integer num1,Integer num2);
}

然后就可以使用了:

@Test
public void test5(){
      MyInterface myInterface = (x,y) -> x*y;//乘法运算
      MyInterface myInterface1 = (x,y) -> x+y;//加法运算
      Integer result1 = myInterface.getValue(100,200);
      Integer result2 = myInterface1.getValue(1024,2048);
      System.out.println(result1);
      System.out.println(result2);
}

所以用lambda的话,只需要定义一个函数式接口,不管进行什么操作,都可以用lambda解决,不用再一种运算对应一个方法。但是,还需要自己定义函数式接口,好像也没简单很多。Java考虑到这点了,所以内置了函数式接口。

3、四大内置函数式接口:
为了不需要我们自己定义函数式接口,Java内置了四大函数式接口,这四大接口加上它们的子类,完全满足我们的使用了。四大函数式接口是:

  • Consumer<T>:消费型接口(void accept(T t)),接收一个参数,无返回值。
  • Supplier<T>:供给型接口(T get()),无参数,有返回值。
  • Function<T,R>:函数型接口(R apply(T t)),接收一个参数,有返回值。
  • Predicate<T>:断言型接口(boolean test(T t)),接收一个参数,返回Boolean值。

4、四大函数式接口的使用:
接下来看看具体如何使用这四大函数式接口。
消费型接口的使用:

 Consumer consumer = (x) -> System.out.println("消费了"+x+"元");
 consumer.accept(100);

供给型接口的使用:

Supplier<Integer> supplier = () -> (int)(Math.random() * 100);//生成随机数
System.out.println(supplier.get());

函数型接口的使用:

Function<String,String> function = str -> str.toUpperCase();//将传入的字符串转成大写
String s = function.apply("adcdefggffs");
System.out.println(s);

断言型接口的使用:

 //需求:将满足条件的字符串添加到集合中去
  public List<String> filterString(List<String> strings, Predicate<String> predicate){
      List<String> stringList = new ArrayList<>();
      for (String string : strings) {
          if (predicate.test(string)){
              stringList.add(string);
          }
      }
      return stringList;
 }
//测试
@Test
 public void test4(){
      List<String> list = Arrays.asList("hello","world","niu","bi");
      List<String> newList = filterString(list,str -> str.length() > 3);//选出长度大于3的字符串
      newList.forEach(System.out::println);
 }

三、方法引用与构造器引用:

当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用。不过实现抽象方法的参数列表,必须与引用方法的参数列表保持一致。
1、方法引用语法:

  • 对象::实例方法
  • 类::静态方法
  • 类::实例方法

2、方法引用具体用法:
说了那么多可能还不清楚到底什么意思,一起来看几个例子。
语法一例子:

Consumer<String> consumer = x -> System.out.println(x);//传统写法
Consumer<String> consumer = System.out::println;//使用方法引用

println方法和Consumer的accept方法都是无返回值,接收一个参数,所以可以这样写。

语法二例子:

Comparator<Integer> comparator = (x,y) -> Integer.compare(x,y);
//因为compare方法已经被Integer实现了,且是静态的,所以这样用就行。
Comparator<Integer> comparator1 = Integer::compare;

语法三例子:

 BiPredicate<String,String> biPredicate = (x,y) -> x.equals(y);
 //可以改成如下写法
 //不过要满足:第一个参数是实例方法的调用者,第二个参数是实例方法的参数时,就可以这样用
 BiPredicate<String,String> biPredicate1 = String::equals;

3、构造器引用:

Supplier<Employee> supplier = () -> new Employee();
//可以改写成这样
//注意:需要调用的构造器的参数列表要与函数接口中抽象方法的参数列表一致
Supplier<Employee> supplier1 = Employee::new;
Employee employee = supplier.get();

四、Stream API:

Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

1、理解Stream:
Stream被称作流,是用来处理集合以及数组的数据的。它有如下特点:

  • Stream 自己不会存储元素。
  • Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
  • Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

2、使用Stream的三个步骤:

  • 创建Stream:一个数据源(如:集合、数组),获取一个流
  • 中间操作:一个中间操作链,对数据源的数据进行处理
  • 终止操作:一个终止操作,执行中间操作链,并产生结果

3、创建Stream:
直接看代码:

//1、通过集合提供的stream方法或parallelStream()方法创建
List<String> list = new ArrayList<>();
Stream<String> stringStream = list.stream();

//2、通过Arrays中的静态方法stream获取数组流
Employee[] employees = new Employee[10];
Stream<Employee> stream = Arrays.stream(employees);

//3、通过Stream类的静态方法of()创建流
Stream<String> stream1 = Stream.of("aa","bb","cc");

//4、创建无限流
//迭代方式创建无限流
//从0开始,每次加2,生成无限个
Stream<Integer> stream2 = Stream.iterate(0,(x) -> x+2);
//生成10个
stream2.limit(10).forEach(System.out::println);

//生成方式创建无限流
Stream.generate(() -> Math.random())
                .limit(5)
                .forEach(System.out::println);

上面介绍了集合、数组创建流的几种方式,都有对应的注解。

4、中间操作:
筛选与切片:

  • filter -- 接收lambda,从流中排除某些数据。
  • limit -- 截断流,使其元素不超过给定数量。
  • skip(n) -- 跳过元素,返回一个扔掉了前n个元素的流,若不足n个元素,则返回空流。
  • distinct -- 筛选,通过流所生成元素的hashCode()和equals()去除重复元素,所以对象必须重新hashCode方法和equals方法。

看代码:

employees.stream()//已有employees集合
         .filter((e) -> e.getAge() > 18)//中间操作(选出年龄大于18的)
         .limit(1)//中间操作(只返回一个)
         .forEach(System.out::println);//终止操作

映射:

  • map -- 接收lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
  • flatMap -- 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所以流连接成一个流。

看例子:

List<String> list = Arrays.asList("aa","bb","cc","dd");
list.stream()
    .map(str -> str.toUpperCase())//将所有的转成大写
    .forEach(System.out::println);

排序:

  • sorted() -- 自然排序(按照Comparable来排序)。
  • sorted(Comparator com) -- 定制排序(按照Comparator来排序)。

看例子:

List<String> list = Arrays.asList("ccc","bbb","aaa","ddd");
list.stream()
    .sorted()//自然排序
    .forEach(System.out::print);//aaa,bbb,ccc,ddd

//定制排序
employees.stream()//employees是一个存有多名员工的集合
      .sorted((e1, e2) -> {
          if (e1.getAge().equals(e2.getAge())){ //如果年龄一样
               return e1.getName().compareTo(e2.getName());//就比较姓名
          }else {
               return e1.getAge().compareTo(e2.getAge());//年龄不一样就比较年龄
          }
      }).forEach(System.out::println);

5、终止操作:
查找与匹配:

  • allMatch -- 检查是否匹配所有元素。
  • anyMatch -- 检查是否至少匹配一个元素。
  • noneMatch -- 检查是否没有匹配所有元素。
  • findFirst -- 返回第一个元素。
  • findAny -- 返回当前流中任意元素。
  • count -- 返回流中元素总个数。
  • max -- 返回流中最大值。
  • min -- 返回流中最小值。
//看看employee集合中是不是所有都是男的
boolean b = employees.stream()
                     .allMatch(e -> e.getGender().equals("男"));
System.out.println(b);

规约:

  • reduce(T identity,BinaryOperator) -- 可以将流中元素反复结合起来,得到一个值。
//规约求和
List<Integer> list = Arrays.asList(1,3,5,4,4,3);
Integer sum = list.stream()
                 .reduce(0,(x,y) -> x+y);//首先把0作为x,把1作为y,进行加法运算得到1,把1再作为x,把3作为y,以此类推
System.out.println(sum);

//获取工资总和
Optional<Double> optional = employees.stream()
                .map(Employee::getSalary)//提取工资
                .reduce(Double::sum);//求工资总和
System.out.println(optional2.get());

收集:

  • collect -- 将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法。
//把公司中所有员工的姓名提取出来并收集到一个集合中去
List<String> stringList = employees.stream()
                .map(Employee::getName)//提取员工姓名
                //.collect(Collectors.toList());//收集到list集合
                //.collect(Collectors.toSet());//收集到set集合
                .collect(Collectors.toCollection(LinkedList::new));//这种方式可收集到任意集合
stringList.forEach(System.out::println);//遍历集合

//计算工资平均值
Double avgSalary = employees.stream()
                .collect(Collectors.averagingDouble(Employee::getSalary));
System.out.println(avgSalary);

//根据年龄分组
Map<Integer,List<Employee>> map = employees.stream()
                .collect(Collectors.groupingBy(Employee::getAge));
System.out.println(map);

//先按性别分组,性别一样时按年龄分组
Map<String,Map<Integer,List<Employee>>> map1 = employees.stream()       
 .collect(Collectors.groupingBy(Employee::getGender,Collectors.groupingBy(Employee::getAge)));
System.out.println(map1);

//分区,满足条件的一个区,不满足的另一个区
Map<Boolean,List<Employee>> map2 = employees.stream()
         .collect(Collectors.partitioningBy(e -> e.getSalary() > 6000));//工资大于6000的为true区,否则为false区
System.out.println(map2);

//获取工资的总额、平均值等
DoubleSummaryStatistics dss = employees.stream()
           .collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println(dss.getSum());
System.out.println(dss.getAverage());
System.out.println(dss.getMax());

五、并行流与串行流:

1、fork/join框架:
此框架就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总。

fork/join

2、并行流与串行流:
通过上面的图可以知道,使用fork/join框架可以提高效率(运算量越大越明显,运算量可能反而更慢,因为拆分也需要时间),但是在Java 8之前需要自己实现fork/join,还是挺麻烦的,Java 8就方便多了,因为提供了并行流,底层就是使用了fork/join。Stream API 可以声明性地通过 parallel() 与 sequential() 在并行流与顺序流之间进行切换。

@Test
public void test(){
      Instant start = Instant.now();
      //普通做法求0加到10000000000的和
      LongStream.rangeClosed(0,100000000000L)
              .reduce(0,Long::sum);
      Instant end = Instant.now();
      System.out.println("耗费"+ Duration.between(end ,start) + "秒");//55秒
}

@Test
public void test2(){
     Instant start = Instant.now();
     //并行流求0加到10000000000的和
     LongStream.rangeClosed(0,100000000000L)
             .parallel()//使用并行流
             .reduce(0,Long::sum);
     Instant end = Instant.now();
     System.out.println("耗费"+ Duration.between(end ,start) + "秒");//30秒
}

通过运行上面的程序可以明显感受到并行流的高效。

六、新时间日期API:

Java 8之前的Date和Calendar都是线程不安全的,而且使用起来比较麻烦,Java 8提供了全新的时间日期API,LocalDate(日期)、LocalTime(时间)、LocalDateTime(时间和日期) 、Instant (时间戳)、Duration(用于计算两个“时间”间隔)、Period(用于计算两个“日期”间隔)等。

1、LocalDate、LocalTime、LocalDateTime:
这三个用法一样。

//获取当前系统时间
LocalDateTime localDateTime = LocalDateTime.now();//当前时间日期
LocalDateTime localDateTime2 = localDateTime.plusYears(2);//加两年
System.out.println(localDateTime.getMonth());
System.out.println(localDateTime);
System.out.println(localDateTime2);
//指定时间
LocalDateTime localDateTime1 = LocalDateTime.of(2018,12,13,21,8);
System.out.println(localDateTime1);

2、Instant 时间戳:
时间戳就是计算机读的时间,它是以Unix元年(传统 的设定为UTC时区1970年1月1日午夜时分)开始算起的。

 //计算机读的时间:时间戳(Instant),1970年1月1日0时0分0秒到此时的毫秒值
Instant instant = Instant.now();
System.out.println(instant);//默认是美国时区,8个时差
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));//加上时差
System.out.println(offsetDateTime);
System.out.println(instant.toEpochMilli());//显示毫秒值

3、Duration 和 Period:

LocalTime localTime = LocalTime.now();
try {
    Thread.sleep(1000);
}catch (Exception e){
    e.printStackTrace();
}
LocalTime localTime1 = LocalTime.now();
System.out.println(Duration.between(localTime,localTime1).toMillis());

//获取两个日期之间的间隔
LocalDate localDate = LocalDate.of(2012,1,1);
LocalDate localDate1 = LocalDate.now();
Period period = Period.between(localDate,localDate1);
System.out.println(period);
System.out.println(period.getYears()+"年"+period.getMonths()+"月"+period.getDays()+"日");

4、时间校正器(TemporalAdjuster):

LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);

LocalDateTime localDateTime1 = localDateTime.withDayOfMonth(1);//localDate日期中月份的1号
System.out.println(localDateTime1);
localDateTime1.with(TemporalAdjusters.firstDayOfNextMonth());//下一个月的第一天
localDateTime.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));//下周日

5、格式化日期(.DateTimeFormatter ):

@Test
public void test6(){
        //DateTimeFormatter:格式化
        //使用预设格式
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ISO_DATE_TIME;
        LocalDateTime localDateTime = LocalDateTime.now();
        String str = localDateTime.format(dateTimeFormatter);
        System.out.println(str);
        System.out.println("==========================");
        //自定义格式
        DateTimeFormatter dateTimeFormatter1 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
        String str2 = localDateTime.format(dateTimeFormatter1);
        //这样格式化也可以
        String str3 = dateTimeFormatter1.format(localDateTime);
        System.out.println(str2);
        System.out.println(str3);
        //退回到解析前的格式
        LocalDateTime newDate = localDateTime.parse(str,dateTimeFormatter);
        System.out.println(newDate);
}

6、时区的处理:
Java8 中加入了对时区的支持,带时区的时间为分别为:ZonedDate、ZonedTime、ZonedDateTime。

 @Test
 public void test7(){
   //ZonedDate ZonedTime ZonedDateTime
   LocalDateTime dateTime = LocalDateTime.now(ZoneId.of("Europe/Tallinn"));
   System.out.println(dateTime);
}

七、接口中的默认方法和静态方法:

public interface MyInterface {
    default String test(){
        return "允许存在有具体实现的方法";
    }

    public static String test2(){
        return "接口中还可以有静态方法";
    }
}

如上所示,Java 8的接口中允许有默认方法和静态方法。如果一个类继承了一个类还实现了一个接口,而且接口中的默认方法和父类中的方法同名,这时采用类优先原则。也就是说,子类使用的是父类的方法,而不是接口中的同名方法。

八、其他新特性:

1、Optional类:
这个类是为了尽可能减少空指针异常的。就是把普通对象用Optional包起来,做了一些封装。看看其用法:

@Data
public class Man { //男人类
    private Godness godness;//女神
}
@Data
public class Godness {
    private String name;
    public Godness(String name){
        this.name = name;
    }
    public Godness(){
    }
}
//获取男人心中的女神的名字(有的人不一定有女神,也就是说女神可能为空)
    //常规做法要加很多判断
    public String getGodnessName(Man man){
        if (man != null){
            Godness godness = man.getGodness();
            if (godness != null){
                return godness.getName();
            }else{
                return "我心中没有女神";
            }
        }else {
            return "男人为空";
        }
    }

一个man类,有一个成员变量女神,女神也是一个类,有一个成员变量,名字。要获取man心中的女神,为了防止控制针异常,要做很多的判断。如果使用Optional呢?做法如下:

//新男人类
@Data
public class NewMan {
    private Optional<Godness> godness = Optional.empty();
}
//使用optional后的方法
public String getGodnessName2(Optional<NewMan> man){
    return man.orElse(new NewMan())
                .getGodness()
                .orElse(new Godness("我没有女神"))
                .getName();
}

这样就简单多了。

2、重复注解与类型注解:
Java 8 可以使用重复注解和类型注解,如下图:

重复注解&类型注解

总结:

本文说了一些Java 8 的新特性,重点就是lambda表达式和Stream API,可以简化很多操作。肯可能还有些文中未涉及的,在此抛砖引玉,望各位大佬指点!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • java8新特性 原创者:文思 一、特性简介 速度更快 代码更少,增加了Lambda 强大的Stream API ...
    文思li阅读 3,032评论 1 1
  • 对于Java开发者来说,Java8的版本显然是一个具有里程碑意义的版本,蕴含了许多令人激动的新特性,如果能利用好这...
    jackcooper阅读 1,017评论 0 6
  • 对于Java开发者来说,Java8的版本显然是一个具有里程碑意义的版本,蕴含了许多令人激动的新特性,如果能利用好这...
    huanfuan阅读 552评论 0 9
  • 原创文章&经验总结&从校招到A厂一路阳光一路沧桑 详情请戳www.codercc.com 对于Java开发者来说,...
    你听___阅读 2,337评论 4 38
  • Java 8自Java 5(发行于2004)以来最具革命性的版本。Java 8 为Java语言、编译器、类库、开发...
    huoyl0410阅读 615评论 1 2