关于java8:Java-亿级项目架构设计与落地应用海上升明月

download:Java 亿级我的项目架构设计与落地利用Java是当今最为风行的编程语言之一,被宽泛用于大型项目的开发。对于亿级我的项目,架构设计至关重要,因为它波及到高可用性、伸缩性和安全性等方面。在本文中,咱们将探讨Java亿级我的项目的架构设计与落地利用,并重点介绍H2数据库。 架构设计在设计亿级我的项目的架构时,须要思考以下几个方面: 高可用性对于一个亿级我的项目,必须保证系统可能24/7稳固运行,即便呈现故障也应该可能主动复原。为此,能够采纳多节点部署、负载平衡和容错机制等形式来进步零碎的可用性。 高伸缩性因为亿级我的项目通常须要解决海量数据和用户申请,因而须要具备高伸缩性,即可能疾速扩大节点以满足业务需要。这能够通过分布式架构、微服务架构和云计算技术等实现。 安全性对于任何一个大型项目来说,数据安全都是至关重要的问题。因而,在设计亿级我的项目的架构时,必须思考数据的安全性,包含数据加密、权限管制和破绽修复等方面。 H2数据库H2是一款轻量级的Java数据库,具备高性能、嵌入式和分布式反对等特点,非常适合于亿级我的项目的架构设计。 高性能H2的查问速度十分快,能够通过应用索引和缓存来优化性能。此外,H2能够在内存中运行,从而进一步提高性能。 嵌入式反对H2能够作为一个嵌入式数据库应用,这意味着应用程序能够间接拜访数据库,而无需应用独立的数据库服务器。这不仅不便了开发人员,还能够缩小系统资源的占用。 分布式反对对于亿级我的项目,通常须要采纳分布式架构来实现高伸缩性。H2反对分布式部署,能够将数据库扩散到多个节点上,从而实现数据的摊派和负载平衡。 落地利用在理论我的项目中,咱们能够通过以下形式将Java亿级我的项目的架构设计落地利用: 确定业务需要首先,须要明确我的项目的业务需要和指标,以确定所需的架构设计和技术栈。 抉择适当的技术栈依据业务需要和指标,抉择适当的技术栈,包含开发框架、数据库和其余相干技术。 实现架构设计在确定业务需要和技术栈后,能够开始实现架构设计。这通常包含开发底层框架、搭建零碎基础设施、设计数据库架构等。 测试和优化一旦实现了架构设计,就须要对系统进行测试和优化,以确保零碎的稳定性、性能和安全性等方面。 总结在设计Java亿级我的项目的架构时,须要思考高可用性、高伸缩性和安全性等方面。H2数据库是一款非常适合于亿级我的项目的轻量级数据库,具备高性能、嵌入式和分布式反对等特点。通过抉择适当的技术栈和实现架构设计,能够将Java亿级我的项目的架构落地利用,并实现高效、稳固、平安的零碎。

May 19, 2023 · 1 min · jiezi

关于java8:java8-map新特性

如果应用map计数,当map中不存在这个key时,map.put(key, map.getOrDefault(key, 0) + 1);能够应用merge,更优雅的实现.代码如下: @Testpublic void merge() { Integer key = 4; Map<Integer, Integer> map = new HashMap<>(); map.put(3, map.getOrDefault(3, 0) + 1); map.merge(key, 1, Integer::sum); System.out.println(map.get(3)); System.out.println(map.get(key)); Map<Integer, String> map1 = new HashMap<>(); map1.merge(4, "123", String::concat); System.out.println(map1.get(key)); // 待定 如何实现呢? 参考:computeIfAbsent Map<Integer, Set<String>> map2 = new HashMap<>(); // map2.merge(4, "123", Set::add); System.out.println(map2.get(key));}输入: 对于下面代码中待定的问题,能够参考下列的代码: @Testpublic void computeIfAbsent() { Integer key1 = 4; Map<Integer, Set<String>> map = new HashMap<>(); map.computeIfAbsent(key1, HashSet::new).add("456"); Integer key = 3; Set<String> set = new HashSet<>(); set.add("123"); map.put(key, set); map.computeIfAbsent(key, HashSet::new).add("123456"); System.out.println(map);}输入:如果有其余更优雅的写法,欢送留言交换. ...

March 16, 2023 · 1 min · jiezi

关于java8:京东云开发者|深入JDK中的Optional

概述:Optional最早是Google公司Guava中的概念,代表的是可选值。Optional类从Java8版本开始退出奢华套餐,次要为了解决程序中的NPE问题,从而使得更少的显式判空,避免代码净化,另一方面,也使得畛域模型中所暗藏的常识,得以显式体现在代码中。Optional类位于java.util包下,对链式编程格调有肯定的反对。实际上,Optional更像是一个容器,其中寄存的成员变量是一个T类型的value,可值可Null,应用的是Wrapper模式,对value操作进行了包装与设计。本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会呈现Optioanl办法之间的比照,实际,误用状况剖析,优缺点等。与大家一起,对这项Java8中的新个性,进行了解和深刻。 1、解决的问题臭名远扬的空指针异样,是每个程序员都会遇到的一种常见异样,任何拜访对象的办法与属性的调用,都可能会呈现NullPointException,如果要确保不触发异样,咱们通常须要进行对象的判空操作。 举个栗子,有一个人(Shopper)进超市购物,可能会用购物车(Trolley)也可能会用其它形式,购物车里可能会有一袋栗子(Chestnut),也可能没有。三者定义的代码如下: PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPythonpublic class Shopper { private Trolley trolley; public Trolley getTrolley(){ return trolley; }}public class Trolley { private Chestnut chestnut; public Chestnut getChestnut(){ return chestnut; }}public class Chestnut { private String name; public String getName(){ return name; }}这时想要取得购物车中栗子的名称,像上面这么写,就可能会见到咱们的“老朋友”(NPE) PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPythonpublic String result(Shopper shopper){ return shopper.getTrolley().getChestnut().getName();}为了能避免出现空指针异样,通常的写法会逐层判空(多层嵌套法),如下 PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPythonpublic String result(Shopper shopper) { if (shopper != null) { Trolley trolley = shopper.getTrolley(); if (trolley != null) { Chestnut chestnut = trolley.getChestnut(); if (chestnut != null) { return chestnut.getName(); } } } return "获取失败辽"; }多层嵌套的办法在对象级联关系比拟深的时候会看的目迷五色的,尤其是那一层一层的括号;另外出错的起因也因为不足对应信息而被含糊(例如trolley为空时也只返回了最初的获取失败。当然也能够在每一层减少return,相应的代码有会变得很简短),所以此时咱们也能够用遇空则返回的卫语句进行改写。 ...

November 7, 2022 · 6 min · jiezi

关于java8:java8的stream将一个List转为按照某个字段分组的map再按照另一个字段取max最终得到一个map

java8的stream将一个List转为依照某个字段分组的map,(Map<String, List<Owner>>)而后再依照 更新日期 字段取分组的每个list里最大的那个,Map<String, Owner>最终失去一个map List<Owner> ---> (Map<String, List<Owner>>) ----> Map<String, Owner>1. Owner对象构造import com.baomidou.mybatisplus.annotation.*;import lombok.AllArgsConstructor;import lombok.Builder;import lombok.Data;import lombok.NoArgsConstructor;import java.io.Serializable;import java.util.Date;@Builder@AllArgsConstructor@NoArgsConstructor@Datapublic class Owner implements Serializable { /** * 成员名称 */ private String name; /** * 身份证号 */ private String idcard; /** * 创立工夫 */ private Date createTime; /** * 更新工夫 */ private Date updateTime;}2. 处理过程 @Test void testList() throws ParseException { List<Owner> list = new ArrayList<>(); // 获得字符串示意的Date对象 Date d1 = DateUtils.toDate("1662-05-04 22:22:22"); Date d2 = DateUtils.toDate("0599-01-23 00:00:00"); Date d3 = DateUtils.toDate("1328-10-21 11:11:11"); Owner o1 = Owner.builder().idcard("1001").updateTime(d1).name("康熙").build(); Owner o2 = Owner.builder().idcard("1001").updateTime(d2).name("李世民").build(); Owner o3 = Owner.builder().idcard("1001").updateTime(d3).name("朱元璋").build(); Owner o4 = Owner.builder().idcard("1002").updateTime(d1).name("张三").build(); Owner o5 = Owner.builder().idcard("1003").updateTime(d1).name("李四").build(); list.add(o1); list.add(o2); list.add(o3); list.add(o4); list.add(o5); // 将List依照Owner对象的 idcard 字段分组 失去map Map<String, List<Owner>> listMap = list.stream().collect(Collectors.groupingBy(Owner::getIdcard)); // java8 Stream max函数应用的比拟器:依照updateTime字段比拟 Comparator<Owner> comparator = Comparator.comparing(Owner::getUpdateTime); // Map转换,获得每个idcard的最初更新的对象(多取一) Map<String, Owner> resultMap = new HashMap<>(); listMap.entrySet().stream().forEach(e-> { Owner owner = e.getValue().stream().max(comparator).get(); resultMap.put(e.getKey(), owner); }); // 最终失去 每个idcard一个对象的map System.out.println(GsonUtils.toGson(resultMap));3. 最终后果:{ "1003":{ "name":"李四", "idcard":"1003", "updateTime":"1662-05-04 22:22:22" }, "1002":{ "name":"张三", "idcard":"1002", "updateTime":"1662-05-04 22:22:22" }, "1001":{ "name":"康熙", "idcard":"1001", "updateTime":"1662-05-04 22:22:22" }}

July 4, 2022 · 1 min · jiezi

关于java8:java8-lambda和Stream-API

java8lambdalambda表达式可作为参数传递给办法应用@FunctionalInterface 自定义函数式接口将接口作为参数传递给办法调用办法时传递lambda表达式java 内置心函数式接口Consumer<T> 消费性接口 泛型是参数 无返回值 调用办法是 void accept (T t)Supplier<T> 供应型接口 无参数 泛型是返回值 调用办法是 T get()Function<T,R> 函数型接口 T泛型为参数 R泛型为返回值 调用办法是 R apply(T t)Predicate<T> 断言式接口 泛型是参数 返回boolean值 调用办法是 boolean test(T t)Stream API创立Stream -> 两头操作 ->终止操作 从一个流转化成另一个流创立Stream list.stream、Array.stream(args[])、stream.of(values ...)、stream.iterate(final T seed, final UnaryOperator<T>f)两头操作:distinct()、 limit(long l)、 skip(long l)、 filter(Predicate p)、 map(Function f)、 flatMap(Function f) 、sorted()(Comparator c)终止操作:allMatch(Predicate p)、anyMatch(Predicate p)、findFirst()、finAny()、count()、max()、min()、reduce(T iden, BinaryOperator b) (BinaryOperator b)collect(Collector c) parallel()与sequential() 并行流与程序流

June 13, 2022 · 1 min · jiezi

关于java8:java8实战学习

[toc] java8实战学习1. lambda表达式1.1 什么是lambda表达式1.2 什么样的场景能应用lambda表达式1.3 lambda表达式实现一个接口的四种写法2. 函数式编程2.1 什么是函数式编程2.2 什么是命令式编程2.3 什么是函数式接口 FunctionalInterface2.4 什么是 default 办法2.5 default办法的意义2.6 java8内置的罕用函数式接口(1). Predicate<T>断言 -> 输出T, 输入 boolean (2). Consumer<T>生产一个输出 -> 输出T, 不输入(void) (3). Supplier<T>生产一个输入 -> 不输出, 输入T (4). Function<T, R>输出T, 输入R的函数 (5). UnaryOperator<T>一元函数: 输出1个输入 1个: 类型都是T (6). BinaryOperatior<T>二元函数: 输出2个输入1个: 类型都是T (7). BiFunction<T, U, R>输出两个输入一个: 输出 T, U 输入 R, 罕用于 reduce/sort等操作, 2.7 办法援用(1). 静态方法的办法援用(2). 实例办法的办法援用(3). 构造方法的办法援用2.8. 变量援用和隐式final为何外部类应用内部变量要是final的3. stream流式编程3.1. 内部迭代和外部迭代(1). 什么是内部迭代(2). 什么是外部迭代3.2. 两头操作/终止操作/惰性求值(1). 什么是两头操作?返回还是流stream的操作, 就是两头操作, 例如 map操作 ...

May 17, 2022 · 3 min · jiezi

关于java8:Java8-判空新写法

引言在文章的结尾,先说下NPE问题,NPE问题就是,咱们在开发中常常碰到的NullPointerException.假如咱们有两个类,他们的UML类图如下图所示 图片 在这种状况下,有如下代码 user.getAddress().getProvince();这种写法,在user为null时,是有可能报NullPointerException异样的。为了解决这个问题,于是采纳上面的写法 if(user!=null){ Address address = user.getAddress(); if(address!=null){ String province = address.getProvince(); }}这种写法是比拟俊俏的,为了防止上述俊俏的写法,让俊俏的设计变得优雅。JAVA8提供了Optional类来优化这种写法,接下来的注释局部进行具体阐明 一个连载多年还在持续更新的收费教程:http://blog.didispace.com/spr... API介绍先介绍一下API,与其余文章不同的是,本文采取类比的形式来讲,同时联合源码。而不像其余文章一样,一个个API列举进去,让人找不到重点。 1、Optional(T value),empty(),of(T value),ofNullable(T value)这四个函数之间具备相关性,因而放在一组进行记忆。 先阐明一下,Optional(T value),即构造函数,它是private权限的,不能由内部调用的。其余三个函数是public权限,供咱们所调用。那么,Optional的实质,就是外部贮存了一个实在的值,在结构的时候,就直接判断其值是否为空。好吧,这么说还是比拟形象。间接上Optional(T value)构造函数的源码,如下图所示 图片 那么,of(T value)的源码如下 public static <T> Optional<T> of(T value) { return new Optional<>(value);}也就是说of(T value)函数外部调用了构造函数。依据构造函数的源码咱们能够得出两个论断: 通过of(T value)函数所结构出的Optional对象,当Value值为空时,仍然会报NullPointerException。通过of(T value)函数所结构出的Optional对象,当Value值不为空时,能失常结构Optional对象。除此之外呢,Optional类外部还保护一个value为null的对象,大略就是长上面这样的 public final class Optional<T> { //省略.... private static final Optional<?> EMPTY = new Optional<>(); private Optional() { this.value = null; } //省略... public static<T> Optional<T> empty() { @SuppressWarnings("unchecked") Optional<T> t = (Optional<T>) EMPTY; return t; }}那么,empty()的作用就是返回EMPTY对象。 ...

April 29, 2022 · 2 min · jiezi

关于java8:Java-之父呼吁弃用-Java-8苹果手机或将改用-USBC-充电器Nodejs-18-发布-思否周刊

40s 新闻速递2023 年 4 月 11 日之后微软将不再为 Office 2013 提供安全更新因不附带充电器,苹果在巴西被判抵偿消费者 7000 元微软或在 Xbox 收费游戏中放广告 最快第三季度启用平安专家发现新型歹意 Windows 11 网站美国上诉法院裁决 Web 抓取非法465 亿美元融资承诺函到手 马斯克思考对 Twitter 提出收买要约欧盟将在所有智能手机中应用 USB-C 充电器,包含苹果被罚 1.5 亿欧元后 Google 发表启用全新 Cookies 同意书macOS 服务器利用进行服务 现有用户可在持续下载应用Java 之父呐喊用户弃用 Java 8Visual Studio 2019 v16.11.13 正式公布eGuideDog Linux 0.9 公布Ubuntu 22.04 LTS (Jammy Jellyfish) 已公布Node.js 18 公布W3C 公布 WebAssembly 2.0 初版草案行业资讯2023 年 4 月 11 日之后微软将不再为 Office 2013 提供安全更新据微软官方消息,在 2023 年 4 月 11 日之后微软将不再为 Office 2013 和 Skype for Business 2015 提供安全更新。 ...

April 23, 2022 · 2 min · jiezi

关于java8:ListT-转-MapK-T通用方法

咱们开发过程中常常遇到把List<T>转成map对象的场景,同时须要对key值雷同的对象做个合并,lambda曾经做得很好了。定义两个实体类别离命名为A、B。 @Dataclass A { private String a1; private String a2; private String a3; public A(String a1, String a2, String a3) { this.a1 = a1; this.a2 = a2; this.a3 = a3; }}@Dataclass B { private String b1; private String b2; private String b3; public B(String b1, String b2, String b3) { this.b1 = b1; this.b2 = b2; this.b3 = b3; }}lambda转换代码: @Testpublic void test1() { List<A> aList = new ArrayList<>(); aList.add(new A("a1", "a21", "a3")); aList.add(new A("a1", "a22", "a3")); aList.add(new A("a11", "a23", "a3")); aList.add(new A("a11", "a24", "a3")); aList.add(new A("a21", "a25", "a3")); System.out.println(aList); Map<String, A> tagMap = CollectionUtils.isEmpty(aList) ? new HashMap<>() : aList.stream().collect(Collectors.toMap(A::getA1, a -> a, (k1, k2) -> k1)); System.out.println("----------------------"); System.out.println(tagMap); System.out.println("----------------------");}能不能把转换的过程提取成公共办法呢?我做了个尝试,公共的转换方法如下: ...

April 19, 2022 · 2 min · jiezi

关于java8:谈谈Java818引入的新特性

Java8于2014年3月18日公布,截止到2022年4月6日,以后最新发行版本是Java18。版本17、11和8是目前反对的长期反对(LTS)版本。这篇文章率领大家回顾从Java 8 开始每个版本的个性,小板凳坐好,发车了! 版本概览Java8 LTS 上一次商业用途的免费软件公共更新是在2019年1月由 Oracle 公布的,而 Oracle 持续更新并公布收费的公共 java8,用于开发和集体用处。java7不再受公众反对。对于java11,Oracle不会为公众提供长期反对; 相同,更宽泛的 OpenJDK 社区,如 Eclipse Adoptium 或其余社区,将会代替Oracle提供这类的反对。 上图展现了从Java SE 8开始到Java SE 18的历史版本公布过程,秉着2017年9月,Java 平台的首席架构师 Mark Reinhold 提议将公布列车改为“每六个月公布一个个性”的约定,也给出了将来到Java SE 21的公布布局。 Java18的个别可用性开始于2022年3月22日,最新的(第三次) LTS Java 17开始于2021年9月14日。java19正在开发中,晚期拜访构建曾经可用。 Java 8 个性Lambda 表达式容许咱们应用函数作为办法参数。 让咱们来看看 Java 8之前的代码,过后咱们必须创立一个匿名类来实现一个简略的接口。 Thread t = new Thread(new Runnable() { public void run() { System.out.println("Start thread"); }});复制代码应用 lambda 表达式,咱们能够这样做。 Thread t1 = new Thread(() -> { System.out.println("Start 1st thread");});复制代码函数接口是只有一个形象办法的接口。此外,Interface 能够有默认办法和静态方法,但只有一个形象办法。 Stream 是java.util.Stream包中的一个接口,它提供了对汇合执行程序和并行操作的办法。Collection Interface 的 Stream ()办法返回给定汇合的类型为 Stream 的元素流。流接口反对过滤、映射或查找流中元素的聚合后果所需的许多操作。 ...

April 6, 2022 · 2 min · jiezi

关于java8:73万字肝爆Java8新特性我不信你能看完建议收藏

大家好,我是冰河~~ 说实话,肝这篇文章花了我一个月的工夫,对于Java8的新个性全在这儿了,倡议先珍藏后浏览。 Java8有哪些新个性? 简略来说,Java8新个性如下所示: Lambda表达式函数式接口办法援用与结构器援用Stream API接口的默认办法与静态方法新工夫日期API其余新个性其中,援用最宽泛的新个性是Lambda表达式和Stream API。 Java8有哪些长处? 简略来说Java8长处如下所示。 速度更快代码更少(减少了新的语法Lambda表达式)弱小的Stream API便于并行最大化缩小空指针异样OptionalLambda表达式什么是Lambda表达式?Lambda表达式是一个匿名函数,咱们能够这样了解Lambda表达式:Lambda是一段能够传递的代码(可能做到将代码像数据一样进行传递)。应用Lambda表达式可能写出更加简洁、灵便的代码。并且,应用Lambda表达式可能使Java的语言表达能力失去晋升。 匿名外部类在介绍如何应用Lambda表达式之前,咱们先来看看匿名外部类,例如,咱们应用匿名外部类比拟两个Integer类型数据的大小。 Comparator<Integer> com = new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return Integer.compare(o1, o2); }};在上述代码中,咱们应用匿名外部类实现了比拟两个Integer类型数据的大小。 接下来,咱们就能够将上述匿名外部类的实例作为参数,传递到其余办法中了,如下所示。 TreeSet<Integer> treeSet = new TreeSet<>(com);残缺的代码如下所示。 @Testpublic void test1(){ Comparator<Integer> com = new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return Integer.compare(o1, o2); } }; TreeSet<Integer> treeSet = new TreeSet<>(com);}咱们剖析下上述代码,在整个匿名外部类中,实际上真正有用的就是上面一行代码。 return Integer.compare(o1, o2);其余的代码实质上都是“冗余”的。然而为了书写下面的一行代码,咱们不得不在匿名外部类中书写更多的代码。 Lambda表达式如果应用Lambda表达式实现两个Integer类型数据的比拟,咱们该如何实现呢? ...

January 5, 2022 · 23 min · jiezi

关于java8:java8之flatMap应用

@Test public void testFlatMap() { Teacher yuwen1 = Teacher.builder().teacherName("飞老师").level(1).build(); Teacher yuwen2 = Teacher.builder().teacherName("木老师").level(2).build(); Teacher shuxue1 = Teacher.builder().teacherName("算老师").level(3).build(); List<Teacher> teacherList = new ArrayList<>(Arrays.asList(yuwen1, yuwen2)); List<Teacher> teacher2List = new ArrayList<>(Arrays.asList(shuxue1)); Student zhangsan = Student.builder().name("张三").gender("男").teacherList(teacherList).build(); Student lisi = Student.builder().name("李四").gender("男").teacherList(teacherList).build(); Student wangwu = Student.builder().name("王五").gender("女").teacherList(teacher2List).build(); List<Student> students = new ArrayList<>(Arrays.asList(zhangsan, lisi, wangwu)); Set<Teacher> allStudentsTeachers = students.stream().flatMap(e -> e.getTeacherList().stream()).collect(Collectors.toSet()); allStudentsTeachers.stream().forEach(System.out::println); }留神是: e -> e.getTeacherList().stream()留神 外部list还要被"化骨绵掌"击为stream!!!

December 17, 2021 · 1 min · jiezi

关于java8:JAVA8新方法也不新啦我们老啦

就记录在这吧,尽管这个名字叫JAVA8新办法,然而实际上JAVA8一点也不新啦,次要是咱们老了list的removeIf办法 List<String> list1 = Arrays.asList("one","two","three","four","five","six","seven");List<String> list2 = new ArrayList<>(list1);//删除boolean result = list2.removeIf(s -> s.length() > 4);System.out.println(result);System.out.println(list2.stream().collect(Collectors.joining(",")));//输入trueone,two,four,five,six//替换list2.replaceAll(s->s.toUpperCase());System.out.println(list2.stream().collect(Collectors.joining(",")));//输入ONE,TWO,FOUR,FIVE,SIX//排序list2.sort(Comparator.naturalOrder());System.out.println(list2.stream().collect(Collectors.joining(",")));//输入FIVE,FOUR,ONE,SIX,TWO比拟 Comparator<Person> comparator = Comparator.comparing(Person::getName).thenComparing(Person::getAge);//反向比拟Comparator<Person> comparatorReversed = comparator.reversed();//默认字母程序Comparator<String> comparator1 = Comparator.naturalOrder();//先按空后再依照字母程序Comparator<String> comparator2 = Comparator.nullsFirst(Comparator.naturalOrder());//先依照字母程序,最初排空Comparator<String> comparator3 = Comparator.nullsLast(Comparator.naturalOrder());//比拟Long max = Long.max(1L,2L);BinaryOperator<Long> sum = (s1,s2) -> s1 + s2;sum = Long::sum;hashCode Long l = 2234324234234324L;int hash = l.hashCode();System.out.println(hash);hash = Long.hashCode(l);System.out.println(hash);//输入642554319642554319Map forEach,getOrDefault,putIfAbsent Map<String,Object> map =new HashMap<>();map.put("key","value");map.forEach((key,value)-> System.out.println(key+" "+value));//输入key value//getOrDefaultPerson deafulPerson = new Person();deafulPerson.setName("Test");Person p = (Person) map.getOrDefault("p",deafulPerson);System.out.println(p.getName());//输入TestPerson p1 = new Person();p1.setName("Test11");//putIfAbsentmap.putIfAbsent("p", p);System.out.println("putIfAbsent=="+map.get("p"));//替换key为p的值为p1map.replace("p", p1);System.out.println("replace=="+map.get("p"));//替换key值为p的为defaultPeronmap.replace("p", p1,deafulPerson);System.out.println("replaceNew=="+map.get("p"));//lambda替换map.replaceAll((key,oldPerson) -> p1);System.out.println("replaceAll=="+map.get("p"));//输入putIfAbsent==Person{name='Test', age=0}replace==Person{name='Test11', age=0}replaceNew==Person{name='Test', age=0}replaceAll==Person{name='Test11', age=0}

October 25, 2021 · 1 min · jiezi

关于java8:ConcurrentHashMap源码深度分析

1、根本变量// Unsafe mechanics//Unsafe类是用来做cas操作的,都是native办法,代码由C++实现,上面的变量示意对应变量的偏移//例如:public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);// 示意对象o偏移地位offset的变量如果和期望值expected相等,把变量值设置为xprivate static final sun.misc.Unsafe U;private static final long SIZECTL;private static final long TRANSFERINDEX;private static final long BASECOUNT;private static final long CELLSBUSY;private static final long CELLVALUE;private static final long ABASE;private static final int ASHIFT;1、table初始化1、table会提早到第一次put时初始化,同过应用循环+CAS的套路,能够保障一次只有一个线程会初始化table。2、在table为空的时候如果sizeCtl小于0,则阐明曾经有线程开始初始化了,其它线程通过Thread.yield()让出CPU工夫片,期待table非空即可。3、否则应用CAS将sizeCtl的值换为-1,置换胜利则初始化table。4、留神table的大小为sizeCtl,初始化后将sizeCtl的值设为n - (n >>> 2)即0.75n,这个值用来确定是否须要为table扩容。 //Initializes table, using the size recorded in sizeCtl.private final Node<K,V>[] initTable() { Node<K,V>[] tab; int sc; while ((tab = table) == null || tab.length == 0) { //判断是否曾经有线程在初始化,如果有则让出CPU,之后持续自旋判断 if ((sc = sizeCtl) < 0) Thread.yield(); // lost initialization race; just spin //cas操作设置sizeCtl的值 else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { try { //持续判断双重查看 if ((tab = table) == null || tab.length == 0) { int n = (sc > 0) ? sc : DEFAULT_CAPACITY; @SuppressWarnings("unchecked") //初始化table Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; table = tab = nt; sc = n - (n >>> 2);//设置sizeCtl=0.75n } } finally { sizeCtl = sc; } break; } } return tab;}2、put操作(putVal办法)1、首先计算key的hash值2、判断table是否为空,如果是就初始化3、依据hash值取余确定桶的地位,并判断桶是否为空,如果是空,通过cas操作设置进去4、如果桶的第一个节点非空,并且hash=MOVED,阐明有线程正在进行扩容,调用helpTransfer帮忙扩容5、对桶加锁并判断节点是链表还是树,依据不同状况插入节点6、判断是否达到链表转树的阈值7、统计节点数 ...

August 12, 2021 · 6 min · jiezi

关于java8:JAVA8实战-日期API

JAVA8实战 - 日期API前言 这一节咱们来讲讲JAVA8的日期类,源代码的作者其实就是Joda-Time,所以能够看到很多代码的API和Joda类比拟像。日期类始终是一个比拟难用的货色,然而JAVA8给日期类提供了一套新的API让日期类更加好用。 本文代码较多,倡议亲自运行代码了解。 内容概述:对于JDK8日期的三个外围类:LocalDate、LocalTime、LocalDateTime的相干介绍机器工夫和日期格局Instant等对于细粒度的工夫操作介绍TemporalAdjusters 用于更加简单的日期计算,比方计算下一个工作日的时候这个类提供了一些实现DateTimeFormatter 格式化器,十分的灵便多变,属于SimpleDateFormat的替代品。日期API的一些集体工具封装举例,以及在应用JDK8的时候一些集体的踩坑 最初心愿通过本文能帮你解脱new Date() 什么是ISO-8601? 日期离不开ISO-8601,上面对ISO-8601简略形容一下,参考自百度百科: ISO-8601: 国际标准化组织制订的日期和工夫的示意办法,全称为《数据存储和替换模式·信息替换·日期和工夫的示意办法》,简称为ISO-8601。日的示意:小时、分和秒都用2位数示意,对UTC工夫最初加一个大写字母Z,其余时区用理论工夫加时差示意。如UTC工夫下午2点30分5秒示意为14:30:05Z或143005Z,过后的北京工夫示意为22:30:05+08:00或223005+0800,也能够简化成223005+08。日期和工夫的组合示意:合并示意时,要在工夫后面加一大写字母T,如要示意北京工夫2004年5月3日下午5点30分8秒,能够写成2004-05-03T17:30:08+08:00或20040503T173008+08。LocalDate、LocalTime、LocalDateTime JDK8把工夫拆分成了三个大部分,一个是工夫,代表了年月日的信息,一个是日期,代表了时分秒的局部,最初是这两个对象总和具体的工夫。 LocalDate LocalDate:类示意一个具体的日期,但不蕴含具体工夫,也不蕴含时区信息。能够通过LocalDate的静态方法of()创立一个实例,LocalDate也蕴含一些办法用来获取年份,月份,天,星期几等,上面是LocalDate的常见应用形式: @Test public void localDateTest() throws Exception { // 创立一个LocalDate: LocalDate of = LocalDate.of(2021, 8, 9); // 获取以后工夫 LocalDate now = LocalDate.now(); // 格式化 LocalDate parse1 = LocalDate.parse("2021-05-11"); // 指定日期格式化 LocalDate parse2 = LocalDate.parse("2021-05-11", DateTimeFormatter.ofPattern("yyyy-MM-dd")); // 上面的代码会呈现格式化异样 // java.time.format.DateTimeParseException: Text '2021-05-11 11:53:53' could not be parsed, unparsed text found at index 10// LocalDate parse3 = LocalDate.parse("2021-05-11 11:53:53", DateTimeFormatter.ofPattern("yyyy-MM-dd")); // 正确的格式化办法 LocalDate parse3 = LocalDate.parse("2021-05-11 11:53:53", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); // 以后工夫 System.out.println("now() => "+ now); // 获取月份 int dayOfMonth = parse1.getDayOfMonth(); System.out.println("dayOfMonth => " + dayOfMonth); // 获取年份 int dayOfYear = parse1.getDayOfYear(); System.out.println("getDayOfYear => " + dayOfYear); // 获取那一周,留神这里获取的是对象 DayOfWeek dayOfWeek = parse1.getDayOfWeek(); System.out.println("getDayOfWeek => " + dayOfWeek); // 获取月份数据 int monthValue = parse3.getMonthValue(); System.out.println("getMonthValue => " + monthValue); // 获取年份 int year = parse3.getYear(); System.out.println("getYear => " + year); // getChronology 获取的是以后工夫的排序,这里输入后果是 ISO System.out.println("getChronology => " + parse3.getChronology()); System.out.println("getEra => " + parse3.getEra()); // 应用timeField获取值:TemporalField 是一个接口,定义了如何拜访 TemporalField 的值,ChronnoField 实现了这个接口 /* LocalDate 反对的格局如下: case DAY_OF_WEEK: return getDayOfWeek().getValue(); case ALIGNED_DAY_OF_WEEK_IN_MONTH: return ((day - 1) % 7) + 1; case ALIGNED_DAY_OF_WEEK_IN_YEAR: return ((getDayOfYear() - 1) % 7) + 1; case DAY_OF_MONTH: return day; case DAY_OF_YEAR: return getDayOfYear(); case EPOCH_DAY: throw new UnsupportedTemporalTypeException("Invalid field 'EpochDay' for get() method, use getLong() instead"); case ALIGNED_WEEK_OF_MONTH: return ((day - 1) / 7) + 1; case ALIGNED_WEEK_OF_YEAR: return ((getDayOfYear() - 1) / 7) + 1; case MONTH_OF_YEAR: return month; case PROLEPTIC_MONTH: throw new UnsupportedTemporalTypeException("Invalid field 'ProlepticMonth' for get() method, use getLong() instead"); case YEAR_OF_ERA: return (year >= 1 ? year : 1 - year); case YEAR: return year; case ERA: return (year >= 1 ? 1 : 0); * */ // Unsupported field: HourOfDay// System.out.println("ChronoField.HOUR_OF_DAY => " + parse1.get(ChronoField.HOUR_OF_DAY)); // Unsupported field: MinuteOfHour// System.out.println("ChronoField.MINUTE_OF_HOUR => " + parse1.get(ChronoField.MINUTE_OF_HOUR)); // Unsupported field: MinuteOfHour// System.out.println("ChronoField.SECOND_OF_MINUTE => " + parse1.get(ChronoField.SECOND_OF_MINUTE)); System.out.println("ChronoField.YEAR => " + parse1.get(ChronoField.YEAR)); // Unsupported field: MinuteOfHour// System.out.println("ChronoField.INSTANT_SECONDS => " + parse1.get(ChronoField.INSTANT_SECONDS)); }/*运行后果: now() => 2021-08-08 dayOfMonth => 11 getDayOfYear => 131 getDayOfWeek => TUESDAY getMonthValue => 5 getYear => 2021 getChronology => ISO getEra => CE ChronoField.YEAR => 2021 */TemporalField 是一个接口,定义了如何拜访 TemporalField 的值,ChronnoField 实现了这个接口LocalTime LocalTime:和LocalDate相似,区别在于蕴含具体工夫,同时领有更多操作具体工夫工夫的办法,上面是对应的办法以及测试: ...

August 8, 2021 · 8 min · jiezi

关于java8:JAVA8实战-Optional工具类

前言 没错,这又是一个新的专栏,JAVA8能够说是JAVA划时代的一个版本,简直是让JAVA焕发了第三春(第二春在JDK5),当然外面的新个性也是非常重要的,尽管Java当初都曾经到了10几的版本,然而国内少数应用的版本还是JAVA8,所以这个系列将会围绕Java8的新个性和相干工具做一些总结。心愿对大家日常学习和工作中有所帮忙。 概述:日常工作学习咱们大抵是如何躲避空指针的。对于Optional的零碎介绍,常见的应用和解决办法Optional的应用场景以及一些小型案例代码来看看《Effective Java》这个作者如何对待Optional这个工具类>空指针躲避 在讲述Optional之前,咱们来看下通常状况下咱们是如何避免空指针的。 字符串equals 字符串的操作是最常见的操作,应用字符串的equals办法很有可能抛出空指针异样,比方像上面的代码,如果a变量为Null,则毫无疑问会抛出空指针异样: a.equals("aaa"); 倡议:应用Objects.equals()或者应用其余工具类办法代替,或者确保obj.equals(target)的obj对象不会为null,比方"test".equals(target)。 比方咱们应用上面的办法保障equals的时候统一: public Tank createTank(String check){ Tank tank = null; if(Objects.equals(check, "my")){ tank = new MyTank(); }else if(Objects.equals(check, "mouse")){ tank = new MouseTank(); }else if (Objects.equals(check, "big")){ tank = new BigTank(); }else { throw new UnsupportedOperationException("unsupport"); } return tank;} 变量 == 操作 变量的==操作也是用的非常多,通常状况下和null搭配的比拟多,咱们通常须要留神上面这些事项: 确保比拟类型统一,比方最经典的Integer和int比拟在超过127的时候为false的问题。应用框架工具类的equals() 进行代替应用Objects.equals()办法代替 特别强调一下Integer的 ==操作的一些陷阱,特地留神最初一个打印是False,具体的起因有网上很多材料,这里就不啰嗦了: public static void main(String[] args) { Integer a = 1; Integer b = 256; System.out.println(a == null); System.out.println(a == b); System.out.println(a == 1); System.out.println(b == 256); System.out.println(b == 257); }/*运行后果: false false true true false */汇合元素为null 如果在一个List或者Set中存在Null元素,那么遍历的时候也很容易呈现空指针的问题,通常状况下咱们能够应用Stream.filter进行过滤,比方像上面这样,这里应用了StringUtils::isNotBlank来判断是否为空字符串并过滤掉所有的空字符串和Null元素: ...

August 8, 2021 · 6 min · jiezi

关于java8:CompletableFuture详解2

这篇文章介绍 Java 8 的 CompletionStage API 和它的规范库的实现 CompletableFuture。API通过例子的形式演示了它的行为,每个例子演示一到两个行为。 既然CompletableFuture类实现了CompletionStage接口,首先咱们须要了解这个接口的契约。它代表了一个特定的计算的阶段,能够同步或者异步的被实现。你能够把它看成一个计算流水线上的一个单元,最终会产生一个最终后果,这意味着几个CompletionStage能够串联起来,一个实现的阶段能够触发下一阶段的执行,接着触发下一次,接着…… 除了实现CompletionStage接口, CompletableFuture也实现了future接口, 代表一个未实现的异步事件。CompletableFuture提供了办法,可能显式地实现这个future,所以它叫CompletableFuture。 1、 创立一个实现的CompletableFuture 最简略的例子就是应用一个预约义的后果创立一个实现的CompletableFuture,通常咱们会在计算的开始阶段应用它。 static void completedFutureExample() { CompletableFuture cf = CompletableFuture.completedFuture("message");assertTrue(cf.isDone());assertEquals("message", cf.getNow(null));}getNow(null)办法在future实现的状况下会返回后果,就比方下面这个例子,否则返回null (传入的参数)。 2、运行一个简略的异步阶段 这个例子创立一个一个异步执行的阶段: static void runAsyncExample() { CompletableFuture cf = CompletableFuture.runAsync(() -> { assertTrue(Thread.currentThread().isDaemon()); randomSleep();});assertFalse(cf.isDone());sleepEnough();assertTrue(cf.isDone());}通过这个例子能够学到两件事件: CompletableFuture的办法如果以Async结尾,它会异步的执行(没有指定executor的状况下), 异步执行通过ForkJoinPool实现, 它应用守护线程去执行工作。留神这是CompletableFuture的个性, 其它CompletionStage能够override这个默认的行为。 参考浏览:工作并行执行神器:Fork&Join框架 3、在前一个阶段上利用函数 上面这个例子应用后面 #1 的实现的CompletableFuture, #1返回后果为字符串message,而后利用一个函数把它变成大写字母。 static void thenApplyExample() { CompletableFuture cf = CompletableFuture.completedFuture("message").thenApply(s -> { assertFalse(Thread.currentThread().isDaemon()); return s.toUpperCase();});assertEquals("MESSAGE", cf.getNow(null));}留神thenApply办法名称代表的行为。 then意味着这个阶段的动作产生以后的阶段失常实现之后。本例中,以后节点实现,返回字符串message。 Apply意味着返回的阶段将会对后果前一阶段的后果利用一个函数。 函数的执行会被阻塞,这意味着getNow()只有打斜操作被实现后才返回。 另外,关注公众号Java技术栈,在后盾回复:面试,能够获取我整顿的 Java 并发多线程系列面试题和答案,十分齐全。 4、在前一个阶段上异步利用函数 通过调用异步办法(办法后边加Async后缀),串联起来的CompletableFuture能够异步地执行(应用ForkJoinPool.commonPool())。 static void thenApplyAsyncExample() { ...

July 18, 2021 · 4 min · jiezi

关于java8:Java-8-StreamBox

Java 8 StreamBox Java 8的装箱流1. 什么是 盒装流(Boxed Stream)在java8 中,如果咱们想把一个对象流转变成一个汇合,咱们能够应用Collectors 类中的一个静态方法 List<String> strings = Stream.of("how", "to", "do", "in", "java") .collect(Collectors.toList());然而对于根本类型的数据缺失不适合的 // 编译谬误IntStream.of(1,2,3,4,5)// .collect(Collectors.toList());如果想不呈现编译不出错,咱们必须打包这个元素,而后再汇合中收集被包装的对象,这种类型的流称为 盒装流 2. 将 int 流转换成 Integer 汇合List<Integer> ints = IntStream.of(1,2,3,4,5) .boxed() .collect(Collectors.toList()); System.out.println(ints); //间接对流进行操作,获取最大值Optional<Integer> max = IntStream.of(1,2,3,4,5) .boxed() .max(Integer::compareTo); System.out.println(max)3. LongStream将 long 类型的流转换成成 Long 类型 List<Long> longs = LongStream.of(1l,2l,3l,4l,5l) .boxed() .collect(Collectors.toList());4. doubleStreamList<Double> doubles = DoubleStream.of(1d,2d,3d,4d,5d) .boxed() .collect(Collectors.toList());

July 14, 2021 · 1 min · jiezi

关于java8:Java-Stream-API

Java Stream API [TOC] Java8 中 的Steam 能够定义为来自起源的元素序列,Streams反对对元素进行聚合操作,这里的元素指的是为流提供数据的汇合和 数组,在聚合操作中咱们能够依据咱们需要来获取出合乎咱们要求的数据 在操作Stream 流之前咱们应该晓得,Streams API 都是间接针对流的操作,因而首先咱们要先会创立一个 Steam。 在粒度级别,Collection 和 Stream 之间的区别与计算事物的工夫无关。甲Collection是一个存储器内数据结构,其中认为所述数据结构以后具备的所有值。必须先计算汇合中的每个元素,而后能力将其增加到汇合中。Stream 在概念上是一个管道,其中元素是按需计算的。 这个概念带来了显着的编程劣势。这个想法是,用户将仅从 Stream 中提取他们须要的值,并且这些元素仅在须要时(对用户不可见)生成。这是生产者-消费者关系的一种模式。 在 Java 中,java.util.Stream示意能够对其执行一个或多个操作的流。流操作要么是两头的,要么是终端的。 的终端操作返回一个特定类型的后果和两头操作返回流自身,所以咱们能够连锁的一排的多个办法,以在多个步骤中执行该操作。 流是在源上创立的,例如java.util.Collection喜爱List或Set。在Map不间接反对,咱们能够创立映射键,值。 流操作能够程序或并行执行。当并行执行时,它被称为并行流 1. 创立流上面给出了一些创立流的形式 1.1 Stream.of() public static void main(String[] args) { // 通过Stream.of 办法获取一个数据流 Stream<Integer> stream = Stream.of(1,2,3,4,5,6,7,8,9); stream.forEach(p -> System.out.println(p)); }1.2 Stream.of(数组)咱们能够通过引入一个数组的形式生成一个流 public static void main(String[] args) { Stream<Integer> stream = Stream.of( new Integer[]{1,2,3,4,5,6,7,8,9} ); stream.forEach(p -> System.out.println(p)); }1.3 列表.steam( 罕用)工作中咱们常常应用通过一个汇合来获取一个流,流中的元素都是来自List ...

July 14, 2021 · 2 min · jiezi

关于java8:02-遍历-foreach

遍历 foreachJava 8的foreach() 办法 是一种无效的形式,用来遍历书架汇合,能够对List,Stream 进行遍历,该办法曾经被增加到以下接口中 Iterable 接口-Map 接口Stream 接口1. Iterable foreach1.1 foreach 办法上面的代码中给定了Iterable 接口中foreach 办法的默认实现 default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); }}该foreach 办法action 对每个元素执行给定,Iterable 直到所有的元素都解决或 action 引发异样 实例1,应用 foreach 遍历list 的java 程序 List<String> names = Arrays.asList("Alex", "Brian", "Charles"); names.forEach(System.out::println);输入: AlexBrianCharles1.2 创立消费者行为--- consumer在下面的实例中,action 示意承受单个输出参数并且不返回任何数据, 它是Consumer 接口的实例,通过这样的创立消费者操作. 咱们制订要以相似办法的语法 执行多个语句 public static void main(String[] args) { List<String> names = Arrays.asList("Alex", "Brian", "Charles"); // 创立一个生产之操作 Consumer<String> makeUpperCase = new Consumer<String>() { @Override public void accept(String o) { System.out.println(o.toUpperCase()); } }; names.forEach(makeUpperCase); }// 后果ALEXBRIANCHARLES应用匿名函数外部类的形式,当初能够应用 lanmbda 表达式代替 ...

July 14, 2021 · 2 min · jiezi

关于java8:java字符串拼接的多种方式1

字符串拼接的多种形式1. 起因:在公司的时候,自研的框架会应用到字符串的拼接,所以在这里想将多种拼接字符串的形式进行一个小的总结2. 形式1:应用+号拼接(最不倡议应用)①不倡议用+号的起因:String底层是常量char数组,具体不可变性,在jvm中是通过字符串常量池来进行存储的。因为底层对加号应用了运算符的重载(c++内容),他在每次拼接的时候都会创立StringBuilder对象,通过StringBuilder对象的append(Stirng str)进行拼接,再通过new String(StringBuilder)来创立String对象。然而这样频繁的创建对象是很耗费性能的,因而不举荐应用+号进行拼接操作。3. 形式2:应用concat函数进行拼接①代码举例String strA = "Hello";String strB = "world";String concat = strA.concat(",").concat(strB);System.out.println(concat);//后果为hello,world②底层源码调用函数图 ③底层源码解析之横向调用(复制原String的值)//String中concat函数public String concat(String str) {//如果增加的字符串为空字符串,返回自身 int otherLen = str.length(); if (otherLen == 0) { return this; } int len = value.length; char buf[] = Arrays.copyOf(value, len + otherLen);//复制原来的String到新的buf数组中-------------------------------------下面的是横向,上面是纵向//复制增加的str到新的buf数组中 str.getChars(buf, len); return new String(buf, true);}//在concat函数中调用了Arrays中copyOf函数//将源数组全副复制到从索引0开始的copy数组中public static char[] copyOf(char[] original, int newLength) { char[] copy = new char[newLength]; System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy;}④底层源码解析之System.arraycopy(对数组进行管制,比copyOf更加灵便)一维数组测试System.arraycopy是否为同一存储空间 ...

July 7, 2021 · 2 min · jiezi

关于java8:java8-LocalDateTime时间方法

1、字符串类型转成LocalDateTime public static LocalDateTime string2LocalDateTime(String dateStr) { return LocalDateTime.parse(dateStr, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); }2、两个日期比拟 if (nowDate.compareTo(endTimeDate) < 0) { // 代码逻辑}3、计算日期相差工夫(参考地址:https://www.cnblogs.com/jpfss...) java.time.Duration duration = java.time.Duration.between(LocalDateTime startTime, LocalDateTime endTime );例如: duration.toMinutes() //两个时间差的分钟数toNanos()//纳秒toMillis()//毫秒toMinutes()//分钟toHours()//小时toDays()//天数

May 11, 2021 · 1 min · jiezi

关于java8:理解枚举类型

枚举的定义public enum AccountActionTypeEnum {LOGIN,REGISTER,EDIT\_INFO;private AccountActionTypeEnum() {}}值个别是大写的字母,多个值之间以逗号分隔。 枚举常量在类型安全性和便捷性都很有保障,如果呈现类型问题编译器也会提醒咱们改良。上面看看如何在其余类中应用枚举类型 public static void main(String[] args){//间接援用AccountActionTypeEnum type =AccountActionTypeEnum.LOGIN;}枚举实现原理咱们大略理解了枚举类型的定义与简略应用后,当初来理解一下枚举类型的根本实现原理。实际上在应用关键字enum创立枚举类型并编译后,编译器会为咱们生成一个相干的类,这个类继承了Java API中的java.lang.Enum类,也就是说通过关键字enum创立枚举类型在编译后事实上也是一个类类型而且该类继承自java.lang.Enum类。上面咱们编译后面定义的AccountActionTypeEnum.java并查看生成的class文件来验证这个论断: javac AccountActionTypeEnum.java   #编译java文件生成AccountActionTypeEnum.class文件而后通过jad来反编译AccountActionTypeEnum.class .jad -sjava .AccountActionTypeEnum.class  #我这里的jad.exe放在AccountActionTypeEnum的同级目录,如果有退出环境变量的话,能够间接jad反编译AccountActionTypeEnum.class的后果 // Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.// Jad home page: http://www.kpdus.com/jad.html// Decompiler options: packimports(3)// Source File Name:   AccountActionTypeEnum.java<br>public final class AccountActionTypeEnum extends Enum{// 编译器重写的办法public static AccountActionTypeEnum[] values(){return (AccountActionTypeEnum[])$VALUES.clone();}// 编译器重写的办法public static AccountActionTypeEnum valueOf(String s){return (AccountActionTypeEnum)Enum.valueOf(AccountActionTypeEnum, s);}// 公有private AccountActionTypeEnum(String s, int i){ // 调用enum的构造方法super(s, i);}// 定义的四种枚举// 定义类变量,这也就是为什么咱们能够间接以AccountActionTypeEnum.LOGIN的模式应用的起因了public static final AccountActionTypeEnum LOGIN;public static final AccountActionTypeEnum REGISTER;public static final AccountActionTypeEnum EDIT\_INFO;private static final AccountActionTypeEnum $VALUES[];//动态代码块复制初始化类变量,在初始化阶段执行,实例化枚举实例static{LOGIN = new AccountActionTypeEnum("LOGIN", 0);REGISTER = new AccountActionTypeEnum("REGISTER", 1);EDIT\_INFO = new AccountActionTypeEnum("EDIT\_INFO", 2);$VALUES = (new AccountActionTypeEnum[] {LOGIN, REGISTER, EDIT\_INFO});}}从反编译的代码能够看出编译器的确帮忙咱们生成了一个AccountActionTypeEnum类(留神该类是final类型的,将无奈被继承)而且该类继承自java.lang.Enum类,该类是一个抽象类,除此之外,编译器还帮忙咱们生成了4个AccountActionTypeEnum类型的实例对象别离对应枚举中定义的三种type和一个type数组。留神编译器还为咱们生成了两个静态方法,别离是values()和 valueOf(),到此咱们也就明确了,应用关键字enum定义的枚举类型,在编译期后,也将转换成为一个实实在在的类,而在该类中,会存在每个在枚举类型中定义好变量的对应实例对象,如上述的LOGIN枚举类型对应public static final AccountActionTypeEnum LOGIN;,同时编译器会为该类创立两个办法,别离是values()和valueOf()。上面咱们深刻理解一下java.lang.Enum类以及values()和valueOf()的用处。 ...

January 28, 2021 · 2 min · jiezi

关于java8:从零开始学习Java8-Stream看这篇就够了

为何须要引入流在咱们平时的开发中简直每天都会有到List、Map等汇合API,若是问Java什么API应用最多,我想也应该是汇合了。举例:如果我有个汇合List,外面元素有1,7,3,8,2,4,9,须要找出外面大于5的元素,具体实现代码: public List<Integer> getGt5Data() { List<Integer> data = Arrays.asList(1, 7, 3, 8, 2, 4, 9); List<Integer> result = new ArrayList<>(); for (Integer num : data) { if (num > 5) { result.add(num); } } return result;}这个实现让咱们感觉到了汇合的操作不是太完满,如果是数据库的话,咱们只须要简略的在where前面加一个条件大于5就能够失去咱们想要的后果,为什么Java的汇合就没有这种API呢?其次,如果咱们遇到有大汇合须要解决,为了进步性能,咱们可能须要应用到多线程来解决,然而写并行程序的复杂度有进步了不少。 基于以上的问题,所有Java8推出了Stream Stream简介Stream有哪些特点: 元素的序列:与汇合一样能够拜访外面的元素,汇合讲的是数据,而流讲的是操作,比方:filter、map源: 流也须要又一个提供数据的源,程序和生成时的程序统一数据的操作:流反对相似于数据库的操作,反对程序或者并行处理数据;下面的例子用流来实现会更加的简洁public List<Integer> getGt5Data() { return Stream.of(1, 7, 3, 8, 2, 4, 9) .filter(num -> num > 5) .collect(toList());}流水线操作:很多流的办法自身也会返回一个流,这样能够把多个操作连接起来,造成流水线操作外部迭代:与以往的迭代不同,流应用的外部迭代,用户只须要专一于数据处理只能遍历一次: 遍历实现之后咱们的流就曾经生产完了,再次遍历的话会抛出异样应用StreamJava8中的Stream定义了很多办法,根本能够把他们分为两类:两头操作、终端操作;要应用一个流个别都须要三个操作: 定义一个数据源定义两头操作造成流水线定义终端操作,执行流水线,生成计算结果构建流应用Stream.of办法构建一个流Stream.of("silently","9527","silently9527.cn") .forEach(System.out::println);应用数组构建一个流int[] nums = {3, 5, 2, 7, 8, 9};Arrays.stream(nums).sorted().forEach(System.out::println);通过文件构建一个流应用java.nio.file.Files.lines办法能够轻松构建一个流对象 ...

December 16, 2020 · 2 min · jiezi

关于java8:Java8新特性函数式编程StreamFunctionOptionalConsumer

Java8新引入函数式编程形式,大大的进步了编码效率。本文将对波及的对象等进行对立的学习及记录。 首先须要分明一个概念:函数式接口;它指的是有且只有一个未实现的办法的接口,个别通过FunctionalInterface这个注解来表明某个接口是一个函数式接口。函数式接口是Java反对函数式编程的根底。 本文目录: 1 Java8函数式编程语法入门2 Java函数式接口 2.1 Consumer2.2 Function2.3 Predicate3 函数式编程接口的应用 3.1 Stream 3.1.1 Stream对象的创立3.1.2 Stream对象的应用 3.1.2.1 filter3.1.2.2 map3.1.2.3 flatMap3.1.2.4 takeWhile3.1.2.5 dropWhile3.1.2.6 reduce与collect3.2 Optional 3.2.1 Optional对象创立 3.2.1.1 empty3.2.1.2 of3.2.1.3 ofNullable3.2.2 办法3.2.3 应用场景 3.2.3.1 判断后果不为空后应用3.2.3.2 变量为空时提供默认值3.2.3.3 变量为空时抛出异样,否则应用1 Java8函数式编程语法入门Java8中函数式编程语法可能精简代码。 应用Consumer作为示例,它是一个函数式接口,蕴含一个形象办法accept,这个办法只有输出而无输入。 当初咱们要定义一个Consumer对象,传统的形式是这样定义的: Consumer c = new Consumer() { @Override public void accept(Object o) { System.out.println(o); }};而在Java8中,针对函数式编程接口,能够这样定义: Consumer c = (o) -> { System.out.println(o);};下面已阐明,函数式编程接口都只有一个形象办法,因而在采纳这种写法时,编译器会将这段函数编译后当作该形象办法的实现。 如果接口有多个形象办法,编译器就不晓得这段函数应该是实现哪个办法的了。 因而,=前面的函数体咱们就能够看成是accept函数的实现。 输出:->后面的局部,即被()突围的局部。此处只有一个输出参数,实际上输出是能够有多个的,如两个参数时写法:(a, b);当然也能够没有输出,此时间接就能够是()。函数体:->前面的局部,即被{}突围的局部;能够是一段代码。输入:函数式编程能够没有返回值,也能够有返回值。如果有返回值时,须要代码段的最初一句通过return的形式返回对应的值。当函数体中只有一个语句时,能够去掉{}进一步简化: Consumer c = (o) -> System.out.println(o);然而这还不是最简的,因为此处只是进行打印,调用了System.out中的println静态方法对输出参数间接进行打印,因而能够简化成以下写法: ...

November 27, 2020 · 4 min · jiezi

关于java8:CompletableFuture让你的代码免受阻塞之苦

前言当初大部分的CPU都是多核,咱们都晓得想要晋升咱们应用程序的运行效率,就必须得充分利用多核CPU的计算能力;Java早曾经为咱们提供了多线程的API,然而实现形式稍微麻烦,明天咱们就来看看Java8在这方面提供的改善。 假如场景当初你须要为在线教育平台提供一个查问用户详情的API,该接口须要返回用户的根本信息,标签信息,这两个信息寄存在不同地位,须要近程调用来获取这两个信息;为了模仿近程调用,咱们须要在代码外面提早 1s; public interface RemoteLoader { String load(); default void delay() { try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } }}public class CustomerInfoService implements RemoteLoader { public String load() { this.delay(); return "根本信息"; }}public class LearnRecordService implements RemoteLoader { public String load() { this.delay(); return "学习信息"; }}同步形式实现版本如果咱们采纳同步的形式来实现这个API接口,咱们的实现代码: @Testpublic void testSync() { long start = System.currentTimeMillis(); List<RemoteLoader> remoteLoaders = Arrays.asList(new CustomerInfoService(), new LearnRecordService()); List<String> customerDetail = remoteLoaders.stream().map(RemoteLoader::load).collect(toList()); System.out.println(customerDetail); long end = System.currentTimeMillis(); System.out.println("总共破费工夫:" + (end - start));}不出所料,因为调用的两个接口都是提早了 1s ,所以后果大于2秒 ...

November 22, 2020 · 3 min · jiezi

关于java8:Java中NullPointerException的完美解决方案

null在Java中带来的麻烦我置信所有的Java程序猿肯定都遇到过NullPointerException,空指针在Java程序中是最常见的,也是最烦人的;它让咱们很多程序猿产生了积重难返的感觉,所有可能产生空指针的中央都的加上if-else查看,然而这带给咱们很多麻烦 Java自身是强类型的,然而null毁坏了这个规定,它能够被赋值给任何对象Java的设计是让程序猿对指针无感知,然而null指针是个例外它会是代码变得很臃肿,到处都充斥着if-else的空查看,甚至是多层嵌套,代码可读性降落null自身毫无意义,示意不了无前两点不须要特地的阐明,后两点举个例子来阐明一下:如果一个人领有一个手机,每个手机都有生成厂商,每个厂商都会有个名字,用类示意的话: public class Person { private Phone phone; public Phone getPhone() { return phone; }}public class Phone { private Producer producer; public Producer getProducer() { return producer; }}public class Producer { private String name; public String getName() { return name; }}在这个例子中,如果咱们须要取到手机生成厂商的名字 public String getPhoneProducerName(Person person) { return person.getPhone().getProducer().getName();}因为不肯定每个人都会有一个手机,所有在调用getProducer()时可能会呈现NullPointerException。 一门设计语言原本就是来形容世界的,在这个事例中有的人有手机,有的人也可能没有手机,所以在调用person.getPhone()返回的值就应该蕴含有和无这两种状况,当初通过返回null来示意无,然而在调用getProducer()却又会抛出异样,这样就不太合乎事实逻辑;所以把null来用来示意无不适合 在遇到这种状况通常的做法是做null查看,甚至是每个中央可能产生null指针的做查看。 public String getPhoneProducerName(Person person) { if (person.getPhone() == null) { return "无名字"; } if (person.getPhone().getProducer() == null) { return "无名字"; } return person.getPhone().getProducer().getName();}这里我曾经试图在缩小代码的层级,如果应用的是if-else,代码的层级会更深,代码可读性降落。 ...

November 18, 2020 · 2 min · jiezi

关于java8:JAVASE2-复习

产生一个随机数 -- n -- 产生n以内的整数,默认从0开始int random = new Random().nextInt(100) ;//[0,100) 变量--1,随着变量呈现的地位不同,作用和名字都不同.--2,呈现在成员地位(在类里办法外)的变量叫做成员变量--整个类中都无效--不必赋值也有默认值--3,呈现在部分地位(办法里)的变量叫做局部变量--办法里无效--必须手动赋值--4,变量应用时有一个准则:就近准则//测试 变量public class Test3_Variable { //2,呈现在类里办法外的变量--是成员变量--作用于整个类中--能够不赋值,会有默认值 //4,成员变量,都有默认值. //整数类型默认值是0,小数类型默认值是0.0,boolean类型默认值是false int count ; int age = 10; boolean flag; //单元测试JUnit办法: //要求:@Test public void 办法名(){办法体} //测试:选中办法名,右键,run as...junit test... @Test public void show() { //1,呈现在办法里的变量--是局部变量--必须初始化--作用于办法里 int age = 0; System.out.println(age);//3,就近准则 - 输入0 System.out.println(count); if(! flag) {//flag的默认值是false,取反,就是true,条件成立,输入1. System.out.println(1); } } }

September 23, 2020 · 1 min · jiezi

关于java8:Java8新特性-Lambda底层实现原理

前言常常会在写业务代码的时候,有这样的需要: 筛选出条件为XX的实体类ID List List<Long> waitTaskList = wflInsTaskList.stream().filter(wflInsTask -> { return wflInsTask.getArrivalStatus().equals(WflInsTask.ARRIVAL_STATUS_NOT_ARRIVED); }).map(WflInsTask::getTaskId).distinct().collect(Collectors.toList());Java8之Lambda下文内容默认以JDK8为前提 什么是LambdaLambda 表达式是 JDK8 的一个新个性,能够取代大部分的匿名外部类,写出更优雅的Java代码,尤其在汇合的遍历和其余汇合操作中,能够极大地优化代码构造。 Lambda 表达式形容了一个代码块(或者叫匿名办法),能够将其作为参数传递给构造方法或者一般办法以便后续执行。如: () -> System.out.println("hello");() 为 Lambda 表达式的参数列表(容许没有参数),-> 标识这串代码为 Lambda 表达式(也就是说,看到 -> 就晓得这是 Lambda,Groovy的闭包语法也相似),System.out.println("hello") 就是执行的代码,将“hello”打印到规范输入流。 以Runnable接口为例,原来咱们创立一个线程并启动它是这样的: public class Test { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println("hello"); } }).start(); }}通过 Lambda 只须要这样: public class Test { public static void main(String[] args) { new Thread(() -> System.out.println("hello")).start(); }}咱们看Runnable接口源码: ...

