Προγραμματισμός

Ξεκινήστε με τις εκφράσεις lambda στην Java

Πριν από το Java SE 8, οι ανώνυμες τάξεις χρησιμοποιούνταν συνήθως για τη μετάδοση της λειτουργικότητας σε μια μέθοδο. Αυτή η πρακτική αποκρύπτει τον πηγαίο κώδικα, καθιστώντας πιο δύσκολη την κατανόηση. Η Java 8 εξάλειψε αυτό το πρόβλημα εισάγοντας lambdas. Αυτό το σεμινάριο εισάγει πρώτα τη δυνατότητα γλώσσας λάμδα και στη συνέχεια παρέχει μια πιο λεπτομερή εισαγωγή στον λειτουργικό προγραμματισμό με εκφράσεις λάμδα μαζί με τύπους στόχων. Θα μάθετε επίσης πώς τα lambdas αλληλεπιδρούν με πεδία, τοπικές μεταβλητές, το Αυτό και σούπερ λέξεις-κλειδιά και εξαιρέσεις Java.

Σημειώστε ότι τα παραδείγματα κώδικα σε αυτό το σεμινάριο είναι συμβατά με το JDK 12.

Ανακαλύπτοντας τύπους για τον εαυτό σας

Σε αυτό το σεμινάριο δεν θα εισαγάγω λειτουργίες γλώσσας που δεν έχουν ήδη μάθει, αλλά θα δείξω λάμδα μέσω τύπων για τους οποίους δεν έχω συζητήσει προηγουμένως σε αυτήν τη σειρά. Ένα παράδειγμα είναι το java.lang.Math τάξη. Θα παρουσιάσω αυτούς τους τύπους σε μελλοντικά Java 101 σεμινάρια. Προς το παρόν, προτείνω να διαβάσετε την τεκμηρίωση JDK 12 API για να μάθετε περισσότερα σχετικά με αυτά.

λήψη Λήψη του κώδικα Λήψη του πηγαίου κώδικα για παράδειγμα εφαρμογές σε αυτό το σεμινάριο. Δημιουργήθηκε από τον Jeff Friesen για το JavaWorld.

Lambdas: Ένα αστάρι

ΕΝΑ έκφραση λάμδα (λάμδα) περιγράφει ένα μπλοκ κώδικα (μια ανώνυμη συνάρτηση) που μπορεί να περάσει σε κατασκευαστές ή μεθόδους για μετέπειτα εκτέλεση. Ο κατασκευαστής ή η μέθοδος λαμβάνει το λάμδα ως επιχείρημα. Εξετάστε το ακόλουθο παράδειγμα:

() -> System.out.println ("Γεια")

Αυτό το παράδειγμα προσδιορίζει ένα λάμδα για την αποστολή ενός μηνύματος στην τυπική ροή εξόδου. Απο αριστερά προς δεξιά, () προσδιορίζει τη λίστα τυπικών παραμέτρων του lambda (δεν υπάρχουν παράμετροι στο παράδειγμα), -> δηλώνει ότι η έκφραση είναι λάμδα, και System.out.println ("Γεια") είναι ο κωδικός που θα εκτελεστεί.

Το Lambdas απλοποιεί τη χρήση του λειτουργικές διεπαφές, οι οποίες είναι σχολιασμένες διεπαφές που ο καθένας δηλώνει ακριβώς μια αφηρημένη μέθοδο (αν και μπορούν επίσης να δηλώσουν οποιονδήποτε συνδυασμό προεπιλεγμένων, στατικών και ιδιωτικών μεθόδων). Για παράδειγμα, η τυπική βιβλιοθήκη κατηγορίας παρέχει ένα java.lang. Εκτελέσιμο διεπαφή με μία περίληψη άκυρη εκτέλεση () μέθοδος. Η δήλωση αυτής της λειτουργικής διεπαφής εμφανίζεται παρακάτω:

