Java8的新特性

一、Lambda表达式

二、函数式接口

三、接口的默认方法和静态方法

四、方法引用

五、Stream流

六、Date Time API

 

一、Lambda表达式

Lambda参数 ->  Lambda主体

Lambda表达式是一个由参数列表、箭头、函数主体、返回值类型组成的一个匿名函数,Lambda表达式可以让我们更为简洁地表示可传递的匿名函数,Lambda表达式允许我们将函数当成参数传递给某个方法。

String[] args = {"wang","li","zhao","sun"};
//1、最简单的Lambda表达式可由逗号分隔的参数列表、->符号和语句块组成
Arrays.asList( args ).forEach(e -> System.out.println( e ) );
//2、如果Lambda表达式需要更复杂的语句块,则可以使用花括号将该语句块括起来,类似于Java中的函数体
Arrays.asList( args ).forEach( e -> {
    System.out.print( e );
    System.out.println( e );
} );
//3、多个参数时用圆括号,Lambda表达式中的语句块只有一行返回值的类型由编译器推理得出
Arrays.asList( args ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
//4、Lambda表达式需要返回值时,在花括号内使用return
Arrays.asList( args ).sort( ( e1, e2 ) -> {
    int result = e1.compareTo( e2 );
    return result;
} );

使用Lambda表达式简化了匿名内部类的表达方式,使用比较器进行演示:

List<String> strList = new ArrayList<>();
strList.add("123456789");
strList.add("1234567");
strList.add("12345678");
strList.add("1234");
//使用匿名内部类
Collections.sort(strList, new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        return o1.length() - o2.length();
    }
});
//使用Lambda表达式
strList.sort((e1,e2) -> { return e1.length() - e2.length(); });

二、函数式接口

函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,Object的public方法除外,但是可以有多个非抽象方法的接口。 函数式接口的一大特性就是可以被lambda表达式和函数引用表达式代替,也就是说声明这样的接口,是可以灵活的以方法来传参。

1、自定义函数式接口示例

FuncInterface接口只有一个抽象接口,在filterUser方法中将函数式接口作为参数,测试方法中直接在传参的时候将筛选条件传递过去,->前面的内容是FuncInterface 接口里test方法需要的参数,后面的内容是方法的返回值。在此示例中Lambda表达式被作为参数转换为函数式接口。nctionalInterface@FunctionalInterface

@FunctionalInterface
public interface FuncInterface{
    boolean test(User user);
}

public static List<User> filterUser(List<User> users , FuncInterface func){
    List<User> result = new ArrayList<>();
    for (User user : users){
        if (func.test(user)){
            result.add(user);
        }
    }
    return result;
}

public void interfaceTest(){
    List<User> users = getUsers();
    System.out.println(
            filterUser(users,user -> user.getUserId() == 1)
    );
}

2、Java8中的函数式接口

在java8的更新中增加了许多函数式接口,查看源码即可找到这些函数式接口,如:

Predicate接口:断言型接口,该接口是一个谓词函数,主要作为一个谓词演算推导真假值存在,其意义在于帮助开发一些返回bool值的Function。本质上也是一个单元函数接口,其抽象方法test接受一个泛型参数T,返回一个boolean值。Predicate接口还提供了and和or等其他方法,在使用复合Lambda表达式的时候会用到。

//调用filter
users.stream().filter(user -> "男".equals(user.getSex())).collect(Collectors.toList());

//查看filter,参数是Predicate接口
Stream<T> filter(Predicate<? super T> predicate);

//Predicate接口就是一个函数式接口,除默认方法和静态方法外只有一个test抽象方法

Consumer接口:消费型接口,接收一个参数进行消费,但无需返回结果。Consumber还有一个andThen的默认方法,可以实现先消费和后消费

User user = new User(1,"张一","123456",50,"男");
Consumer<User> consumer1 = s -> System.out.println("姓名:"+user.getUserName());
Consumer<User> consumer2 = s -> System.out.println("年龄:"+user.getAge());
consumer1.andThen(consumer2).accept(user);

Supplier接口:供给型接口。无其他默认方法和静态方法

Supplier<String> supplier = () -> "Hello World";
System.out.println(supplier.get());

Function接口:函数型接口,R apply(T t),传入一个参数,返回想要的结果。该接口有compose和andThen两个默认方法,identity一个静态方法。andThen先执行function1后执行function2,compose正好相反。

