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

Προγραμματισμός Java με εκφράσεις λάμδα

Στην τεχνική βασική διεύθυνση για το JavaOne 2013, ο Mark Reinhold, επικεφαλής αρχιτέκτονας της Java Platform Group στο Oracle, περιέγραψε τις εκφράσεις lambda ως τη μοναδική μεγαλύτερη αναβάθμιση στο μοντέλο προγραμματισμού Java πάντα. Ενώ υπάρχουν πολλές εφαρμογές για εκφράσεις λάμδα, αυτό το άρθρο επικεντρώνεται σε ένα συγκεκριμένο παράδειγμα που εμφανίζεται συχνά σε μαθηματικές εφαρμογές. δηλαδή, η ανάγκη μετάδοσης μιας συνάρτησης σε έναν αλγόριθμο.

Ως geek γκρίζα μαλλιά, έχω προγραμματίσει σε πολλές γλώσσες όλα αυτά τα χρόνια και έχω προγραμματίσει εκτενώς στην Java από την έκδοση 1.1. Όταν άρχισα να δουλεύω με υπολογιστές, σχεδόν κανείς δεν είχε πτυχίο στην επιστήμη των υπολογιστών. Οι επαγγελματίες των υπολογιστών προέρχονταν κυρίως από άλλους κλάδους όπως η ηλεκτρολογία, η φυσική, οι επιχειρήσεις και τα μαθηματικά. Στην προηγούμενη ζωή μου ήμουν μαθηματικός, και έτσι δεν πρέπει να προκαλεί έκπληξη το γεγονός ότι η αρχική μου άποψη για έναν υπολογιστή ήταν αυτή ενός τεράστιου προγραμματιζόμενου υπολογιστή. Έχω διευρύνει σημαντικά την άποψή μου για τους υπολογιστές με τα χρόνια, αλλά εξακολουθώ να χαιρετίζω την ευκαιρία να εργαστώ σε εφαρμογές που περιλαμβάνουν κάποια πτυχή των μαθηματικών.

Πολλές εφαρμογές στα μαθηματικά απαιτούν μια συνάρτηση να περάσει ως παράμετρος σε έναν αλγόριθμο. Παραδείγματα από την άλγεβρα κολλεγίων και το βασικό λογισμό περιλαμβάνουν την επίλυση μιας εξίσωσης ή τον υπολογισμό της ολοκλήρωσης μιας συνάρτησης. Για περισσότερα από 15 χρόνια η Java ήταν η γλώσσα προγραμματισμού της επιλογής μου για τις περισσότερες εφαρμογές, αλλά ήταν η πρώτη γλώσσα που χρησιμοποίησα σε συχνή βάση που δεν μου επέτρεψε να περάσω μια συνάρτηση (τεχνικά δείκτης ή αναφορά σε μια λειτουργία) ως παράμετρος με απλό, απλό τρόπο. Αυτό το μειονέκτημα πρόκειται να αλλάξει με την επερχόμενη έκδοση του Java 8.

Η ισχύς των εκφράσεων lambda εκτείνεται πολύ πέρα ​​από μια περίπτωση χρήσης, αλλά η μελέτη διαφόρων εφαρμογών του ίδιου παραδείγματος θα σας αφήσει με μια σταθερή αίσθηση του πώς τα lambda θα ωφελήσουν τα προγράμματα Java. Σε αυτό το άρθρο θα χρησιμοποιήσω ένα κοινό παράδειγμα για να βοηθήσω στην περιγραφή του προβλήματος και, στη συνέχεια, να δώσω λύσεις γραμμένες σε C ++, Java πριν από τις εκφράσεις lambda και Java με εκφράσεις lambda. Σημειώστε ότι δεν απαιτείται ισχυρό υπόβαθρο στα μαθηματικά για να κατανοήσετε και να εκτιμήσετε τα κύρια σημεία αυτού του άρθρου.

Μαθαίνοντας για τα λάμδα

