JavaRush /Blog Java /Random-VI /Bản dịch của cuốn sách. Lập trình chức năng trong Java. C...
timurnav
Mức độ

Bản dịch của cuốn sách. Lập trình chức năng trong Java. Chương 1

Xuất bản trong nhóm
Tôi sẽ sẵn lòng giúp bạn tìm ra lỗi và cải thiện chất lượng bản dịch. Tôi dịch để nâng cao khả năng tiếng Anh của mình, và nếu bạn đọc và tìm lỗi dịch thuật thì bạn sẽ còn tiến bộ hơn tôi. Tác giả cuốn sách viết rằng cuốn sách có rất nhiều kinh nghiệm làm việc với Java; thành thật mà nói, bản thân tôi không có nhiều kinh nghiệm nhưng tôi hiểu tài liệu trong cuốn sách. Cuốn sách đề cập đến một số lý thuyết khó giải thích bằng ngón tay. Nếu có bài viết hay trên wiki, tôi sẽ cung cấp liên kết đến chúng, nhưng để hiểu rõ hơn, tôi khuyên bạn nên tự tìm trên Google. chúc mọi người may mắn. :) Đối với những ai muốn sửa bản dịch của tôi, cũng như những ai thấy tiếng Nga quá kém để đọc, bạn có thể tải xuống sách gốc tại đây . Nội dung Chương 1 Xin chào, Biểu thức Lambda - hiện đang đọc Chương 2 Sử dụng Bộ sưu tập - đang phát triển Chương 3 Chuỗi, Bộ so sánh và Bộ lọc - đang phát triển Chương 4 Phát triển với Biểu thức Lambda - đang phát triển Chương 5 Làm việc với Tài nguyên - đang phát triển Chương 6 Lười biếng - đang phát triển phát triển Chương 7 Tối ưu hóa tài nguyên - đang phát triển Chương 8 Bố cục với biểu thức lambda - đang phát triển Chương 9 Kết hợp tất cả lại với nhau - đang phát triển

Chương 1 Xin chào, Biểu thức Lambda!

Mã Java của chúng tôi đã sẵn sàng cho những chuyển đổi đáng chú ý. Các công việc hàng ngày chúng ta thực hiện trở nên đơn giản hơn, dễ dàng hơn và mang tính biểu cảm hơn. Cách lập trình Java mới đã được sử dụng trong nhiều thập kỷ ở các ngôn ngữ khác. Với những thay đổi này đối với Java, chúng ta có thể viết mã ngắn gọn, trang nhã, biểu cảm với ít lỗi hơn. Chúng ta có thể sử dụng điều này để dễ dàng áp dụng các tiêu chuẩn và triển khai các mẫu thiết kế phổ biến với ít dòng mã hơn. Trong cuốn sách này, chúng ta khám phá phong cách lập trình chức năng bằng cách sử dụng các ví dụ đơn giản về các vấn đề mà chúng ta gặp phải hàng ngày. Trước khi đi sâu vào phong cách tao nhã và cách phát triển phần mềm mới này, hãy xem tại sao nó lại tốt hơn.
Thay đổi suy nghĩ của bạn
Phong cách mệnh lệnh là những gì Java đã mang lại cho chúng ta kể từ khi ngôn ngữ này ra đời. Phong cách này gợi ý rằng chúng ta mô tả cho Java từng bước mà chúng ta muốn ngôn ngữ thực hiện và sau đó chúng ta chỉ cần đảm bảo rằng các bước đó được tuân thủ một cách trung thực. Điều này hoạt động rất tốt, nhưng nó vẫn ở mức độ thấp. Mã cuối cùng quá dài dòng và chúng tôi thường muốn có một ngôn ngữ thông minh hơn một chút. Sau đó, chúng ta có thể nói điều đó một cách tuyên bố - những gì chúng ta muốn và không đi sâu vào cách thực hiện điều đó. Nhờ các nhà phát triển, Java giờ đây có thể giúp chúng tôi thực hiện điều này. Hãy xem xét một vài ví dụ để hiểu lợi ích và sự khác biệt giữa các phương pháp này.
Cách thông thường
Hãy bắt đầu với những điều cơ bản quen thuộc để xem hai mô hình hoạt động như thế nào. Điều này sử dụng một phương pháp bắt buộc để tìm kiếm Chicago trong bộ sưu tập các thành phố - danh sách trong cuốn sách này chỉ hiển thị các đoạn mã. boolean found = false; for(String city : cities) { if(city.equals("Chicago")) { found = true; break; } } System.out.println("Found chicago?:" + found); Phiên bản bắt buộc của mã rất ồn ào (từ này có liên quan gì đến nó?) và ở cấp độ thấp, có một số phần có thể thay đổi. Đầu tiên, chúng tôi tạo cờ boolean hôi thối này được gọi là đã tìm thấy và sau đó chúng tôi lặp lại từng phần tử trong bộ sưu tập. Nếu chúng tôi tìm thấy thành phố mà chúng tôi đang tìm kiếm, chúng tôi đặt cờ thành đúng và phá vỡ vòng lặp. Cuối cùng, chúng tôi in kết quả tìm kiếm của mình ra bảng điều khiển.
Có một cách tốt hơn
Là những lập trình viên Java tinh ý, chỉ cần nhìn thoáng qua mã này có thể biến nó thành thứ gì đó biểu cảm hơn và dễ đọc hơn, như sau: System.out.println("Found chicago?:" + cities.contains("Chicago")); Đây là một ví dụ về kiểu khai báo - phương thức contains() giúp chúng ta đi thẳng đến những gì chúng ta cần.
Những thay đổi thực tế
Những thay đổi này sẽ mang lại nhiều cải tiến cho mã của chúng tôi:
  • Không phiền phức với các biến có thể thay đổi
  • Lặp lại vòng lặp được ẩn dưới mui xe
  • Ít lộn xộn mã hơn
  • Mã rõ ràng hơn, tập trung sự chú ý
  • Trở kháng ít hơn; mã theo sát mục đích kinh doanh
  • Ít khả năng xảy ra lỗi hơn
  • Dễ hiểu và hỗ trợ hơn