Function<Integer, Integer> function1 = e -> e * 2;
Function<Integer, Integer> function2 = e -> e * e;

Integer apply1 = function1.compose(function2).apply(3);//18
Integer apply2 = function1.andThen(function2).apply(3);//36

其他的函数型接口在遇到的时候与上面的借口类似,了解其参数、返回值、默认方法、静态方法即可知道其功能。

三、接口的默认方法和静态方法

在之前的函数式接口中大量的应用到了默认方法和静态方法,这两个概念是在Java8中新增的概念,扩展了接口的含义,使得接口可以有具体的实现。在 Java 8 之前,接口只能定义抽象方法,而不能有方法的实现,只有抽象类才能同时拥有抽象方法和非抽象方法。

1、默认方法

默认方法使用default关键字声明,接口通过声明默认方法,提供了该方法的默认实现,子类继承的时候可以选择是否实现该方法,如果子类实现了该方法优先调用子类中的实现,否则使用默认实现。

public void methodTest(){
    new imClass().hello();
}

class imClass implements Interface {}

interface Interface{
    default void hello(){
        System.out.println("Hello Interface");
    }
}

多继承问题,当一个类同时继承多个接口,多个接口中都存在相同方法名称的默认方法,此时类会调用哪个方法。

public void methodTest(){
    new imClass().hello();
}

class imClass implements Interface1,Interface2 {
    public void hello(){
        Interface1.super.hello();
    }
}

interface Interface1 {
    default void hello(){
        System.out.println("Hello Interface1");
    }
}

interface Interface2 {
    default void hello(){
        System.out.println("Hello Interface2");
    }
}

当同时继承多个有相同方法名称的接口时,继承的类会报错,编译器无法确定使用哪个默认方法,此时必须要在子类中重写或覆盖该方法。

因此多继承问题的优先级:

(1)类中的方法优先级最高,如果类中实现了接口中的默认方法,接口中的具有相同方法名称和参数的默认方法都会被忽略。

(2)类同时继承子接口和父接口,子接口中实现了父接口中的默认方法,子接口中的实现方法有限。

(3)类同时继承多个相同优先级的接口,并且都有相同方法名和参数的默认方法,此时必须要在类中重写或覆盖该默认方法。

2、静态方法

静态方法通常用于为特定操作提供工具方法,比如Predicate接口中的isEqual方法。

@FunctionalInterface
public interface Predicate<T> {

    ······

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

四、方法引用

方法引用是一种更加简洁易懂的Lambda表达式,在Java 8中,我们会使用Lambda表达式创建匿名方法,但是有时候,我们的Lambda表达式可能仅仅调用一个已存在的方法,而不做任何其它事,对于这种情况,通过一个方法名字来引用这个已存在的方法会更加清晰,Java 8的方法引用允许我们这样做。方法引用是一个更加紧凑,易读的Lambda表达式,注意方法引用是一个Lambda表达式,其中方法引用的操作符是双冒号"::"

使用比较器进行对比:

List<User> users = getUsers();
//使用匿名内部类
Collections.sort(users, new Comparator<User>() {
    @Override
    public int compare(User u1, User u2) {
        return u1.getAge() - u2.getAge();
    }
});
//使用Lambda表达式
Collections.sort(users, (u1,u2) -> User.compareByAge(u1,u2));
//使用方法引用
Collections.sort(users, User::compareByAge);

1、方法引用类型

public class User {

    private Integer age;

······

    public Integer getAge() {
        return age;
    }

······

    public static int compareByAge1(User u1, User u2) {
        return u1.getAge() - u2.getAge();
    }

    public int compareByAge2(User u1, User u2) {
        return u1.getAge() - u2.getAge();
    }
}

(1)构造器引用,此为无参格式,有参格式为Class<T>::new

//构造器引用
Supplier<User> user = User::new;

(2)静态方法引用

//静态方法引用
Collections.sort(users, User::compareByAge1);

(3)类成员方法引用

//类成员方法引用(无参)
Collections.sort(users, Comparator.comparing(User::getAge));

(4)某个实例对象的成员方法引用,对于非静态有参的方法只能引用实例对象中的该方法

//某个实例对象成员方法引用
Collections.sort(users, users.get(0)::compareByAge2);

五、Stream流

Stream流常用来对集合进行处理,但是Stream并不是某种数据结构,它只是数据源的一种视图,这里的数据源可以是一个数组,Java容器或I/O channel。通常流不会由手动创建,而是由容器调用产生,如:调用 Collection.stream() 或者 Collection.parallelStream() 方法,调用 Arrays.stream(T[] array) 方法,parallelStream方法为创建并行流。对 stream 的操作分为为两类, 中间操作和结束操作 ,中间操作的结果是返回一个新的stream,并不会对数据源进行任何操作,而是将数据源中符合条件的数据放入新的stream中返回。结束操作会遍历中间操作产生的stream得出结果或者附带结果,在结束操作之后stream就会失效。

1、Stream的中间操作

Stream的中间操作很多,如:concat() distinct() filter() flatMap() limit() map() peek() skip() sorted() parallel() sequential() unordered(),中间操作也可以连接起来就像一个流水线一样

部分常用操作实例:

private static List<User> getUsers(){
    List<User> users = new ArrayList<>();
    users.add(new User(1,"张一","123456",50,"男"));
    users.add(new User(2,"张二","123456",20,"女"));
    users.add(new User(3,"王三","123456",29,"男"));
    users.add(new User(4,"王四","123456",40,"男"));
    users.add(new User(5,"李五","123456",44,"女"));
    users.add(new User(6,"李六","123456",34,"男"));
    return users;
}

filter():接受一个Lambda表达式,过滤流中符合条件的元素返回一个满足条件的stream。

List<User> users = getUsers();
//filter
users.stream().filter(user -> "男".equals(user.getSex())).forEach(System.out::println);

distinct():去除重复信息,distinct用于字符串数组等的去重没有任何问题,但是实体类的去重需要特殊处理,正常情况下要达到预期效果需要重写实体类的hashcode和equals方法,但是如此太过麻烦,可以自己实现distinctByKey方法去重。

//字符串去重
List<String> strs = Arrays.asList("1", "2", "3", "4", "5", "3");
strs.stream().distinct().forEach(System.out::println);
//实体类去重
users.stream().filter(distinctByKey(User::getUserId)).forEach(System.out::println);

public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
    Set<Object> seen = ConcurrentHashMap.newKeySet();
    return t -> seen.add(keyExtractor.apply(t));
}

limit():限制返回流的长度

users.stream().filter(user -> "男".equals(user.getSex())).limit(2).forEach(System.out::println);

Sorted():对流进行排序

users.stream().sorted(Comparator.comparing(User::getAge)).forEach(System.out::println);

Map():对手流中的元素按某种规则进行映射之后的结果

List<Integer> args = Arrays.asList(3, 5, 2, 4, 8, 6);
args.stream().map( i -> i+1).forEach(System.out::println);

2、Stream的结束操作

流的结束操作:allMatch() anyMatch() collect() count() findAny() findFirst() forEach() forEachOrdered() max() min() noneMatch() reduce() toArray()

部分常用操作实例:

forEach():迭代流中的每个数据

users.stream().filter(user -> "男".equals(user.getSex())).forEach(System.out::println);

Count():用来统计流中的元素个数

long count = users.stream().filter(user -> "男".equals(user.getSex())).count();

Collect():就是一个归约操作,可以接受各种做法作为参数,将流中的元素累积成一个汇总结果,collect方法在对List内容进行分组的时候非常实用

//Collectors.toList()
users.stream().filter(user -> "男".equals(user.getSex())).collect(Collectors.toList());
//Collectors.groupingBy:单一条件分组
users.stream().collect(Collectors.groupingBy(User::getSex));
//Collectors.groupingBy:多条件分组
StringBuffer sb = new StringBuffer();
users.stream().collect(Collectors.groupingBy(user -> {
    sb.setLength(0);
    sb.append(user.getSex());
    sb.append(":");
    sb.append(user.getUserName());
    return sb.toString();
}
));

allMatch() anyMatch()  max() min() noneMatch()等其余操作作用与其名称一致, findAny() findFirst() 常在并行时使用

六、Date Time API

在项目中常用传统的Date、Calendar和SimpleDateFormat去管理时间,但是java.util.Date和java.util.Calendar类的API较为复杂易用性差,线程不安全,并且不支持时区。Java 8中新的时间和日期管理API深受第三方库Joda-Time影响,并吸收了很多Joda-Time的精华。新的java.time包包含了所有关于日期、时间、时区、Instant(跟日期类似但是精确到纳秒)、duration(持续时间)和时钟操作的类。新设计的API认真考虑了这些类的不变性(从java.util.Calendar吸取的教训),如果某个实例需要修改,则返回一个新的对象。

