Java での関数型プログラミング
Java 8 以降、この言語は 関数型プログラミング ラムダ式、メソッド参照、ストリーム API、およびオプションを使用します。 これらのツールを使用すると、より簡潔で読みやすい、宣言的なコードが可能になります。
何を学ぶか
- ラムダ式と構文
- 機能インターフェース
- メソッドのリファレンス
- ストリーム API: 中間操作と端末操作
- コレクターとグループ化
- Null を処理するためのオプション
ラムダ式
Una ラムダ これは渡すことができる匿名関数です パラメータとして、または変数に割り当てられます。
// Prima di Java 8 - classe anonima
Comparator<String> comparatorVecchio = new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
};
// Con Lambda - Java 8+
Comparator<String> comparatorLambda = (s1, s2) -> s1.length() - s2.length();
// Varianti sintattiche
// Un solo parametro: parentesi opzionali
Consumer<String> stampa = s -> System.out.println(s);
// Nessun parametro: parentesi vuote obbligatorie
Runnable azione = () -> System.out.println("Eseguito!");
// Corpo con più istruzioni: usare blocco {}
Comparator<String> conBlocco = (s1, s2) -> {
int diff = s1.length() - s2.length();
return diff != 0 ? diff : s1.compareTo(s2);
};
機能インターフェイス
主な機能インターフェイス
| インタフェース | 方法 | 使用 |
|---|---|---|
| 説教<T> | ブール値テスト(T) | 条件、フィルター |
| 関数<T,R> | R適用(T) | 変換 |
| 消費者<T> | 無効受け入れ(T) | 要素に対するアクション |
| サプライヤー<T> | T get() | 価値の生成 |
| BiFunction<T,U,R> | R適用(T,U) | 2つの入力、1つの出力 |
| 単項演算子<T> | T適用(T) | 同じタイプの入力/出力 |
import java.util.function.*;
public class EsempiFunzionali {
public static void main(String[] args) {
// Predicate: condizione
Predicate<Integer> isSufficiente = voto -> voto >= 18;
System.out.println(isSufficiente.test(25)); // true
System.out.println(isSufficiente.test(15)); // false
// Function: trasformazione
Function<String, Integer> lunghezza = s -> s.length();
System.out.println(lunghezza.apply("Java")); // 4
// Consumer: azione
Consumer<String> saluta = nome -> System.out.println("Ciao " + nome);
saluta.accept("Mario"); // Ciao Mario
// Supplier: generazione
Supplier<Double> randomVoto = () -> 18 + Math.random() * 13;
System.out.println(randomVoto.get()); // voto casuale 18-30
// Composizione
Predicate<Integer> isEccellente = voto -> voto >= 28;
Predicate<Integer> isBuono = isSufficiente.and(isEccellente.negate());
Function<String, String> maiuscolo = String::toUpperCase;
Function<String, String> conPrefisso = s -> "Prof. " + s;
Function<String, String> titolo = maiuscolo.andThen(conPrefisso);
System.out.println(titolo.apply("rossi")); // Prof. ROSSI
}
}
メソッドのリファレンス
import java.util.*;
public class MethodReferences {
public static void main(String[] args) {
List<String> nomi = Arrays.asList("Mario", "Laura", "Giuseppe");
// 1. Riferimento a metodo statico
// Lambda: s -> System.out.println(s)
nomi.forEach(System.out::println);
// 2. Riferimento a metodo di istanza (oggetto specifico)
String prefisso = "Studente: ";
// Lambda: s -> prefisso.concat(s)
nomi.stream()
.map(prefisso::concat)
.forEach(System.out::println);
// 3. Riferimento a metodo di istanza (tipo)
// Lambda: s -> s.toUpperCase()
nomi.stream()
.map(String::toUpperCase)
.forEach(System.out::println);
// 4. Riferimento a costruttore
// Lambda: s -> new StringBuilder(s)
nomi.stream()
.map(StringBuilder::new)
.forEach(sb -> System.out.println(sb.reverse()));
}
}
ストリームAPI
Gli ストリーム 要素のシーケンスを処理できるようにします 宣言的に、並列操作をサポートします。
import java.util.*;
import java.util.stream.*;
public class CreazioneStream {
public static void main(String[] args) {
// Da Collection
List<String> lista = Arrays.asList("A", "B", "C");
Stream<String> streamLista = lista.stream();
// Da array
String[] array = {"A", "B", "C"};
Stream<String> streamArray = Arrays.stream(array);
// Stream.of()
Stream<String> streamOf = Stream.of("A", "B", "C");
// Stream.generate() - infinito
Stream<Double> casuali = Stream.generate(Math::random).limit(5);
// Stream.iterate() - sequenza
Stream<Integer> pari = Stream.iterate(0, n -> n + 2).limit(10);
// IntStream, LongStream, DoubleStream - primitivi
IntStream interi = IntStream.range(1, 11); // 1-10
IntStream interiChiuso = IntStream.rangeClosed(1, 10); // 1-10
// Da file (righe)
// Stream<String> righe = Files.lines(Path.of("file.txt"));
}
}
中間操作
import java.util.*;
import java.util.stream.*;
public class OperazioniIntermedie {
public static void main(String[] args) {
List<Studente> studenti = Arrays.asList(
new Studente("Mario", 28, "Informatica"),
new Studente("Laura", 30, "Matematica"),
new Studente("Giuseppe", 25, "Informatica"),
new Studente("Anna", 27, "Fisica")
);
// FILTER: seleziona elementi
List<Studente> promossi = studenti.stream()
.filter(s -> s.getVoto() >= 18)
.collect(Collectors.toList());
// MAP: trasforma elementi
List<String> nomi = studenti.stream()
.map(Studente::getNome)
.collect(Collectors.toList());
// MAP con trasformazione
List<String> nomiMaiuscoli = studenti.stream()
.map(s -> s.getNome().toUpperCase())
.collect(Collectors.toList());
// FLATMAP: appiattisce stream di stream
List<List<String>> corsiPerStudente = Arrays.asList(
Arrays.asList("Java", "Python"),
Arrays.asList("SQL", "MongoDB"),
Arrays.asList("Java", "JavaScript")
);
List<String> tuttiCorsi = corsiPerStudente.stream()
.flatMap(List::stream)
.distinct()
.collect(Collectors.toList());
// [Java, Python, SQL, MongoDB, JavaScript]
// SORTED: ordina
List<Studente> perVoto = studenti.stream()
.sorted(Comparator.comparing(Studente::getVoto).reversed())
.collect(Collectors.toList());
// DISTINCT: rimuove duplicati
List<String> corsiUnici = studenti.stream()
.map(Studente::getCorso)
.distinct()
.collect(Collectors.toList());
// PEEK: debug (non modifica lo stream)
studenti.stream()
.peek(s -> System.out.println("Elaborando: " + s.getNome()))
.filter(s -> s.getVoto() >= 28)
.forEach(System.out::println);
// LIMIT e SKIP
List<Studente> primi2 = studenti.stream()
.limit(2)
.collect(Collectors.toList());
List<Studente> dopoPrimi2 = studenti.stream()
.skip(2)
.collect(Collectors.toList());
}
}
class Studente {
private String nome;
private int voto;
private String corso;
public Studente(String nome, int voto, String corso) {
this.nome = nome;
this.voto = voto;
this.corso = corso;
}
public String getNome() { return nome; }
public int getVoto() { return voto; }
public String getCorso() { return corso; }
}
端末の操作
import java.util.*;
import java.util.stream.*;
public class OperazioniTerminali {
public static void main(String[] args) {
List<Integer> voti = Arrays.asList(28, 30, 25, 27, 30, 24);
// FOREACH: azione su ogni elemento
voti.stream().forEach(System.out::println);
// COUNT: conta elementi
long quanti = voti.stream()
.filter(v -> v >= 28)
.count(); // 3
// SUM, AVERAGE, MIN, MAX (IntStream)
int somma = voti.stream()
.mapToInt(Integer::intValue)
.sum();
OptionalDouble media = voti.stream()
.mapToInt(Integer::intValue)
.average();
OptionalInt massimo = voti.stream()
.mapToInt(Integer::intValue)
.max();
// REDUCE: riduce a singolo valore
int sommaReduce = voti.stream()
.reduce(0, (a, b) -> a + b);
Optional<Integer> maxReduce = voti.stream()
.reduce(Integer::max);
// MATCH: verifica condizioni
boolean tuttiSufficienti = voti.stream()
.allMatch(v -> v >= 18); // true
boolean almenoUn30 = voti.stream()
.anyMatch(v -> v == 30); // true
boolean nessunoInsuff = voti.stream()
.noneMatch(v -> v < 18); // true
// FIND: trova elementi
Optional<Integer> primo = voti.stream()
.filter(v -> v >= 28)
.findFirst();
Optional<Integer> qualsiasi = voti.stream()
.filter(v -> v >= 28)
.findAny(); // Utile con parallelStream
// TOARRAY
Integer[] arrayVoti = voti.stream()
.filter(v -> v >= 25)
.toArray(Integer[]::new);
}
}
高度なコレクター
import java.util.*;
import java.util.stream.*;
public class CollectorsAvanzati {
public static void main(String[] args) {
List<Studente> studenti = Arrays.asList(
new Studente("Mario", 28, "Informatica"),
new Studente("Laura", 30, "Matematica"),
new Studente("Giuseppe", 25, "Informatica"),
new Studente("Anna", 27, "Fisica"),
new Studente("Marco", 30, "Informatica")
);
// GROUPING BY: raggruppa per chiave
Map<String, List<Studente>> perCorso = studenti.stream()
.collect(Collectors.groupingBy(Studente::getCorso));
// {Informatica=[Mario, Giuseppe, Marco], Matematica=[Laura], Fisica=[Anna]}
// GroupingBy con downstream collector
Map<String, Long> contaPerCorso = studenti.stream()
.collect(Collectors.groupingBy(
Studente::getCorso,
Collectors.counting()
));
// {Informatica=3, Matematica=1, Fisica=1}
Map<String, Double> mediaPerCorso = studenti.stream()
.collect(Collectors.groupingBy(
Studente::getCorso,
Collectors.averagingInt(Studente::getVoto)
));
Map<String, Optional<Studente>> migliorPerCorso = studenti.stream()
.collect(Collectors.groupingBy(
Studente::getCorso,
Collectors.maxBy(Comparator.comparing(Studente::getVoto))
));
// PARTITIONING BY: divide in true/false
Map<Boolean, List<Studente>> promossiVsNon = studenti.stream()
.collect(Collectors.partitioningBy(s -> s.getVoto() >= 18));
// {true=[tutti], false=[]}
Map<Boolean, Long> contaPromossi = studenti.stream()
.collect(Collectors.partitioningBy(
s -> s.getVoto() >= 28,
Collectors.counting()
));
// JOINING: concatena stringhe
String nomiConcatenati = studenti.stream()
.map(Studente::getNome)
.collect(Collectors.joining(", "));
// "Mario, Laura, Giuseppe, Anna, Marco"
String conPrefissoSuffisso = studenti.stream()
.map(Studente::getNome)
.collect(Collectors.joining(", ", "Studenti: [", "]"));
// "Studenti: [Mario, Laura, Giuseppe, Anna, Marco]"
// TO MAP
Map<String, Integer> nomeVoto = studenti.stream()
.collect(Collectors.toMap(
Studente::getNome,
Studente::getVoto
));
// SUMMARIZING
IntSummaryStatistics stats = studenti.stream()
.collect(Collectors.summarizingInt(Studente::getVoto));
System.out.println("Min: " + stats.getMin());
System.out.println("Max: " + stats.getMax());
System.out.println("Media: " + stats.getAverage());
System.out.println("Somma: " + stats.getSum());
System.out.println("Count: " + stats.getCount());
}
}
オプション
Optional それは値を含む場合も含まない場合もあるコンテナーです。
避ける NullPointerException.
import java.util.*;
public class EsempiOptional {
public static Optional<Studente> trovaStudente(List<Studente> studenti,
String nome) {
return studenti.stream()
.filter(s -> s.getNome().equals(nome))
.findFirst();
}
public static void main(String[] args) {
List<Studente> studenti = Arrays.asList(
new Studente("Mario", 28, "Informatica"),
new Studente("Laura", 30, "Matematica")
);
// Creare Optional
Optional<String> pieno = Optional.of("Valore");
Optional<String> vuoto = Optional.empty();
Optional<String> nullable = Optional.ofNullable(null);
// isPresent e isEmpty
Optional<Studente> mario = trovaStudente(studenti, "Mario");
if (mario.isPresent()) {
System.out.println("Trovato: " + mario.get().getVoto());
}
// ifPresent: esegue azione se presente
mario.ifPresent(s -> System.out.println("Voto: " + s.getVoto()));
// ifPresentOrElse (Java 9+)
Optional<Studente> paolo = trovaStudente(studenti, "Paolo");
paolo.ifPresentOrElse(
s -> System.out.println("Trovato: " + s.getNome()),
() -> System.out.println("Studente non trovato")
);
// orElse: valore default
Studente result = paolo.orElse(new Studente("Default", 0, "N/A"));
// orElseGet: supplier per default (lazy)
Studente resultLazy = paolo.orElseGet(
() -> new Studente("Default", 0, "N/A")
);
// orElseThrow: eccezione se vuoto
Studente obbligatorio = mario.orElseThrow(
() -> new NoSuchElementException("Studente richiesto!")
);
// map: trasforma se presente
Optional<String> nomeStudente = mario.map(Studente::getNome);
// flatMap: per Optional nested
Optional<String> corsoOpt = mario.flatMap(s ->
Optional.ofNullable(s.getCorso())
);
// filter: filtra valore
Optional<Studente> eccellente = mario
.filter(s -> s.getVoto() >= 28);
// Chaining completo
String risultato = trovaStudente(studenti, "Mario")
.filter(s -> s.getVoto() >= 25)
.map(Studente::getNome)
.map(String::toUpperCase)
.orElse("NON TROVATO");
System.out.println(risultato); // MARIO
}
}
完全な例: 学生登録簿の分析
import java.util.*;
import java.util.stream.*;
public class AnalisiRegistro {
public static void main(String[] args) {
List<Esame> esami = Arrays.asList(
new Esame("Mario", "Programmazione", 28, 2024),
new Esame("Mario", "Database", 30, 2024),
new Esame("Mario", "Reti", 25, 2024),
new Esame("Laura", "Programmazione", 30, 2024),
new Esame("Laura", "Database", 29, 2024),
new Esame("Giuseppe", "Programmazione", 24, 2024),
new Esame("Giuseppe", "Database", 26, 2023),
new Esame("Anna", "Programmazione", 30, 2024),
new Esame("Anna", "Reti", 28, 2024)
);
// 1. Media voti per studente
System.out.println("=== Media per studente ===");
Map<String, Double> mediaPerStudente = esami.stream()
.collect(Collectors.groupingBy(
Esame::getStudente,
Collectors.averagingInt(Esame::getVoto)
));
mediaPerStudente.forEach((nome, media) ->
System.out.printf("%s: %.2f%n", nome, media)
);
// 2. Studente con media più alta
System.out.println("\n=== Migliore studente ===");
mediaPerStudente.entrySet().stream()
.max(Map.Entry.comparingByValue())
.ifPresent(e ->
System.out.printf("%s con media %.2f%n", e.getKey(), e.getValue())
);
// 3. Distribuzione voti
System.out.println("\n=== Distribuzione voti ===");
Map<String, Long> distribuzione = esami.stream()
.collect(Collectors.groupingBy(
e -> {
int v = e.getVoto();
if (v >= 28) return "Ottimo (28-30)";
if (v >= 24) return "Buono (24-27)";
return "Sufficiente (18-23)";
},
Collectors.counting()
));
distribuzione.forEach((fascia, count) ->
System.out.println(fascia + ": " + count)
);
// 4. Esami per materia con lista studenti
System.out.println("\n=== Studenti per materia ===");
Map<String, String> studentiPerMateria = esami.stream()
.collect(Collectors.groupingBy(
Esame::getMateria,
Collectors.mapping(
Esame::getStudente,
Collectors.joining(", ")
)
));
studentiPerMateria.forEach((materia, studenti) ->
System.out.println(materia + ": " + studenti)
);
// 5. Top 3 voti
System.out.println("\n=== Top 3 voti ===");
esami.stream()
.sorted(Comparator.comparing(Esame::getVoto).reversed())
.limit(3)
.forEach(e -> System.out.printf("%s - %s: %d%n",
e.getStudente(), e.getMateria(), e.getVoto()));
// 6. Studenti con tutti esami >= 28
System.out.println("\n=== Studenti eccellenti ===");
Map<String, Boolean> tuttiEccellenti = esami.stream()
.collect(Collectors.groupingBy(
Esame::getStudente,
Collectors.collectingAndThen(
Collectors.toList(),
list -> list.stream().allMatch(e -> e.getVoto() >= 28)
)
));
tuttiEccellenti.entrySet().stream()
.filter(Map.Entry::getValue)
.map(Map.Entry::getKey)
.forEach(System.out::println);
// 7. Statistiche complete
System.out.println("\n=== Statistiche ===");
IntSummaryStatistics stats = esami.stream()
.mapToInt(Esame::getVoto)
.summaryStatistics();
System.out.println("Totale esami: " + stats.getCount());
System.out.println("Media: " + String.format("%.2f", stats.getAverage()));
System.out.println("Min: " + stats.getMin());
System.out.println("Max: " + stats.getMax());
}
}
class Esame {
private String studente;
private String materia;
private int voto;
private int anno;
public Esame(String studente, String materia, int voto, int anno) {
this.studente = studente;
this.materia = materia;
this.voto = voto;
this.anno = anno;
}
public String getStudente() { return studente; }
public String getMateria() { return materia; }
public int getVoto() { return voto; }
public int getAnno() { return anno; }
}
結論
Java の関数型プログラミングによりコードがより簡潔になります そして読みやすい。 Lambda、Stream、Optional は必須のツールです 現代の発展のために。
覚えておくべき重要なポイント
- ラムダ: 簡潔なコードのための匿名関数
- メソッドリファレンス: 短縮構文 (クラス::メソッド)
- ストリーム: データ処理のための宣言型パイプライン
- コレクター: グループ化、パーティショニング、統計
- オプション: Null 許容値の安全な処理
- 遅延評価: ストリームは必要になるまで処理されません
Nel 次の記事 私たちは直面します 競争 そしてマルチスレッド化: スレッド、ExecutorService、および同期。