@FunctionalInterface δημόσια διεπαφή Runnable {public abstract void run (); }

Η βιβλιοθήκη τάξεων σχολιάζει Τρέξιμο με @FunctionalInterface, που είναι μια παρουσία του java.lang.FunctionalInterface τύπος σχολιασμού. Λειτουργική διεπαφή χρησιμοποιείται για τον σχολιασμό εκείνων των διεπαφών που πρόκειται να χρησιμοποιηθούν σε λάμδα περιβάλλοντα.

Ένα λάμδα δεν έχει συγκεκριμένο τύπο διεπαφής. Αντ 'αυτού, ο μεταγλωττιστής χρησιμοποιεί το περιβάλλον περιβάλλον για να συμπεράνει ποια λειτουργική διασύνδεση θα καθορίζει όταν καθορίζεται ένα λάμδα - το λάμδα είναι όριο σε αυτήν τη διεπαφή. Για παράδειγμα, ας υποθέσουμε ότι έχω καθορίσει το ακόλουθο τμήμα κώδικα, το οποίο περνά το προηγούμενο λάμδα ως όρισμα στο java.lang.Tread της τάξης Νήμα (στόχος με δυνατότητα εκτέλεσης) κατασκευαστής:

νέο νήμα (() -> System.out.println ("Γεια σας"));