Ngoài những trường hợp đơn giản
Đây là một ví dụ đơn giản về hàm khai báo để kiểm tra sự hiện diện của một phần tử trong một bộ sưu tập; hàm này đã được sử dụng từ lâu trong Java. Bây giờ hãy tưởng tượng bạn không cần phải viết mã bắt buộc cho các hoạt động nâng cao hơn như phân tích tệp, làm việc với cơ sở dữ liệu, đưa ra yêu cầu cho dịch vụ web, tạo đa luồng, v.v. Giờ đây, Java cho phép viết mã ngắn gọn, tinh tế giúp khó mắc lỗi hơn, không chỉ trong các thao tác đơn giản mà trong toàn bộ ứng dụng của chúng ta.
Cách cũ
Hãy xem một ví dụ khác. Chúng tôi đang tạo một bộ sưu tập có giá và sẽ thử một số cách để tính tổng của tất cả các mức giá đã chiết khấu. Giả sử chúng ta được yêu cầu tổng hợp tất cả các mức giá có giá trị vượt quá 20 USD, với mức chiết khấu 10%. Đầu tiên chúng ta hãy làm điều này theo cách Java thông thường. Mã này hẳn rất quen thuộc với chúng ta: đầu tiên chúng ta tạo một biến có thể thay đổi TotalOfDiscountedPrices để lưu giá trị kết quả vào đó. Sau đó, chúng tôi lặp lại việc thu thập giá, chọn các mức giá trên 20 USD, lấy giá đã chiết khấu và thêm giá trị đó vào TotalOfDiscountedPrices . Cuối cùng, chúng tôi hiển thị tổng của tất cả các mức giá có tính đến khoản chiết khấu. Dưới đây là những gì được xuất ra bàn điều khiển final List prices = Arrays.asList( new BigDecimal("10"), new BigDecimal("30"), new BigDecimal("17"), new BigDecimal("20"), new BigDecimal("15"), new BigDecimal("18"), new BigDecimal("45"), new BigDecimal("12")); BigDecimal totalOfDiscountedPrices = BigDecimal.ZERO; for(BigDecimal price : prices) { if(price.compareTo(BigDecimal.valueOf(20)) > 0) totalOfDiscountedPrices = totalOfDiscountedPrices.add(price.multiply(BigDecimal.valueOf(0.9))); } System.out.println("Total of discounted prices: " + totalOfDiscountedPrices);
Tổng giá đã giảm: 67,5
Nó hoạt động, nhưng mã trông lộn xộn. Nhưng đó không phải lỗi của chúng tôi, chúng tôi đã sử dụng những gì có sẵn. Mã ở mức độ khá thấp - nó bị ám ảnh bởi những thứ nguyên thủy (google nó, những thứ thú vị) và nó đi ngược lại nguyên tắc trách nhiệm duy nhất . Những người làm việc tại nhà như chúng ta nên để những đoạn mã như vậy tránh xa tầm mắt của những đứa trẻ đang khao khát trở thành lập trình viên, nó có thể cảnh báo tâm trí mỏng manh của chúng, hãy chuẩn bị cho câu hỏi “Đây có phải là điều bạn phải làm để tồn tại?”
Có một cách tốt hơn, một cách khác
Bây giờ chúng ta có thể làm tốt hơn, tốt hơn nhiều. Mã của chúng tôi có thể giống với yêu cầu đặc tả. Điều này sẽ giúp chúng tôi giảm khoảng cách giữa nhu cầu kinh doanh và mã triển khai chúng, đồng thời giảm hơn nữa khả năng các yêu cầu bị hiểu sai. Thay vì tạo một biến và sau đó thay đổi nó nhiều lần, hãy làm việc ở mức độ trừu tượng cao hơn, chẳng hạn như trong danh sách sau. final BigDecimal totalOfDiscountedPrices = prices.stream() .filter(price -> price.compareTo(BigDecimal.valueOf(20)) > 0) .map(price -> price.multiply(BigDecimal.valueOf(0.9))) .reduce(BigDecimal.ZERO, BigDecimal::add); System.out.println("Total of discounted prices: " + totalOfDiscountedPrices); Hãy đọc to - bộ lọc giá lớn hơn 20, ánh xạ (tạo cặp "khóa" "giá trị") bằng cách sử dụng khóa "giá", giá bao gồm chiết khấu và sau đó thêm chúng
- nhận xét của người dịch có nghĩa là những từ xuất hiện trong đầu bạn khi đọc mã .filter(price -> price.compareTo(BigDecimal.valueOf(20)) > 0)
Mã được thực thi cùng nhau theo cùng một trình tự logic như chúng ta đã đọc. Mã đã được rút ngắn nhưng chúng tôi đã sử dụng rất nhiều thứ mới từ Java 8. Đầu tiên, chúng tôi gọi phương thức stream() trên bảng giá . Điều này mở ra cơ hội cho một trình vòng lặp tùy chỉnh với nhiều tính năng tiện lợi mà chúng ta sẽ thảo luận sau. Thay vì lặp trực tiếp qua tất cả các giá trị trong bảng giá , chúng tôi sử dụng một số phương pháp đặc biệt như filter()map() . Không giống như các phương thức chúng tôi đã sử dụng trong Java và JDK, các phương thức này lấy hàm ẩn danh - biểu thức lambda - làm tham số trong dấu ngoặc đơn. Chúng ta sẽ nghiên cứu nó chi tiết hơn sau. Bằng cách gọi phương thức less() , chúng ta tính tổng các giá trị (giá chiết khấu) thu được trong phương thức map() . Vòng lặp được ẩn giống như khi sử dụng phương thức contains() . Tuy nhiên, các phương thức filter()map() thậm chí còn phức tạp hơn. Đối với mỗi mức giá trong bảng giá , họ gọi hàm lambda đã truyền và lưu nó vào bộ sưu tập mới. Phương thức less() được gọi trên bộ sưu tập này để tạo ra kết quả cuối cùng. Dưới đây là những gì được xuất ra bàn điều khiển
Tổng giá đã giảm: 67,5
Thay đổi
Dưới đây là những thay đổi liên quan đến phương pháp thông thường:
  • Mã này vừa mắt và không lộn xộn.
  • Không có hoạt động cấp thấp
  • Dễ dàng cải thiện hoặc thay đổi logic hơn
  • Việc lặp lại được điều khiển bởi một thư viện các phương thức
  • Đánh giá vòng lặp lười biếng , hiệu quả
  • Dễ dàng song song hơn khi cần thiết
