Khi tìm kiếm dữ liệu trên trang tính Excel, bạn thường sử dụng hàm nào? Có lẽ câu trả lời phổ biến nhất là hàm VLOOKUP.
Học tập, chia sẻ kiến thức cơ bản về tin học.
Khi tìm kiếm dữ liệu trên trang tính Excel, bạn thường sử dụng hàm nào? Có lẽ câu trả lời phổ biến nhất là hàm VLOOKUP.
Hàm VLOOKUP, HLOOKUP là 2 hàm dò tìm dữ liệu và phổ biến với nhiều người. Tuy nhiên, hàm VLOOKUP, HLOOKUP có giới hạn đó là giá trị trả về phải nằm ở cột bên phải so với giá trị dò tìm với hàm VLOOKUP và nằm ở cột bên dưới với hàm HLOOKUP.
Ở ví dụ bên dưới, ta có thể dùng hàm VLOOKUP và HLOOKUP để dò tìm giá trị cho cột Tên hãng và hàng Tên hãng trong 2 bảng màu vàng từ 2 bảng dữ liệu màu xanh.
Nếu thứ tự bảng màu xanh đảo ngược lại, bạn không thể sử dụng hàm VLOOKUP và HLOOKUP nữa.
Như vậy, nếu bạn cần dò tìm giá trị theo chiều ngược lại, hàm INDEX kết hợp với hàm MATCH sẽ giúp bạn giải quyết vấn đề này.
Ngoài ra, các lợi ích khác khi dùng hàm INDEX kết hợp với hàm MATCH so với hàm VLOOKUP, HLOOKUP đó là:
Xem thêm: Hàm SUMIF
Hàm INDEX trả về giá trị theo vị trí của hàng và cột trong một bảng hoặc một phạm vi.
=INDEX(array;row_num;column_num)
Trong đó:
Hàm MATCH trả về vị trí tương đối của một giá trị trong một phạm vi.
=MATCH(lookup_value,lookup_array,match_type)
Trong đó:
Giả sử ta có đơn giá theo Sản phẩm và Hãng sản xuất trong Bảng 2 (B15:E18). Dựa theo bảng 2, ta cần điền đơn giá vào Bảng 1 (B3:D12).
Cách ta sử dụng hàm INDEX kết hợp hàm MATCH để dò tìm giá trị cho ô D4 như sau:
1. Sử dụng hàm MATCH để xác định vị trí hàng tương ứng của sản phẩm CDRom trong phạm vi B15:B18 của Bảng 2:
Kết quả 4 tương ứng với hàng số 4 trong Bảng 2.
2. Sử dụng hàm MATCH để xác định vị trí cột tương ứng của hãng sản xuất Samsung trong phạm vi B15:E15:
Kết quả 2 tương ứng với cột số 2 trong Bảng 2.
3. Sử dụng hàm INDEX kết hợp với 2 hàm MATCH ở trên để trả về giá trị dựa theo hàng và cột trong Bảng 2:
Kết quả trả về giá trị tương ứng của hàng 4 (Hàng Mouse), cột 2 (Cột Samsung) trong Bảng 2 là 5.
Sau đó, ta copy công thức cho các ô khác để hoàn thành.
Như đã đề cập ở phần 1, vì hàm VLOOKUP, HLOOKUP có hạn chế của nó nên nếu giá trị trả về nằm ở cột bên trái hoặc cột ở trên so với giá trị dò tìm thì 2 công thức sẽ không hoạt động. Hàm INDEX kết hợp hàm MATCH sẽ linh hoạt hơn rất nhiều khi không quan tâm về vị trí các cột giá trị trả về ở đâu.
Trong ví dụ ở dưới, ta có bảng 1 liệt kê thủ đô của các nước và cần điền dữ liệu tương ứng ở bảng 2. Trường hợp này ta không thể sử dụng hàm VLOOKUP nhưng hàm INDEX kết hợp hàm MATCH sẽ giải quyết được điều này.
Trong lúc sử dụng hàm INDEX kết hợp với hàm MATCH, bạn sẽ gặp lỗi #NA và lỗi #VALUE xuất hiện. Các nguyên nhân gây ra lỗi này thường là:
Lỗi #NA
Lỗi #VALUE
Nếu bạn đang sử dụng chỉ mục dưới dạng công thức mảng cùng với kết quả phù hợp để có thể dò tìm một giá trị, bạn sẽ cần chuyển công thức của bạn thành công thức mảng bằng cách nhấn Ctrl+Shift+Enter, nếu không thì bạn sẽ thấy lỗi #VALUE! xuất hiện.
Một vấn đề với các lớp vô danh là nếu thực hiện cho một lớp
vô danh thì rất đơn giản, như một interface nó chỉ bao gồm một phương thức, thì
cú pháp của các lớp vô danh có vẻ khó thực hiện để truyền vào chức năng các đối
số cho phương thức khác, như một hành động nào nên được thực hiện khi ai đó nhấn
nút. Biểu thức Lambda cho phép bạn làm điều này, để coi chức năng là đối số
phương thức ,hoặc mã nguồn như dữ liệu.
Trong phần trước, các phương thức vô danh, biểu diễn cho bạn
làm thế làm để thực hiện một lớp cơ sở không có tên. Mặc dù việc này thường ngắn
gọn hơn lớp được đặt tên, cho các lớp chỉ một phương thức, thậm chí các lớp vô
danh có vẻ quá cồng kềnh. Biểu thức Lambda giúp thể hiện các lớp đơn phương thức
một cách gọn gàng hơn.
Trong phần này bao gồm các chủ đề sau:
·
Ý tưởng cho trường hợp sử dụng các biểu thức
Lambda.
Các
tiếp cận 1: Tạo các phương thức để tìm các thành viên khớp với một đặc tính.
Cách
tiếp cận 2: Tạo nhiều phương thức tìm kiếm tổng quát hơn.
Cách
tiếp cận 3: Mã nguồn điều kiện tìm kiếm trong một lớp cục bộ.
Cách
tiếp cận 4: Mã nguồn điều kiện tìm kiếm trong một lớp vô danh.
Cách
tiếp cận 5: Mã nguồn điều kiện tìm kiếm bằng một biểu thức Lambda.
Cách
tiếp cận 6: Sử dụng các chức năng Interface chuẩn với các biểu thức Lambda.
Cách
tiếp cận 7: Sử dụng các biểu thức Lambda trong ứng dụng của bạn.
Cách
tiếp cận 8: Sử dụng Generics một cách rộng rãi hơn.
Cách
tiếp cận 9: Sử dụng các toán tử tổng hợp chấp nhận biểu thức Lambda như các
tham số.
·
Các biểu thức Lambda trong ứng dụng GUI
·
Cú pháp của biểu thức Lambda
·
Truy cập các biến cục bộ trong phạm vi bao bọc
nó.
·
Mục tiêu
Ý tưởng trường hợp sử dụng cho các biểu thức Lambda.
Giả sử rằng, bạn đang tạo ra một ứng dụng mạng xã hội. Bạn
muốn tạo một đặc điểm đó là cho phép một quản trị viên thi hành bất kỳ một hành
động nào, như gửi một tin nhắn, cho các thành viên trong ứng dụng mạng xã hội
đương nhiên phải đáp ứng xác tiêu chí nhất định. Bảng 3.3 mô tả các trường
hợp sử dụng chi tiết:
Bảng mô tả các trường hợp sử dụng trong một
mạng xã hội.
Giả sử rằng các thành viên trong ứng dụng mạng xã hội này được
đại diện bằng lớp Person:
public class Person {
public enum Sex {
MALE, FEMALE
}
String name;
LocalDate birthday;
Sex gender;
String emailAddress;
public int getAge() {
// ...
}
public void printPerson() {
// ...
}
}
Giả sử rằng các thành viên trong ứng dụng mạng xã hội này được
lưu trữ trong một thể hiện danh sách List<Person>.
Phần này bắt đầu với cách tiếp cận đơn giản cho trường hợp sử
dụng này. Nó cải thiện cách tiếp cận này với lớp cục bộ và lớp vô danh, và sau
đó kết thúc một cách hiệu quả và ngắn gọn và hiệu quả bằng cách sử dụng biểu thức
Lambda.
Cách tiếp cận 1: Tạo các phương thức tìm kiếm cho các
thành viên phù hợp với một đặc tính.
Một cách tiếp cận đơn giản là tạo một vài phương thức; mỗi
phuowgn thức tìm các thành viên phù hợp với một đặc tính, như giới tính hoặc tuổi.
Phương thức in ra các thành viên nhiều tuổi hơn một tuổi nhất định.
public static void printPersonsOlderThan(List<Person> roster, int age) {
for (Person p : roster) {
if (p.getAge() >= age) {
p.printPerson();
}
}
}
Chú ý: Một List là một Collection có thứ
tự. Một collection là một đối tượng nhóm các thành phần trong một đơn vị. Collection
được sử dụng để lưu trữ, lấy lại, thao tác và giao tiếp với các dữ liệu tổng hợp.
Các bạn có thể tìm hiểu về Collections để hiểu rõ hơn.
Cách tiếp cận này có thể tiềm năng làm hỏng ứng dụng của bạn,
có khả năng xảy là ứng dụng không làm việc. Giả sử rằng bạn nâng cấp ứng dụng của
mình và thay đổi cấu trúc của lớp Person như nội dung bao gồm các biến
thành viên khác; có lẽ các bản ghi lớp và đo lường tuổi với một kiểu dữ liệu
khác hoặc thuật toán khác. Bạn nên viết lại hầu hết các API để cung cấp cho sự
thay đổi này. Ngoài ra , cách tiếp cận này hạn chế một cách không cần thiết; ví
dụ nếu bạn muốn in các thành viên trẻ hơn một độ tuổi nhất định.
Cách tiếp cận 2: Tạo nhiều phương thức tìm kiếm tổng
quát hơn.
Phương thức khái quát hơn printPersonsOlderThan; nó in các
thành viên trong độ tuổi nhất định.
public static void printPersonsWithinAgeRange(
List<Person> roster, int low, int high) {
for (Person p : roster) {
if (low <= p.getAge() && p.getAge() < high) {
p.printPerson();
}
}
}
Điều gì sẽ xảy ra khi bạn muốn in các thành viên có một giới
tính cụ thể, hoặc một sự kết hợp của giới tính và phạm vi tuổi? Cái gì xảy ra nếu
bạn quyết địnhthay đổi lớp Person và thêm các thuộc tính như mối quan hệ giữa
tình trạng hoặc vị trí địa lý. Mặc dù phương thức này khái quát hơn phương thức
printPersonsOlderThan, thử tạo ra các phương thức riêng biệt cho mỗi
truy vấn có thể dẫn đến làm hỏng mã nguồn. Bạn có thể thay thế các mã nguồn
riêng biệt bằng các điều kiện bạn muốn tìm kiếm trong lớp khác.
Cách tiếp cận 3: Chỉ định các tiêu chí tìm kiếm trong
một lớp cục bộ.
Phương thức sau in ra các thành viên khớp với các điều kiện
tìm kiếm mà bạn chỉ định.
public static void printPersons(
List<Person> roster, CheckPerson tester) {
for (Person p : roster) {
if (tester.test(p)) {
p.printPerson();
}
}
}
Phương thức này kiểm tra mỗi cá thể của lớp Person
trong một danh sách List là kiểu dữ liệu của tham số roster khi
nó thỏa mã các tiêu chí tìm kiếm CheckPerson là kiểu dữ liệu của tham số
tester được gọi bởi phương thức tester.test. Nếu phương thức tester.test
trả về một giá trị true, thì phương thức printPersons được gọi từ
cá thể Person.
Để xác định tiêu chí tìm kiếm, bạn thực hiện interface
CheckPerson:
interface CheckPerson {
boolean test(Person p);
}
Lớp sau thực hiện interface CheckPerson bằng xác định một thực
hiện cho phương thức test. Phương thức này lọc các thành viên đạt chuẩn cho Selective
Service trong United States: nó trả về một giá trị true nếu tham số Person là
male và trong độ tuổi từ 18 tới 25.
class CheckPersonEligibleForSelectiveService implements CheckPerson {
public boolean test(Person p) {
return p.gender == Person.Sex.MALE &&
p.getAge() >= 18 &&
p.getAge() <= 25;
}
}
Để sử dụng lớp này, bạn tạo một cá thể gọi phương thức printPersons.
printPersons(
roster, new CheckPersonEligibleForSelectiveService());
Mặc dù cách tiếp cận này ít làm hỏng mã nguồn hơn – bạn
không được viết lại các phương thức nếu bạn thay đổi cấu trúc của lớp Person
– bạn vẫn phải thêm mã nguồn: một interface mới và một lớp cục bộ cho mỗi
tìm kiếm mà bạn dự định thực hiện trong ứng dụng. Bởi vì CheckPersonEligibleForSelectiveService
thực hiện một interface, bạn cần sử dụng một lớp vô danh thay thế cho lớp
cục bộ và bỏ qua sự cần thiết khai báo một lớp mới cho mỗi tìm kiếm.
Cách tiếp cận 4: Code chỉ định điều kiện tìm kiếm
trong một lớp vô danh
Một trong các đối số của của lời gọi hàm printPersons
là một lớp vô danh, nó lọc các thành viên hợp lệ cho Selective Service
trong Hoa Kỳ: những ai trong độ tuổi từ 18 đến 25.
printPersons(
roster,
new CheckPerson() {
public boolean test(Person p) {
return p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25;
}
}
);
Trong cách tiếp cận nầy giảm số lượng mã nguồn bởi vì bạn
không phải tạo một lớp mới cho mỗi tìm kiếm mà bạn muốn thực hiện. Tuy nhiên,
cú pháp các lớp vô danh là rất cồng kềnh vì interface CheckPerson chỉ chứa một
phương thức. Trong trường hợp này, bạn cần sử dụng một biểu thức Lambda thay thế cho một lớp vô
danh, được miêu tả ở phần tiếp theo.
Cách tiếp cận 5: Tạo mã nguồn chỉ định điều kiện tìm
kiếm với biểu thức Lambda.
Interface CheckPerson là một giao diện chức năng. Một
giao diện chức năng là một interface bất kỳ nó bao gồm chỉ một phương thức trừu
tượng (abstract method) (sẽ học trong phần sau). (Một giao diện chức năng
có thể bao từ một trở lên của phương thức mặc định hoặc phương thức tĩnh). Bởi
vì một giao diện chức năng chỉ bao gồm một phương thức trừu tượng, bạn có thể bỏ
sót tên của phương thức khi bạn thực hiện nó. Để làm điều này, thay thế sử dụng
một biểu thức lớp vô danh, bằng sử dụng một biểu thức Lambda, nó được
đánh dấu trong phương thức sau:
printPersons(
roster,
(Person p) -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25
);
Nhìn cú pháp của biểu thức Lambda cho thông tin về làm thế
nào để định nghĩa các biểu thức Lambda.
Bạn có thể sử dụng một giao diện chức năng chuẩn trong chỗ đặt
interface CheckPerson, nó giảm số lượng mã nguồn cần thiết.
Cách tiếp cận 6. Sử dụng các giao diện chức năng chuẩn
với biểu thức Lambda.
Xem xét interface CheckPerson sau:
interface CheckPerson {
boolean test(Person p);
}
Đây là một interface rất đơn giản. Nó là một giao diện chức
năng bởi vì nó gồm chỉ một phương thức trừu tượng. Phương thức này lấy một tham
số và trả về một giá trị kiểu boolean. Phương thức khá đơn giản, nó có thể
không không đáng để xác định là một phương thức trong ứng dụng của bạn. Hệ quả
là JDK định nghĩa một vài giao diện chức năng chuẩn, bạn có thể tìm thấy trong
gói java.util.function.
Cho ví dụ, bạn có thể sử dụng interface Predicate<T> tại
vị trí của CheckPerson. Interface này bao gồm phương thức boolean test(T t):
interface Predicate<T> {
boolean test(T t);
}
Interface Predicate<T> là một ví dụ của giao diện
generic. (Tìm hiểu thêm các thông tin về generics để biết kiểu dữ liệu này).
Các kiểu dữ liệu Generic (giống như các giao diện generic) chỉ định một hoặc
nhiều hơn một các kiểu tham số, T. Khi bạn khai báo hoặc khởi tạo một kiểu
generic với các kiểu đối số thật, bạn có một kiểu tham số hóa. Cho ví dụ, về kiểu
tham số hóa Predicate<Person> như sau:
interface Predicate<Person> {
boolean test(Person t);
}
Đây là kiểu tham số hóa bao gồm một phương thức có cùng kiểu
trả về và các tham số như CheckPerson.boolean, test(Person p). Hệ quả, bạn có
thể sử dụng Predicate<T> trong vị trí của CheckPerson, giống như phương
thức sau đây thể hiện:
public static void printPersonsWithPredicate(
List<Person> roster, Predicate<Person> tester) {
for (Person p : roster) {
if (tester.test(p)) {
p.printPerson();
}
}
}
Kết quả là, phương thức gọi là cũng với khi bạn gọi printPersons
trong cách tiếp cận thứ 3: Tạo mã nguồn chỉ định tìm kiếm trong lớp cục bộ để
có thể lọc các thành viên đủ điều kiện cho Selective Service.
printPersonsWithPredicate(
roster,
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25
);
Đây không phải là nơi duy nhất trong phương thức này có thể
sử dụng biểu thức Lambda. Cách tiếp cận dưới đây, đề xuất các cách sử dụng biểu
thức Lambda.
Cách tiếp cận thứ 7: Sử dụng biểu thức Lambda trong ứng dụng
của bạn.
Xem xét lại phương thức printPersonsWithPredicate để
thấy chỗ khác bạn có thể sử dụng biểu thức Lambda.
public static void printPersonsWithPredicate(
List<Person> roster, Predicate<Person> tester) {
for (Person p : roster) {
if (tester.test(p)) {
p.printPerson();
}
}
}
Phương thức kiểm tra mỗi cá thể Person được chứa
trong roster là tham số kiểu List mà nó thỏa mãn điều kiện chỉ định
trong tham số tester có kiểu Predicate. Nếu một cá thể Person thỏa
mãn điều kiện chỉ định bởi tester, phương thức printPerson sẽ được
gọi trong cá thể Person.
Thay thế lời gọi phương thức printPerson, bạn có thể
chị định một hành động khác để thực hiện trên các cá thể Person đó là thỏa
mãn điều kiện cụ thể bởi tester. Bạn có thể chị định các hành động này với
một biểu thức Lambda tương tự như printPerson, một trong đó có một đối số (một
đối tượng của kiểu Person) và trả về kiểu void. Nhớ rằng, để sử dụng một biểu
thức Lambda, bạn cần thực hiện một giao diện chức năng. Trong trường hợp này, bạn
cần một giao diện chức năng chứa một phương thức trừu tượng đó có thể lấy một đối
số của kiểu Person và trả về kiểu void. Giao diện Consumer<T> chứa
phương thức void accept(T t), nó có các đặc điểm đó. Phương thức sau
thay thế lời gọi p.printPerson() với một cá thể của Consumer<Person>
nó gọi phương thức accept.
public static void processPersons(
List<Person> roster,
Predicate<Person> tester,
Consumer<Person> block) {
for (Person p : roster) {
if (tester.test(p)) {
block.accept(p);
}
}
}
Kết quả, sau khi gọi phương thức giống với khi bạn gọi
phương thức printPersons trong cách tiếp cận 3: Viết mã nguồn chỉ định tìm kiếm
trong lớp cục bộ để tìm những thành viên đạt chuẩn trong Selective Service. Biểu
thức Lambda sử dụng để in ra các thành viên trong phần đánh dấu:
processPersons(
roster,
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25,
p -> p.printPerson()
);
Điều gì sẽ xảy ra khi bạn muốn làm hơn nữa với các hồ sơ của
các thành viên so với việc in chúng ra. Giả sử rằng bạn muốn xác thực các hồ sơ
các thành viên hoặc lưu trữ thông tin liên hệ của họ. Trong trường hợp này, bạn
cần một giao diện chức năng có chưa một phương thức trừu tượng và trả về một
giá trị. Interface Function<T,R> chứa phương thứ R apply(T t). Phương thức
sau truy xuất dữ liệu được chỉ định bởi trình ánh xạ tham số, sau đó thực hiện
một hành động trên nó được chỉ định bởi khối tham số:
public static void processPersonsWithFunction(
List<Person> roster,
Predicate<Person> tester,
Function<Person, String> mapper,
Consumer<String> block) {
for (Person p : roster) {
if (tester.test(p)) {
String data = mapper.apply(p);
block.accept(data);
}
}
}
Phương thức lấy địa chỉ email từ mỗi thành viên chứa trong
roster những ai đạt trong Selective Service và sẽ in chúng ra:
processPersonsWithFunction(
roster,
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25,
p -> p.getEmailAddress(),
email -> System.out.println(email)
);
Cách tiếp cận 8: Sử dụng Generics mở rộng.
Xem xét phương thức processPersonsWithFunction.
Phương thức sau là một phiên bản generic, nó chấp nhận như một tham số, một tập
hợp chứa các phần tử của bất kỳ dữ liệu nào.
public static <X, Y> void processElements(
Iterable<X> source,
Predicate<X> tester,
Function <X, Y> mapper,
Consumer<Y> block) {
for (X p : source) {
if (tester.test(p)) {
Y data = mapper.apply(p);
block.accept(data);
}
}
}
Để in ra địa chỉ email của các thành viên đạt chuẩn
cho Selective Service, gọi phương thức processElements như sau:
processElements(
roster,
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25,
p -> p.getEmailAddress(),
email -> System.out.println(email)
);
Phương thức này gọi thực thi các hành động sau:
·
1. Lấy một nguồn của các đối tượng từ tập hợp
source. Trong trường hợp ví dụ này, nó lấy nguồn của các đối tượng Person
từ tập hợp roster. Chú ý rằng tập hợp roster, nó là một tập hợp
có kiểu List, cũng là một đối tượng kiểu Iterable.
·
2. Lọc các đối tượng phù hợp với trình kiểm tra
đối tượng tester của Predicate. Trong ví dụ này, đối tượng kiểm
tra Predicate là một biểu thức Lambda nó chỉ định các thành viên đạt yêu cầu
cho Selective Service.
·
3. Ánh xạ mỗi đối tượng được lọc thành một giá
trị được chỉ định bởi Function, có đối tượng là mapper. Trong ví
dụ này, đối tượng Function làm một biểu thức Lambda trả về địa chỉ
email của một thành viên.
·
4. Thực hiện một hành động trên đối tượng phù hợp
được chỉ định bởi Consumer có đối tượng block. Trong trường hợp
này, đối tượng Consumer là một biểu thức Lambda nó in ra một chuỗi, đó
là đại chỉ email đã được trả về bởi đối tượng Function.
Bạn có thể thay thế mỗi hành động bằng một toán tử tổng hợp.
Cách tiếp cận 9: Sử dụng toán tử tổng hợp chấp nhận
biểu thức Lambda như những tham số.
Ví dụ sau sử dụng toán tử tổng hợp để in ra các địa chỉ email
của các thành viên chứa trong tập hợp roster là những thành viên đạt
tiêu chuẩn cho Selective Service.
roster
.stream()
.filter(
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25)
.map(p -> p.getEmailAddress())
.forEach(email -> System.out.println(email));
Bảng sau ánh xạ mỗi toán tử ở phương thức processElements thực
hiện với toán tử tổng hợp.
Ánh xạ mỗi toán tử ở phương thức processElements thực hiện với
toán tủ tổng hợp
Toán tử filter, map và forEach là các toán tử tổng hợp. Các toán tử tổng hợp xử lý các phần tử trong một luồng, không trực tiếp từ một tập hợp (đó là lý do tại sao lời gọi phương thức đầu tiên trong ví dụ là stream). Một stream là một tuần tự các phần tử. Không giống như tập hợp, nó không có cấu trúc dữ liệu lưu trữ các phần tử. Thay vào đó, một stream mang các giá trị từ một nguồn, giống như tập hợp, thông qua một pipeline. Một pipeline là một tuần tự của luồng các toán tử, trong ví dụ là filter-map-forEach. Ngoài ra, các toán tử tổng hợp phổ biến chấp nhận các biểu thức Lambda như các tham số, cho phép bạn tùy chỉnh cách chúng hoạt động. Các bạn cần tìm hiểu nhiều hơn về các toán tử tổng hợp này.
Các biểu thức Lambda trong ứng dụng GUI.
Để xử lý các sự kiện trên ứng dụng giao diện người dùng (GUI),
giống như các hành động trên bàn phím, hành động trên chuột, và hành động lăn
chuột, bạn thường tạo ra các trình xử lý sự kiện, chúng thường liên quan đến
triển khai một giao diện cụ thể. Thông thường, trình sử lý sự kiện trên các
giao diện là các giao diện chức năng; chúng có khuynh hướng chỉ có một phương
thức.
Trong ví dụ JavaFX HelloWord.Java (đã được bàn trong
phần trước là các lớp vô danh), bạn có thể thay thế lớp vô danh được đánh dấu bằng
một biểu thức Lambda trong câu lệnh sau:
btn.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});
Lời gọi phương thức btn.setOnAction chỉ định cái gì xảy ra
khi bạn chọn vào nút thể hiện bằng đối tượng btn. Phương thức này yêu cầu một đối
tượng có kiểu EventHandler<ActionEvent>. Interface
EventHandler<ActionEvent> chứa chỉ duy nhất một phương thức, void
handle(T event). Interface này là giao diện chức năng, do đó bạn có thể sử
dụng như sau trong phần đánh dấu dùng biểu thức Lambda thay thế.
btn.setOnAction(
event -> System.out.println("Hello World!")
);
Cú pháp của các biểu
thức Lambda.
Một biểu thức lambda bao gồm các thành phần sau:
·
Một danh sách các tham số hình thức được phân
tách bởi dấu phẩy được đặt trong dấu ngoặc đơn. Phương thức CheckPerson.test
chứa một tham số, p, nó thể hiện là một cá thể của lớp Person.
Chú ý: Bạn có thể bỏ sót kiểu dữ liệu
của các tham số trong biểu thức Lambda. Ngoài ra, bạn có thể bỏ sót dấu ngoặc
đơn nếu nó chỉ có một tham số. Ví dụ sau biể thức Lamda là hợp lệ:
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25
·
Mã thông báo mũi tên, ->
·
Một thân, nó bao gồm một biểu thức đơn hoặc một
khối lệnh. Trong ví dụ này sử dụng biểu thức sau:
p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25
Nếu bạn chỉ định một biểu thức đơn thì bộ chạy thời
gian Java tính toán biểu thức và trả vê một giá trị cho nó. Ngoài ra bạn cần sử
dụng một câu lệnh trả về:
p -> {
return p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25;
}
Một câu lện trả về không là một biểu thức; trong một
biểu thức Lambda, bạn phải bao bọc các câu lệnh trong dấu mở đóng ngoặc nhọn
({}). Tuy nhiên, bạn không cần phải bao bọc một phương thức trả về giá trị kiểu
void. Ví dụ sau đây là một biểu thức Lambda hợp lệ:
email -> System.out.println(email)
Chú ý rằng biểu thức Lambda nhìn tất cả giống như một khai
báo phương thức; bạn có thể xem xét các biểu thức Lambda như những phương thức
vô danh – các phương thức không có tên.
Ví dụ sau, Caculator, là một ví dụ của các biểu thức Lambda
có nhiều hơn một tham số hình thức.
public class Calculator {
interface IntegerMath {
int operation(int a, int b);
}
public int operateBinary(int a, int b, IntegerMath op) {
return op.operation(a, b);
}
public static void main(String... args) {
Calculator myApp = new Calculator();
IntegerMath addition = (a, b) -> a + b;
IntegerMath subtraction = (a, b) -> a - b;
System.out.println("40 + 2 = " +
myApp.operateBinary(40, 2, addition));
System.out.println("20 - 10 = " +
myApp.operateBinary(20, 10, subtraction));
}
}
Phương thức operateBinary thực hiện một thao tác phép toán
trên hai toán tử số nguyên. Thao tác này chỉ định một thể hiện của IntegerMath.
Ví dụ định nghĩa hai toán tử với các biểu thức Lambda, addition và subtraction.
Kết quả của ví dụ như sau:
40 + 2 = 42
20 - 10 = 10
Truy cập vào các biến cục bộ trong phạm vi bao bọc biểu
thức Lambda.
Giống như các lớp cục bộ và các lớp vô danh, các biểu thức Lambda
có thể bắt được các biến; chúng có thể truy cập vào các biến cục bộ của khối phạm
vi bao bọc nó. Tuy nhiên, không giống với các lớp vô danh, các biểu thức Lambda
không làm mờ được các biến (tìm hiểu sâu hơn về làm mờ để hiểu hơn). Các biểu
thức Lambda có phạm vi từ vựng. Điều này có nghĩa là chúng không kế thừa bất kỳ
tên nào từ một siêu dữ liệu hoặc một giới thiệu phạm vi mới. Khai báo trong biểu
thức Lambda là chỉ diễn giải như chúng trong phạm vi môi trường bao bọc nó. Ví dụ
sau, LambdaScopeTest, minh họa điều này:
import java.util.function.Consumer;
public class LambdaScopeTest {
public int x = 0;
class FirstLevel {
public int x = 1;
void methodInFirstLevel(int x) {
int z = 2;
Consumer<Integer> myConsumer = (y) ->
{
// The following statement causes the compiler to generate
// the error "Local variable z defined in an enclosing scope
// must be final or effectively final"
//
// z = 99;
System.out.println("x = " + x);
System.out.println("y = " + y);
System.out.println("z = " + z);
System.out.println("this.x = " + this.x);
System.out.println("LambdaScopeTest.this.x = " +
LambdaScopeTest.this.x);
};
myConsumer.accept(x);
}
}
public static void main(String... args) {
LambdaScopeTest st = new LambdaScopeTest();
LambdaScopeTest.FirstLevel fl = st.new FirstLevel();
fl.methodInFirstLevel(23);
}
}
Ví dụ này có kết quả như sau:
x = 23
y = 23
z = 2
this.x = 1
LambdaScopeTest.this.x = 0
Nếu bạn thay thế tham số x vào thay cho y trong khai báo biểu
thức Lambda myComsumer, thì trình biên dịch sẽ sinh ra lỗi:
Consumer<Integer> myConsumer = (x) -> {
// ...
}
Trình biên dịch sinh ra lỗi "Lambda expression's parameter x cannot redeclare
another local variable defined in an enclosing scope" tạm dịch là “Tham
sỗ của biểu thức Lambda không thể định nghĩa lại một biến khác trong phạm vi nó
được bao bọc” bởi vì biểu thức Lambda không giới thiệu một cấp độ phạm
vi mới. Hệ quả là bạn có thể truy cập trực tiếp vào các thuộc tính, phương thức
và các biến cục bộ trong phạm vi bao bọc nó. Ví dụ, biểu thức Lambda truy cập
trực tiếp tham số x của phương thức methodInFirstLevel. Để truy cập các
biến trong phạm vi lớp bao bọc, sử dụng từ khóa this. Trong ví dụ này là, this.x,
tham chiếu đến biến thành viên FistLevel.x.
Tuy nhiên, giống như các lớp cục bộ và các lớp vô danh, một biểu
thức Lambda chỉ có thể truy cập các biến cục bộ và các tham số trong khối
bao bọc nó khi biến đó là final hoặc efectively final. Trong ví dụ
này, biến z là biến effectively final; giá trị của nó không bao giờ thay đổi
sau khi nó được khởi gán. Tuy nhiên, giả sử bạn thêm câu lệnh gán sau trong biểu
thức Lambda myConsumer:
Consumer<Integer> myConsumer = (y) -> {
z = 99;
// ...
}
Bởi vì câu lệnh gán này, biến z không phải là là effiectively
final nữa. Kết quả, trình biên dịch Java sẽ tạo ra một lỗi tương tự như “Local variable z defined in an
enclosing scope must be final or effectively final” tạm dịch là biến z được
đã được định nghĩa trong khối bao bọc nó phải là final hoặc effectively final.
Kiểu dữ liệu mục tiêu.
Làm thế nào để bạn quyết định kiểu cho một biểu thưc Lambda?
Gọi lại một biểu thức Lambda chọn các thành viên có giới tính nam và có độ tuổi
từ khoảng 18 tới 25 tuổi:
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25
Trong biểu thức Lambda này đã sử dụng hai phương thức sau:
·
public static void
printPersons(List<Person> roster, CheckPerson tester), trong cách tiếp
cận 3: chỉ định điều kiện tìm kiếm trong lớp cục bộ.
·
public void
printPersonsWithPredicate(List<Person> roster, Predicate<Person>
tester), trong cách tiếp cận 6: sử dụng các giao diện chức năng chuẩn với
biểu thức Lambda.
Khi chạy chương trình Java gọi phương thức printPersons, nó chờ
kiểu dữ liệu trả về là CheckPerson, do đó biểu thức Lambda là kiểu dữ liệu này.
Tuy nhiên, khi chương trình Java gọi phương thức printPersonsWithPredicate nó
chờ kiểu dữ liệu trả về là Predicate<Person>, do vậy biểu thức Lambda là
kiểu dữ liệu này. Kiểu dữ liệu của các phương thức chờ đợi trả về gọi là kiểu dữ liệu mục tiêu. Để
quyết định một kiểu cho một biểu thức Lambda, trình biên dịch Java sử dụng kiểu
dữ liệu mục tiêu cho ngữ cảnh hoặc tình uống mà biểu thức Lambda được tìm thấy.
Theo đó, bạn cần sử dụng chỉ một biểu thức Lambda cho tình huống mà trình biên dịch
có thể quyết định kiểu dữ liệu mục tiêu:
·
Khai báo biến.
·
Câu lệnh gán
·
Câu lệnh trả về
·
Khởi gán mảng
·
Đối số cho phương thức hoặc hàm khởi tạo.
·
Thân biểu thức Lambda.
·
Biểu thức điểu kiện, ?:
·
Truyền biểu thức.
Các kiểu dữ liệu mục tiêu và các đối số phương thức.
Các đối số phương thức, trình biên dịch Java quyết định kiểu
dữ liệu mục tiêu với hai đoặc điểm ngôn ngữ: giải quyết nạp chồng và kiểu suy
luận đối số.
Xem xét hai giao diện chức năng (java.lang.Runnable và
java.util.concurrent.Callable<V>):
public interface Runnable {
void run();
}
public interface Callable<V> {
V call();
}
Phương thức Runnable.run
không trả về một giá trị, phương thức Callable<V>.call thì có.
Hình dung rằng, bạn có nạp chồng khi gọi phương thức sau (xem
lại phần định nghĩa phương thức để hiểu về phương thức nạp chồng.)
void invoke(Runnable r) {
r.run();
}
<T> T invoke(Callable<T> c) {
return c.call();
}
Phương thức nào sẽ được gọi trong câu lệnh sau:
String s = invoke(() -> "done");
Phương thức invoke(Callable<T>) sẽ được gọi bởi vì nó
là phương thức trả về một giá trị; phương thức invoke(Runnable) thì không.
Trong trường hợp này, kiểu của biểu thức Lambda () -> "done" là Callable<T>.
Tuần tự hóa
Bạn có thể tuần tự hóa biểu thức Lambda nếu kiểu dữ liệu mục
tiêu và các đối số bắt được có khả năng tuần tự. Tuy nhiên, như các lớp trong,
việc tuần tự hóa biểu thức Lambda là không được khuyến khích.
Ngôn ngữ lập trình Java cho phép bạn định nghĩa một lớp
trong một lớp khác. Một lớp như vậy được gọi là một lớp lồng và được
minh họa như sau:
class OuterClass {
...
class NestedClass {
...
}
}
Thuật ngữ: Các lớp lồng được chia thành hai thể loại: non-static và static. Lớp lồng non-static được gọi là lớp trong. Lớp lồng được khai báo static được gọi là lớp lồng tĩnh.
class OuterClass {
...
class InnerClass {
...
}
static class StaticNestedClass {
...
}
}
Một lớp lồng là một thành viên của lớp bao quanh nó. Lớp lồng
non-static (lớp trong) có được truy cập các thành viên khác trong lớp
bao quanh nó, nếu chúng được khai báo là private. Lớp lồng tĩnh không
truy cập được các thành viên khác trong lớp bao quanh nó. Như một thành viên của
lớp OuterClass, một lớp lồng có thể được định nghĩa private, public,
protected, hoặc packate-private. (Gọi lại lớp ngoài có thể chỉ được
định nghĩa public hoặc package-private).
Tại sao sử dụng các lớp lồng?
Các lý do thuyết phục để sử dụng các lớp lồng bao gồm:
·
Nó là một cách cục bộ các nhóm lớp chỉ được sử dụng
trong một nơi: Nếu một lớp là được sử dụng chỉ với một lớp khác, thì là hợp lý
để nhúng nó vào lớp đó và giữ chúng bên nhau. Lớp lồng như “các lớp hỗ trợ” tạo
cho các gói của chúng sắp xếp hợp lý hơn.
·
Tăng tính đóng gói: Xem xét hai mức đột các lớp,
A và B, nơi B cần truy cập các thành viên của lớp A mà hơn nữa chúng được khai
báo private. Bằng cách ẩn lớp B trong lớp A, các thành viên cùa lớp A có
thể được khai báo private và B có thể truy cập được chúng. Ngoài ra, bản thân B
có thể được ẩn đi từ bên ngoài.
·
Nó có thể dẫn đến dễ đọc và dễ bảo trì mã nguồn
hơn: Việc đặt lớp lồng trong lớp ở mức cao nhất sẽ đặt mã gần nhất với nơi nó
được sử dụng.
Các lớp trong.
Giống như instance methods và các biến, một lớp trong
được liên kết với một cá thể của lớp bao quanh nó và truy cập trực tiếp vào các
phương thức và thuộc tính của đối tượng. Ngoài ra, bởi vì một lớp bên trong được
liên kết với một cá thể, nó không thể định nghĩa bất kỳ một thành viên tĩnh nào
bên trong nó.
Các đối tượng là cá thể của một lớp trong tồn tại trong một
cá thể của lớp ngoài. Xem xét lớp sau:
class OuterClass {
...
class InnerClass {
...
}
}
Một cá thể của lớp InnerClass có thể chỉ tồn tại trong một
cá thể OuterClass và có thể truy cập trực tiếp đến các phương thức và các thuộc
tính của cá thể lớp ngoài.
Để khởi tạo một lớp trong, bạn trước hết phải khởi tạo lớp
bên ngoài. Sau đó, tạo một đối tượng lớp trong bên trong lớp ngoài với cú pháp
này:
OuterClass outerObject = new OuterClass();
OuterClass.InnerClass innerObject = outerObject.new InnerClass();
Có hai loại đặc biệt của các lớp trong: lớp cục bộ và lớp vô
danh sẽ được bàn đến ở phần sau.
Các lớp lồng tĩnh.
Như các phương thức
class methods và các biến, một lớp lồng tĩnh được liên kết với các lớp bên
ngoài. Và giống như phương thức tĩnh (static class methods), một lớp lồng tĩnh
không thể tham chiếu trực tiếp đến các biến instance variable hoặc các phương
thức định nghĩa bên trong lớp bao bọc nó: nó có thể sử dụng chúng chỉ trong một
đối tượng tham chiếu. Trong phần ví dụ lớp trong và lớp lồng sẽ minh họa vấn đề
này.
Chú ý: một lớp lồng tĩnh tương tác với các thành viên
instance của lớp ngoài (và các lớp khác) chỉ như bất kỳ lớp khác ở mức cao nhất.
Minh chứng là, một lớp lồng tĩnh về mặt hành vi là của một lớp mức cao nhất để
thuận lợi cho việc đóng gói. Trong ví dụ về lớp trong và lớp lồng tĩnh cũng
mình họa điều này.
Bạn khởi tạo một lớp lồng tĩnh như cách khởi tạo của một lớp
mức cao nhất:
StaticNestedClass staticNestedObject = new StaticNestedClass();
Ví dụ về các lớp trong và các lớp lồng tĩnh.
Trong ví dụ, OuterClass, cùng với lớp TopLevelClass
minh họa các thành viên lớp của OuterClass một lớp trong (InnerClass),
một lớp lồng tĩnh (StaticNestedClass), và một lớp mức cao nhất (TopLevelClass)
cần truy cập:
Kết quả của chương trình này là:
Inner class:
------------
Outer field
Static outer field
Static nested class:
--------------------
Outer field
Static outer field
Top-level class:
--------------------
Outer field
Static outer field
Chú ý rằng một lớp lồng tĩnh tương tác với các thành viên
instance của lớp ngoài chỉ như bất kỳ lớp mức độ cao nhất khác. Trong lớp lồng
tĩnh StaticNestedClass không thể truy cập trực tiếp vào thuộc tính outerField
bởi vì nó là một biến instance của lớp bao bọc nó, OuterClass. Trình biên dịch sẽ tạo ra lỗi tại câu lệnh được
in đậm dưới đây:
static class StaticNestedClass {
void accessMembers(OuterClass outer) {
// Compiler error: Cannot make a static reference to the non-static
// field outerField
System.out.println(outerField);
}
}
Để sửa lỗi này, truy cập vào outerField thông qua một
đối tượng tham chiếu:
System.out.println(outer.outerField);
Làm mờ (Shadowing)
Nếu một khai báo cho một kiểu (như là một biến thành viên hoặc
một tên tham số) trong một phạm vi cụ thể (như trong một lớp trong hoặc một
khai báo phương thức) có tên giống với khai báo của phạm vi ngoài, thì khai báo
làm mờ shadows phạm vi xunh quanh. Bạn không thể tham chiếu đến một khai báo
làm mờ bằng tên độc lập. Trong ví dụ ShadowTest, minh họa điều này:
public class ShadowTest {
public int x = 0;
class FirstLevel {
public int x = 1;
void methodInFirstLevel(int x) {
System.out.println("x = " + x);
System.out.println("this.x = " + this.x);
System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
}
}
public static void main(String... args) {
ShadowTest st = new ShadowTest();
ShadowTest.FirstLevel fl = st.new FirstLevel();
fl.methodInFirstLevel(23);
}
}
Kết quả của chương trình này:
x = 23
this.x = 1
ShadowTest.this.x = 0
Ví dụ này định nghĩa ba biến tên x: biến thành viên của lớp ShadowTest,
biến thành viên của lớp trong FirstLevel, và tham số trong phương thức methodInFirstLevel.
Biến x được định nghĩa là một tham số của phương thức methodInFirstLevel
làm mở đi biến trong lớp FirstLevel. Hệ quả là khi bạn sử dụng biến x
trong phương thức methodInFirstLevel, nó tham chiếu đến tham số phương
thức. Để tham chiếu đến biến thành viên của lớp FirstLevel, sử dụng từ
khóa this để đại diện cho phạm vi bao quanh:
System.out.println("this.x = " + this.x);
Tham chiếu đến biến thành viên của phạm vi bao quanh lớn hơn
bằng tên lớp mà chúng thuộc về. Ví dụ, câu lệnh truy cập biến thành viên của lớp
ShadowTest từ phương thức methodInFirstLevel:
System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
Tuần tự hóa
Tuần tự hóa của các lớp trong, bao gồm các lớp địa
phương và các lớp vô danh, rất không được khuyến khích. Khi chương
trình Java biên dịch cấu trúc nhất định, như các lớp trong, nó tạo ra cấu
trúc tổng hợp; có các lớp, các phương thức, các thuộc tính, và các cấu trúc
khác không có cấu trúc tương ứng trong mã nguồn. Các cấu trúc tổng hợp cho phép
Java biên dịch thực hiện các tính năng ngôn ngữ không có sự thay đổi JVM.
Tuy nhiên, các cấu trúc tổng hợp có thể thay đổi khác nhau giữa các trình biên
dịch thực hiện, có nghĩa là file .class có thể thay đổi giữa các cách triển
khai khác nhau. Hệ quả là bạn có thể giải quyết những vấn đề tương thích nếu bạn
tuần tự hóa một lớp trong và sau đó giải mã hóa nó bằng một triển khai JRE khác.
Xem trong phần các tham số ngầm và tổng hợp cho nhiều thông tin hơn về các cấu
trúc ngầm được tạo ra khi một lớp trong được biên dịch.
Để xem một lớp trong được sử dụng, trước hết xem xét một mảng.
Trong ví dụ, bạn tạo một mảng, điền các giá trị số nguyên, và sau đó đầu ra chỉ
các giá trị có chỉ số chẵn tăng dần của mảng.
public class DataStructure {
// Create an array
private final static int SIZE = 15;
private int[] arrayOfInts = new int[SIZE];
public DataStructure() {
// fill the array with ascending integer values
for (int i = 0; i < SIZE; i++) {
arrayOfInts[i] = i;
}
}
public void printEven() {
// Print out values of even indices of the array
DataStructureIterator iterator = this.new EvenIterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
System.out.println();
}
interface DataStructureIterator extends java.util.Iterator<Integer> { }
// Inner class implements the DataStructureIterator interface,
// which extends the Iterator<Integer> interface
private class EvenIterator implements DataStructureIterator {
// Start stepping through the array from the beginning
private int nextIndex = 0;
public boolean hasNext() {
// Check if the current element is the last in the array
return (nextIndex <= SIZE - 1);
}
public Integer next() {
// Record a value of an even index of the array
Integer retValue = Integer.valueOf(arrayOfInts[nextIndex]);
// Get the next even element
nextIndex += 2;
return retValue;
}
}
public static void main(String s[]) {
// Fill the array with integer values and print out only
// values of even indices
DataStructure ds = new DataStructure();
ds.printEven();
}
}
Đầu ra của chương trình là:
0 2 4 6 8 10 12 14
Chú ý rằng lớp EvenIterator tham chiếu trực tiếp đến arrayOfInts
là biến instance của đối tượng DataStructure.
Bạn có thể sử dụng các lớp trong để thực hiện các lớp tiện
ích như một thể hiện trong ví dụ này. Để xử lý các dữ kiện của giao diện người
dùng, bạn phải biết sử dụng các lớp trong, bởi vì cơ chế xử lý các sự kiện sử dụng
rộng rãi.
Các lớp cục bộ và lớp vô danh.
Có hai kiểu được thêm vào cho các lớp trong. Bạn có thể khai
báo một lớp trong bên trong phần thân của một phương thức. Các lớp như vậy được
biết đến là các lớp cục bộ. Bạn có thể cũng khai báo một lớp một lớp trong bên
trong một phương thức nhưng không khai báo tên. Các lớp như vậy được biết đến
là các lớp vô danh.
Các bổ ngữ.
Bạn có thể sử dụng các bổ ngữ giống nhau cho các lớp trong
như bạn sử dụng cho các thành viên khác trong lớp ngoài. Ví dụ, bạn có thể sử dụng
từ chỉ định truy cập như private, public, protected để hạn chế truy cập đến các
lớp trong, giống như bạn sử dụng chúng để hạn chế truy cập cho các thành viên
khác của lớp.
Giới thiệu.
Các lớp cục bộ là các lớp được khai báo trong một khối, bao
gồm nhóm lệnh trong cặp mở/ đóng ngoặc nhọn. Bạn thường tìm thấy các lớp cục bộ
được định nghĩa trong một phương thức.
Phần này bao gồm các chủ đề sau:
·
Khai báo các lớp cục bộ.
·
Truy cập các thành viên trong lớp bao quanh.
o
Làm mờ và các lớp cục bộ.
·
Các lớp cục bộ tương tự các lớp trong.
Khai báo các lớp cục bộ.
Bạn cần định nghĩa một lớp cục bộ bên trong một khối (Xem phần
biểu thức, các câu lệnh và các khối để thêm thông tin). Ví dụ, bạn có thể định
nghĩa một lớp cục bộ trong thân một phương thức, một vòng lặp for hoặc một câu
lệnh điều kiện if.
Ví dụ LocalClassExample, kiểm tra hai số điện thoại.
Định nghĩa một lớp cục bộ PhoneNumber trong phương thức validatePhoneNumber:
public class LocalClassExample {
static String regularExpression = "[^0-9]";
public static void validatePhoneNumber(
String phoneNumber1, String phoneNumber2) {
final int numberLength = 10;
// Valid in JDK 8 and later:
// int numberLength = 10;
class PhoneNumber {
String formattedPhoneNumber = null;
PhoneNumber(String phoneNumber){
// numberLength = 7;
String currentNumber = phoneNumber.replaceAll(
regularExpression, "");
if (currentNumber.length() == numberLength)
formattedPhoneNumber = currentNumber;
else
formattedPhoneNumber = null;
}
public String getNumber() {
return formattedPhoneNumber;
}
// Valid in JDK 8 and later:
// public void printOriginalNumbers() {
// System.out.println("Original numbers are " + phoneNumber1 +
// " and " + phoneNumber2);
// }
}
PhoneNumber myNumber1 = new PhoneNumber(phoneNumber1);
PhoneNumber myNumber2 = new PhoneNumber(phoneNumber2);
// Valid in JDK 8 and later:
// myNumber1.printOriginalNumbers();
if (myNumber1.getNumber() == null)
System.out.println("First number is invalid");
else
System.out.println("First number is " + myNumber1.getNumber());
if (myNumber2.getNumber() == null)
System.out.println("Second number is invalid");
else
System.out.println("Second number is " + myNumber2.getNumber());
}
public static void main(String... args) {
validatePhoneNumber("123-456-7890", "456-7890");
}
}
Trong ví dụ kiểm tra một số điện thoại bằng cách lấy ra số đầu
tiên của tất cả các ký tự từ số điện thoại ngoại trừ các số từ 0 đến 9. Sau đó,
nó kiểm tra các số điện thoại bao gồm chính xác 10 số (kích thước của một số điện
thoại ở Bắc Mỹ). Ví dụ này in ra:
First number is 1234567890
Second number is invalid
Truy cập các thành viên của một lớp bao bọc bên ngoài lớp
cục bộ.
Một lớp cục bộ có thể truy cập vào các thành viên của lớp
bao bọc bên ngoài. Trong ví dụ trước, hàm khởi tạo PhoneNumber truy cập
vào thành viên LocalClassExample.regularExpression.
Ngoài ra, một lớp cục bộ có thể truy cập vào các biến cục bộ.
Tuy nhiên, một lớp cục bộ chỉ có thể truy cập vào các biến cục bộ được định
nghĩa là final. Khi một lớp cục bộ truy cập vào một biến cục bộ hoặc
tham số của khối đóng mở ({}), nó nắm bắt được biến hoặc tham số. Ví dụ hàm khởi
tạo PhoneNumber có thể truy cập vào biến cục bộ numberLength bởi vì biến
này được khai báo là final; numberLangth là một biến nắm bắt được
của lớp cục bộ, gọi là biến captured variable.
Tuy nhiên, bắt đầu từ Java SE 8, một lớp cục bộ có thể truy
cập các biến cục bộ và các tham số trong khối đóng mở ({}) được khai báo final
hoặc effectively final. Một biến hoặc tham số có giá trị không bao giờ
thay đổi sau khi khởi tạo là effectively final. Ví dụ, giả sử biến
numberLength không được khai báo là final, và trong lệnh gán được bôi đậm trong
hàm khởi tạo PhoneNumber dưới đây thay đổi độ dài của một số điện thoại đến 7 số:
PhoneNumber(String phoneNumber) {
numberLength = 7;
String currentNumber = phoneNumber.replaceAll(
regularExpression, "");
if (currentNumber.length() == numberLength)
formattedPhoneNumber = currentNumber;
else
formattedPhoneNumber = null;
}
Bởi vì ở câu lệnh gán, biến numberLength không là effectively
final. Có một kết quả, trình biên dịch Java tạo ra một lỗi tương tự như ("local variables referenced from an
inner class must be final or effectively final") tạm dịch là
các biến tham chiếu cục bộ từ một lớp trong phải được khai báo là final
hoặc effectively final, khi một lớp trong PhoneNumber có gắng truy cập
vào biến numberLenth:
if (currentNumber.length() == numberLength)
Bắt đầu từ Java SE 8 trở đi, nếu bạn khai báo một lớp trong trong một phương thức, nó có thể truy cập được các tham số của phương thức. Ví dụ, bạn có thể khai báo phương thức sau trong lớp cục bộ PhoneNumber:
public void printOriginalNumbers() {
System.out.println("Original numbers are " + phoneNumber1 +
" and " + phoneNumber2);
}
Phương thức printOriginalNumbers truy cập các tham số
phoneNumber1 và phoneNumber2 của phương thức validateNumber.
Làm mờ và các biến cục bộ: Khai báo một kiểu (như là một biến)
trong một lớp cục bộ làm mờ khai báo trong phạm vi bao bọc nó thống như cái cái
tên “làm mờ”. Xem lại phần làm mờ (Shadowing trong để thêm thông tin)
Các lớp cục bộ tương đương với các lớp trong.
Các lớp cục bộ là tương đương với các lớp trong bởi chúng
không thể định nghĩa hoặc khai báo bất kỳ một thành viên tĩnh nào. Lớp cục bộ
trong phương thức tĩnh, giống như lớp PhoneNumber nó định nghĩa trong phương thức
validatePhoneNumber, chỉ có thể tham chiếu đến các thành viên tĩnh trong khối
đóng mở của lớp. Ví dụ, nếu bạn không định nghĩa một biến thành viên regularExpression
như một biến tĩnh, thì trình biên dịc Java sẽ tạo ra một lỗi là “non-static variable regularExpression
cannot be referenced from a static context”, tạm dịch là biến
regularaExpression không thể là biến tĩnh, nó không thể tham chiếu đến từ một
ngữ cảnh tĩnh (hiểu là là biến tĩnh).
Các lớp cục bộ không thể là lớp tĩnh bởi vì chúng truy cập
vào các thành viên instance bên trong khối. Hệ quả là chúng không thể bao gồm hầu
hết các loại khai báo tĩnh.
Bạn không thể khai báo một interface trong một khối; các
interface vốn dĩ là tĩnh. Ví dụ, đoạn trích mã nguồn dưới đây không thể biên dịch
được bởi interface HelloThere được khai báo bên trong thân của phương thức
greetInEnglish:
public void greetInEnglish() {
interface HelloThere {
public void greet();
}
class EnglishHelloThere implements HelloThere {
public void greet() {
System.out.println("Hello " + name);
}
}
HelloThere myGreeting = new EnglishHelloThere();
myGreeting.greet();
}
Bạn không thể khai báo các khởi tạo tĩnh hoặc các interface
thành viên trong một lớp cục bộ. Đoạn trích mã nguồn dưới đây không thể biên dịch
bởi phương thức EnglishGoodbye.sayGoodbye được khai báo là static. Trình biên dịch
sinh ra lỗi như “modifier 'static'
is only allowed in constant variable declaration”, tạm dịch là bổ ngữ
‘static’ chỉ cho phép khai báo biến hằng. Nó được định nghĩa như này:
public void sayGoodbyeInEnglish() {
class EnglishGoodbye {
public static void sayGoodbye() {
System.out.println("Bye bye");
}
}
EnglishGoodbye.sayGoodbye();
}
Một lớp cục bộ có thể có các thành viên tĩnh, chúng là các
biến hằng (Một biến hằng là một biến có kiểu nguyên thủy hoặc kiểu String,
được khai báo là final và khởi tạo với một biểu thức hằng thời gian biên
dịch. Một biểu thức hằng thời gian biên dịch thường là một chuỗi hoặc một biểu
thức số nó có thể được đánh giá bằng thời gian biên dịch. Xem lại phần hiểu về
các thành viên của lớp trong phần trước để rõ hơn thông tin.) Ví dụ đoạn trích
mã nguồn biên dịch bởi thành biên tĩnh EnglishGoodbye.farewell là một biến
hằng:
public void sayGoodbyeInEnglish() { class EnglishGoodbye { public static final String farewell = "Bye bye"; public void sayGoodbye() { System.out.println(farewell); } } EnglishGoodbye myEnglishGoodbye = new EnglishGoodbye(); myEnglishGoodbye.sayGoodbye(); }
Các lớp vô danh cho phép bạn tạo ra mã nguồn ngắn gọn. Chúng
cho phép bạn khai báo và khởi tạo một lớp trong cùng thời gian. Chúng giống như
lớp cục bộ ngoại trừ chúng không có tên. Sử dụng chúng nếu bạn cần sử dụng một
lớp cục bộ một lần.
Trong phần này sẽ có các chủ đề sau:
·
Khai báo các lớp vô danh.
·
Cú pháp trong các lớp vô danh.
·
Truy cập các biến cục bộ của khối, và khai báo
và truy cập các thành viên trong các lớp vô danh.
·
Các ví dụ về các lớp vô danh.
Khai báo các lớp
vô danh.
Trong khi các lớp cục bộ là các lớp khai báo, thì lớp vô
danh là một biểu thức, có nghĩa bạn định nghĩa một lớp trong một biểu thức
khác. Ví dụ HelloWorldAnonymousClasses sử dụng các lớp vô danh trong câu lệnh
khai báo của các biến cục bộ frenchGreeting và spanishGreeting, nhưng sử dụng
trong một lớp cục bộ cho khai báo các biến englishGreeting:
public class HelloWorldAnonymousClasses {
interface HelloWorld {
public void greet();
public void greetSomeone(String someone);
}
public void sayHello() {
class EnglishGreeting implements HelloWorld {
String name = "world";
public void greet() {
greetSomeone("world");
}
public void greetSomeone(String someone) {
name = someone;
System.out.println("Hello " + name);
}
}
HelloWorld englishGreeting = new EnglishGreeting();
HelloWorld frenchGreeting = new HelloWorld() {
String name = "tout le monde";
public void greet() {
greetSomeone("tout le monde");
}
public void greetSomeone(String someone) {
name = someone;
System.out.println("Salut " + name);
}
};
HelloWorld spanishGreeting = new HelloWorld() {
String name = "mundo";
public void greet() {
greetSomeone("mundo");
}
public void greetSomeone(String someone) {
name = someone;
System.out.println("Hola, " + name);
}
};
englishGreeting.greet();
frenchGreeting.greetSomeone("Fred");
spanishGreeting.greet();
}
public static void main(String... args) {
HelloWorldAnonymousClasses myApp =
new HelloWorldAnonymousClasses();
myApp.sayHello();
}
}
Cú pháp của các lớp vô danh.
Được nhắc đến trong phần trước, một lớp vô danh là một biểu
thức. Cú pháp của một biểu thức lớp vô danh là giống lời gọi một hàm khởi dựng,
ngoại trừ việc có một định nghĩa nội dung bên trong một khối mã nguồn.
Xem xét khởi tạo đối tượng frenchGreeting:
HelloWorld frenchGreeting = new HelloWorld() { String name = "tout le monde"; public void greet() { greetSomeone("tout le monde"); } public void greetSomeone(String someone) { name = someone; System.out.println("Salut " + name); } };
Biểu thức lớp vô danh bao gồm:
·
Toán tử new
·
Tên của một interface để thực hiện hoặc một lớp
kế thừa. Trong ví dụ này, lớp vô danh thực hiện interface HelloWorld.
·
Dấu ngoặc đơn bao gồm các đối số ở trong hàm khởi
tạo, chỉ giống một instance được tạo ra từ biểu thức tạo lớp. Chú ý: khi bạn thực
hiện một interface, nó không có hàm khởi tạo, do vậy bạn sử dụng một cặp
đóng mở ngoặc đơn bên trong để trống, như ví dụ này.
·
Một thân, một lớp được khai báo là một thân. Hơn
nữa ,trong thân lớp, các phương thức được phép, còn các câu lệnh thì không được
phép khai báo.
Bởi vì một lớp vô danh định nghĩa là một biểu thức, nó phải
có thành phần của một câu lệnh. Trong ví dụ, biểu thức lớp vô danh là một phần
của câu lệnh đó là một cá thể đối tượng frenchGreeting. (Điều này giải
thích tại sao có dấu chấm phẩy sau dấu ngoặc.)
Truy cập các biến cục bộ trong phạm vi bao bọc, và khai
báo và truy cập các thành viên của lớp vô danh.
Giống như các lớp cục bộ, lớp vô danh có thể nắm bắt các biến;
chúng có thể truy cập vào cùng các biến cục bộ của phạm vi bao bọc nó.
·
Một lớp ẩn danh có thể truy cập vào các thành
viên của lớp bao bọc nó.
·
Một lớp ẩn danh không thể truy cập vào các biến
trong phạm vi bao bọc nó mà không được khai báo final hoặc effectively
final.
·
Giống như một lớp lồng, một khai báo cho kiểu
(giống như một biến) trong một lớp ẩn danh làm mờ bất kỳ một khai báo khác
trong phạm vi bao bọc nếu nó cùng tên. Xem lại làm mờ ở phần trên để biết thêm
thông tin.
Các lớp ẩn danh cũng có cùng hạn chế giống các lớp cục bộ cho
các mối quan hệ cho các thành viên của chúng:
·
Bạn không thể khai báo khởi tạo tĩnh hoặc thành
viên interface trong một lớp ẩn danh.
·
Một lớp ẩn danh có thể có các thành viên tĩnh
nhưng chúng phải là các biến hằng.
Lưu ý rằng bạn có thể khai báo những điều sau cho lớp ẩn
danh:
·
Fields (các thuộc tính).
·
Các phương thức bổ sung (ngay cả khi chúng không
triển khai bất kỳ một phương thức nào cho supertype).
·
Khởi tạo cho cá thể.
·
Các lớp cục bộ.
·
Tuy nhiên, bạn không thể khai báo các hàm khởi tạo
trong một lớp vô danh.
Ví dụ vê lớp Vô danh.
Các lớp vô danh thường sử dụng trong một ứng dụng giao diện
đồ họa (GUI).
Xem xét trong JavaFX ví dụ Helloworld.java. Trong ví dụ này,
bao gồm nút Say ‘Hello World’. Lớp vô danh trong phần highlight:
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class HelloWorld extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Hello World!");
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});
StackPane root = new StackPane();
root.getChildren().add(btn);
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
}
}
Trong ví dụ này, phương thức gọi btn.setOnAction xảy ra khi
bạn chọn nút Say 'Hello World'. Phương thức này yêu cầu đối tượng có kiểu
EventHandler<ActionEvent>. Interface EventHandler<ActionEvent>
bao gồm chỉ một phương thức, là: thay thế thực thi phương thước này bằng một lớp
mới, trong ví dụ sử dụng một biểu thức lớp vô danh. Chú ý rằng biểu thức này là
đối số truyền vào cho phương thức btn.setOnAction.
Bởi vì interface EventHandler<ActionEvent> chỉ
bao gồm một phương thức, bạn có thể sử dụng một biểu thức lambda thay thế biểu
thức lớp vô danh. Bạn tìm hiểu nhiều hơn ở phần biểu thức Lambda.
Các lớp vô danh là một ý tưởng cho thực hiện một interface
mà chứa từ hai phương thức trở lên. Trong phần highlight mã nguồn tạo một thuộc
tính văn bẳn chỉ cho phép nhập giá trị số. Nó định nghĩa lại thực hiện mặc định
cho lớp TextField với một lớp vô danh bằng việc ghi đè replaceText và replaceSelection
kế thừa từ lớp TextInputControl class.
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class CustomTextFieldSample extends Application {
final static Label label = new Label();
@Override
public void start(Stage stage) {
Group root = new Group();
Scene scene = new Scene(root, 300, 150);
stage.setScene(scene);
stage.setTitle("Text Field Sample");
GridPane grid = new GridPane();
grid.setPadding(new Insets(10, 10, 10, 10));
grid.setVgap(5);
grid.setHgap(5);
scene.setRoot(grid);
final Label dollar = new Label("$");
GridPane.setConstraints(dollar, 0, 0);
grid.getChildren().add(dollar);
final TextField sum = new TextField() {
@Override
public void replaceText(int start, int end, String text) {
if (!text.matches("[a-z, A-Z]")) {
super.replaceText(start, end, text);
}
label.setText("Enter a numeric value");
}
@Override
public void replaceSelection(String text) {
if (!text.matches("[a-z, A-Z]")) {
super.replaceSelection(text);
}
}
};
sum.setPromptText("Enter the total");
sum.setPrefColumnCount(10);
GridPane.setConstraints(sum, 1, 0);
grid.getChildren().add(sum);
Button submit = new Button("Submit");
GridPane.setConstraints(submit, 2, 0);
grid.getChildren().add(submit);
submit.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent e) {
label.setText(null);
}
});
GridPane.setConstraints(label, 0, 1);
GridPane.setColumnSpan(label, 3);
grid.getChildren().add(label);
scene.setRoot(grid);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Tin học cơ bản - Nền tảng của mọi kỹ năng
Mọi thông tin trên blog đều được giữ bản quyền bởi Tin học cơ bản. Các bạn nếu muốn lấy thông tin từ blog vui lòng ghi rõ nguồn Tinhoccoban.net
TIN HỌC CƠ BẢN