怎樣用jsp做網(wǎng)站微信推廣平臺
1.Lambda
1.1?格式
JDK?從?1.8?版本開始支持?Lambda?表達式,通過?Lambda?表達式我們可以將一個函數(shù)作為參數(shù)傳入方法中。在?JDK?1.8?之前,我們只能通過匿名表達式來完成類似的功能,但是匿名表達式比較繁瑣,存在大量的模板代碼,不利于將行為參數(shù)化,而采用?Lamdba?則能很好的解決這個問題。Lambda?表達式的基本語法如下:
(parameters)?->?expression
或采用花括號的形式:
(parameters) -> { statements; }
Lambda?表達式具有如下特點:
可選的參數(shù):不需要聲明參數(shù)類型,編譯器會依靠上下文進行自動推斷;
可選的參數(shù)圓括號:當且僅當只有一個參數(shù)時,包裹參數(shù)的圓括號可以省略;?
可選的花括號:如果主體只有一個表達式,則無需使用花括號;?
可選的返回關鍵字:如果主體只有一個表達式,則該表達式的值就是整個?Lambda?表達式的返回值,此時不需要使用?return?關鍵字進行顯式的返回。
1.2?行為參數(shù)化
?上面我們說過,Lambda?表達式主要解決的是行為參數(shù)化的問題,而什么是行為參數(shù)化?下面給出一個具體的示例:
/***?定義函數(shù)式接口* @param <T>?參數(shù)類型*/
@FunctionalInterface
public interface CustomPredicate<T> {boolean test(T t);
}
/***?集合過濾* @param list?待過濾的集合* @param predicate?函數(shù)式接口* @param <T>?集合中元素的類型* @return?滿足條件的元素的集合*/
public static <T> List<T> filter(List<T> list, CustomPredicate<T> predicate) {ArrayList<T> result = new ArrayList<>();for (T t : list) {//?將滿足條件的元素添加到返回集合中if (predicate.test(t)) result.add(t);}return result;
}
針對不同類型的集合,我們可以通過傳入不同的?Lambda?表達式作為參數(shù)來表達不同的過濾行為,這就是行為參數(shù)化:
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
filter(integers, x -> x % 2 == 0); //?過濾出所有偶數(shù)List<Employee> employees = Arrays.asList(new Employee("張某", 21, true),new Employee("李某", 30, true),new Employee("王某", 45, false));
filter(employees, employee -> employee.getAge() > 25); //?過濾出所有年齡大于25的員工
需要注意的是上面我們聲明接口時,使用了?@FunctionalInterface?注解,它表示當前的接口是一個函數(shù)式接口。函數(shù)式接口就是只含有一個抽象方法的接口;即一個接口不論含有多少個默認方法和靜態(tài)方法,只要它只有一個抽象方法,它就是一個函數(shù)式接口。使用?@FunctionalInterface?修飾后,當該接口有一個以上的抽象方法時,編譯器就會進行提醒。
任何使用到函數(shù)式接口的地方,都可以使用?Lambda?表達式進行簡寫。例如?Runnable?接口就是一個函數(shù)式接口,我們可以使用?Lambda?表達式對其進行簡寫:
new Thread(() -> {System.out.println("hello");
});
1.3?方法引用和構造器引用
緊接上面的例子,如果我們需要過濾出所有的正式員工,除了可以寫成下面的形式外:
filter(employees, employee -> employee.isOfficial());
還可以使用方法引用的形式進行簡寫:
filter(employees, Employee::isOfficial);
除了方法引用外,還可以對構造器進行引用,示例如下:
Stream<Integer> stream = Stream.of(1, 3, 5, 2, 4);
stream.collect(Collectors.toCollection(ArrayList::new)); //等價于?toCollection(()->new ArrayList<>())
方法引用和構造器引用的目的都是為了讓代碼更加的簡潔。
2. 函數(shù)式接口
通常我們不需要自定義函數(shù)式接口,JDK?中內(nèi)置了大量函數(shù)式接口,基本可以滿足大多數(shù)場景下的使用需求,最基本的四種如下:
2.1.?Consumer<T>消費型接口
消費輸入的變量,沒有返回值:
@FunctionalInterface
public interface Consumer<T> {void accept(T t);...
}
2.2?Consumer<T>:供給型接口
供給變量:
@FunctionalInterface
public interface Supplier<T> {T get();
}
2.3?Function<T,?R>:
對輸入類型為?T?的變量執(zhí)行特定的轉(zhuǎn)換操作,并返回類型為?R?的返回值:
@FunctionalInterface
public interface Function<T, R> {R apply(T t);...
}
2.4?Predicate<T>:
判斷類型為?T?的變量是否滿足特定的條件,如果滿足則返回?true,否則返回?false:
@FunctionalInterface
public interface Predicate<T> {boolean test(T t);...
}
其他函數(shù)式接口都是這四種基本類型的擴展和延伸。以?BiFunction?和?BinaryOperator?接口為例:
BiFunction<T,?U,?R>:是函數(shù)型接口?Function<T,?R>?的擴展,Function?只能接收一個入?yún)?#xff1b;而?BiFunction?可以用于接收兩個不同類型的入?yún)?#xff1b;
BinaryOperator<T>:是?BiFunction?的一種特殊化情況,即兩個入?yún)⒑头祷刂档念愋途嗤?#xff0c;通常用于二元運算。定義如下:
@FunctionalInterface
public interface BiFunction<T, U, R> {R apply(T t, U u);
}@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {....
}
下面演示一下?BinaryOperator?的用法:
/*?執(zhí)行歸約操作*/
public static <T> T reduce(List<T> list, T initValue, BinaryOperator<T> binaryOperator) {for (T t : list) {initValue = binaryOperator.apply(initValue, t);}return initValue;
}public static void main(String[] args) {List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);reduce(integers, 0, (a, b) -> a + b); //?求和??輸出:15reduce(integers, 1, (a, b) -> a * b); //?求積??輸出:120
}
3. 創(chuàng)建流
JDK?1.8?中另一個大的改進是引入了流,通過流、Lamda?表達式以及函數(shù)式接口,可以高效地完成數(shù)據(jù)的處理。創(chuàng)建流通常有以下四種方法:
3.1?由值創(chuàng)建
使用靜態(tài)方法?Stream.of()?由指定的值進行創(chuàng)建:
Stream<String> stream = Stream.of("a", "b", "c", "d");
3.2?由集合或數(shù)組創(chuàng)建
使用靜態(tài)方法?Arrays.stream()?由指定的數(shù)組進行創(chuàng)建:
String[] strings={"a", "b", "c", "d"};
Stream<String> stream = Arrays.stream(strings);
調(diào)用集合類的?stream()?方法進行創(chuàng)建:
List<String> strings = Arrays.asList("a", "b", "c", "d");
Stream<String> stream = strings.stream();
stream()?方法定義在?Collection?接口中,它是一個默認方法,因此大多數(shù)的集合都可以通過該方法來創(chuàng)建流:
public interface Collection<E> extends Iterable<E> {default Stream<E> stream() {return StreamSupport.stream(spliterator(), false);}
}
3.3?由文件創(chuàng)建
try (Stream<String> lines = Files.lines(Paths.get("pom.xml"), StandardCharsets.UTF_8)) {lines.forEach(System.out::println);
} catch (IOException e) {e.printStackTrace();
}
3.4?由函數(shù)創(chuàng)建
除了以上方法外,還可以通過?Stream.iterate()?和?Stream.generate()?方法來來創(chuàng)建無限流:
Stream.iterate()?接受兩個參數(shù):第一個是初始值;第二個參數(shù)是一個輸入值和輸出值相同的函數(shù)型接口,主要用于迭代式地產(chǎn)生新的元素,示例如下:
//?依次輸出0到9
Stream.iterate(0, x -> x + 1).limit(10).forEach(System.out::print);
Stream.generate()??接收一個供應型函數(shù)作為參數(shù),用于按照該函數(shù)產(chǎn)生新的元素:
//?依次輸出隨機數(shù)Stream.generate(Math::random).limit(10).forEach(System.out::print);
4. 操作流
4.1?基本操作
當流創(chuàng)建后,便可以利用?Stream?類上的各種方法對流中的數(shù)據(jù)進行處理,常用的方法如下:
操作 | 作用 | 返回類型 | 使用的類型/函數(shù)式接口 |
filter | 過濾符合條件的元素 | Stream<T> | Predicate<T> |
distinct | 過濾重復元素 | Stream<T> | |
skip | 跳過指定數(shù)量的元素 | Stream<T> | long |
limit | 限制元素的數(shù)量 | Stream<T> | long |
map | 對元素執(zhí)行特定轉(zhuǎn)換操作 | Stream<T> | Function<T,R> |
flatMap | 將元素扁平化后執(zhí)行特定轉(zhuǎn)換操作 | Stream<T> | Function<T,Stream<R>> |
sorted | 對元素進行排序 | Stream<T> | Comparator<T> |
anyMatch | 是否存在任意一個元素能滿足指定條件 | boolean | Predicate<T> |
noneMatch | 是否所有元素都不滿足指定條件 | boolean | Predicate<T> |
allMatch | 是否所有元素都滿足指定條件 | boolean | Predicate<T> |
findAny | 返回任意一個滿足指定條件的元素 | Optional<T> | |
findFirst | 返回第一個滿足指定條件的元素 | Optional<T> | |
forEach | 對所有元素執(zhí)行特定的操作 | void | Cosumer<T> |
collect | 使用收集器 | R | Collector<T,?A,?R> |
reduce | 執(zhí)行歸約操作 | Optional<T> | BinaryOperator<T> |
count | 計算流中元素的數(shù)量 | long |
注:上表中返回類型為?Stream<T>?的操作都是中間操作,代表還可以繼續(xù)調(diào)用其它方法對流進行處理。返回類型為其它的操作都是終止操作,代表處理過程到此為止。
使用示例如下:
Stream.iterate(0, x -> x + 1) //?構建流.limit(20) //?限制元素的個數(shù).skip(10) //?跳過前10個元素.filter(x -> x % 2 == 0) //?過濾出所有偶數(shù).map(x -> "偶數(shù):" + x) //?對元素執(zhí)行轉(zhuǎn)換操作.forEach(System.out::println); //?打印出所有元素輸出結果如下:shell
偶數(shù):10
偶數(shù):12
偶數(shù):14
偶數(shù):16
偶數(shù):18
?上表的?flatMap()?方法接收一個參數(shù),該參數(shù)是一個函數(shù)型接口?Function<??super?T,???extends?Stream<??extends?R>>?mapper,主要用于將流中的元素轉(zhuǎn)換為?Stream?,從而可以將原有的元素進行扁平化,示例如下:
String[] strings = {"hello", "world"};Arrays.stream(strings).map(x -> x.split("")) //?拆分得到: ['h','e','l','l','o'],['w','o','r','l','d'].flatMap(x -> Arrays.stream(x)) //?將每個數(shù)組進行扁平化處理得到:'h','e','l','l','o','w','o','r','l','d'.forEach(System.out::println);
而上表的?reduce()?方法則接收兩個參數(shù):第一個參數(shù)表示執(zhí)行歸約操作的初始值;第二個參數(shù)是上文我們介紹過的函數(shù)式接口?BinaryOperator<T>?,使用示例如下:
Stream.iterate(0, x -> x + 1).limit(10).reduce(0, (a, b) -> a + b); //進行求和操作
4.2?數(shù)值流
上面的代碼等效于對?Stream?中的所有元素執(zhí)行了求和操作,因此我們還可以調(diào)用簡便方法?sum()?來進行實現(xiàn),但是需要注意的是?Stream.iterate()?生成流中的元素類型都是包裝類型:
Stream<Integer> stream = Stream.iterate(0, x -> x + 1); //包裝類型Integer
而?sum()?方法則是定義在?IntStream?上,此時需要將流轉(zhuǎn)換為具體的數(shù)值流,對應的方法是?mapToInt():
Stream.iterate(0, x -> x + 1).limit(10).mapToInt(x -> x).sum();
類似的方法還有?mapToLong()?和?mapToDouble()?。如果你想要將數(shù)值流轉(zhuǎn)換為原有的流,相當于對其中的元素進行裝箱操作,此時可以調(diào)用?boxed()?方法:
IntStream intStream = Stream.iterate(0, x -> x + 1).limit(10).mapToInt(x -> x);
Stream<Integer> boxed = intStream.boxed();
5.流收集器
5.1?常用流收集器
Stream?中最強大一個終止操作是?collect()?,它接收一個收集器?Collector?作為參數(shù),可以將流中的元素收集到集合中,或進行分組、分區(qū)等操作。Java?中內(nèi)置了多種收集器的實現(xiàn),可以通過?Collectors?類的靜態(tài)方法進行調(diào)用,常用的收集器如下:
工廠方法 | 返回類型 | 用于 |
toList | List<T> | 把流中所有元素收集到?List?中 |
toSet | Set<T> | 把流中所有元素收集到?Set?中 |
toCollection | Collection<T> | 把流中所有元素收集到指定的集合中 |
counting | Long | 計算流中所有元素的個數(shù) |
summingInt | Integer | 將流中所有元素轉(zhuǎn)換為整數(shù),并計算其總和 |
averagingInt | Double | 將流中所有元素轉(zhuǎn)換為整數(shù),并計算其平均值 |
summarizingInt | IntSummaryStatistics | 將流中所有元素轉(zhuǎn)換為整數(shù),并返回統(tǒng)計結果,包含最大值、最小值、 總和與平均值等信息 |
joining | String | 將流中所有元素轉(zhuǎn)換為字符串,并使用給定連接符進行連接 |
maxBy | Optional<T> | 查找流中最大元素的?Optional |
minBy | Optional<T> | 查找流中最小元素的?Optional |
reducing | 規(guī)約操作產(chǎn)生的類型 | 對流中所有元素執(zhí)行歸約操作 |
collectingAndThen | 轉(zhuǎn)換返回的類型 | 先把流中所有元素收集到指定的集合中,再對集合執(zhí)行特定的操作 |
groupingBy | Map<K,List<T>> | 對流中所有元素執(zhí)行分組操作 |
partitionBy | Map<Boolean,List<T>> | 對流中所有元素執(zhí)行分區(qū)操作 |
使用示例如下:
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 4, 5, 6); stream.collect(Collectors.toSet()); // [1, 2, 3, 4, 5, 6]
stream.collect(Collectors.toList()); // [1, 2, 3, 4, 4, 5, 6]
stream.collect(Collectors.toCollection(ArrayList::new)); // [1, 2, 3, 4, 4, 5, 6]
stream.collect(Collectors.counting()); // 7?等效于?stream.count();
stream.collect(Collectors.summarizingInt(x -> x)); // IntSummaryStatistics{count=7, sum=25, min=1, average=3.571429, max=6}
stream.collect(Collectors.maxBy((Integer::compareTo))); // Optional[6]
stream.collect(Collectors.reducing(1, (a, b) -> a * b)); //?等效于?stream.reduce(1, (a, b) -> a * b);
collect(Collectors.collectingAndThen(Collectors.toSet(), Set::size)); //?先把所有元素收集到Set中,再計算Set的大小
注意:以上每個終止操作只能單獨演示,因為對一個流只能執(zhí)行一次終止操作。并且執(zhí)行完終止操作后,就不能再對這個流進行任何操作,否則將拋出?java.lang.IllegalStateException:?stream?has?already?been?operated?upon?or?closed?的異常。
5.2?分組收集器
分組收集器可以實現(xiàn)類似數(shù)據(jù)庫?groupBy?子句的功能。假設存在如下員工信息:
Stream<Employee> stream = Stream.of(new Employee("張某", "男", "A公司", 20),new Employee("李某", "女", "A公司", 30),new Employee("王某", "男", "B公司", 40),new Employee("田某", "女", "B公司", 50));
public class Employee {private String name;private String gender;private String company;private int age;@Overridepublic String toString() {return "Employee{" + "name='" + name + '\'' + '}';}
}
此時如果需要按照公司進行分組,則可以使用?groupingBy()?收集器:?
stream.collect(Collectors.groupingBy(Employee::getCompany));對應的分組結果如下:
{ B公司=[Employee{name='王某'}, Employee{name='田某'}], A公司=[Employee{name='張某'}, Employee{name='李某'}]
}
如果想要計算分組后每家公司的人數(shù),還可以為?groupingBy()?傳遞一個收集器?Collector?作為其第二個參數(shù),調(diào)用其重載方法:
stream.collect(Collectors.groupingBy(Employee::getCompany, Collectors.counting()));對應的結果如下:
{B公司=2, A公司=2
}
因為第二個參數(shù)是一個?Collector,這意味著你可以再傳入一個分組收集器來完成多級分組,示例如下:
stream.collect(Collectors.groupingBy(Employee::getCompany, Collectors.groupingBy(Employee::getGender)));先按照公司分組,再按照性別分組,結果如下:
{ B公司={女=[Employee{name='田某'}],?男=[Employee{name='王某'}]}, A公司={女=[Employee{name='李某'}],?男=[Employee{name='張某'}]}
}
除此之外,也可以通過代碼塊來自定義分組條件,示例如下:
Map<String, List<Employee>> collect = stream.collect(Collectors.groupingBy(employee -> {if (employee.getAge() <= 30) {return "青年員工";} else if (employee.getAge() < 50) {return "中年員工";} else {return "老年員工";}
}));對應的分組結果如下:
{
??中年員工=[Employee{name='王某'}],
??青年員工=[Employee{name='張某'}, Employee{name='李某'}],
??老年員工=[Employee{name='田某'}]
}
5.3?分區(qū)
分區(qū)是分組的一種特殊情況,即將滿足指定條件的元素分為一組,將不滿足指定條件的元素分為另一組,兩者在使用上基本類似,示例如下:
stream.collect(Collectors.partitioningBy(x -> "A公司".equals(x.getCompany())));對應的分區(qū)結果如下:
{false=[Employee{name='王某'}, Employee{name='田某'}], true=[Employee{name='張某'}, Employee{name='李某'}]
}
6. 并行流
想要將普通流轉(zhuǎn)換為并行流非常簡單,只需要調(diào)用?Stream?的?parallel()?方法即可:
stream.parallel();
此時流中的所有元素會被均勻的分配到多個線程上進行處理。并行流內(nèi)部使用的是?ForkJoinPool?線程池,它默認的線程數(shù)量就是處理器數(shù)量,可以通過?Runtime.getRuntime().availableProcessors()?來查看該值,通常不需要更改。
當前也沒有辦法為某個具體的流指定線程數(shù)量,只能通過修改系統(tǒng)屬性?java.util.concurrent.ForkJoinPool.common.parallelism?的值來改變所有并行流使用的線程數(shù)量,示例如下:
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12");
如果想將并行流改回普通的串行流,則只需要調(diào)用?Stream?的?sequential()?方法即可:
stream.sequential();