Sau này chúng ta sẽ thảo luận về cách Java cung cấp những cải tiến này.
Lambda để giải cứu :)
Lambda là chìa khóa chức năng giúp chúng ta thoát khỏi những rắc rối khi lập trình mệnh lệnh. Bằng cách thay đổi cách chúng ta lập trình, với các tính năng mới nhất của Java, chúng ta có thể viết mã không chỉ thanh lịch và ngắn gọn mà còn ít xảy ra lỗi hơn, hiệu quả hơn và dễ dàng tối ưu hóa, cải thiện và tạo đa luồng hơn.
Giành chiến thắng lớn từ lập trình chức năng
Kiểu lập trình chức năng có tỷ lệ tín hiệu trên tạp âm cao hơn ; Chúng tôi viết ít dòng mã hơn nhưng mỗi dòng hoặc biểu thức thực hiện nhiều chức năng hơn. Chúng tôi thu được rất ít từ phiên bản chức năng của mã so với mệnh lệnh:
  • Chúng tôi đã tránh những thay đổi không mong muốn hoặc việc gán lại các biến, vốn là nguyên nhân gây ra lỗi và gây khó khăn cho việc xử lý mã từ các luồng khác nhau cùng một lúc. Trong phiên bản bắt buộc, chúng ta đặt các giá trị khác nhau cho biến TotalOfDiscountedPrices trong suốt vòng lặp . Trong phiên bản chức năng, không có thay đổi rõ ràng nào về biến trong mã. Ít thay đổi hơn dẫn đến ít lỗi hơn trong mã.
  • Phiên bản chức năng của mã dễ dàng song song hơn. Ngay cả khi các phép tính trong phương thức map() dài, chúng ta có thể chạy chúng song song mà không sợ bất cứ điều gì. Nếu chúng ta truy cập mã kiểu mệnh lệnh từ các luồng khác nhau, chúng ta sẽ cần phải lo lắng về việc thay đổi biến TotalOfDiscountedPrices cùng một lúc . Trong phiên bản chức năng, chúng tôi chỉ truy cập vào biến sau khi tất cả các thay đổi đã được thực hiện, điều này giúp chúng tôi không phải lo lắng về tính an toàn của luồng của mã.
  • Mã có tính biểu cảm cao hơn. Thay vì thực thi mã theo một số bước - tạo và khởi tạo một biến có giá trị giả, lặp qua danh sách giá, thêm giá chiết khấu vào biến, v.v. - chúng ta chỉ cần yêu cầu phương thức map() của danh sách trả về một danh sách khác giá chiết khấu và cộng chúng lại.
  • Kiểu chức năng ngắn gọn hơn: cần ít dòng mã hơn phiên bản mệnh lệnh. Mã nhỏ gọn hơn có nghĩa là viết ít hơn, ít đọc hơn và dễ bảo trì hơn.
  • Phiên bản chức năng của mã rất trực quan và dễ hiểu khi bạn biết cú pháp của nó. Phương thức map() áp dụng hàm được truyền (tính giá chiết khấu) cho từng thành phần của bộ sưu tập và tạo ra một bộ sưu tập có kết quả, như chúng ta có thể thấy trong hình ảnh bên dưới.