Οι εκφράσεις Lambda, επίσης γνωστές ως κλείσιμο, λειτουργικά γράμματα, ή απλά lambdas, περιγράφουν ένα σύνολο χαρακτηριστικών που ορίζονται στο Java Specification Request (JSR) 335. Λιγότερο επίσημες / πιο ευανάγνωστες εισαγωγές στις εκφράσεις lambda παρέχονται σε μια ενότητα της τελευταίας έκδοσης του Java Tutorial και σε μερικά άρθρα του Brian Goetz, "State of the lambda" και "State of the lambda: Libraries edition." Αυτοί οι πόροι περιγράφουν τη σύνταξη των εκφράσεων lambda και παρέχουν παραδείγματα περιπτώσεων χρήσης όπου ισχύουν οι εκφράσεις lambda. Για περισσότερες πληροφορίες σχετικά με τις εκφράσεις lambda στο Java 8, παρακολουθήστε την τεχνική βασική διεύθυνση του Mark Reinhold για το JavaOne 2013.

Λάμδα εκφράσεις σε μαθηματικό παράδειγμα

Το παράδειγμα που χρησιμοποιείται σε αυτό το άρθρο είναι ο Κανόνας του Simpson από τον βασικό λογισμό. Ο Κανόνας του Simpson, ή πιο συγκεκριμένα ο Σύνθετος Κανόνας του Simpson, είναι μια τεχνική αριθμητικής ολοκλήρωσης για την προσέγγιση ενός ορισμένου ολοκλήρου. Μην ανησυχείτε εάν δεν είστε εξοικειωμένοι με την έννοια του α οριστικό ακέραιο; αυτό που πραγματικά πρέπει να καταλάβετε είναι ότι ο Κανόνας του Simpson είναι ένας αλγόριθμος που υπολογίζει έναν πραγματικό αριθμό με βάση τέσσερις παραμέτρους:

  • Μια λειτουργία που θέλουμε να ενσωματώσουμε.
  • Δύο πραγματικοί αριθμοί ένα και σι που αντιπροσωπεύουν τα τελικά σημεία ενός διαστήματος [α, β] στην πραγματική γραμμή αριθμών. (Σημειώστε ότι η συνάρτηση που αναφέρεται παραπάνω πρέπει να είναι συνεχής σε αυτό το διάστημα.)
  • Ένας ομοιόμορφος ακέραιος ν που καθορίζει έναν αριθμό υποδιαστημάτων. Κατά την εφαρμογή του κανόνα του Simpson διαιρούμε το διάστημα [α, β] σε ν υποδιαστήματα.

Για να απλοποιήσουμε την παρουσίαση, ας επικεντρωθούμε στη διεπαφή προγραμματισμού και όχι στις λεπτομέρειες εφαρμογής. (Αλήθεια, ελπίζω ότι αυτή η προσέγγιση θα μας επιτρέψει να παρακάμψουμε επιχειρήματα σχετικά με τον καλύτερο ή πιο αποτελεσματικό τρόπο εφαρμογής του Κανόνα του Simpson, ο οποίος δεν είναι το επίκεντρο αυτού του άρθρου.) Θα χρησιμοποιήσουμε τον τύπο διπλό για παραμέτρους ένα και σικαι θα χρησιμοποιήσουμε τον τύπο int για παράμετρο ν. Η συνάρτηση που θα ενσωματωθεί θα έχει μία μόνο παράμετρο τύπου διπλό και επιστρέφει μια τιμή τύπου διπλό.

Λήψη Λήψη του παραδείγματος πηγαίου κώδικα C ++ για αυτό το άρθρο. Δημιουργήθηκε από τον John I. Moore για το JavaWorld

Παράμετροι λειτουργίας σε C ++

Για να παρέχουμε μια βάση σύγκρισης, ας ξεκινήσουμε με μια προδιαγραφή C ++. Όταν περνά μια συνάρτηση ως παράμετρος στο C ++, συνήθως προτιμώ να καθορίσω την υπογραφή της παραμέτρου συνάρτησης χρησιμοποιώντας το a γραφομηχανή. Η λίστα 1 εμφανίζει ένα αρχείο κεφαλίδας C ++ με το όνομα simpson.h που καθορίζει και τα δύο γραφομηχανή για την παράμετρο συνάρτησης και τη διεπαφή προγραμματισμού για μια συνάρτηση C ++ που ονομάζεται ενσωματώνουν. Το σώμα λειτουργίας για ενσωματώνουν περιέχεται σε ένα αρχείο πηγαίου κώδικα C ++ με το όνομα simpson.cpp (δεν εμφανίζεται) και παρέχει την εφαρμογή για τον Κανόνα του Simpson.

