【技術積累】Java 8 新特性

来源:https://www.cnblogs.com/deyo/archive/2023/08/17/17638586.html
-Advertisement-
Play Games

![](https://cdn.nlark.com/yuque/0/2023/jpeg/28753938/1691067189459-f51a48da-0da6-4e6e-aeee-75b39662cd20.jpeg) ## 一、Lambda表達式 > Lambda 是一個匿名函數,我們可以把 La ...


一、Lambda表達式

Lambda 是一個匿名函數,我們可以把 Lambda表達式理解為是一段可以傳遞的代碼(將代碼像數據一樣進行傳遞)。可以寫出更簡潔、更靈活的代碼。作為一種更緊湊的代碼風格,使Java的語言表達能力得到了提升

1、舉例

( o1 , o2 ) -> Integer.compare( o1 , o2 )

2、格式

  • -> : lambda 操作符或箭頭操作符
  • -> 左邊 :lambda 形參列表(其實就是抽象中的抽象方法的形參列表)
  • -> 右邊 :lambda 體(其實就是重寫的抽象方法的方法體)

3、lambda 表達式的使用(6種情況)

        Runnable r = () -> System.out.println("hello");
        Consumer<String> consumer =  (args) -> System.out.println(args);
        Consumer<String> consumer2 =  args -> System.out.println(args);
    	BinaryOperator<Long> bo = (Long x,Long y) -> {
            System.out.println("實現函數介面方法!");
            return x + y;
        };
        BinaryOperator<Long> bio = (Long x,Long y) -> x + y;
        BinaryOperator<Long> bio = (x, y) -> x + y;

4、lambda 表達式的本質:作為介面的實例

二、函數式介面

1、什麼是函數式介面

  • 只包含一個抽象方法的介面,稱為函數式介面。
  • 你可以通過 Lambda 表達式來創建該介面的對象。(若 Lambda 表達式拋出一個受檢異常,那麼該異常需要在目標介面的抽象方法上進行聲明)。
  • 我們可以在任意函數式介面上使用 @FunctionalInterface 註解,這樣做可以檢查它是否是一個函數式介面,同時 javadoc 也會包含一條聲明,說明這個介面是一個函數式介面。

image.png

2、自定義函數式介面

@FunctionalInterface
public interface MyInterface {
    public String getValue();
}

@FunctionalInterface
public interface MyInterface<T> {
    public T getValue(T t);
}

3、Lambda 表達式作為參數傳遞

註意:為了將 Lambda 表達式作為參數傳遞,接收Lambda 表達式的參數類型必須是與該 Lambda 表達式相容的函數式介面的類型。
image.png

4、Java 內置四大核心函數式介面

image.png

消費型介面Consumer void accept(T t)

	@Test
    public void testConsumer() {
        // 消費型介面Consumer<T>        void accept(T t)
        buyCar(265000.00, new Consumer<Double>() {
            @Override
            public void accept(Double aDouble) {
                System.out.println("買車花費了:" + aDouble);
            }
        });
        System.out.println("使用lambda表達式如下");
        buyCar(99999.99, money -> System.out.println("買車花費了:" + money));
    }

	/**
     * 利用Consumer 實現消費
     * @param money 金額
     * @param con 所消費金額
     */
    public void buyCar(Double money, Consumer<Double> con) {
        con.accept(money);
    }

image.png

斷定型介面Predicate boolean test(T t)

	@Test
    public void testPredicate() {
        // 斷定型介面Predicate<T>       boolean test(T t)
        List<String> list = Arrays.asList("aabb", "bbcc", "ccdd", "aadd");
        List<String> res = filterString(list, new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.contains("aa");
            }
        });
        System.out.println("過濾後:" + res);
        System.out.println("使用lambda表達式如下");
        List<String> res2 = filterString(list, s -> s.contains("aa"));
        System.out.println("過濾後:" + res2);

    }

    /**
     * 利用Predicate 實現過濾
     * @param list 原數組
     * @param pre 約束條件
     * @return 過濾後數組
     */
    public List<String> filterString(List<String> list, Predicate<String> pre) {
        List<String> result = new ArrayList<>();

        for (String s : list) {
            if (pre.test(s)) {
                result.add(s);
            }
        }
        return result;
    }

image.png供給型介面Supplier T get()

