Java8新特性的使用

文章目录

1. Lambda表达式

在Java8开始引入的语法规则,这个属于基础语法,这个语法可以理解成是一个语法糖,是对匿名内部类的包装实现,这是非常重要的使用,在Java8中许多地方使用了这个特性,这个使用一般不能单独使用,要依靠接口函数来实现,上面说了是匿名内部类的包装实现,这个理解对下面的局部变量的理解的非常重要的。

函数式接口要求这个接口只能有一个必须实现的函数,其他方法要么是默认方法,要么是静态方法,用lambda表达式实现的只会是这个必须实现的函数。

表达式组成由()标识参数的接收参数的口子,用->连接函数体{};这里有一个特性,当入参只有一个参数的时候,()可以省略,例如 a->{return a;};如果函数体只有一行,{}也可以省略,如果还有返回值,修饰词return也可以省略,例如:a-> a。

1.1. 四大基础函数式接口

所在路径:java.util.function

1.1.1. Function <T,R> 函数接口

这个接口是以接收T类型的参数,返回R类型的结果,只接受一个参数

@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

由上面的源码,可以知道,这个接口的使用需要实现apply方法,原来使用的方式:

public class TestFunction {
    public static void main(String[] args) {
        Function<String, Integer> function = new Function<String, Integer>() {
            @Override
            public Integer apply(String s) {
                return Integer.valueOf(s);
            }
        };

        System.out.println(function.apply("123"));
    }
}

使用lambda表达式:

public class TestFunction {
    public static void main(String[] args) {
        Function<String, Integer> function = s -> Integer.valueOf(s);
        System.out.println(function.apply("123"));
    }
}

这样大大简化了我们的写法,同时他们是等价的;

1.1.2. Predicate<T> 断言接口

接收一个T类型参数,返回一个布尔型返回值

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

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

通过源码,这个接口实现的是test函数;

原来的写法:

public class TestPredicate {
    public static void main(String[] args) {
        Predicate<String> predicate = new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return StringUtils.isNotBlank(s);
            }
        };
        System.out.println(predicate);
    }
}

使用lambda表达式:

public class TestPredicate {
    public static void main(String[] args) {
        Predicate<String> predicate = s -> StringUtils.isNotBlank(s);
        System.out.println(predicate);
    }
}

这就简化了我们的写法,上下两种是等价的,这个方法可以用来校验。

1.1.3. Consumer<T> 消费者接口

这个接口接收一个T类型的参数,没有返回值:

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

通过上面的源码,要实现的方法是accept.

原来接口的使用:

public class TestConsumer {
    public static void main(String[] args) {
        Consumer<String> consumer = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };
        consumer.accept("测试 consumer");
    }
}

使用lambda表达式:

public class TestConsumer {
    public static void main(String[] args) {
        Consumer<String> consumer = s -> System.out.println(s);
        consumer.accept("测试 consumer");
    }
}

这个消费者接口一般用来处理任务,不用返回值。

1.1.4. Supplier<T> 供应商接口

这是一个无参的接口函数,返回值是T类型。

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

通过源码,这里要实现的是get方法

使用原来的方法来实现:

public class TestSupplier {
    public static void main(String[] args) {
        Supplier<String> supplier = new Supplier<String>() {
            @Override
            public String get() {
                return String.valueOf(Math.random());
            }
        };
        System.out.println(supplier.get());
    }
}

使用lambda表达式:

public class TestSupplier {
    public static void main(String[] args) {
        Supplier<String> supplier = () -> String.valueOf(Math.random());
        System.out.println(supplier.get());
    }
}

这些就是四个标准接口函数,在学习Java8的时候这些是基础的。

1.2. 自定义接口方法

我们可以根据上面的接口来自己定义自己的函数接口,首先定义一个自定义接口,接口要求有三个泛型,两个参数,一个返回值,效果如下;

@FunctionalInterface
interface CustomizeFunction<T, V, R> {
    R customize(T t, V v);
}

自定义接口的使用:

