[JAVA] Une note de revue sur l'interface fonctionnelle

Aperçu

Ceci est une revue de l'interface fonctionnelle introduite dans Java 1.8.

environnement

référence

Un simple examen de la façon d'écrire

Dans ce code, la méthode forEach reçoit une interface fonctionnelle de type Consumer. Cette écriture est simplifiée, mais

List<String> list = List.of("a", "b", "c");
list.forEach(System.out::println);

Si vous écrivez Java 1.8 ou une version antérieure, ce sera comme suit.

public class PrintLine implements Consumer<Object> {
	@Override
	public void accept(Object t) {
		System.out.println(t);
	}
}
List<String> list = List.of("a", "b", "c");
PrintLine println = new PrintLine();
list.forEach(println);

De plus, si vous écrivez une classe anonyme, ce sera comme suit.

List<String> list = List.of("a", "b", "c");
list.forEach(new Consumer<Object>() {
	@Override
	public void accept(Object t) {
		System.out.println(t);
	}
});

Ces styles d'écriture peuvent être simplifiés en utilisant les expressions lambda introduites dans Java 1.8.

// (1)Si vous modifiez le style d'écriture de la classe anonyme en expression lambda, vous pouvez l'écrire comme ceci.
list.forEach((String t) -> {
	System.out.println(t);
});

// (2)Le type d'argument est explicite et peut être omis.
list.forEach((t) -> {
	System.out.println(t);
});

// (3)S'il n'y a qu'un seul argument, l'argument()Peut être omis.
list.forEach(t -> {
	System.out.println(t);
});

// (4)Si le code dans le bloc est une ligne{}Peut être omis. Vous n'avez même pas besoin d'un point-virgule dans votre code.
list.forEach(t -> System.out.println(t));

// (5)Méthode abstraite d'interface fonctionnelle (dans ce cas Consumer).accepter) signature et
//Si les signatures des méthodes à exécuter correspondent, il peut être écrit comme référence de méthode.
list.forEach(System.out::println);

Annotation FunctionalInterface

Les interfaces fonctionnelles sont annotées avec @ FunctionalInterface,

@FunctionalInterface
public interface Consumer<T> {
  //...
}

Un type d'annotation d'information utilisé pour indiquer qu'une déclaration de type d'interface est destinée à être une interface fonctionnelle telle que définie dans la spécification du langage Java. Conceptuellement, une interface fonctionnelle n'a qu'une seule méthode abstraite.

Comme indiqué ci-dessus, la condition qui satisfait à la définition d'une interface fonctionnelle est que l'interface n'a qu'une seule méthode abstraite déclarée.

Indépendamment du fait que le type d'annotation FunctionalInterface existe ou non dans la déclaration d'interface, le compilateur traite toute interface répondant à la définition d'une interface fonctionnelle comme une interface fonctionnelle.

De plus, si cette condition est remplie, le compilateur sera traité comme une interface fonctionnelle avec ou sans cette annotation.

package java.util.function

Ce package définit une interface fonctionnelle de base. Des exemples typiques sont «Consommateur», «Fonction», «Prédicat» et «Fournisseur», et il y a aussi «IntCounsumer »et« IntFunction »qui sont spécialisés de ceux-ci. Il existe également des interfaces fonctionnelles implémentées à des fins spécifiques en dehors de ce package.

Consommateur (T pour annuler la fonction monomorphe)

Paramètres de type: Type d'entrée T-Operation

Méthode abstraite


void accept(T t);

BiConsumer Il existe également une interface fonctionnelle appelée «BiConsumer» qui accepte deux arguments, qui est une spécialisation de «Consommateur».

Paramètres de type: T --Type du premier argument de l'opération U --Type du deuxième argument de l'opération

Méthode abstraite


void accept​(T t, U u);

Exemple d'utilisation dans JDK

java.base : java.util.Iterator<E>

forEach