函數型介面Function<T,R> R apply(T t)

5、其他介面

image.png

三、函數式引用(方法引用與構造器引用)

使用場景

當要傳遞給 Lambda 體的操作,已經有實現的方法了,可以使用方法引用!
方法引用,本質上也是 Lambda 表達式,而 Lambda 表達式作為函數式介面的實例。所以,方法引用也是函數式介面的實例。

使用格式

方法引用使用的要求:要求介面中的抽象方法的形參列表和返回值類型與方法引用的方法的形參列表和返回值類型相同!(情況三特殊)
情況一 對象 :: 非靜態方法
情況二 類 :: 靜態方法
情況三 類 :: 非靜態方法

示例代碼

package com.mv.java8.basic;

import org.junit.Test;

import java.io.PrintStream;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

/**
* 方法引用
* 1.使用情景:當要傳遞給 Lambda 體的操作,已經有實現的方法了,可以使用方法引用!
* 2.方法引用,本質上也是 Lambda 表達式,而 Lambda 表達式作為函數式介面的實例。所以,方法引用也是函數式介面的實例。
* 3.使用格式: 類 (或對象) :: 方法名
* 4.具體分為以下三種情況
* 5.方法引用使用的要求:要求介面中的抽象方法的形參列表和返回值類型與方法引用的方法的形參列表和返回值類型相同!
* 對象 :: 非靜態方法
* 類 :: 靜態方法
* 類 :: 非靜態方法
*
* @author wv
* @version V1.0
* @date 2023/8/3 19:45
*/
public class MethodRefTest {


    @Test
    public void test01() {

        // 情況一 : 對象 :: 實例方法
        // Consumer 中的 void accept(T t)
        // PrintStream 中的 void println(T t)

        Consumer<String> con1 = str -> System.out.println(str);
        con1.accept("hello");

        System.out.println("使用方法引用如下:");

        //        PrintStream printStream = System.out;
        //        Consumer<String> con2 = printStream::println;
        Consumer<String> con2 = System.out::println;
        con2.accept("world");

    }

    @Test
    public void test02() {

        // 情況二 : 類 :: 靜態方法
        // Comparator 中的 int compare(T t1, T t2)
        // Integer 中的 int compare(T t1, T t2)

        Comparator<Integer> com1 = (a, b) -> Integer.compare(a, b);
        System.out.println(com1.compare(10, 5));

        System.out.println("使用方法引用如下:");
        Comparator<Integer> com2 = Integer::compare;
        System.out.println(com1.compare(5, 10));

        // Function 中的 apply(T t)
        // Math 中的 Long round(Double d)
        Function<Double, Long> func = new Function<Double, Long>() {
            @Override
            public Long apply(Double aDouble) {
                return Math.round(aDouble);
            }
        };
        System.out.println(func.apply(3.14));
        System.out.println("使用方法引用如下:");
        Function<Double, Long> func1 = Math::round;
        System.out.println(func1.apply(3.14));


    }

    @Test
    public void test03() {

        // 情況二 : 類 :: 非靜態方法
        // Comparator 中的 int compare(T t1, T t2)
        // String 中的 int t1.compareTo(t2)

        Comparator<String> com1 = (a, b) -> a.compareTo(b);
        System.out.println(com1.compare("a", "b"));

        System.out.println("使用方法引用如下:");
        Comparator<String> com2 = String::compareTo;
        System.out.println(com1.compare("a", "b"));

    }

    @Test
    public void test04() {

        // 構造器引用
        // Supplier 中的 T get()

        Supplier<Object> supplier1 = new Supplier<Object>() {
            @Override
            public Object get() {
                return new Object();
            }
        };
        System.out.println(supplier1.get());
        System.out.println("使用方法引用如下:");
        Supplier<Object> supplier2 = Object::new;
        System.out.println(supplier2.get());



        // 數組引用
        // Function 中的 R apply(T t)

        Function<Integer, String[]> func1 = length -> new String[length];
            String[] strings = func1.apply(6);
            System.out.println(Arrays.toString(strings));
            System.out.println("使用方法引用如下:");
            Function<Integer, String[]> func2 = String[] ::new;
            String[] strings2 = func1.apply(6);
            System.out.println(Arrays.toString(strings2));

            }
            }

四、Stream 流

1、概念

Java8中有兩大最為重要的改變。第一個是 Lambda 表達式;另外一個則是 Stream API(java.util.stream.*)。
Stream 是 Java8 中處理集合的關鍵抽象概念,它可以指定你希望對集合進行的操作,可以執行非常複雜的查找、過濾和映射數據等操作。使用Stream API 對集合數據進行操作,就類似於使用 SQL 執行的資料庫查詢。也可以使用 Stream API 來並行執行操作。簡而言之,Stream API 提供了一種高效且易於使用的處理數據的方式。
stream 流是數據渠道,用於操作數據源(集合、數組等)所生成的元素序列。
集合講究的是數據,流講究的是計算!

2、操作步驟

  1. 創建 Stream :一個數據源(如:集合、數組),獲取一個流
  2. 中間操作:一個中間操作鏈,對數據源的數據進行處理
  3. 終止操作(終端操作)

image.png

3、特點

  1. Stream 關註的是對數據的運算,CPU 打交道;集合關註的是數據的存儲,與記憶體打交道
  2. Stream 流
    1. 不會自己存儲元素
    2. 不會改變數據源。相反,他們會返回一個持有結果的 Stream 流
    3. 操作是延遲執行的,這意味著他們會等到需要結果的時候執行
  3. Stream 執行流程
    1. stream 的實例化
    2. 一系列的中間操作(過濾、映射、......)
    3. 終止操作
  4. 說明
    1. 一個中間操作鏈,對數據源的數據進行處理
    2. 一旦執行終止操作,就執行中間操作鏈,並產生結果。之後,不會再被使用。(需重新執行

4、Stream 流創建方式

創建方式一:通過集合創建 Stream 流
創建方式一:通過數組創建 Stream 流
創建方式一:通過 Stream 流的靜態方法 of( ) 創建
創建方式一:由函數創建流:創建無限流

	/**
     * 通過集合創建 Stream 流
     */
    @Test
    public void createStreamByCollection() {
        List<String> list = Arrays.asList("aaa","bbb","ccc");
        // 1.串列流
        Stream<String> stream = list.stream();

        // 2.並行流
        Stream<String> parallelStream = list.parallelStream();

    }

    /**
     * 通過數組創建 Stream 流
     */
    @Test
    public void createStreamByArray() {
        int[] arr = new int[]{1,2,3,4,5,6};
        // 調用 Arrays 類的靜態方法 stream
        IntStream stream = Arrays.stream(arr);

    }


    /**
     * 通過 Stream 流的 靜態方法of() 創建
     */
    @Test
    public void createStreamByOf() {
        Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);

    }


    /**
     * 由函數創建流:創建無限流
     */
    @Test
    public void createStream() {
        Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
        // 迭代
        //public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
        Stream.iterate(0,t -> t + 2).limit(10).forEach(System.out::println);

        // 生成
        // public static<T> Stream<T> generate(Supplier<T> s)
        Stream.generate(Math::random).limit(10).forEach(System.out::println);

    }

5、Stream 流的中間操作

篩選與切片

  • filter(Predicate p) 接收Lambda,從流中排除某些元素。
  • limit(n) 截斷流,使其元素不超過給定數量。
  • skip(n) 跳過元素,返回一個扔掉了前n個元素的流。若流中元素不足n個,則返回一個空流。與limit()互補。
  • distinct() 篩選,通過流所生成元素的hashCode()和equals()去除重覆元素。
public class StreamAPITest2 {

	/**
     * 篩選與切片
     */
    @Test
    public void test() {

        List<String> list = Arrays.asList("李華","小明","小紅","小王","李華");
        System.out.println(list);
        System.out.println("*************************");


        // filter(Predicate p) 接收Lambda,從流中排除某些元素。
        List<String> filterList = list.stream().filter(e -> !e.equals("李華")).collect(Collectors.toList());
        System.out.println(filterList);
        System.out.println("*************************");


        // limit(n) 截斷流,使其元素不超過給定數量。
        System.out.println(list.stream().limit(2).collect(Collectors.toList()));
        System.out.println("*************************");

        // skip(n) 跳過元素,返回一個扔掉了前n個元素的流。若流中元素不足n個,則返回一個空流。與limit()互補.
        System.out.println(list.stream().skip(2).collect(Collectors.toList()));
        System.out.println("*************************");

        // distinct() 篩選,通過流所生成元素的hashCode()和equals()去除重覆元素
        System.out.println(list);
        System.out.println(list.stream().distinct().collect(Collectors.toList()));
        System.out.println("*************************");

    }
}

image.png

映射

  • **map(Function f) **:接收一個函數作為參數,將元素轉換成其他形式或提取信息,該函數會被應用到每個元素上,並將其映射成一個新的元素。
  • flatMap(Function f) :接收一個函數作為參數,將流中的每個值都換成另一個流,然後把所有流連接成一個流。
  • mapToDouble(ToDoubleFunction f):接收一個函數作為參數,該函數會被應用到每個元素上,產生一個新的 DoubleStream。
  • mapToInt(ToIntFunction f):接收一個函數作為參數,該函數會被應用到每個元素上,產生一個新的 IntStream。
  • mapToLong(ToLongFunction f):接收一個函數作為參數,該函數會被應用到每個元素上,產生一個新的 LongStream。

	/**
     * map 與 flatMap映射
     */
    @Test
    public void testMap() {
        List<String> list = Arrays.asList("aa", "bb", "cc", "dd");

        // map(Function f)  接收一個函數作為參數,將元素轉換成其他形式或提取信息,
        // 該函數會被應用到每個元素上,並將其映射成一個新的元素。
        // System.out.println(list.stream().map(str -> str.toUpperCase()).collect(Collectors.toList()));

        System.out.println(list.stream().map(String::toUpperCase).collect(Collectors.toList()));

        // flatMap(Function f)  接收一個函數作為參數,將流中的每個值都換成另一個流,然後把所有流連接成一個流。
        // 未使用 flatMap
        Stream<Stream<Character>> streamStream = list.stream().map(StreamAPITest2::formStringToStream);
        streamStream.forEach(s -> {
            s.forEach(System.out::println);
        });
        System.out.println("****************************");
        // 使用 flatMap
        Stream<Character> characterStream = list.stream().flatMap(StreamAPITest2::formStringToStream);
        characterStream.collect(Collectors.toList()).forEach(System.out::println);

    }

    public static Stream<Character> formStringToStream(String str) {
        List<Character> list = new ArrayList<>();
        char[] chars = str.toCharArray();
        for (char c : chars) {
            list.add(c);
        }
        return list.stream();
    }

image.png

排序

  • sorted() 自然排序
  • sorted(Comparator com) 定製排序
 /**
     * 排序
     */
    @Test
    public void testSort() {
        // sorted() 自然排序
        List<Integer> integerList = Arrays.asList(12, 23, -12, 32, 55, 66, 11);
        integerList.stream().sorted().forEach(System.out::println);
        System.out.println("***************************");

        // sorted(Comparator com) 定製排序
        integerList.stream().sorted( (x,y) -> Integer.compare(y,x)).forEach(System.out::println);
    }

image.png

6、Stream 的終止操作

查找與匹配

  • allMatch(Predicate p) :檢查是否匹配所有元素
  • anyMatch(Predicate p):檢查是否至少匹配一個元素
  • noneMatch(Predicate p):檢查是否沒有匹配所有元素
  • findFirst():返回第一個元素
  • findAny():返回當前流中的任意元素
  • count():返迴流中元素總數
  • max(Comparator c):返迴流中最大值
  • min(Comparator c):返迴流中最小值
  • forEach(Consumer c) 內部迭代(使用 Collection 介面需要用戶去做迭代,稱為外部迭代。相反,Stream API 使用內部迭代——它幫你把迭代做了)

歸約

  • reduce(T t, BinaryOperator b) 可以將流中元素反覆結合起來,得到一個值。返回 T
  • reduce(BinaryOperator b) 可以將流中元素反覆結合起來,得到一個值。返回 Optional
	/**
     * 終止操作 : 歸約
     */
    @Test
    public void testReduce() {
        List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
        // reduce(T t, BinaryOperator b) 可以將流中元素反覆結合起來,得到一個值。返回 T
        System.out.println(integerList.stream().reduce(0, Integer::sum));


        // reduce(BinaryOperator b) 可以將流中元素反覆結合起來,得到一個值。返回 Optional<T>
        // System.out.println(integerList.stream().reduce((a, b) -> a + b));
        System.out.println(integerList.stream().reduce(Integer::sum));

    }

