Java의 함수형 프로그래밍
Java 8부터 언어는 다음을 지원합니다. 함수형 프로그래밍 람다 식, 메서드 참조, Stream API 및 Optional을 통해. 이러한 도구를 사용하면 더욱 간결하고 읽기 쉽고 선언적인 코드를 사용할 수 있습니다.
무엇을 배울 것인가
- 람다 표현식 및 구문
- 기능적 인터페이스
- 메소드 참조
- Stream API: 중간 및 터미널 작업
- 수집가 및 그룹화
- Null을 처리하는 선택 사항
람다 표현식
에이 람다 전달할 수 있는 익명 함수입니다. 매개변수로 사용하거나 변수에 할당합니다.
// 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() | 가치 창출 |
| 이중함수<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
그만큼 개울 일련의 요소를 처리할 수 있습니다. 선언적으로 병렬 작업을 지원합니다.
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 허용 값의 안전한 처리
- 게으른 평가: 스트림은 필요할 때까지 처리되지 않습니다.
에서 다음 기사 우리는 직면할 것이다 경쟁 그리고 멀티스레딩: 스레드, ExecutorService 및 동기화.







