目录1、安装JDK2、Android SDK安装3、安装gradle4、打包APK在centos环境下，想实现Android程序的编译，只能通过gradle 命令去打包版本apk。如下：1. 安装JDK============JDK下载地址：https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html# 这里使用的是openjdk 1.8.0版本，有需要的话需要到java官网上进行下载对应的JDK版本。 $ yum install java -y # 其他版本JDK的安装方式 $ mv jdk1.8.0_161 /usr/local/ $ ln -s /usr/local/jdk1.8.0_161 /usr/local/jdk $ vim /etc/profile #配置JDK的环境变量 export JAVA_HOME=/usr/local/jdk export PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH export CLASSPATH=.$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib:$JAVA_HOME/lib/tools.jar $ source /etc/profile #重新加载系统环境变量 $ java -version #查看java版本2. Android SDK安装====================# 下载sdk工具包 $ wget https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip # 创建sdk工具文件夹和解压工具包 $ mkdir -p /opt/android/sdk $ unzip sdk-tools-linux-3859397.zip -d /opt/android/sdk # 使用sdkmanager工具配置构建工具和平台版本 $ cd /opt/android/sdk/tools/bin/ $ ./sdkmanager "build-tools;31.0.0" "platforms;android-31" "platform-tools" $ ./sdkmanager --list #可以查看有哪些版本，自行选择对应的版本 # 增加系统环境变量 $ vim /etc/profile export ANDROID_HOME=/opt/android/sdk PATH=$PATH:$ANDROID_HOME:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator:$ANDROID_HOME/tools/bin $ adb version Android Debug Bridge version 1.0.41 Version 31.0.3-7562133 Installed as /opt/android/sdk/platform-tools/adb3. 安装gradle===============下载地址：https://services.gradle.org/distributions/$ wget https://downloads.gradle-dn.com/distributions/gradle-6.3-all.zip $ mkdir /opt/gradle $ unzip gradle-6.3-all.zip -d /opt/gradle/ $ vim /etc/profile export PATH=$PATH:/opt/gradle/gradle-6.3/bin $ source /etc/profile $ gradle -v ------------------------------------------------------------ Gradle 6.3 ------------------------------------------------------------ Build time: 2020-03-24 19:52:07 UTC Revision: bacd40b727b0130eeac8855ae3f9fd9a0b207c60 Kotlin: 1.3.70 Groovy: 2.5.10 Ant: Apache Ant(TM) version 1.10.7 compiled on September 1 2019 JVM: 1.8.0_201 (Oracle Corporation 25.201-b09) OS: Linux 3.10.0-693.el7.x86_64 amd644. 打包APK============程序从windows Android Studio 上移植过来，程序目录结构如下：进入项目的根目录，执行打包命令：gradle assembleRelease（打包成Release版本） gradle assembleDebug（打包成Debug版本）打包编译完成，在项目的app/build/outputs/apk中可以找到debug版本或者是release版本。本文转自 Centos系统下部署Android打包生成APK - 烟雨浮华，如有侵权，请联系删除。
自从上次狗剩被隔壁大娘嚼舌根以后，便怀恨在心，一气之下破解了大妈的农场密码，并偷走了大娘种的菜。狗剩：“隔壁大娘，快去农场看看吧，你菜死了！”￼万能账号密码一、什么是万能账号密码1、万能账号1.1 万能账号的使用2、万能密码2.1 万能密码的使用二、万能账号密码原理剖析1、万能账号原理1.1 注释符1.2 比较运算符2、万能密码原理2.1 注释符总结一、什么是万能账号密码我们平时登录账号时，如果是第一次登录，系统会提示我们注册账号，并将我们注册的账号和密码保存到数据库中。当我们再次登录时，系统会将我们输入的账号和密码和数据库中的数据进行匹配，匹配成功能登录。这就意味着我们需要知道一个已经注册过并且正确的账号和密码才能进行登录，那如果我不小心忘记了我的账号或者密码，或者我压根就不知道账号和密码，还有没有办法登录呢？答案是肯定的，那就是 【万能账号密码】万能账号密码是指 【万能账号】和 【万能密码】，顾名思义，就是可以 【登录任意网站】的账号和密码￼1、万能账号当我们不知道用户的账号并且不知道用户的密码时，可以使用万能账号。万能账号并不是一个真正意义上的账号，它是一种【拥有不同变体的格式】需要注意的是，以下所有万能账号中的 a 可以是自定义的数字或字母，比如 1，2，3，b，c，d【数值型万能账号】a or true #a or 1 #a or 1=1 #a or true -- aa or 1 -- aa or 1=1 -- a【单引号字符型万能密码】a' or true #a' or 1 #a' or 1=1 #a' or true --aa' or 1 -- aa' or 1=1 -- a【双引号字符型万能密码】a" or true #a" or 1 #a" or 1=1 #a" or true -- aa" or 1 -- aa" or 1=1 -- a1.1 万能账号的使用账号输入 【万能账号】， 比如 a or true #密码随便输入，比如 1234562、万能密码当我们知道用户的账号，但不知道用户的密码时，可以使用万能密码万能密码并不是一个真正意义上的密码，而是一个【拥有不同变体的格式】需要注意的是：万能密码中的 admin，必须是真实的用户名【数值型万能密码】admin #admin -- a【单引号字符串型万能密码】admin' #admin' -- a【双引号字符串型万能密码】admin” #admin" -- a2.1 万能密码的使用用户名输入 【万能密码】，比如 admin’ #密码随便输入，比如 123456二、万能账号密码原理剖析一个网络安全意识淡薄的网站，其登录功能的SQL语句大概是下面这个样子select from user where username='user' and password='pass' 11、万能账号原理当我们在登录界面输入 【万能账号】比如 a’ or true # 以后，后端会将我们输入的参数拼接到SQL中，然后去数据库中查询账号和密码，SQL语句大概是下面这样select from user where username='a' or true #' and password='pass' 1由于 # 在SQL中是注释符，注释符后面的内容不起作用，所以真正执行的SQL语句大概是下面这样select from user where username='a' or true 1or true 会使SQL语句恒成立，从而查询出数据库中的所有账号和密码，从而使我们成功登录1.1 注释符除了 # 以外， -- 也是SQL中的注释符，但SQL的语法格式规定--和后面的注释内容必须间隔一个空格，所以这需要使用 a' or 1 -- a 而不是 a' or 1 -- 其原理和 a’ or 1 # 大同小异，拼接到SQL中大概是下面这样select from user where username='a' or true -- a' and password='pass' 1注释后面的内容不生效，真正执行的SQL大概是下面这样select from user where username='a' or true 1SQL语句恒成立，从而登录成功换句话来说，a' or true -- a经过SQL的转化后，结果等价于 a’ or true #1.2 比较运算符SQL中规定，非布尔类型的数据参与比较运算时，会转化为布尔类型再参与运算。比如 or 1 或者 or 1=1 ，会转化为布尔类型的 true 再参与 or 的比较运算，也就是变成 or true ，同样能使条件恒成立，从而登录成功简单来讲就是：a' or 1 # 或者 a' or 1=1 # 等价于 a' or true #2、万能密码原理当我们在登录界面输入 【万能密码】 比如 admin’ # 以后，后端会将我们输入的参数拼接到SQL中，大概是下面这样select from user where username='admin' #' and password='pass' 1由于 # 在SQL中是注释符，注释符后面的内容不起作用，所以真正执行的SQL大概是下面这样select * from user where username='admin' 1SQL只会在数据库中查询用户名，而不是同时查询用户名和密码，这就意味着，只要用户名正确，就可以登录成功2.1 注释符除了 # ，-- 在SQL中也是注释符，SQL的语法格式规定 --后面必须使用空格来间隔后面的注释内容，所以需要将 --写作 -- a,后面的 a 可以是任意数字或者字母简单来件就是 admin’ – a 等价于 admin‘ #总结【万能账号密码】适用于网络安全意识较差的网站，如果发现了可以使用的网站，切勿进行下一步操作，动歪脑筋的同学请在心理默念三遍：【我国有一部完整且历史悠久的法律】
带你解惑大厂必会使用的stream流、方法引用
带你解惑大厂必会使用的 Stream流、方法引用🔥## 前言：带你解惑大厂必会使用的 Lambda表达式、函数式接口带你解惑大厂必会使用的 Stream流、方法引用若对你和身边的朋友有帮助, 点赞! 点赞! 点赞! 评论！收藏! 分享给更多的朋友共同学习交流第三章 Stream流在Java 8中，得益于Lambda所带来的函数式编程，引入了一个全新的Stream概念，用于解决已有集合类库既有的弊端。3.1 引言传统集合的多步遍历代码几乎所有的集合（如Collection接口或Map接口等）都支持直接或间接的遍历操作。而当我们需要对集合中的元素进行操作的时候，除了必需的添加、删除、获取外，最典型的就是集合遍历。例如：public class Demo10ForEach { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("张无忌"); list.add("周芷若"); list.add("赵敏"); list.add("张强"); list.add("张三丰"); for (String name : list) { System.out.println(name); } } }这是一段非常简单的集合遍历操作：对集合中的每一个字符串都进行打印输出操作。循环遍历的弊端Java 8的Lambda让我们可以更加专注于做什么（What），而不是怎么做（How），这点此前已经结合内部类进行了对比说明。现在，我们仔细体会一下上例代码，可以发现：for循环的语法就是“怎么做”for循环的循环体才是“做什么”为什么使用循环？因为要进行遍历。但循环是遍历的唯一方式吗？遍历是指每一个元素逐一进行处理，而并不是从第一个到最后一个顺次处理的循环。前者是目的，后者是方式。试想一下，如果希望对集合中的元素进行筛选过滤：将集合A根据条件一过滤为子集B；然后再根据条件二过滤为子集C。那怎么办？在Java 8之前的做法可能为：这段代码中含有三个循环，每一个作用不同：首先筛选所有姓张的人；然后筛选名字有三个字的人；最后进行对结果进行打印输出。public class Demo11NormalFilter { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("张无忌"); list.add("周芷若"); list.add("赵敏"); list.add("张强"); list.add("张三丰"); List<String> zhangList = new ArrayList<>(); for (String name : list) { if (name.startsWith("张")) { zhangList.add(name); } } List<String> shortList = new ArrayList<>(); for (String name : zhangList) { if (name.length() == 3) { shortList.add(name); } } for (String name : shortList) { System.out.println(name); } } }每当我们需要对集合中的元素进行操作的时候，总是需要进行循环、循环、再循环。这是理所当然的么？不是。循环是做事情的方式，而不是目的。另一方面，使用线性循环就意味着只能遍历一次。如果希望再次遍历，只能再使用另一个循环从头开始。那，Lambda的衍生物Stream能给我们带来怎样更加优雅的写法呢？Stream的更优写法下面来看一下借助Java 8的Stream API，什么才叫优雅：public class Demo12StreamFilter { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("张无忌"); list.add("周芷若"); list.add("赵敏"); list.add("张强"); list.add("张三丰"); list.stream() .filter(s -> s.startsWith("张")) .filter(s -> s.length() == 3) .forEach(s -> System.out.println(s)); } }直接阅读代码的字面意思即可完美展示无关逻辑方式的语义：获取流、过滤姓张、过滤长度为3、逐一打印。代码中并没有体现使用线性循环或是其他任何算法进行遍历，我们真正要做的事情内容被更好地体现在代码中。3.2 流式思想概述注意：请暂时忘记对传统IO流的固有印象！整体来看，流式思想类似于工厂车间的“生产流水线”。当需要对多个元素进行操作（特别是多步操作）的时候，考虑到性能及便利性，我们应该首先拼好一个“模型”步骤方案，然后再按照方案去执行它。这张图中展示了过滤、映射、跳过、计数等多步操作，这是一种集合元素的处理方案，而方案就是一种“函数模型”。图中的每一个方框都是一个“流”，调用指定的方法，可以从一个流模型转换为另一个流模型。而最右侧的数字3是最终结果。这里的filter、map、skip都是在对函数模型进行操作，集合元素并没有真正被处理。只有当终结方法count执行的时候，整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性。备注：“Stream流”其实是一个集合元素的函数模型，它并不是集合，也不是数据结构，其本身并不存储任何元素（或其地址值）。3.3 获取流方式java.util.stream.Stream<T>是Java 8新加入的最常用的流接口。（这并不是一个函数式接口。）获取一个流非常简单，有以下几种常用的方式：所有的Collection集合都可以通过stream默认方法获取流；Stream接口的静态方法of可以获取数组对应的流。方式1 : 根据Collection获取流首先，java.util.Collection接口中加入了default方法stream用来获取流，所以其所有实现类均可获取流。import java.util.*; import java.util.stream.Stream; /* 获取Stream流的方式 1.Collection中 方法 Stream stream() 2.Stream接口 中静态方法 of(T...t) 向Stream中添加多个数据 */ public class Demo13GetStream { public static void main(String[] args) { List<String> list = new ArrayList<>(); // ... Stream<String> stream1 = list.stream(); Set<String> set = new HashSet<>(); // ... Stream<String> stream2 = set.stream(); } }方式2: 根据数组获取流如果使用的不是集合或映射而是数组，由于数组对象不可能添加默认方法，所以Stream接口中提供了静态方法of，使用很简单：import java.util.stream.Stream; public class Demo14GetStream { public static void main(String[] args) { String[] array = { "张无忌", "张翠山", "张三丰", "张一元" }; Stream<String> stream = Stream.of(array); } }备注：of方法的参数其实是一个可变参数，所以支持数组。3.4 常用方法流模型的操作很丰富，这里介绍一些常用的API。这些方法可以被分成两种：终结方法：返回值类型不再是Stream接口自身类型的方法，因此不再支持类似StringBuilder那样的链式调用。本小节中，终结方法包括count和forEach方法。非终结方法：返回值类型仍然是Stream接口自身类型的方法，因此支持链式调用。（除了终结方法外，其余方法均为非终结方法。）备注：本小节之外的更多方法，请自行参考API文档。forEach : 逐一处理虽然方法名字叫forEach，但是与for循环中的“for-each”昵称不同，该方法并不保证元素的逐一消费动作在流中是被有序执行的。void forEach(Consumer<? super T> action);该方法接收一个Consumer接口函数，会将每一个流元素交给该函数进行处理。例如：import java.util.stream.Stream; public class Demo15StreamForEach { public static void main(String[] args) { Stream<String> stream = Stream.of("大娃","二娃","三娃","四娃","五娃","六娃","七娃","爷爷","蛇精","蝎子精"); //Stream<String> stream = Stream.of("张无忌", "张三丰", "周芷若"); stream.forEach((String str)->{System.out.println(str);}); } }在这里，lambda表达式(String str)->{System.out.println(str);}就是一个Consumer函数式接口的示例。filter：过滤可以通过filter方法将一个流转换成另一个子集流。方法声明：Stream<T> filter(Predicate<? super T> predicate);该接口接收一个Predicate函数式接口参数（可以是一个Lambda）作为筛选条件。基本使用Stream流中的filter方法基本使用的代码如：public class Demo16StreamFilter { public static void main(String[] args) { Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若"); Stream<String> result = original.filter((String s) -> {return s.startsWith("张");}); } }在这里通过Lambda表达式来指定了筛选的条件：必须姓张。count：统计个数正如旧集合Collection当中的size方法一样，流提供count方法来数一数其中的元素个数：long count();该方法返回一个long值代表元素个数（不再像旧集合那样是int值）。基本使用：public class Demo17StreamCount { public static void main(String[] args) { Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若"); Stream<String> result = original.filter(s -> s.startsWith("张")); System.out.println(result.count()); // 2 } }limit：取用前几个limit方法可以对流进行截取，只取用前n个。方法签名：Stream<T> limit(long maxSize):获取Stream流对象中的前n个元素,返回一个新的Stream流对象参数是一个long型，如果集合当前长度大于参数则进行截取；否则不进行操作。基本使用：import java.util.stream.Stream; public class Demo18StreamLimit { public static void main(String[] args) { Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若"); Stream<String> result = original.limit(2); System.out.println(result.count()); // 2 } }skip：跳过前几个如果希望跳过前几个元素，可以使用skip方法获取一个截取之后的新流：Stream<T> skip(long n): 跳过Stream流对象中的前n个元素,返回一个新的Stream流对象如果流的当前长度大于n，则跳过前n个；否则将会得到一个长度为0的空流。基本使用：import java.util.stream.Stream; public class Demo19StreamSkip { public static void main(String[] args) { Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若"); Stream<String> result = original.skip(2); System.out.println(result.count()); // 1 } }concat：组合如果有两个流，希望合并成为一个流，那么可以使用Stream接口的静态方法concat：static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b): 把参数列表中的两个Stream流对象a和b,合并成一个新的Stream流对象备注：这是一个静态方法，与java.lang.String当中的concat方法是不同的。该方法的基本使用代码如：import java.util.stream.Stream; public class Demo20StreamConcat { public static void main(String[] args) { Stream<String> streamA = Stream.of("张无忌"); Stream<String> streamB = Stream.of("张翠山"); Stream<String> result = Stream.concat(streamA, streamB); } }3.5 Stream综合案例现在有两个ArrayList集合存储队伍当中的多个成员姓名，要求使用传统的for循环（或增强for循环）依次进行以下若干操作步骤：第一个队伍只要名字为3个字的成员姓名；第一个队伍筛选之后只要前3个人；第二个队伍只要姓张的成员姓名；第二个队伍筛选之后不要前2个人；将两个队伍合并为一个队伍；打印整个队伍的姓名信息。两个队伍（集合）的代码如下：public class Demo21ArrayListNames { public static void main(String[] args) { List<String> one = new ArrayList<>(); one.add("迪丽热巴"); one.add("宋远桥"); one.add("苏星河"); one.add("老子"); one.add("庄子"); one.add("孙子"); one.add("洪七公"); List<String> two = new ArrayList<>(); two.add("古力娜扎"); two.add("张无忌"); two.add("张三丰"); two.add("赵丽颖"); two.add("张二狗"); two.add("张天爱"); two.add("张三"); // .... } }传统方式使用for循环 , 示例代码:public class Demo22ArrayListNames { public static void main(String[] args) { List<String> one = new ArrayList<>(); // ... List<String> two = new ArrayList<>(); // ... // 第一个队伍只要名字为3个字的成员姓名； List<String> oneA = new ArrayList<>(); for (String name : one) { if (name.length() == 3) { oneA.add(name); } } // 第一个队伍筛选之后只要前3个人； List<String> oneB = new ArrayList<>(); for (int i = 0; i < 3; i++) { oneB.add(oneA.get(i)); } // 第二个队伍只要姓张的成员姓名； List<String> twoA = new ArrayList<>(); for (String name : two) { if (name.startsWith("张")) { twoA.add(name); } } // 第二个队伍筛选之后不要前2个人； List<String> twoB = new ArrayList<>(); for (int i = 2; i < twoA.size(); i++) { twoB.add(twoA.get(i)); } // 将两个队伍合并为一个队伍； List<String> totalNames = new ArrayList<>(); totalNames.addAll(oneB); totalNames.addAll(twoB); // 打印整个队伍的姓名信息。 for (String name : totalNames) { System.out.println(name); } } }运行结果为：宋远桥 苏星河 洪七公 张二狗 张天爱 张三Stream方式等效的Stream流式处理代码为：public class Demo23StreamNames { public static void main(String[] args) { List<String> one = new ArrayList<>(); // ... List<String> two = new ArrayList<>(); // ... // 第一个队伍只要名字为3个字的成员姓名； // 第一个队伍筛选之后只要前3个人； Stream<String> streamOne = one.stream().filter(s -> s.length() == 3).limit(3); // 第二个队伍只要姓张的成员姓名； // 第二个队伍筛选之后不要前2个人； Stream<String> streamTwo = two.stream().filter(s -> s.startsWith("张")).skip(2); // 将两个队伍合并为一个队伍； // 根据姓名创建Person对象； // 打印整个队伍的Person对象信息。 Stream.concat(streamOne, streamTwo).forEach(s->System.out.println(s)); } }运行效果完全一样：宋远桥 苏星河 洪七公 张二狗 张天爱 张三3.6 函数拼接与终结方法在上述介绍的各种方法中，凡是返回值仍然为Stream接口的为函数拼接方法，它们支持链式调用；而返回值不再为Stream接口的为终结方法，不再支持链式调用。如下表所示：| 方法名 | 方法作用 | 方法种类 | 是否支持链式调用 || - | - | - | - || count | 统计个数 | 终结 | 否 || forEach | 逐一处理 | 终结 | 否 || filter | 过滤 | 函数拼接 | 是 || limit | 取用前几个 | 函数拼接 | 是 || skip | 跳过前几个 | 函数拼接 | 是 || concat | 组合 | 函数拼接 | 是 |第四章 方法引用4.1 概述和方法引用符来看一个简单的函数式接口以应用Lambda表达式 , 在accept方法中接收字符串 , 目的就是为了打印显示字符串 , 那么通过Lambda来使用它的代码很简单：public class DemoPrintSimple { private static void printString(Consumer<String> data, String str) { data.accept(str); } public static void main(String[] args) { printString(s -> System.out.println(s), "Hello World"); } }由于lambda表达式中,调用了已经实现的println方法 ,可以使用方法引用替代lambda表达式.符号表示 : ::符号说明 : 双冒号为方法引用运算符，而它所在的表达式被称为方法引用。应用场景 : 如果Lambda要表达的函数方案 , 已经存在于某个方法的实现中，那么则可以使用方法引用。如上例中，System.out对象中有个println(String)方法 , 恰好就是我们所需要的 , 那么对于Consumer接口作为参数，对比下面两种写法，完全等效：Lambda表达式写法：s -> System.out.println(s); 拿到参数之后经Lambda之手，继而传递给System.out.println方法去处理。方法引用写法：System.out::println 直接让System.out中的println方法来取代Lambda。推导与省略 : 如果使用Lambda，那么根据“可推导就是可省略”的原则，无需指定参数类型，也无需指定的重载形式——它们都将被自动推导。而如果使用方法引用，也是同样可以根据上下文进行推导。函数式接口是Lambda的基础，而方法引用是Lambda的简化形式。4.2 方法引用简化只要“引用”过去就好了：public class DemoPrintRef { private static void printString(Consumer<String> data, String str) { data.accept(str); } public static void main(String[] args) { printString(System.out::println, "HelloWorld"); } }请注意其中的双冒号::写法，这被称为“方法引用”，而双冒号是一种新的语法。4.3 扩展的引用方式对象名--引用成员方法这是最常见的一种用法，与上例相同。如果一个类中已经存在了一个成员方法，则可以通过对象名引用成员方法，代码为：public class DemoMethodRef { public static void main(String[] args) { String str = "hello"; printUP(str::toUpperCase); } public static void printUP(Supplier< String> sup ){ String apply =sup.get(); System.out.println(apply); } }类名--引用静态方法由于在java.lang.Math类中已经存在了静态方法random，所以当我们需要通过Lambda来调用该方法时,可以使用方法引用 , 写法是：public class DemoMethodRef { public static void main(String[] args) { printRanNum(Math::random); } public static void printRanNum(Supplier<Double> sup ){ Double apply =sup.get(); System.out.println(apply); } }在这个例子中，下面两种写法是等效的：Lambda表达式：n -> Math.abs(n)方法引用：Math::abs类--构造引用由于构造器的名称与类名完全一样，并不固定。所以构造器引用使用类名称::new的格式表示。首先是一个简单的Person类：public class Person { private String name; public Person(String name) { this.name = name; } public String getName() { return name; } }要使用这个函数式接口，可以通过方法引用传递：public class Demo09Lambda { public static void main(String[] args) { String name = "tom"; Person person = createPerson(Person::new, name); System.out.println(person); } public static Person createPerson(Function<String, Person> fun , String name){ Person p = fun.apply(name); return p; } }在这个例子中，下面两种写法是等效的：Lambda表达式：name -> new Person(name)方法引用：Person::new数组--构造引用数组也是Object的子类对象，所以同样具有构造器，只是语法稍有不同。如果对应到Lambda的使用场景中时，需要一个函数式接口：在应用该接口的时候，可以通过方法引用传递：public class Demo11ArrayInitRef { public static void main(String[] args) { int[] array = createArray(int[]::new, 3); System.out.println(array.length); } public static int[] createArray(Function<Integer , int[]> fun , int n){ int[] p = fun.apply(n); return p; } }在这个例子中，下面两种写法是等效的：Lambda表达式：length -> new int[length]方法引用：int[]::new注意 : 方法引用是对Lambda表达式符合特定情况下的一种缩写，它使得我们的Lambda表达式更加的精简，也可以理解为Lambda表达式的缩写形式 , 同学们可以尝试着 , 将之前使用lambda的地方 , 改写成方法引用的形式 ,不过要注意的是方法引用只能"引用"已经存在的方法!带你解惑大厂必会使用的 Stream流、方法引用🔥前言：应广大读者的需要，霈哥给大家带来新一期的干货啦！带你解惑大厂必会使用的 Lambda表达式、函数式接口带你解惑大厂必会使用的 Stream流、方法引用若对你和身边的朋友有帮助, 抓紧关注 IT霈哥 点赞! 点赞! 点赞! 评论！收藏! 分享给更多的朋友共同学习交流, 每天持续掘金离不开你的点赞支持!第三章 Stream流在Java 8中，得益于Lambda所带来的函数式编程，引入了一个全新的Stream概念，用于解决已有集合类库既有的弊端。3.1 引言传统集合的多步遍历代码几乎所有的集合（如Collection接口或Map接口等）都支持直接或间接的遍历操作。而当我们需要对集合中的元素进行操作的时候，除了必需的添加、删除、获取外，最典型的就是集合遍历。例如：public class Demo10ForEach {public static void main(String[] args) {List list = new ArrayList<>();list.add("张无忌");list.add("周芷若");list.add("赵敏");list.add("张强");list.add("张三丰");for (String name : list) {System.out.println(name);}}}这是一段非常简单的集合遍历操作：对集合中的每一个字符串都进行打印输出操作。循环遍历的弊端Java 8的Lambda让我们可以更加专注于做什么（What），而不是怎么做（How），这点此前已经结合内部类进行了对比说明。现在，我们仔细体会一下上例代码，可以发现：for循环的语法就是“怎么做”for循环的循环体才是“做什么”为什么使用循环？因为要进行遍历。但循环是遍历的唯一方式吗？遍历是指每一个元素逐一进行处理，而并不是从第一个到最后一个顺次处理的循环。前者是目的，后者是方式。试想一下，如果希望对集合中的元素进行筛选过滤：将集合A根据条件一过滤为子集B；然后再根据条件二过滤为子集C。那怎么办？在Java 8之前的做法可能为：这段代码中含有三个循环，每一个作用不同：首先筛选所有姓张的人；然后筛选名字有三个字的人；最后进行对结果进行打印输出。public class Demo11NormalFilter {public static void main(String[] args) {List list = new ArrayList<>();list.add("张无忌");list.add("周芷若");list.add("赵敏");list.add("张强");list.add("张三丰");List<String> zhangList = new ArrayList<>(); for (String name : list) { if (name.startsWith("张")) { zhangList.add(name); } } List<String> shortList = new ArrayList<>(); for (String name : zhangList) { if (name.length() == 3) { shortList.add(name); } } for (String name : shortList) { System.out.println(name); } }}每当我们需要对集合中的元素进行操作的时候，总是需要进行循环、循环、再循环。这是理所当然的么？不是。循环是做事情的方式，而不是目的。另一方面，使用线性循环就意味着只能遍历一次。如果希望再次遍历，只能再使用另一个循环从头开始。那，Lambda的衍生物Stream能给我们带来怎样更加优雅的写法呢？Stream的更优写法下面来看一下借助Java 8的Stream API，什么才叫优雅：public class Demo12StreamFilter {public static void main(String[] args) {List list = new ArrayList<>();list.add("张无忌");list.add("周芷若");list.add("赵敏");list.add("张强");list.add("张三丰");list.stream() .filter(s -> s.startsWith("张")) .filter(s -> s.length() == 3) .forEach(s -> System.out.println(s)); }}直接阅读代码的字面意思即可完美展示无关逻辑方式的语义：获取流、过滤姓张、过滤长度为3、逐一打印。代码中并没有体现使用线性循环或是其他任何算法进行遍历，我们真正要做的事情内容被更好地体现在代码中。3.2 流式思想概述注意：请暂时忘记对传统IO流的固有印象！整体来看，流式思想类似于工厂车间的“生产流水线”。当需要对多个元素进行操作（特别是多步操作）的时候，考虑到性能及便利性，我们应该首先拼好一个“模型”步骤方案，然后再按照方案去执行它。这张图中展示了过滤、映射、跳过、计数等多步操作，这是一种集合元素的处理方案，而方案就是一种“函数模型”。图中的每一个方框都是一个“流”，调用指定的方法，可以从一个流模型转换为另一个流模型。而最右侧的数字3是最终结果。这里的filter、map、skip都是在对函数模型进行操作，集合元素并没有真正被处理。只有当终结方法count执行的时候，整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性。备注：“Stream流”其实是一个集合元素的函数模型，它并不是集合，也不是数据结构，其本身并不存储任何元素（或其地址值）。3.3 获取流方式java.util.stream.Stream是Java 8新加入的最常用的流接口。（这并不是一个函数式接口。）获取一个流非常简单，有以下几种常用的方式：所有的Collection集合都可以通过stream默认方法获取流；Stream接口的静态方法of可以获取数组对应的流。方式1 : 根据Collection获取流首先，java.util.Collection接口中加入了default方法stream用来获取流，所以其所有实现类均可获取流。import java.util.*;import java.util.stream.Stream;/*获取Stream流的方式1.Collection中 方法 Stream stream() 2.Stream接口 中静态方法 of(T...t) 向Stream中添加多个数据*/public class Demo13GetStream {public static void main(String[] args) {List list = new ArrayList<>();// ...Stream stream1 = list.stream();Set<String> set = new HashSet<>(); // ... Stream<String> stream2 = set.stream(); }}方式2: 根据数组获取流如果使用的不是集合或映射而是数组，由于数组对象不可能添加默认方法，所以Stream接口中提供了静态方法of，使用很简单：import java.util.stream.Stream;public class Demo14GetStream {public static void main(String[] args) {String[] array = { "张无忌", "张翠山", "张三丰", "张一元" };Stream stream = Stream.of(array);}}备注：of方法的参数其实是一个可变参数，所以支持数组。3.4 常用方法流模型的操作很丰富，这里介绍一些常用的API。这些方法可以被分成两种：终结方法：返回值类型不再是Stream接口自身类型的方法，因此不再支持类似StringBuilder那样的链式调用。本小节中，终结方法包括count和forEach方法。非终结方法：返回值类型仍然是Stream接口自身类型的方法，因此支持链式调用。（除了终结方法外，其余方法均为非终结方法。）备注：本小节之外的更多方法，请自行参考API文档。forEach : 逐一处理虽然方法名字叫forEach，但是与for循环中的“for-each”昵称不同，该方法并不保证元素的逐一消费动作在流中是被有序执行的。void forEach(Consumer<? super T> action);该方法接收一个Consumer接口函数，会将每一个流元素交给该函数进行处理。例如：import java.util.stream.Stream;public class Demo15StreamForEach {public static void main(String[] args) {Stream stream = Stream.of("大娃","二娃","三娃","四娃","五娃","六娃","七娃","爷爷","蛇精","蝎子精");//Stream stream = Stream.of("张无忌", "张三丰", "周芷若");stream.forEach((String str)->{System.out.println(str);});}}在这里，lambda表达式(String str)->{System.out.println(str);}就是一个Consumer函数式接口的示例。filter：过滤可以通过filter方法将一个流转换成另一个子集流。方法声明：Stream filter(Predicate<? super T> predicate);该接口接收一个Predicate函数式接口参数（可以是一个Lambda）作为筛选条件。基本使用Stream流中的filter方法基本使用的代码如：public class Demo16StreamFilter {public static void main(String[] args) {Stream original = Stream.of("张无忌", "张三丰", "周芷若");Stream result = original.filter((String s) -> {return s.startsWith("张");});}}在这里通过Lambda表达式来指定了筛选的条件：必须姓张。count：统计个数正如旧集合Collection当中的size方法一样，流提供count方法来数一数其中的元素个数：long count();该方法返回一个long值代表元素个数（不再像旧集合那样是int值）。基本使用：public class Demo17StreamCount {public static void main(String[] args) {Stream original = Stream.of("张无忌", "张三丰", "周芷若");Stream result = original.filter(s -> s.startsWith("张"));System.out.println(result.count()); // 2}}limit：取用前几个limit方法可以对流进行截取，只取用前n个。方法签名：Stream limit(long maxSize):获取Stream流对象中的前n个元素,返回一个新的Stream流对象参数是一个long型，如果集合当前长度大于参数则进行截取；否则不进行操作。基本使用：import java.util.stream.Stream;public class Demo18StreamLimit {public static void main(String[] args) {Stream original = Stream.of("张无忌", "张三丰", "周芷若");Stream result = original.limit(2);System.out.println(result.count()); // 2}}skip：跳过前几个如果希望跳过前几个元素，可以使用skip方法获取一个截取之后的新流：Stream skip(long n): 跳过Stream流对象中的前n个元素,返回一个新的Stream流对象如果流的当前长度大于n，则跳过前n个；否则将会得到一个长度为0的空流。基本使用：import java.util.stream.Stream;public class Demo19StreamSkip {public static void main(String[] args) {Stream original = Stream.of("张无忌", "张三丰", "周芷若");Stream result = original.skip(2);System.out.println(result.count()); // 1}}concat：组合如果有两个流，希望合并成为一个流，那么可以使用Stream接口的静态方法concat：static Stream concat(Stream<? extends T> a, Stream<? extends T> b): 把参数列表中的两个Stream流对象a和b,合并成一个新的Stream流对象备注：这是一个静态方法，与java.lang.String当中的concat方法是不同的。该方法的基本使用代码如：import java.util.stream.Stream;public class Demo20StreamConcat {public static void main(String[] args) {Stream streamA = Stream.of("张无忌");Stream streamB = Stream.of("张翠山");Stream result = Stream.concat(streamA, streamB);}}3.5 Stream综合案例现在有两个ArrayList集合存储队伍当中的多个成员姓名，要求使用传统的for循环（或增强for循环）依次进行以下若干操作步骤：第一个队伍只要名字为3个字的成员姓名；第一个队伍筛选之后只要前3个人；第二个队伍只要姓张的成员姓名；第二个队伍筛选之后不要前2个人；将两个队伍合并为一个队伍；打印整个队伍的姓名信息。两个队伍（集合）的代码如下：public class Demo21ArrayListNames {public static void main(String[] args) {List one = new ArrayList<>();one.add("迪丽热巴");one.add("宋远桥");one.add("苏星河");one.add("老子");one.add("庄子");one.add("孙子");one.add("洪七公");List<String> two = new ArrayList<>(); two.add("古力娜扎"); two.add("张无忌"); two.add("张三丰"); two.add("赵丽颖"); two.add("张二狗"); two.add("张天爱"); two.add("张三"); // .... }}传统方式使用for循环 , 示例代码:public class Demo22ArrayListNames {public static void main(String[] args) {List one = new ArrayList<>();// ...List<String> two = new ArrayList<>(); // ... // 第一个队伍只要名字为3个字的成员姓名； List<String> oneA = new ArrayList<>(); for (String name : one) { if (name.length() == 3) { oneA.add(name); } } // 第一个队伍筛选之后只要前3个人； List<String> oneB = new ArrayList<>(); for (int i = 0; i < 3; i++) { oneB.add(oneA.get(i)); } // 第二个队伍只要姓张的成员姓名； List<String> twoA = new ArrayList<>(); for (String name : two) { if (name.startsWith("张")) { twoA.add(name); } } // 第二个队伍筛选之后不要前2个人； List<String> twoB = new ArrayList<>(); for (int i = 2; i < twoA.size(); i++) { twoB.add(twoA.get(i)); } // 将两个队伍合并为一个队伍； List<String> totalNames = new ArrayList<>(); totalNames.addAll(oneB); totalNames.addAll(twoB); // 打印整个队伍的姓名信息。 for (String name : totalNames) { System.out.println(name); } }}运行结果为：宋远桥苏星河洪七公张二狗张天爱张三Stream方式等效的Stream流式处理代码为：public class Demo23StreamNames {public static void main(String[] args) {List one = new ArrayList<>();// ...List<String> two = new ArrayList<>(); // ... // 第一个队伍只要名字为3个字的成员姓名； // 第一个队伍筛选之后只要前3个人； Stream<String> streamOne = one.stream().filter(s -> s.length() == 3).limit(3); // 第二个队伍只要姓张的成员姓名； // 第二个队伍筛选之后不要前2个人； Stream<String> streamTwo = two.stream().filter(s -> s.startsWith("张")).skip(2); // 将两个队伍合并为一个队伍； // 根据姓名创建Person对象； // 打印整个队伍的Person对象信息。 Stream.concat(streamOne, streamTwo).forEach(s->System.out.println(s)); }}运行效果完全一样：宋远桥苏星河洪七公张二狗张天爱张三3.6 函数拼接与终结方法在上述介绍的各种方法中，凡是返回值仍然为Stream接口的为函数拼接方法，它们支持链式调用；而返回值不再为Stream接口的为终结方法，不再支持链式调用。如下表所示：方法名方法作用方法种类是否支持链式调用count统计个数终结否forEach逐一处理终结否filter过滤函数拼接是limit取用前几个函数拼接是skip跳过前几个函数拼接是concat组合函数拼接是第四章 方法引用4.1 概述和方法引用符来看一个简单的函数式接口以应用Lambda表达式 , 在accept方法中接收字符串 , 目的就是为了打印显示字符串 , 那么通过Lambda来使用它的代码很简单：public class DemoPrintSimple {private static void printString(Consumer data, String str) {data.accept(str);}public static void main(String[] args) {printString(s -> System.out.println(s), "Hello World");}}由于lambda表达式中,调用了已经实现的println方法 ,可以使用方法引用替代lambda表达式.符号表示 : ::符号说明 : 双冒号为方法引用运算符，而它所在的表达式被称为方法引用。应用场景 : 如果Lambda要表达的函数方案 , 已经存在于某个方法的实现中，那么则可以使用方法引用。如上例中，System.out对象中有个println(String)方法 , 恰好就是我们所需要的 , 那么对于Consumer接口作为参数，对比下面两种写法，完全等效：Lambda表达式写法：s -> System.out.println(s); 拿到参数之后经Lambda之手，继而传递给System.out.println方法去处理。方法引用写法：System.out::println 直接让System.out中的println方法来取代Lambda。推导与省略 : 如果使用Lambda，那么根据“可推导就是可省略”的原则，无需指定参数类型，也无需指定的重载形式——它们都将被自动推导。而如果使用方法引用，也是同样可以根据上下文进行推导。函数式接口是Lambda的基础，而方法引用是Lambda的简化形式。4.2 方法引用简化只要“引用”过去就好了：public class DemoPrintRef {private static void printString(Consumer data, String str) {data.accept(str);}public static void main(String[] args) {printString(System.out::println, "HelloWorld");}}请注意其中的双冒号::写法，这被称为“方法引用”，而双冒号是一种新的语法。4.3 扩展的引用方式对象名--引用成员方法这是最常见的一种用法，与上例相同。如果一个类中已经存在了一个成员方法，则可以通过对象名引用成员方法，代码为：public class DemoMethodRef {public static void main(String[] args) {String str = "hello";printUP(str::toUpperCase);}public static void printUP(Supplier< String> sup ){ String apply =sup.get(); System.out.println(apply); }}类名--引用静态方法由于在java.lang.Math类中已经存在了静态方法random，所以当我们需要通过Lambda来调用该方法时,可以使用方法引用 , 写法是：public class DemoMethodRef {public static void main(String[] args) {printRanNum(Math::random);}public static void printRanNum(Supplier<Double> sup ){ Double apply =sup.get(); System.out.println(apply); }}在这个例子中，下面两种写法是等效的：Lambda表达式：n -> Math.abs(n)方法引用：Math::abs类--构造引用由于构造器的名称与类名完全一样，并不固定。所以构造器引用使用类名称::new的格式表示。首先是一个简单的Person类：public class Person {private String name;public Person(String name) {this.name = name;}public String getName() {return name;}}要使用这个函数式接口，可以通过方法引用传递：public class Demo09Lambda {public static void main(String[] args) {String name = "tom";Person person = createPerson(Person::new, name);System.out.println(person);} public static Person createPerson(Function<String, Person> fun , String name){ Person p = fun.apply(name); return p; }}在这个例子中，下面两种写法是等效的：Lambda表达式：name -> new Person(name)方法引用：Person::new数组--构造引用数组也是Object的子类对象，所以同样具有构造器，只是语法稍有不同。如果对应到Lambda的使用场景中时，需要一个函数式接口：在应用该接口的时候，可以通过方法引用传递：public class Demo11ArrayInitRef {public static void main(String[] args) {int[] array = createArray(int[]::new, 3); System.out.println(array.length); } public static int[] createArray(Function<Integer , int[]> fun , int n){ int[] p = fun.apply(n); return p; }}在这个例子中，下面两种写法是等效的：Lambda表达式：length -> new int[length]方法引用：int[]::new注意 : 方法引用是对Lambda表达式符合特定情况下的一种缩写，它使得我们的Lambda表达式更加的精简，也可以理解为Lambda表达式的缩写形式 , 同学们可以尝试着 , 将之前使用lambda的地方 , 改写成方法引用的形式 ,不过要注意的是方法引用只能"引用"已经存在的方法!
Java之Lambda表达式的学习1、背景好吧，其实导师昨天说我昨个写的这个代码还可以简化下（第一版代码请看上一篇学习stream流的博客），可以不用new Comparetor对象（匿名内部类）去写，当时我还没想到应该怎么样去改，今早来上班，开启电脑打开IDEA定位到昨天写的这个地方，发现昨天写的那个比较方法可以简化为利用Integer类自带的compare函数不是更好嘛，反正是提取俩对象中字符串的长度进行对比，利用Integer库自带的compare函数去比较就可以免去自己写三段式判断，代码顿时简化了不少呢。第一次改完后是这样的int labelType = blockList.stream().max(new Comparator<JSONObject>() { @Override public int compare(JSONObject o1, JSONObject o2) { return Integer.compare(o1.getStr("paramet_text").length(),o2.getStr("paramet_text").length()) } }).get().getStr("paramet_text").length();然后呢觉得这块代码是不是还可以进行优化，去除匿名内部类，于是乎脑子里一下窜出了一个想法--“JDK8之后不是出了个Lambda表达式嘛，利用Lambda表达式代替匿名内部类来简化代码不是挺好的嘛”，说干咱就开干！第二次改完代码后是这样的int labelType = blockList.stream().max( (o1, o2) -> Integer.compare(o1.getStr("paramet_text").length(),o2.getStr("paramet_text").length()) ) .get() .getStr("paramet_text").length();最后idea提示我还能继续修改，最终版代码：int labelType = blockList.stream().max( Comparator.comparingInt(o -> o.getStr("paramet_text").length()) ) .get() .getStr("paramet_text").length();最后这版代码是利用Comparator接口中直接比较int值得办法去完成此项功能的。改完后进行测试，返回结果和原来一样，好了，至此也就大功告成了！2、Lambda表达式介绍好了，先说完使用背景下面正式开始对Lambda表达式进行学习、记录。Lambda 表达式也可称为闭包，它是 JDK8 的一个新特性，可以取代大部分的匿名内部类，写出更优雅的 Java 代码，尤其在集合的遍历和其他集合操作中，Lambda 允许把函数作为一个方法的参数（函数作为参数传递进方法中），可以极大地优化代码结构。2.1 语法lambda 表达式的语法格式如下：(parameters) -> expression 或 (parameters) ->{ statements; }语法形式为 () -> {}，其中 () 用来描述参数列表，{} 用来描述方法体，-> 为 lambda运算符 ，读作(goes to)。以下是lambda表达式的重要特征:可选类型声明：不需要声明参数类型，编译器可以统一识别参数值。可选的参数圆括号：一个参数无需定义圆括号，但多个参数需要定义圆括号。可选的大括号：如果主体包含了一个语句，就不需要使用大括号。可选的返回关键字：如果主体只有一个表达式返回值则编译器会自动返回值，大括号需要指定明表达式返回了一个数值。2.2 简单用法/** =================== 先上接口代码 =================== */ /**多参数无返回*/ @FunctionalInterface public interface NoReturnMultiParam { void method(int a, int b); } /**无参无返回值*/ @FunctionalInterface public interface NoReturnNoParam { void method(); } /**一个参数无返回*/ @FunctionalInterface public interface NoReturnOneParam { void method(int a); } /**多个参数有返回值*/ @FunctionalInterface public interface ReturnMultiParam { int method(int a, int b); } /*** 无参有返回*/ @FunctionalInterface public interface ReturnNoParam { int method(); } /**一个参数有返回值*/ @FunctionalInterface public interface ReturnOneParam { int method(int a); } /** =================== 接口代码end =================== */ /** =================== 简单测试start =================== */ import lambda.interfaces.*; public class Test2 { public static void main(String[] args) { //1.简化参数类型，可以不写参数类型，但是必须所有参数都不写 NoReturnMultiParam lamdba1 = (a, b) -> { System.out.println("简化参数类型"); }; lamdba1.method(1, 2); //2.简化参数小括号，如果只有一个参数则可以省略参数小括号 NoReturnOneParam lambda2 = a -> { System.out.println("简化参数小括号"); }; lambda2.method(1); //3.简化方法体大括号，如果方法条只有一条语句，则可以胜率方法体大括号 NoReturnNoParam lambda3 = () -> System.out.println("简化方法体大括号"); lambda3.method(); //4.如果方法体只有一条语句，并且是 return 语句，则可以省略方法体大括号 ReturnOneParam lambda4 = a -> a+3; System.out.println(lambda4.method(5)); ReturnMultiParam lambda5 = (a, b) -> a+b; System.out.println(lambda5.method(1, 1)); } } /** =================== 简单测试end =================== */2.3 Lambda 表达式常用示例lambda 表达式引用方法有时候我们不是必须要自己重写某个匿名内部类的方法，我们可以可以利用 lambda表达式的接口快速指向一个已经被实现的方法。语法方法归属者::方法名 静态方法的归属者为类名，普通方法归属者为对象public class Exe1 { public static void main(String[] args) { ReturnOneParam lambda1 = a -> doubleNum(a); System.out.println(lambda1.method(3)); //lambda2 引用了已经实现的 doubleNum 方法 ReturnOneParam lambda2 = Exe1::doubleNum; System.out.println(lambda2.method(3)); Exe1 exe = new Exe1(); //lambda4 引用了已经实现的 addTwo 方法 ReturnOneParam lambda4 = exe::addTwo; System.out.println(lambda4.method(2)); } /** * 要求 * 1.参数数量和类型要与接口中定义的一致 * 2.返回值类型要与接口中定义的一致 */ public static int doubleNum(int a) { return a * 2; } public int addTwo(int a) { return a + 2; } }构造方法的引用一般我们需要声明接口，该接口作为对象的生成器，通过 类名::new 的方式来实例化对象，然后调用方法返回对象。interface ItemCreatorBlankConstruct { Item getItem(); } interface ItemCreatorParamContruct { Item getItem(int id, String name, double price); } public class Exe2 { public static void main(String[] args) { ItemCreatorBlankConstruct creator = () -> new Item(); Item item = creator.getItem(); ItemCreatorBlankConstruct creator2 = Item::new; Item item2 = creator2.getItem(); ItemCreatorParamContruct creator3 = Item::new; Item item3 = creator3.getItem(112, "鼠标", 135.99); } }lambda 表达式创建线程我们以往都是通过创建 Thread 对象，然后通过匿名内部类重写 run() 方法，一提到匿名内部类我们就应该想到可以使用 lambda 表达式来简化线程的创建过程。Thread t = new Thread(() -> { for (int i = 0; i < 10; i++) { System.out.println(2 + ":" + i); } }); t.start();遍历集合我们可以调用集合的 public void forEach(Consumer action) 方法，通过 lambda 表达式的方式遍历集合中的元素。以下是 Consumer 接口的方法以及遍历集合的操作。Consumer 接口是 jdk 为我们提供的一个函数式接口。@FunctionalInterface public interface Consumer<T> { void accept(T t); //.... } ArrayList<Integer> list = new ArrayList<>(); Collections.addAll(list, 1,2,3,4,5); //lambda表达式 方法引用 list.forEach(System.out::println); list.forEach(element -> { if (element % 2 == 0) { System.out.println(element); } });删除集合中的某个元素我们通过 public boolean removeIf(Predicate filter)方法来删除集合中的某个元素，Predicate 也是 jdk 为我们提供的一个函数式接口，可以简化程序的编写。ArrayList<Item> items = new ArrayList<>(); items.add(new Item(11, "小牙刷", 12.05 )); items.add(new Item(5, "日本马桶盖", 999.05 )); items.add(new Item(7, "格力空调", 888.88 )); items.add(new Item(17, "肥皂", 2.00 )); items.add(new Item(9, "冰箱", 4200.00 )); items.removeIf(ele -> ele.getId() == 7); //通过 foreach 遍历，查看是否已经删除 items.forEach(System.out::println);集合内元素的排序在以前我们若要为集合内的元素排序，就必须调用 sort 方法，传入比较器匿名内部类重写 compare 方法，我们现在可以使用 lambda 表达式来简化代码。ArrayList<Item> list = new ArrayList<>(); list.add(new Item(13, "背心", 7.80)); list.add(new Item(11, "半袖", 37.80)); list.add(new Item(14, "风衣", 139.80)); list.add(new Item(12, "秋裤", 55.33)); /* list.sort(new Comparator<Item>() { @Override public int compare(Item o1, Item o2) { return o1.getId() - o2.getId(); } }); */ list.sort((o1, o2) -> o1.getId() - o2.getId()); System.out.println(list);2.4 Lambda 表达式中的闭包问题这个问题我们在匿名内部类中也会存在，如果我们把注释放开会报错，告诉我 num 值是 final 不能被改变。这里我们虽然没有标识 num 类型为 final，但是在编译期间虚拟机会帮我们加上 final 修饰关键字。import java.util.function.Consumer; public class Main { public static void main(String[] args) { int num = 10; Consumer<String> consumer = ele -> { System.out.println(num); }; //num = num + 2; consumer.accept("hello"); } }参考链接：参考链接
Java之stream流的学习1、学习背景你还在为处理对象数组中的元素而困恼吗?传统写法在对数组中的对象进行操作时，首先需要遍历数组(写for循环)，然后再根据需要对对象中的数据进行相应的操作，你写出来的代码或许是这样的：class Student{ private int age; private String sex; private String name; //get set函数省略 //... ... } ... ... //其他代码省略 List<Student> studenNewtList = new ArrayList<Student>(); //1.筛选出年纪小于30的学生 for (Student student : studentList) { if (student.getAge() < 30) { studenNewtList.add(dish); } } //2.对筛选出的学生进行排序 Collections.sort(studenNewtList, new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { return Integer.compare(o1.getAge(), o2.getAge()); } }); //3.获取排序后菜肴的名字 List<String> lowStudentName = new ArrayList<>(); for (Student s : studenNewtList) { lowStudentName.add(s.getName()); } return lowStudentName而自从在java8以后，学习stream流操作后，代码有了极大的简化，使用更加快速便捷，不需要再去写繁琐的遍历代码，几行语句即可解决所遇到的问题。简化后的代码如下：return studentList.stream() .filter(s -> s.getAge() < 30) //筛选出年纪小于30的学生 .sorted(comparing(Student::getAge)) //根据学生年纪进行排序 .map(Student::getName) //提取符合条件的学生名字 .collect(Collectors.toList()); //转换为List看过上述代码后是不是感觉自己之前写的代码有多low了！没接触stream前我也是这样的，现在了解stream后再次写代码应该趋于简洁化，以提高工作效率！2、stream介绍流是从支持数据处理操作的源生成的元素序列，源可以是数组、文件、集合、函数。流不是集合元素，它不是数据结构并不保存数据，它的主要目的在于计算。2.1 创建流通过集合生成，应用中最常用的一种List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); Stream<Integer> stream = integerList.stream();通过数组生成int[] intArr = new int[]{1, 2, 3, 4, 5}; IntStream stream = Arrays.stream(intArr);通过Arrays.stream方法生成流，并且该方法生成的流是数值流【即IntStream】而不是Stream。补充一点使用数值流可以避免计算过程中拆箱装箱，提高性能。Stream API提供了mapToInt、mapToDouble、mapToLong三种方式将对象流【即Stream】转换成对应的数值流，同时提供了boxed方法将数值流转换为对象流通过值生成Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);通过Stream的of方法生成流，通过Stream的empty方法可以生成一个空流通过文件生成Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())通过Files.line方法得到一个流，并且得到的每个流是给定文件中的一行通过函数生成 提供了iterate和generate两个静态方法从函数中生成流iterateStream<Integer> stream = Stream.iterate(0, n -> n + 2).limit(5); iterate方法接受两个参数，第一个为初始化值，第二个为进行的函数操作，因为iterator生成的流为无限流，通过limit方法对流进行了截断，只生成5个偶数generateStream<Double> stream = Stream.generate(Math::random).limit(5); generate方法接受一个参数，方法参数类型为Supplier，由它为流提供值。generate生成的流也是无限流，因此通过limit对流进行了截断 ---2.2 使用流流的使用将分为中间操作和终端操作进行介绍2.2.1 中间操作一个流可以后面跟随零个或多个中间操作。其目的主要是打开流，做出某种程度的数据映射/过滤，然后返回一个新的流，交给下一个操作使用。这类操作都是惰性化的，仅仅调用到这类方法，并没有真正开始流的遍历，真正的遍历需等到终端操作时，常见的中间操作有下面即将介绍的filter、map等filter筛选filter中填筛选条件。List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5); Stream<Integer> stream = integerList.stream().filter(i -> i > 3);distinct去除重复元素直接使用，进行去重操作List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5); Stream<Integer> stream = integerList.stream().distinct();limit返回指定流个数通过limit方法指定返回流的个数，limit的参数值必须>=0，否则将会抛出异常List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5); Stream<Integer> stream = integerList.stream().limit(3);skip跳过流中的元素通过skip方法跳过流中的元素，上述例子跳过前两个元素，所以打印结果为2,3,4,5，skip的参数值必须>=0，否则将会抛出异常List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5); Stream<Integer> stream = integerList.stream().skip(2);map流映射通过map方法可以完成映射，该例子完成中String -> Integer的映射，之前上面的例子通过map方法完成了Dish->String的映射List<String> stringList = Arrays.asList("Java 8", "Lambdas", "In", "Action"); Stream<Integer> stream = stringList.stream().map(String::length);flatMap流转换map(w -> w.split(" "))的返回值为Stream<String[]>，我们想获取Stream，可以通过flatMap方法完成Stream ->Stream的转换。关注微信公众号：Java技术栈，在后台回复：新特性，可以获取我整理的 N 篇最新 Java 新特性教程，都是干货。List<String> wordList = Arrays.asList("Hello", "World"); List<String> strList = wordList.stream() .map(w -> w.split(" ")) .flatMap(Arrays::stream) .distinct() .collect(Collectors.toList());元素匹配allMatch匹配所有List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); if (integerList.stream().allMatch(i -> i > 3)) { System.out.println("值都大于3"); }anyMatch匹配其中一个List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); if (integerList.stream().anyMatch(i -> i > 3)) { System.out.println("存在大于3的值"); }noneMatch全部不匹配List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); if (integerList.stream().noneMatch(i -> i > 3)) { System.out.println("值都小于3"); }2.2.2 终端操作一个流有且只能有一个终端操作，当这个操作执行后，流就被关闭了，无法再被操作，因此一个流只能被遍历一次，若想在遍历需要通过源数据在生成流。终端操作的执行，才会真正开始流的遍历。如下面即将介绍的count、collect等统计流中元素个数countList<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); Long result = integerList.stream().count();countingList<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); Long result = integerList.stream().collect(counting()); 这一种统计元素个数的方法在与collect联合使用的时候特别有用查找findFirst查找第一个List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); Optional<Integer> result = integerList.stream().filter(i -> i > 3).findFirst();findAny随机查找一个List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); Optional<Integer> result = integerList.stream().filter(i -> i > 3).findAny(); 通过findAny方法查找到其中一个大于三的元素并打印，因为内部进行优化的原因，当找到第一个满足大于三的元素时就结束，该方法结果和findFirst方法结果一样。 提供findAny方法是为了更好的利用并行流，findFirst方法在并行上限制更多(不做记录 ~~其实是不会~~)reduce将流中的元素组合起来int sum = integerList.stream().reduce(0, (a, b) -> (a + b)); //或者 int sum = integerList.stream().reduce(0, Integer::sum);reduce接受两个参数，一个初始值这里是0，一个BinaryOperator accumulator来将两个元素结合起来产生一个新值，另外reduce方法还有一个没有初始化值的重载方法。获取流中最小最大值通过min/max获取最小最大值Optional<Integer> min = studentList.stream().map(Student::getAge).min(Integer::compareTo); Optional<Integer> max = studentList.stream().map(Student::getAge).max(Integer::compareTo); //或者 OptionalInt min = studentList.stream().mapToInt(Student::getAge).min(); OptionalInt max = studentList.stream().mapToInt(Student::getAge).max(); min获取流中最小值，max获取流中最大值，方法参数为Comparator<? super T> comparator通过minBy/maxBy获取最小最大值Optional<Integer> min = studentList.stream().map(Student::getAge).collect(minBy(Integer::compareTo)); Optional<Integer> max = studentList.stream().map(Student::getAge).collect(maxBy(Integer::compareTo)); minBy获取流中最小值，maxBy获取流中最大值，方法参数为Comparator<? super T> comparator通过reduce获取最小最大值Optional<Integer> min = studentList.stream().map(Student::getAge).reduce(Integer::min); Optional<Integer> max = studentList.stream().map(Student::getAge).reduce(Integer::max);求和通过summingIntint sum = studentList.stream().collect(summingInt(Student::getAge)); 如果数据类型为double、long，则通过summingDouble、summingLong方法进行求和通过reduceint sum = studentList.stream().map(Student::getAge).reduce(0, Integer::sum);通过sumint sum = studentList.stream().mapToInt(Student::getAge).sum(); 在上面求和、求最大值、最小值的时候，对于相同操作有不同的方法可以选择执行。 可以选择collect、reduce、min/max/sum方法，推荐使用min、max、sum方法。因为它最简洁易读，同时通过mapToInt将对象流转换为数值流，避免了装箱和拆箱操作通过averagingInt求平均值double average = studentList.stream().collect(averagingInt(Student::getAge));如果数据类型为double、long，则通过averagingDouble、averagingLong方法进行求平均通过summarizingInt同时求总和、平均值、最大值、最小值IntSummaryStatistics intSummaryStatistics = studentList.stream().collect(summarizingInt(Student::getAge)); double average = intSummaryStatistics.getAverage(); //获取平均值 int min = intSummaryStatistics.getMin(); //获取最小值 int max = intSummaryStatistics.getMax(); //获取最大值 long sum = intSummaryStatistics.getSum(); //获取总和如果数据类型为double、long，则通过summarizingDouble、summarizingLong方法通过foreach进行元素遍历List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); integerList.stream().forEach(System.out::println);返回集合List<String> strings = studentList.stream().map(Student::getName).collect(toList()); Set<String> sets = studentList.stream().map(Student::getName).collect(toSet());通过遍历和返回集合的使用发现流只是把原来的外部迭代放到了内部进行，这也是流的主要特点之一。内部迭代可以减少好多代码量。通过joining拼接流中的元素String result = studentList.stream().map(Student::getName).collect(Collectors.joining(", "));默认如果不通过map方法进行映射处理拼接的toString方法返回的字符串，joining的方法参数为元素的分界符，如果不指定生成的字符串将是一串的，可读性不强 - 进阶通过groupingBy进行分组 ```java Map<Type, List<Dish>> result = studentList.stream().collect(groupingBy(Student::getSex)); ``` 在collect方法中传入groupingBy进行分组，其中groupingBy的方法参数为分类函数。还可以通过嵌套使用groupingBy进行多级分类 3、总结哈哈，总算是写完了（copy完了） ，今天给的导师让我给数据加上前端标签样式，我想了想，之前好像看过一篇关于stream流的文章，当时看完觉得很是神奇，可惜一直没有机会去实践，这次总算是让我逮到机会了（就是想试用下学到的新技术） ，于是开始搜搜stream流的简单基本操作，查查资料为了避免忘记，写个blog记录下，方便下次用时快速找到相关文档（节省时间）//判断blockList数组中元素字典的paramet_text值的长度,获取其最大长度 int labelType = blockList.stream().max(new Comparator<JSONObject>() { @Override public int compare(JSONObject o1, JSONObject o2) { return o1.getStr("paramet_text").length() > o2.getStr("paramet_text").length() ? 1 : (o1.getStr("paramet_text").length() == o2.getStr("paramet_text").length() ? 0 : -1); } }).get().getStr("paramet_text").length();贴下今天写的代码，还被导师夸了下（O(∩_∩)O哈哈~） 这段代码应该是不涉密吧(ˇˍˇ) ～好了，一看时间又到了下班点了，收拾收拾划划水准备下班吧，今天写的代码大概也就20行左右吧，毕竟刚来，还算比较闲参考文章：链接
java计算后序表达式简介题目要求如下：用二叉树读取前缀表达式，打印该算式的后缀表达式，并求解代码实现TreeNode类：package com.zbl; /** * @Author {zbl} * @Date: 2019/12/09/ 12:15 * @Description */ public class TreeNode<T> { public T data; public TreeNode left; public TreeNode right; TreeNode(T data){ this.data = data; } } BiTree类：package com.zbl; import java.util.LinkedList; import java.util.Queue; /** * @Author {zbl} * @Date: 2019/12/09/ 11:54 * @Description */ public class BiTree<T> { //根结点 protected TreeNode<T> root; protected Queue<T> queue; //构造函数 public BiTree(LinkedList<T> inputList){ root = this.creatBiTree(inputList); queue = new LinkedList<T>(); } //添加结点 public TreeNode creatBiTree(LinkedList<T> inputList){ TreeNode<T> tmp = null; //判空 if(inputList==null || inputList.size()==0) return null; //存值 T data = inputList.removeFirst(); if(data != null){ tmp = new TreeNode(data); tmp.left = this.creatBiTree(inputList); tmp.right = this.creatBiTree(inputList); } //返回根结点 return tmp; } public void PostOrder(){ this.PostOrder(this.root); //换行 System.out.println(); } //后续遍历 protected void PostOrder(TreeNode<T> node){ if(node != null){ PostOrder(node.left); PostOrder(node.right); System.out.print(node.data + " "); //入队 queue.add(node.data); } } public Queue<T> getQueue() { return this.queue; } } 主函数package com.zbl; import java.util.LinkedList; import java.util.Queue; import java.util.Scanner; import java.util.Stack; /** * @Author {zbl} * @Date: 2019/12/09/ 11:54 * @Description */ public class Main { // 前序表达式+-+*3..*1..1..1../1..1..5.. public static void main(String[] args) { // write your code here Scanner scan = new Scanner(System.in); System.out.println("请输入前缀表达式："); String str =scan.next(); LinkedList<Character> list = new LinkedList<>(); for (int i = 0; i <str.length() ; i++) { char c = str.charAt(i); if(c=='.') list.add(null); else list.add(c); } BiTree<Character> tree = new BiTree<>(list); //后续遍历 tree.PostOrder(); //求解 System.out.println(solve(tree)); } public static Double solve(BiTree<Character> tree){ //获取后续表达式 Queue<Character> queue = tree.getQueue(); //数值栈 Stack<Double> num = new Stack<>(); //若栈不为空 while(!queue.isEmpty()){ char x = queue.poll(); if(x >= '0' && x <= '9'){ int tmp = x-'0'; num.push(new Double(tmp)); } //弹栈计算新值,并将新值入栈 else{ //此处注意数值先后顺序 double b = num.pop(); double a = num.pop(); double result = 0; switch (x){ case '+': result = a + b;break; case '-': result = a - b;break; case '*': result = a * b;break; case '/': result = a / b;break; } num.push(result); } } return num.pop(); } }
欢迎移步博主CSDN：CSDN博客简介今天有人问了一个关于JAVA对象之间的显隐式转换问题。为详细解释一下该原理，故此写下本篇博客！最后又顺便写了下该容器的具体实现。具体缘由问Question： 在一个容器里取出来时候 （没有定义泛型）为啥要强转 才能取出来？ 知道索引不能直接取出来吗？答Answer: object对象不能直接赋值给int，不能隐式转换，需要手动写对象类型显式转换，这就像猫一定是动物，但动物却不一定是猫一样。强转弱能使用隐式转换，而弱转强则一定要显示转换JAVA对象之间的显隐式转换：public class MyTest { public static void main(String[] args) { MyTest mytest = new MyTest(); Object obj = new Object(); Animal a = new Animal(); Cat c = new Cat(); System.out.println("obj的类型:" + obj.getClass()); System.out.println("a的类型:" + a.getClass()); System.out.println("c的类型:" + c.getClass()); System.out.println(); //隐式转换 obj = a; a = c; System.out.println("obj的类型:" + obj.getClass()); System.out.println("a的类型:" + a.getClass()); System.out.println("c的类型:" + c.getClass()); System.out.println(); //显式转换 c = (Cat)a; a = (Animal)obj; System.out.println("obj的类型:" + obj.getClass()); System.out.println("a的类型:" + a.getClass()); System.out.println("c的类型:" + c.getClass()); System.out.println(); } } class Animal{ } class Cat extends Animal{ }输出结果：obj的类型:class java.lang.Object a的类型:class test.Animal c的类型:class test.Cat obj的类型:class test.Animal a的类型:class test.Cat c的类型:class test.Cat obj的类型:class test.Animal a的类型:class test.Animal c的类型:class test.Cat注因为在java中Objec类是所有类的超类,Object对象转Integer对象需要使用显式转换，然后Integer赋值给int 类型的a变量，这里会进行拆包。我认为写成下面这样比较好理解int a = (Integer) c.get(0);补全该容器的实现(增删查改)class MyCollection { //定于data对象用于存放数据 private Object[] data; //实际长度 private int size; //容器大小 private int maxSize = 10; public MyCollection(int maxSize) { this.maxSize = maxSize; this.data = new Object[maxSize]; } public MyCollection() { this.data = new Object[maxSize]; } public int getSize() { return this.size; } //根据索引取对象 public Object get(int index) { return this.data[index]; } //增 public void add(int index,Object obj) { if(index > size) { throw new ArrayIndexOutOfBoundsException(); } //扩容 if(index >= maxSize) { Object[] newData = new Object[maxSize + 10]; System.arraycopy(this.data, 0, newData, 0, this.data.length); this.data = newData; this.maxSize += 10; } for(int i = this.size; i>index;i--) { this.data[i] = this.data[i-1]; } this.data[index] = obj; this.size += 1; } //删 public Boolean remove(int index) { if(index > this.size) { System.out.println("error:越界！"); return false; } int i = index; for(;i<this.size;i++) { this.data[index] = this.data[index + 1]; } this.data[i] = 0; this.size -= 1; return true; } //查 public int find(Object obj) { int index = 0; while(index < this.size) { if(this.data[index].equals(obj)) { return index; } index += 1; } return -1; } //改 public void set(int index, Object obj) { this.data[index] = obj; } }以上内容纯属个人见解，如有错误，还请各位不吝指正！