image.png

收集

  • collect(Collector c):將流轉換為其他形式。接收一個 Collector介面的實現,用於給Stream中元素做彙總的方法
/**
* 終止操作 : 收集
*/
@Test
    public void testCollect() {
    List<Integer> integerList = Arrays.asList(1, 2, 2, 2, 3, 4);
    // toList
    System.out.println(integerList.stream().skip(1).collect(Collectors.toList()));

    // toSet
    System.out.println(integerList.stream().skip(1).collect(Collectors.toSet()));

}

image.png

五、Optional 類

Optional 類(java.util.Optional) 是一個容器類,代表一個值存在或不存在,原來用 null 表示一個值不存在,現在 Optional 可以更好的表達這個概念。並且可以避免空指針異常。

常用方法

Optional.of(T t) : 創建一個 Optional 實例
Optional.empty() : 創建一個空的 Optional 實例
Optional.ofNullable(T t):若 t 不為 null,創建 Optional 實例,否則創建空實例
isPresent() : 判斷是否包含值
orElse(T t) : 如果調用對象包含值,返回該值,否則返回t
orElseGet(Supplier s) :如果調用對象包含值,返回該值,否則返回 s 獲取的值
map(Function f): 如果有值對其處理,並返回處理後的Optional,否則返回 Optional.empty()
flatMap(Function mapper):與 map 類似,要求返回值必須是Optional

本文來自博客園,作者:自律即自由-,轉載請註明原文鏈接:https://www.cnblogs.com/deyo/p/17638586.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、背景 會員系統是一種基礎系統,跟公司所有業務線的下單主流程密切相關。如果會員系統出故障,會導致用戶無法下單,影響範圍是全公司所有業務線。所以,會員系統必須保證高性能、高可用,提供穩定、高效的基礎服務。 隨著同程和藝龍兩家公司的合併,越來越多的系統需要打通同程APP、藝龍APP、同程微信小程式、藝 ...
  • ### 歡迎訪問我的GitHub > 這裡分類和彙總了欣宸的全部原創(含配套源碼):[https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos) ### 本篇概覽 - 本文是《quarkus資料庫篇》系列的第 ...
  • 函數的功能是從輸入的字元串切片中去除重覆的元素,並返回去重後的結果。具體的實現邏輯如下: 創建一個空的結果切片result,用於存儲去重後的字元串。 創建一個臨時的maptempMap,用於存放不重覆的字元串。map的鍵是字元串,值是位元組類型。 遍歷輸入的字元串切片slc中的每個元素e: 首先,獲取 ...
  • 位元組碼指令簡介 Java虛擬機的指令由一個位元組長度的、代表著某種特定操作含義的數字(稱為操作碼,Opcode)以及跟隨其後的零至多個代表此操作所需參數(稱為操作數,Operands)而構成。由於Java虛擬機採用面向操作數棧而不是寄存器的架構(這兩種架構的區別和影響將在第8章中探討),所以大多數的指 ...
  • # C++ 函數參數匹配 ## 1 單個參數匹配 ```C++ void f(); //f1 void f(int); //f2 void f(int, int); //f3 void f(double, double=3.14);//f4 int main() { f(5.6); //調用f4 r ...
  • > 本篇文章結合筆者的經歷,介紹一種通過**重寫QTreeView繪製事件**,使用**QPainter**來實現好看的列表的方式。 ## 導語 Hi🐇,各位讀者朋友,大家好。相信大家在日常的工作中,經常會接觸到**QTreeView**這個控制項吧! **QTreeView**,顧名思義,就是一種 ...
  • 預設你已經看了我的Mybatis-Plus+Mysql的教程,現在有了一個簡單的項目如下(之前的教程: https://www.cnblogs.com/leafstar/p/17638741.html) 1.下載nacao,我這裡下的是2.1.0版本 提供一下我用的版本( 鏈接:https://pa ...
  • 本文通過簡單的示例代碼和說明,讓讀者能夠瞭解Mybatis-Plus+Mysql的簡單使用 必須說明的是,本文有部分內容是為了後續的微服務寫的,所以如果只想用Mybatis-Plus的話,直接使用bank1項目即可 1.新建父項目,選用spring initializr即可,可以刪除其他文件,僅僅留 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...