ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Java8新特性的使用

2021-09-10 11:02:58  阅读:162  来源: 互联网

标签:String 特性 Java8 User Female 使用 new SexEnum public


文章目录

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里,这样的链式计算使用是非常频繁的,也是一种非常基础的使用。

标签:String,特性,Java8,User,Female,使用,new,SexEnum,public
来源: https://blog.csdn.net/X_ABU/article/details/120217394

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有