1、常用新增时间类操作

Java8常用的日期和时间类包含LocalDate、LocalTime、Instant、Duration、Period、LocalDateTime以及ZonedDateTime等。

LocalDate类:LocalDate类内只包含日期,不包含具体时间。

/**
 * LocalDate的创建:
 * 1、LocalDate.now(),当前日期
 * 2、LocalDate.of(2019,11,11),指定年月日
 * 3、使用Clock方式创建
 * 4、LocalDate.ofYearDay(2019, 200),指定年份和天数
 * 5、LocalDate.parse("2019-11-11"),字符串类型转为LocalDate类型
 */
LocalDate today = LocalDate.of(2019,11,11);
/**
 * 获取LocalDate的年、月、日
 * 1、getYear(),以int类型返回此LocalDate的年
 * 2、getMonth(),Month枚举常量类型LocalDate的月份
 * 3、getMonthValue(),以int类型返回此LocalDate的月
 * 4、getDayOfMonth(),以int类型返回此LocalDate的月
 * 5、其他日期返回值
 */
System.out.println(
        "年:"+today.getYear()+
        " 月:"+today.getMonthValue()+
        " 日:"+today.getDayOfMonth()
);
/**
 * LocalDate的运算
 * plusDays, minusDays,加减天数
 * plusWeeks, minusWeeks,加减星期
 * plusMonths, minusMonths,加减月份
 * plusYears, minusYears,加减年份
 */
System.out.println(
        "加一天:"+ today.plusDays(1)
        + "\n加一星期:"+today.plusWeeks(1)
        + "\n加一月:"+today.plusMonths(1)
        + "\n加一年:"+today.plusYears(1)
);
/**
 * LocalDate的的判断
 * isLeapYear,判断LocalDate指定的年份是否为闰年
 * isAfter, isBefore,检查此LocalDate是在给定日期之后还是之前
 * equals,判断两个日期是否相同
 */
LocalDate tomorrow = LocalDate.of(2019,11,12);
System.out.println("2019-11-11在2019-11-12之前:"+today.isBefore(tomorrow));
/**
 * LocalDate的拷贝
 * withDayOfMonth,返回此LocalDate的拷贝,将月份中的某天设置为给定值
 * withMonth,返回此LocalDate的拷贝,其月份设置为给定值
 * withYear,返回此LocalDate的拷贝,并将年份设置为给定值
 */
LocalDate withYear = today.withYear(2018);
System.out.println(withYear);
/**
 * 日期的格式化:DateTimeFormatter类
 */
String str = today.format(DateTimeFormatter.ISO_LOCAL_DATE);
System.out.println(str);
str = today.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
System.out.println(str);

运行输出结果:

 

LocalTime类:LocalTime和LocalDate类似,区别在于LocalDate只包含日期不包含具体时间,而LocalTime只包含具体时间。LocalTime类的具体方法也与LocalDate相类似。

LocalTime now = LocalTime.now();
System.out.println(now);//14:28:02.526,HH:mm:ss.nnn,这里nnn是纳秒

LocalDateTime类:是LocalDate和LocalTime的组合,既包含日期也包含时间,并且可以与二者进行转换。具体方法也与LocalDate相类似。

LocalDate date = LocalDate.of(2019, 11, 19);
LocalTime time = LocalTime.of(14, 20, 23);
// 合并为 LocalDateTime
LocalDateTime localDateTime = LocalDateTime.of(date, time);
System.out.println(localDateTime);  //2019-11-19T14:20:23
// 转为LocalDate
LocalDate localDate = localDateTime.toLocalDate();
System.out.println(localDate);  //2019-11-19
// 转为 LocalTime
LocalTime localTime = localDateTime.toLocalTime();
System.out.println(localTime);  //14:20:23

其他新增的日期类各有作用,如:

Instant:获取时间戳,获取的时间戳可以精确到纳秒。

Duration:获取时间段,通过between方法创建,可计算两日期之间的天数、小时数、毫秒数等。

Period:获取日期段,与Duration相类似,获取一个时间段,只不过单位为年月日,也可以通过of方法和between方法创建。

ZonedDateTime:用于处理带时区的日期和时间,ZoneId表示不同的时区,大约有40不同的时区。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值