Λίστα 1. C ++ αρχείο κεφαλίδας για το Simpson's Rule

 #if! define (SIMPSON_H) #define SIMPSON_H #include χρησιμοποιώντας το namespace std; typedef double DoubleFunction (διπλό x); διπλή ολοκλήρωση (DoubleFunction f, double a, double b, int n) ρίξτε (μη έγκυρο όρισμα); #τέλος εαν 

Κλήση ενσωματώνουν είναι απλή στο C ++. Ως απλό παράδειγμα, ας υποθέσουμε ότι θέλετε να χρησιμοποιήσετε τον Κανόνα του Simpson για να προσεγγίσετε το ολοκλήρωμα του ημίτονο λειτουργία από 0 έως π (πι) χρησιμοποιώντας 30 υποδιαστήματα. (Όποιος έχει ολοκληρώσει τον Λογισμό θα πρέπει να μπορώ να υπολογίσω την απάντηση χωρίς τη βοήθεια μιας αριθμομηχανής, καθιστώντας το αυτό μια καλή δοκιμαστική περίπτωση για το ενσωματώνουν Υποθέτοντας ότι είχατε περιλαμβάνεται τα κατάλληλα αρχεία κεφαλίδας όπως και "simpson.h", θα μπορούσατε να καλέσετε τη λειτουργία ενσωματώνουν όπως φαίνεται στην καταχώριση 2.

Λίστα 2. C ++ call to function integrate

 διπλό αποτέλεσμα = ολοκλήρωση (sin, 0, M_PI, 30); 

Αυτό είναι το μόνο που υπάρχει. Στο C ++ περνάτε το ημίτονο λειτουργούν τόσο εύκολα όσο περνάτε τις άλλες τρεις παραμέτρους.

Ενα άλλο παράδειγμα

Αντί του κανόνα του Simpson θα μπορούσα να χρησιμοποιήσω εξίσου εύκολα τη μέθοδο διχοτομίας (γνωστός ο αλγόριθμος διχοτομίας) για την επίλυση μιας εξίσωσης της φόρμας f (x) = 0. Στην πραγματικότητα, ο πηγαίος κώδικας για αυτό το άρθρο περιλαμβάνει απλές υλοποιήσεις τόσο του Κανονισμού Simpson όσο και της μεθόδου διχοτομής.

Λήψη Λήψη παραδειγμάτων πηγαίου κώδικα Java για αυτό το άρθρο. Δημιουργήθηκε από τον John I. Moore για το JavaWorld

Java χωρίς εκφράσεις λάμδα

Τώρα ας δούμε πώς μπορεί να καθοριστεί ο Κανόνας του Simpson στην Java. Ανεξάρτητα από το αν χρησιμοποιούμε ή όχι εκφράσεις lambda, χρησιμοποιούμε τη διεπαφή Java που εμφανίζεται στην καταχώριση 3 αντί για το C ++ γραφομηχανή για να καθορίσετε την υπογραφή της παραμέτρου συνάρτησης.

Λίστα 3. Διεπαφή Java για την παράμετρο λειτουργίας

 δημόσια διεπαφή DoubleFunction {public double f (double x); } 

Για να εφαρμόσουμε τον Κανόνα του Simpson στην Java δημιουργούμε μια τάξη με το όνομα Σίμπσον που περιέχει μια μέθοδο, ενσωματώνουν, με τέσσερις παραμέτρους παρόμοιες με αυτές που κάναμε στο C ++. Όπως με πολλές αυτοδύναμες μαθηματικές μεθόδους (δείτε, για παράδειγμα, java.lang.Math), θα κάνουμε ενσωματώνουν μια στατική μέθοδο. Μέθοδος ενσωματώνουν καθορίζεται ως εξής:

Λίστα 4. Υπογραφή Java για ενσωμάτωση μεθόδου στην τάξη Simpson

 δημόσια στατική διπλή ενσωμάτωση (DoubleFunction df, double a, double b, int n) 

Όλα όσα έχουμε κάνει μέχρι τώρα στην Java είναι ανεξάρτητα από το αν θα χρησιμοποιήσουμε ή όχι εκφράσεις λάμδα. Η κύρια διαφορά με τις εκφράσεις λάμδα είναι ο τρόπος με τον οποίο περνάμε τις παραμέτρους (πιο συγκεκριμένα, πώς περνάμε την παράμετρο συνάρτησης) σε μια κλήση στη μέθοδο ενσωματώνουν. Πρώτα θα επεξηγήσω πώς θα γίνει αυτό σε εκδόσεις Java πριν από την έκδοση 8. δηλαδή, χωρίς εκφράσεις λάμδα. Όπως με το παράδειγμα C ++, ας υποθέσουμε ότι θέλουμε να προσεγγίσουμε το ολοκλήρωμα του ημίτονο λειτουργία από 0 έως π (πι) χρησιμοποιώντας 30 υποδιαστήματα.

Χρήση του μοτίβου προσαρμογέα για τη λειτουργία ημιτονοειδούς

Στην Java έχουμε μια εφαρμογή του ημίτονο λειτουργία διαθέσιμη σε java.lang.Math, αλλά με εκδόσεις Java πριν από το Java 8, δεν υπάρχει απλός, άμεσος τρόπος για να το περάσετε ημίτονο συνάρτηση με τη μέθοδο ενσωματώνουν στην τάξη Σίμπσον. Μια προσέγγιση είναι να χρησιμοποιήσετε το μοτίβο προσαρμογέα. Σε αυτήν την περίπτωση θα γράψαμε μια απλή τάξη προσαρμογέα που εφαρμόζει το Διπλή λειτουργία διεπαφή και το προσαρμόζει για να καλέσει το ημίτονο συνάρτηση, όπως φαίνεται στην καταχώριση 5.

Λίστα 5. Κατηγορία προσαρμογέα για τη μέθοδο Math.sin

 εισαγωγή com.softmoore.math.DoubleFunction; Δημόσια τάξη DoubleFunctionSineAdapter υλοποιεί το DoubleFunction {public double f (double x) {return Math.sin (x); }} 

Χρησιμοποιώντας αυτήν την τάξη προσαρμογέα μπορούμε τώρα να καλέσουμε το ενσωματώνουν μέθοδος τάξης Σίμπσον όπως φαίνεται στην καταχώριση 6.

Λίστα 6. Χρησιμοποιώντας την τάξη προσαρμογέα για να καλέσετε τη μέθοδο Simpson.integrate

 DoubleFunctionSineAdapter sine = νέο DoubleFunctionSineAdapter (); διπλό αποτέλεσμα = Simpson.integrate (sine, 0, Math.PI, 30); 

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

Θα μπορούσαμε να συντομεύσουμε τον κωδικό που απαιτείται για την κλήση ενσωματώνουν ελαφρώς από δύο δηλώσεις Java σε μία, δημιουργώντας τη νέα παρουσία της κλάσης προσαρμογέα στην κλήση προς ενσωματώνουν. Η χρήση μιας ανώνυμης τάξης αντί της δημιουργίας μιας ξεχωριστής τάξης προσαρμογέα θα ήταν ένας άλλος τρόπος για να μειώσετε ελαφρώς τη συνολική προσπάθεια, όπως φαίνεται στην Λίστα 7.

Λίστα 7. Χρησιμοποιώντας μια ανώνυμη κλάση για να καλέσετε τη μέθοδο Simpson.integrate

 DoubleFunction sineAdapter = νέο DoubleFunction () {public double f (double x) {return Math.sin (x); }} διπλό αποτέλεσμα = Simpson.integrate (sineAdapter, 0, Math.PI, 30); 

Χωρίς εκφράσεις lambda, αυτό που βλέπετε στην Λίστα 7 είναι το λιγότερο ποσό κώδικα που θα μπορούσατε να γράψετε στην Java για να καλέσετε το ενσωματώνουν μέθοδος, αλλά εξακολουθεί να είναι πολύ πιο δυσκίνητη από ό, τι απαιτείται για το C ++. Επίσης, δεν είμαι τόσο ευχαριστημένος με τη χρήση ανώνυμων τάξεων, αν και τα έχω χρησιμοποιήσει πολύ στο παρελθόν. Δεν μου αρέσει η σύνταξη και πάντα το θεωρούσα αδέξιο αλλά απαραίτητο χάκερ στη γλώσσα Java.

Java με εκφράσεις λάμδα και λειτουργικές διεπαφές

Τώρα ας δούμε πώς θα μπορούσαμε να χρησιμοποιήσουμε τις εκφράσεις lambda στο Java 8 για να απλοποιήσουμε την κλήση ενσωματώνουν στην Ιάβα. Επειδή η διεπαφή Διπλή λειτουργία απαιτεί την εφαρμογή μιας μόνο μεθόδου που είναι υποψήφια για εκφράσεις λάμδα. Εάν γνωρίζουμε εκ των προτέρων ότι πρόκειται να χρησιμοποιήσουμε εκφράσεις lambda, μπορούμε να σχολιάσουμε τη διεπαφή με @FunctionalInterface, ένας νέος σχολιασμός για το Java 8 που λέει ότι έχουμε ένα λειτουργική διεπαφή. Λάβετε υπόψη ότι αυτός ο σχολιασμός δεν απαιτείται, αλλά μας δίνει έναν επιπλέον έλεγχο ότι όλα είναι συνεπή, παρόμοια με το @Καταπατώ σχολιασμός σε παλαιότερες εκδόσεις της Java.

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

Λίστα 8. Χρησιμοποιώντας μια έκφραση λάμδα για να καλέσετε τη μέθοδο Simpson.integrate

 DoubleFunction sine = (διπλό x) -> Math.sin (x); διπλό αποτέλεσμα = Simpson.integrate (sine, 0, Math.PI, 30); 

Σημειώστε ότι δεν χρειάστηκε να γράψουμε την τάξη του προσαρμογέα ή να δημιουργήσουμε μια παρουσία μιας ανώνυμης τάξης. Σημειώστε επίσης ότι θα μπορούσαμε να γράψουμε τα παραπάνω σε μία μόνο δήλωση αντικαθιστώντας την ίδια την έκφραση λάμδα, (διπλό x) -> Math.sin (x), για την παράμετρο ημίτονο στη δεύτερη δήλωση παραπάνω, εξαλείφοντας την πρώτη δήλωση. Τώρα πλησιάζουμε πολύ περισσότερο στην απλή σύνταξη που είχαμε στο C ++. Αλλά περίμενε! Υπάρχουν περισσότερα!

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

Λίστα 9. Μια εναλλακτική μορφή έκφρασης λάμδα σε κλήση στο Simpson.integrate

 διπλό αποτέλεσμα = Simpson.integrate (x -> Math.sin (x), 0, Math.PI, 30); 

Αλλά περίμενε! Υπάρχουν ακόμα περισσότερα!

Αναφορές μεθόδου στην Java 8

Ένα άλλο σχετικό χαρακτηριστικό στο Java 8 είναι κάτι που ονομάζεται a αναφορά μεθόδου, που μας επιτρέπει να αναφερόμαστε σε μια υπάρχουσα μέθοδο με το όνομα. Οι αναφορές της μεθόδου μπορούν να χρησιμοποιηθούν στη θέση των εκφράσεων λάμδα εφόσον ικανοποιούν τις απαιτήσεις της λειτουργικής διεπαφής. Όπως περιγράφεται στους πόρους, υπάρχουν πολλά διαφορετικά είδη αναφορών μεθόδων, καθεμία με ελαφρώς διαφορετική σύνταξη. Για στατικές μεθόδους η σύνταξη είναι Classname :: methodName. Επομένως, χρησιμοποιώντας μια μέθοδο αναφοράς, μπορούμε να καλέσουμε το ενσωματώνουν μέθοδος στην Java όσο πιο απλά μπορούσαμε στο C ++. Συγκρίνετε την κλήση Java 8 που εμφανίζεται στην καταχώριση 10 παρακάτω με την αρχική κλήση C ++ που εμφανίζεται στην καταχώριση 2 παραπάνω.

Λίστα 10. Χρησιμοποιώντας μια αναφορά μεθόδου για να καλέσετε το Simpson.integrate

 διπλό αποτέλεσμα = Simpson.integrate (Math :: sin, 0, Math.PI, 30);