通俗易懂的Java8-Lambda與StreamAPI與新DateAPI
已更新至完整版
接口的默認方法(Default Methods for Interfaces)
Java 8使我們能夠通過使用 default
關鍵字向接口添加非抽象方法實現。 此功能也稱虛擬擴展方法
interface Formula {
double calculate(int a);
//接口的默認實現方法
default double sqrt(int a) {
return Math.sqrt(a);
}
}
主函數:
public class GoJava8 {
public static void main(String[] args) {
// 通過匿名內部類方式訪問接口
Formula formula = new Formula() {
//實現接口方法
@Override
public double calculate(int a) {
return sqrt(a * 100);
}
};
System.out.println(formula.calculate(100)); // 100.0
//接口默認實現方法可以直接調用
System.out.println(formula.sqrt(16)); // 4.0
}
}
formula 是作爲匿名對象實現的,我們可以這樣理解:一個內部類實現了接口裏的抽象方法並且返回一個內部類對象,之後我們讓接口的引用來指向這個對象
關於匿名內部類,這不是Java8的新特性,如果你沒有掌握,參考我之前寫的
Lambda表達式(Lambda expressions)
首先來看看,我們之前是如果對集合進行比較的
//要排序的字符串
List<String> names = Arrays.asList("bb","aa","cc") ;
//傳統方式,匿名內部類
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
//字符串的比較方法
return o1.compareTo(o2);
}
});
:通過匿名內部類,實現比較器Comparator接口
如果使用Lambda表達式呢?
Collections.sort(names, (a,b)->a.compareTo(b));
只需要一行代碼即可實現
Lambda表達式的基本語法爲:
- 操作符:->
- 左側:參數列表
- 右側:執行代碼塊 / Lambda 體
函數式接口(Functional Interfaces)
使用Lambda表達式需要函數式接口的支持
“函數式接口”是指僅僅只包含一個抽象方法,但是可以有多個非抽象方法(也就是上面提到的默認方法)的接口
像這樣的接口,可以被隱式轉換爲lambda表達式,java.lang.Runnable
與 java.util.concurrent.Callable
是函數式接口最典型的兩個例子
Java 8增加了一種特殊的註解@FunctionalInterface
,但是這個註解通常不是必須的(某些情況建議使用),只要接口只包含一個抽象方法,虛擬機會自動判斷該接口爲函數式接口
一般建議在接口上使用@FunctionalInterface
註解進行聲明,這樣的話,編譯器如果發現你標註了這個註解的接口有多於一個抽象方法的時候會進行報錯
注意:
- 函數式接口只有一個抽象方法
- default方法某默認實現,不屬於抽象方法
- 接口重寫了Object的公共方法也不算入內,如equal()
在上述代碼中,Comparator就是一個函數式接口
Lambda表達式的幾種常用情況
1.無參數,無返回值的Lambda
Runnable runnable = ()-> System.out.println("it is run method");
runnable.run();
@FunctionalInterface
public interface Runnable { //jdk提供
public abstract void run();
}
2.有一個參數,無返回值(小括號可省略)
Consumer consumer = (x)-> System.out.println(x) ;
consumer.accept("it accept method");
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
}
3.有兩個及以上參數,有返回值
Collections.sort(names, (a,b)->a.compareTo(b));
這個就是
可以看下比較器接口(sort的第二個參數)
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
...
}
內置的四種基本接口
函數式接口我們一般無需自己定義,根據返回值和參數的不同,函數式接口的形式是有限的,每次要用lambda都去寫個函數式接口那不是更麻煩了嗎
1.消費型接口-Consumer
2.提供型接口
-
無參有返回值
-
模擬返回隨機數
-
Supplier<Integer> supplier1 = ()->(int)(Math.random()*10); System.out.println(supplier1.get());
-
@FunctionalInterface public interface Supplier<T> { T get(); }
-
注意,在有返回值的情況下,如有多句語句,必須寫成:
-
Supplier<Integer> supplier1 = ()->{ System.out.println("hehe"); return (int)(Math.random()*10);//一定要有return }; System.out.println(supplier1.get());
3.函數型接口
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
String str = "abcdefg" ;
Function<String,String> function = (st)->st.substring(1,3);
System.out.println(function.apply(str));
典型的傳入參數與返回參數型接口
其他函數式接口:
4.斷言型接口
Predicate<Integer> predicate = (i)->i>=35 ;
System.out.println(predicate.test(25)); ;
@FunctionalInterface
public interface Predicate<T> {
//傳入參數,返回布爾值
boolean test(T t);
}
引用
若 Lambda 表達式體中的內容已有方法實現,則我們可以使用“方法引用”
分爲:
- 對象 :: 實例方法
- 類 :: 靜態方法
- 類 :: 實例方法
/*對象::實例方法
* 要求:實現的方法返回值與參數與函數式接口中的方法保持一致
* */
PrintStream out = System.out;//PrintStream中實現了println方法
Consumer<String> consumer = out::println; //實例::方法名
consumer.accept("hello");
/*類::靜態方法
* 要求:實現的方法返回值與參數與函數式接口中的方法保持一致
* */
Comparator<Integer> comparator = (x,y)->Integer.compare(x,y);
System.out.println(comparator.compare(2,1));
Comparator<Integer> comparator1 = Integer::compare ;
System.out.println(comparator1.compare(1,2));
/*類::實例方法
* Lambda 參數列表中的第一個參數是方法的調用者,第二個參數是方法的參數時,才能使用 ClassName :: Method
* 如 "ab".equal("cd")
* */
BiPredicate<String,String > biPredicate = String::equals ;
System.out.println(biPredicate.test("ab","cd"));
/*構造器引用
* className::new
* */
//傳統
Supplier<List> supplier = ()-> new ArrayList<>();
//引用
supplier = ArrayList::new ;
StreamAPI
流(Stream)是數據渠道,用於操作數據源(集合、數組等)所生成的元素序列
集合講數據,流講計算
注意:
- Stream不會自己存儲數據
- Stream不會改變源對象,相反,它們回返回一個持有結果的新Stream
- Stream操作是延遲執行的,這意味着它們會等到需要結果的時候才執行(懶漢)
Stream操作的三個步驟
- 創建Stream
- 中間操作:一條操作鏈,對數據源的數據進行處理
- 終止操作:執行中間操作,產生結果
創建流的幾種方式
/*創建流的幾種方式*/
//集合流
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
//數組流
String[] strings = {"aa","bb","cc"} ;
Stream<String> stream1 = Arrays.stream(strings);
//Stream類 -> of方法 返回的其實就是Arrays.stream(strings)
Stream<String> stream2 = Stream.of("aa", "bb", "cc");
/*
* 無限流 seed:the initial element
* */
Stream<Integer> iterate = Stream.iterate(0, (i) -> ++i+i++);
iterate.forEach(System.out::println);
/*生成流*/
Stream.generate(()->Math.random())
.limit(3)
.forEach(System.out::println);
中間操作_篩選/切片
- filter:接收 Lambda ,從流中排除某些元素
- limit:截斷流,使其元素不超過給定數量
- skip(n):跳過元素,返回一個捨棄了前n個元素的流;若流中元素不足n個,則返回一個空流;與 limit(n) 互補
- distinct:篩選,通過流所生成的 hashCode() 與 equals() 取除重複元素
實例:
//模擬數據庫->表
class Employee{
Integer id ;
String name ;
Integer age ;
double sal ;
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", sal=" + sal +
'}';
}
public Employee(Integer id, String name, Integer age, double sal) {
this.id = id;
this.name = name;
this.age = age;
this.sal = sal;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
public double getSal() {
return sal;
}
}
List<Employee> emps = Arrays.asList(
new Employee(101, "Z3", 19, 9999.99),
new Employee(102, "L4", 20, 7777.77),
new Employee(103, "W5", 35, 6666.66),
new Employee(104, "Tom", 44, 1111.11),
new Employee(105, "Jerry", 60, 4444.44)
);
emps.stream()
.filter((x)->x.getAge()>20)
.limit(3)
.distinct()
.skip(1)
.forEach(System.out::println);
結果:
Employee{id=104, name=‘Tom’, age=44, sal=1111.11}
Employee{id=105, name=‘Jerry’, age=60, sal=4444.44}
映射
- map:接收 Lambda ,將元素轉換爲其他形式或提取信息;接受一個函數作爲參數,該函數會被應用到每個元素上,並將其映射成一個新的元素
- flatMap:接收一個函數作爲參數,將流中每一個值都換成另一個流,然後把所有流重新連接成一個流
List<String> list = Arrays.asList("bb","aa","cc") ;
list.stream()
.map((str)->str.toUpperCase())
.forEach(System.out::println);
result:
BB
AA
CC
排序
sorted():自然排序
sorted(Comparator c):定製排序
list.stream()
.sorted()
.forEach(System.out::println);
emps.stream()
.sorted((e1,e2)->{
return e1.getAge().compareTo(e2.getAge()) ;
})
.forEach(System.out::println);
查找、匹配
- allMatch:檢查是否匹配所有元素
- anyMatch:檢查是否至少匹配一個元素
- noneMatch:檢查是否沒有匹配所有元素
- findFirst:返回第一個元素
- findAny:返回當前流中的任意元素
- count:返回流中元素的總個數
- max:返回流中最大值
- min:返回流中最小值
List<Status> statuses = Arrays.asList(Status.FREE, Status.BUSY, Status.VOCATION) ;
boolean flag1 = statuses.stream()
.allMatch((x)->x.equals(Status.BUSY));
System.out.println(flag1);
boolean flag2 = statuses.stream()
.anyMatch((x)->x.equals(Status.BUSY));
System.out.println(flag2);
Optional<Status> first = statuses.stream()
.findFirst();
System.out.println(first);
public enum Status{
FREE,BUSY,VOCATION ;
}
規約
歸約:reduce(T identity, BinaryOperator) / reduce(BinaryOperator) 可以將流中的數據反覆結合起來,得到一個值
List<Integer> list1 = Arrays.asList(1,2,3,4) ;
Integer reduce = list1.stream()
.reduce(3, (x, y) -> x - y);//x代表3,y代表list中數字
System.out.println(reduce); //-7
新的DateAPI
- Clock 類提供了訪問當前日期和時間的方法,Clock 是時區敏感的,可以用來取代
System.currentTimeMillis()
來獲取當前的微秒數。某一個特定的時間點也可以使用Instant
類來表示,Instant
類也可以用來創建舊版本的java.util.Date
對象。 - 在新API中時區使用 ZoneId 來表示。時區可以很方便的使用靜態方法of來獲取到。 抽象類
ZoneId
(在java.time
包中)表示一個區域標識符。 它有一個名爲getAvailableZoneIds
的靜態方法,它返回所有區域標識符。 - jdk1.8中新增了 LocalDate 與 LocalDateTime等類來解決日期處理方法,同時引入了一個新的類DateTimeFormatter 來解決日期格式化問題。可以使用Instant代替 Date,LocalDateTime代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat。
Clock
Clock 類提供了訪問當前日期和時間的方法,Clock 是時區敏感的,可以用來取代 System.currentTimeMillis()
來獲取當前的微秒數。某一個特定的時間點也可以使用 Instant
類來表示,Instant
類也可以用來創建舊版本的java.util.Date
對象
Clock clock = Clock.systemDefaultZone() ;
System.out.println(clock);
System.out.println(System.currentTimeMillis());
long millis = clock.millis();
System.out.println(millis);
Instant instant = clock.instant() ;
System.out.println(instant);
Date from = Date.from(instant);
System.out.println(from);
Timezones(時區)
在新API中時區使用 ZoneId 來表示。時區可以很方便的使用靜態方法of來獲取到。 抽象類ZoneId
(在java.time
包中)表示一個區域標識符。 它有一個名爲getAvailableZoneIds
的靜態方法,它返回所有區域標識符。
/*Timezones*/
//所有區域標識符
System.out.println(ZoneId.getAvailableZoneIds());
ZoneId zoneId = ZoneId.of("Asia/Aden") ;
System.out.println(zoneId.getRules()); //有偏移量
LocalDate(本地日期)
LocalDate 表示了一個確切的日期,比如 2014-03-11。該對象值是不可變的,用起來和LocalTime基本一致。下面的例子展示瞭如何給Date對象加減天/月/年。另外要注意的是這些對象是不可變的,操作返回的總是一個新實例,這保證了線程的安全性
/*LocalData*/
LocalDate now = LocalDate.now();
System.out.println(now);
LocalDate tomorrow = now.plus(1, ChronoUnit.DAYS);
System.out.println(tomorrow);
LocalDate yest = now.minusDays(1);
System.out.println(yest);
LocalDate date = LocalDate.of(2020, 6, 18);
System.out.println(date);
DateTimeFormatter
String str = "2020==06==18 03時06分09秒" ;
// 根據需要解析的日期、時間字符串定義解析所用的格式器
DateTimeFormatter fomatter = DateTimeFormatter
.ofPattern("yyyy==MM==dd HH時mm分ss秒");
LocalDateTime parse = LocalDateTime.parse(str,fomatter);
System.out.println(parse);
LocalDateTime now1 = LocalDateTime.now();
String format = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(now1);
System.out.println(format);
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss");
System.out.println(dateTimeFormatter.format(now1));