L'un des moyens les plus courants d'utiliser Consumer est la méthode forEach de l'itérateur.

Iterable.forEach


default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

échantillon Depuis Java 1.8, n'écrivez pas comme (1), mais écrivez comme (2) ou (3) avec une expression lambda. Si vous voulez utiliser une ligne comme (2), vous n'avez pas besoin de la mettre dans un bloc et vous pouvez écrire comme (3). De plus, le code peut être simplifié en l'écrivant comme référence de méthode comme dans (4).

List<String> list = List.of("a", "b", "c");

// (1)
list.forEach(new Consumer<String>() {
	@Override
	public void accept(String t) {
		System.out.println(t);
	}
});

// (2)
list.forEach(s -> {
	System.out.println(s);
});

// (3)
list.forEach(s -> System.out.println(s));

// (4)Référence de la méthode
list.forEach(System.out::println);

Fonction (fonction à un seul terme de T à R)

Paramètres de type: Type d'entrée T-Function R - Type de résultat de la fonction

Méthode abstraite


R apply(T t);

BiFunction Il existe également une interface fonctionnelle appelée «BiFunction» qui prend deux arguments, qui est une spécialisation de «Function».

Paramètres de type: T --Type du premier argument de la fonction U --Type du deuxième argument de la fonction R --Type de résultat de la fonction

Méthode abstraite


R apply​(T t, U u);

Exemple d'utilisation dans JDK

java.base : java.util.Map<K,​V>

computeIfAbsent

Map.computeIfAbsent


default V computeIfAbsent(K key,
        Function<? super K, ? extends V> mappingFunction) {
    Objects.requireNonNull(mappingFunction);
    V v;
    if ((v = get(key)) == null) {
        V newValue;
        if ((newValue = mappingFunction.apply(key)) != null) {
            put(key, newValue);
            return newValue;
        }
    }

    return v;
}

échantillon

Map<String, Integer> map = new HashMap<>();

// (1)
Integer value = map.computeIfAbsent("apple", new Function<String, Integer>(){
	@Override
	public Integer apply(String t) {
		return t.length();
	}
});

// (2)
value = map.computeIfAbsent("banana", s -> {
	return s.length();
});

// (3)
value = map.computeIfAbsent("cherry", s -> s.length());

// (4)Référence de méthode d'instance
value = map.computeIfAbsent("durian", String::length);

UnaryOperator<T>

C'est une interface de type de fonction qui a Function dans la super interface et se spécialise dans Function qui renvoie le même type que le type d'argument.

Paramètres de type: Opérandes T-Operator et types de résultats

Exemple d'utilisation dans JDK

java.base : List<E>

replaceAll

List.replaceAll


default void replaceAll(UnaryOperator<E> operator) {
    Objects.requireNonNull(operator);
    final ListIterator<E> li = this.listIterator();
    while (li.hasNext()) {
        li.set(operator.apply(li.next()));
    }
}

échantillon

List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("cherry");

// (1)
list.replaceAll(new UnaryOperator<String>() {
	@Override
	public String apply(String t) {
		return t.toUpperCase();
	}
});

// (2)
list.replaceAll(t -> t.toUpperCase());

// (3)Référence de méthode d'instance
list.replaceAll(String::toUpperCase);

UnaryOperator.identity

L'interface UnaryOperator a une méthode statique appelée identité. (Il se trouve également dans l'interface de fonction.)

static <T> UnaryOperator<T> identity() {
    return t -> t;
}

Le code que vous voyez souvent comme exemple d'utilisation de cette méthode convertit une liste en carte comme indiqué ci-dessous. Cette conversion définit l'id de la classe Item dans la clé de la mappe et l'instance de la classe Item dans la valeur de la mappe, mais l'intention du code devient claire en utilisant la méthode d'identité qui renvoie l'argument d'entrée.