Hình 1 - phương thức bản đồ áp dụng hàm được truyền cho từng phần tử của bộ sưu tập
Với sự hỗ trợ của biểu thức lambda, chúng ta có thể khai thác tối đa sức mạnh của phong cách lập trình chức năng trong Java. Nếu nắm vững phong cách này, chúng ta có thể tạo mã ngắn gọn hơn, biểu cảm hơn với ít thay đổi và lỗi hơn. Trước đây, một trong những ưu điểm chính của Java là hỗ trợ mô hình hướng đối tượng. Và phong cách chức năng không mâu thuẫn với OOP. Sự xuất sắc thực sự trong việc chuyển từ lập trình mệnh lệnh sang lập trình khai báo. Với Java 8 chúng ta có thể kết hợp lập trình chức năng với phong cách hướng đối tượng khá hiệu quả. Chúng ta có thể tiếp tục áp dụng kiểu OO cho các đối tượng, phạm vi, trạng thái và mối quan hệ của chúng. Ngoài ra, chúng ta có thể mô hình hóa hành vi và trạng thái thay đổi, quy trình kinh doanh và xử lý dữ liệu dưới dạng một chuỗi các bộ chức năng.
Tại sao mã theo phong cách chức năng?
Chúng ta đã thấy những lợi ích tổng thể của phong cách lập trình chức năng, nhưng liệu phong cách mới này có đáng học không? Đây sẽ là một thay đổi nhỏ trong ngôn ngữ hay nó sẽ thay đổi cuộc sống của chúng ta? Chúng ta phải tìm được câu trả lời cho những câu hỏi này trước khi lãng phí thời gian và sức lực của mình. Viết mã Java không khó lắm; cú pháp của ngôn ngữ rất đơn giản. Chúng tôi cảm thấy thoải mái với các thư viện và API quen thuộc. Điều thực sự đòi hỏi chúng ta phải nỗ lực viết và duy trì mã là các ứng dụng Doanh nghiệp điển hình mà chúng ta sử dụng Java để phát triển. Chúng ta cần đảm bảo rằng các lập trình viên đồng nghiệp đóng các kết nối với cơ sở dữ liệu vào đúng thời điểm, họ không giữ nó hoặc thực hiện các giao dịch lâu hơn mức cần thiết, rằng họ nắm bắt được các ngoại lệ đầy đủ và ở mức chính xác, rằng họ áp dụng và giải phóng các khóa đúng cách. ... tờ này có thể được tiếp tục trong một thời gian rất dài. Riêng mỗi lập luận trên đều không có trọng lượng, nhưng khi kết hợp với sự phức tạp trong việc triển khai vốn có, nó sẽ trở nên quá sức, tốn thời gian và khó thực hiện. Điều gì sẽ xảy ra nếu chúng ta có thể gói gọn những sự phức tạp này thành những đoạn mã nhỏ có thể quản lý chúng tốt? Sau đó, chúng tôi sẽ không liên tục tốn sức lực để thực hiện các tiêu chuẩn. Điều này sẽ mang lại một lợi thế thực sự, vì vậy hãy xem phong cách chức năng có thể giúp ích như thế nào.
Joe hỏi
Mã ngắn* đơn giản có nghĩa là ít ký tự mã hơn phải không?
* chúng ta đang nói về từ súc tích , từ này mô tả kiểu chức năng của mã bằng cách sử dụng biểu thức lambda
Trong bối cảnh này, mã có nghĩa là ngắn gọn, không rườm rà và giảm tác động trực tiếp để truyền đạt ý định hiệu quả hơn. Đây là những lợi ích sâu rộng. Viết mã giống như kết hợp các thành phần lại với nhau: làm cho nó ngắn gọn giống như thêm nước sốt vào đó. Đôi khi phải mất nhiều công sức hơn để viết mã như vậy. Ít mã để đọc hơn nhưng nó làm cho mã trở nên minh bạch hơn. Điều quan trọng là phải giữ mã rõ ràng khi rút ngắn nó. Mã ngắn gọn giống như các thủ thuật thiết kế. Mã này yêu cầu ít khiêu vũ hơn với tambourine. Điều này có nghĩa là chúng ta có thể nhanh chóng thực hiện các ý tưởng của mình và tiếp tục nếu chúng hiệu quả và từ bỏ chúng nếu chúng không đáp ứng được mong đợi.
Lặp lại trên steroid
Chúng tôi sử dụng các trình vòng lặp để xử lý danh sách đối tượng cũng như làm việc với Bộ và Bản đồ. Các trình vòng lặp mà chúng ta sử dụng trong Java rất quen thuộc với chúng ta; mặc dù chúng còn nguyên thủy nhưng chúng không hề đơn giản. Chúng không chỉ tốn nhiều dòng mã mà còn khá khó viết. Làm cách nào để lặp qua tất cả các phần tử của một bộ sưu tập? Chúng ta có thể sử dụng vòng lặp for. Làm cách nào để chọn một số phần tử từ bộ sưu tập? Sử dụng cùng một vòng lặp for, nhưng sử dụng một số biến có thể thay đổi bổ sung cần được so sánh với nội dung nào đó trong bộ sưu tập. Sau đó, sau khi chọn một giá trị cụ thể, làm cách nào để chúng ta thực hiện các thao tác trên một giá trị duy nhất, chẳng hạn như giá trị tối thiểu, tối đa hoặc trung bình nào đó? Một lần nữa chu kỳ, một lần nữa các biến mới. Điều này gợi nhớ đến câu tục ngữ, bạn không thể nhìn thấy cây vì rừng (bản gốc sử dụng cách chơi chữ liên quan đến sự lặp lại và có nghĩa là “Mọi thứ đều được thực hiện, nhưng không phải mọi thứ đều thành công” - ghi chú của người dịch). jdk hiện cung cấp các trình vòng lặp nội bộ cho nhiều câu lệnh khác nhau: một để đơn giản hóa vòng lặp, một để liên kết phần phụ thuộc kết quả được yêu cầu, một để lọc các giá trị đầu ra, một để trả về các giá trị và một số hàm tiện lợi để lấy giá trị tối thiểu, tối đa, trung bình, v.v. Ngoài ra, chức năng của các hoạt động này có thể được kết hợp rất dễ dàng, do đó chúng ta có thể kết hợp các bộ khác nhau của chúng để triển khai logic nghiệp vụ dễ dàng hơn và ít mã hơn. Khi chúng ta hoàn thành, mã sẽ dễ hiểu hơn vì nó tạo ra giải pháp hợp lý theo trình tự mà vấn đề yêu cầu. Chúng ta sẽ xem xét một số ví dụ về mã như vậy trong Chương 2 và sau đó trong cuốn sách này.
Ứng dụng thuật toán
Các thuật toán thúc đẩy các ứng dụng doanh nghiệp. Ví dụ: chúng tôi cần cung cấp một hoạt động yêu cầu kiểm tra thẩm quyền. Chúng tôi sẽ phải đảm bảo rằng các giao dịch được hoàn thành nhanh chóng và việc kiểm tra được hoàn thành chính xác. Những nhiệm vụ như vậy thường được rút gọn thành một phương pháp rất thông thường, như trong danh sách dưới đây: Transaction transaction = getFromTransactionFactory(); //... Операция выполняющаяся во время транзакции... checkProgressAndCommitOrRollbackTransaction(); UpdateAuditTrail(); Có hai vấn đề với cách tiếp cận này. Đầu tiên, điều này thường dẫn đến nỗ lực phát triển tăng gấp đôi, từ đó dẫn đến tăng chi phí duy trì ứng dụng. Thứ hai, rất dễ bỏ sót các ngoại lệ có thể được đưa vào mã của ứng dụng này, do đó gây nguy hiểm cho việc thực hiện giao dịch và thông qua kiểm tra. Chúng ta có thể sử dụng khối thử cuối cùng thích hợp, nhưng mỗi khi ai đó chạm vào mã này, chúng ta sẽ cần kiểm tra lại xem tính logic của mã có bị hỏng hay không. Nếu không, chúng ta có thể từ bỏ nhà máy và đảo lộn toàn bộ mã nguồn của nó. Thay vì nhận các giao dịch, chúng ta có thể gửi mã xử lý đến một chức năng được quản lý tốt, chẳng hạn như mã bên dưới. runWithinTransaction((Transaction transaction) -> { //... Операция выполняющаяся во время транзакции... }); Những thay đổi nhỏ này sẽ mang lại khoản tiết kiệm rất lớn. Thuật toán kiểm tra trạng thái và kiểm tra ứng dụng được cung cấp một mức độ trừu tượng mới và được gói gọn bằng phương thức runWithinTransaction() . Trong phương pháp này, chúng tôi đặt một đoạn mã sẽ được thực thi trong ngữ cảnh giao dịch. Chúng ta không còn phải lo lắng về việc quên làm điều gì đó hoặc liệu chúng ta có bắt được ngoại lệ đúng chỗ hay không. Các chức năng thuật toán đảm nhiệm việc này. Vấn đề này sẽ được thảo luận chi tiết hơn ở Chương 5.
Phần mở rộng thuật toán
Các thuật toán đang được sử dụng ngày càng thường xuyên hơn, nhưng để chúng được sử dụng đầy đủ trong phát triển ứng dụng doanh nghiệp thì cần có các cách để mở rộng chúng.
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION