Java Language
Lambda-uttryck
Sök…
Introduktion
Lambda-uttryck ger ett tydligt och kortfattat sätt att implementera ett gränssnitt med en metod med hjälp av ett uttryck. De låter dig minska mängden kod du måste skapa och underhålla. Även om de liknar anonyma klasser, har de ingen typinformation av sig själva. Typinferensen måste ske.
Metodreferenser implementerar funktionella gränssnitt med befintliga metoder snarare än uttryck. De tillhör också lambda-familjen.
Syntax
- () -> {return expression; } // Nollaritet med funktionskroppen för att returnera ett värde.
- () -> uttryck // Kort för ovanstående deklaration; det finns ingen semikolon för uttryck.
- () -> {function-body} // Biverkning i lambda-uttrycket för att utföra operationer.
- parameterName -> expression // One-arity lambda expression. I lambda-uttryck med endast ett argument kan parentesen tas bort.
- (Type parameterName, Type secondParameterName, ...) -> expression // lambda utvärderar ett uttryck med parametrar listade till vänster
- (parameternamn, sekundparameternamn, ...) -> uttryck // Korthand som tar bort parametertyperna för parameternamn. Kan endast användas i sammanhang som kan dras av kompilatorn där den angivna parameterns lista stämmer överens med en (och endast en) av storleken på de förväntade funktionsgränssnitten.
Använda Lambda-uttryck för att sortera en samling
Sortera listor
Innan Java 8 var det nödvändigt att implementera java.util.Comparator
gränssnittet med en anonym (eller namngiven) klass när man sorterade en lista 1 :
List<Person> people = ...
Collections.sort(
people,
new Comparator<Person>() {
public int compare(Person p1, Person p2){
return p1.getFirstName().compareTo(p2.getFirstName());
}
}
);
Från och med Java 8 kan den anonyma klassen ersättas med ett lambda-uttryck. Observera att typerna för parametrarna p1
och p2
kan utelämnas, eftersom kompilatorn kommer att sluta dem automatiskt:
Collections.sort(
people,
(p1, p2) -> p1.getFirstName().compareTo(p2.getFirstName())
);
Exemplet kan förenklas med hjälp av Comparator.comparing
och metodreferenser uttryckta med ::
(dubbel kolon) symbolen.
Collections.sort(
people,
Comparator.comparing(Person::getFirstName)
);
En statisk import tillåter oss att uttrycka detta mer kortfattat, men det kan diskuteras om detta förbättrar den totala läsbarheten:
import static java.util.Collections.sort;
import static java.util.Comparator.comparing;
//...
sort(people, comparing(Person::getFirstName));
Jämförare byggda på detta sätt kan också kedjas ihop. Till exempel, efter att ha jämfört personer med deras förnamn, om det finns personer med samma förnamn, thenComparing
metoden med jämförelse med efternamn:
sort(people, comparing(Person::getFirstName).thenComparing(Person::getLastName));
1 - Observera att Collections.sort (...) bara fungerar på samlingar som är subtyper i List
. Set
och Collection
API: erna innebär inte någon beställning av elementen.
Sortera kartor
Du kan sortera posterna på en HashMap
efter värde på liknande sätt. (Observera att en LinkedHashMap
måste användas som mål. Nycklarna i en vanlig HashMap
är oordnade.)
Map<String, Integer> map = new HashMap(); // ... or any other Map class
// populate the map
map = map.entrySet()
.stream()
.sorted(Map.Entry.<String, Integer>comparingByValue())
.collect(Collectors.toMap(k -> k.getKey(), v -> v.getValue(),
(k, v) -> k, LinkedHashMap::new));
Introduktion till Java lambdas
Funktionella gränssnitt
Lambdas kan bara fungera på ett funktionellt gränssnitt, som är ett gränssnitt med bara en abstrakt metod. Funktionella gränssnitt kan ha valfritt antal default
eller static
metoder. (Av denna anledning kallas de ibland Single Abstract Method Interfaces, eller SAM Interfaces).
interface Foo1 {
void bar();
}
interface Foo2 {
int bar(boolean baz);
}
interface Foo3 {
String bar(Object baz, int mink);
}
interface Foo4 {
default String bar() { // default so not counted
return "baz";
}
void quux();
}
Vid deklarering av ett funktionellt gränssnitt kan @FunctionalInterface
kommentarer läggas till. Detta har ingen speciell effekt, men ett kompilatorfel kommer att genereras om denna kommentar tillämpas på ett gränssnitt som inte är funktionellt, och därmed fungerar som en påminnelse om att gränssnittet inte ska ändras.
@FunctionalInterface
interface Foo5 {
void bar();
}
@FunctionalInterface
interface BlankFoo1 extends Foo3 { // inherits abstract method from Foo3
}
@FunctionalInterface
interface Foo6 {
void bar();
boolean equals(Object obj); // overrides one of Object's method so not counted
}
Omvänt är detta inte ett funktionellt gränssnitt, eftersom det har mer än en abstrakt metod:
interface BadFoo {
void bar();
void quux(); // <-- Second method prevents lambda: which one should
// be considered as lambda?
}
Detta är inte heller ett funktionellt gränssnitt, eftersom det inte har några metoder:
interface BlankFoo2 { }
Notera följande. Anta att du har det
interface Parent { public int parentMethod(); }
och
interface Child extends Parent { public int ChildMethod(); }
Då kan Child
inte vara ett funktionellt gränssnitt eftersom det har två specifika metoder.
Java 8 tillhandahåller också ett antal generiska mallade funktionsgränssnitt i paketet java.util.function
. Det inbyggda gränssnittet Predicate<T>
slår till exempel in en enda metod som matar in ett värde av typ T
och returnerar en boolean
.
Lambda-uttryck
Grundstrukturen för ett Lambda-uttryck är:
fi
kommer då att hålla en singletoninstans av en klass, liknande en anonym klass, som implementerar FunctionalInterface
och där den ena metodens definition är { System.out.println("Hello"); }
. Med andra ord är ovanstående mestadels motsvarande:
FunctionalInterface fi = new FunctionalInterface() {
@Override
public void theOneMethod() {
System.out.println("Hello");
}
};
Lambda är bara "mestadels ekvivalent" till den anonyma klassen eftersom i en lambda hänvisar betydelsen av uttryck som this
, super
eller toString()
den klass inom vilken uppdraget sker, inte det nyskapade objektet.
Du kan inte ange metodens namn när du använder en lambda - men det borde du inte behöva, eftersom ett funktionellt gränssnitt bara måste ha en abstrakt metod, så Java åsidosätter den.
I de fall då typen av lambda inte är säker (t.ex. överbelastade metoder) kan du lägga till en roll till lambda för att berätta kompilatorn vad dess typ ska vara, så:
Object fooHolder = (Foo1) () -> System.out.println("Hello");
System.out.println(fooHolder instanceof Foo1); // returns true
Om det funktionella gränssnittets enda metod tar parametrar, bör de lokala formella namnen på dessa visas mellan parenteserna i lambda. Det finns inget behov att deklarera parametertypen eller returnera eftersom dessa tas från gränssnittet (även om det inte är ett fel att deklarera parametertyperna om du vill). Således är dessa två exempel likvärdiga:
Foo2 longFoo = new Foo2() {
@Override
public int bar(boolean baz) {
return baz ? 1 : 0;
}
};
Foo2 shortFoo = (x) -> { return x ? 1 : 0; };
Parenteserna runt argumentet kan utelämnas om funktionen bara har ett argument:
Foo2 np = x -> { return x ? 1 : 0; }; // okay
Foo3 np2 = x, y -> x.toString() + y // not okay
Implicit Returns
Om koden som placeras inuti ett lambda är ett Java- uttryck snarare än ett uttalande , behandlas den som en metod som returnerar värdet på uttrycket. Följande två är således likvärdiga:
IntUnaryOperator addOneShort = (x) -> (x + 1);
IntUnaryOperator addOneLong = (x) -> { return (x + 1); };
Få åtkomst till lokala variabler (värdeslutningar)
Eftersom lambdas är syntaktisk kortfattning för anonyma klasser följer de samma regler för att få tillgång till lokala variabler i det bifogade omfånget; variablerna måste behandlas som final
och inte modifieras inuti lambda.
IntUnaryOperator makeAdder(int amount) {
return (x) -> (x + amount); // Legal even though amount will go out of scope
// because amount is not modified
}
IntUnaryOperator makeAccumulator(int value) {
return (x) -> { value += x; return value; }; // Will not compile
}
Om det är nödvändigt att linda in en förändrad variabel på detta sätt, bör ett vanligt objekt som har en kopia av variabeln användas. Läs mer i Java-stängningar med lambda-uttryck.
Acceptera Lambdas
Eftersom en lambda är en implementering av ett gränssnitt behöver inget särskilt göras för att en metod ska acceptera en lambda: varje funktion som tar ett funktionellt gränssnitt kan också acceptera en lambda.
public void passMeALambda(Foo1 f) {
f.bar();
}
passMeALambda(() -> System.out.println("Lambda called"));
Typen av ett Lambda-uttryck
Ett lambda-uttryck har i sig inte någon specifik typ. Även om det är sant att typerna och antalet parametrar, tillsammans med typen av returvärde kan förmedla viss typinformation, kommer sådan information endast att begränsa vilka typer den kan tilldelas. Lambda får en typ när den tilldelas en funktionell gränssnitttyp på ett av följande sätt:
- Direkt tilldelning till en funktionell typ, t.ex.
myPredicate = s -> s.isEmpty()
- Att skicka den som en parameter som har en funktionell typ, t.ex.
stream.filter(s -> s.isEmpty())
- Återvända den från en funktion som returnerar en funktionell typ, t.ex.
return s -> s.isEmpty()
- Kasta den till en funktionell typ, t.ex.
(Predicate<String>) s -> s.isEmpty()
Tills en sådan tilldelning till en funktionell typ görs har lambda inte en bestämd typ. För att illustrera, överväga lambda-uttrycket o -> o.isEmpty()
. Samma lambda-uttryck kan tilldelas många olika funktionella typer:
Predicate<String> javaStringPred = o -> o.isEmpty();
Function<String, Boolean> javaFunc = o -> o.isEmpty();
Predicate<List> javaListPred = o -> o.isEmpty();
Consumer<String> javaStringConsumer = o -> o.isEmpty(); // return value is ignored!
com.google.common.base.Predicate<String> guavaPredicate = o -> o.isEmpty();
Nu när de tilldelats är exemplen som visas av helt olika typer även om lambda-uttryck såg ut lika, och de kan inte tilldelas varandra.
Metodreferenser
Metodreferenser tillåter fördefinierade statiska eller instansmetoder som följer ett kompatibelt funktionellt gränssnitt att skickas som argument istället för ett anonymt lambda-uttryck.
Antag att vi har en modell:
class Person {
private final String name;
private final String surname;
public Person(String name, String surname){
this.name = name;
this.surname = surname;
}
public String getName(){ return name; }
public String getSurname(){ return surname; }
}
List<Person> people = getSomePeople();
Instansmetodreferens (till en godtycklig instans)
people.stream().map(Person::getName)
Motsvarande lambda:
people.stream().map(person -> person.getName())
I det här exemplet getName()
av typen Person
. Eftersom det är känt att det är av samlingstypen kommer metoden i förekomsten (känd senare) att åberopas.
Instansmetodreferens (till en specifik instans)
people.forEach(System.out::println);
Eftersom System.out
är en instans av PrintStream
, PrintStream
en PrintStream
till denna specifika instans som ett argument.
Motsvarande lambda:
people.forEach(person -> System.out.println(person));
Statisk metodreferens
Även för att transformera strömmar kan vi tillämpa referenser till statiska metoder:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
numbers.stream().map(String::valueOf)
I det här exemplet hänvisas till valueOf()
statisk valueOf()
på String
. Därför skickas valueOf()
i samlingen som ett argument till valueOf()
.
Motsvarande lambda:
numbers.stream().map(num -> String.valueOf(num))
Hänvisning till en konstruktör
List<String> strings = Arrays.asList("1", "2", "3");
strings.stream().map(Integer::new)
Läs Samla element i en ström i en samling för att se hur du samlar in element till samling.
Den enskilda strängargumentkonstruktorn av Integer
används här för att konstruera ett heltal med den sträng som tillhandahålls som argumentet. I detta fall, så länge strängen representerar ett nummer, kommer strömmen att kartläggas till heltal. Motsvarande lambda:
strings.stream().map(s -> new Integer(s));
Fusklapp
Metodreferensformat | Koda | Ekvivalent Lambda |
---|---|---|
Statisk metod | TypeName::method | (args) -> TypeName.method(args) |
Icke-statisk metod (på förekomst * ) | instance::method | (args) -> instance.method(args) |
Icke-statisk metod (ingen instans) | TypeName::method | (instance, args) -> instance.method(args) |
Konstruktör ** | TypeName::new | (args) -> new TypeName(args) |
Arraykonstruktör | TypeName[]::new | (int size) -> new TypeName[size] |
* instance
kan vara vilket uttryck som helst som utvärderar till en referens till en instans, t.ex. getInstance()::method
, this::method
** Om TypeName
är en icke-statisk inre klass, är konstruktörens referens endast giltig inom ramen för en yttre klassinstans
Implementera flera gränssnitt
Ibland kanske du vill ha ett lambda-uttryck som implementerar mer än ett gränssnitt. Detta är mest användbart med markörgränssnitt (som java.io.Serializable ) eftersom de inte lägger till abstrakta metoder.
Till exempel vill du skapa en TreeSet
med en anpassad Comparator
och sedan serialisera den och skicka den över nätverket. Den triviala metoden:
TreeSet<Long> ts = new TreeSet<>((x, y) -> Long.compare(y, x));
fungerar inte eftersom lambda för komparatorn inte implementerar Serializable
. Du kan fixa detta genom att använda skärningstyper och uttryckligen ange att denna lambda måste vara seriebar:
TreeSet<Long> ts = new TreeSet<>(
(Comparator<Long> & Serializable) (x, y) -> Long.compare(y, x));
Om du ofta använder korsningstyper (till exempel om du använder ett ramverk som Apache Spark där nästan allt måste vara seriellt) kan du skapa tomma gränssnitt och använda dem i din kod istället:
public interface SerializableComparator extends Comparator<Long>, Serializable {}
public class CustomTreeSet {
public CustomTreeSet(SerializableComparator comparator) {}
}
På det här sättet är du garanterad att den godkända komparatorn kan vara seriell.
Lambdas och exekveringsmönster
Det finns flera bra exempel på att använda lambdas som ett funktionsinterface i enkla scenarier. Ett ganska vanligt användningsfall som kan förbättras av lambdas är det som kallas execute-Around-mönstret. I det här mönstret har du en uppsättning standarduppsättning / nedräkningskod som behövs för flera scenarier kring användningsfallspecifik kod. Några vanliga exempel på detta är fil io, databas io, försök / fånga block.
interface DataProcessor {
void process( Connection connection ) throws SQLException;;
}
public void doProcessing( DataProcessor processor ) throws SQLException{
try (Connection connection = DBUtil.getDatabaseConnection();) {
processor.process(connection);
connection.commit();
}
}
För att kalla den här metoden med en lambda kan det se ut:
public static void updateMyDAO(MyVO vo) throws DatabaseException {
doProcessing((Connection conn) -> MyDAO.update(conn, ObjectMapper.map(vo)));
}
Detta är inte begränsat till I / O-operationer. Det kan tillämpas på alla scenarier där liknande inställnings- / rivningsuppgifter är tillämpliga med mindre variationer. Den huvudsakliga fördelen med detta mönster är återanvändning av kod och upprätthållande av DRY (inte upprepa dig själv).
Använda lambda expression med ditt eget funktionella gränssnitt
Lambdas är avsedda att tillhandahålla inline implementeringskod för gränssnitt med en enda metod och förmågan att passera dem som vi har gjort med normala variabler. Vi kallar dem funktionellt gränssnitt.
Att skriva en Runnable i anonym klass och starta en tråd ser till exempel ut:
//Old way
new Thread(
new Runnable(){
public void run(){
System.out.println("run logic...");
}
}
).start();
//lambdas, from Java 8
new Thread(
()-> System.out.println("run logic...")
).start();
Nu, i linje med ovan, kan vi säga att du har något anpassat gränssnitt:
interface TwoArgInterface {
int operate(int a, int b);
}
Hur använder du lambda för att implementera detta gränssnitt i din kod? Samma som Runnable-exemplet som visas ovan. Se drivrutinsprogrammet nedan:
public class CustomLambda {
public static void main(String[] args) {
TwoArgInterface plusOperation = (a, b) -> a + b;
TwoArgInterface divideOperation = (a,b)->{
if (b==0) throw new IllegalArgumentException("Divisor can not be 0");
return a/b;
};
System.out.println("Plus operation of 3 and 5 is: " + plusOperation.operate(3, 5));
System.out.println("Divide operation 50 by 25 is: " + divideOperation.operate(50, 25));
}
}
"return" returnerar bara från lambda, inte den yttre metoden
return
återgår bara från lambda, inte den yttre metoden.
Se upp för att detta skiljer sig från Scala och Kotlin!
void threeTimes(IntConsumer r) {
for (int i = 0; i < 3; i++) {
r.accept(i);
}
}
void demo() {
threeTimes(i -> {
System.out.println(i);
return; // Return from lambda to threeTimes only!
});
}
Detta kan leda till oväntat beteende när man försöker skriva egna språkkonstruktioner, som i inbyggda konstruktioner som for
slingor return
uppför sig annorlunda:
void demo2() {
for (int i = 0; i < 3; i++) {
System.out.println(i);
return; // Return from 'demo2' entirely
}
}
I Scala och Kotlin skulle demo
och demo2
bara skriva ut 0
. Men detta är inte mer konsekvent. Java-metoden överensstämmer med refactoring och användning av klasser - return
i koden längst upp, och koden nedan fungerar på samma sätt:
void demo3() {
threeTimes(new MyIntConsumer());
}
class MyIntConsumer implements IntConsumer {
public void accept(int i) {
System.out.println(i);
return;
}
}
Därför är Java- return
mer konsekvent med klassmetoder och refactoring, men mindre med for
och while
inbyggda förblir dessa speciella.
På grund av detta är följande två likvärdiga i Java:
IntStream.range(1, 4)
.map(x -> x * x)
.forEach(System.out::println);
IntStream.range(1, 4)
.map(x -> { return x * x; })
.forEach(System.out::println);
Dessutom är användningen av try-with-resources säker i Java:
class Resource implements AutoCloseable {
public void close() { System.out.println("close()"); }
}
void executeAround(Consumer<Resource> f) {
try (Resource r = new Resource()) {
System.out.print("before ");
f.accept(r);
System.out.print("after ");
}
}
void demo4() {
executeAround(r -> {
System.out.print("accept() ");
return; // Does not return from demo4, but frees the resource.
});
}
kommer att skriva ut before accept() after close()
. I Scala- och Kotlins semantik skulle försöksresurserna inte stängas, utan skrivas ut before accept()
.
Java-stängningar med lambda-uttryck.
En lambda-stängning skapas när ett lambda-uttryck refererar till variablerna i ett slutande omfång (globalt eller lokalt). Reglerna för att göra detta är desamma som för inline metoder och anonyma klasser.
Lokala variabler från ett slutande omfång som används inom en lambda måste vara final
. Med Java 8 (den tidigaste versionen som stöder lambdas) behöver de inte förklaras final
i det yttre sammanhanget, utan måste behandlas på det sättet. Till exempel:
int n = 0; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
int i = n;
// do something
};
Detta är lagligt så länge värdet på n
variabeln inte ändras. Om du försöker ändra variabeln, inom eller utanför lambda, får du följande sammanställningsfel:
"lokala variabler hänvisade till från ett lambda-uttryck måste vara slutliga eller effektivt slutliga ".
Till exempel:
int n = 0;
Runnable r = () -> { // Using lambda
int i = n;
// do something
};
n++; // Will generate an error.
Om det är nödvändigt att använda en förändrad variabel inom en lambda är den normala metoden att förklara en final
kopia av variabeln och använda kopian. Till exempel
int n = 0;
final int k = n; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
int i = k;
// do something
};
n++; // Now will not generate an error
r.run(); // Will run with i = 0 because k was 0 when the lambda was created
Naturligtvis ser inte lambdas kropp förändringarna i den ursprungliga variabeln.
Observera att Java inte stöder riktiga stängningar. En Java-lambda kan inte skapas på ett sätt som gör att den kan se förändringar i den miljö där den initierades. Om du vill genomföra en stängning som observerar eller gör ändringar i dess miljö, bör du simulera den med en vanlig klass. Till exempel:
// Does not compile ...
public IntUnaryOperator createAccumulator() {
int value = 0;
IntUnaryOperator accumulate = (x) -> { value += x; return value; };
return accumulate;
}
Ovanstående exempel kommer inte att sammanställas av skäl som diskuterats tidigare. Vi kan lösa kompilationsfelet enligt följande:
// Compiles, but is incorrect ...
public class AccumulatorGenerator {
private int value = 0;
public IntUnaryOperator createAccumulator() {
IntUnaryOperator accumulate = (x) -> { value += x; return value; };
return accumulate;
}
}
Problemet är att detta bryter designkontraktet för gränssnittet IntUnaryOperator
som säger att instanser ska vara funktionella och statslösa. Om en sådan stängning överförs till inbyggda funktioner som accepterar funktionella objekt kan det orsaka kraschar eller felaktigt beteende. Stängningar som kapslar in muterbara tillstånd bör implementeras som vanliga klasser. Till exempel.
// Correct ...
public class Accumulator {
private int value = 0;
public int accumulate(int x) {
value += x;
return value;
}
}
Lambda - Lyssnarexempel
Anonym klass lyssnare
Innan Java 8 är det mycket vanligt att en anonym klass används för att hantera klickhändelse för en JButton, som visas i följande kod. Detta exempel visar hur man implementerar en anonym lyssnare inom ramen för btn.addActionListener
.
JButton btn = new JButton("My Button");
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Button was pressed");
}
});
Lambda lyssnare
Eftersom ActionListener
gränssnittet bara definierar en metod actionPerformed()
är det ett funktionellt gränssnitt som innebär att det finns en plats att använda Lambda-uttryck för att ersätta pannplattkoden. Ovanstående exempel kan skrivas om med hjälp av Lambda-uttryck på följande sätt:
JButton btn = new JButton("My Button");
btn.addActionListener(e -> {
System.out.println("Button was pressed");
});
Traditionell stil till Lambda stil
Traditionellt sätt
interface MathOperation{
boolean unaryOperation(int num);
}
public class LambdaTry {
public static void main(String[] args) {
MathOperation isEven = new MathOperation() {
@Override
public boolean unaryOperation(int num) {
return num%2 == 0;
}
};
System.out.println(isEven.unaryOperation(25));
System.out.println(isEven.unaryOperation(20));
}
}
Lambdastil
- Ta bort klassens namn och funktionella gränssnittskropp.
public class LambdaTry {
public static void main(String[] args) {
MathOperation isEven = (int num) -> {
return num%2 == 0;
};
System.out.println(isEven.unaryOperation(25));
System.out.println(isEven.unaryOperation(20));
}
}
- Valfri typdeklaration
MathOperation isEven = (num) -> {
return num%2 == 0;
};
- Valfri parentes kring parameter, om det är en enda parameter
MathOperation isEven = num -> {
return num%2 == 0;
};
- Valfritt lockigt stöd, om det bara finns en linje i funktionskroppen
- Valfritt returnyckel, om det bara finns en rad i funktionskroppen
MathOperation isEven = num -> num%2 == 0;
Lambdas och minnesanvändning
Eftersom Java lambdas är stängningar, kan de "fånga" värdena på variabler i det bifogade lexikala omfånget. Medan inte alla lambdas fångar någonting - enkla lambdas som s -> s.length()
fångar ingenting och kallas statslösa - kräver det att fånga lambdas ett tillfälligt objekt för att hålla de infångade variablerna. I det här kodavsnittet är lambda () -> j
en fånga lambda och kan leda till att ett objekt tilldelas när det utvärderas:
public static void main(String[] args) throws Exception {
for (int i = 0; i < 1000000000; i++) {
int j = i;
doSomethingWithLambda(() -> j);
}
}
Även om det kanske inte är omedelbart uppenbart eftersom det new
sökordet inte visas någonstans i kodavsnittet kan koden skapa 1 000 000 000 separata objekt för att representera förekomsten av uttrycket () -> j
lambda. Det bör emellertid också noteras att framtida versioner av Java 1 kanske kan optimera detta så att lambda-instansen vid körning återanvändes eller representerades på något annat sätt.
1 - Till exempel introducerar Java 9 en valfri "länk" -fas till Java-byggsekvensen som ger möjlighet att göra globala optimeringar som denna.
Använda lambda-uttryck och predikat för att hämta ett visst värde från en lista
Från och med Java 8 kan du använda lambda-uttryck och predikat.
Exempel: Använd ett lambda-uttryck och ett predikat för att få ett visst värde från en lista. I detta exempel kommer varje person att skrivas ut med det faktum om de är 18 eller äldre eller inte.
Personklass:
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() { return age; }
public String getName() { return name; }
}
Det inbyggda gränssnittet Predikat från java.util.function.Predicate-paketen är ett funktionellt gränssnitt med en boolean test(T t)
-metod.
Exempel på användning:
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class LambdaExample {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Jeroen", 20));
personList.add(new Person("Jack", 5));
personList.add(new Person("Lisa", 19));
print(personList, p -> p.getAge() >= 18);
}
private static void print(List<Person> personList, Predicate<Person> checker) {
for (Person person : personList) {
if (checker.test(person)) {
System.out.print(person + " matches your expression.");
} else {
System.out.println(person + " doesn't match your expression.");
}
}
}
}
print(personList, p -> p.getAge() >= 18);
metoden tar ett lambda-uttryck (eftersom Predikatet används en parameter) där du kan definiera det uttryck som behövs. Checker testmetod kontrollerar om detta uttryck är korrekt eller inte: checker.test(person)
.
Du kan enkelt ändra detta till något annat, till exempel för att print(personList, p -> p.getName().startsWith("J"));
. Detta kommer att kontrollera om personens namn börjar med en "J".