List<Item> list = new ArrayList<>();
list.add(new Item(1L, "apple"));
list.add(new Item(2L, "banana"));
list.add(new Item(3L, "cherry"));

//Map<Long, Item> fruitIdMap = list.stream()
//				.collect(Collectors.toMap(Item::getId, i -> i));
// more than better
Map<Long, Item> fruitIdMap = list.stream()
				.collect(Collectors.toMap(Item::getId, UnaryOperator.identity()));

System.out.println(fruitIdMap);
// {1=Item [id=1, name=apple], 2=Item [id=2, name=banana], 3=Item [id=3, name=cherry]}

Prédicat (T en fonction booléenne)

Paramètres de type: Type d'entrée T-Predicate

Méthode abstraite


boolean test(T t);

BiPredicate Il existe également une interface fonctionnelle appelée «BiPredicate» qui prend deux arguments qui sont une spécialisation de «Predicate».

Paramètres de type: T --Type du premier argument du prédicat U --Type du deuxième argument du prédicat

Méthode abstraite


boolean test​(T t, U u);

Exemple d'utilisation dans JDK

java.base : java.util.Collection<E>

removeIf

Collection.removeIf


default boolean removeIf(Predicate<? super E> filter) {
    Objects.requireNonNull(filter);
    boolean removed = false;
    final Iterator<E> each = iterator();
    while (each.hasNext()) {
        if (filter.test(each.next())) {
            each.remove();
            removed = true;
        }
    }
    return removed;
}

échantillon Ceci est un exemple qui supprime les nombres pairs de la liste. Puisque removeIf a un effet secondaire sur la liste, une erreur se produira s'il s'agit d'une liste immuable.

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);

// (1)
list.removeIf(new Predicate<Integer>() {
	@Override
	public boolean test(Integer t) {
		return t % 2 == 0;
	}
});

// (2)
list.removeIf(t -> {
	return t % 2 == 0;
});

// (3)
list.removeIf(t -> t % 2 == 0);

java.base : java.util.Optional<T>

filter

Optional.filter


public Optional<T> filter(Predicate<? super T> predicate) {
    Objects.requireNonNull(predicate);
    if (!isPresent()) {
        return this;
    } else {
        return predicate.test(value) ? this : empty();
    }
}

échantillon

Optional<Integer> opt = Optional.of(1);

// (1)
Optional<Integer> result = opt.filter(new Predicate<Integer>() {
	@Override
	public boolean test(Integer t) {
		return t % 2 == 0;
	}
});

// (2)
result = opt.filter(t -> {
	return t % 2 == 0;
});

// (3)
result = opt.filter(t -> t % 2 == 0);

result.ifPresentOrElse(System.out::println, () -> {
	System.out.println("it's odd number");
});
// → it's odd number

java.base : java.util.regex.Pattern

asPredicate

Pattern.asPredicate


public Predicate<String> asPredicate() {
    return s -> matcher(s).find();
}

échantillon

Pattern pattern = Pattern.compile("^\\d+$");

List<String> list = List.of("123", "abc", "456", "78d");

List<String> result = list.stream()
						.filter(pattern.asPredicate())
						.collect(Collectors.toList());

result.forEach(System.out::println);
// → 123
// → 456

Fournisseur (fonction infinie de R)

Paramètres de type: T - Type de résultat fourni par ce fournisseur

Méthode abstraite


T get();

Exemple d'utilisation dans JDK

java.base : java.util.Optional<T>

orElseGet

Optional.orElseGet


public T orElseGet(Supplier<? extends T> supplier) {
    return value != null ? value : supplier.get();
}

échantillon

Optional<String> fruit = Optional.ofNullable(null);

// (1)
String result = fruit.orElseGet(new Supplier<String>() {
	@Override
	public String get() {
		return "banana";
	}
});

// (2)
result = fruit.orElseGet(() -> {
	return "banana";
});

// (3)
result = fruit.orElseGet(() -> "banana");

