Kiểu giới hạn null và kiểu cho phép null
(bugs.openjdk.org)- Java đang chuẩn bị một tính năng ngôn ngữ preview gắn dấu nullness vào kiểu:
Foo!từ chốinull, cònFoo?cho phépnullmột cách có chủ ý Fookhông có dấu vẫn có nullness chưa chỉ định, tương thích với mã hiện có; việc chuyển đổi giữa các kiểu có nullness khác nhau có thể đi kèm cảnh báo hoặc kiểm tra lúc chạy- Các trường và mảng từ chối
nullkhông thể dùng giá trị mặc địnhnull, nên trường instance phải được gán xác định trước lời gọisuper(...), còn mảng cần giá trị khởi tạo cho component - Khi
nullbị thu hẹp thànhFoo!,NullPointerExceptionsẽ xảy ra; trên đường lưu vào mảng/trường, ArrayStoreException và FieldStoreException ngăn chặn việc làm ô nhiễm - Đây là nền tảng giúp các tối ưu như làm phẳng value class của Valhalla có thể tin cậy việc loại trừ
null; việc áp dụng cho thư viện chuẩn hay tự động diễn giải lại mã hiện có hiện không phải mục tiêu
Tính năng preview để chỉ rõ nullness trong kiểu Java
- Gắn dấu nullness vào kiểu Java để biểu diễn liệu tập giá trị của kiểu có bao gồm
nullhay không Foo!là kiểu null-restricted, loại trừnullkhỏi tập giá trịFoo?là kiểu nullable, chủ ý bao gồmnulltrong tập giá trịFookhông có dấu có nullness unspecified;nullcó thể xuất hiện, nhưng không biết sự hiện diện đó có phải là có chủ ý hay không- Tính năng này là tính năng preview, được bật bằng cờ biên dịch và runtime
--enable-preview
Mục tiêu và không phải mục tiêu
- Biểu diễn liệu một kiểu tham chiếu Java có kỳ vọng
nullhay không, đồng thời cung cấp cảnh báo và kiểm tra cho các chuyển đổi giữa những kiểu có khác biệt về nullness - Tương thích với mã Java hiện có, và phải có thể được đưa vào dần dần mà không tạo ra bất tương thích source hoặc binary
- Biến có kiểu từ chối
nullphải được khởi tạo trước khi được đọc lần đầu, và việc từ chốinullđược cưỡng chế lúc chạy ngay cả trong các lớp được biên dịch riêng - Cung cấp metadata và bảo đảm tính toàn vẹn để các tối ưu runtime như làm phẳng value class của Valhalla có thể tin cậy các kiểu giới hạn null
- Những hạng mục hiện không phải mục tiêu gồm
- Không tự động diễn giải lại mã hiện có
- Không biến mọi trường hợp
nullcó thể xảy ra thành lỗi biên dịch - Không thêm dạng nullable cho các kiểu nguyên thủy như
int - Không áp dụng phần mở rộng ngôn ngữ này cho thư viện chuẩn ở giai đoạn hiện tại
Vì sao cần thiết
- Một biến
Stringtrong Java có thể chứa tham chiếu tới đối tượngStringhoặcnull, nhưng ở cấp độ ngôn ngữ không có cách nào biểu diễn bên nào là chủ ý - Nhiều chương trình giả định không có
null, nhưng cần thêm công việc để cưỡng chế điều đó nhất quán trong đặc tả Javadoc và mã triển khai - Khi kỳ vọng này bị phá vỡ, giá trị
nullcó thể chảy trong mã triển khai rồi gây ngoại lệ ở một điểm rất xa lỗi gốc - Nếu lập trình viên biểu diễn ý định từ chối hoặc cho phép
nullnhư một phần của kiểu, phản hồi lúc biên dịch và kiểm tra lúc chạy sẽ giúp phát hiệnnullngoài dự kiến sớm hơn - Trong Valhalla, biến kiểu value class có thể được tối ưu thành biểu diễn làm phẳng của giá trị, nhưng nếu cần thêm bit để mã hóa
null, mức dùng bộ nhớ có thể tăng hoặc không thể tối ưu lưu trữ - Trong Amber, nullness của ứng viên pattern matching có thể ảnh hưởng tới việc đánh giá tính exhaustiveness của
switch, và nullness của type pattern có thể ảnh hưởng tới việc có matchnullhay không
Cú pháp dấu nullness và cấu trúc kiểu
- Nullness được xem là một phần nội tại của kiểu; vì
Foo?vàFookhác nullness nên chúng là các kiểu khác nhau - Cả kiểu mảng lẫn kiểu component của mảng đều có thể có dấu nullness
Foo?[]!là kiểu mà bản thân mảng là null-restricted, còn component làFoonullable- Với mảng nhiều chiều, có thể gắn dấu sau từng cặp ngoặc vuông, và theo quy ước được diễn giải từ trái sang phải, từ ngoài vào trong
- Kiểu được tham số hóa và đối số kiểu cũng có thể có dấu nullness
Predicate!<Foo?>làPredicatenull-restricted, còn đối số kiểu làFoonullable
- Để biểu diễn kiểu giới hạn null hoặc cho phép null,
!hoặc?phải xuất hiện tường minh trong source - Trong tương lai, có thể xem xét cách diễn giải mặc định mọi kiểu của một lớp hoặc đơn vị biên dịch là null-restricted và chỉ dùng
?làm ngoại lệ, nhưng chi tiết là công việc riêng
Quy tắc khởi tạo trường và mảng
- Trong Java hiện tại, giá trị mặc định của trường kiểu tham chiếu và component mảng là
null, nhưng giá trị đó không phù hợp làm giá trị ban đầu cho trường hoặc component mảng null-restricted - Trường và mảng null-restricted luôn phải được chương trình khởi tạo trước khi được đọc
- Nếu một trường instance null-restricted không có initializer, trường đó phải được gán xác định trước lời gọi
super(...)tường minh hoặc ngầm định của mỗi constructor- Flexible Constructor Bodies JEP cho phép viết mã khởi tạo cần thiết ở phần đầu constructor
- Trong early construction context này, không cho phép các hành động có nguy cơ tham chiếu
thishoặc đọc trường chưa được khởi tạo
- Nếu trường instance null-restricted có initializer, initializer đó được chạy ở đầu mỗi constructor trước lời gọi
super(...)- Constructor gọi
this(...)là trường hợp đặc biệt không chạy initializer, như các quy tắc hiện có
- Constructor gọi
- Trường static null-restricted phải được gán xác định trước khi tất cả static initializer và khối khởi tạo của lớp kết thúc
- Nếu lớp khác cố đọc trường đó trong khi lớp đang khởi tạo, kiểm tra runtime sẽ phát hiện việc đọc sớm và ném ngoại lệ
- Mảng có kiểu component null-restricted phải cung cấp giá trị khởi tạo cho từng component trong biểu thức tạo mảng
- Có thể liệt kê toàn bộ giá trị bằng array initializer
- Cú pháp rút gọn mới cũng có thể có, nhưng cú pháp hiện vẫn TBD
Nullness của biểu thức và chuyển đổi nullness
- Trình biên dịch Java xác định nullness của mọi biểu thức trong quá trình kiểm tra kiểu
- Nullness của tham chiếu biến đến từ khai báo biến, còn nullness của lời gọi phương thức đến từ kiểu trả về của phương thức được tham chiếu
- Literal
nulllà nullable - Hầu hết các biểu thức kiểu tham chiếu khác là null-restricted
- Bao gồm literal, nối chuỗi,
this, tạo instance lớp, tạo mảng, method reference và biểu thức lambda
- Bao gồm literal, nối chuỗi,
- Chuyển đổi nullness được cho phép trong ngữ cảnh gán, gọi và ép kiểu
- Chuyển đổi nullness mở rộng bao gồm
Foo!→Foo?Foo!→Foochưa chỉ địnhFoo?→Foochưa chỉ địnhFoochưa chỉ định →Foo?
- Chuyển đổi nullness thu hẹp bao gồm
Foo?→Foo!Foochưa chỉ định →Foo!
- Chuyển đổi nullness thu hẹp được trình biên dịch tự động thực hiện tương tự chuyển đổi unboxing, nhưng lúc chạy sẽ thực hiện kiểm tra động và có thể gây
NullPointerException - Cố trực tiếp chuyển literal
nullsang kiểu null-restricted là lỗi biên dịch
Kiểm tra runtime và ngoại lệ
- Nếu lúc chạy một giá trị
nullđược chuyển đổi nullness thu hẹp thành kiểu null-restricted, NullPointerException sẽ xảy ra - Chuyển đổi nullness thu hẹp không hiện tường minh trong source cũng có thể xảy ra trong khi thực thi runtime
- Mảng được gán kiểu component null-restricted sẽ từ chối giá trị
nulltrong kiểm tra lưu mảng thông thường, ngay cả khi trong source code nó được xử lý như một kiểu kém cụ thể hơn- Thất bại chuyển đổi này gây ArrayStoreException
- Nếu một trường vốn không null-restricted tại thời điểm biên dịch sau đó trở thành null-restricted qua biên dịch riêng, một field store check mới sẽ từ chối việc lưu
null- Thất bại chuyển đổi này gây FieldStoreException
- Trong lời gọi phương thức thuộc quan hệ overriding, có thể có chuyển đổi sang kiểu lời gọi của tham số phương thức cấp trên, rồi tiếp tục chuyển đổi sang kiểu tham số của phương thức override
- Giá trị trả về của phương thức cũng có thể được chuyển đổi sang kiểu trả về đã khai báo của phương thức, rồi chuyển tiếp sang kiểu trả về kỳ vọng tại điểm gọi
Generic, đối số kiểu và overriding
- Việc dùng biến kiểu cũng có thể gắn dấu nullness;
T!là kiểu null-restricted, cònT?là kiểu nullable - Các kiểu biến kiểu null-restricted và nullable khẳng định một nullness cụ thể bên trong mã generic
- Kiểu dùng làm đối số kiểu cũng có thể biểu diễn nullness; dấu nullness gắn trên kiểu biến kiểu sẽ ghi đè nullness đã khẳng định trong đối số kiểu
- Bên trong triển khai erased của API generic không thể cưỡng chế giới hạn null
- Tuy nhiên, các cast ngầm định thông thường xảy ra tại ranh giới API generic sẽ cưỡng chế đối số kiểu null-restricted lúc chạy
- Để đảm bảo khả năng tương tác, nullness bên trong đối số kiểu không bị cưỡng chế mạnh
Predicate<String!>có thể được chuyển đổi thànhPredicate<String>hoặcPredicate<String?>- unchecked nullness conversion như vậy có thể gây cảnh báo
- Thay đổi nullness của kiểu component mảng cũng được cho phép dưới dạng unchecked nullness conversion, và điều kiện nào sẽ kích hoạt kiểm tra runtime hiện là TBD
- Nullness bị bỏ qua khi xác định tính đồng nhất của chữ ký phương thức
- Một phương thức có thể override phương thức khác dù nullness của tham số và kiểu trả về không khớp
- Nếu các API khác nhau độc lập đưa vào dấu nullness, sự không khớp như vậy có thể phổ biến
- Nullness không ảnh hưởng tới tính áp dụng của phương thức và không thể gây thất bại suy luận đối số kiểu, nhưng có thể ảnh hưởng tới nullness được suy luận cho kiểu trả về của phương thức generic
- Chi tiết của thuật toán suy luận là TBD
Cảnh báo và lỗi của trình biên dịch
- Khi biến một kiểu thành null-restricted, có thể phát sinh lỗi biên dịch mới
- Khi trường hoặc mảng thuộc kiểu đó chưa được khởi tạo
- Khi cố chuyển literal
nullsang kiểu đó - Việc so sánh literal
nullvới biểu thức kiểu null-restricted cũng có thể trở thành lỗi biên dịch
- Trong các tình huống khác, phân tích nullness chỉ mang tính hỗ trợ và không tạo lỗi biên dịch
javaccung cấp cảnh báo để tránh lỗi runtime, và IDE cùng các công cụ phân tích khác cũng được khuyến nghị đi theo hướng này- Các nguyên nhân cảnh báo có thể có gồm
- Chuyển đổi nullness thu hẹp, bao gồm trường hợp đến từ kiểu
? - Dùng biểu thức kiểu
?cho truy cập member hoặc thao tác khác không thân thiện với null - Nullness của đối số kiểu không nhất quán với bound
- Nullness của tham số hoặc kiểu trả về phương thức không khớp với phương thức bị override
- Unchecked conversion làm thay đổi nullness của kiểu
- Chuyển đổi nullness thu hẹp, bao gồm trường hợp đến từ kiểu
Class file, reflection và thay đổi hỗ trợ
- Hầu hết việc dùng dấu null bị xóa trong file
class, còn các chuyển đổi runtime đi kèm được biểu diễn trực tiếp trong bytecode - Cú pháp thuộc tính
Signatuređược cập nhật để cho phép!và?bên trong kiểu - Nullness không được mã hóa trong descriptor của phương thức và trường
- Để ngăn ô nhiễm trường, thuộc tính
NullRestrictedmới đánh dấu rằng trường không cho phép giá trịnull- Trường này cũng phải được đánh dấu
ACC_STRICTvà phải được khởi tạo nghiêm ngặt - Verifier kiểm tra rằng tất cả trường instance khởi tạo nghiêm ngặt đã được gán tại thời điểm constructor gọi
super(...) - Mọi nỗ lực ghi vào trường đều kiểm tra giá trị
nullvà nếu phát hiện thì némFieldStoreException
- Trường này cũng phải được đánh dấu
- Tạo mảng null-restricted không được hỗ trợ bằng lệnh
anewarraymà phải thực hiện qua lời gọi reflection API - Không có literal
Foo!.classhayFoo?.class, và cũng không có instancejava.lang.Classtương ứng - RuntimeType API mới mô tả tập kiểu được cưỡng chế lúc chạy trong các kiểm tra lưu mảng và trường, bao gồm các biến thể null-restricted của mọi kiểu class và interface
- API
Fieldhỗ trợ truy vấnRuntimeTypecủa trường, và giá trị này có thể khác kết quả củagetType - API
Arrayhỗ trợ biến thểnewInstancebiểu diễn kiểu component bằngRuntimeType- Biến thể này cũng cho phép cung cấp giá trị khởi tạo cho component mảng
- Từ chối nỗ lực tạo mảng null-restricted không có giá trị khởi tạo
- Cơ chế deserialization truyền thống không tương thích với trường và mảng null-restricted; một JEP riêng dự kiến sẽ cung cấp cơ chế serialization không để lộ trường và mảng null-restricted chưa được khởi tạo
- Tài liệu do
javadoctạo ra bao gồm dấu nullness - API
java.lang.reflect.Typevàjavax.lang.modelmã hóa nullness trong biểu diễn kiểu
Phương án thay thế và quan hệ phụ thuộc
- Nhiều công cụ phát triển trong hệ sinh thái Java đã triển khai theo dõi null ở thời điểm biên dịch riêng, nhưng vì không thay đổi ngôn ngữ Java nên cú pháp chủ yếu bị giới hạn ở annotation và hành vi có thể tác động cũng bị giới hạn ở kiểm tra lúc biên dịch
- Các ngôn ngữ khác theo dõi nullness trong hệ thống kiểu, và nhiều ngôn ngữ mặc định là null-restricted, xem việc gán vào kiểu null-restricted mà không kiểm tra null tường minh là lỗi
- Trong Java, tính năng này phải là tùy chọn và có thể dùng dần dần mà không cần một đợt migration quy mô lớn duy nhất
- Việc cưỡng chế nullness lúc chạy cũng có thể được triển khai bằng kiểm tra tường minh hoặc lời gọi
Objects.requireNonNull- Tuy nhiên, việc áp dụng nhất quán rất phiền phức, cần thêm tài liệu hóa và làm giảm khả năng đọc của chương trình
- Không có cách áp dụng trực tiếp cho nơi lưu biến như trường và mảng
- Điều kiện tiên quyết là Flexible Constructor Bodies (Second Preview)
- Constructor có thể thực thi câu lệnh trước lời gọi
super(...)và gán vào trường instance, qua đó đáp ứng yêu cầu khởi tạo trường null-restricted
- Constructor có thể thực thi câu lệnh trước lời gọi
- Công việc trong tương lai bao gồm
- Null-Restricted Value Class Types (Preview): tối ưu mã hóa cho trường và mảng có kiểu value class null-restricted
- JEP 402: Enhanced Primitive Boxing (Preview): theo dõi nullness khi dùng rộng hơn chuyển đổi boxing ngầm định trong ngôn ngữ
- Chuyên biệt hóa class và phương thức JVM (JEP 218 và bản sửa đổi): cụ thể hóa và cưỡng chế nullness của một số đối số kiểu
- Các cải tiến tiềm năng khác trong tương lai bao gồm áp dụng dấu nullness cho một phần API chuẩn, biểu diễn null check ngắn gọn trong bytecode, cưỡng chế cấp thấp mạnh hơn cho tham số phương thức null-restricted, và cơ chế ngôn ngữ để ngầm khẳng định mọi kiểu trong một số ngữ cảnh là null-restricted
1 bình luận
Ý kiến trên Hacker News
Điều thú vị là cách tiếp cận này khác với cách C# đã đưa vào vài năm trước. Trong C#, nếu bật nullability trong dự án, mọi biến sẽ được khai báo là non-null trừ khi được đánh dấu rõ là nullable; còn trong đề xuất này, biến hiện có trên thực tế sẽ thuộc một trong ba loại: nullable, nullable tường minh, hoặc non-nullable tường minh
Kotlin cũng là ngôn ngữ JVM nhưng giống C#, nếu không ghi rõ thì xem là non-null; chỉ là Kotlin không có gánh nặng tương thích ngược. Kotlin cũng cung cấp cách lách bằng
lateinit var, cho phép để trống một kiểu non-nullable cho đến khi nó được khởi tạo trong một phương thức khác, và ném ra ngoại lệ riêng nếu truy cập trước khi khởi tạoTôi thắc mắc vì sao lại để ba lựa chọn. Không thể giữ biến không có chú thích là nullable, và chỉ biến có chú thích mới là non-null tường minh sao? Nếu không ghi gì cũng tự động là nullable thì tôi không nghĩ ra lý do gì để muốn khai báo nullable riêng cả
Tôi thích cách của C# hơn, nhưng cách này có ưu điểm là có thể dùng trong codebase legacy mà không cần giải quyết toàn bộ vấn đề nullability. Ngược lại, C# làm lộ ngay các vấn đề nullability, còn đề xuất này vẫn có thể giấu chúng như trước giờ
Ngoài ra, phần “một phương thức có thể override phương thức khác ngay cả khi nullability của tham số và giá trị trả về không khớp” nghe rất lạ. Khi override/implement callback, nếu phương thức gốc chỉ định trả về non-null mà lại trả về null thì có vẻ sẽ thành một khẩu súng bắn vào chân
Vì thời gian, hiện tại chúng tôi chọn chỉ bật chú thích ngoại trừ một vài vị trí cốt lõi, nhưng vẫn giữ
NotNullAttributevàCanBeNullAttributecủa JetBrains làm dấu hiệu để nhìn ngay những nơi đã được quyết định có chủ ý. Cái sau có thể bỏ vì nullable đã có dấu hiệu tường minh, nhưng cái trước lại xung đột tên với tính năng của chính C#Theo nghĩa đó, ba lựa chọn là khá đáng mong muốn. Lý do là khi mã có hàng trăm nghìn dòng thì rất khó chuyển đổi nhanh và dễ dàng
Ở các dự án nội bộ khác, chúng tôi đang rải
#nullable enablequanh phần mã đang đụng tới để mở rộng dần phạm vi nullability. Cũng có điều kiện rằng mã mới phải nằm trong ngữ cảnh nullable. Cách này cũng ổn để chỉ rõ những phần đã được chú thích, nhưng chỉ khả thi với codebase và đội ngũ nhỏ hơn nhiềuString!Thuật ngữ “platform type” và ký hiệu dấu chấm than có hơi gây nhầm lẫn, nhưng ngoài ra thì cách của Kotlin hoạt động khá tốt. Vì Kotlin đã có nullability ngay từ đầu nên lập trình viên không thể tự chỉ định platform type trực tiếp, nhưng chúng vẫn cần thiết để tương thích với các nền tảng cơ sở có nullability không rõ ràng như JVM hay JavaScript
Trong cách tiếp cận này, giá trị mặc định vẫn hợp lý. Mặc định phải luôn là non-nullable, và ai nghĩ khác thì coi như chưa học được gì từ Tony Hoare. Đồng thời vẫn duy trì được tương thích ngược. Kotlin thì tương đối dễ, còn Java và C# còn phải giữ tương thích với mã nguồn hiện có
Cả cách của Java lẫn C# đều không lý tưởng. Cách của C# thay đổi đáng kể hành vi mã tùy theo cờ compiler, còn cách của Java biến giá trị mặc định thành lựa chọn tệ nhất
Dù vậy, tôi vẫn nghiêng về phía C# hơn. Vì nếu biến “có lẽ có thể null” thành lựa chọn dễ nhất thì đa số lập trình viên sẽ chọn nó làm mặc định. Đặc biệt với một ngôn ngữ thân thiện với doanh nghiệp như Java thì càng có vẻ như vậy. Linter và cảnh báo compiler sẽ giúp ích về lâu dài, nhưng có lẽ sẽ mất nhiều năm trước khi phần lớn mã Java được chú thích nullability đúng cách. Người dùng C# sẽ đau hơn trong ngắn hạn, nhưng có khả năng đạt mục tiêu nullability rõ ràng nhanh hơn nhiều
https://kotlinlang.org/docs/java-interop.html#null-safety-an...
https://www.infoq.com/presentations/Null-References-The-Bill...
T?nghĩa là nullable,T!nghĩa là non-nullable, còn mặc định củaTthông thường trong mã nguồn mới biên dịch sẽ được chỉ định bằng một khai báo kiểu pragma theo từng file nguồn, từng filepackage-info, hoặc toàn cục qua compiler switchSau đó, ở một bản Java LTS về sau có thể đổi mặc định toàn cục. Như vậy việc chuyển đổi dự án sẽ dễ hơn, có thể tự động chèn/cập nhật pragma, và nếu cần vẫn có thể giữ mặc định cũ
Ngoài ra, nếu tận dụng chú thích
@NonnullJSR-305 hiện có làm cách biểu thị sự xuất hiện của kiểu non-nullable trong class file, thì cũng đã có thể cung cấp tương thích hai chiều với các JDK cũ!tường minh”Chỉ cần cỡ compiler flag hoặc tag module thôi có lẽ cũng ổn
Dù vậy, tôi nghĩ rồi sẽ có một compiler flag giả định tự động là non-nullable nếu không có chỉ định riêng
Có vẻ ổn. Cuối cùng cũng có cách ở cấp độ ngôn ngữ để loại bỏ hàng nghìn ngoại lệ và kiểm tra null không cần thiết. Tuy vậy, việc tự động chuyển đổi để thu hẹp trạng thái null có vẻ sai sai
Trong các ví dụ của đề xuất như
String? id(String! arg) { return arg; },String s = null;,Object! o1 = s; // NPE,Object o2 = id(s); // NPE,Object o3 = (String!) s; // NPE, ít nhất hai trường hợp đầu nên là lỗi biên dịch mới đúng chăngTrường hợp cuối thì là ép kiểu tường minh nên hơi khó nói, nhưng có lẽ tốt hơn là bên trong
if (s != null), trình biên dịch hiểu kiểu hợp lệ làString!và cho phépString! ss = s;. Như vậy sẽ không có khả năng lỗiThực ra so với phép cast ở trường hợp cuối, tôi thích dùng
Objects.requireNonNull(s)hơn vì tường minh hơn. Nhưng cũng sẽ hay nếu có thứ nhưObjects.unsafeForceNonNull(s)để bỏ qua kiểm tra tường minh, ngoại trừ những trường hợp bị chặn vì tối ưu hóa. Nếu có phương thức unsafe, ta có thể tự triển khai trực tiếprequireNonNullmà không cần thêm phân tích tĩnh phức tạpĐáng tiếc là điều này sẽ chỉ được xác nhận ở runtime
Ví dụ, thư viện chuẩn đã nói rõ rằng ít nhất trong thời gian trước mắt họ sẽ không chuyển sang kiểu null
Có vẻ rất cần một cách đánh dấu mọi biến mặc định là non-null ở cấp package, hoặc ít nhất là cấp file. Nếu không, vì lý do an toàn, lập luận rằng gần như mọi biến đều nên dùng cú pháp
T!sẽ trở nên rất mạnh, và điều đó chỉ tạo ra nhiều nhiễu!tường minh”@NullVấn đề chỉ phát sinh ở ranh giới mã tương tác với thư viện. Cú pháp mới này có lẽ cũng tương tự
Phần “hiện tại, mục tiêu không phải là áp dụng cải tiến ngôn ngữ này cho thư viện chuẩn” hơi đáng tiếc
Theo kinh nghiệm bất đắc dĩ phải dùng PHP, việc cứ phải bóc bỏ hoặc dựng lại các thuộc tính đã được đảm bảo trước về dữ liệu mỗi khi tương tác với thư viện chuẩn khổng lồ là rất phiền
Java cũng nên đưa tính biểu đạt như vậy vào thư viện chuẩn một cách tích cực hơn và biến nó thành công dân hạng nhất
Optionalsẽ là gánh nặng cho đề xuất nullable/non-nullable tường minh. Tôi nghĩ lẽ ra record type cũng đã có thể mặc định là non-nullableNgoài ra, nếu chia làm hai bước thì tính năng này có thể được phát hành dưới dạng preview dễ hơn, nhận phản hồi rồi mới chốt thiết kế. Nếu cố làm tất cả cùng lúc, gần như không còn chỗ để lặp lại và cải tiến tính năng trên thực tế
Tôi mong tính năng này được đưa vào Java. Khả năng chọn lựa tường minh ở cấp độ ngôn ngữ như
T?đã cải thiện đáng kể chất lượng sống của lập trình viên trong Kotlin và TypeScript. Java có các công cụ như NullAway, nhưng khá phiềnTôi cho rằng hỗ trợ ở cấp độ ngôn ngữ tốt hơn nhiều so với
Optional/Maybe, vì nó giúp tập trung vào logic thực tế thay vì đặt mã lên đường raymap/flatMaphttps://github.com/uber/NullAway
Chỉ cần dùng một ngôn ngữ JVM khác là được mà
Trang hiện đang sập nên để lại link lưu trữ: https://web.archive.org/web/20240802081039/https://bugs.open...
“Việc yêu cầu chương trình xử lý tường minh mọi giá trị null có thể phát sinh không phải là mục tiêu; các giá trị null chưa được xử lý có thể trở thành cảnh báo khi biên dịch, nhưng không phải lỗi” là một quyết định tệ
Java phần lớn là ngôn ngữ kiểu tĩnh, vậy tại sao lại đưa thêm một hành vi động nữa vào thì không hiểu nổi. Hy vọng có cách dễ dàng để nâng các cảnh báo kiểu này thành lỗi
Những ngôn ngữ mà hệ thống kiểu bắt buộc xử lý null/nil trong mọi trường hợp thì tốt hơn nhiều. Nhưng Java hiện tại không phải là ngôn ngữ như vậy. Dù thế, đây vẫn sẽ là một cải tiến lớn
Thật tiếc là họ học những bài học này quá muộn. Mặc định phải là non-nullable, mặc định bất biến, và mặc định là phạm vi hẹp nhất
Trong thiết kế mới, quá thường xuyên người ta chọn sự tiện lợi tức thời thay vì “để rơi vào con đường an toàn”. Giá trị mặc định an toàn đòi hỏi thiết kế và trải nghiệm người dùng tinh tế hơn nhiều, nhưng kết quả là hầu như mọi ngôn ngữ, nền tảng và công nghệ đều đầy rẫy những khẩu súng tự bắn vào chân. Kỹ thuật xây dựng và kỹ thuật điện có quy chuẩn, còn phần mềm thì cứ khoảng 30 năm lại học lại cùng một bài học trong các ngôn ngữ và công nghệ mới
Java giống như một ngôi làng ở thế giới thứ ba vừa học được rằng muốn diệt vi khuẩn thì chỉ cần đun sôi nước
Phần lớn những gì tôi làm ở Facebook là dùng Hack. Tính nullable là một yếu tố cốt lõi của hệ thống kiểu Hack, và nó giải quyết được thật sự rất nhiều lỗi vô nghĩa
Tất nhiên điều đó không có nghĩa là hoàn toàn không thể nhận được null ngoài dự kiến. Vì tính năng này cũng được thêm vào ngôn ngữ về sau, nên vẫn còn nhiều kiểu
mixedlegacy phản ánh gốc PHP, về cơ bản nghĩa là gì cũng đượcTrước hết tôi tò mò mảng nullable sẽ như thế nào. Ví dụ
String![]cho thấy trường hợp đối tượng có thể là null, nhưng bản thân mảng thì sao? Trong Java,String labels[] = null;là hoàn toàn hợp lệ. Vậy có phải phải khai báo kiểuString![]! labels;không?Trong Hack,
vec $foonghĩa là cả foo lẫn phần tử đều không phải null, còn?vec $foonghĩa là phần tử là non-null nhưng foo có thể null. Thực tế gần như không có lý do để dùng mảng null, nên giá trị mặc định phải là không cho phép null. Tuy nhiên Java có vấn đề là mọi mã legacy đều giả định khả năng nullTrong các ví dụ
Object! o1 = s,Object o2 = id(s),Object o3 = (String!) scủa đề xuất, tôi nghĩ số 2 và số 3 lẽ ra phải là lỗi biên dịchCuối cùng, tôi thích toán tử ép buộc
ascủa Hack hơn casting của Java. Ví dụfoo($b)là lỗi biên dịch,$b as Alà lỗi runtime nếu là null, còn$a as ?Bthì nếu là B sẽ cast, nếu không thì trả về nullRốt cuộc câu hỏi là liệu có thể phủ tính năng này lên Java SDK hay không, và nó sẽ trông như thế nào trong mã legacy
https://docs.hhvm.com/hack/types/nullable-types
String![]? labels = null;Có vẻ những điểm hay của Kotlin giờ đang dần được đưa hết vào Java
Dù vậy tôi vẫn muốn tiếp tục làm việc với Kotlin, nơi không phải đối phó với những thứ như Lombok. Java record thì đúng là ổn