August 25, 2020 · 3 min · jiezi

Java8-list-stream-测试用例小结

一、测试对象package com.demo.crwien.test;public class Student { private Integer id; private String groupId; private String name; private Integer age;}二、测试用例package com.demo.lee.util;import org.junit.Test;import java.util.Arrays;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.function.Function;import java.util.stream.Collectors;import static java.util.stream.Collectors.groupingBy;/** * @author lee */public class ListCollectionTest { @Test public void test1() { //1.分组计数 List<Student> list = Arrays.asList( new Student(1, "one", "zhao",11), new Student(2, "one", "qian",22), new Student(3, "two", "sun",33), new Student(4, "two", "lee",18)); //1.1依据某个属性分组计数 Map<String, Long> result1 = list.stream().collect(Collectors.groupingBy(Student::getGroupId, Collectors.counting())); System.out.println(result1); //1.2依据整个实体对象分组计数,当其为String时常应用 Map<Student, Long> result2 = list.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); System.out.println(result2); //1.3依据分组的key值对后果进行排序、放进另一个map中并输入 Map<String, Long> xMap = new HashMap<>(); result1.entrySet().stream().sorted(Map.Entry.<String, Long>comparingByKey().reversed()) //reversed不失效 .forEachOrdered(x -> xMap.put(x.getKey(), x.getValue())); System.out.println(xMap); } @Test public void test2() { List<Student> list = Arrays.asList( new Student(1, "one", "zhao",11), new Student(2, "one", "qian",22), new Student(3, "two", "sun",33), new Student(4, "two", "lee",18)); //2.分组,并统计其中一个属性值得sum或者avg:id总和 Map<String, Integer> result3 = list.stream().collect( Collectors.groupingBy(Student::getGroupId, Collectors.summingInt(Student::getId)) ); System.out.println(result3); } @Test public void test3(){ List<Student> list = Arrays.asList( new Student(1, "one", "zhao",11), new Student(2, "one", "qian",22), new Student(3, "two", "sun",33), new Student(4, "two", "lee",18)); //3.依据某个属性过滤 List<Student> list1 = list.stream().filter(a -> a.getName().contains("a")).collect(Collectors.toList()); System.out.println(list1); } @Test public void test4(){ List<Student> list = Arrays.asList( new Student(1, "one", "zhao",11), new Student(2, "one", "qian",22), new Student(3, "two", "sun",33), new Student(4, "two", "lee",18)); //4.依据对象属性进行分组,自行编辑分组条件 Map<String, List<Student>> collect = list.stream().collect(groupingBy(s -> { Integer age = s.getAge(); if (0<age && age<=10){ return "baby"; }else if (10<age && age<=20){ return "teenager"; }else if (20<age && age<=30){ return "youth"; }else { return "old"; } })); System.out.println(collect); }@testpublic void test5(){ List<Student> list = Arrays.asList( new Student(1, "one", "zhao",11), new Student(2, "one", "qian",22), new Student(3, "two", "sun",33), new Student(4, "two", "lee",18)); //对象属性拼字符串 String investor = list.stream().map(Student::getName).collect(Collectors.joining(",")}@testpublic void test6(){ List<Student> list = Arrays.asList( new Student(1, "one", "zhao",11), new Student(2, "one", "qian",22), new Student(3, "two", "sun",33), new Student(4, "two", "lee",18)); //对象属性求和 Integer result = list.stream().collect(Collectors.summingInt(Student::getAge)); System.out.println("所有学生年龄之和 : " + reuslt);}@testpublic void test7(){ List<Student> list = Arrays.asList( new Student(1, "one", "zhao",11), new Student(2, "one", "qian",22), new Student(3, "two", "sun",33), new Student(4, "two", "lee",18)); //list转map Map<Integer,String> map = userlist.stream().collect(Collectors.toMap(User::getAge,User::getName));}@testpublic void test8(){ //按名称形容排序 List<ECLabelEnum> enumList = Arrays.stream(ECLabelEnum.values()).sorted(Comparator.comparing(ECLabelEnum::desc)).collect(Collectors.toList());}//排序@testpublic void test9(){ //倒序,从大到小 list.stream().sorted(Comparator.comparing(Student::getAge)); //正序,从小到大 list.stream().sorted(Comparator.comparing(Student::getAge).reversed()) }}三、实战Map<String, String> dataMap = new HashMap<>(10);List<String> dataList = request.getDataList();// 获取未加密的字符串(过滤不符合条件的)List<String> unEncryptData = dataList.stream().filter(s -> !isEncryptData(s)).collect(Collectors.toList());// 移除未加密的dataList.removeAll(unEncryptData);if (encryptData != null && encryptData.size() > 0) { collect = encryptData.stream().collect(Collectors.toMap(x -> x, x -> x,(k1,k2) -> k1)); }

July 17, 2020 · 2 min · jiezi

java8实战学习总结1

1. lambda表达式1.1 什么是lambda表达式1.2 什么样的场景能应用lambda表达式1.3 lambda表达式实现一个接口的四种写法2. 函数式编程2.1 什么是函数式编程2.2 什么是命令式编程2.3 什么是函数式接口 FunctionalInterface2.4 什么是 default 办法2.5 default办法的意义2.6 java8内置的罕用函数式接口(1). Predicate<T>断言 -> 输出T, 输入 boolean (2). Consumer<T>生产一个输出 -> 输出T, 不输入(void) (3). Supplier<T>生产一个输入 -> 不输出, 输入T (4). Function<T, R>输出T, 输入R的函数 (5). UnaryOperator<T>一元函数: 输出1个输入 1个: 类型都是T (6). BinaryOperatior<T>二元函数: 输出2个输入1个: 类型都是T (7). BiFunction<T, U, R>输出两个输入一个: 输出 T, U 输入 R, 罕用于 reduce/sort等操作, 2.7 办法援用(1). 静态方法的办法援用(2). 实例办法的办法援用(3). 构造方法的办法援用2.8. 变量援用和隐式final为何外部类应用内部变量要是final的3. stream流式编程3.1. 内部迭代和外部迭代(1). 什么是内部迭代(2). 什么是外部迭代3.2. 两头操作/终止操作/惰性求值(1). 什么是两头操作?返回还是流stream的操作, 就是两头操作, 例如 map操作 ...

July 14, 2020 · 3 min · jiezi

Java8方法引用

办法援用就是通过类名或办法名援用曾经存在的办法来简化lambda表达式。那么什么时候须要用办法援用呢?如果lamdba体中的内容曾经有办法实现了,咱们就能够应用办法援用。 一、办法援用的三种语法格局1. 对象::实例办法名lamdba写法: @Testvoid test1(){ Consumer<String> con = x -> System.out.println(x);}办法援用写法: @Testvoid test2(){ PrintStream out = System.out; Consumer<String> con = out::println;}consumer接口: @FunctionalInterfacepublic interface Consumer<T> { void accept(T t);}留神:被调用的办法的参数列表和返回值类型须要与函数式接口中形象办法的参数列表和返回值类型要统一。 2. 类::静态方法名lamdba写法: @Testvoid test3(){ Comparator<Integer> com = (x, y) -> Integer.compare(x,y);}办法援用写法: @Testvoid test4(){ Comparator<Integer> com = Integer::compare;}Comparator接口: @FunctionalInterfacepublic interface Comparator<T> { int compare(T o1, T o2);}Integer类局部内容: public final class Integer extends Number implements Comparable<Integer> { public static int compare(int x, int y) { return (x < y) ? -1 : ((x == y) ? 0 : 1); }}留神:被调用的办法的参数列表和返回值类型须要与函数式接口中形象办法的参数列表和返回值类型要统一。 ...

July 14, 2020 · 1 min · jiezi

Java8-streams-map-示例

在开发过程中,经常会对list进行遍历操作,有时候操作后也需要返回List。这时候可以使用java8的 stream map进行操作。如下面的示例说明 具体应用1 /* addressList是一个地址列表,把实体数据都转换成dto数据。返回给addressDTOList */List<AddressDTO> addressDTOList = addressList.stream().map(item->addressMapper.entityToDto(item)).collect(Collectors.toList());具体应用2 /* 将字符串转换成大写 */List<String> alpha = Arrays.asList("a", "b", "c", "d");// Java8之前的写法List<String> alphaUpper = new ArrayList<>();for (String s : alpha) { alphaUpper.add(s.toUpperCase());}System.out.println(alpha); //[a, b, c, d]System.out.println(alphaUpper); //[A, B, C, D] // Java8之后的写法 List<String> collect = alpha.stream().map(String::toUpperCase).collect(Collectors.toList());System.out.println(collect); //[A, B, C, D]// 其他数据类型的应用List<Integer> num = Arrays.asList(1,2,3,4,5);List<Integer> collect1 = num.stream().map(n -> n * 2).collect(Collectors.toList());System.out.println(collect1); //[2, 4, 6, 8, 10]

July 5, 2020 · 1 min · jiezi

玩转Java8中的-Stream-之从零认识-Stream

作者:litesky来源: http://www.jianshu.com/p/11c9... 相信Java8的Stream 大家都已听说过了,但是可能大家不会用或者用的不熟,文章将带大家从零开始使用,循序渐进,带你走向Stream的巅峰。 操作符什么是操作符呢?操作符就是对数据进行的一种处理工作,一道加工程序;就好像工厂的工人对流水线上的产品进行一道加工程序一样。 Stream的操作符大体上分为两种:中间操作符和终止操作符 中间操作符对于数据流来说,中间操作符在执行制定处理程序后,数据流依然可以传递给下一级的操作符。 中间操作符包含8种(排除了parallel,sequential,这两个操作并不涉及到对数据流的加工操作): map(mapToInt,mapToLong,mapToDouble) 转换操作符,把比如A->B,这里默认提供了转int,long,double的操作符。flatmap(flatmapToInt,flatmapToLong,flatmapToDouble) 拍平操作比如把 int[]{2,3,4} 拍平 变成 2,3,4 也就是从原来的一个数据变成了3个数据,这里默认提供了拍平成int,long,double的操作符。limit 限流操作,比如数据流中有10个 我只要出前3个就可以使用。distint 去重操作,对重复元素去重,底层使用了equals方法。filter 过滤操作,把不想要的数据过滤。peek 挑出操作,如果想对数据进行某些操作,如:读取、编辑修改等。skip 跳过操作,跳过某些元素。sorted(unordered) 排序操作,对元素排序,前提是实现Comparable接口,当然也可以自定义比较器。 终止操作符数据经过中间加工操作,就轮到终止操作符上场了;终止操作符就是用来对数据进行收集或者消费的,数据到了终止操作这里就不会向下流动了,终止操作符只能使用一次。 collect 收集操作,将所有数据收集起来,这个操作非常重要,官方的提供的Collectors 提供了非常多收集器,可以说Stream 的核心在于Collectors。count 统计操作,统计最终的数据个数。findFirst、findAny 查找操作,查找第一个、查找任何一个 返回的类型为Optional。noneMatch、allMatch、anyMatch 匹配操作,数据流中是否存在符合条件的元素 返回值为bool 值。min、max 最值操作,需要自定义比较器,返回数据流中最大最小的值。reduce 规约操作,将整个数据流的值规约为一个值,count、min、max底层就是使用reduce。forEach、forEachOrdered 遍历操作,这里就是对最终的数据进行消费了。toArray 数组操作,将数据流的元素转换成数组。这里只介绍了Stream,并没有涉及到IntStream、LongStream、DoubleStream,这三个流实现了一些特有的操作符,我将在后续文章中介绍到。Java知音公众号内回复“面试题聚合”,送你一份各大公司面试汇总宝典。 说了这么多,只介绍这些操作符还远远不够;俗话说,实践出真知。那么,Let‘s go。 代码演练Stream 的一系列操作必须要使用终止操作,否者整个数据流是不会流动起来的,即处理操作不会执行。 map,可以看到 map 操作符要求输入一个Function的函数是接口实例,功能是将T类型转换成R类型的。 map操作将原来的单词 转换成了每个单的长度,利用了String自身的length()方法,该方法返回类型为int。这里我直接使用了lambda表达式,关于lambda表达式 还请读者们自行了解吧。 public class Main { public static void main(String[] args) { Stream.of("apple","banana","orange","waltermaleon","grape") .map(e->e.length()) //转成单词的长度 int .forEach(e->System.out.println(e)); //输出 }}当然也可以这样,这里使用了成员函数引用,为了便于读者们理解,后续的例子中将使用lambda表达式而非函数引用。 ...

June 28, 2020 · 4 min · jiezi

你使用过Java8中的parallelStream

前言并行编程势不可挡,Java从1.7开始就提供了Fork/Join 支持并行处理。java1.8 进一步加强。 并行处理就是将任务拆分子任务,分发给多个处理器同时处理,之后合并。 Stream APIJava 8 引入了许多特性,Stream API是其中重要的一部分。区别 InputStream OutputStream,Stream API 是处理对象流而不是字节流。 执行原理如下,流分串行和并行两种执行方式 // 串行执行流stream().filter(e -> e > 10).count();// 并行执行流.parallelStream().filter(e -> e > 10).count() ParallelStreams执行原理并行执行时,java将流划分为多个子流,分散在不同CPU并行处理,然后进行合并。 并行一定比串行更快吗?这不一定,取决于两方面条件: 处理器核心数量,并行处理核心数越多自然处理效率会更高。处理的数据量越大,优势越强。这也很好理解,比如十个人干一个人就能完成的活儿会比它自己干更便宜?ParallelStreams注意事项使用并行流时,不要使用collectors.groupingBy,collectors.toMap,替代为 collectors.groupingByConcurrent , collectors.toConcurrentMap,或直接使用串行流。 原因,并行流执行时,通过操作Key来合并多个map的操作比较昂贵。详细大家可以查看官网介绍。 https://docs.oracle.com/javas...Map<String, List<Person>> byGender = roster .stream() .collect(Collectors.groupingBy(Person::getGender));ConcurrentMap<String, List<Person>> byGender = roster .parallelStream() .collect(Collectors.groupingByConcurrent(Person::getGender));ParallelStreams 默认使用 ForkJoinPool.commonPool()线程池。 注意:默认情况下,你写的 ParallelStreams 都是通过该线程池调度执行,整个应用程序都共享这个线程池。 看一个例子,我们查询一批新闻数据,可以利用并行化来处理远程新闻下载。 public List<News> queryNews(Stream<String> ids) { return ids.parallel() .map(this::getNews) // 网络操作,新闻下载 .collect(toList());}因为是网络操作,存在很多不确定性,假如某个任务运行时间较长,导致线程池资源占据,阻塞其它线程,这样就阻止了其他的并行流任务正常进行。 如果解决这个问题的其中一种方式,进行线程池隔离。那么如何自定义并行流的线程池呢? ...

June 5, 2020 · 1 min · jiezi

第一章简介

1.1 为什么再次修改Java多核CPU的出现,大大提高了计算机的处理能力。人们开发的java.util.concurrent包和很多第三方类库,试图将并发抽象化,帮助程序员写出在多核CPU上运行良好的程序。很可惜,目前成果还不够。处理大型数据集合就是一个很好的例子。面对大型数据集合,java还欠缺高效的并行操作。开发者能够使用java8编写复杂的集合处理算法,只需要简单修改一个方法,就能让代码在多核CPU高效执行。为了编写这类处理批量数据的并行类库,需要在语言层面修改现有的java:增加Lambda表达式。1.2 什么是函数式编程每个人对函数式编程有不同的理解,其核心是:使用不可变值和函数,函数对一个值进行处理,映射成另一个值。1.3 示例后面示例主要围绕一个常见问题进行领域构造:音乐 Artist:创建音乐的个人或团队 name:艺术家的名字 member:乐队成员 origin:乐队来自于哪里 Track:专辑的一支曲目 name:曲目名称 Album:专辑,有若干曲目组成 name:专辑名 tracks:专辑上所有曲目的列表 musicians:参与创作本专辑的艺术家列表

June 4, 2020 · 1 min · jiezi

java8的stream流练习2-改选自java8实战书

import lombok.extern.slf4j.Slf4j;import java.util.Arrays;import java.util.Comparator;import java.util.List;import java.util.Optional;import java.util.stream.Collectors;/** * @Author weijun.nie * @Date 2020/5/26 10:35 * @Version 1.0 */@Slf4jpublic class TranTestDemo { public static void main(String[] args) { Trader niuj = new Trader("nj牛进", "上海"); Trader maik = new Trader("mk买康", "香港"); Trader niuf = new Trader("nf牛发", "上海"); Trader jiny = new Trader("jy靳扬", "上海"); List<Transaction> transactions = Arrays.asList( new Transaction(jiny, 2011, 300), new Transaction(niuj, 2012, 1000), new Transaction(niuj, 2011, 400), new Transaction(maik, 2012, 710), new Transaction(maik, 2012, 700), new Transaction(niuf, 2012, 950) ); // (1) 找出2011年发生的所有交易,并按交易额排序(从低到高)。 // (1-1). 打印 log.info("======================遍历打印1==============================="); transactions.stream().filter(t -> 2011 == t.getYear()).sorted((t1, t2) -> Integer.valueOf(t1.getValue()).compareTo(Integer.valueOf(t2.getValue()))).forEach(System.out::println); log.info("======================遍历打印2==============================="); transactions.stream().filter(t -> 2011 == t.getYear()).sorted(Comparator.comparingInt(Transaction::getValue)).forEach(System.out::println); log.info("======================遍历打印3==============================="); transactions.stream().filter(t -> 2011 == t.getYear()).sorted(Comparator.comparing(Transaction::getValue)).forEach(System.out::println); log.info("======================返回集合==============================="); // (1-2). 返回List<Transaction> List<Transaction> result1List = transactions.stream().filter(t -> 2011 == t.getYear()).sorted(Comparator.comparingInt(Transaction::getValue)).collect(Collectors.toList()); log.info("找出2011年发生的所有交易,并按交易额排序(从低到高):\t{}", result1List); // (2) 交易员都在哪些不同的城市工作过? log.info("====================================================="); List<String> cities = transactions.stream().map(t -> t.getTrader().getCity()).distinct().collect(Collectors.toList()); log.info("交易员都在哪些不同的城市工作过:\t{}", cities); // (3) 查找所有来自于上海的交易员,并按姓名排序。 log.info("====================================================="); List<Trader> SHTradersSorted = transactions.stream().map(Transaction::getTrader).filter(t -> t.getCity().equals("上海")).sorted(Comparator.comparing(Trader::getName)).collect(Collectors.toList()); log.info("查找所有来自于上海的交易员,并按姓名排序:\t{}", SHTradersSorted); // (4) 返回所有交易员的姓名字符串,按字母顺序排序。 log.info("====================================================="); List<String> traderNamesList = transactions.stream().map(t -> t.getTrader().getName()).distinct().sorted().collect(Collectors.toList()); log.info("返回所有交易员的姓名字符串,按字母顺序排序:\t{}", traderNamesList); // (5) 有没有交易员是在北京工作的? log.info("====================================================="); boolean hasInBeijing = transactions.stream().filter(t -> t.getTrader().getCity().equals("北京")).findAny().isPresent(); log.info("有没有交易员是在北京工作的:{}", hasInBeijing); // (6) 打印生活在上海的交易员的所有交易额。 log.info("====================================================="); List<Integer> valuesInShanghai = transactions.stream().filter(t -> t.getTrader().getCity().equals("上海")).map(t -> t.getValue()).collect(Collectors.toList()); log.info("打印生活在上海的交易员的所有交易额:\t{}", valuesInShanghai); // (7) 所有交易中,最高的交易额是多少? log.info("====================================================="); Optional<Integer> max = transactions.stream().map(t -> t.getValue()).max(Comparator.comparingInt(Integer::intValue)); log.info("所有交易中,最高的交易额是多少\t{}", max.get()); // (8) 找到交易额最小的交易。 log.info("====================================================="); Transaction minTransanction = transactions.stream().collect(Collectors.minBy(Comparator.comparing(t -> t.getValue()))).get(); log.info("找到交易额最小的交易\t:{}", minTransanction); }}======================遍历打印1==============================={Trader{name='jy靳扬', city='上海'}, year: 2011, value:300}{Trader{name='nj牛进', city='上海'}, year: 2011, value:400}======================遍历打印2==============================={Trader{name='jy靳扬', city='上海'}, year: 2011, value:300}{Trader{name='nj牛进', city='上海'}, year: 2011, value:400}======================遍历打印3==============================={Trader{name='jy靳扬', city='上海'}, year: 2011, value:300}{Trader{name='nj牛进', city='上海'}, year: 2011, value:400}======================返回集合=============================== ...

May 27, 2020 · 2 min · jiezi

使用Java8-Stream-API对Map按键或值进行排序

一、什么是Java 8 Stream使用Java 8 Streams,我们可以按键和按值对映射进行排序。下面是它的工作原理: 将Map或List等集合类对象转换为Stream对象使用Streams的sorted()方法对其进行排序最终将其返回为LinkedHashMap(可以保留排序顺序)sorted()方法以Comparator作为参数,从而可以按任何类型的值对Map进行排序。如果对Comparator不熟悉,可以看本号前几天的文章,有一篇文章专门介绍了使用Comparator对List进行排序。 二、学习一下HashMap的merge()函数在学习Map排序之前,有必要讲一下HashMap的merge()函数,该函数应用场景就是当Key重复的时候,如何处理Map的元素值。这个函数有三个参数: 参数一:向map里面put的键参数二:向map里面put的值参数三:如果键发生重复,如何处理值。可以是一个函数,也可以写成lambda表达式。 String k = "key"; HashMap<String, Integer> map = new HashMap<String, Integer>() {{ put(k, 1); }}; map.merge(k, 2, (oldVal, newVal) -> oldVal + newVal);看上面一段代码,我们首先创建了一个HashMap,并往里面放入了一个键值为k:1的元素。当我们调用merge函数,往map里面放入k:2键值对的时候,k键发生重复,就执行后面的lambda表达式。表达式的含义是:返回旧值oldVal加上新值newVal(1+2),现在map里面只有一项元素那就是k:3。 其实lambda表达式很简单:表示匿名函数,箭头左侧是参数,箭头右侧是函数体。函数的参数类型和返回值,由代码上下文来确定。三、按Map的键排序下面一个例子使用Java 8 Stream按Map的键进行排序: // 创建一个Map,并填入数据Map<String, Integer> codes = new HashMap<>();codes.put("United States", 1);codes.put("Germany", 49);codes.put("France", 33);codes.put("China", 86);codes.put("Pakistan", 92);// 按照Map的键进行排序Map<String, Integer> sortedMap = codes.entrySet().stream() .sorted(Map.Entry.comparingByKey()) .collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (oldVal, newVal) -> oldVal, LinkedHashMap::new ) );// 将排序后的Map打印sortedMap.entrySet().forEach(System.out::println);看上文中第二段代码: ...

November 2, 2019 · 1 min · jiezi

深入探寻JAVA8-part2浅谈几个内置的函数式接口

前情提要深入探寻JAVA8 part1:函数式编程与Lambda表达式看此文前,不熟悉函数式编程和Lambda表达式的可以先看一下上文回忆一下。 本文将会简单介绍Java8中内置的一些函数式接口 回顾函数式接口函数式接口就是只定义一个抽象方法的接口。在JAVA8以前,就有很多符合函数式接口定义的接口。 //比较器public interface Comparator<T> { int compare(T o1, T o2);}//多线程接口public interface Runnable{ void run();}因为JAVA8中还引入了默认方法的概念,所以即使接口中有多个默认方法,只要接口之定义了一个抽象方法,就满足函数式接口的定义。 JAVA8中对这些可以定义为函数式接口的接口加了一个@FuncationalInterface注解。如果一个接口中定义了多个抽象方法,又添加了这个注解,则会在编译时抛出错误提示。 Consumerpackage java.util.function;import java.util.Objects;@FunctionalInterfacepublic interface Consumer<T> { void accept(T var1); default Consumer<T> andThen(Consumer<? super T> var1) { Objects.requireNonNull(var1); return (var2) -> { this.accept(var2); var1.accept(var2); }; }}这是JAVA8中对Consumer的定义,该函数式接口可以接收一个T类型的数据,并对该数据进行操作。JDK中一个典型的例子是forEach中对Consumer的使用,下面给出了ArrayList中的forEach源码。 @Override public void forEach(Consumer<? super E> consumer) { checkNotNull(consumer); for (E e : array) { consumer.accept(e); } }forEach的接口定义中传入了一个Consumer接口,并且调用Consumer的accept方法对数组中的每个元素进行处理。加入这是一个String数组,则可以使用如下的方式进行调用 list.forEach((String s) -> System.out::println);在Consumer的接口定义中,还有一个andThen的默认方法,后面会再介绍一下这个默认方法。 ...

October 15, 2019 · 2 min · jiezi

什么是lambdalambda表达式你用对了吗

什么是lambda,lambda表达式你用对了吗?Java 8于2014年3月18日发布以来,Lambdas现在已经成为Java环境中熟悉的一部分。带来了期待已久的lambda表达式(又名闭包)特性。它们对我们用Java编程的影响比平台历史上的任何其他变化都要大。什么是lambda表达式?在数学和计算中,lambda表达式通常是一个函数:对于某些或所有输入值的组合,它指定一个输出值。Java中的Lambda表达式将函数的概念引入到语言中。在传统的Java术语中,lambdas可以理解为一种具有更紧凑语法的匿名方法,它还允许省略修饰符、返回类型,在某些情况下还允许省略参数类型。 语法lambda的基本语法是 (parameters) -> expression或者 (parameters) -> { statements; }例子// 1(int x, int y) -> x + y // 接受两个整数并返回它们的和// 2(x, y) -> x - y // 接受两个数字并返回它们的差值// 3() -> 42 // 不接受任何值并返回42// 4(String s) -> System.out.println(s) // 接受一个字符串,将其值打印到控制台,然后什么也不返回//5x -> 2 * x // 接受一个数字,并返回加倍的结果// 6c -> { int s = c.size(); c.clear(); return s; } // 获取一个集合,清除它,并返回它以前的大小笔记参数类型可以显式声明(例1、4),也可以隐式推断(例2、5、6)。声明型和推断型参数不能混合在一个lambda表达式中。主体可以是块(用括号括起来,例6)或表达式(例1-5)。块体可以返回一个值(例6),也可以什么都不返回。在块体中使用或省略return关键字的规则与普通方法体的规则相同。如果主体是一个表达式,它可能返回一个值(例如1、2、3、5)或什么也不返回(例如4)。单个推断类型参数可以省略括号(例如5、6)例6的注释应该被理解为lambda可以作用于一个集合。同样,根据它出现的上下文,它可以作用于其他类型的对象,这些对象具有方法大小和clear,以及适当的参数和返回类型。为什么lambda表达式被添加到Java中?在Java 8中,其目的是为集合提供方法,这些方法将获取函数并以不同的方式使用它们来处理它们的元素。我们将使用一个非常简单的方法forEach作为示例,它获取一个函数并将其应用于每个元素。这种变化带来的好处是集合现在可以在内部组织自己的迭代,将并行化的责任从客户端代码转移到库代码。但是,要让客户机代码利用这一点,需要有一种简单的方法为集合方法提供函数。目前,实现此目的的标准方法是通过适当接口的匿名类实现。但是,用于定义匿名内部类的语法太过笨拙,无法实现这一目的。例如,集合上的forEach方法将获取消费者接口的一个实例,并为每个元素调用它的accept方法: interface Consumer<T> { void accept(T t); } 假设我们要使用forEach来转置java.awt.Point列表中每个元素的x和y坐标。使用匿名内部类实现的消费者,我们将传递在像这样的换位函数: ...

October 8, 2019 · 1 min · jiezi

深入探寻JAVA8-part1函数式编程与Lambda表达式

开篇在很久之前粗略的看了一遍《Java8 实战》。客观的来,说这是一本写的非常好的书,它由浅入深的讲解了JAVA8的新特性以及这些新特性所解决的问题。最近重新拾起这本书并且对书中的内容进行深入的挖掘和沉淀。接下来的一段时间将会结合这本书,以及我自己阅读JDK8源码的心路历程,来深入的分析JAVA8是如何支持这么多新的特性的,以及这些特性是如何让Java8成为JAVA历史上一个具有里程碑性质的版本。 Java8的新特性概览在这个系列博客的开篇,结合Java8实战中的内容,先简单列举一下JAVA8中比较重要的几个新特性: 函数式编程与Lambda表达式Stram流处理Optional解决空指针噩梦异步问题解决方案CompletableFuture颠覆Date的时间解决方案后面将针对每个专题发博进行详细的说明。 简单说一说函数式编程函数式编程的概念并非这两年才涌现出来,这篇文章用一种通俗易懂的方式对函数式编程的理念进行讲解。顾名思义,函数式编程的核心是函数。函数在编程语言中的映射为方法,函数中的参数被映射为传入方法的参数,函数的返回结果被映射为方法的返回值。但是函数式编程的思想中,对函数的定义更加严苛,比如参数只能被赋值一次,即参数必须为final类型,在整个函数的声明周期中不能对参数进行修改。这个思想在如今看来是不可理喻的,因为这意味着任何参数的状态都不能发生变更。 那么函数式编程是如何解决状态变更的问题呢?它是通过函数来实现的。下面给了一个例子: String reverse(String arg) { if(arg.length == 0) { return arg; } else { return reverse(arg.substring(1, arg.length)) + arg.substring(0, 1); }}对字符串arg进行倒置并不会修改arg本身,而是会返回一个全新的值。它完全符合函数式编程的思想,因为在整个函数的生命周期中,函数中的每一个变量都没有发生修改。这种不变行在如今称为Immutable思想,它极大的减少了函数的副作用。这一特性使得它对单元测试,调试以及编发编程极度友好。因此在面向对象思想已经成为共识的时代,被重新提上历史的舞台。 但是,编程式思想并不只是局限于此,它强调的不是将所有的变量声明为final,而是将这种可重入的代码块在整个程序中自由的传递和复用。JAVA中是通过对象的传递来实现的。举个例子,假如现在有一个筛选订单的功能,需要对订单从不同的维度进行筛选,比如选出所有已经支付完成的订单,或是选出所有实付金额大于100的订单。 简化的订单模型如下所示: public class Order{ private String orderId; //实付金额 private long actualFee; //订单创建时间 private Date createTime; private boolean isPaid}接着写两段过滤逻辑分别实现选出已经支付完成的订单,和所有实付金额大于100的订单 //选出已经支付完成的订单public List<Order> filterPaidOrder(List<Order> orders) { List<Order> paidOrders = new ArrayList<>(); for(Order order : orders) { if(order.isPaid()) { paidOrders.add(order); } } return paidOrdres;}//选出实付金额大于100的订单public List<Order> filterByFee(List<Order> orders) { List<Order> resultOrders = new ArrayList<>(); for(Order order : orders) { if(order.getActualFee()>100) { resultOrders.add(order); } } return resultOrders;}可以看到,上面出现了大量的重复代码,明显的违背了DRY(Dont Repeat Yourself)原则,可以先通过模板模式将判断逻辑用抽象方法的形式抽取出来,交给具体的子类来实现。代码如下: ...

October 7, 2019 · 2 min · jiezi

Java87自制多糖-switch

背景JDK 12 和 JDK 13 已经发布了,伴随着许多对 Java 语法的小改进,比如我们非常熟悉的 switch: JDK12 之前switch (type) { case "all": System.out.println("列出所有帖子"); break; case "auditing": System.out.println("列出审核中的帖子"); break; case "accepted": System.out.println("列出审核通过的帖子"); break; case "rejected": System.out.println("列出审核不通过的帖子"); break; default: System.out.println("参数'type'错误,请检查"); break;}JDK12switch (type) { case "all" -> System.out.println("列出所有帖子"); case "auditing" -> System.out.println("列出审核中的帖子"); case "accepted" -> System.out.println("列出审核通过的帖子"); case "rejected" -> System.out.println("列出审核不通过的帖子"); default -> System.out.println("参数'type'错误,请检查");}JDK13String value = switch (i) { case 0 -> "zero" case 1 -> "one" case 2 -> "two" default -> "many"};新特性很美好,但是如今在业界最流行的版本依然是 JDK8,所以想要在生产环境用上这么舒服的 switch,目前看来还是遥遥无期。好在我们还有 Lambda,正所谓 “自己动手,丰衣足食”,我们来试试能不能自己做出一个和 JDK12 & JDK13 的 swtich 类似的东西,好给我们平淡的编码生活,加点糖。 ...

October 5, 2019 · 4 min · jiezi

java8-lambda表达式二方法引用

当我们在使用lambda去表示某个函数式接口的实例时,需要在lambda表达式的主体里去编写函数式接口抽象方法的实现,如果在现有的类中已经存在与抽象方法类似的方法了,我们希望直接引用现有的方法,而不用再去重新写实现了。方法引用让你可以重复使用现有的方法定义,并像Lambda一样传递它们。 方法引用和lambda表达式拥有相同的特性,它们都需要代表一个目标类型,并需要被转化为函数式接口的实例,不过我们并不需要为方法引用提供方法体,我们可以直接通过方法名称引用已有方法方。方法引用要使用到操作符“::”,左边是类名或者对象名,右边是方法名或者关键字new 方法引用分类首先被引用方法的返回值类型要和函数式接口抽象方法的返回值类型一致,至于参数列表要根据每种引用方式而定。 静态方法引用引用语法:ClassName::staticMethodName//Function<String, Long> f = x -> Long.valueOf(x);Function<String, Long> f = Long::valueOf;Long result = f.apply("10");静态方法引用时,静态方法要与函数式接口抽象方法参数列表一致 类型的实例方法引用引用语法:ClassName::methodName//BiPredicate<String, String> bpredicate = (x,y) -> x.equals(y);BiPredicate<String, String> bpredicate = String::equals;boolean result = bpredicate.test("abc", "abcd");//ToIntFunction<String> f = (s) -> s.length();ToIntFunction<String> f = String::length;int result2 = f.applyAsInt("hello");类型的实例方法引用时,函数式接口抽象方法的第一个参数是被引用方法的调用者,第二个参数(或者无参)是被引用方法的参数 外部对象方法引用引用语法:objectName::methodNameList<String> list = Arrays.asList("hello", "world", "aaa");//Predicate<String> p = (s) -> list.contains(s);Predicate<String> p = list::contains; //list是lambda的一个外部对象boolean result = p.test("aaa");外部对象方法引用时,被引用方法与函数式接口抽象方法参数列表一致 构造器引用引用语法:ClassName::new//Function<Long, Date> fun = (number) -> new Date(number);Function<Long, Date> fun = Date::new;Date date = fun.apply(1000000000000L);构造器引用时,被引用的构造方法与函数式接口抽象方法参数列表一致 ...

September 6, 2019 · 1 min · jiezi

修炼内功Java8-Lambda究竟是不是匿名类的语法糖

本文已收录【修炼内功】跃迁之路 初次接触Java8的时候感觉Lambda表达式很神奇(Lambda表达式带来的编程新思路),但又总感觉它就是匿名类或者内部类的语法糖而已,只是语法上更为简洁罢了,如同以下的代码 public class Lambda { private static void hello(String name, Consumer<String> printer) { printer.accept(name); } public static void main(String[] args) { hello("lambda", (name) -> System.out.println("Hello " + name)); hello("匿名类", new Consumer<String> () { @Override public void accept(String name) { System.out.println("Hello " + name); } }); hello("内部类", new SupplierImpl()); } static class SupplierImpl implements Consumer<String> { @Override public void accept(String name) { System.out.println("Hello " + name); } }}编译后会产生三个文件 ...

June 25, 2019 · 2 min · jiezi

乐字节Java8核心特性之Optional

大家好啊,上次小乐给大家介绍了Java8最最重要的一个特性——Stream流,点击可以回顾哦。 Optional<T>类(java.util.Optional)是一个容器类,代表一个值存在或不存在,原来用null表示一个值不存在,现在Optional可以更好的表达这个概念。并且可以避免空指针异常。 1、Optinal对象构建&值获取方法 实例代码如下 Optional<String> optional = Optional.of("java8");// NullPointerException空指针异常 值不能为空optional = Optional.of(null);optional = Optional.ofNullable("java8");System.out.println(optional.get());System.out.println(optional.orElse("java"));System.out.println(optional.orElseGet(()-> "java"));System.out.println(optional.orElseThrow(()->new RuntimeException()));// 值可空 推荐使用optional = Optional.ofNullable(null);// 运行时抛出NoSuchElementException异常System.out.println(optional.get());System.out.println(optional.orElse("java"));System.out.println(optional.orElseGet(()-> "java"));System.out.println(optional.orElseThrow(()->new RuntimeException()));2、Optional 逻辑判断操作这里可以使用Optional提供的API相关方法来执行逻辑判断操作 . 3、用户记录查询-消除null判断以用户模块为例,UserService中提供queryUserById方法供客户端调用,如下: public User queryUserById(Integer userId){ return null;}客户端调用Java8以前逻辑代码为例避免null通常为如下形式 User user= userService.queryUserById(10);if(null != user){ System.out.println("匹配到该用户"); /** * 执行其他操作 */}else{ System.out.println("用户不存在");}使用Optional 形式如下: Optional<User> userOptional = Optional.ofNullable(user);// 使用isPresent 方法进行判断if(userOptional.isPresent()){ System.out.println("匹配到该用户"); /** * 执行其他操作 */}else{ System.out.println("用户不存在");}当然,既然使用了Optional了,对于if else 的代码通常也是可以给省略掉 如下(程序逻辑只关注非空的情况,使用ifPresent 进行if判断): // 使用ifPresent 执行if 判断操作 userOptional.ifPresent((u)->{ System.out.println("匹配到该用户"); /** * 执行其他操作 */ });使用map orElse方法同样也可以执行if else的逻辑判断 如下: ...

June 10, 2019 · 1 min · jiezi

乐字节Java8核心特性实战之Stream流

大家好,我是乐字节的小乐。说起流,我们会联想到手机、电脑组装流水线,物流仓库商品包装流水线等等,如果把手机 ,电脑,包裹看做最终结果的话,那么加工商品前的各种零部件就可以看做数据源,而中间一系列的加工作业操作,就可以看做流的处理。 一、概念Java Se中对于流的操作有输入输出IO流,而Java8中引入的Stream 属于Java API中的一个新成员,它允许你以声明性方式处理数据集合,Stream 使用一种类似 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。 注意这里的流操作可以看做是对集合数据的处理。 简单来说,流是一种数据渠道,用于操作数据源(集合、数组、文件等)所生产的元素序列。 源-流会使用一个提供数据的源,如集合、数组或输入|输出资源。 从有序集生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致 元素序列-就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序值。数据处理操作-流的数据处理功能支持类似于数据库的操作(数据筛选、过滤、排序等操作)。流水线-多个流操作本身会返回一个流,多个操作就可以链接起来,成为数据处理的一道流水线。二、流 & 集合计算的时期 集合中数据都是计算完毕的数据,例如从数据库中查询用户记录 按用户id 查询 降序排列 然后通过list 接收用户记录,数据的计算已在放入集合前完成 流中数据按需计算,按照使用者的需要计算数据,例如通过搜索引擎进行搜索,搜索出来的条目并不是全部呈现出来的,而且先显示最符合的前 10 条或者前 20 条,只有在点击 “下一页” 的时候,才会再输出新的 10 条。流的计算也是这样,当用户需要对应数据时,Stream 才会对其进行计算处理。 外部迭代与内部迭代 把集合比作一个工厂的仓库的话,一开始工厂硬件比较落后,要对货物作什么修改,此时工人亲自走进仓库对货物进行处理,有时候还要将处理后的货物转运到另一个仓库中。此时对于开发者来说需要亲自去做迭代,一个个地找到需要的货物,并进行处理,这叫做外部迭代。 当工厂发展起来后,配备了流水线作业,工厂只要根据需求设计出相应的流水线,然后工人只要把货物放到流水线上,就可以等着接收成果了,而且流水线还可以根据要求直接把货物输送到相应的仓库。这就叫做内部迭代,流水线已经帮你把迭代给完成了,你只需要说要干什么就可以了(即设计出合理的流水线)。相当于 Java8 引入的Stream 对数据的处理实现了”自动化”操作。 三、流操作过程 整个流操作就是一条流水线,将元素放在流水线上一个个地进行处理。需要注意的是:很多流操作本身就会返回一个流,所以多个操作可以直接连接起来, 如下图这样,操作可以进行链式调用,并且并行流还可以实现数据流并行处理操作。 总的来说,流操作过程分为三个阶段: 创建借助数据源创建流对象 中间处理筛选、切片、映射、排序等中间操作 终止流匹配、汇总、分组等终止操作 四、流的创建对流操作首先要创建对应的流,流的创建集中形式如下: 4.1 集合创建流在 Java 8 中, 集合接口有两个方法来生成流: stream() − 为集合创建串行流。parallelStream() − 为集合创建并行流。示例代码如下: public static void main(String[] args) { /** * 定义集合l1 并为集合创建串行流 */ List<String> l1 = Arrays.asList("周星驰", "周杰伦", "周星星", "周润发"); // 返回串行流 l1.stream(); // 返回并行流 l1.parallelStream();}上述操作得到的流是通过原始数据转换过来的流,除了这种流创建的基本操作外,对于流的创建还有以下几种方式。 ...

June 8, 2019 · 4 min · jiezi

乐字节Java8核心特性实战之方法引用

大家好,我是乐字节的小乐,上一次我们说到了Java8核心特性之函数式接口,接下来我们继续了解Java8又一核心特性——方法引用。 Java8 中引入方法引用新特性,用于简化应用对象方法的调用, 方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。 方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文。计算时,方法引用会创建函数式接口的一个实例。 当Lambda表达式中只是执行一个方法调用时,不用Lambda表达式,直接通过方法引用的形式可读性更高一些。方法引用是一种更简洁易懂的Lambda表达式。 1、语法目标引用::方法名称 目标引用:类名、实例对象 方法名称:实例方法名、静态方法名 等效Lambda的方法引用示例如下: 2、方法引用分类Java8 中对于方法引用主要分为四大类: 构造器引用 Class::new 静态方法引用 Class::static_method 实例对象方法引用Instance::method 特定类型任意对象的实例方法引用Class:: method 3、构造器引用语法是Class::new,或者更一般的Class< T >::new 实例如下 /*** Lambda 表达式 实例化User 对象*/Supplier<User> s01 =()->new User();Function<Integer,User> f01 = (id)->{ return new User(id);};f01.apply(2019);BiFunction<Integer,String,User> bf01 = (id,uname)->{ return new User(id,uname);};bf01.apply(2019,"admin");//方法引用 等价写法Supplier<User> s02 = User::new;Function<Integer,User> f02 = User::new;f02.apply(2019);BiFunction<Integer,String,User> bf02 = User::new;bf02.apply(2019,"admin");4、静态方法引用 语法是Class::static_method,实例如下: // 判断字符串是否为空串Function<String,Boolean> f03 = (str)-> StringUtils.isBlank(str);System.out.println(f03.apply(""));// Base64 对输入字符串执行编码操作Supplier<Base64.Encoder> s01 = ()->Base64.getEncoder();s01.get().encodeToString("java8 is so easy!!!".getBytes());Function<String,Boolean> f04 = StringUtils::isBlank;System.out.println(f04.apply(""));Supplier<Base64.Encoder> s02 = Base64::getEncoder;s02.get().encodeToString("java8 is so easy!!!".getBytes());5、实例对象方法引用语法是Instance::method ,此时引用方法时必须存在实例,示例如下 ...

June 4, 2019 · 1 min · jiezi

乐字节Java8核心特性实战之函数式接口

大家好,上一篇小乐给大家讲述了《乐字节-Java8核心特性-Lambda表达式》,点击回顾。接下来继续:Java8核心特性之函数式接口。 什么时候可以使用Lambda?通常Lambda表达式是用在函数式接口上使用的。从Java8开始引入了函数式接口,其说明比较简单:函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。 java8引入@FunctionalInterface 注解声明该接口是一个函数式接口。 一、语法抽象方法有且仅有一个接口使用@FunctionalInterface 注解进行标注接口中可以存在默认方法和静态方法实现如下形式: /** * 定义函数式接口 * 接口上标注@FunctionalInterface 注解 */@FunctionalInterfacepublic interface ICollectionService { /** * 定义打印方法 */ void print();}在Java8 以前,已有大量函数式接口形式的接口(接口中只存在一个抽象方法),只是没有强制声明。例如java.lang.Runnable,java.util.concurrent.Callable,java.security.PrivilegedAction,java.io.FileFilter等,Java8 新增加的函数接口在java.util.function 包下,它包含了很多类,用来支持 Java的 函数式编程,该包中的函数式接口如下: 序号接口 & 描述1BiConsumer<T,U>代表了一个接受两个输入参数的操作,并且不返回任何结果2BiFunction<T,U,R>代表了一个接受两个输入参数的方法,并且返回一个结果3BinaryOperator<T>代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果4BiPredicate<T,U>代表了一个两个参数的boolean值方法5BooleanSupplier代表了boolean值结果的提供方6Consumer<T>代表了接受一个输入参数并且无返回的操作7DoubleBinaryOperator代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。8DoubleConsumer代表一个接受double值参数的操作,并且不返回结果。9DoubleFunction<R>代表接受一个double值参数的方法,并且返回结果10DoublePredicate代表一个拥有double值参数的boolean值方法11DoubleSupplier代表一个double值结构的提供方12DoubleToIntFunction接受一个double类型输入,返回一个int类型结果。13DoubleToLongFunction接受一个double类型输入,返回一个long类型结果14DoubleUnaryOperator接受一个参数同为类型double,返回值类型也为double 。15Function<T,R>接受一个输入参数,返回一个结果。16IntBinaryOperator接受两个参数同为类型int,返回值类型也为int 。17IntConsumer接受一个int类型的输入参数,无返回值 。18IntFunction<R>接受一个int类型输入参数,返回一个结果 。19IntPredicate:接受一个int输入参数,返回一个布尔值的结果。20IntSupplier无参数,返回一个int类型结果。21IntToDoubleFunction接受一个int类型输入,返回一个double类型结果 。22IntToLongFunction接受一个int类型输入,返回一个long类型结果。23IntUnaryOperator接受一个参数同为类型int,返回值类型也为int 。24LongBinaryOperator接受两个参数同为类型long,返回值类型也为long。25LongConsumer接受一个long类型的输入参数,无返回值。26LongFunction<R>接受一个long类型输入参数,返回一个结果。27LongPredicateR接受一个long输入参数,返回一个布尔值类型结果。28LongSupplier无参数,返回一个结果long类型的值。29LongToDoubleFunction接受一个long类型输入,返回一个double类型结果。30LongToIntFunction接受一个long类型输入,返回一个int类型结果。31LongUnaryOperator接受一个参数同为类型long,返回值类型也为long。32ObjDoubleConsumer<T>接受一个object类型和一个double类型的输入参数,无返回值。33ObjIntConsumer<T>接受一个object类型和一个int类型的输入参数,无返回值。34ObjLongConsumer<T>接受一个object类型和一个long类型的输入参数,无返回值。35Predicate<T>接受一个输入参数,返回一个布尔值结果。36Supplier<T>无参数,返回一个结果。37ToDoubleBiFunction<T,U>接受两个输入参数,返回一个double类型结果38ToDoubleFunction<T>接受一个输入参数,返回一个double类型结果39ToIntBiFunction<T,U>接受两个输入参数,返回一个int类型结果。40ToIntFunction<T>接受一个输入参数,返回一个int类型结果。41ToLongBiFunction<T,U>接受两个输入参数,返回一个long类型结果。42ToLongFunction<T>接受一个输入参数,返回一个long类型结果。43UnaryOperator<T>接受一个参数为类型T,返回值类型也为T。对于Java8中提供的这么多函数式接口,开发中常用的函数式接口有以下几个 Predicate,Consumer,Function,Supplier。 二、函数式接口实例1、Predicatejava.util.function.Predicate<T> 接口定义了一个名叫 test 的抽象方法,它接受泛型 T 对象,并返回一个boolean值。在对类型 T进行断言判断时,可以使用这个接口。通常称为断言型接口 。 字符串判空 Predicate<String> p01=(str)->str.isEmpty()||str.trim().isEmpty(); /** * 测试传入的字符串是否为空 */ System.out.println(p01.test("")); System.out.println(p01.test(" ")); System.out.println(p01.test("admin"));用户合法性校验接口静态方法完成手机号合法校验功能,方法返回函数式接口Predicate public interface MyStringInter { public final String checkPhone= "^((13[0-9])|(14[5,7,9])|(15([0-3]|[5-9]))|(16[0-9])" + "|(17[0,1,3,5,6,7,8])|(18[0-9])|(19[8|9]))\\d{8}$"; /** * 用户手机格式合法性 * 返回L函数式接口Predicate 的实现 Lambda表达式 * @return */ static Predicate<String> checkPhone(){ return (e)-> { return Pattern.compile(checkPhone).matcher(e).matches(); }; }}2、Consumerjava.util.function.Consumer<T>接口定义了一个名叫 accept 的抽象方法,它接受泛型T,没有返回值(void)。如果需要访问类型 T 的对象,并对其执行某些操作,可以使用这个接口,通常称为消费型接口。 ...

June 3, 2019 · 2 min · jiezi

修炼内功Java8-Stream是怎么工作的

Java8中新增的Stream,相信使用过的同学都已经感受到了它的便利,允许你以声明性的方式处理集合,而不用去做繁琐的for-loop/while-loop,并且可以以极低的成本并行地处理集合数据 如果需要从菜单中筛选出卡路里在400以下的菜品,并按卡路里排序后,输出菜品名称 在java8之前,需要进行两次显示迭代,并且还需要借助中间结果存储 List<Menu> lowCaloricDishes = new LinkedList<>();// 按照热量值进行筛选for(Dish dish : dishes) { if (dish.getCalories() < 400) { lowCaloricDishes.add(dish); }}// 按照热量进行排序lowCaloricDishes.sort(new Comparator<Dish>() { @Override public int compare(Dish d1, Dish d2) { return d1.getCalories().compareTo(d2.getCalories); }})// 提取名称List<String> lowCaloricDishesName = new LinkedList<>();for(Dish dish : lowCaloricDishes) { lowCaloricDishesName.add(dish.getName());}如果使用Stream API,只需要 List<String> lowCaloricDishesName = dishes.parallelStream() // 开启并行处理 .filter(d -> d.getCalories() < 400) // 按照热量值进行筛选 .sorted(Comparator.comparing(Dish::getCalories)) // 按照热量进行排序 .map(Dish::getName) // 提取名称 .collect(Collectors.toList()); // 将结果存入List甚至,可以写出更复杂的功能 ...

May 10, 2019 · 6 min · jiezi

【修炼内功】[Java8] 使用Optional的正确姿势及序列化问题

本文已收录【修炼内功】跃迁之路 Java8的Optional为解决'空'的问题带来了很多新思路,查看Optional源码,实现非常简单,逻辑也并不复杂。Stuart Marks在其一次演讲中花了约1个小时的时间来讲述如何正确的使用Optional (Optional - The Mother of All Bikesheds by Stuart Marks),也有人调侃道1 hour for Optional, you gotta be kidding me.使用Optional不难,但用好Optional并不容易 Stuart Marks在演讲中提到了Optional的基本作用 Optional is intended to provide a limited mechanism for library method return types where there is a clear need to represent "no result", and where using null for that is overwhelmingly likely to cause errors.在以往的编程模型中,对于“没有内容”,大多数情况需要使用null来表示,而null值总是被人忽略处理(判断),从而在使用过程中极易引起NPE异常 Optional的出现并不是为了替代null,而是用来表示一个不可变的容器,它可以包含一个非null的T引用,也可以什么都不包含(不包含不等于null),非空的包含被称作persent,而空则被称作absent 本质上讲Optional类似于异常检查,它迫使API用户去关注/处理Optional中是否包含内容,从而避免因为忽略null值检查而导致的一些潜在隐患 假设有一个函数用来根据ID查询学生信息public Student search(Long id),现在有一个需求,需要根据ID查询学生姓名 public String searchName(Long id) { Student student = search(id); return student.getName();}注意,search函数是可能返回null的,在这种情况下searchName很有可能会抛出NPE异常 ...

April 21, 2019 · 3 min · jiezi

Java8的流(stream)操作

Stream是什么Stream是Java8中新加入的api,更准确的说:Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作 。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性.以前我们处理复杂的数据只能通过各种for循环,不仅不美观,而且时间长了以后可能自己都看不太明白以前的代码了,但有Stream以后,通过filter,map,limit等等方法就可以使代码更加简洁并且更加语义化。流机器(动画来自 Tagir Valeev)Stream的效果就像上图展示的它可以先把数据变成符合要求的样子(map),吃掉不需要的东西(filter)然后得到需要的东西(collect)。而关于如何使用,下面就用一个简单的例子来说明。Stream的使用下面是一个简单的示例代码List<Dish> menu = …List<String> lowCaloricDishesName = menu.stream() //筛选出卡路里大于400的 .filter(d -> d.getCalories() < 400) //抽取名字属性创建一个新的流 .map(Dish::getName) //这个流按List类型返回 .collect(toList());在这段代码 filter 和 map 操作被称为中间操作,中间操作会返回一个新的流,而 collect 则被称为终端操作只有终端操作才会让整个流执行并关闭。也就是说 每个流只能遍历一次 ,因为collect以后这个流就已经关闭了。List<String> test = Arrays.asList(“Java8”, “In”, “Action”);Stream<String> s = title.stream();s.forEach(System.out::println);s.forEach(System.out::println); // 代码会抛出一个java.lang.IllegalStateException异常想了解更多Stream的api可以查阅官方文档。串行与并行Stream可以分为串行与并行两种,串行流和并行流差别就是单线程和多线程的执行。default Stream stream() : 返回串行流default Stream parallelStream() : 返回并行流stream()和parallelStream()方法返回的都是java.util.stream.Stream<E>类型的对象,说明它们在功能的使用上是没差别的。唯一的差别就是单线程和多线程的执行。性能问题记得以前正好看过一篇关于Stream的性能的文章,在此就直接引用结论了结果可以总结如下:1.对于简单操作,比如最简单的遍历,Stream串行API性能明显差于显示迭代,但并行的Stream API能够发挥多核特性。 2.对于复杂操作,Stream串行API性能可以和手动实现的效果匹敌,在并行执行时Stream API效果远超手动实现。所以,如果出于性能考虑,1. 对于简单操作推荐使用外部迭代手动实现,2. 对于复杂操作,推荐使用Stream API, 3. 在多核情况下,推荐使用并行Stream API来发挥多核优势,4.单核情况下不建议使用并行Stream API。如果出于代码简洁性考虑,使用Stream API能够写出更短的代码。即使是从性能方面说,尽可能的使用Stream API也另外一个优势,那就是只要Java Stream类库做了升级优化,代码不用做任何修改就能享受到升级带来的好处。

April 19, 2019 · 1 min · jiezi

乐字节-Java8新特性之Base64和重复注解与类型注解

上一篇小乐给大家说了《乐字节-Java8新特性之Date API》,接下来小乐继续给大家说一说Java8新特性之Base64和重复注解与类型注解。一、Base64在Java 8中,内置了Base64编解码相关的特性。Java 8中使用三种类型的Base64编解码:简易模式:输出是完全按照A-Za-z0-9+/字符集映射的。编码不会自己增加输出行,解码器也不会接受任何超出A-Za-z0-9+/范围的内容。URL模式:输出基于A-Za-z0-9+/的映射,但对于URL和文件名是安全的。MIME模式:输出对于MIME类型的内容是友好的。如果超过76个字符,则会换行输出。,并且换行符n之后会自动添加一个r。如果某行没有r则说明输出的内容已经结束。1、Base64 内部类与方法Base64相关的内部类:Base64.Encoder:这是一个静态类。实现了Base64的编码功能,格式遵循了RFC 4648和RFC 2045标准。Base64.Decoder:也是一个静态类。实现了Base64的解码功能。相关的方法:getEncoder():该方法返回一个使用基本Base64编码格式的Encoder对象。相反的解码方法是getDecoder()。getUrlEncoder():该方法返回一个使用URL类型的Base64编码格式的Encoder对象。相反的解码方法是getUrlDecoder()。getMimeEncoder():该方法返回一个使用MIME类型的Base64编码格式的Encoder对象。相反的解码方法是getMimeDecoder()。2、Base64 使用对于Base64应用场景 无论是传统软件还是互联网项目开发都是比较常见的,比如传统的邮件,Http Url 地址通常都会应用Base64 来对协议内容或Url 地址信息进行编解码操作。public static void main(String[] args) throws Exception { // 使用基本的Base64编码 String base64encodedString = Base64.getEncoder() .encodeToString(“java8 is so Easy!!!".getBytes(“utf-8”)); System.out.println(“Basic base64 encoding:” + base64encodedString); // 解码并输出结果 byte[] base64decodedBytes = Base64.getDecoder().decode(base64encodedString); System.out.println(“Original content: " + new String(base64decodedBytes, “utf-8”)); // 使用URL类型的Base64编码 base64encodedString = Base64.getUrlEncoder().encodeToString(“https://www.sina.com”.getBytes(“utf-8”)); System.out.println(“URL base64 encoding:” + base64encodedString); // MIME类型的Base64编码 StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < 10; ++i) { stringBuilder.append(UUID.randomUUID().toString()); } byte[] mimeBytes = stringBuilder.toString().getBytes(“utf-8”); String mimeEncodedString = Base64.getMimeEncoder().encodeToString(mimeBytes); System.out.println(“MIME base64 encoding:” + mimeEncodedString); }二、重复注解与类型注解Java5引入了注解特性,使得开发更加的灵活,特别是现在很多的应用都是基于注解零配置开发,都是建立在Annotation基础之上,同时使得开发变得简单,Java 8对注解处理提供了两点改进:可重复的注解及可用于类型的注解。 通常用于框架底层代码开发1、可重复注解定义与使用/** * 定义可重复注解 /@Repeatable(MyParams.class)@Target({ ElementType.FIELD, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface MyParam { String value() default “”;}@Target({ ElementType.FIELD, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface MyParams { MyParam[] value();}// 使用注解 方法上标注重复注解@MyParam(“hello”)@MyParam(“java8”)public void testAnnotation(){ System.out.println(“可重复注解测试…”);}/* 查找指定方法级别注解 遍历输出注解value 值*/public static void main(String[] args) throws Exception{ Class<TestAnnotation> clazz = TestAnnotation.class; Method method = clazz.getMethod(“testAnnotation”); MyParam[] params = method.getAnnotationsByType(MyParam.class); for (MyParam param : params) { System.out.println(param.value()); }}2、用于类型的注解@Repeatable(MyParams.class)@Target({ ElementType.FIELD, ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface MyParam { String value() default “”;}//使用public class TestAnnotation { private MyParam param;// 定义成员变量param 类型为MyParam 注解类型 public static void main(String[] args) throws Exception{ // 获取成员变量 并输出变量类型 Field field= clazz.getDeclaredField(“param”); System.out.println(field); System.out.println(field.getType()); }} ...

April 15, 2019 · 1 min · jiezi

乐字节-Java8新特性之Date API

上一篇文章,小乐给大家带来了Java8新特性之Optional,接下来本文将会给大家介绍Java8新特性之Date API前言:Java 8通过发布新的Date-Time API来进一步加强对日期与时间的处理。 旧版的 Java 中,日期时间 API 存在诸多问题 :非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,设计很差 − Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。时区处理麻烦 − 日期类并不提供国际化,没有时区支持Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:Local(本地) : 简化了日期时间的处理,没有时区的问题。Zoned(时区) − 通过制定的时区处理日期时间。新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。1、LocalDateTimeLocalDateTime ldt = LocalDateTime.now();// 获取系统当前时间System.out.println(ldt);LocalDateTime ldt2 = LocalDateTime.of(2019, 01, 01, 12, 12, 12, 888000000);// 构建LocalDateTime对象ldtSystem.out.println(ldt2);// 获取明年此时的时间LocalDateTime ldt3 = ldt.plusYears(1);System.out.println(ldt3);// 获取去年此刻时间LocalDateTime ldt4 = ldt.minusYears(1);System.out.println(ldt4);// 获取年System.out.println(ldt.getYear());// 获取月份System.out.println(ldt.getMonthValue());// 获取本月第某天System.out.println(ldt.getDayOfMonth());// 获取时System.out.println(ldt.getHour());// 获取分System.out.println(ldt.getMinute());// 获取秒System.out.println(ldt.getSecond());// 获取纳秒System.out.println(ldt.getNano());2、时间戳时间戳:以Unix元年:1970年1月1日 00:00:00 至目前时间之间的毫秒值 public static void testInstant(){ // 时间戳 Instant Instant ins1 = Instant.now(); // 默认获取UTC时间,协调世界时 System.out.println(ins1); OffsetDateTime odt = ins1.atOffset(ZoneOffset.ofHours(8)); System.out.println(odt); System.out.println(ins1.toEpochMilli()); Instant ins2 = Instant.ofEpochSecond(60); System.out.println(ins2);}3、日期时间间隔计算:Duration、Periodpublic static void testDuration(String[] args) { Instant ins1 = Instant.now(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } Instant ins2 = Instant.now(); Duration dura = Duration.between(ins1, ins2); System.out.println(dura.toMillis()); System.out.println("———————-"); LocalTime lt1 = LocalTime.now(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } LocalTime lt2 = LocalTime.now(); Duration dura2 = Duration.between(lt1, lt2); System.out.println(dura2.toMillis()); } public static void testPeriod(){ LocalDate ld1 = LocalDate.of(2015, 2, 2); LocalDate ld2 = LocalDate.now(); Period period = Period.between(ld1, ld2); System.out.println(period); System.out.println(“相差” + period.getYears() + “年” + period.getMonths() + “月” + period.getDays() + “天”); }4、时间校正:TemporalAdjusterpublic static void testTemporalAdjuster(){ LocalDateTime ldt = LocalDateTime.now(); System.out.println(ldt); LocalDateTime ldt2 = ldt.withDayOfMonth(10); System.out.println(ldt2); LocalDateTime ldt3 = ldt.with(TemporalAdjusters.next(DayOfWeek.SATURDAY)); System.out.println(ldt3);}5、日期时间格式化public static void testDateFormate(){ DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE; LocalDateTime ldt = LocalDateTime.now(); String strDate = ldt.format(dtf); System.out.println(strDate); System.out.println("—————————-"); DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern(“yyyy年MM月dd日 HH:mm:ss”); String strDate2 = dtf2.format(ldt); System.out.println(strDate2); System.out.println("—————————–"); LocalDateTime newDate = ldt.parse(strDate2, dtf2); System.out.println(newDate); }6、TimeZone 时区处理 // 时区的处理 public static void testTimeZone(){ LocalDateTime ldt = LocalDateTime.now(ZoneId.of(“Europe/Dublin”)); System.out.println(ldt); LocalDateTime ldt2 = LocalDateTime.now(ZoneId.of(“Europe/Dublin”)); ZonedDateTime zdt = ldt2.atZone(ZoneId.of(“Europe/Dublin”)); System.out.println(zdt); }这次就分享到这里了,后面小乐会继续给大家介绍Java8新特性的,请大家继续多多关注哦!乐字节只讲Java技术干货。 ...

April 13, 2019 · 2 min · jiezi

【修炼内功】[Java8] Lambda表达式里的陷阱

本文已收录【修炼内功】跃迁之路Lambdab表达式带来的好处就不再做过多的介绍了,这里重点介绍几点,在使用Lambda表达式过程中可能遇到的"陷阱"Effectively Final在使用Lambda表达式的过程中,经常会遇到如下的问题图中的sayWords为什么一定要是final类型,effectively final又是什么?但,如果改为如下,貌似问题又解决了似乎,只要对sayWords不做变动就可以如果将sayWords从方法体的变量提到类的属性中,情况又会有变化,即使对sayWords有更改,也会编译通过难道,就是因为局部变量和类属性的区别?在Java 8 in Action一书中有这样一段话You may be asking yourself why local variables have these restrictions. First, there’s a key difference in how instance and local variables are implemented behind the scenes. Instance variables are stored on the heap, whereas local variables live on the stack. If a lambda could access the local variable directly and the lambda were used in a thread, then the thread using the lambda could try to access the variable after the thread that allocated the variable had deallocated it. Hence, Java implements access to a free local variable as access to a copy of it rather than access to the original variable. This makes no difference if the local variable is assigned to only once—hence the restriction. Second, this restriction also discourages typical imperative programming patterns (which, as we explain in later chapters, prevent easy parallelization) that mutate an outer variable.首先,要理解Local Variables和Instance Variables在JVM内存中的区别Local Variables随Thread存储在Stack栈内存中,而Instance Variables则随Instance存储在Heap堆内存中Local Variables的回收取决于变量的作用域,程序的运行一旦超出变量的作用域,该内存空间便被立刻回收另作他用Instance Variables的回收取决于引用数,当再没有引用的时候,便会在一个"合适"的时间被JVM垃圾回收器回收试想,如果Lambda表达式引用了局部变量,并且该Lambda表达式是在另一个线程中执行,那在某种情况下该线程则会在该局部变量被收回后(函数执行完毕,超出变量作用域)被使用,显然这样是不正确的;但如果Lambda表达式引用了类变量,则该类(属性)会增加一个引用数,在线程执行完之前,引用数不会归为零,也不会触发JVM对其的回收操作但这解释不了图2的情况,同样是局部变量,只是未对sayWords做改动,也是可以通过编译的,这里便要介绍effectively finalBaeldung大神的博文中有这样一段话Accessing a non-final variable inside lambda expressions will cause the compile-time error. But it doesn’t mean that you should mark every target variable as final.According to the “effectively final” concept, a compiler treats every variable as final, as long as it is assigned only once.It is safe to use such variables inside lambdas because the compiler will control their state and trigger a compile-time error immediately after any attempt to change them.其中提到了 assigned only once,字面理解便是只赋值了一次,对于这种情况,编译器便会 treats variable as final,对于只赋值一次的局部变量,编译器会将其认定为effectively final,其实对于effectively final的局部变量,Lambda表达式中引用的是其副本,而该副本的是不会发生变化的,其效果就和final是一致的Throwing ExceptionJava的异常分为两种,受检异常(Checked Exception)和非受检异常(Unchecked Exception)Checked Exception, the exceptions that are checked at compile time. If some code within a method throws a checked exception, then the method must either handle the exception or it must specify the exception using throws keyword.Unchecked Exception, the exceptions that are not checked at compiled time. It is up to the programmers to be civilized, and specify or catch the exceptions.简单的讲,受检异常必须使用try…catch进行捕获处理,或者使用throws语句表明该方法可能抛出受检异常,由调用方进行捕获处理,而非受检异常则不用。受检异常的处理是强制的,在编译时检测。在Lambda表达式内部抛出异常,我们该如何处理?Unchecked Exception首先,看一段示例public class Exceptional { public static void main(String[] args) { Stream.of(3, 8, 5, 6, 0, 2, 4).forEach(lambdaWrapper(i -> System.out.println(15 / i))); } private static Consumer<Integer> lambdaWrapper(IntConsumer consumer) { return i -> consumer.accept(i); }}该段代码是可以编译通过的,但运行的结果是> 5> 1> 3> 2> Exception in thread “main” java.lang.ArithmeticException: / by zero at Exceptional.lambda$main$0(Exceptional.java:13) at Exceptional.lambda$lambdaWrapper$1(Exceptional.java:17) at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580) at Exceptional.main(Exceptional.java:13)由于Lambda内部计算时,由于除数为零抛出了ArithmeticException异常,导致流程中断,为了解决此问题可以在lambdaWrapper函数中加入try…catchprivate static Consumer<Integer> lambdaWrapper(IntConsumer consumer) { return i -> { try { consumer.accept(i); } catch (ArithmeticException e) { System.err.println(“Arithmetic Exception occurred : " + e.getMessage()); } };}再次运行> 5> 1> 3> 2> Arithmetic Exception occurred : / by zero> 7> 3对于Lambda内部非受检异常,只需要使用try…catch即可,无需做过多的处理Checked Exception同样,一段示例public class Exceptional { public static void main(String[] args) { Stream.of(3, 8, 5, 6, 0, 2, 4).forEach(lambdaWrapper(i -> writeToFile(i))); } private static Consumer<Integer> lambdaWrapper(IntConsumer consumer) { return i -> consumer.accept(i); } private static void writeToFile(int integer) throws IOException { // logic to write to file which throws IOException }}由于IOException为受检异常,该段将会程序编译失败按照Unchecked Exception一节中的思路,我们在lambdaWrapper中使用try…catch处理异常private static Consumer<Integer> lambdaWrapper(IntConsumer consumer) { return i -> { try { consumer.accept(i); } catch (IOException e) { System.err.println(“IOException Exception occurred : " + e.getMessage()); } };}但出乎意料,程序依然编译失败查看IntConsumer定义,其并未对接口accept声明异常@FunctionalInterfacepublic interface IntConsumer { /** * Performs this operation on the given argument. * * @param value the input argument / void accept(int value);}为了解决此问题,我们可以自己定义一个声明了异常的ThrowingIntConsumer@FunctionalInterfacepublic interface ThrowingIntConsumer<E extends Exception> { /* * Performs this operation on the given argument. * * @param value the input argument * @throws E / void accept(int value) throws E;}改造代码如下private static Consumer<Integer> lambdaWrapper(ThrowingIntConsumer<IOException> consumer) { return i -> { try { consumer.accept(i); } catch (IOException e) { System.err.println(“IOException Exception occurred : " + e.getMessage()); } };}但,如果我们希望在出现异常的时候终止流程,而不是继续运行,可以在获取到受检异常后抛出非受检异常private static Consumer<Integer> lambdaWrapper(ThrowingIntConsumer<IOException> consumer) { return i -> { try { consumer.accept(i); } catch (IOException e) { throw new RuntimeException(e.getMessage(), e.getCause()); } };}所有使用了ThrowingIntConsumer的地方都需要写一遍try…catch,有没有优雅的方式?或许可以从ThrowingIntConsumer下手@FunctionalInterfacepublic interface ThrowingIntConsumer<E extends Exception> { /* * Performs this operation on the given argument. * * @param value the input argument * @throws E / void accept(int value) throws E; /* * @return a IntConsumer instance which wraps thrown checked exception instance into a RuntimeException */ default IntConsumer uncheck() { return i -> { try { accept(i); } catch (final E e) { throw new RuntimeException(e.getMessage(), e.getCause()); } }; }}我们在ThrowingIntConsumer中定义了一个默认函数uncheck,其内部会自动调用Lambda表达式,并在捕获到异常后将其转为非受检异常并重新抛出此时,我们便可以将lambdaWrapper函数优化如下private static Consumer<Integer> lambdaWrapper(ThrowingIntConsumer<IOException> consumer) { return i -> consumer.accept(i).uncheck();}unCheck会将IOException异常转为RuntimeException抛出有没有更优雅一些的方式?由于篇幅原因不再过多介绍,感兴趣的可以参考 throwing-function 及 Vavrthis pointerJava中,类(匿名类)中都可以使用this,Lambda表达式也不例外public class ThisPointer { public static void main(String[] args) { ThisPointer thisPointer = new ThisPointer(“manerfan”); new Thread(thisPointer.getPrinter()).start(); } private String name; @Getter private Runnable printer; public ThisPointer(String name) { this.name = name; this.printer = () -> System.out.println(this); } @Override public String toString() { return “hello " + name; }}在ThisPointer类的构造函数中,使用Lambda表达式定义了printer属性,并重写了类的toString方法运行后结果> hello manerfanThisPointer类的构造函数中,将printer属性的定义改为匿名类public class ThisPointer { public static void main(String[] args) { ThisPointer thisPointer = new ThisPointer(“manerfan”); new Thread(thisPointer.getPrinter()).start(); } private String name; @Getter private Runnable printer; public ThisPointer(String name) { this.name = name; this.printer = new Runnable() { @Override public void run() { System.out.println(this); } }; } @Override public String toString() { return “hello " + name; }}重新运行后结果> ThisPointer$1@782b1823可见,Lambda表达式及匿名类中的this指向的并不是同一内存地址这里我们需要理解,在Lambda表达式中它在词法上绑定到周围的类 (定义该Lambda表达式时所处的类),而在匿名类中它在词法上绑定到匿名类Java语言规范在15.27.2描述了这种行为Unlike code appearing in anonymous class declarations, the meaning of names and the this and super keywords appearing in a lambda body, along with the accessibility of referenced declarations, are the same as in the surrounding context (except that lambda parameters introduce new names).The transparency of this (both explicit and implicit) in the body of a lambda expression – that is, treating it the same as in the surrounding context – allows more flexibility for implementations, and prevents the meaning of unqualified names in the body from being dependent on overload resolution.Practically speaking, it is unusual for a lambda expression to need to talk about itself (either to call itself recursively or to invoke its other methods), while it is more common to want to use names to refer to things in the enclosing class that would otherwise be shadowed (this, toString()). If it is necessary for a lambda expression to refer to itself (as if via this), a method reference or an anonymous inner class should be used instead.那,如何在匿名类中如何做到Lambda表达式的效果,获取到周围类的this呢?这时候就必须使用qualified this了,如下public class ThisPointer { public static void main(String[] args) { ThisPointer thisPointer = new ThisPointer(“manerfan”); new Thread(thisPointer.getPrinter()).start(); } private String name; @Getter private Runnable printer; public ThisPointer(String name) { this.name = name; this.printer = new Runnable() { @Override public void run() { System.out.println(ThisPointer.this); } }; } @Override public String toString() { return “hello " + name; }}运行结果如下> hello manerfan其他在排查问题的时候,查看异常栈是必不可少的一种方法,其会记录异常出现的详细记录,包括类名、方法名行号等等信息那,Lambda表达式中的异常栈信息是如何的?public class ExceptionStack { public static void main(String[] args) { new ExceptionStack().run(); } private Function<Integer, Integer> divBy100 = divBy(100); void run() { Stream.of(1, 7, 0, 6).filter(this::isEven).map(this::div).forEach(System.out::println); } boolean isEven(int i) { return 0 == i / 2; } int div(int i) { return divBy100.apply(i); } Function<Integer, Integer> divBy(int div) { return i -> div / i; }}这里我们故意制造了一个ArithmeticException,并且增加了异常的栈深,运行后的异常信息如下Exception in thread “main” java.lang.ArithmeticException: / by zero at ExceptionStack.lambda$divBy$0(ExceptionStack.java:30) at ExceptionStack.div(ExceptionStack.java:26) at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175) at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418) at ExceptionStack.run(ExceptionStack.java:18) at ExceptionStack.main(ExceptionStack.java:12)异常信息中的ExceptionStack.lambda$divBy$0 ReferencePipeline$3$1.accept等并不能让我们很快地了解,具体是类中哪个方法出现了问题,此类问题在很多编程语言中都存在,也希望JVM有朝一日可以彻底解决关于Lambda表达式中的"陷阱"不仅限于此,也希望大家能够一起来讨论 ...

April 13, 2019 · 6 min · jiezi

乐字节-Java8新特性之Stream流(下)

接上一篇:《Java8新特性之stream》,下面继续接着讲Stream5、流的中间操作常见的流的中间操作,归为以下三大类:筛选和切片流操作、元素映射操作、元素排序操作:5.1、筛选和切片例如以订单数据为例,在做报表展示时,会根据订单状态、用户信息、支付结果等状态来分别展示(即过滤和统计展示)定义订单Order类public class Order { // 订单id private Integer id; // 订单用户id private Integer userId; // 订单编号 private String orderNo; // 订单日期 private Date orderDate; // 收货地址 private String address; // 创建时间 private Date createDate; // 更新时间 private Date updateDate; // 订单状态 0-未支付 1-已支付 2-代发货 3-已发货 4-已接收 5-已完成 private Integer status; // 是否有效 1-有效订单 0-无效订单 private Integer isValid; //订单总金额 private Double total; /** 此处省略getter/setter方法 /}测试public static void main(String[] args) { Order order01 = new Order(1,10,“20190301”, new Date(),“上海市-浦东区”,new Date(),new Date(),4,1,100.0); Order order02 = new Order(2,30,“20190302”, new Date(),“北京市四惠区”,new Date(),new Date(),1,1,2000.0); Order order03 = new Order(3,20,“20190303”, new Date(),“北京市-朝阳区”,new Date(),new Date(),4,1,500.0); Order order04 = new Order(4,40,“20190304”, new Date(),“北京市-大兴区”,new Date(),new Date(),4,0,256.0); Order order05 = new Order(5,40,“20190304”, new Date(),“上海市-松江区”,new Date(),new Date(),4,0,1000.0); List<Order> ordersList= Arrays.asList(order01,order02,order03,order04); // 过滤订单集合 有效订单 并打印到控制台 ordersList.stream().filter((order)->order.getIsValid()==1).forEach(System.out::println); // 过滤订单集合有效订单 取前两条有效订单 并打印到控制台 ordersList.stream().filter((order)->order.getIsValid()==1).limit(2).forEach(System.out::println); } // 过滤订单集合有效订单 取最后一条记录 ordersList.stream().filter((order)->order.getIsValid()==1) .skip(ordersList.size()-2).forEach(System.out::println);// 去除订单编号重复的无效订单记录 此时因为比较的为Object Order对象需要重写HashCode 与Equals 方法/* * 重写 equals 方法 * @param obj * @return / @Override public boolean equals(Object obj) { boolean flag = false; if (obj == null) { return flag; } Order order = (Order) obj; if (this == order) { return true; } else { return (this.orderNo.equals(order.orderNo)); } } /* * 重写hashcode方法 * @return */ @Override public int hashCode() { int hashno = 7; hashno = 13 * hashno + (orderNo == null ? 0 : orderNo.hashCode()); return hashno; } // 过滤订单集合无效订单 去除订单号重复记录 ordersList.stream().filter((order)->order.getIsValid()==0).distinct().forEach(System.out::println);5.2、映射//过滤订单集合有效订单 获取所有订单订单编号ordersList.stream().filter((order)->order.getIsValid()==1).map((order)->order.getOrderNo()).forEach(System.out::println);// 过滤有效订单 并分离每个订单下收货地址市区信息ordersList.stream().map(o->o.getAddress().split("-")).flatMap(Arrays::stream).forEach(System.out::println);5.3、排序 //过滤有效订单 并根据用户id 进行排序 ordersList.stream().filter((order)->order.getIsValid()==1) .sorted((o1,o2)->o1.getUserId()-o2.getUserId()).forEach(System.out::println);//或者等价写法ordersList.stream().filter((order)->order.getIsValid()==1) .sorted(Comparator.comparingInt(Order::getUserId)).forEach(System.out::println);// 定制排序规则/过滤有效订单 * 定制排序:如果订单状态相同 根据订单创建时间排序 反之根据订单状态排序/ordersList.stream().filter((order)->order.getIsValid()==1).sorted((o1,o2)->{ if(o1.getStatus().equals(o2.getStatus())){ return o1.getCreateDate().compareTo(o2.getCreateDate()); }else{ return o1.getStatus().compareTo(o2.getStatus()); }}).forEach(System.out::println);6、流的终止操作终止操作会从流的流水线生成结果。其结果是任何不是流的值,比如常见的List、 Integer,甚 至void等结果。对于流的终止操作,分为以下三类:6.1、查找与匹配 // 筛选所有有效订单 匹配用户id =20 的所有订单System.out.println(“allMatch匹配结果:"+ordersList.stream(). filter((order) -> order.getIsValid() == 1).allMatch((o) -> o.getUserId() == 20));System.out.println(“anyMatch匹配结果:"+ordersList.stream(). filter((order) -> order.getIsValid() == 1).anyMatch((o) -> o.getUserId() == 20));System.out.println(“noneMatch匹配结果:"+ordersList.stream(). filter((order) -> order.getIsValid() == 1).noneMatch((o) -> o.getUserId() == 20));// 筛选所有有效订单 返回订单总数System.out.println(“count结果:"+ordersList.stream(). filter((order) -> order.getIsValid() == 1).count());// 筛选所有有效订单 返回金额最大订单值Optional<Double> max=ordersList.stream().filter((order) -> order.getIsValid() == 1) .map(Order::getTotal).max(Double::compare);System.out.println(“订单金额最大值:"+max.get());// 筛选所有有效订单 返回金额最小订单值Optional<Double> min=ordersList.stream().filter((order) -> order.getIsValid() == 1) .map(Order::getTotal).min(Double::compare);System.out.println(“订单金额最小值:"+min.get());6.2、归约将流中元素反复结合起来,得到一个值的操作。// 归约操作 计算有效订单总金额System.out.println(“有效订单总金额:"+ordersList.stream().filter((order) -> order.getIsValid() == 1).map(Order::getTotal).reduce(Double::sum).get());6.3、Collector收集数据6.3.1、收集将流转换为其他形式,coollect 方法作为终端操作, 接收一个Collector接口的实现,用于给Stream中元素做汇总的方法。最常用的方法,把流中所有元素收集到一个 List, Set 或 Collection 中toListtoSettoCollectiontoMap// 收集操作// 筛选所有有效订单 并收集订单列表List<Order> orders= ordersList.stream().filter((order) -> order.getIsValid() == 1).collect(Collectors.toList());orders.forEach(System.out::println);// 筛选所有有效订单 并收集订单号 与 订单金额Map<String,Double> map=ordersList.stream().filter((order) -> order.getIsValid() == 1).collect(Collectors.toMap(Order::getOrderNo, Order::getTotal));// java8 下对map 进行遍历操作 如果 Map 的 Key 重复了,会报错map.forEach((k,v)->{System.out.println(“k:"+k+"✌️"+v);});6.3.2、汇总countintg():用于计算总和count():用于计算总和(推荐使用,写法更简洁)summingInt() ,summingLong(),summingDouble():用于计算总和averagingInt(),averagingLong(),averagingDouble()用于平均summarizingInt,summarizingLong,summarizingDouble 同样可以实现计算总和,平均等操作,比如summarizingInt 结果会返回IntSummaryStatistics 类型 ,然后通过get方法获取对应汇总值即可// 汇总操作//筛选所有有效订单 返回订单总数System.out.println(“count结果:"+ordersList.stream(). filter((order) -> order.getIsValid() == 1).collect(Collectors.counting()));System.out.println(“count结果:"+ordersList.stream(). filter((order) -> order.getIsValid() == 1).count());// 返回订单总金额System.out.println(“订单总金额:"+ordersList.stream(). filter((order) -> order.getIsValid() == 1).collect(Collectors.summarizingDouble(Order::getTotal)));System.out.println(“订单总金额:"+ordersList.stream(). filter((order) -> order.getIsValid() == 1).mapToDouble(Order::getTotal).sum());System.out.println(“订单总金额:"+ordersList.stream(). filter((order) -> order.getIsValid() == 1).map(Order::getTotal).reduce(Double::sum).get());// 返回 用户id=20 有效订单平均每笔消息金额System.out.println(“用户id=20 有效订单平均每笔消费金额:"+ordersList.stream(). filter((order) -> order.getIsValid() == 1). filter((order -> order.getUserId()==20)) .collect(Collectors.averagingDouble(Order::getTotal)));System.out.println(“用户id=20 有效订单平均每笔消费金额:"+ ordersList.stream(). filter((order) -> order.getIsValid() == 1). filter((order -> order.getUserId()==20)) .mapToDouble(Order::getTotal).average().getAsDouble());System.out.println(“用户id=20 有效订单平均每笔消费金额:"+ ordersList.stream(). filter((order) -> order.getIsValid() == 1). filter((order -> order.getUserId()==20)) .collect(Collectors.summarizingDouble(Order::getTotal)).getAverage());// 筛选所有有效订单 并计算订单总金额System.out.println(“订单总金额:"+ordersList.stream().filter((order) -> order.getIsValid() == 1) .collect(Collectors.summingDouble(Order::getTotal)));// 筛选所有有效订单 并计算最小订单金额System.out.println(“最小订单金额:"+ordersList.stream().filter((order) -> order.getIsValid() == 1) .map(Order::getTotal).collect(Collectors.minBy(Double::compare)));// 筛选所有有效订单 并计算最大订单金额System.out.println(“最大订单金额:"+ordersList.stream().filter((order) -> order.getIsValid() == 1) .map(Order::getTotal).collect(Collectors.maxBy(Double::compare)));6.3.3、最值maxBy,minBy 两个方法,需要一个 Comparator 接口作为参数,实现最大 最小值获取操作// 取最会// 筛选所有有效订单 并计算最小订单金额System.out.println(“最小订单金额:"+ordersList.stream().filter((order) -> order.getIsValid() == 1) .map(Order::getTotal).collect(Collectors.minBy(Double::compare)));// 筛选所有有效订单 并计算最大订单金额System.out.println(“最大订单金额:"+ordersList.stream().filter((order) -> order.getIsValid() == 1) .map(Order::getTotal).collect(Collectors.maxBy(Double::compare)));6.3.4、分组groupingBy 用于将数据分组,最终返回一个 Map 类型 groupingBy 可以接受一个第二参数实现多级分组// 分组-根据有效订单支付状态进行分组操作Map<Integer,List<Order>> g01=ordersList.stream().filter((order) -> order.getIsValid() == 1) .collect(Collectors.groupingBy(Order::getStatus));g01.forEach((status,order)->{ System.out.println(”—————-”); System.out.println(“订单状态:"+status); order.forEach(System.out::println);});// 分组-查询有效订单 根据用户id 和 支付状态进行分组Map<Integer,Map<String,List<Order>>> g02= ordersList.stream().filter((order) -> order.getIsValid() == 1) .collect(Collectors.groupingBy(Order::getUserId,Collectors.groupingBy((o)->{ if(o.getStatus()==0){ return “未支付”; }else if (o.getStatus()==1){ return “已支付”; }else if (o.getStatus()==2){ return “待发货”; }else if (o.getStatus()==3){ return “已发货”; }else if (o.getStatus()==4){ return “已接收”; } else{ return “已完成”; } })));g02.forEach((userId,m)->{ System.out.println(“用户id:"+userId+”–>有效订单如下:”); m.forEach((status,os)->{ System.out.println(“状态:"+status+”—订单列表如下:”); os.forEach(System.out::println); }); System.out.println(”———————–”);});6.3.5、partitioningBy 分区分区与分组的区别在于,分区是按照 true 和 false 来分的,因此partitioningBy 接受的参数的 lambda 也是 T -> boolean // 分区操作 筛选订单金额>1000 的有效订单Map<Boolean,List<Order>> g03= ordersList.stream().filter((order) -> order.getIsValid() == 1) .collect(Collectors.partitioningBy((o)->o.getTotal()>1000));g03.forEach((b,os)->{ System.out.println(“分区结果:"+b+”–列表结果:”); os.forEach(System.out::println);});// 拼接操作 筛选有效订单 并进行拼接String orderStr=ordersList.stream().filter((order) -> order.getIsValid() == 1).map(Order::getOrderNo) .collect(Collectors.joining(”,”));System.out.println(orderStr);乐字节-Java新特性之stream流就介绍到这里了,接下来小乐还会接着给大家讲解Java8新特性之Optional,欢迎关注,转载请说明出处和作者。 ...

April 10, 2019 · 3 min · jiezi

乐字节-Java8新特性之Stream流(上)

上一篇文章,小乐给大家介绍了《Java8新特性之方法引用》,下面接下来小乐将会给大家介绍Java8新特性之Stream,称之为流,本篇文章为上半部分。1、什么是流?Java Se中对于流的操作有输入输出IO流,而Java8中引入的Stream 属于Java API中的一个新成员,它允许你以声明性方式处理数据集合,Stream 使用一种类似 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。 注意这里的流操作可以看做是对集合数据的处理。简单来说,流是一种数据渠道,用于操作数据源(集合、数组)所生产的元素序列。元素序列 :就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序值。源:流会使用一个提供数据的源,如集合、数组或输入/输出资源。 请注意,从有序集 合生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致数据处理操作:流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中 的常用操作,如filter、 map、 reduce、 find、 match、 sort等。流操作可以顺序执 行,也可并行执行。流水线:很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大 的流水线。内部迭代:与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的。2、流操作整个流操作就是一条流水线,将元素放在流水线上一个个地进行处理。需要注意的是:很多流操作本身就会返回一个流,所以多个操作可以直接连接起来, 如下图这样,操作可以进行链式调用,并且并行流还可以实现数据流并行处理操作。3、流与集合3.1、计算的时机Stream 和集合的其中一个差异在于什么时候进行计算,集合,它会包含当前数据结构中所有的值,你可以随时增删,但是集合里面的元素毫无疑问地都是已经计算好了的。 流则是按需计算,按照使用者的需要计算数据,你可以想象我们通过搜索引擎进行搜索,搜索出来的条目并不是全部呈现出来的,而且先显示最符合的前 10 条或者前 20 条,只有在点击 “下一页” 的时候,才会再输出新的 10 条。 5.3.2、外部迭代与内部迭代把集合比作一个工厂的仓库,一开始工厂比较落后,要对货物作什么修改,只能工人亲自走进仓库对货物进行处理,有时候还要将处理后的货物放到一个新的仓库里面。在这个时期,我们需要亲自去做迭代,一个个地找到需要的货物,并进行处理,这叫做外部迭代。 后来工厂发展了起来,配备了流水线作业,只要根据需求设计出相应的流水线,然后工人只要把货物放到流水线上,就可以等着接收成果了,而且流水线还可以根据要求直接把货物输送到相应的仓库。这就叫做内部迭代,流水线已经帮你把迭代给完成了,你只需要说要干什么就可以了(即设计出合理的流水线)。 Java 8 引入 Stream 很大程度是因为,流的内部迭代可以自动选择一种合适你硬件的数据表示和并行实现。 4、创建流在 Java 8 中, 集合接口有两个方法来生成流:stream() − 为集合创建串行流。parallelStream() − 为集合创建并行流。示例代码如下:public static void main(String[] args) { /** * 定义集合l1 并为集合创建串行流 */ List<String> l1 = Arrays.asList(“周星驰”, “周杰伦”, “周星星”, “周润发”); // 返回串行流 l1.stream(); // 返回并行流 l1.parallelStream();}上述操作得到的流是通过原始数据转换过来的流,除了这种流创建的基本操作外,对于流的创建还有以下几种方式。4.1、值创建流Stream.of(T…) : Stream.of(“aa”, “bb”) 生成流//值创建流 生成一个字符串流Stream<String> stream = Stream.of(“java8”, “Spring”, “SpringCloud”);stream.forEach(System.out::println);4.2、数组创建流根据参数的数组类型创建对应的流。Arrays.stream(T[ ])Arrays.stream(int[ ])Arrays.stream(double[ ])Arrays.stream(long[ ]) // 只取索引第 1 到第 2 位的: int[] a = {1, 2, 3, 4}; Arrays.stream(a, 1, 3).forEach(System.out :: println);4.3、文件生成流//每个元素是给定文件的其中一行Stream<String> stream02 = Files.lines(Paths.get(“data.txt”));4.4、函数生成流两个方法:iterate : 依次对每个新生成的值应用函数generate :接受一个函数,生成一个新的值 //生成流,首元素为 0,之后依次加 2 Stream.iterate(0, n -> n + 2) //生成流,为 0 到 1 的随机双精度数 Stream.generate(Math :: random) //生成流,元素全为 1 Stream.generate(() -> 1)上半部分就介绍到这里,下半部分将会给大家介绍流的中间操作和终止操作。请多关注!转载请注明出处和作者。 ...

April 9, 2019 · 1 min · jiezi

乐字节-Java8新特性之方法引用

上一篇小乐介绍了《Java8新特性-函数式接口》,大家可以点击回顾。这篇文章将接着介绍Java8新特性之方法引用。Java8 中引入方法引用新特性,用于简化应用对象方法的调用, 方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。 方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文。计算时,方法引用会创建函数式接口的一个实例。 当Lambda表达式中只是执行一个方法调用时,不用Lambda表达式,直接通过方法引用的形式可读性更高一些。方法引用是一种更简洁易懂的Lambda表达式。1、基本格式方法引用使用一对冒号 :: 来简化对象方法的调用,当你想要使用方法引用时,目标引用放在分隔符 :: 前,方法名称放在后面, 如下形式:方法引用参考示例:2、方法引用分类Java8 中对于方法引用主要分为三大类:构造器引用 Class::new静态方法引用 Class::static_method特定对象的方法引用 instance::method2.1、构造器引用语法是Class::new,或者更一般的Class< T >::new实例如下 借助构造器引用实例化Iphone 对象,代码如下:public class IPhone { private Integer id; private String version; private Date createTime; private String name; public IPhone() { } public IPhone(Integer id) { this.id = id; } public IPhone(Integer id, String name) { this.id = id; this.name = name; } …}public static void main(String[] args) { /** * 构造器引用 * 无参构造器 / // 实现Supplier 接口 通过构造器引用 Supplier<IPhone> factory01= IPhone::new; IPhone p01 = factory01.get(); System.out.println(p01); /* * 等价的Lambda 写法 / Supplier<IPhone> factory02 = ()->new IPhone(); IPhone p02 = factory02.get(); System.out.println(p02); /* * 当构造器方法存在参数 参数个数为1个时 / Function<Integer,IPhone> factory03 = IPhone::new; IPhone p03 = factory03.apply(2019); System.out.println(p03); /* * 等价的Lambda 写法 / Function<Integer,IPhone> factory04 = (id)-> new IPhone(id); IPhone p04 = factory04.apply(2019); System.out.println(p04); /* * 当构造器方法存在参数 参数个数为2个时 / BiFunction<Integer,String,IPhone> factory05 = IPhone::new; IPhone p05 = factory05.apply(2019,“iphoneX”); System.out.println(p05); /* * 等价的Lambda 写法 / BiFunction<Integer,String,IPhone> factory06 = (id,name)-> new IPhone(id,name); IPhone p06 = factory06.apply(2019,“iphoneMax”); System.out.println(p06); /* 当构造器参数参过2个时怎么解决呢??? / }2.2、静态方法引用语法是Class::static_method,实例如下: 使用静态方法引用 执行IPhone 静态方法public class IPhone { private Integer id; private String version; private Date createTime; private String name; public IPhone() { } public IPhone(Integer id) { this.id = id; } public IPhone(Integer id, String name) { this.id = id; this.name = name; } / 静态方法 / public static void info(){ System.out.println(“这是一部IPhone”); }}/* * 定义函数式接口 /@FunctionalInterfacepublic interface PrintFunction{ void print();} // 静态方法引用PrintFunction pf01= IPhone::info;pf01.print();/* * 等价的Lambda 写法*/PrintFunction pf02 = () -> { IPhone.info();};pf02.print();// 静态方法引用 静态方法存在参数时/** * 定义函数式接口 /@FunctionalInterfacepublic interface PrintFunction02<T,R> { R print(T t);}/* * 静态方法引用 方法存在参数时 /PrintFunction02<String,Double> pf03 = IPhone::getPrice;System.out.println(pf03.print(“iphone”));/* * 等价的Lambda 写法 /PrintFunction02<String,Double> pf04 =(str)->{ return IPhone.getPrice(str);};2.3、特定类的任意实例化对象的方法引用语法是instance::method ,此时引用方法时必须存在实例,示例代码如下:/* * 构造器引用 实例化对象 * 成员方法引用*/BiFunction<Integer,String,IPhone> factory07= IPhone::new;IPhone p07 = factory07.apply(2019,“iphoneX”);PrintFunction pp= p07::mm;pp.print();/** * 等价的Lambda 写法 */BiFunction<Integer,String,IPhone> factory08 = (id,name)-> new IPhone(id,name);IPhone p08 = factory08.apply(2019,“iphoneMax”);PrintFunction pp02 = ()->{ p08.mm();};pp02.print();2.4 类的成员方法引用(略)接下来,小乐会继续介绍Java8新特性之Stream,敬请期待。欢迎关注乐字节,记得评论点赞哦。转发请记得注明出处和作者。 ...

April 8, 2019 · 2 min · jiezi

乐字节-Java8新特性之函数式接口

上一篇小乐带大家学过 Java8新特性-Lambda表达式,什么时候可以使用Lambda?通常Lambda表达式是用在函数式接口上使用的。从Java8开始引入了函数式接口,其说明比较简单:函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。 java8引入@FunctionalInterface 注解声明该接口是一个函数式接口。1、语法定义/** * 定义函数式接口 * 接口上标注@FunctionalInterface 注解 /@FunctionalInterfacepublic interface ICollectionService { /* * 定义打印方法 / void print();}在Java8 以前,已有大量函数式接口形式的接口(接口中只存在一个抽象方法),只是没有强制声明。例如:java.lang.Runnablejava.util.concurrent.Callablejava.security.PrivilegedActionjava.io.FileFilterjava.nio.file.PathMatcherjava.lang.reflect.InvocationHandlerjava.beans.PropertyChangeListenerjava.awt.event.ActionListenerjavax.swing.event.ChangeListenerJava8 新增加的函数接口在java.util.function 包下,它包含了很多类,用来支持 Java的 函数式编程,该包中的函数式接口有: 序号 接口 & 描述 1 BiConsumer<T,U>代表了一个接受两个输入参数的操作,并且不返回任何结果 2 BiFunction<T,U,R>代表了一个接受两个输入参数的方法,并且返回一个结果 3 BinaryOperator<T>代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果 4 BiPredicate<T,U>代表了一个两个参数的boolean值方法 5 BooleanSupplier代表了boolean值结果的提供方 6 Consumer<T>代表了接受一个输入参数并且无返回的操作 7 DoubleBinaryOperator代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。 8 DoubleConsumer代表一个接受double值参数的操作,并且不返回结果。 9 DoubleFunction<R>代表接受一个double值参数的方法,并且返回结果 10 DoublePredicate代表一个拥有double值参数的boolean值方法 11 DoubleSupplier代表一个double值结构的提供方 12 DoubleToIntFunction接受一个double类型输入,返回一个int类型结果。 13 DoubleToLongFunction接受一个double类型输入,返回一个long类型结果 14 DoubleUnaryOperator接受一个参数同为类型double,返回值类型也为double 。 15 Function<T,R>接受一个输入参数,返回一个结果。 16 IntBinaryOperator接受两个参数同为类型int,返回值类型也为int 。 17 IntConsumer接受一个int类型的输入参数,无返回值 。 18 IntFunction<R>接受一个int类型输入参数,返回一个结果 。 19 IntPredicate:接受一个int输入参数,返回一个布尔值的结果。 20 IntSupplier无参数,返回一个int类型结果。 21 IntToDoubleFunction接受一个int类型输入,返回一个double类型结果 。 22 IntToLongFunction接受一个int类型输入,返回一个long类型结果。 23 IntUnaryOperator接受一个参数同为类型int,返回值类型也为int 。 24 LongBinaryOperator接受两个参数同为类型long,返回值类型也为long。 25 LongConsumer接受一个long类型的输入参数,无返回值。 26 LongFunction<R>接受一个long类型输入参数,返回一个结果。 27 LongPredicateR接受一个long输入参数,返回一个布尔值类型结果。 28 LongSupplier无参数,返回一个结果long类型的值。 29 LongToDoubleFunction接受一个long类型输入,返回一个double类型结果。 30 LongToIntFunction接受一个long类型输入,返回一个int类型结果。 31 LongUnaryOperator接受一个参数同为类型long,返回值类型也为long。 32 ObjDoubleConsumer<T>接受一个object类型和一个double类型的输入参数,无返回值。 33 ObjIntConsumer<T>接受一个object类型和一个int类型的输入参数,无返回值。 34 ObjLongConsumer<T>接受一个object类型和一个long类型的输入参数,无返回值。 35 Predicate<T>接受一个输入参数,返回一个布尔值结果。 36 Supplier<T>无参数,返回一个结果。 37 ToDoubleBiFunction<T,U>接受两个输入参数,返回一个double类型结果 38 ToDoubleFunction<T>接受一个输入参数,返回一个double类型结果 39 ToIntBiFunction<T,U>接受两个输入参数,返回一个int类型结果。 40 ToIntFunction<T>接受一个输入参数,返回一个int类型结果。 41 ToLongBiFunction<T,U>接受两个输入参数,返回一个long类型结果。 42 ToLongFunction<T>接受一个输入参数,返回一个long类型结果。 43 UnaryOperator<T>接受一个参数为类型T,返回值类型也为T。 对于Java8中提供的这么多函数式接口,开发中常用的函数式接口有以下几个Predicate,Consumer,Function,Supplier。2、函数式接口实例2.1、Predicatejava.util.function.Predicate<T> 接口定义了一个名叫 test 的抽象方法,它接受泛型 T 对象,并返回一个boolean值。在对类型 T进行断言判断时,可以使用这个接口。通常称为断言性接口 。 使用Predicate接口实现字符串判空操作@FunctionalInterfacepublic interface Predicate<T> { /* * Evaluates this predicate on the given argument. * * @param t the input argument * @return {@code true} if the input argument matches the predicate, * otherwise {@code false} / boolean test(T t); …}public static void main(String[] args) { /* * 借助Lambda 表达式实现Predicate test方法 / Predicate<String> p01=(str)->str.isEmpty()||str.trim().isEmpty(); /* * 测试传入的字符串是否为空 / System.out.println(p01.test("")); System.out.println(p01.test(" “)); System.out.println(p01.test(“admin”));}测试代码public static void main(String[] args) { /* * 借助Lambda 表达式实现Predicate test方法 / Predicate<String> p01=(str)->str.isEmpty()||str.trim().isEmpty(); /* * 测试传入的字符串是否为空 / System.out.println(p01.test(”")); System.out.println(p01.test(" “)); System.out.println(p01.test(“admin”));}测试结果:2.2、Consumerjava.util.function.Consumer<T>接口定义了一个名叫 accept 的抽象方法,它接受泛型T,没有返回值(void)。如果需要访问类型 T 的对象,并对其执行某些操作,可以使用这个接口,通常称为消费性接口。 使用Consumer实现集合遍历操作@FunctionalInterfacepublic interface Consumer<T> { /* * Performs this operation on the given argument. * * @param t the input argument / void accept(T t); …}/** 借助Lambda表达式实现Consumer accept方法*/Consumer<Collection> c01 = (collection) -> {if (null != collection && collection.size() > 0) {for (Object c : collection) {System.out.println(c);}}};List<String> list = new ArrayList<String>();list.add(“诸葛亮”);list.add(“曹操”);list.add(“关羽”);// 遍历list 输出元素内容到控制台c01.accept(list);2.3、Functionjava.util.function.Function<T, R>接口定义了一个叫作apply的方法,它接受一个泛型T的对象,并返回一个泛型R的对象。如果需要定义一个Lambda,将输入的信息映射到输出,可以使用这个接口(比如提取苹果的重量,或把字符串映射为它的长度),通常称为功能性接口。使用Function实现用户密码 Base64加密操作@FunctionalInterfacepublic interface Function<T, R> { /** * Applies this function to the given argument. * * @param t the function argument * @return the function result / R apply(T t);}// 实现用户密码 Base64加密操作Function<String,String> f01=(password)->Base64.getEncoder().encodeToString(password.getBytes());// 输出加密后的字符串System.out.println(f01.apply(“123456”));加密后结果如下:2.4、Supplierjava.util.function.Supplier<T>接口定义了一个get的抽象方法,它没有参数,返回一个泛型T的对象,这类似于一个工厂方法,通常称为功能性接口。使用Supplier实现SessionFactory创建@FunctionalInterfacepublic interface Supplier<T> { /* * Gets a result. * * @return a result / T get();}/* * 产生一个session工厂对象 */Supplier<SessionFactory> s = () -> { return new SessionFactory();};s.get().info();以上就是小乐带给大家的Java8新特性之函数式接口,下一篇将会为大家带来Java8新特性之方法引用,敬请关注。转载请注明文章出处和作者,谢谢合作! ...

April 4, 2019 · 2 min · jiezi

Spring 官方文档完整翻译

以下所有文档均包含多个版本,并支持多语言(英文及中文)。Spring Boot 中文文档Spring Framework 中文文档Spring Cloud 中文文档Spring Security 中文文档Spring Session 中文文档Spring AMQP 中文文档Spring DataSpring Data JPASpring Data JDBCSpring Data RedisContributing如果你希望参与文档的校对及翻译工作,请在 这里 提 PR。

April 3, 2019 · 1 min · jiezi

乐字节-Java8新特性-Lambda表达式

上一篇文章我们了解了Java8新特性-接口默认方法,接下来我们聊一聊Java8新特性之Lambda表达式。Lambda表达式(也称为闭包),它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理。很多语言(Groovy、Scala等)从设计之初就支持Lambda表达式。但是java中使用的是 匿名内部类代替。最后借助强大的社区力量,找了一个折中的Lambda实现方案,可以实现简洁而紧凑的语言结构。1、匿名内部类到Lambda的演化匿名内部类,即一个没有名字的,存在于一个类或方法内部的类。当我们需要用某个类且只需要用一次,创建和使用和二为一时,我们可以选择匿名内部类,省掉我们定义类的步骤。匿名内部类会隐士的继承一个类或实现一个接口,或者说匿名内部类是一个继承了该类或者实现了该接口的子类匿名对象。下面看一个匿名内部类的例子:测试类中调用方法package com.lotbyte.main;/* 定义和使用匿名内部类 */public class NoNameClass { public static void main(String[] args) { Model m = new Model(){ @Override public void func() { System.out.println(“方法的实现”); } }; m.func(); }}// 需要被实现的接口interface Model{ void func();}2、Lambda快速使用从某种意义上来说,Lambda表达式可以看作是匿名内部类对象的简写形式。最简单的Lambda表达式可以由 用逗号分隔的参数列表、->符号和语句块组成。注意:此时匿名内部类只能实现接口,不能是继承抽象类.例如将上面的例子做一个简化,使用Lambda的形式如下:public class NonameClassForLambda { public static void main(String[] args) { // Lambda方式简写,方法实现可以很简单 Model1 md = ()-> System.out.println(“hello”); md.func(); // 也可以是比较复杂的操作 md = () -> { for (int i = 1; i <=5; i++) { System.out.println(i); } }; md.func(); }}// 接口interface Model1{ void func();}以上是一个简单的Lambda的书写形式,()中是形参列表,没有则为空括号, ->为语法格式,之后则为方法的实现(一条语句可以直接书写,当有多条语句时,需要使用{}进行包裹)。从这可以看出在接口中必须只能存在一个抽象方法。注意:Lambda中必须有个接口3、Lambda的形式使用Lambda时,实现方法可以有参数,也可以有返回值,如果没指定参数类型,则由编译器自行推断得出。3.1、 无参带返回值生成[1,10]之间的任意整数interface Model2{ int func();}Model2 md2 = () -> {return (int)(Math.random()10+1)};说明:Lambda的改写需要有对应的抽象方法,当没有参数时需要使用()占位,当表达式只有一行代码时,可以省略return和{}以上的Lambda等价于: Model2 md2 = () -> (int)(Math.random()10+1);3.2 、带参带返回值返回一个对数字描述的字符串:interface Model3{ String func(int a);}Model3 md3 = (int a) -> { return “This is a number " + a;};说明:形参写在()内即可,参数的类型可以省略,此时将由编译器自行推断得出,同时还可以省略() md3 = a -> “This is a number " + a;省略了参数类型,小括号,同时连带实现体的括号和return都省了。3.3 、带多个参数根据输入的运算符计算两个数的运算,并返回结果:interface Model4{ String func(int a, int b, String oper);} Model4 md4 = (a, b, s) -> { String res = “”; if("+".equals(s)){ res = ( a+b ) + “”; }else if(”-".equals(s)){ res = ( a-b ) + “”; }else if(”".equals(s)){ res = ( ab ) + “”; }else if("/".equals(s)){ res = ( a/b ) + “”; // 暂不考虑除0的情况 }else{ res = “操作有失误”; } return res; }; System.out.println(md4.func(1,1,"+"));以上例子为多个参数的Lambda表达式,其中省略掉了每一个参数的类型,编译器自动推断。多条语句时实现体的{}不能省。4、Lambda作为参数在jdk8之前,接口可以作为方法参数传入,执行时必须提供接口实现类的实例。从java8开始,Lambda可以作为接口方法实现,当作参数传入,无论从形式上还是实际上都省去了对象的创建。使代码更加的紧凑简单高效。使用Lambda表达式需要有以下几步: 1、定义接口,抽象方法的模板; 2、在某方法中需要接口作为参数; 3、调用方法时需要将抽象方法实现(此时我们使用Lambda表达式)并传入即可。4.1、定义接口在接口中,必须有且仅有一个抽象方法,以确定Lambda模板// 无参无返回值的方法interface LambdaInterface1{ void printString();}// 带参无返回值的方法interface LambdaInterface2{ void printString(String str);}4.2、定义方法接收参数在某方法中需要使用接口作为参数// 无参public static void testLambda(LambdaInterface1 lam1){ lam1.printString();}// 带参public static void testLambda2(String s,LambdaInterface2 lam2){ lam2.printString(s);}4.3、Lambda实现使用方法时需要用Lambda将抽象方法实现使用方法时需要用Lambda将抽象方法实现// 无参Lambda作为参数testLambda(()->{ System.out.println(“可以简单,可以复杂”);});// 带参Lambda作为参数testLambdaParam(“hello”,(a)->{ System.out.println(a);});通过以上三步,能够完整地展示Lambda从和演变而来。此后在使用时,jdk中已经提供很多场景了,即前两部已经完成,我们更多的是实现第三步即可。5、forEach展示Lambda例如以ArrayList的遍历为例子,分析Lambda的使用方式。public static void main(String[] args) { List<String> strs = new ArrayList<String>(){ { add(“aaa”); add(“bbb”); add(“ccc”); } }; strs.forEach((str)-> System.out.println(str));}下面看看forEach的源码,定义中使用了接口Consumer作为参数,并调用了其方法:Consumer中的抽象方法只有accept一个通过在forEach方法中调用Consumer的accept方法,并将每一个元素作为参数传入,使得accept方法可以对每一个元素进行操作,当我们使用Lambda实现accept时就变成了我们自己对每一个元素的处理了。我们只负责处理即可。6、Lambda中使用变量在Lambda中可以定义自己的局部变量,也可以使用外层方法的局部变量,还可以使用属性。这一点也不难理解,既然是一个方法的实现,只写了一个代码块,那么使用本身所属方法的局部变量和类的属性也并不过分。public static void main(String[] args) { List<String> strs = new ArrayList<String>(){ { add(“aaa”); add(“bbb”); add(“ccc”); } }; int j = 1; strs.forEach((str)->{ int i = 0; System.out.println(str + " " + i + " " + j); });}注意:此时外部局部变量将自动变为final7、Lambda作为方法返回值例子:返回判断字符串是否为空public class Demo004_2 { public static void main(String[] args) { System.out.println(testLambda().isEmpty(“string”)); } // 判断字符串是否为空 public static AssertEmpty testLambda(){ return (n)-> null==n||n.trim().isEmpty(n); }}interface AssertEmpty{ boolean isEmpty(String str);}今天关于Java8新特性-Lambda表达式就讲到这里了,接下来我会继续讲述Java8新特性之函数式接口。敬请继续关注!欢迎留言评论。 ...

April 2, 2019 · 2 min · jiezi

乐字节-Java8新特性-接口默认方法

总概JAVA8 已经发布很久,而且毫无疑问,java8是自java5(2004年发布)之后的最重要的版本。其中包括语言、编译器、库、工具和JVM等诸多方面的新特性。Java8 新特性列表如下:接口默认方法函数式接口Lambda表达式方法引用StreamOptional 类Date APIBase64重复注解与类型注解接下来乐字节将会带大家一一讲以上Java8新特性详细讲解,作为Java8新特性系列文章连载。一、接口默认方法1、什么是接口默认方法从Java8开始,程序允许在接口中包含带有具体实现的方法,使用default修饰,这类方法就是默认方法。默认方法在接口中可以添加多个,并且Java8提供了很多对应的接口默认方法。2、设计接口默认方法好处使用接口编程的好处是,开发是面向抽象而不再是面向具体来编程,使得程序变得很灵活,缺陷是,当需要修改接口时候,此时对应需要修改全部实现该接口的类,举个例子, java 8 之前对于我们常用的集合框架没有 foreach 方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。从Java8开始,引入了接口默认方法,这样的好处也是很明显的,首先解决了Java8以前版本接口兼容性问题,同时对于我们以后的程序开发,也可以在接口子类中直接使用接口默认方法,而不再需要再各个子类中各自实现响应接口方法。3、默认方法编写jdk8中接口可以包含实现方法,需要使用default修饰,此类方法称为默认方法。默认方法在接口中必须提供实现,在实现类中可以按需重写。默认方法只能在实现类中或通过实现类对象调用。注意:当多个父接口中存在相同的默认方法时,子类中以就近原则继承。public interface IMathOperation { /** * 定义接口默认方法 支持方法形参 / default void print(){ System.out.println(“这是数值运算基本接口。。。”); }}4、静态方法静态方法即通过static 修饰的方法。接口中静态方法页也必须实现,提供了可以直接通过接口调用方法的方式。public interface IMathOperation { /* * 定义接口默认方法 支持方法形参 / default void print(){ System.out.println(“这是数值运算基本接口。。。”); } /* * 定义静态默认方法 / static void version(){ System.out.println(“这是1.0版简易计算器”); } }5、接口默认方法使用1)、定义IMathOperation 接口 并提供默认打印方法public interface IMathOperation { /* * 定义接口默认方法 支持方法形参 / default void print(){ System.out.println(“这是数值运算基本接口。。。”); } /* * 定义静态默认方法 / static void version(){ System.out.println(“这是1.0版简易计算器”); } /* * 整数加法运算方法 * @param a * @param b * @return / public int add(int a,int b); }2)、子类实现定义MathOperationImpl子类 实现IMathOperation 接口这里可以看到,在实现IMathOperation 接口时可以选择实现 ( 也可以不实现 ,在子类方法中直接使用 ) 接口默认方法。public class MathOperationImpl implements IMathOperation { @Override public int add(int a, int b) { // 子类中可以直接调用父类接口默认方法 IMathOperation.super.print(); // 调用父类静态默认方法 IMathOperation.version(); return a+b; }}3)、多个默认方法情况使用Java8开发应用程序,子类实现多个接口时,对于接口默认方法定义可能会出现多个默认方法,并且接口默认方法可能会出现同名情况,此时对于子类在实现或者调用时通常遵循以下原则:1.类中的方法优先级最高2.如果第一条无法进行判断,那么子接口的优先级更高:函数签名相同时,优先选择拥有最具体实现的默认方法的接口,即如果B继承了A,那么B就比A更加具体示例代码如下:/* * 定义手机接口 提供默认info方法 /public interface Phone { default void info(){ System.out.println(“这是一部手机”); }}/* * 定义MiPhone子接口 并继承 Phone 父接口 同时也提供info方法 /public interface MiPhone extends Phone{ default void info(){ System.out.println(“这是一部小米手机”); }}/* * 实现 Phone MiPhone 接口 */public class M2sPhone implements Phone,MiPhone { public static void main(String[] args) { new M2sPhone().info(); }}打印结果: 这是一部小米手机乐字节原创,转载请先联系,谢谢阅读。这是乐字节Java8新特性连载文章之一,欢迎继续关注看下一篇Java8新特性介绍。 ...

April 2, 2019 · 1 min · jiezi

【修炼内功】[Java8] Lambda表达式带来的编程新思路

该文章已收录 【修炼内功】跃迁之路Lambda表达式,可以理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型。这里,默认您已对Java8的Lambda表达式有一定了解,并且知道如何使用。Java8中引入的Lambda表达式,为编程体验及效率带来了极大的提升。行为参数化行为参数化,是理解函数式编程的一个重要概念。简单来说便是,一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不同行为的能力。更为通俗的讲,行为参数化是指,定义一段代码,这段代码并不会立即执行,而是可以像普通变量/参数一样进行传递,被程序的其他部分调用。我们通过一个特别通用的筛选苹果的例子,来逐步了解如何使用Lambda表达式实现行为参数化。(如果对行为参数化已十分了解,可直接跳过本节)需求1:筛选绿色苹果我们需要将仓库中绿色的苹果过滤出来,对于这样的问题,大多数人来说都是手到擒来 (step1: 面向过程)public static List<Apple> filterGreenApples(List<Apple> apples) { List<apple> filteredApples = new LinkedList<>(); for (Apple apple: apples) { if (“green”.equals(apple.getColor())) { filteredApples.add(apple); } } return filteredApples;}List<Apple> greenApples = filterGreenApples(inventory);需求2:筛选任意颜色苹果对于这样的需求变更,可能也不是很难public static List<Apple> filterApplesByColor(List<Apple> apples, String color) { List<apple> filteredApples = new LinkedList<>(); for (Apple apple: apples) { if (color.equals(apple.getColor())) { filteredApples.add(apple); } } return filteredApples;}List<Apple> someColorApples = filterApplesByColor(inventory, “red”);需求3:筛选重量大于150克的苹果有了先前的教训,可能会学聪明一些,不会把重量直接写死到程序里,而是提供一个入参public static List<Apple> filterApplesByWeight(List<Apple> apples, int minWeight) { List<apple> filteredApples = new LinkedList<>(); for (Apple apple: apples) { if (apple.getWeight() > minWeight) { filteredApples.add(apple); } } return filteredApples;}List<Apple> heavyApples = filterApplesByColor(inventory, 150);需求4:筛选颜色为红色且重量大于150克的苹果如果照此下去,程序将变得异常难于维护,每一次小的需求变更,都需要进行大范围的改动。为了避免永无休止的加班,对于了解设计模式的同学,可能会将筛选逻辑抽象出来 (step2: 面向对象)public interface Predicate<Apple> { boolean test(Apple apple);}预先定义多种筛选策略,将策略动态的传递给筛选函数public static List<Apple> filterApples(List<Apple> apples, Predicate predicate) { List<apple> filteredApples = new LinkedList<>(); for (Apple apple: apples) { if (predicate.test(apple)) { filteredApples.add(apple); } } return filteredApples;}Predicate predicate = new Predicate() { @override public boolean test(Apple apple) { return “red”.equals(apple.getColor()) && apple.getWeight > 150; }};List<Apple> satisfactoryApples = filterApples(inventory, predicate);或者直接使用匿名类,将筛选逻辑传递给筛选函数List<Apple> satisfactoryApples = filterApples(inventory, new Predicate() { @override public boolean test(Apple apple) { return “red”.equals(apple.getColor()) && apple.getWeight > 150; }});至此,已经可以满足大部分的需求,但对于这种十分啰嗦、被Java程序员诟病了多年的语法,在Lambda表达式出现后,便出现了一丝转机 (step3: 面向函数)@FunctionalInterfacepublic interface Predicate<Apple> { boolean test(Apple apple);}public List<Apple> filterApples(List<Apple> apples, Predicate<Apple> predicate) { return apples.stream.filter(predicate::test).collect(Collectors.toList());}List<Apple> satisfactoryApples = filterApples(inventory, apple -> “red”.equals(apple.getColor()) && apple.getWeight > 150);以上示例中使用了Java8的stream及lambda表达式,关于stream及lambda表达式的具体使用方法,这里不再赘述,重点在于解释什么是行为参数化,示例中直接将筛选逻辑(红色且重量大于150克)的代码片段作为参数传递给了函数(确切的说是将lambda表达式作为参数传递给了函数),而这段代码片段会交由筛选函数进行执行。Lambda表达式与匿名类很像,但本质不同,关于Lambda表达式及匿名类的区别,会在之后的文章详细介绍如果想让代码更为简洁明了,可以继续将筛选逻辑提取为函数,使用方法引用进行参数传递private boolean isRedColorAndWeightGt150(Apple apple) { return “red”.equals(apple.getColor()) && apple.getWeight > 150;}List<Apple> satisfactoryApples = filterApples(inventory, this::isRedColorAndWeightGt150);至此,我们完成了从面向过程到面向对象再到面向函数的编程思维转变,代码也更加具有语义化,不论是代码阅读还是维护,都较之前有了很大的提升等等,如果需要过滤颜色为黄色并且重量在180克以上的苹果,是不是还要定义一个isYellowColorAndWeightGt180的函数出来,貌似又陷入了无穷加班的怪圈~还有没有优化空间?能否将筛选条件抽离到单一属性,如byColor、byMinWeight等,之后再做与或计算传递给筛选函数?接下来就是我们要介绍的高阶函数高阶函数高阶函数是一个函数,它接收函数作为参数或将函数作为输出返回接受至少一个函数作为参数返回的结果是一个函数以上的定义听起来可能有些绕口。结合上节示例,我们的诉求是将苹果的颜色、重量或者其他筛选条件也抽离出来,而不是硬编码到代码中private Predicate<apple> byColor(String color) { return (apple) -> color.equals(apple.getColor);}private Predicate<Apple> byMinWeight(int minWeight) { return (apple) -> apple.getWeight > minWeight;}以上两个函数的返回值,均为Predicate类型的Lambda表达式,或者可以说,以上两个函数的返回值也是函数接下来我们定义与运算,只有传入的所有条件均满足才算最终满足private Predicate<Apple> allMatches(Predicate<Apple> …predicates) { return (apple) -> predicates.stream.allMatch(predicate -> predicate.test(apple));}以上函数,是将多个筛选逻辑做与计算,注意,该函数接收多个函数(Lambda)作为入参,并返回一个函数(Lambda),这便是高阶函数如何使用该函数?作为苹果筛选示例的延伸,我们可以将上一节最后一个示例代码优化如下List<Apple> satisfactoryApples = filterApples(inventory, allMatches(byColor(“red”), byMinWeight(150)));至此,还可以抽象出anyMatches、nonMatches等高阶函数,组合使用// 筛选出 颜色为红色 并且 重量在150克以上 并且 采摘时间在1周以内 并且 产地在中国、美国、加拿大任意之一的苹果List<Apple> satisfactoryApples = filterApples( inventory, allMatches( byColor(“red”), byMinWeight(150), apple -> apple.pluckingTime - currentTimeMillis() < 7L * 24 * 3600 * 1000, anyMatches(byGardens(“中国”), byGardens(“美国”), byGardens(“加拿大”) ));如果使用jvm包中的java.util.function.Predicate,我们还可以继续优化,使代码更为语义化// 筛选出 颜色为红色 并且 重量在150克以上 并且 采摘时间在1周以内 并且 产地在中国、美国、加拿大任意之一的苹果List<Apple> satisfactoryApples = filterApples( inventory, byColor(“red”) .and(byMinWeight(150)) .and(apple -> apple.pluckingTime - currentTimeMillis() < 7L * 24 * 3600 * 1000) .and(byGardens(“中国”).or(byGardens(“美国”).or(byGardens(“加拿大”))));这里使用了Java8中的默认函数,默认函数允许你在接口interface中定义函数的默认行为,从某方面来讲也可以实现类的多继承示例中,and/or函数接收一个Predicate函数(Lambda表达式)作为参数,并返回一个Predicate函数(Lambda表达式),同样为高阶函数关于默认函数的使用,会在之后的文章详细介绍闭包闭包(Closure),能够读取其他函数内部变量的函数又是一个比较抽象的概念,其实在使用Lambda表达式的过程中,经常会使用到闭包,比如public Future<List<Apple>> filterApplesAsync() { List<Apple> inventory = getInventory(); return CompletableFuture.supplyAsync(() -> filterApples(inventory, byColor(“red”)));}在提交异步任务时,传入了内部函数(Lambda表达式),在内部函数中使用了父函数filterApplesAsync中的局部变量inventory,这便是闭包。如果该示例不够明显的话,可以参考如下示例private Supplier<Integer> initIntIncreaser(int i) { AtomicInteger atomicInteger = new AtomicInteger(i); return () -> atomicInteger.getAndIncrement();}Supplier<Integer> increaser = initIntIncreaser(1);System.out.println(increaser.get());System.out.println(increaser.get());System.out.println(increaser.get());System.out.println(increaser.get());//[out]: 1//[out]: 2//[out]: 3//[out]: 4initIntIncreaser函数返回另一个函数(内部函数),该函数(increaser)使用了父函数initIntIncreaser的局部变量atomicInteger,该变量会被函数increaser持有,并且会在调用increaser时使用(更改)柯里化柯里化(Currying),是把接受多个参数的函数变换成接受一个单一参数的函数。柯里化是逐步传值,逐步缩小函数的适用范围,逐步求解的过程。如,设计一个函数,实现在延迟一定时间之后执行给定逻辑,并可以指定执行的执行器public ScheduledFuture executeDelay(Runnable runnable, ScheduledExecutorService scheduler, long delay, TimeUnit timeunit) { return scheduler.schedule(runnable, delay, timeunit);}目前有一批任务,需要使用执行器scheduler1,并且均延迟5分钟执行另一批任务,需要使用执行器scheduler2,并且均延迟15分钟执行可以这样实现executeDelay(runnable11, scheduler1, 5, TimeUnit.SECONDS);executeDelay(runnable12, scheduler1, 5, TimeUnit.SECONDS);executeDelay(runnable13, scheduler1, 5, TimeUnit.SECONDS);executeDelay(runnable21, scheduler2, 15, TimeUnit.SECONDS);executeDelay(runnable22, scheduler2, 15, TimeUnit.SECONDS);executeDelay(runnable23, scheduler2, 15, TimeUnit.SECONDS);其实,我们发现这里是有规律可循的,比如,使用某个执行器在多久之后执行什么,我们可以将executeDelay函数进行第一次柯里化public Function3<ScheduledFuture, Runnable, Integer, TimeUnit> executeDelayBySomeScheduler(ScheduledExecutorService scheduler) { return (runnable, delay, timeunit) -> executeDelay(runable, scheduler, delay, timeunit);}Function3<ScheduledFuture, Runnable, Integer, TimeUnit> executeWithScheduler1 = executeDelayBySomeScheduler(scheduler1);Function3<ScheduledFuture, Runnable, Integer, TimeUnit> executeWithScheduler2 = executeDelayBySomeScheduler(scheduler2);executeWithScheduler1.apply(runnable11, 5, TimeUnit.SECONDS);executeWithScheduler1.apply(runnable12, 5, TimeUnit.SECONDS);executeWithScheduler1.apply(runnable13, 5, TimeUnit.SECONDS);executeWithScheduler2.apply(runnable21, 15, TimeUnit.SECONDS);executeWithScheduler2.apply(runnable22, 15, TimeUnit.SECONDS);executeWithScheduler2.apply(runnable23, 15, TimeUnit.SECONDS);函数executeDelay接收4个参数,函数executeWithScheduler1/executeWithScheduler2接收3个参数,我们通过executeDelayBySomeScheduler将具体的执行器封装在了executeWithScheduler1/executeWithScheduler2中进一步,我们可以做第二次柯里化,将延迟时间也封装起来public Function<ScheduledFuture, Runnable> executeDelayBySomeSchedulerOnDelay(ScheduledExecutorService scheduler, long delay, TimeUnit timeunit) { return (runnable) -> executeDelay(runable, scheduler, delay, timeunit);}Function<ScheduledFuture, Runnable> executeWithScheduler1After5M = executeDelayBySomeSchedulerOnDelay(scheduler1, 5, TimeUnit.SECONDS);Function<ScheduledFuture, Runnable> executeWithScheduler2After15M = executeDelayBySomeSchedulerOnDelay(scheduler2, 15, TimeUnit.SECONDS);Stream.of(runnable11, runnable12,runnable13).forEach(this::executeWithScheduler1After5M);Stream.of(runnable21, runnable22,runnable23).forEach(this::executeWithScheduler2After15M);将具体的执行器及延迟时间封装在executeWithScheduler1After5M/executeWithScheduler2After15M中,调用的时候,只需要关心具体的执行逻辑即可环绕执行(提取共性)有时候我们会发现,很多代码块十分相似,但又有些许不同比如,目前有两个接口可以查询汇率,queryExchangeRateA及queryExchangeRateB,我们需要在开关exchangeRateSwitch打开的时候使用queryExchangeRateA查询,否则使用queryExchangeRateB查询,同时在一个接口异常失败的时候,自动降低到另一个接口进行查询同样,目前有两个接口可以查询关税,queryTariffsA及queryTariffsB,同样地,我们需要在开关tariffsSwitch打开的时候使用queryTariffsA查询,否则使用queryTariffsB查询,同时在一个接口异常失败的时候,自动降低到另一个接口进行查询其实,以上两种场景,除了开关及具体的接口逻辑外,整体流程是一致的再分析,其实接口调用的降级逻辑也是一样的这里不再列举如何使用抽象类的方法如解决该类问题,我们直接使用Java8的Lambda表达式首先,可以将降级逻辑提取为一个函数@FunctionalInterfaceinterface ThrowingSupplier<T> { T get() throw Exception;}/** * 1. 执行A * 2. 如果A执行异常,则执行B /public <T> ThrowingSupplier<T> executeIfThrowing(ThrowingSupplier<T> supplierA, ThrowingSupplier<T> supplierB) throws Exception { try { return supplierA.get(); } catch(Exception e) { // dill with exception return supplierB.get(); }}至此,我们完成了降级的逻辑。接来下,将开关逻辑提取为一个函数/* * 1. switcher打开,执行A * 2. switcher关闭,执行B /public <T> T invoke(Supplier<Boolean> switcher, ThrowingSupplier<T> executeA, ThrowingSupplier<T> executeB) throws Exception { return switcher.get() ? executeIfThrowing(this::queryExchangeRateA, this::queryExchangeRateB) : executeIfThrowing(this::queryExchangeRateB, this::queryExchangeRateA);}回到上边的两个需求,查询汇率及关税,我们可以/* * 查询汇率 /val exchangeRate = invoke( exchangeRateSwitch::isOpen, this::queryExchangeRateA, this::queryExchangeRateB)/* * 查询关税 */val queryTariffs = invoke( tariffsSwitch::isOpen, this::queryTariffsA, this::queryTariffsB)以上,用到了ThrowingSupplier,该点会在《 [Java] Lambda表达式“陷阱”》中详细介绍设计模式Lambda表达式,会给以往面向对象思想的设计模式带来全新的设计思路,这部分内容希望在设计模式系列文章中详细介绍。关于Lambda表达式,还有非常多的内容及技巧,无法使用有限的篇幅进行介绍,同时也希望与各位一同讨论。 ...

April 1, 2019 · 3 min · jiezi

说说Java 位运算

本文首发于个人微信公众号《andyqian》,期待你的关注前言 我们都知道,在计算机世界里,再复杂,再美的程序,到最后都会变成0与1。也就是我们常说的:二进制。二进制相信大家都很熟悉。与现实世界不同的是,在现实世界里,我们通常都是用十进制来表示的,也就是遇十进一,这些都是我们熟悉的。到这里,我们就会发现端倪,现实世界中的十进制与计算机中的二进制其计量单元是不一样的。那它们之间怎么转换呢?这就涉及到一些比较基础的计算机知识。不在本文中讨论(如果有兴趣,可以在下次讲讲)。嗯,回到今天的主题,来说说位运算,这又是一个怎样的概念呢?我们从小就开始接触,现实世界中的加减乘除这些运算,也就是十进制中的运算。今天我们要说的是:二进制位中的一些常用运算。例如:& (位与),| (位或) ,^(异或),<<(左移),>>(右移) 等等。真与假 在进行运算符使用之前,我们有必要说下真假。在Java中,我们都知道,用 true 值表示真,false 值表示假。其实在计算机中,通常使用 1 表示真,0表示假。使用过Json的同学应该知道,Java中的boolean类型,用1也是可以反序列化成true,0反序列化为false的。& (位与) 在说位与之前,我们先来说说我们熟悉的 && 逻辑与操作。简单来说: A&&B 也就是:A且B同时成立时为真,否则为假。也有人称之为:“一假必假”。现在我们再来看位与。首先,我们来看一段程序:@Testpublic void testBit(){ int a = 8; int b = 9; System.out.println(“a binary: “+Integer.toBinaryString(a)); System.out.println(“b binary: “+Integer.toBinaryString(b)); System.out.println(“a & b binary: “+Integer.toBinaryString(a&b)); System.out.println(“a & b result: “+(a&b));}再看解释之前,我们先猜猜结果是多少?代码解释:位与:我们从字面意思上来理解,也是二进制位的与操作。数字 8 的十进制是: 1000 。数字 9 的十进制是: 1001。我们再来进行位于操作:如下所示:8:10009:1001&8 1000最左边的 1&1 = 1,中间的 0&0 = 0,最右边的0&1 = 0。二进制的结果为:1000,转换为10进制后为 8。程序运行结果如下:a binary: 1000b binary: 1001a & b binary: 1000a & b result: 8结果是符合预期的。| (位或)上面说 & (位与) 操作,现在我们来看看位或操作,继续使用上面的例子:如下所示:@Testpublic void testBit(){ int a = 8; int b = 9; System.out.println(“a binary: “+Integer.toBinaryString(a)); System.out.println(“b binary: “+Integer.toBinaryString(b)); System.out.println(“a & b binary: “+Integer.toBinaryString(a|b)); System.out.println(“a & b result: “+(a|b));}再看看二进制:8:10009:1001|9 1001最左边的 1|1 = 1,中间的0|0 = 0 ,最右边的 0|1 = 1。结果二进制为: 1001 对应的10进制为 9。运算结果如下:a binary: 1000b binary: 1001a & b binary: 1001a & b result: 9^(异或)这个运算符比较有意思,异从字面上来理解是:不同的。放在位操作里也是一样的。继续使用上面的例子:@Testpublic void testBit(){ int a = 8; int b = 9; System.out.println(“a binary: “+Integer.toBinaryString(a)); System.out.println(“b binary: “+Integer.toBinaryString(b)); System.out.println(“a & b binary: “+Integer.toBinaryString(a^b)); System.out.println(“a & b result: “+(a^b));}继续看二进制:8:10009:1001^1 0001位相同时取假,不同时取真。左边的 1=1 相同取假,也就是0。中间的0=0 也为假为0。最右边的0不等于1,为真。结果也就为1。<<(左移)在现实世界里,我们经常使用乘法。<< 则表示二进制中的位移操作,低位补0。例如:8<<1。@Testpublic void testCode(){ int a =8; System.out.println(“a toBinaryString: “+Integer.toBinaryString(a)); System.out.println(“a<<1 toBinaryString: “+Integer.toBinaryString(a<<1)); System.out.println(“result: “+(a<<1));二进制如下:8 10008<<116 10000结果为: 2^4 = 16。 << 左边 a 表示基数, 右边 1 则表示需要位移动的位数。 箭头指向哪边,则向哪边位移。程序运行结果:a toBiryString: 1000a<<1 toBinaryString: 10000result: 16 >> 右移(右移) 与左移 << 则是相反的,高位补0 。继续上面的例子:@Testpublic void testCode(){ int a =8; System.out.println(“a toBinaryString: “+Integer.toBinaryString(a)); System.out.println(“1>>a toBinaryString: “+Integer.toBinaryString(a>>1)); System.out.println(“result: “+(a>>1)}二进制:8 : 10008>>14 : 0100运行结果:a toBinaryString: 1000a>>1 toBinaryString: 100result: 4其实这里还有一个比较好记的口诀:a>>n 则表示: a / (2^n) 次方。 (取整)a<<n 则结果为: a * (2^n) 次方。现在我们来速算一下:当a = 13, n = 2 时。13<<2 等于 13* 4 = 52 。 13/4 = 3。(上述速算法,如有错误,欢迎打脸!!!)我们在源码以及常见算法中位移运算是非常常见的,一位Java程序员掌握位运算也是很有必要的。这对我们算法,源码理解都非常有帮助!相关阅读:《上千行存储过程有感!》《软件之路》《浅谈 Java JPDA》《说说MySQL权限》<center></center> ...

March 7, 2019 · 2 min · jiezi

java8 内置函数(api)总结

常用的函数接口记录方便以后翻吧接口参数返回类型说明Predicate<T>Tboolean输入某个值,输出boolean 值,用于对某值进行判定Consumer<T>Tvoid输入某值,无输出。用于消费某值Function<T,R>TR输入某类型值,输出另种类型值,用于类型转化等Supplier<T>NoneT无输入,输出某值,用于生产某值UnaryOperator<T>TT输入某类型值,输出同类型值,用于值的同类型转化,如对值进行四则运算等BinaryOperator<T>(T,T)T输入两个某类型值,输出一个同类型值,用于两值合并等PredicatesPredicates是包含一个参数的布尔值接口。其包括一些缺省方法,组合他们使用可以实现复杂的业务逻辑(如:and, or, negate)。示例代码如下:Predicate<String> predicate = (s) -> s.length() > 0; predicate.test(“foo”); // truepredicate.negate().test(“foo”); // false Predicate<Boolean> nonNull = Objects::nonNull;Predicate<Boolean> isNull = Objects::isNull; Predicate<String> isEmpty = String::isEmpty;Predicate<String> isNotEmpty = isEmpty.negate();FunctionsFunctions接口接收一个参数并产生一个结果。其缺省方法通常被用来链接多个功能一起使用 (compose, andThen)。Function<String, Integer> toInteger = Integer::valueOf;Function<String, String> backToString = toInteger.andThen(String::valueOf); backToString.apply(“123”); // “123"SuppliersSuppliers接口生成一个给定类型结果。和Functions不同,其没有接收参数。Supplier<Person> personSupplier = Person::new;personSupplier.get(); // new PersonConsumersConsumers表现执行带有单个输入参数的操作。Consumer<Person> greeter = (p) -> System.out.println(“Hello, " + p.firstName);greeter.accept(new Person(“Luke”, “Skywalker”));ComparatorsComparators是从java旧版本升级并增加了一些缺省方法。Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName); Person p1 = new Person(“John”, “Doe”);Person p2 = new Person(“Alice”, “Wonderland”); comparator.compare(p1, p2); // > 0comparator.reversed().compare(p1, p2); // < 0Stream 常用方法创建Stream将现有数据结构转化成StreamStream<Integer> s = Stream.of(1, 2, 3, 4, 5);Stream<Integer> s = Arrays.stream(arr);Stream<Integer> s = aList.stream();通过Stream.generate()方法:// 这种方法通常表示无限序列Stream<T> s = Stream.generate(SuppLier<T> s);// 创建全体自然数的Streamclass NatualSupplier implements Supplier<BigInteger> { BigInteger next = BigInteger.ZERO; @Override public BigInteger get() { next = next.add(BigInteger.ONE); return next; }}通过其他方法返回Stream<String> lines = Files.lines(Path.get(filename))…map方法把一种操作运算映射到Stream的每一个元素上,从而完成一个Stream到另一个Stream的转换map方法接受的对象是Function接口,这个接口是一个函数式接口:<R> Stream<R> map(Function<? super T, ? extends R> mapper);@FunctionalInterfacepublic interface Function<T, R> { // 将T转换为R R apply(T t);}使用:// 获取Stream里每个数的平方的集合Stream<Integer> ns = Stream.of(1, 2, 3, 4, 5);ns.map(n -> n * n).forEach(System.out::println);flatMapmap方法是一个一对一的映射,每输入一个数据也只会输出一个值。 flatMap方法是一对多的映射,对每一个元素映射出来的仍旧是一个Stream,然后会将这个子Stream的元素映射到父集合中:Stream<List<Integer>> inputStream = Stream.of(Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6));List<Integer> integerList = inputStream.flatMap((childList) -> childList.stream()).collect(Collectors.toList());//将一个“二维数组”flat为“一维数组”integerList.forEach(System.out::println);filter方法filter方法用于过滤Stream中的元素,并用符合条件的元素生成一个新的Stream。filter方法接受的参数是Predicate接口对象,这个接口是一个函数式接口:Stream<T> filter(Predicate<? super T>) predicate;@FunctionInterfacepublic interface Predicate<T> { // 判断元素是否符合条件 boolean test(T t);}使用// 获取当前Stream所有偶数的序列Stream<Integer> ns = Stream.of(1, 2, 3, 4, 5);ns.filter(n -> n % 2 == 0).forEach(System.out::println);limit、skiplimit用于限制获取多少个结果,与数据库中的limit作用类似,skip用于排除前多少个结果。sortedsorted函数需要传入一个实现Comparator函数式接口的对象,该接口的抽象方法compare接收两个参数并返回一个整型值,作用就是排序,与其他常见排序方法一致。distinctdistinct用于剔除重复,与数据库中的distinct用法一致。findFirstfindFirst方法总是返回第一个元素,如果没有则返回空,它的返回值类型是Optional<T>类型,接触过swift的同学应该知道,这是一个可选类型,如果有第一个元素则Optional类型中保存的有值,如果没有第一个元素则该类型为空。Stream<User> stream = users.stream();Optional<String> userID = stream.filter(User::isVip).sorted((t1, t2) -> t2.getBalance() - t1.getBalance()).limit(3).map(User::getUserID).findFirst();userID.ifPresent(uid -> System.out.println(“Exists”));min、maxmin可以对整型流求最小值,返回OptionalInt。 max可以对整型流求最大值,返回OptionalInt。 这两个方法是结束操作,只能调用一次。allMatch、anyMatch、noneMatchallMatch:Stream中全部元素符合传入的predicate返回 trueanyMatch:Stream中只要有一个元素符合传入的predicate返回 truenoneMatch:Stream中没有一个元素符合传入的predicate返回 truereduce方法reduce方法将一个Stream的每一个元素一次作用于BiFunction,并将结果合并。reduce方法接受的方法是BinaryOperator接口对象。Optional<T> reduce(BinaryOperator<T> accumulator);@FuncationalInterfacepublic interface BinaryOperator<T> extends BiFunction<T, T, T> { // Bi操作,两个输入,一个输出 T apply(T t, T u);}使用:// 求当前Stream累乘的结果Stream<Integer> ns = Stream.of(1, 2, 3, 4, 5);int r = ns.reduce( (x, y) -> x * y ).get();System.out.println(r); ...

March 6, 2019 · 2 min · jiezi

java通过smtp服务 给指定邮箱发送邮件含附件

用程序发邮件首先需要一个smtp服务器,虽然说网上也有自建服务器的教程,但是由于工程量大,还要兼容各大邮箱厂商,有可能发送失败或被归为垃圾邮件。所以不推荐自建smtp服务器实现。推荐是有2种方法来实现 第三方邮箱发邮件1、买类似阿里云的smtp资源包(阿里云 1000条 / 2元)2、申请一个腾讯、网易163的邮箱,开通smtp服务端口,借由他们的服务器来转发。(其中部分第三方邮箱可以实现用自己的域名来接发邮件,例如service@baidu.com)本文中介绍的是第二种方法,用腾讯企业邮箱为例参考借鉴的大神的原文地址:https://www.cnblogs.com/LUA123/p/5575134.html这里重点只说明一下,腾讯企业邮箱 + javamail 来实现发邮件,代码的部分。其他邮箱,例如个人的qq邮箱 163邮箱也可以用这个方法实现,申请和设置方法借鉴百度吧补充一下!腾讯企业邮箱和qq邮箱方法有几个不同,我在末尾加了qq邮箱的方案正文开始先说腾讯企业邮箱maven<dependency> <groupId>javax.mail</groupId> <artifactId>mail</artifactId> <version>1.4.7</version></dependency>另外我用到了一个 StringUtils.isNotBlank() 方法 可以选择引入以下maven依赖,也可以改写成 xxx != null && !"".equals(xxx) 等价的代码<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.7</version></dependency>java 工具类需要把 用户名、密码、发件人别名 等参数填成你自己申请的package com.gemini.common.utils; import com.sun.mail.util.MailSSLSocketFactory;import org.apache.commons.lang.StringUtils; import java.io.IOException;import java.io.UnsupportedEncodingException;import java.security.GeneralSecurityException;import java.util.Date;import java.util.Properties; import javax.mail.Authenticator;import javax.mail.Message;import javax.mail.MessagingException;import javax.mail.Multipart;import javax.mail.PasswordAuthentication;import javax.mail.Session;import javax.mail.Transport;import javax.mail.internet.InternetAddress;import javax.mail.internet.MimeBodyPart;import javax.mail.internet.MimeMessage;import javax.mail.internet.MimeMultipart; public class EmailUtils { // 腾讯企业邮箱 也可以换成别家的 private static final String protocol = “smtp”;// 协议 private static final String host = “smtp.exmail.qq.com”;// 地址 private static final String port = “465”;// 端口 private static final String account = “用户名”;// 用户名 private static final String pass = “密码”;// 密码 private static final String personal = “发件人别名(选填)”;// 发件人别名,不需要设为空串或null // 权限认证 static class MyAuthenricator extends Authenticator { String u = null; String p = null; public MyAuthenricator(String u, String p) { this.u = u; this.p = p; } @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(u, p); } } /** * 发送邮件工具方法 * * @param recipients 收件人 * @param subject 主题 * @param content 内容 * @param fileStr 附件路径 * @return true/false 发送成功 */ public static boolean sendEmail(String recipients, String subject, String content, String fileStr) { Properties prop = new Properties(); //协议 prop.setProperty(“mail.transport.protocol”, protocol); //服务器 prop.setProperty(“mail.smtp.host”, host); //端口 prop.setProperty(“mail.smtp.port”, port); //使用smtp身份验证 prop.setProperty(“mail.smtp.auth”, “true”); //使用SSL,企业邮箱必需! //开启安全协议 MailSSLSocketFactory mailSSLSocketFactory = null; try { mailSSLSocketFactory = new MailSSLSocketFactory(); mailSSLSocketFactory.setTrustAllHosts(true); } catch (GeneralSecurityException e1) { e1.printStackTrace(); } prop.put(“mail.smtp.ssl.enable”, “true”); prop.put(“mail.smtp.ssl.socketFactory”, mailSSLSocketFactory); Session session = Session.getDefaultInstance(prop, new MyAuthenricator(account, pass)); session.setDebug(true); MimeMessage mimeMessage = new MimeMessage(session); try { //发件人 if (StringUtils.isNotBlank(personal)) mimeMessage.setFrom(new InternetAddress(account, personal));//可以设置发件人的别名 else mimeMessage.setFrom(new InternetAddress(account));//如果不需要就省略 //收件人 mimeMessage.addRecipient(Message.RecipientType.TO, new InternetAddress(recipients)); //主题 mimeMessage.setSubject(subject); //时间 mimeMessage.setSentDate(new Date()); //容器类,可以包含多个MimeBodyPart对象 Multipart mp = new MimeMultipart(); //MimeBodyPart可以包装文本,图片,附件 MimeBodyPart body = new MimeBodyPart(); //HTML正文 body.setContent(content, “text/html; charset=UTF-8”); mp.addBodyPart(body); //添加图片&附件 if(StringUtils.isNotBlank(fileStr)){ body = new MimeBodyPart(); body.attachFile(fileStr); mp.addBodyPart(body); } //设置邮件内容 mimeMessage.setContent(mp); //仅仅发送文本 //mimeMessage.setText(content); mimeMessage.saveChanges(); Transport.send(mimeMessage); // 发送成功 return true; } catch (MessagingException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return false; } public static void main(String[] args) { sendEmail(“你的邮箱地址”,“test”,“test”,null); }}关于上述方案,适用于一般的邮箱申请(腾讯企业邮箱、网易邮箱),但不适用于qq邮箱,原因是qq邮箱目前只接受授权码方案登录,官方的解释是“温馨提示:在第三方登录QQ邮箱,可能存在邮件泄露风险,甚至危害Apple ID安全,建议使用QQ邮箱手机版登录。 继续获取授权码登录第三方客户端邮箱 。”使用上述方法登录qq邮箱会遇到报错javax.mail.AuthenticationFailedException: 535 Error: ÇëʹÓÃÊÚȨÂëµÇ¼¡£ÏêÇéÇë¿´: http://service.mail.qq.com/cgi-bin/help?subtype=1&&id=28&&no=1001256如图意思就是不支持直接用默认密码登录,必须去申请一个授权码作为密码登录其实流程和工具类都一样就重点说 2个不一样的地方1、密码不是你的邮箱密码了,而是授权码。获取方式 [登录邮箱] - [设置] - [账户] ,然后如下图找到POP3/SMTP服务的下面,有一句温馨提示 先点 [生成授权码] ,再根据提示获取到授权码。授权码就是javamail里的password2、host不同腾讯企业邮箱的host是private static final String host = “smtp.exmail.qq.com”;// 地址普通qq邮箱的host是private static final String host = “smtp.qq.com”;// 地址修改这两个地方即可适用于个人普通的qq邮箱最终效果如下另外本文也发布在了我的个人博客: https://zzzmh.cn/single?id=49 ...

January 17, 2019 · 2 min · jiezi

Java 8 Stream并行流

流可以并行执行,以增加大量输入元素的运行时性能。并行流ForkJoinPool通过静态ForkJoinPool.commonPool()方法使用公共可用的流。底层线程池的大小最多使用五个线程 - 具体取决于可用物理CPU核心的数量:ForkJoinPool commonPool = ForkJoinPool.commonPool();System.out.println(commonPool.getParallelism()); // 3在我的机器上,公共池初始化为默认值为3的并行度。通过设置以下JVM参数可以减小或增加此值:-Djava.util.concurrent.ForkJoinPool.common.parallelism=5集合支持创建并行元素流的方法parallelStream()。或者,您可以在给定流上调用中间方法parallel(),以将顺序流转换为并行流。为了评估并行流的并行执行行为,下一个示例将有关当前线程的信息打印出来:Arrays.asList(“a1”, “a2”, “b1”, “c2”, “c1”) .parallelStream() .filter(s -> { System.out.format(“filter: %s [%s]\n”, s, Thread.currentThread().getName()); return true; }) .map(s -> { System.out.format(“map: %s [%s]\n”, s, Thread.currentThread().getName()); return s.toUpperCase(); }) .forEach(s -> System.out.format(“forEach: %s [%s]\n”, s, Thread.currentThread().getName()));通过调查调试输出,我们应该更好地理解哪些线程实际用于执行流操作:filter: b1 [main]filter: a2 [ForkJoinPool.commonPool-worker-1]map: a2 [ForkJoinPool.commonPool-worker-1]filter: c2 [ForkJoinPool.commonPool-worker-3]map: c2 [ForkJoinPool.commonPool-worker-3]filter: c1 [ForkJoinPool.commonPool-worker-2]map: c1 [ForkJoinPool.commonPool-worker-2]forEach: C2 [ForkJoinPool.commonPool-worker-3]forEach: A2 [ForkJoinPool.commonPool-worker-1]map: b1 [main]forEach: B1 [main]filter: a1 [ForkJoinPool.commonPool-worker-3]map: a1 [ForkJoinPool.commonPool-worker-3]forEach: A1 [ForkJoinPool.commonPool-worker-3]forEach: C1 [ForkJoinPool.commonPool-worker-2]如您所见,并行流利用公共中的所有可用线程ForkJoinPool来执行流操作。输出在连续运行中可能不同,因为实际使用的特定线程的行为是非确定性的。让我们通过一个额外的流操作来扩展该示例:Arrays.asList(“a1”, “a2”, “b1”, “c2”, “c1”) .parallelStream() .filter(s -> { System.out.format(“filter: %s [%s]\n”, s, Thread.currentThread().getName()); return true; }) .map(s -> { System.out.format(“map: %s [%s]\n”, s, Thread.currentThread().getName()); return s.toUpperCase(); }) .sorted((s1, s2) -> { System.out.format(“sort: %s <> %s [%s]\n”, s1, s2, Thread.currentThread().getName()); return s1.compareTo(s2); }) .forEach(s -> System.out.format(“forEach: %s [%s]\n”, s, Thread.currentThread().getName()));结果可能最初看起来很奇怪:filter: c2 [ForkJoinPool.commonPool-worker-3]filter: c1 [ForkJoinPool.commonPool-worker-2]map: c1 [ForkJoinPool.commonPool-worker-2]filter: a2 [ForkJoinPool.commonPool-worker-1]map: a2 [ForkJoinPool.commonPool-worker-1]filter: b1 [main]map: b1 [main]filter: a1 [ForkJoinPool.commonPool-worker-2]map: a1 [ForkJoinPool.commonPool-worker-2]map: c2 [ForkJoinPool.commonPool-worker-3]sort: A2 <> A1 [main]sort: B1 <> A2 [main]sort: C2 <> B1 [main]sort: C1 <> C2 [main]sort: C1 <> B1 [main]sort: C1 <> C2 [main]forEach: A1 [ForkJoinPool.commonPool-worker-1]forEach: C2 [ForkJoinPool.commonPool-worker-3]forEach: B1 [main]forEach: A2 [ForkJoinPool.commonPool-worker-2]forEach: C1 [ForkJoinPool.commonPool-worker-1]似乎sort只在主线程上顺序执行。实际上,sort在并行流上使用新的Java 8方法Arrays.parallelSort()。如Javadoc中所述,如果排序将按顺序或并行执行,则此方法决定数组的长度:如果指定数组的长度小于最小粒度,则使用适当的Arrays.sort方法对其进行排序。回到reduce一节的例子。我们已经发现组合器函数只是并行调用,而不是顺序流调用。让我们看看实际涉及哪些线程:List<Person> persons = Arrays.asList( new Person(“Max”, 18), new Person(“Peter”, 23), new Person(“Pamela”, 23), new Person(“David”, 12));persons .parallelStream() .reduce(0, (sum, p) -> { System.out.format(“accumulator: sum=%s; person=%s [%s]\n”, sum, p, Thread.currentThread().getName()); return sum += p.age; }, (sum1, sum2) -> { System.out.format(“combiner: sum1=%s; sum2=%s [%s]\n”, sum1, sum2, Thread.currentThread().getName()); return sum1 + sum2; });控制台输出显示累加器和组合器函数在所有可用线程上并行执行:accumulator: sum=0; person=Pamela; [main]accumulator: sum=0; person=Max; [ForkJoinPool.commonPool-worker-3]accumulator: sum=0; person=David; [ForkJoinPool.commonPool-worker-2]accumulator: sum=0; person=Peter; [ForkJoinPool.commonPool-worker-1]combiner: sum1=18; sum2=23; [ForkJoinPool.commonPool-worker-1]combiner: sum1=23; sum2=12; [ForkJoinPool.commonPool-worker-2]combiner: sum1=41; sum2=35; [ForkJoinPool.commonPool-worker-2]总之,并行流可以为具有大量输入元素的流带来良好的性能提升。但请记住,某些并行流操作reduce,collect需要额外的计算(组合操作),这在顺序执行时是不需要的。此外,我们了解到所有并行流操作共享相同的JVM范围ForkJoinPool。因此,您可能希望避免实施慢速阻塞流操作,因为这可能会减慢严重依赖并行流的应用程序的其他部分。 ...

January 9, 2019 · 2 min · jiezi

Java 8 Strem高级操作

Streams支持大量不同的操作。我们已经了解了最重要的操作,如filter,map。发现所有其他可用的操作(参见Stream Javadoc)。我们深入研究更复杂的操作collect,flatMap,reduce。本节中的大多数代码示例使用以下人员列表进行演示:class Person { String name; int age; Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return name; }}List<Person> persons = Arrays.asList( new Person(“Max”, 18), new Person(“Peter”, 23), new Person(“Pamela”, 23), new Person(“David”, 12));CollectCollect是一个非常有用的终端操作,以流的元素转变成一种不同的结果,例如一个List,Set或Map。Collect接受Collector包含四种不同操作的操作:供应商,累加器,组合器和修整器。这听起来非常复杂,但是Java 8通过Collectors类支持各种内置收集器。因此,对于最常见的操作,您不必自己实现收集器。让我们从一个非常常见的用例开始:List<Person> filtered = persons .stream() .filter(p -> p.name.startsWith(“P”)) .collect(Collectors.toList());System.out.println(filtered);代码输出: [Peter, Pamela]正如您所看到的,流的元素构造列表非常简单。需要一个集合而不是列表 - 只需使用Collectors.toList()。下一个示例按年龄对所有人进行分组:Map<Integer, List<Person>> personsByAge = persons .stream() .collect(Collectors.groupingBy(p -> p.age));personsByAge .forEach((age, p) -> System.out.format(“age %s: %s\n”, age, p));代码产出age 18: [Max]age 23: [Peter, Pamela]age 12: [David]您还可以在流的元素上创建聚合,例如,确定所有人的平均年龄:Double averageAge = persons .stream() .collect(Collectors.averagingInt(p -> p.age));System.out.println(averageAge); 代码产出19.0如果您对更全面的统计信息感兴趣,汇总收集器将返回一个特殊的内置摘要统计信息对象。因此,我们可以简单地确定人的最小,最大和算术平均年龄以及总和和计数。IntSummaryStatistics ageSummary = persons .stream() .collect(Collectors.summarizingInt(p -> p.age));System.out.println(ageSummary);代码产出IntSummaryStatistics{count=4, sum=76, min=12, average=19.000000, max=23}下一个示例将所有人连接成一个字符串:String phrase = persons .stream() .filter(p -> p.age >= 18) .map(p -> p.name) .collect(Collectors.joining(" and “, “In Germany “, " are of legal age.”));System.out.println(phrase);代码产出In Germany Max and Peter and Pamela are of legal age.Collect接受分隔符以及可选的前缀和后缀。为了将流元素转换为映射,我们必须指定如何映射键和值。请记住,映射的键必须是唯一的,否则抛出一个IllegalStateException。您可以选择将合并函数作为附加参数传递以绕过异常:Map<Integer, String> map = persons .stream() .collect(Collectors.toMap( p -> p.age, p -> p.name, (name1, name2) -> name1 + “;” + name2));System.out.println(map);代码产出{18=Max, 23=Peter;Pamela, 12=David}现在我们知道了一些强大的Collect,让我们尝试构建我们自己的特殊Collect。我们希望将流的所有人转换为单个字符串,该字符串由|管道字符分隔的大写字母组成。为了实现这一目标,我们创建了一个新的Collector.of()。Collector<Person, StringJoiner, String> personNameCollector = Collector.of( () -> new StringJoiner(” | “), // supplier (j, p) -> j.add(p.name.toUpperCase()), // accumulator (j1, j2) -> j1.merge(j2), // combiner StringJoiner::toString); // finisherString names = persons .stream() .collect(personNameCollector);System.out.println(names);// MAX | PETER | PAMELA | DAVID由于Java中的字符串是不可变的,我们需要一个帮助类StringJoiner,让Collect构造我们的字符串。供应商最初使用适当的分隔符构造这样的StringJoiner。累加器用于将每个人的大写名称添加到StringJoiner。组合器知道如何将两个StringJoiners合并为一个。在最后一步中,整理器从StringJoiner构造所需的String。FlatMap我们已经学会了如何利用map操作将流的对象转换为另一种类型的对象。Map有点受限,因为每个对象只能映射到另一个对象。但是如果我们想要将一个对象转换为多个其他对象或者根本不转换它们呢?这是flatMap救援的地方。FlatMap将流的每个元素转换为其他对象的流。因此,每个对象将被转换为由流支持的零个,一个或多个其他对象。然后将这些流的内容放入返回flatMap操作流中。在我们看到flatMap实际操作之前,我们需要一个适当的类型层class Foo { String name; List<Bar> bars = new ArrayList<>(); Foo(String name) { this.name = name; }}class Bar { String name; Bar(String name) { this.name = name; }}接下来,我们利用有关流的知识来实例化几个对象:List<Foo> foos = new ArrayList<>();// create foosIntStream .range(1, 4) .forEach(i -> foos.add(new Foo(“Foo” + i)));// create barsfoos.forEach(f -> IntStream .range(1, 4) .forEach(i -> f.bars.add(new Bar(“Bar” + i + " <- " + f.name))));现在我们列出了三个foos,每个foos由三个数据组成。FlatMap接受一个必须返回对象流的函数。所以为了解决每个foo的bar对象,我们只传递相应的函数:foos.stream() .flatMap(f -> f.bars.stream()) .forEach(b -> System.out.println(b.name));代码产出Bar1 <- Foo1Bar2 <- Foo1Bar3 <- Foo1Bar1 <- Foo2Bar2 <- Foo2Bar3 <- Foo2Bar1 <- Foo3Bar2 <- Foo3Bar3 <- Foo3如您所见,我们已成功将三个foo对象的流转换为九个bar对象的流。最后,上面的代码示例可以简化为流操作的单个管道:IntStream.range(1, 4) .mapToObj(i -> new Foo(“Foo” + i)) .peek(f -> IntStream.range(1, 4) .mapToObj(i -> new Bar(“Bar” + i + " <- " f.name)) .forEach(f.bars::add)) .flatMap(f -> f.bars.stream()) .forEach(b -> System.out.println(b.name));FlatMap也可用于Java 8中引入的Optional类。Optionals flatMap操作返回另一种类型的可选对象。因此,它可以用来防止令人讨厌的null检查。这样一个高度分层的结构:class Outer { Nested nested;}class Nested { Inner inner;}class Inner { String foo;}为了解析foo外部实例的内部字符串,您必须添加多个空值检查以防止可能的NullPointerExceptions:Outer outer = new Outer();if (outer != null && outer.nested != null && outer.nested.inner != null) { System.out.println(outer.nested.inner.foo);}利用选项flatMap操作可以获得相同的行为:Optional.of(new Outer()) .flatMap(o -> Optional.ofNullable(o.nested)) .flatMap(n -> Optional.ofNullable(n.inner)) .flatMap(i -> Optional.ofNullable(i.foo)) .ifPresent(System.out::println);每个调用flatMap返回一个Optional包装所需对象(如果存在)或null不存在。ReduceReduce操作将流的所有元素组合成单个结果。Java 8支持三种不同的reduce方法。第一个将元素流简化为流的一个元素。让我们看看我们如何使用这种方法来确定最老的人:persons .stream() .reduce((p1, p2) -> p1.age > p2.age ? p1 : p2) .ifPresent(System.out::println); // Pamelareduce方法接受一个BinaryOperator累加器函数。这实际上是一个双函数,两个操作数共享同一类型,在这种情况下是Person。双函数类似于函数,但接受两个参数。示例函数比较两个人的年龄,以返回年龄最大的人。第二种reduce方法接受标识值和BinaryOperator累加器。此方法可用于构造一个新的Person,其中包含来自流中所有其他人的聚合名称和年龄:Person result = persons .stream() .reduce(new Person(””, 0), (p1, p2) -> { p1.age += p2.age; p1.name += p2.name; return p1; });System.out.format(“name=%s; age=%s”, result.name, result.age);// name=MaxPeterPamelaDavid; age=76第三种reduce方法接受三个参数:标识值,BiFunction累加器和类型的组合器函数BinaryOperator。由于身份值类型不限于Person类型,我们可以利用reduce来确定所有人的年龄总和:Integer ageSum = persons .stream() .reduce(0, (sum, p) -> sum += p.age, (sum1, sum2) -> sum1 + sum2);System.out.println(ageSum); // 76正如你所看到的结果是76,但是究竟发生了什么?让我们通过一些调试输出扩展上面的代码:Integer ageSum = persons .stream() .reduce(0, (sum, p) -> { System.out.format(“accumulator: sum=%s; person=%s\n”, sum, p); return sum += p.age; }, (sum1, sum2) -> { System.out.format(“combiner: sum1=%s; sum2=%s\n”, sum1, sum2); return sum1 + sum2; });代码产出accumulator: sum=0; person=Maxaccumulator: sum=18; person=Peteraccumulator: sum=41; person=Pamelaaccumulator: sum=64; person=David正如你所看到的,累加器函数完成了所有的工作。它首先以初始恒等值0和第一个person Max被调用。在接下来的三个步骤中,总和随着最后一个步骤的年龄不断增加,人的总年龄达到76岁。为什么组合器永远不会被调用?并行执行相同的流将解除秘密:Integer ageSum = persons .parallelStream() .reduce(0, (sum, p) -> { System.out.format(“accumulator: sum=%s; person=%s\n”, sum, p); return sum += p.age; }, (sum1, sum2) -> { System.out.format(“combiner: sum1=%s; sum2=%s\n”, sum1, sum2); return sum1 + sum2; });代码产出accumulator: sum=0; person=Pamelaaccumulator: sum=0; person=Davidaccumulator: sum=0; person=Maxaccumulator: sum=0; person=Petercombiner: sum1=18; sum2=23combiner: sum1=23; sum2=12combiner: sum1=41; sum2=35并行执行此流会导致完全不同的执行行为。现在实际上调用了组合器。由于累加器是并行调用的,因此需要组合器来对各个累加值求和。 ...

January 9, 2019 · 3 min · jiezi

Java 8 Strem基本操作

本文提供了有关Java 8 Stream的深入概述。当我第一次读到的Stream API,我感到很困惑,因为它听起来类似Java I/O的InputStream,OutputStream。但Java 8 Stream是完全不同的东西。Streams是Monads,因此在为Java提供函数式编程方面发挥了重要作用:在函数式编程中,monad是表示定义为步骤序列的计算的结构。具有monad结构的类型定义链操作的含义,或将该类型的函数嵌套在一起。本文详解如何使用Java 8 Stream以及如何使用不同类型的可用流操作。您将了解处理顺序以及流操作的顺序如何影响运行时性能。并对更强大的reduce,collect,flatMap流操作详细介绍。如果您还不熟悉Java 8 lambda表达式,函数接口和方法引用,那么您可能需要了解Java 8。Stram如何工作Stream表示一系列元素,并支持不同类型的操作以对这些元素执行计算:List<String> streams = Arrays.asList(“a1”, “a2”, “b1”, “c2”, “c1”);streams .stream() .filter(s -> s.startsWith(“c”)) .map(String::toUpperCase) .sorted() .forEach(System.out::println);以上代码的产出:C1C2Stream操作是中间操作或终端操作。中间操作返回一个流,因此我们可以链接多个中间操作而不使用分号。终端操作无效或返回非流结果。在上述例子中filter,map和sorted是中间操作,而forEach是一个终端的操作。有关所有可用流操作的完整列表,请参阅Stream Javadoc。如上例中所见的这种流操作链也称为操作管道。大多数流操作都接受某种lambda表达式参数,这是一个指定操作的确切行为的功能接口。大多数这些操作必须是不受干扰和无状态。当函数不修改流的基础数据源时,该函数是不受干扰的,例如在上面的示例中,没有lambda表达式通过从集合中添加或删除元素来修改streams。当操作的执行是确定性的时,函数是无状态的,例如在上面的示例中,没有lambda表达式依赖于任何可变变量或来自外部作用域的状态,其可能在执行期间改变。不同种类的Stream可以从各种数据源创建流,尤其是集合。Lists和Sets支持新的方法stream()和parallelStream()来创建顺序流或并行流。并行流能够在多个线程上操作,后面的部分将对此进行介绍。我们现在关注的是顺序流:Arrays.asList(“a1”, “a2”, “a3”) .stream() .findFirst() .ifPresent(System.out::println);以上代码的产出:a1在对象列表上调用stream()方法将返回常规对象流。但是我们不必创建集合以便使用流,就像我们在下一个代码示例中看到的那样:Stream.of(“a1”, “a2”, “a3”) .findFirst() .ifPresent(System.out::println);以上代码的产出:a1只是用来Stream.of()从一堆对象引用创建一个流。除了常规对象流之外,Java 8还附带了特殊类型的流,用于处理原始数据类型int,long以及double。你可能已经猜到了IntStream,LongStream,DoubleStream。IntStreams可以使用IntStream.range()方法替换常规for循环:IntStream.range(1, 4) .forEach(System.out::println);以上代码的产出:123所有这些原始流都像常规对象流一样工作,但有以下不同之处:原始流使用专门的lambda表达式,例如IntFunction代替Function或IntPredicate代替Predicate。原始流支持额外的终端聚合操作,sum(),average():Arrays.stream(new int[] {1, 2, 3}) .map(n -> 2 * n + 1) .average() .ifPresent(System.out::println);以上代码的产出:5.0有时将常规对象流转换为基本流是有用的,反之亦然。为此,对象流支持特殊的映射操作mapToInt(),mapToLong(),mapToDouble:Stream.of(“a1”, “a2”, “a3”) .map(s -> s.substring(1)) .mapToInt(Integer::parseInt) .max() .ifPresent(System.out::println);以上代码的产出:3可以通过mapToObj()方式将原始流转换为对象流:IntStream.range(1, 4) .mapToObj(i -> “a” + i) .forEach(System.out::println);以上代码的产出:a1a2a3下面是一个组合示例:双精度流首先映射到int流,然后映射到字符串的对象流:Stream.of(1.0, 2.0, 3.0) .mapToInt(Double::intValue) .mapToObj(i -> “a” + i) .forEach(System.out::println);以上代码的产出:a1a2a3处理过程现在我们已经学会了如何创建和使用不同类型的流,让我们深入了解如何在流程下处理流操作。中间操作的一个重要特征是懒惰。查看缺少终端操作的示例:Stream.of(“d2”, “a2”, “b1”, “b3”, “c”) .filter(s -> { System.out.println(“filter: " + s); return true; });执行此代码段时,不会向控制台打印任何内容。这是因为只有在存在终端操作时才执行中间操作。让我们通过forEach终端操作扩展上面的例子:Stream.of(“d2”, “a2”, “b1”, “b3”, “c”) .filter(s -> { System.out.println(“filter: " + s); return true; }) .forEach(s -> System.out.println(“forEach: " + s));执行此代码段会在控制台上产生所需的输出:filter: d2forEach: d2filter: a2forEach: a2filter: b1forEach: b1filter: b3forEach: b3filter: cforEach: c结果的顺序可能会令人惊讶。默认认为是在流的所有元素上一个接一个地水平执行操作。但相反,每个元素都沿着链垂直移动。第一个字符串“d2”通过filter,然后forEach,然后处理第二个字符串“a2”。此行为可以减少对每个元素执行的实际操作数,如下一个示例所示:Stream.of(“d2”, “a2”, “b1”, “b3”, “c”) .map(s -> { System.out.println(“map: " + s); return s.toUpperCase(); }) .anyMatch(s -> { System.out.println(“anyMatch: " + s); return s.startsWith(“A”); });代码产出map: d2anyMatch: D2map: a2anyMatch: A2一旦谓词应用于给定的输入元素,anyMatch操作将返回true。这对于传递给“A2”的第二个元素是正确的。由于流链的垂直执行,map在这种情况下映射只需执行两次。因此,不是映射流的所有元素,而是map尽可能少地调用。复杂的处理过程下一个示例包括两个map,filter中间操作和forEach终端操作。让我们再次检查这些操作是如何执行的:Stream.of(“d2”, “a2”, “b1”, “b3”, “c”) .map(s -> { System.out.println(“map: " + s); return s.toUpperCase(); }) .filter(s -> { System.out.println(“filter: " + s); return s.startsWith(“A”); }) .forEach(s -> System.out.println(“forEach: " + s));代码产出:map: d2filter: D2map: a2filter: A2forEach: A2map: b1filter: B1map: b3filter: B3map: cfilter: C正如您可能已经猜到的,对于底层集合中的每个字符串,map和filter都被调用5次,而forEach只被调用一次。如果我们改变操作的顺序,移动filter到链的开头,我们可以大大减少实际的执行次数:Stream.of(“d2”, “a2”, “b1”, “b3”, “c”) .filter(s -> { System.out.println(“filter: " + s); return s.startsWith(“a”); }) .map(s -> { System.out.println(“map: " + s); return s.toUpperCase(); }) .forEach(s -> System.out.println(“forEach: " + s));代码产出:filter: d2filter: a2map: a2forEach: A2filter: b1filter: b3filter: c现在,map只调用一次,因此操作管道对大量输入元素的执行速度要快得多。在编写复杂的方法链时要记住这一点。让我们通过一个sorted额外的操作来扩展上面的例子:Stream.of(“d2”, “a2”, “b1”, “b3”, “c”) .sorted((s1, s2) -> { System.out.printf(“sort: %s; %s\n”, s1, s2); return s1.compareTo(s2); }) .filter(s -> { System.out.println(“filter: " + s); return s.startsWith(“a”); }) .map(s -> { System.out.println(“map: " + s); return s.toUpperCase(); }) .forEach(s -> System.out.println(“forEach: " + s));排序是一种特殊的中间操作。这是一个所谓的有状态操作,因为为了对在排序期间必须维护状态的元素集合进行排序。执行此示例将导致以下控制台输出:sort: a2; d2sort: b1; a2sort: b1; d2sort: b1; a2sort: b3; b1sort: b3; d2sort: c; b3sort: c; d2filter: a2map: a2forEach: A2filter: b1filter: b3filter: cfilter: d2首先,对整个输入集合执行排序操作。换句话说,sorted是水平执行的。因此,在这种情况下sorted,对输入集合中的每个元素的多个组合调用八次。我们可以通过重新排序链来优化性能:Stream.of(“d2”, “a2”, “b1”, “b3”, “c”) .filter(s -> { System.out.println(“filter: " + s); return s.startsWith(“a”); }) .sorted((s1, s2) -> { System.out.printf(“sort: %s; %s\n”, s1, s2); return s1.compareTo(s2); }) .map(s -> { System.out.println(“map: " + s); return s.toUpperCase(); }) .forEach(s -> System.out.println(“forEach: " + s));代码产出filter: d2filter: a2filter: b1filter: b3filter: cmap: a2forEach: A2在此示例sorted从未被调用过,因为filter将输入集合减少到只有一个元素。因此,对于较大的输入集合,性能会大大提高。重用StreamJava 8 Stream无法重用。只要您调用任何终端操作,流就会关闭:Stream<String> stream = Stream.of(“d2”, “a2”, “b1”, “b3”, “c”) .filter(s -> s.startsWith(“a”));stream.anyMatch(s -> true); // okstream.noneMatch(s -> true); // exception在同一流上的anyMatch之后调用noneMatch会导致以下异常:java.lang.IllegalStateException: stream has already been operated upon or closed at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229) at java.util.stream.ReferencePipeline.noneMatch(ReferencePipeline.java:459) at com.winterbe.java8.Streams5.test7(Streams5.java:38) at com.winterbe.java8.Streams5.main(Streams5.java:28)为了克服这个限制,我们必须为我们想要执行的每个终端操作创建一个新的流链,例如我们可以创建一个流供应商来构建一个新的流,其中已经设置了所有中间操作:Supplier<Stream<String>> streamSupplier = () -> Stream.of(“d2”, “a2”, “b1”, “b3”, “c”) .filter(s -> s.startsWith(“a”));streamSupplier.get().anyMatch(s -> true); // okstreamSupplier.get().noneMatch(s -> true); // ok每次调用get()构造一个我们保存的新流,以调用所需的终端操作。 ...

January 9, 2019 · 2 min · jiezi