Ο μεταγλωττιστής καθορίζει ότι το λάμδα μεταφέρεται Νήμα (Runnable r) γιατί αυτός είναι ο μόνος κατασκευαστής που ικανοποιεί το λάμδα: Τρέξιμο είναι μια λειτουργική διεπαφή, η κενή λίστα τυπικών παραμέτρων του lambda () αγώνες τρέξιμο()λίστα κενών παραμέτρων και τύποι επιστροφής (κενόςσυμφωνώ επίσης. Το λάμδα δεσμεύεται Τρέξιμο.

Η λίστα 1 παρουσιάζει τον πηγαίο κώδικα σε μια μικρή εφαρμογή που σας επιτρέπει να παίξετε με αυτό το παράδειγμα.

Λίστα 1. LambdaDemo.java (έκδοση 1)

δημόσια τάξη LambdaDemo {public static void main (String [] args) {new Thread (() -> System.out.println ("Hello")). έναρξη (); }}

Μεταγλώττιση καταχώρισης 1 (javac LambdaDemo.java) και εκτελέστε την εφαρμογή (java LambdaDemo). Πρέπει να παρατηρήσετε την ακόλουθη έξοδο:

γεια

Το Lambdas μπορεί να απλοποιήσει σημαντικά τον αριθμό του πηγαίου κώδικα που πρέπει να γράψετε και μπορεί επίσης να καταστήσει τον πηγαίο κώδικα πολύ πιο κατανοητό. Για παράδειγμα, χωρίς lambdas, πιθανότατα θα καθορίζατε τον πιο λεπτομερή κώδικα της Λίστα 2, ο οποίος βασίζεται σε μια παρουσία μιας ανώνυμης τάξης που εφαρμόζει Τρέξιμο.

Λίστα 2. LambdaDemo.java (έκδοση 2)

δημόσια τάξη LambdaDemo {public static void main (String [] args) {Runnable r = new Runnable () {@Override public void run () {System.out.println ("Hello"); }} νέο νήμα (r) .start (); }}

Μετά τη σύνταξη αυτού του πηγαίου κώδικα, εκτελέστε την εφαρμογή. Θα ανακαλύψετε την ίδια έξοδο με την προηγούμενη εμφάνιση.

Το Lambdas και το API ροών

Εκτός από την απλοποίηση του πηγαίου κώδικα, τα lambdas διαδραματίζουν σημαντικό ρόλο στο λειτουργικό προσανατολισμό Java Streams API. Περιγράφουν μονάδες λειτουργικότητας που μεταβιβάζονται σε διάφορες μεθόδους API.

Java lambdas σε βάθος

Για να χρησιμοποιήσετε αποτελεσματικά το lambdas, πρέπει να κατανοήσετε τη σύνταξη των εκφράσεων lambda μαζί με την έννοια ενός τύπου στόχου. Πρέπει επίσης να καταλάβετε πώς τα lambda αλληλεπιδρούν με τα πεδία, τις τοπικές μεταβλητές, το Αυτό και σούπερ λέξεις-κλειδιά και εξαιρέσεις. Θα καλύψω όλα αυτά τα θέματα στις ενότητες που ακολουθούν.

Πώς εφαρμόζονται τα lambdas

Τα Lambdas εφαρμόζονται σε όρους της εικονικής μηχανής Java επικαλυμμένο δυναμικό οδηγίες και το java.lang.invoke API. Παρακολουθήστε το βίντεο Lambda: A Peek Under the Hood για να μάθετε για την αρχιτεκτονική λάμδα.

Σύνταξη λάμδα

Κάθε λάμδα συμμορφώνεται με την ακόλουθη σύνταξη:

( επίσημη παράμετρος-λίστα ) -> { έκφραση-ή-δηλώσεις }

ο επίσημη παράμετρος-λίστα είναι μια λίστα τυπικών παραμέτρων διαχωρισμένων με κόμμα, οι οποίες πρέπει να ταιριάζουν με τις παραμέτρους της μεμονωμένης αφηρημένης μεθόδου μιας λειτουργικής διεπαφής στο χρόνο εκτέλεσης. Εάν παραλείψετε τους τύπους τους, ο μεταγλωττιστής εισάγει αυτούς τους τύπους από το πλαίσιο στο οποίο χρησιμοποιείται το λάμδα. Εξετάστε τα ακόλουθα παραδείγματα:

(double a, double b) // τύποι που προσδιορίζονται ρητά (a, b) // τύποι που συνάγονται από τον μεταγλωττιστή

Lambdas και var

Ξεκινώντας με το Java SE 11, μπορείτε να αντικαταστήσετε ένα όνομα τύπου με var. Για παράδειγμα, μπορείτε να καθορίσετε (var a, var b).

Πρέπει να καθορίσετε παρενθέσεις για πολλές ή καθόλου τυπικές παραμέτρους. Ωστόσο, μπορείτε να παραλείψετε τις παρενθέσεις (αν και δεν χρειάζεται) κατά τον καθορισμό μίας τυπικής παραμέτρου. (Αυτό ισχύει μόνο για το όνομα της παραμέτρου - απαιτείται παρένθεση όταν καθορίζεται επίσης ο τύπος.) Εξετάστε τα ακόλουθα επιπλέον παραδείγματα:

x // παρενθέσεις παραλείπονται λόγω μίας τυπικής παραμέτρου (διπλό x) // παρενθέσεις απαιτούνται επειδή υπάρχει επίσης τύπος

ο επίσημη παράμετρος-λίστα ακολουθείται από ένα -> διακριτικό, το οποίο ακολουθείται από έκφραση-ή-δηλώσεις- μια έκφραση ή ένα μπλοκ δηλώσεων (είτε είναι γνωστό ως το σώμα του λάμδα). Σε αντίθεση με τα σώματα που βασίζονται στην έκφραση, τα σώματα που βασίζονται σε δηλώσεις πρέπει να τοποθετούνται μεταξύ ανοιχτών ({) και κλείσιμο (}) χαρακτήρες αγκύλης:

(διπλή ακτίνα) -> Math.PI * radius * radius radius -> {return Math.PI * radius * radius; } ακτίνα -> {System.out.println (ακτίνα); επιστροφή Math.PI * ακτίνα * ακτίνα; }

Το σώμα λάμδα που βασίζεται στην έκφραση του πρώτου παραδείγματος δεν χρειάζεται να τοποθετείται ανάμεσα σε τιράντες. Το δεύτερο παράδειγμα μετατρέπει το σώμα που βασίζεται στην έκφραση σε ένα σώμα που βασίζεται σε μια δήλωση, στην οποία ΕΠΙΣΤΡΟΦΗ πρέπει να καθοριστεί για να επιστρέψει την τιμή της έκφρασης. Το τελευταίο παράδειγμα δείχνει πολλές δηλώσεις και δεν μπορεί να εκφραστεί χωρίς τα άγκιστρα.

Σώματα λάμδα και ερωτηματικά

Σημειώστε την απουσία ή την παρουσία ερωτηματικών (;) στα προηγούμενα παραδείγματα. Σε κάθε περίπτωση, το σώμα λάμδα δεν τερματίζεται με ερωτηματικό επειδή το λάμδα δεν είναι δήλωση. Ωστόσο, σε ένα σώμα λάμδα που βασίζεται σε μια δήλωση, κάθε δήλωση πρέπει να τερματίζεται με ερωτηματικό.

Η λίστα 3 παρουσιάζει μια απλή εφαρμογή που δείχνει τη σύνταξη lambda. Σημειώστε ότι αυτή η καταχώριση βασίζεται στα δύο προηγούμενα παραδείγματα κώδικα.

Λίστα 3. LambdaDemo.java (έκδοση 3)

@FunctionalInterface interface BinaryCalculator {διπλός υπολογισμός (διπλή τιμή1, διπλή τιμή2); } @FunctionalInterface interface UnaryCalculator {διπλός υπολογισμός (διπλή τιμή); } δημόσια κλάση LambdaDemo {public static void main (String [] args) {System.out.printf ("18 + 36,5 =% f% n", υπολογισμός ((διπλό v1, διπλό v2) -> v1 + v2, 18, 36.5)); System.out.printf ("89 / 2.9 =% f% n", υπολογισμός ((v1, v2) -> v1 / v2, 89, 2.9)); System.out.printf ("- 89 =% f% n", υπολογισμός (v -> -v, 89)); System.out.printf ("18 * 18 =% f% n", υπολογισμός ((διπλό v) -> v * v, 18)); } στατικός διπλός υπολογισμός (BinaryCalculator calc, double v1, double v2) {return calc.calculate (v1, v2); } στατικός διπλός υπολογισμός (UnaryCalculator calc, double v) {return calc.calculate (v); }}

Η λίστα 3 εισάγει πρώτα το Δυαδικός υπολογιστής και UnaryCalculator λειτουργικές διεπαφές των οποίων υπολογίζω() Οι μέθοδοι εκτελούν υπολογισμούς σε δύο ορίσματα εισόδου ή σε ένα όρισμα εισόδου, αντίστοιχα. Αυτή η λίστα εισάγει επίσης ένα Λάμδα Ντέμο τάξη του οποίου κύριος() Η μέθοδος δείχνει αυτές τις λειτουργικές διεπαφές.

Οι λειτουργικές διεπαφές παρουσιάζονται στο στατικός διπλός υπολογισμός (BinaryCalculator calc, double v1, double v2) και στατικός διπλός υπολογισμός (UnaryCalculator calc, double v) μεθόδους. Οι lambdas περνούν τον κωδικό ως δεδομένα σε αυτές τις μεθόδους, οι οποίες λαμβάνονται ως Δυαδικός υπολογιστής ή UnaryCalculator περιπτώσεις.

Μεταγλωττίστε την καταχώριση 3 και εκτελέστε την εφαρμογή. Πρέπει να παρατηρήσετε την ακόλουθη έξοδο:

18 + 36.5 = 54.500000 89 / 2.9 = 30.689655 -89 = -89.000000 18 * 18 = 324.000000

Τύποι στόχων

Ένα λάμδα συνδέεται με μια σιωπηρή τύπος στόχου, που προσδιορίζει τον τύπο του αντικειμένου στο οποίο συνδέεται ένα λάμδα. Ο τύπος στόχου πρέπει να είναι μια λειτουργική διεπαφή που συνάγεται από το περιβάλλον, το οποίο περιορίζει την εμφάνιση lambda στα ακόλουθα περιβάλλοντα:

  • Μεταβλητή δήλωση
  • ΑΝΑΘΕΣΗ ΕΡΓΑΣΙΑΣ
  • Δήλωση επιστροφής
  • Αρχικοποιητής συστοιχίας
  • Ορίσματα μεθόδου ή κατασκευαστή
  • Σώμα λάμδα
  • Τερματική υπό όρους έκφραση
  • Εκφραστική έκφραση

Η λίστα 4 παρουσιάζει μια εφαρμογή που δείχνει αυτά τα περιβάλλοντα τύπου στόχου.

Λίστα 4. LambdaDemo.java (έκδοση 4)

εισαγωγή java.io.File; εισαγωγή java.io.FileFilter; εισαγωγή java.nio.file.Files; εισαγωγή java.nio.file.FileSystem; εισαγωγή java.nio.file.FileSystems; εισαγωγή java.nio.file.FileVisitor; εισαγωγή java.nio.file.FileVisitResult; εισαγωγή java.nio.file.Path; εισαγωγή java.nio.file.PathMatcher; εισαγωγή java.nio.file.Paths; εισαγωγή java.nio.file.SimpleFileVisitor; εισαγωγή java.nio.file.attribute.BasicFileAttributes; εισαγωγή java.security.AccessController; εισαγωγή java.security.PrivilegedAction; εισαγωγή java.util.Arrays; εισαγωγή java.util.Collections; εισαγωγή java.util.Comparator; εισαγωγή java.util.List; εισαγωγή java.util.concurrent.Callable; δημόσια τάξη LambdaDemo {public static void main (String [] args) ρίχνει Εξαίρεση {// Τύπος στόχου # 1: μεταβλητή δήλωση Runnable r = () -> {System.out.println ("running"); }; r.run (); // Τύπος στόχου # 2: ανάθεση r = () -> System.out.println ("running"); r.run (); // Τύπος στόχου # 3: δήλωση επιστροφής (στο getFilter ()) File [] files = new File ("."). ListFiles (getFilter ("txt")); για (int i = 0; i path.toString (). berakhirWith ("txt"), (διαδρομή) -> path.toString (). berakhirWith ("java")}; FileVisitor επισκέπτης; visitor = new SimpleFileVisitor () { @Override public FileVisitResult visitFile (Αρχείο διαδρομής, BasicFileAttributes attribs) {Path name = file.getFileName (); for (int i = 0; i System.out.println ("running")). Start (); // Τύπος στόχου # 6: lambda body (ένα ένθετο lambda) Callable callable = () -> () -> System.out.println ("calling"); callable.call (). Run (); // Τύπος στόχου # 7: ternary υπό όρους έκφραση boolean ascendingSort = false; Συγκριτικό cmp; cmp = (ascendingSort)? (s1, s2) -> s1.compareTo (s2): (s1, s2) -> s2.compareTo (s1); Λίστα πόλεων = Arrays.asList ("Ουάσιγκτον", "Λονδίνο", "Ρώμη", "Βερολίνο", "Ιερουσαλήμ", "Οττάβα", "Σίδνεϊ", "Μόσχα"); Collections.sort (πόλεις, cmp); για (int i = 0; i <city.size (); i ++) System.out.println (city.get (i)); // Τύπος στόχου # 8: cast express String user = AccessController.doPrivileged ((PrivilegedAction) () -> System.getProperty ("όνομα χρήστη ")); System.out.println (χρήστης); } στατικό FileFilter getFilter (String ext) {return (pathname) -> pathname.toString (). berakhir με (ext); }}