// (4)Référence de la méthode
result = fruit.orElseGet(SupplierDemo::getDefault);

SupplierDemo


public static String getDefault() {
	return "banana";
}

orElseThrow

Optional.orElseThrow


public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
    if (value != null) {
        return value;
    } else {
        throw exceptionSupplier.get();
    }
}

échantillon

Optional<String> fruit = Optional.ofNullable(null);

// (1)
String result = fruit.orElseThrow(new Supplier<RuntimeException>() {
	@Override
	public RuntimeException get() {
		return new FruitNotFoundException();
	}
});

// (2)
result = fruit.orElseThrow(() -> {
	return new FruitNotFoundException();
});

// (3)
result = fruit.orElseThrow(() -> new FruitNotFoundException());

// (4)Référence constructeur
result = fruit.orElseThrow(FruitNotFoundException::new);

FruitNotFoundException


class FruitNotFoundException extends RuntimeException {
	public FruitNotFoundException() {
		super();
	}
}

Exemple de référence de constructeur

** Exemple de référence de constructeur pour générer un tableau **

List<String> list = List.of("apple", "banana", "cherry", "durian", "elderberry");
// String[] fruits = list.toArray(size -> new String[size]);
// more than better?
String[] fruits = list.toArray(new String[0]);

Le code pour convertir la collection ci-dessus en tableau est le suivant lorsqu'il est écrit avec une référence de constructeur.

List<String> list = List.of("apple", "banana", "cherry", "durian", "elderberry");
String[] fruits = list.toArray(String[]::new);

java.logging : java.util.logging.Logger

log

Logger.log


public void log(Level level, Supplier<String> msgSupplier) {
    if (!isLoggable(level)) {
        return;
    }
    LogRecord lr = new LogRecord(level, msgSupplier.get());
    doLog(lr);
}

Autre que le package java.util.function

Cette interface existe depuis la version 1.8, mais elle peut être utilisée comme destination d'affectation pour les expressions lambda ou les références de méthode car elle répond aux exigences d'une interface fonctionnelle.

FileFilter

Méthode abstraite


boolean	accept​(File pathname);

Exemple d'utilisation dans JDK

java.base : File

listFiles

File.listFiles​


public File[] listFiles(FileFilter filter) {
    String ss[] = list();
    if (ss == null) return null;
    ArrayList<File> files = new ArrayList<>();
    for (String s : ss) {
        File f = new File(s, this);
        if ((filter == null) || filter.accept(f))
            files.add(f);
    }
    return files.toArray(new File[files.size()]);
}

échantillon

File directory = new File("D:\\temp");
Long limit = 1024L * 1024L * 7L;

File[] overLimitFiles = directory.listFiles(pathname -> pathname.length() > limit);

Runnable

Méthode abstraite


void run();

Exemple d'utilisation dans JDK

java.base : java.util.Optional<T>

ifPresentOrElse

public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {
    if (value != null) {
        action.accept(value);
    } else {
        emptyAction.run();
    }
}

échantillon

Optional<String> opt = Optional.ofNullable(null);
opt.ifPresentOrElse(s -> {
	System.out.println(s);
},() -> {
	System.out.println("null");
});

PathMatcher

Méthode abstraite


boolean	matches​(Path path);

Exemple d'utilisation dans JDK

java.base : java.nio.file.FileSystem

getPathMatcher

FileSystem.getPathMatcher


public abstract PathMatcher getPathMatcher(String syntaxAndPattern);

échantillon

S'écartant du sujet de l'article, la valeur passée à la méthode getPathMatcher est une chaîne de syntaxe et de motif séparés par deux points (:). Si vous spécifiez "glob" pour la syntaxe, vous pouvez spécifier un modèle plus simple similaire à une expression régulière pour Pattern, et si vous spécifiez "regex", vous pouvez spécifier une expression régulière pour Pattern.

FileSystem fileSystem = FileSystems.getDefault();
PathMatcher matcher = fileSystem.getPathMatcher("glob" + ":" + "**/*.java");

Path path = Paths.get("D:", "temp");
try {
	Files.walk(path).filter(matcher::matches).forEach(System.out::println);
} catch (IOException e) {
	throw new UncheckedIOException(e);
}

Comparator<T>

En plus de compare, cette interface déclare une méthode abstraite appelée boolean equals (Object obj), comme indiqué ci-dessous.

Si l'interface déclare une méthode abstraite qui remplace l'une des méthodes publiques de java.lang.Object, elle ne sera pas reflétée dans le nombre de méthodes abstraites dans l'interface. La raison en est que toute implémentation de cette interface inclut des implémentations de java.lang.Object ou d'ailleurs.

Il remplit les conditions d'une interface fonctionnelle.

Méthode abstraite


int compare(T o1, T o2);

Exemple d'utilisation dans JDK

java.base : Collections

min

Collections.min


@SuppressWarnings({"unchecked", "rawtypes"})
public static <T> T min(Collection<? extends T> coll, Comparator<? super T> comp) {
    if (comp==null)
        return (T)min((Collection) coll);

    Iterator<? extends T> i = coll.iterator();
    T candidate = i.next();

    while (i.hasNext()) {
        T next = i.next();
        if (comp.compare(next, candidate) < 0)
            candidate = next;
    }
    return candidate;
}

échantillon

Il s'agit d'un exemple qui trie la priorité et l'ID de la classe Job dans l'ordre croissant et renvoie le plus petit Job. Notez que si la collection comporte un grand nombre d'éléments, comme cité ci-dessous, cela sera coûteux.

Cette méthode parcourt toute la collection, elle prend donc un temps proportionnel à la taille de la collection.

List<Job> list = List.of(
	// Id, Priority
	new Job(1L, 3),
	new Job(2L, 3),
	new Job(3L, 2),
	new Job(4L, 1),
	new Job(5L, 1),
	new Job(6L, 2)
);

// (1)
Job minJob = Collections.min(list, new Comparator<Job>() {
	@Override
	public int compare(Job j1, Job j2) {
		if (Objects.equals(j1.getPriority(), j2.getPriority())) {
			return Long.compare(j1.getId(), j2.getId());
		}
		return Integer.compare(j1.getPriority(), j2.getPriority());
	}
});
System.out.println(minJob);
// → Job [id=4, priority=1]

// (2)
minJob = Collections.min(list, Comparator.comparing(Job::getPriority).thenComparing(Job::getId));
System.out.println(minJob);
// → Job [id=4, priority=1]

Callable<V>

Méthode abstraite


V call() throws Exception;

Exemple d'utilisation dans JDK

java.base : ExecutorService

submit

ExecutorService.submit


<T> Future<T> submit(Callable<T> task);

échantillon

ExecutorService service = Executors.newSingleThreadExecutor();

// (1)
Future<LocalDateTime> future = service.submit(new Callable<LocalDateTime>() {
	@Override
	public LocalDateTime call() throws Exception {
		TimeUnit.SECONDS.sleep(5L);
		return LocalDateTime.now();
	}
});

/*
// (2)
Future<LocalDateTime> future = service.submit(() -> {
    TimeUnit.SECONDS.sleep(5L);
    return LocalDateTime.now();
});
*/

LocalDateTime result = future.get();
service.shutdown();

Recommended Posts

Une note de revue sur l'interface fonctionnelle
Introduction de l'interface fonctionnelle
Note de révision Enum
[Java] Interface fonctionnelle
interface de type de fonction standard java
Notes de révision de Java NIO 2
[Java] Interface fonctionnelle / expression lambda
Notes de révision de Java Collections Framework
Notes de révision des files d'attente / BlockingQueue / TransferQueue
interface
Imiter l'interface fonctionnelle de Java avec Kotlin