Java8新特性

2020-10-15  本文已影响0人  逝去丶浅秋

一、Lambda表达式

Lambda表达式引入了一个新的操作符" -> "。Lambda表达式将函数当成参数传递给某个方法,或是把代码本身当作数据处理。

Lambda表达式分为两部分:

() -> { //执行的功能 }
左侧:Lambda表达式的参数列表
右侧:Lambda表达式中所需要执行的功能

1、Lambda表达式格式

下面讲解Lambda的写法:

void test(){
  (x,y) -> { //语句 }
  (String x,String y) -> { //语句 }
}

上面两种写法时一样的。

() -> { //语句 }

(x) -> { //语句 }
x -> { //语句 }
上面两句等价。

(x,y) -> x + y;

当Lambda表达式中有多条语句时,需加上大括号和return

(x,y) -> {
  int i = x + y;
  System.out.println(i);
  return i;
}
2、Lambda表达式使用
void test(){
  //不使用Lambda表达式
  Runnable r = new Runnable() {
    @Override
    public void run() {
      System.out.println("不使用Lambda表达式方式--->hello");
    }
  };
  r.run;

  //使用Lambda表达式
  Runnable runnable = () -> System.out.println("使用Lambda表达式方式--->hello");
  runnable.run;
}

上面的例子介绍了使用Lambda表达式和不使用时的区别。

3、Lambda表达式需要函数式接口的支持

函数式接口:接口中只有一个抽象方法的接口,称为函数式接口。可以使用注解@FunctionalInterface标注,此注解可以检查接口是否是函数式接口,此注解可以加也可以不加。

java中给我们提供了一些函数式接口,我们也可以自定义函数式接口,下面先举个自定义函数式接口的例子,接着再讲解java给我们提供的部分函数式接口。

/**自定义函数式接口*/
@FunctionalInterface
public interface HandleData<T> {
    public T handle(T t);
}

下面是自定义函数接口的使用:

需求:计算数组中元素的和,正常情况步骤如下:

int[] ints = {1,2};
HandleData<int[]> h = new HandleData<int[]>() {
    @Override
    public Object handle(int[] ints) {
        return ints[0] + ints[1];
    }
};
//调用handle方法,将数组ints传进去,获得计算结果
int result = (int) h.handle(ints);

接下来我们来看看使用Lambda表达式的写法:

int[] ints = {1,2};
HandleData<int[]> h = (arr) -> arr[0] + arr[1];
int result = (int) h.handle(ints);

上面代码中的(arr) -> arr[0] + arr[1];其实就是重写的handle(int[] ints)方法。是不是用Lambda表达式更简洁一些,但是需要注意的是Lambda表达式需要函数式接口的支持

Function<T,R>:函数型接口.
  R apply(T t);
  T作为输入,返回的R作为输出,输入和返回的类型可以一致,T、R可以不一致。

int i = 0;
//如果需要处理的代码是多行,就需要加大括号
Function<Integer,Integer> fun = (x) -> x + 1;
int result = fun.apply(i);

Predicate<T>:判断型接口。
  boolean test(T t);
  T作为输入,返回boolean值的输出。

Consumer<T>:消费型接口。

void accpet(T t);

​ T作为输入没有输出。

Supplier<T>:供给型接口。

T get();

​ 没有输入,T作为输出。

BinaryOperator<T>:二元运算型接口。

R apply(T t, U u);

​ 两个T作为输入,T同样是输出,是BiFunction的子接口。

UnaryOperator<T>:一元运算型接口。

T apply(T t);

​ 是Function的变种,输入输出者都是T,与Function区别是,UnaryOperator输入和返回的类型一致,在此种情况下可以使用UnaryOperator替换Function。

4、方法引用和构造器引用

方法引用:若Lambda体中的功能有方法以及实现了,我们可以使用"方法引用",可以理解为方法引用是Lambda表达式的另外一种表现形式

使用条件:方法引用所引用的方法的参数列表和返回值必须和函数式接口中抽象方法的参数列表和返回值完全一致。

语法格式:主要有三种

  • 对象::实例方法名 ---> Object::method

  • 类名::静态方法名 ---> ClassName::static method

  • 类名::实例方法名--->ClassName::method

注意类::静态方法名类::实例方法名的区别:

类::实例方法名方式中Lambda的第一个参数是实例方法的调用者,例如:x,第二个参数(或无参)是实例方法的参数,例如:y (参见下面ClassName::method中的代码所示)。

下面看代码来看如何使用:

Employee emp = new Employee("name",18);
//获取Employee中的age,因为getAge()方法没有输入只有输出,我们可以选择供给型接口Supplier<R>供给型接口。接口方法:T get();Employee类中获取年龄方法:int getAge();
Supplier<Integer> sup = () -> emp.getAge();
System.out.println("Lambda表达式方式:" + sup.get());

//emp对象::getAge
Supplier<Integer> supplier = emp::getAge;
System.out.println("方法引用方式:" + supplier.get());
//Integer类的compare方法
Comparator<Integer> c = (x,y) -> Integer.compare(x,y);
System.out.println("Lambda表达式方式:" + c.compare(1,2));

//Integer类名::compare
Comparator<Integer> com = Integer::compare;
System.out.println("方法引用方式:" + com.compare(1,2));
BiPredicate<String ,String > b = (x, y) -> x.equals(y);
System.out.println("Lambda表达式方式:" + b.test("a","b"));

BiPredicate<String ,String > bip = String::equals;
System.out.println("方法引用方式:" + bip.test("a","b"));

构造器引用:构造器能与函数式接口中的方法相兼容。

语法格式:

  • 类名::new--->ClassName::new

使用条件:构造器参数列表与函数式接口中抽象方法的参数列表一致。

//默认无参构造器
Supplier<Employee> s = () -> new Employee();
System.out.println("Lambda表达式方式:" + s.get());

Supplier<Employee> sup = Employee::new;
System.out.println("构造器引用方式:" + sup.get());

//有参数构造器,也可以使用BiFunction或是自己写函数式接口来实现两个或多个构造器引用
Function<String,Employee> f = (x) -> new Employee(x);
System.out.println("Lambda表达式方式:" + f.apply("z"));

Function<String,Employee> fun = Employee::new;
System.out.println("构造器引用方式:" + fun.apply("z"));

//数组引用
Function<Integer,String[]> a = (x) -> new String[x];
System.out.println("Lambda表达式方式:" + a.apply(10).length);

Function<Integer,String[]> arr = String[]::new;
System.out.println("数组引用方式:" + arr.apply(10).length);

二、Stream流

1、Stream流是什么?

是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。

2、Stream的操作步骤
3、创建Stream流的方式
4、中间操作-筛选切片

filter(Predicate p)limit(long maxSize)skip(long n)distinct(),下面介绍这四个方法:

//class Employee {name,age,salary}
List<Employee> list = Arrays.asList(new Employee("emp1",1,1000.00),new Employee("emp2",2,2000.00),new Employee("emp3",3,3000.00),new Employee("emp4",4,4000.00),new Employee("emp4",4,4000.00),
);
Stream<Employee> stream = empList.stream()
    .filter((e) -> {
        return e.getAge() > 2;
    });
//只会显示年龄大于2的
Stream<Employee> stream = empList.stream()
    .limit(2);
//只会得到前两条
Stream<Employee> stream = empList.stream()
    .skip(2);
//只会得到后两条
Stream<Employee> stream = empList.stream()
    .distinct();
//会去除掉最后一个new Employee("emp4",4,4000.00)的数据

distinct()方法是根据元素的hashCode()和equals()的规则来判断元素是否重复的,所以如果有需求就重写这两个方法。

5、中间操作-映射

map(Function f)mapToDouble(ToDoubleFunction f)mapToInt(ToIntFunction f)mapToLong(ToLongFunction f)flatMap(Function f),下面介绍这五个方法:

//class StreamTest03
List<String> list = Arrays.asList("aa","bb");
list.stream()
    .map((str) -> str.toUpperCase())
    .forEach(System.out::println);
//结果:AA BB

//
Stream<Stream<Character>> characterStream = list.stream()
                .map(StreamTest03::filterCharacter);//结果:a a b b
//如何遍历characterStream,Stream<Stream<Character>>里的数据:{{a,a},{b,b}}
characterStream.forEach( (stream) -> {
    stream.forEach(System.out::println);
});

//将字符串拆成char然后存到list的方法
public static Stream<Character> filterCharacter(String str){
    List<Character> list = new ArrayList<>();
    for (Character c : str.toCharArray()) {
        list.add(c);
    }
    return list.stream();
}
List<Double> list = Arrays.asList(1.1,2.2);
list.stream().mapToDouble((d) -> d+1).forEach(System.out::println);
//结果:2.1  3.1
Stream<Character> cStream = list.stream()
    .flatMap(StreamTest03::filterCharacter);//{a,a,b,b}
cStream.forEach(System.out::println);//结果:a a b b

如上代码所示,和map(Function f)的区别是它会把所有的连接成一个流。

6、中间操作-排序

sorted()sorted(Comparator comp),下面介绍这两个方法:

List<String> list = Arrays.asList("bb","aa","cc");
list.stream()
    .sorted()
    .forEach(System.out::println);
//结果:aa bb cc
//list是上面的List<Employee>
list.stream()
    .sorted((e1,e2) -> {
        if (e1.getAge() == e2.getAge()) {   //如果年龄相等则根据名字排序
            return e1.getName().compareTo(e2.getName());
        }else {
            return e2.getAge() - e1.getAge();   //年龄不等从大往小排序
        }
    })
    .forEach(System.out::println);
//结果:4 4 3 2 1
7、终止操作-查找与匹配

直接介绍有哪些方法,例子就不列举了。

8、归约reduce
List<Integer> list = Arrays.asList(1,2,3,4,5);
Integer reduce = list.stream()
    .reduce(0, (x, y) -> x + y);
System.out.println(reduce);
//结果:15

其实它是将identity这个起始值作为上面代码中的x,然后从流中取出一个作为y,然后进行x+y运算。

Optional<Integer> op1 = list.stream().reduce((x, y) -> x - y);
System.out.println(op1.get());
//结果:-13
Optional<Empoyee> op2 = employees.stream()
    .map(Employee::getSalary)
    .reduce(Double::sum);
System.out.println(op2.get());
//可以得到工资的总和
9、收集collect

Collector接口中方法的实现决定了如何对流执行收集操作(如收集到List、Set、Map)。

public interface Collector<T, A, R> {} 中:

T:归约操作的输入元素的类型

A:归约操作的可变累积类型(通常作为一个实现细节隐藏)

R:归约操作结果的类型

Collectors类是Collector的实现,它实现了各种有用的归约操作,例如将元素累积到集合中,根据各种标准对元素进行汇总等。提供了很多静态方法,可以方便的创建常见收集器实例,具体方法与实例如下表:

方法 返回值类型 作用
toList() List<T> 把流中元素收集到list
toSet() Set<T> 把流中元素收集到set
toCollection(Supplier s) Collection<T> 把流中的元素收集到创建的集合
counting() Long 计算流中元素的个数
summingInt(ToIntFunction mapper) Integer 对流中元素的整数属性求和
averagingInt(ToIntFunction mapper) Double 计算流中元素Integer属性的平均值
summarizingInt(ToIntFunction mapper) IntSummaryStatistics 收集流中Integer属性的统计值。可通过返回的对象获取平均值等
joining() String 连接流中每个字符串
maxBy(Comparator com) Optional<T> 根据比较器选择最大值
minBy(Comparator com) Optional<T> 根据比较器选择最小值
collectingAndThen(Collector c,Function f) f返回的类型 包裹另一个收集器,对其结果转换函数
groupingBy(Function classifier) Map<K,List<T>> 根据某属性值对流分组,属性为K,结果为V
partitioningBy(Predicate p) Map<Boolean,List<T>> 根据true或false进行分区

暂且介绍上面几个方法,更多方法可以去看JDK8的API。

下面以CollectorscollectingAndThen为例来介绍collect方法的使用。

Collector<T,A,RR> collectingAndThen(Collector<T,A,R> downstream,Function<R,RR> finisher)方法:

此方法中泛型:

  • <T>:输入元素的类型
  • <A>: downstream收集器中的中间积累的类型
  • <R>: downstream收集器的结果的类型
  • <RR>: 结果收集器的结果的类型

方法有两个参数:

  • Collector<T,A,R> downstream:对数据进行处理
  • Function<R,RR> finisher:对downstream收集器最终的结果再处理一下

返回值:downstream收集器的操作完成后,最终的结果再通过Function再处理一下,然后返回一个Collector。

说明:finisher是对downstream的结果进行处理,finisher接口的参数是downstream的结果。collectingAndThen方法的返回值是finisher中返回的值。

//List<Employee> list
Employee collect = list.stream()
    .collect(Collectors.collectingAndThen(
        Collectors.maxBy(Comparator.comparing(Employee::getName)),
        (e) -> e.get()
    ));

List<String> list = Arrays.asList("a","b");
String str = list.stream()
    .collect(Collectors.collectingAndThen(
        Collectors.joining(","),
        String::toUpperCase
    ));
//结果是:A,B

上面代码,downstream先用joining方法将字符串连接成:"a,b",然后将使用String::toUpperCase"a,b"转大写。

三、Optional类

Optional<T> 类(java.util.Optional)是一个容器类,代表一个值存在或不存在,原来用null表示一个值不存在,现在Optional可以更好的表达这个概念。并且可以避免空指针。

常用方法:

Optional.of(T t):创建一个Optional实例

Optional.empty():创建一个空的Optional实例

Optional.ofNullable(T t):若t不为null,创建Optional实例,否则创建空实例

isPresent():判断是否包含空值,空返回false

orElse(T t):如果调用对象包含值,返回该值,否则返回t

orElseGet(Supplier s):如果调用对象包含值,返回该值,否则返回s获取的值

map(Function f):如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()

flatMap(Function mapper):与map类似,要求返回值必须是Optional

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

1、接口中的默认方法

java8中接口里面包含具体的实现方法,该方法称为默认方法,默认方法使用default关键字修饰。

public interface MyInterface {
    default String getName(){
        return "MyInterface";
    }
}

对于默认的方法,遵循的原则:

若一个接口中定义了一个默认方法,另一个的父类或接口中又定义一个同名方法时:

public class MyClass {
    public String getName(){
        return "MyClass";
    }
}
public interface MyInterface {
    default String getName(){
        return "MyInterface";
    }
}
public class SubClass extends MyClass implements MyInterface {}

SubClass sub = new SubClass();
String name = sub.getName();
System.out.println(name);
//MyClass
public interface MyInterface2 {
    default String getName(){
        return "MyInterface2";
    }
}
public class SubClass implements MyInterface1,MyInterface2{
    @Override
    public String getName() {
        return "SubClass重写该方法";
    }
}

如上代码,实现类SubClass里要重写该方法解决冲突。

2、接口中的静态方法
public interface MyInterface1 {
    default String getName(){
        return "MyInterface1";
    }
    public static void show(){
        System.out.println("接口中的静态方法");
    }
}

MyInterface1.show();
//接口中的静态方法

五、新时间日期API

JDK8新增的时间API主要有三种:

java.time.LocalDate:只对年月日做出处理

java.time.LocatTime:只对时分秒纳秒做出处理

java.time.LocalDateTime:同时可以处理年月日和时分秒

java.time.format.DateTimeFormatter:日期时间对象格式化和解析

1、LocalDate、LocalTime、LocalDateTime

LocalDate、LocalTime、LocalDateTime 类的实例是不可变的对象,分别表示使用 ISO-8601日系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息。

方法 描述 示例
now() 静态方法,根据当前时间创建对象 LocalDate.now();<br />LocalTime.now();<br />LocalDateTime.now();
of() 静态方法,根据指定日期/时间创建对象 LocalDate.of(2020,01,01);
plusDays<br />plusWeeks
plusMonths
plusYears
向当前LocalDate对象添加几天、几周、几个月、几年
minusDays<br />minusWeeks
minusMonths
minusYears
从当前LocalDate对象减去几天、几周、几个月、几年
plus,minus 添加或减去一个Duration或Period
with 通过TemporalAdjuster将日期修改为指定的值,并返回新的LocalDate对象
withDayOfMonth
withDayOfYear
withMonth
withYear
将月份天数,年份天数,月份,年份修改为指定的值并返回新的LocalDate对象
getDayOfMonth 获得月份天数(1-31)
getDayOfYear 获得年份天数(1-366)
getDayOfWeek 获得星期几(返回一个DayOfWeek枚举值)
getMonth 获得月份,返回一个Month枚举值
getMonthValue 获得月份(1-12)
getYear 获得年份
until 获得两个日期之间的Period对象,或者指定ChronoUnits的数字
isBefore,isAfter 比较两个LocalDate
isLeapYear 判断是否是闰年
2、Instant时间戳

用于“时间戳”的运算。它是以Unix元年(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的描述进行运算,默认UTC时区。

3、Duration和Period计算时间/日期间隔
Instant start = Instant.now();
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
     e.printStackTrace();
}
Instant end = Instant.now();
Duration d = Duration.between(start, end);
d.toMillis();//获取相差的毫秒
d.toMillis();//获取相差的秒
LocalDate old = LocalDate.of(2019,10,1);
LocalDate now = LocalDate.now();//2020-10-01
Period p = Period.between(old, now);

p.getYears();//两个日期之间间隔的年,结果为:1
p.getMonths();//两个日期之间间隔的月,结果为:0
p.getDays();//两个日期之间间隔的日,结果为:0
//这三个加起来就是两个日期之间相差的时间
4、TemporalAdjuster时间校正器
LocalDateTime now = LocalDateTime.now();
now.with(TemporalAdjusters.next(DayOfWeek.MONDAY));//返回以今天为准的下周一的时间

自定义方式:

LocalDate result = now.with((time) -> {
    LocalDate date = (LocalDate) time;
    return date.plusDays(1);
});
//返回下一天的日期
5、DateTimeFormatter时间/日期格式化

DateTimeFormatter是时间日期格式化的类,更复杂的格式化可以使用DateTimeFormatterBuilder类。

LocalDateTime now = LocalDateTime.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
DateTimeFormatterBuilder dtfb  = new DateTimeFormatterBuilder();
dtfb.append(dtf).appendLiteral(" 星期三");
String format = now.format(dtfb.toFormatter());
//2020-10-01 17:39:33 星期三

这个类提供了三种格式化方法:

//格式化
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatDate = now.format(dtf);
System.out.println("formatDate--->" + formatDate);//formatDate--->2020-10-01 17:57:00
//解析
LocalDateTime parseDate = now.parse(formatDate, dtf);
System.out.println("parseDate--->" + parseDate);//parseDate--->2020-01-14T17:57
6、ZonedDateTime、ZoneId时区处理

带时区的时间分别为:ZonedDateZonedTimeZonedDateTime

其中每个时区都对应着 ID,地区ID都为 “{区域}/{城市}”的格式:例如 :Asia/Shanghai 等。

ZoneId:该类中包含了所有的时区信息

LocalDateTime now = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
ZonedDateTime zdt = now.atZone(ZoneId.of("Asia/Shanghai"));

六、重复注解与类型注解

对注解处理提供了两点改进:可重复注解及可用于类型的注解。

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {
    MyAnnotation[] value();
}
@Repeatable(MyAnnotations.class)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,ElementType.TYPE_PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value();
}
@MyAnnotation("Hello")
@MyAnnotation("World")
public void test(@MyAnnotation("a") String str){}
上一篇下一篇

猜你喜欢

热点阅读