public class TestCustomize {
    public static void main(String[] args) {
        CustomizeFunction<Integer,Float, BigDecimal> customizeFunction = (t,v)->{
            BigDecimal tt = BigDecimal.valueOf(t);
            BigDecimal vv = BigDecimal.valueOf(v);
            return tt.add(vv).setScale(2, RoundingMode.HALF_UP);
        };

        System.out.println(customizeFunction.customize(2,0.2f));
    }
}

这样就完成了对自定义接口的使用了。

2. 函数式参数

说完了函数式接口,在Java里,可以把这些函数当作参数来传入使用,例如多线的Thread类的使用,例子如下:

public static void main(String[] args) {
    new Thread(()->{
        System.out.println(Thread.currentThread().getName());
    }).start();
}

从这里可以看到Thread接收的是一个lambda表达式,这样的表达式是可以当作参数使用的;这里理解也是,比较简单的,上面说到这个表达式是为了处理匿名类的,我们把上面的写法拆开看;

public class test {
    public static void main(String[] args) {
        Runnable runnable = () -> {
            System.out.println(Thread.currentThread().getName());
        };
        new Thread(runnable).start();
    }
}

这样就可以看出,本质传递的就是一个对象,但是上面的写法表象就是传递了一个表达式,一个函数。

这里我们使用自定义接口来测试。

public class TestCustomize {
    public static void main(String[] args) {

        CustomizeClass<Integer, Float, BigDecimal> integerFloatCustomizeClass = new CustomizeClass<>();

        integerFloatCustomizeClass.task((t, v) -> {
            BigDecimal tt = BigDecimal.valueOf(t);
            BigDecimal vv = BigDecimal.valueOf(v);
            return tt.add(vv).setScale(2, RoundingMode.HALF_UP);
        }, 2, 1.11f);
    }
}

@FunctionalInterface
interface CustomizeFunction<T, V, R> {
    R customize(T t, V v);
}

class CustomizeClass<T, V, R> {
    void task(CustomizeFunction<T, V, R> c, T t, V v) {
        System.out.println(c.customize(t, v));
    }
}

从上面可以知道,定义了一个自定义类,里面有一个方法,这个方法有三个参数,一个是自定义的函数式接口,两个运行参数,这两个参数又是函数式参数的入参,然后调用这个函数式参数的方法,完成任务,打印计算结果。

3. Stream流运算和链式编程

在Java8中使用流元素对数据进行操作,可以快速完成运算结果,这里要知道一个点,就是操作是对数据计算的,并不会改变数据本身,调用赋值方法除外。下面给个例子:

public class testA {
    public static void main(String[] args) {
        List<User> list = Arrays.asList(
                new User("1", User.SexEnum.Female, 12)
                , new User("2", User.SexEnum.Female, 22)
                , new User("3", User.SexEnum.Female, 17)
                , new User("4", User.SexEnum.Female, 32)
                , new User("5", User.SexEnum.Female, 45)
                , new User("6", User.SexEnum.Female, 23)
                , new User("7", User.SexEnum.Female, 14)
                , new User("8", User.SexEnum.Female, 11));

        list.stream().map(e -> e.setSex(User.SexEnum.Male)).forEach(System.out::println);
        list.forEach(System.out::println);
    }
}

这里可以对List的数据进行批量赋值,上面我们说了,链式是不能改变数据源的,但是后面却改变了,是不对吗,当然不是,这里要知道,赋值不是流操作导致的,是User的set方法导致的,这个一定要区分出来,否则后面会难以理解一些操作,例如:

public class testA {
    public static void main(String[] args) {
        List<User> list = Arrays.asList(
                new User("1", User.SexEnum.Female, 12)
                , new User("2", User.SexEnum.Female, 22)
                , new User("3", User.SexEnum.Female, 17)
                , new User("4", User.SexEnum.Female, 32)
                , new User("5", User.SexEnum.Female, 45)
                , new User("6", User.SexEnum.Female, 23)
                , new User("7", User.SexEnum.Female, 14)
                , new User("8", User.SexEnum.Female, 11));

        list.stream().filter(e -> e.getAge() >= 18).forEach(e -> System.out.println("stream" + e));
        list.forEach(e -> System.out.println("list" + e));
    }
}

这个操作是通过筛选来处理年龄大于等于18的操作,操作结果:

streamUser{name='2', sex=Female, age=22}
streamUser{name='4', sex=Female, age=32}
streamUser{name='5', sex=Female, age=45}
streamUser{name='6', sex=Female, age=23}
listUser{name='1', sex=Female, age=12}
listUser{name='2', sex=Female, age=22}
listUser{name='3', sex=Female, age=17}
listUser{name='4', sex=Female, age=32}
listUser{name='5', sex=Female, age=45}
listUser{name='6', sex=Female, age=23}
listUser{name='7', sex=Female, age=14}
listUser{name='8', sex=Female, age=11}

从上面可以知道,数据源没有变化,输出结果是处理后的,这里就要注意,要使用处理后的结果,一定要注意接收。

上面不难看出,这些操作十分类似于SQL语法,所以我们可以通过这个操作来处理流数据,因为优化的缘故,流运算效果要比普通的逻辑写法效率高很多。同时这里也看出来,这里的许多写法使用到了上面的lambda表达式,和函数式参数,同时通过.来连接的编程方式,被称为链式编程。

下面简单的介绍一些常用的方法。

3.1. map 和 forEach、flatMap

这两个方法使用的比较高,两个都有遍历的效果,但是也有不同,他们的入参不同导致了性质不同:

3.1.1. map

这个方法的入参是这样的

<R> Stream<R> map(Function<? super T,? extends R> mapper)  

这个不难看出,这个方法是有返回值的,同时他的返回结果是一个流数据。遍历后处理的结果集合会被返回。

3.1.2. forEach

这个方法如下:

void forEach(Consumer<? super T> action)  

这个可以看出,方法是没有返回值的,同时对处理结果不会收集,使用的是消费者接口。

通过上面可以看出一个有对返回的操作收集结果,一个不会。

3.1.3. flatMap

这个方法如下:

<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)  

这个方法也会一个一个遍历,同时还会把处理结果返回出来:但是看map会发现函数式参数返回的结果是不同的,一个返回的是元素,一个返回的是集合,表现如下,也就是说map无论返回的是什么都当作一个元素里替换原来的值,flatMap无论返回的是什么,都当做集合来合并原来的集合,然后剔除原来的数。下面一个例子来说明:

public class testB {
    public static void main(String[] args) {
        List<Integer> list1 = Arrays.asList(1,2,3,4);

        System.out.println(JSON.toJSONString(list1.stream().map(e -> {
            List<Integer> list2 = new ArrayList<>();
            list2.add(e);
            list2.add(e + 10);
            return list2;
        }).collect(Collectors.toList())));

        System.out.println(JSON.toJSONString(list1.stream().flatMap(e -> {
            List<Integer> list2 = new ArrayList<>();
            list2.add(e);
            list2.add(e + 10);
            return list2.stream();
        }).collect(Collectors.toList())));
    }

}

为了好看出关系,这里用JSON来输出:

[[1,11],[2,12],[3,13],[4,14]]
[1,11,2,12,3,13,4,14]

这样就好理解了,例如1来说,map是把返回的值作为元素来替换1的,从对象类型理解就是用List替换原来Integer类型的值,但是flatMap是把值插入了原来的位置,是集合的合并。

一个是单纯的替换,一个是数据的扁平化处理。

3.2. filter

这个方法是筛选功能:

Stream<T> filter(Predicate<? super T> predicate)  

这个方法会遍历流数据,把符合条件的数据组建成新的集合,这个入参的断言接口函数;

public class testB {
    public static void main(String[] args) {
        List<Integer> list1 = Arrays.asList(1,2,3,4);
        System.out.println(JSON.toJSONString(list1.stream().filter(e->e>2).collect(Collectors.toList())));
    }
}

返回结果:

[3,4]

3.3. limit

方法如下,是限制数量的函数,类似于mysql的limit,但是这个参数只有一个参数:

Stream<T> limit(long maxSize)  

使用方法:

public class testB {
    public static void main(String[] args) {
        List<Integer> list1 = Arrays.asList(1,2,3,4);
        System.out.println(JSON.toJSONString(list1.stream().limit(2).collect(Collectors.toList())));
    }
}

输出结果:

[1,2]

3.4. sorted

这个方法用来排序使用的。无参以自然排序,有参的可以使用比较大小的参数来比较

Stream<T> sorted()  
Stream<T> sorted(Comparator<? super T> comparator)  

案例如下:

public class testB {
    public static void main(String[] args) {
        List<Integer> list1 = Arrays.asList(1,5,2,6,3,4);
        System.out.println(JSON.toJSONString(list1.stream().sorted().collect(Collectors.toList())));
        System.out.println(JSON.toJSONString(list1.stream().sorted((e1,e2)->e2.compareTo(e1)).collect(Collectors.toList())));
    }
}

运行结果:

[1,2,3,4,5,6]
[6,5,4,3,2,1]

但是推荐使用Comparator的方法实现,这个也是推荐的方法:

public class testB {
    public static void main(String[] args) {
        List<Integer> list1 = Arrays.asList(1,5,2,6,3,4);
        System.out.println(JSON.toJSONString(list1.stream().sorted(Comparator.naturalOrder()).collect(Collectors.toList())));
        System.out.println(JSON.toJSONString(list1.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList())));
    }
}

这样也可以实现,同时推荐使用这个方法,实现正反排序。当然自定义也是可以的,例如先排序奇数再排序偶数,这就要自定义了。

public class testB {
    public static void main(String[] args) {
        List<Integer> list1 = Arrays.asList(1,5,2,6,3,4);
        System.out.println(JSON.toJSONString(list1.stream().sorted((e1,e2)->{
            Integer ee1;
            Integer ee2;
            if (e1%2==0){
                ee1 = e1+10;
            }else {
                ee1 = e1- 10;
            }
            if (e2%2==0){
                ee2 = e2+10;
            }else {
                ee2 = e2- 10;
            }
            return ee1.compareTo(ee2);
        }).collect(Collectors.toList())));

    }
}

结果如下:

[1,3,5,2,4,6]

其他的高级使用就不去一个个介绍了。

3.5. 综合案例

有一组用户信息,先男女性别互换,获取年龄大于18岁的,按年龄排序倒序,按性别分组;

测试代码:

public class testA {
    public static void main(String[] args) {
        List<User> list = Arrays.asList(
                new User("1", User.SexEnum.Female, 12)
                , new User("2", User.SexEnum.Female, 22)
                , new User("3", User.SexEnum.Female, 17)
                , new User("4", User.SexEnum.Male, 32)
                , new User("5", User.SexEnum.Female, 45)
                , new User("6", User.SexEnum.Male, 23)
                , new User("7", User.SexEnum.Male, 14)
                , new User("8", User.SexEnum.Male, 11));

        System.out.println(JSON.toJSONString(
                list.stream().map(e->e.setSex(User.SexEnum.Female.equals(e.getSex())?User.SexEnum.Male:User.SexEnum.Female))
                        .filter(e->e.getAge()>18)
                        .sorted((e1,e2)->e2.getAge().compareTo(e1.getAge()))
                        .collect(Collectors.groupingBy(User::getSex))
        ));
    }
}

测试结果如下:

{
    "Male": [
        {
            "age": 45, 
            "name": "5", 
            "sex": "Male"
        }, 
        {
            "age": 22, 
            "name": "2", 
            "sex": "Male"
        }
    ], 
    "Female": [
        {
            "age": 32, 
            "name": "4", 
            "sex": "Female"
        }, 
        {
            "age": 23, 
            "name": "6", 
            "sex": "Female"
        }
    ]
}

这样就完成了案例的测试了。在Java8里,这样的链式计算使用是非常频繁的,也是一种非常基础的使用。

上一篇:快速掌握java8新特性


下一篇:Java8新特性-Lambda表达式,被spring相关问题虐哭了