聊聊Java 8 Lambda 表達式

来源:https://www.cnblogs.com/linlinismine/archive/2018/07/09/9283532.html
-Advertisement-
Play Games

早在2014年oracle發佈了jdk 8,在裡面增加了lambda模塊。於是java程式員們又多了一種新的編程方式:函數式編程,也就是lambda表達式。我自己用lambda表達式也差不多快4年了,但在工作中卻鮮有看到同事使用這種編程方式,即使有些使用了,但感覺好像對其特性也不是很瞭解。我看了一上 ...


 

    早在2014年oracle發佈了jdk 8,在裡面增加了lambda模塊。於是java程式員們又多了一種新的編程方式:函數式編程,也就是lambda表達式。我自己用lambda表達式也差不多快4年了,但在工作中卻鮮有看到同事使用這種編程方式,即使有些使用了,但感覺好像對其特性也不是很瞭解。我看了一上網上的資料也不少,自己整理了一下順便寫下一些自己的看法,希望我的分享能帶給別人一些幫助。

 

    函數式編程基本概念入門

  •    什麼是函數式編程 

        函數式編程(英語:functional programming)或稱函數程式設計,又稱泛函編程,是一種編程典範,它將電腦運算視為數學上的函數計算,並且避免使用程式狀態以及易變對象。函數編程語言最重要的基礎是λ演算(lambda calculus)。而且λ演算的函數可以接受函數當作輸入(引數)和輸出(傳出值)。比起指令式編程,函數式編程更加強調程式執行的結果而非執行的過程,倡導利用若幹簡單的執行單元讓計算結果不斷漸進,逐層推導複雜的運算,而不是設計一個複雜的執行過程。這是維基百科給出的定義。從這個我們知道函數式編程是相對於指令式編程的一種編程典範,並且對而言具有一些優點。

  •   函數式編程的特性與優缺點

      特性

      1、函數是"第一等公民" 

         什麼是"第一等公民"?所謂"第一等公民"(first class),指的是函數與其他數據類型一樣,處於平等地位,它不僅擁有一切傳統函數的使用方式(聲明和調用),可以賦值給其他變數(賦值),也可以作為參數,傳入另一個函數(傳參),或者作為別的函數的返回值(返回)。函數可以作為參數進行傳遞,意味我們可以把行為"參數化",處理邏輯可以從外部傳入,這樣程式就可以設計得更靈活。

     2、沒有"副作用"

     所謂"副作用"(side effect),指的是函數內部與外部互動(最典型的情況,就是修改全局變數的值),產生運算以外的其他結果。函數式編程強調沒有"副作用",意味著函數要保持獨立,所有功能就是返回一個新的值,沒有其他行為,尤其是不得修改外部變數的值。

    3、引用透明

    引用透明(Referential transparency),指的是函數的運行不依賴於外部變數或"狀態",只依賴於輸入的參數,任何時候只要參數相同,引用函數所得到的返回值總是相同的。這裡強調了一點"輸入"不變則"輸出"也不變,就像數學函數裡面的f(x),只要輸入的x一樣那得到的結果也肯定定是一樣的。

      優點:

    1、代碼簡潔,開發快速。

     函數式編程大量使用函數,減少了代碼的重覆,因此程式比較短,開發速度較快。Paul Graham在《黑客與畫家》一書中寫道:同樣功能的程式,極端情況下,Lisp代碼的長度可能是C代碼的二十分之一。如果程式員每天所寫的代碼行數基本相同,這就意味著,"C語言需要一年時間完成開發某個功能,Lisp語言只需要不到三星期。反過來說,如果某個新功能,Lisp語言完成開發需要三個月,C語言需要寫五年。"當然,這樣的對比故意誇大了差異,但是"在一個高度競爭的市場中,即使開發速度只相差兩三倍,也足以使得你永遠處在落後的位置。" 

    2. 接近自然語言,易於理解

       函數式編程的自由度很高,可以寫出很接近自然語言的代碼。以java為例把學生以性別分組:

       沒用labmda表達式:       

Map<String,List<Student>> studentsMap = new HashMap<>();
        for(Student student : students){
            List<Student> studentList = studentsMap.getOrDefault(student.getSex(), new ArrayList<>());
            studentList.add(student);
            studentsMap.put(student.getSex(),studentList);
        }

  用了lambda表達式:

        Map<String,List<Student>> studentsMap = students.stream().collect(Collectors.groupingBy(Student::getSex));

       這基本就是自然語言的表達了,大家應該一眼就能明白它的意思吧。 

      3. 更方便的代碼管理

      函數式編程不依賴、也不會改變外界的狀態,只要給定輸入參數,返回的結果必定相同。因此,每一個函數都可以被看做獨立單元,很有利於進行單元測試(unit testing)和除錯(debugging),以及模塊化組合。 

      4. 易於"併發編程"       函數式編程不需要考慮"死鎖"(deadlock),因為它不修改變數,所以根本不存在"鎖"線程的問題。不必擔心一個線程的數據,被另一個線程修改,所以可以很放心地把工作分攤到多個線程,部署"併發編程"(concurrency)。      請看下麵的代碼:      var s1 = Op1();      var s2 = Op2();      var s3 = concat(s1, s2);      由於s1和s2互不幹擾,不會修改變數,誰先執行是無所謂的,所以可以放心地增加線程,把它們分配在兩個線程上完成。其他類型的語言就做不到這一點,因為s1可能會修改系統狀態,而s2可能會用到這些狀態,所以必須保證s2在s1之後運行,自然也就不能部署到其他線程上了。多核CPU是將來的潮流,所以函數式編程的這個特性非常重要。

      5. 代碼的熱升級

      函數式編程沒有副作用,只要保證介面不變,內部實現是外部無關的。所以,可以在運行狀態下直接升級代碼,不需要重啟,也不需要停機。Erlang語言早就證明瞭這一點,它是瑞典愛立信公司為了管理電話系統而開發的,電話系統的升級當然是不能停機的。

     缺點:

     1、函數式編程常被認為嚴重耗費在CPU和存儲器資源。主因有二:

  • 早期的函數式編程語言實現時並無考慮過效率問題。
  • 有些非函數式編程語言為求提升速度,不提供自動邊界檢查或自動垃圾回收等功能。
    惰性求值亦為語言如Haskell增加了額外的管理工作。          2、語言學習曲線陡峭,難度高

    函數式語言對開發者的要求比較高,學習曲線比較陡,而且很容易因為其靈活的語法控制不好程式的結構。

 

     介紹完函數式編程的概念和優缺點之後,下麵讓我們來進入java8 lambda的編程世界~

 

      Lambda表達式的組成

       java 8 中Lambda 表達式由三個部分組成:第一部分為一個括弧內用逗號分隔的形式參數,參數是函數式介面裡面方法的參數;第二部分為一個箭頭符號:->;第三部分為方法體,可以是表達式和代碼塊。語法如下

       1、方法體為表達式,該表達式的值作為返回值返回。

(parameters) -> expression
(int a,int b) -> return a + b; //求和

       2、方法體為代碼塊,必須用 {} 來包裹起來,且需要一個 return 返回值,但若函數式介面裡面方法返回值是 void,則無需返回值。

(parameters) -> { statements; }
(int a) -> {System.out.println("a = " + a);} //列印,無返回值
(int a) -> {return a * a;} //求平方

     

      Lambda表達式的底層實現

      java 8 內部Lambda 表達式的實現方式在本質是以匿名內部類的形式的實現的,看下麵代碼。代碼中我們定義了一個叫binaryOperator的Lambda表達式,看返回值它是一個IntBinaryOperator實例。  

IntBinaryOperator binaryOperator = (int a, int b) -> {
    return a + b;
};
int result = binaryOperator.applyAsInt(1, 2);
System.out.println("result = " + result); //3

    我們再看一下IntBinaryOperator的定義 

@FunctionalInterface
public interface IntBinaryOperator {
    /**
     * Applies this operator to the given operands.
     * @param left the first operand
     * @param right the second operand
     * @return the operator result
     */
    int applyAsInt(int left, int right);
}

       

     我們得知IntBinaryOperator是一個介面並且上面有一個@FunctionalInterface的註解,@FunctionalInterface標註了這是一個函數式介面,所以我們知道了(int a, int b) -> {return a + b;}返回的一個IntBinaryOperator的匿名實現類。

 

     Lambda表達式的函數式介面 

     上面提到了函數式介面,那這是一個什麼樣的概念呢?

      函數式介面(Functional Interface)是Java 8對一類特殊類型的介面的稱呼。這類介面只定義了唯一的抽象方法的介面(除了隱含的Object對象的公共方法,因此最開始也就做SAM類型的介面(Single Abstract Method)。定義函數式介面的原因是在Java Lambda的實現中,開發組不想再為Lambda表達式單獨定義一種特殊的Structural函數類型,稱之為箭頭類型(arrow type,依然想採用Java既有的類型(class, interface, method等).原因是增加一個結構化的函數類型會增加函數類型的複雜性,破壞既有的Java類型,並對成千上萬的Java類庫造成嚴重的影響。權衡利弊,因此最終還是利用SAM 介面作為 Lambda表達式的目標類型.另外對於函數式介面來說@FunctionalInterface並不是必須的,只要介面中只定義了唯一的抽象方法的介面那它就是一個實質上的函數式介面,就可以用來實現Lambda表達式。

       在java 8中已經為我們定義了很多常用的函數式介面它們都放在java.util.function包下麵,一般有以下常用的四大核心介面:       

函數式介面參數類型返回類型用途
Consumer<T>(消費型介面) T void 對類型為T的對象應用操作。void accept(T t)
Supplier<T>(供給型介面) T 返回類型為T的對象。 T get();
Function<T, R>(函數型介面) T R 對類型為T的對象應用操作並返回R類型的對象。R apply(T t);
Predicate<T>(斷言型介面) T boolean 確定類型為T的對象是否滿足約束。boolean test(T t);

 

    Lambda表達式的應用場景

     1、使用() -> {} 替代匿名類

 Thread t1 = new Thread(new Runnable() {
              @Override
              public void run() {
                  System.out.println("no use lambda");
              }
          });
         
Thread t2 = new Thread(() -> System.out.println("use lambda"));

  我們看到相對而言Lambda表達式要比匿名類要優雅簡潔很多~。

     2、以流水線的方式處理數據

        List<Integer> integers = Arrays.asList(4, 5, 6,1, 2, 3,7, 8,8,9,10);

        List<Integer> evens = integers.stream().filter(i -> i % 2 == 0)
                .collect(Collectors.toList()); //過濾出偶數列表 [4,6,8,8,10]
List<Integer> sortIntegers = integers.stream().sorted() .limit(5).collect(Collectors.toList());//排序並且提取出前5個元素 [1,2,3,4,5] List<Integer> squareList = integers.stream().map(i -> i * i).collect(Collectors.toList());//轉成平方列表 int sum = integers.stream().mapToInt(Integer::intValue).sum();//求和 Set<Integer> integersSet = integers.stream().collect(Collectors.toSet());//轉成其它數據結構比如set Map<Boolean, List<Integer>> listMap = integers.stream().collect(Collectors.groupingBy(i -> i % 2 == 0)); //根據奇偶性分組 List<Integer> list = integers.stream().filter(i -> i % 2 == 0).map(i -> i * i).distinct().collect(Collectors.toList());//複合操作

  藉助stream api和Lambda表達式,以住需要定義多個變數,編寫數十行甚至數百行的代碼的集合操作,現在都基本簡化成了可以在一行之內完成~

      3、更簡單的數據並行處理

        List<Integer> squareList = integers.stream().parallel().map(i -> i * i).collect(Collectors.toList());//轉成平方列表

    數據並行處理,只需要在原來的基礎上加一個parallel()就可以開啟~。順便提一下這裡parallel()開啟的底層並行框架是fork/join,預設的並行數是Ncpu個。

      4、用內部迭代取代外部迭代

       外部迭代:描述怎麼乾,代碼里嵌套2個以上的for迴圈的都比較難讀懂;只能順序處理List中的元素;

       內部迭代:描述要乾什麼,而不是怎麼乾;不一定需要順序處理List中的元素

List features = Arrays.asList("Lambdas", "Default Method", "Stream API", "Date and Time API");
for (String feature : features) {
    System.out.println(feature); //外部迭代
}

List features = Arrays.asList("Lambdas", "Default Method", "Stream API",
 "Date and Time API");
features.stream.forEach(n -> System.out.println(n)); //內部迭代

       5、重構現有臃腫代碼,更高的開發效率

      在Lambda表達式出現之前,我們的處理邏輯只能是以命令式編程的方式來實現,需要大量的代碼去編寫程式的每一步操作,定義非常多的變數,代碼量和工作量都相對的巨大。如果用Lambda表達式我們看到以往數十行甚至上百行的代碼都可以濃縮成幾行甚至一行代碼。這樣處理邏輯就會相對簡單,開發效率可以得到明顯提高,維護工作也相對容易。

 

       Lambda表達式中的Stream

        在java 8 中 Stream 不是集合元素,它不保存數據,它是有關演算法和計算的,它更像一個高級版本的 Iterator。原始版本的 Iterator,用戶只能顯式地一個一個遍歷元素並對其執行某些操作;高級版本的 Stream,用戶只要給出需要對其包含的元素執行什麼操作,比如 “過濾掉長度大於 10 的字元串”、“獲取每個字元串的首字母”等,Stream 會隱式地在內部進行遍歷,做出相應的數據轉換。

        Stream 就如同一個迭代器(Iterator),單向,不可往複,數據只能遍歷一次,遍歷過一次後即用盡了,就好比流水從面前流過,一去不復返。而和迭代器又不同的是,Stream 可以並行化操作,迭代器只能命令式地、串列化操作。顧名思義,當使用串列方式去遍歷時,每個 item 讀完後再讀下一個 item。而使用並行去遍歷時,數據會被分成多個段,其中每一個都在不同的線程中處理,然後將結果一起輸出。Stream 的並行操作依賴於 Java7 中引入的 Fork/Join 框架(JSR166y)來拆分任務和加速處理過程。

        Stream可以有限的也可以是無限的,流的構造方式有很多可以從常用的Collection(List,Array,Set and so on...),文件,甚至函數....

      由值創建流:

               Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");

       由數組創建流: 

               int[] numbers = {2, 3, 5, 7, 11, 13}; int sum = Arrays.stream(numbers).sum();       

       由文件創建流

               Stream<String> lines =Files.lines(Paths.get("data.txt"), Charset.defaultCharset())

       上面的這些Stream都是有限的,我們可以用函數來創建一個無限Stream

               Stream.iterate(0, n -> n + 2).forEach(System.out::println);

        Stream也很懶惰,它只會在你真正需要數據的時候才會把數據給傳給你,在你不需要時它一個數據都不會產生。

 

       Lambda表達式的Best Practice

        1、保持Lambda表達式簡短和一目瞭然

values.stream()
  .mapToInt(e -> {     
    int sum = 0;
    for(int i = 1; i <= e; i++) {
      if(e % i == 0) {
        sum += i;
      }
    }   
    return sum;
  })
  .sum());  //代碼複雜難懂 
values.stream()
  .mapToInt(e -> sumOfFactors(e))
  .sum() //代碼簡潔一目瞭然

  長長的Lambda表達式通常是危險的,因為代碼越長越難以讀懂,意圖看起來也不明,並且代碼也難以復用,測試難度也大。

      2、使用@FunctionalInterface 註解

         如果你確定了某個interface是用於Lambda表達式,請一定要加上@FunctionalInterface,表明你的意圖不然將來說不定某個不知情的家伙比如你旁邊的好基友,在這個interface上面加了另外一個抽像方法時,你的代碼就悲劇了。

      3、優先使用java.util.function包下麵的函數式介面

         java.util.function 這個包下麵提供了大量的功能性介面,可以滿足大多數開發人員為lambda表達式和方法引用提供目標類型的需求。每個介面都是通用的和抽象的,使它們易於適應幾乎任何lambda表達式。開發人員應該在創建新的功能介面之前研究這個包,避免重覆定義介面。另外一點就是,裡面的介面不會被別人修改~。

      4、不要在Lambda表達中執行有"副作用"的操作

        "副作用"是嚴重違背函數式編程的設計原則,在工作中我經常看到有人在forEach操作裡面操作外面的某個List或者設置某個Map這其實是不對的。

      5、不要把Lambda表達式和匿名內部類同等對待

         雖然我們可以用匿名內部類來實現Lambda表達式,也可以用Lambda表達式來替換內部類,但並不代表這兩者是等價的。這兩者在某一個重要概念是不同的:this指代的上下文是不一樣的。當您使用內部類時,它將創建一個新的範圍。通過實例化具有相同名稱的新局部變數,可以從封閉範圍覆蓋局部變數。您還可以在內部類中使用這個關鍵字作為它實例的引用。但是,lambda表達式可以使用封閉範圍。您不能在lambda的主體內覆蓋範圍內的變數

private String value = "Enclosing scope value";

public String scopeExperiment() {
    Foo fooIC = new Foo() {
        String value = "Inner class value";
 
        @Override
        public String method(String string) {
            return this.value;
        }
    };
    String resultIC = fooIC.method("");
 
    Foo fooLambda = parameter -> {
        String value = "Lambda value";
        return this.value;
    };
    String resultLambda = fooLambda.method("");
 
    return "Results: resultIC = " + resultIC + 
      ", resultLambda = " + resultLambda;
}

   運行上面這段代碼我們將到 resultIC = "Inner class value",resultLambda = "Enclosing scope value"。也就是說在匿名內部類中this指的是自身的引用,在Lambda表達式中this指的是外部。

       6、多使用方法引用

       在Lambda表達式中 a -> a.toLowerCase()和String::toLowerCase都能起到相同的作用,但兩者相比,後者通常可讀性更高並且代碼會簡短。

       7、儘量避免在Lambda的方法體中使用{}代碼塊

       優先使用

Foo foo = parameter -> buildString(parameter);
private String buildString(String parameter) {
    String result = "Something " + parameter;
    //many lines of code
    return result;
}

     而不是

Foo foo = parameter -> { String result = "Something " + parameter; 
    //many lines of code 
    return result; 
};

  8、不要盲目的開啟並行流

       Lambda的並行流雖好,但也要註意使用場景。如果平常的業務處理比如過濾,提取數據,沒有涉及特別大的數據和耗時操作,則真的不需要開啟並行流。我在工作中看到有些人一個只有幾十個元素的列表的過濾操作也開啟了並行流,其實這樣做會更慢。因為多行線程的開啟和同步這些花費的時間往往比你真實的處理時間要多很多。但一些耗時的操作比如I/O訪問,DB查詢,遠程調用,這些如果可以並行的話,則開啟並行流是可提升很大性能的。因為並行流的底層原理是fork/join,如果你的數據分塊不是很好切分,也不建議開啟並行流。舉個例子ArrayList的Stream可以開啟並行流,而LinkedList則不建議,因為LinkedList每次做數據切分要遍歷整個鏈表,這本身就已經很浪費性能,而ArrayList則不會。

   


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

-Advertisement-
Play Games
更多相關文章
  • 創建一個socketserver 至少分以下幾步 First, you must create a request handler class by subclassing the BaseRequestHandlerclass and overriding its handle() method; ...
  • 1、引言 點陣圖是使用位(bit)數組來對數據進行統計,排序和去重,其結構圖如下: 其中點陣圖的索引映射需要存儲的值,點陣圖索引所在位置的值表示索引對應的值是否已經存儲。 2、介面 3、實現 定義靜態byte數組常量,用於快速檢驗點陣圖上索引對應的值: 聲明欄位: 其中size為點陣圖的大小;當點陣圖的size ...
  • O’Reilly的電子書《Reactive Microservices Architecture》講述了微服務/分散式系統的一些設計原則,本文是筆者閱讀完此書後的理解。 微服務相比傳統的單體應用能夠帶來快速的響應,以小的系統產生大的影響。而隨著網路加速、磁碟成本降低、RAM成本降低、多核技術的發展、 ...
  • 1 /* 2 queue.h -- Queue介面 3 */ 4 5 #ifndef QUEUE_H 6 #define QUEUE_H 7 8 #define MAXQUEUE 10 9 10 typedef int Item; 11 12 typedef struct node 13 { 14 ...
  • 問題的提出: 俄羅斯方塊允許90度的坡,是不是有點不夠科學#(滑稽) 想辦法加一種會“滑坡”的方塊 本文兩大部分: 詳細的描繪是怎樣的“流動” 寫代碼,並整合進游戲 本文基於我寫的 俄羅斯方塊(一):簡版 事先上兩個動圖, 說明下我想做什麼 第一部分 首先是假象圖 這是一個長條逐漸“癱軟”的過程 歸 ...
  • 12、函數: 函數的功能: 定義:在真實的項目開發過程中,有些代碼會重覆利用,我們可以把它提出來,做成公共的代碼,供團隊來使用,這個我們封裝的代碼段,就是函數(功能)。 優點: 1、提高代碼的利用率。 2、減少開發時間。 3、減少代碼冗餘。 4、可維護性提高。 5、方便調試代碼。 函數的定義格式: ...
  • jdk目錄相關介紹: bin:存放的是java的相關開發工具 db:顧名思義jre附帶的輕量級資料庫 include:存放的是調用系統資源的介面文件 jre:java的運行環境 lib:核心的類庫 src.zip:java的開源代碼 JVM:指的是java虛擬機(作用:解釋class文件並且通知系統 ...
  • 參考了網路上各路大神的實現方法。主要使用了io.h庫 #include <iostream> #include <iostream> #include <cstring> #include <cstring> #include <io.h> #include <io.h> using namespa ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...