Bases de JAVA - nouvelles fonctionnalités du JDK1.8

1. Méthode d'interface par défaut

Java 8 nous permet d'ajouter une implémentation de méthode non abstraite à l'interface en utilisant le mot-clé par défaut. Cette fonctionnalité est également appelée méthode d'extension. L'exemple de code est le suivant :

interface Formula { 
    double calculate(int a);
    default double sqrt(int a) { 
        return Math.sqrt(a); 
    } 
}

L'interface Formula définit également une méthode sqrt en plus de la méthode calculate. Une sous-classe qui implémente l'interface Formula n'a besoin que d'implémenter une méthode calculate. La méthode par défaut sqrt sera directement disponible sur la sous-classe. le code s'affiche comme ci-dessous :

Formula formula = new Formula() { 
    @Override 
    public double calculate(int a) { 
        return sqrt(a * 100); 
    } 
};
formula.calculate(100); // 100.0 formula.sqrt(16); // 4.0

La formule ci-dessus est implémentée comme une instance d'une classe anonyme. Le code est très simple à comprendre. Les 6 lignes de code implémentent le calcul de sqrt(a * 100).

Il n'y a qu'un seul héritage en Java. Si vous souhaitez donner de nouvelles caractéristiques à une classe, elle est généralement implémentée à l'aide d'une interface. L'héritage multiple est pris en charge en C++, permettant à une sous-classe d'avoir les interfaces et les fonctions de plusieurs classes parents en même temps. Dans d'autres langages, la méthode permettant à une classe d'avoir un autre code réutilisable est appelée mixin. Cette fonctionnalité du nouveau Java 8 est plus proche du trait Scala du point de vue de l'implémentation du compilateur. Il existe également un concept appelé méthode d'extension en C#, qui permet d'étendre les méthodes aux types existants. Ceci est sémantiquement différent de Java 8.

2. Expression lambda

Voyons d'abord comment les chaînes étaient disposées dans les versions antérieures à Java 1.8. Le code est le suivant :

List<String> names = Arrays.asList("peterF", "anna", "mike", "xenia");
Collections.sort(names, new Comparator<String>() { 
    @Override 
    public int compare(String a, String b) { 
        return b.compareTo(a); 
    } 
});
//list的排序为 [xenia, peterF, mike, anna]  

Transmettez simplement un objet List et un comparateur à la méthode statique Collections.sort pour les trier dans l'ordre spécifié. L'approche habituelle consiste à créer un objet comparateur anonyme et à le transmettre à la méthode de tri.

Dans Java 8, il n'est pas nécessaire d'utiliser cette méthode d'objet anonyme traditionnelle. Java 8 fournit une syntaxe plus concise, une expression lambda, le code est le suivant :

Collections.sort(names, (String a, String b) -> { return b.compareTo(a); });

Après avoir utilisé des expressions lambda, le code devient plus court et plus lisible, mais il peut en réalité être écrit encore plus court. Le code est le suivant :

Collections.sort(names, (String a, String b) -> b.compareTo(a));

Pour un corps de fonction avec une seule ligne de code, vous pouvez supprimer les accolades {} et le mot-clé return, ou l'écrire plus court. Le code est le suivant :

Collections.sort(names, (a, b) -> b.compareTo(a));

Le compilateur Java peut déduire automatiquement le type du paramètre, il n'est donc pas nécessaire de réécrire le type.

3. Interface fonctionnelle

Comment les expressions lambda sont-elles représentées dans le système de types Java ? Chaque expression lambda correspond à un type, généralement un type d'interface. "Interface fonctionnelle" fait référence à une interface qui ne contient qu'une seule méthode abstraite. Chaque expression lambda de ce type sera mise en correspondance avec cette méthode abstraite. Étant donné que les méthodes par défaut ne sont pas considérées comme des méthodes abstraites, vous pouvez également ajouter des méthodes par défaut aux interfaces fonctionnelles.

Nous pouvons traiter les expressions lambda comme n'importe quel type d'interface qui contient une seule méthode abstraite. Pour garantir que l'interface doit répondre à cette exigence, il suffit d'ajouter l'annotation @FunctionalInterface à l'interface. Si le compilateur constate que l'interface marquée avec cette annotation a plus d'une méthode abstraite Une erreur sera signalée lorsque . L'exemple de code est le suivant :

@FunctionalInterface 
interface Converter<F, T> { T convert(F from); } 
Converter<String, Integer> converter = (from) -> Integer.valueOf(from); 
Integer converted = converter.convert("123"); 
System.out.println(converted); // 123

Il convient de noter que si @FunctionalInterface n'est pas spécifié, le code ci-dessus est également correct.

Le mappage des expressions lambda vers une interface à méthode unique a été implémenté dans d'autres langages avant Java 8, comme l'interpréteur JavaScript Rhino. Si un paramètre de fonction reçoit une interface à méthode unique et transmet une fonction, l'interpréteur Rhino créera automatiquement un adaptateur. d'une instance d'interface unique à une fonction. Les scénarios d'application typiques incluent le deuxième paramètre EventListener de addEventListener de org.w3c.dom.events.EventTarget.

4. Références de méthodes et de constructeurs

Le code de la section précédente peut également être représenté par une référence de méthode statique, comme suit :

Converter<String, Integer> converter = Integer::valueOf; 
Integer converted = converter.convert("123"); 
System.out.println(converted); // 123

Java 8 permet l'utilisation du mot-clé :: pour transmettre des références à une méthode ou à un constructeur. Le code ci-dessus montre comment référencer une méthode statique ou une méthode d'un objet. Le code est le suivant :

converter = something::startsWith; 
String converted = converter.convert("Java"); 
System.out.println(converted); // "J"

Voyons ensuite comment les constructeurs sont référencés à l'aide du mot-clé ::. Tout d'abord, définissons une classe simple contenant plusieurs constructeurs. Le code est le suivant :

class Person { 
    String firstName; 
    String lastName;
    Person() {}
    Person(String firstName, String lastName) { 
        this.firstName = firstName; 
        this.lastName = lastName; 
    } 
}

Ensuite, spécifiez une interface de fabrique d'objets utilisée pour créer des objets Personne. Le code est le suivant :

interface PersonFactory{ P create(String firstName, String lastName); }

Les références de constructeur sont utilisées ici pour les associer au lieu d'implémenter une fabrique complète. Le code est le suivant :

PersonFactory<Person> personFactory = Person::new; 
Person person = personFactory.create("Peter", "Parker");

Utilisez simplement Person::new pour obtenir une référence au constructeur de la classe Person, et le compilateur Java sélectionnera automatiquement le constructeur approprié en fonction de la signature de la méthode PersonFactory.create.

5. Portée Lambda

L'accès à la portée externe dans les expressions lambda est similaire à celui des anciennes versions d'objets anonymes. Vous pouvez accéder directement aux variables locales externes marquées comme finales, ou aux champs d'instance et aux variables statiques.

6. Accédez aux variables locales

Vous pouvez accéder directement aux variables locales externes dans l'expression lambda. Le code est le suivant :

final int num = 1; 
Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);
stringConverter.convert(2); // 3

Mais contrairement aux objets anonymes, la variable num n'a pas besoin ici d'être déclarée finale. Le code est également correct. Le code est le suivant :

int num = 1; 
Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);
stringConverter.convert(2); // 3

Cependant, le num ici ne doit pas être modifié par le code ultérieur (c'est-à-dire qu'il a une sémantique finale implicite). Par exemple, les éléments suivants ne peuvent pas être compilés :

int num = 1; 
Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num); 
num = 3;

Tenter de modifier num dans une expression lambda n'est pas non plus autorisé.

7. Accéder aux champs d'objet et aux variables statiques

Différent des variables locales, les champs internes et les variables statiques de lambda sont à la fois lisibles et inscriptibles. Ce comportement est cohérent avec les objets anonymes, le code est le suivant :

class Lambda4 { 
    static int outerStaticNum; 
    int outerNum;
    void testScopes() { 
        Converter<Integer, String> stringConverter1 = (from) -> { outerNum = 23; return String.valueOf(from); };
        Converter<Integer, String> stringConverter2 = (from) -> { outerStaticNum = 72; return String.valueOf(from); }; 
    } 

}

8. Méthode par défaut d'accès à l'interface

Par exemple, dans l'exemple de formule de la section 1, l'interface Formula définit une méthode par défaut sqrt qui est directement accessible par les instances de formule incluant des objets anonymes, mais cela n'est pas possible dans les expressions lambda. La méthode par défaut n'est pas accessible dans les expressions Lambda et le code suivant ne sera pas compilé. Le code est le suivant :

Formula formula = (a) -> sqrt( a * 100); //Built-in Functional Interfaces

L'API JDK 1.8 contient de nombreuses interfaces fonctionnelles intégrées, telles que les interfaces Comparator ou Runnable couramment utilisées dans l'ancien Java. Ces interfaces ont ajouté l'annotation @FunctionalInterface afin de pouvoir être utilisées dans les lambdas. L'API Java 8 fournit également de nombreuses nouvelles interfaces fonctionnelles pour rendre le travail plus pratique. Certaines interfaces sont issues de la bibliothèque Google Guava. Même si vous les connaissez, il est encore nécessaire de voir comment celles-ci peuvent être étendues à lambda.

Interface de prédicat

L'interface Predicate n'a qu'un seul paramètre et renvoie un type booléen. Cette interface contient une variété de méthodes par défaut pour combiner Predicate dans d'autres logiques complexes (telles que : et, ou, non), le code est le suivant :

Predicate<String> predicate = (s) -> s.length() > 0;
predicate.test("foo"); // true 
predicate.negate().test("foo"); // false
Predicate<Boolean> nonNull = Objects::nonNull; 
Predicate<Boolean> isNull = Objects::isNull;
Predicate<String> isEmpty = String::isEmpty; 
Predicate<String> isNotEmpty = isEmpty.negate();

Interface de fonction

L'interface Function a un paramètre et renvoie un résultat, et est livrée avec des méthodes par défaut (compose, andThen) qui peuvent être combinées avec d'autres fonctions. Le code est le suivant :

Function<String, Integer> toInteger = Integer::valueOf; 
Function<String, String> backToString = toInteger.andThen(String::valueOf);
backToString.apply("123"); // "123"

Interface fournisseur

L'interface Fournisseur renvoie une valeur de n'importe quel type. Contrairement à l'interface Fonction, cette interface ne possède aucun paramètre. Le code est le suivant :

Supplier<Person> personSupplier = Person::new; personSupplier.get(); // new Person

Interface consommateur

L'interface Consumer représente les opérations effectuées sur un seul paramètre. le code s'affiche comme ci-dessous :

Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName); 
greeter.accept(new Person("Luke", "Skywalker"));

Interface du comparateur

Comparator est une interface classique de l'ancien Java. Java 8 ajoute une variété de méthodes par défaut. Le code est le suivant :

Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
Person p1 = new Person("John", "Doe"); Person p2 = new Person("Alice", "Wonderland");
comparator.compare(p1, p2); // > 0 comparator.reversed().compare(p1, p2); // < 0

Interface optionnelle

Facultatif n'est pas une fonction mais une interface. Il s'agit d'un type auxiliaire utilisé pour empêcher NullPointerException. C'est un concept important qui sera utilisé dans la prochaine session. Voyons maintenant brièvement ce que cette interface peut faire :

Facultatif est défini comme un simple conteneur dont la valeur peut ou non être nulle. Avant Java 8, une fonction devait généralement renvoyer un objet non nul mais elle pouvait parfois renvoyer null. Cependant, dans Java 8, il n'est pas recommandé de renvoyer null mais de renvoyer Facultatif. le code s'affiche comme ci-dessous :

Optional<String> optional = Optional.of("bam");
optional.isPresent(); // true optional.get(); // "bam" 
optional.orElse("fallback"); // "bam"
optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"

Interface de flux

java.util.Stream représente une séquence d'opérations pouvant être effectuées sur un ensemble d'éléments à la fois. Les opérations de flux sont divisées en deux types : les opérations intermédiaires ou les opérations finales. L'opération finale renvoie un type spécifique de résultat de calcul, tandis que l'opération intermédiaire renvoie le flux lui-même, afin que plusieurs opérations puissent être enchaînées en séquence. La création de Stream nécessite de spécifier une source de données, telle qu'une sous-classe de java.util.Collection, List ou Set, qui n'est pas prise en charge par Map. Les opérations de flux peuvent être exécutées en série ou en parallèle.

Voyons d'abord comment Stream est utilisé. Tout d'abord, créons une liste de données utilisée dans l'exemple de code. Le code est le suivant :

List<String> stringCollection = new ArrayList<>(); 
stringCollection.add("ddd2"); 
stringCollection.add("aaa2"); 
stringCollection.add("bbb1"); 
stringCollection.add("aaa1"); 
stringCollection.add("bbb3"); 
stringCollection.add("ccc"); 
stringCollection.add("bbb2"); 
stringCollection.add("ddd1");

Java 8 étend la classe collection pour créer un Stream via Collection.stream() ou Collection.parallelStream(). Les sections suivantes expliquent en détail les opérations Stream couramment utilisées :

FiltreFiltre _

Le filtrage utilise une interface de prédicat pour filtrer et conserver uniquement les éléments qui remplissent les conditions. Cette opération est une opération intermédiaire, de sorte que d'autres opérations Stream (telles que forEach) peuvent être appliquées aux résultats filtrés. forEach nécessite qu'une fonction soit exécutée séquentiellement sur les éléments filtrés. forEach est une opération finale, donc d'autres opérations Stream ne peuvent pas être effectuées après forEach. le code s'affiche comme ci-dessous :

stringCollection.stream().filter((s) -> s.startsWith("a")).forEach(System.out::println);
// "aaa2", "aaa1"

TrierTri _

Le tri est une opération intermédiaire et ce qui est renvoyé est le Stream trié. Si vous ne spécifiez pas de comparateur personnalisé, le tri par défaut sera utilisé. le code s'affiche comme ci-dessous :

stringCollection.stream().sorted().filter((s) -> s.startsWith("a")).forEach(System.out::println);
// "aaa1", "aaa2"

Il convient de noter que le tri crée uniquement un Stream organisé et n'affecte pas la source de données d'origine. Les données d'origine stringCollection ne seront pas modifiées après le tri. Le code est le suivant :

System.out.println(stringCollection); // ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1

Cartographie cartographique

La carte d'opération intermédiaire convertira séquentiellement les éléments en d'autres objets en fonction de l'interface de fonction spécifiée. L'exemple suivant montre la conversion d'une chaîne en chaîne majuscule. Les objets peuvent également être convertis en d'autres types via map.Le ​​type Stream renvoyé par map est déterminé en fonction de la valeur de retour de la fonction transmise par map. le code s'affiche comme ci-dessous :

stringCollection.stream().map(String::toUpperCase).sorted((a, b) -> b.compareTo(a)).forEach(System.out::println);
// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"

Correspondre _

Stream fournit une variété d'opérations de correspondance, vous permettant de détecter si le prédicat spécifié correspond à l'intégralité du flux. Toutes les opérations de correspondance sont des opérations finales et renvoient une valeur booléenne. le code s'affiche comme ci-dessous :

boolean anyStartsWithA = stringCollection.stream().anyMatch((s) -> s.startsWith("a"));
System.out.println(anyStartsWithA); // true
boolean allStartsWithA = stringCollection.stream().allMatch((s) -> s.startsWith("a"));
System.out.println(allStartsWithA); // false
boolean noneStartsWithZ = stringCollection.stream().noneMatch((s) -> s.startsWith("z"));
System.out.println(noneStartsWithZ); // true

Nombre_ _

Le comptage est une opération finale qui renvoie le nombre d'éléments dans le Stream. Le type de valeur de retour est long. le code s'affiche comme ci-dessous :

long startsWithB = stringCollection.stream().filter((s) -> s.startsWith("b")).count();
System.out.println(startsWithB); // 3

Réduire le protocole

Il s'agit d'une opération finale qui permet de réduire plusieurs éléments du flux à un seul élément via une fonction spécifiée. Le résultat après la réduction est exprimé via l'interface facultative. Le code est le suivant :

Optional<String> reduced = stringCollection.stream().sorted().reduce((s1, s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println); // "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"

Flux parallèles

Comme mentionné précédemment, il existe deux types de Stream : série et parallèle. Les opérations sur le Stream série sont effectuées séquentiellement dans un seul thread, tandis que le Stream parallèle est exécuté sur plusieurs threads simultanément.

L'exemple suivant montre comment améliorer les performances via des flux parallèles :

Nous créons d’abord une grande table sans éléments en double. Le code est le suivant :

int max = 1000000; 
List<String> values = new ArrayList<>(max); 
for (int i = 0; i < max; i++) { 
    UUID uuid = UUID.randomUUID(); 
    values.add(uuid.toString()); 
}

Ensuite on calcule combien de temps il faut pour trier ce Stream, tri en série, le code est le suivant :

long t0 = System.nanoTime();
long count = values.stream().sorted().count(); 
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); 
System.out.println(String.format("sequential sort took: %d ms", millis));
// 1000000
// sequential sort took: 816 ms
// 即串行耗时: 816 ms 

Le code de tri parallèle est le suivant :

long t0 = System.nanoTime();
long count = values.parallelStream().sorted().count(); 
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); 
System.out.println(String.format("parallel sort took: %d ms", millis));
// 1000000
// parallel sort took: 385 ms
// 即并行排序耗时: 385 ms

Les deux codes ci-dessus sont presque identiques, mais la version parallèle est jusqu'à 50 % plus rapide. Le seul changement à apporter est de remplacer stream() par parallelStream().

Carte

Comme mentionné précédemment, le type Map ne prend pas en charge les flux, mais Map fournit de nouvelles méthodes utiles pour gérer certaines tâches quotidiennes. le code s'affiche comme ci-dessous :

Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < 10; i++) { map.putIfAbsent(i, "val" + i); }
map.forEach((id, val) -> System.out.println(val));

Le code ci-dessus est facile à comprendre. putIfAbsent ne nous oblige pas à effectuer des vérifications d'existence supplémentaires, et forEach reçoit une interface Consumer pour opérer sur chaque paire clé-valeur de la carte.

L'exemple suivant montre d'autres fonctions utiles sur la carte, le code est le suivant :

map.computeIfPresent(3, (num, val) -> val + num); 
map.get(3); // val33
map.computeIfPresent(9, (num, val) -> null); 
map.containsKey(9); // false
map.computeIfAbsent(23, num -> "val" + num); 
map.containsKey(23); // true
map.computeIfAbsent(3, num -> "bam"); 
map.get(3); // val33

Ensuite, nous montrons comment supprimer un élément de la carte dont les valeurs clés correspondent toutes. Le code est le suivant :

map.remove(3, "val3"); 
map.get(3); // val33
map.remove(3, "val33"); 
map.get(3); // null

Autre méthode utile :

map.getOrDefault(42, "not found"); // not found

La fusion des éléments de la Map est également devenue très simple. Le code est le suivant :

map.merge(9, "val9", (value, newValue) -> value.concat(newValue)); 
map.get(9); // val9
map.merge(9, "concat", (value, newValue) -> value.concat(newValue)); 
map.get(9); // val9concat

Ce que fait Merge, c'est insérer le nom de la clé si elle n'existe pas, sinon fusionner les valeurs correspondant aux clés d'origine et les réinsérer dans la carte.

Par exemple、API Date

Java 8 inclut un nouvel ensemble d'API d'heure et de date sous le package java.time. La nouvelle API de date est similaire à la bibliothèque open source Joda-Time, mais pas exactement la même. L'exemple suivant montre certaines des parties les plus importantes de cette nouvelle API :

Horloge_ _

La classe Clock fournit des méthodes pour accéder à la date et à l'heure actuelles. Clock est sensible au fuseau horaire et peut être utilisée pour remplacer System.currentTimeMillis() pour obtenir le nombre actuel de microsecondes. Un moment spécifique peut également être représenté à l'aide de la classe Instant, qui peut également être utilisée pour créer d'anciens objets java.util.Date. le code s'affiche comme ci-dessous :

Clock clock = Clock.systemDefaultZone(); 
long millis = clock.millis();
Instant instant = clock.instant(); 
Date legacyDate = Date.from(instant); // legacy java.util.Date

Fuseaux horaires _

Dans la nouvelle API, les fuseaux horaires sont représentés par ZoneId. Le fuseau horaire peut être facilement obtenu en utilisant la méthode statique de. Le fuseau horaire définit le décalage horaire par rapport à l'heure UTS et est extrêmement important lors de la conversion entre des objets d'heure instantanée et des objets de date locaux. le code s'affiche comme ci-dessous :

System.out.println(ZoneId.getAvailableZoneIds()); // prints all available timezone ids
ZoneId zone1 = ZoneId.of("Europe/Berlin"); 
ZoneId zone2 = ZoneId.of("Brazil/East"); 
System.out.println(zone1.getRules()); System.out.println(zone2.getRules());
// ZoneRules[currentStandardOffset=+01:00] // ZoneRules[currentStandardOffset=-03:00]

LocalTime heure locale

LocalTime définit une heure sans informations de fuseau horaire, par exemple 22h ou 17:30:15. L'exemple suivant crée deux heures locales en utilisant le fuseau horaire créé par le code précédent. Comparez ensuite les heures et calculez le décalage horaire entre les deux heures en heures et minutes : Le code est le suivant :

LocalTime now1 = LocalTime.now(zone1); 
LocalTime now2 = LocalTime.now(zone2);
System.out.println(now1.isBefore(now2)); // false
long hoursBetween = ChronoUnit.HOURS.between(now1, now2); 
long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);
System.out.println(hoursBetween); // -3 System.out.println(minutesBetween); // -239

LocalTime fournit une variété de méthodes de fabrique pour simplifier la création d'objets, notamment l'analyse des chaînes temporelles. le code s'affiche comme ci-dessous :

LocalTime late = LocalTime.of(23, 59, 59); 
System.out.println(late); // 23:59:59
DateTimeFormatter germanFormatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withLocale(Locale.GERMAN);
LocalTime leetTime = LocalTime.parse("13:37", germanFormatter); 
System.out.println(leetTime); // 13:37

LocalDate date locale

LocalDate représente une date exacte, telle que 2014-03-11. La valeur de l'objet est immuable et son utilisation est fondamentalement la même que celle de LocalTime. L'exemple suivant montre comment ajouter et soustraire des jours/mois/années à un objet Date. Notez également que ces objets sont immuables et que les opérations renvoient toujours une nouvelle instance. le code s'affiche comme ci-dessous :

LocalDate today = LocalDate.now(); 
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS); LocalDate yesterday = tomorrow.minusDays(2);
LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4); 
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println(dayOfWeek); // FRIDAY 

Analyser un type LocalDate à partir d'une chaîne est aussi simple que d'analyser un LocalTime. Le code est le suivant :

DateTimeFormatter germanFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(Locale.GERMAN);
LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter); 
System.out.println(xmas); // 2014-12-24

LocalDateTime date et heure locales

LocalDateTime représente à la fois l'heure et la date, ce qui équivaut à fusionner le contenu des deux sections précédentes en un seul objet. LocalDateTime, comme LocalTime et LocalDate, est immuable. LocalDateTime fournit quelques méthodes pour accéder à des champs spécifiques. le code s'affiche comme ci-dessous :

LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);
DayOfWeek dayOfWeek = sylvester.getDayOfWeek(); 
System.out.println(dayOfWeek); // WEDNESDAY
Month month = sylvester.getMonth(); 
System.out.println(month); // DECEMBER
long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY); 
System.out.println(minuteOfDay); // 1439

Tant que les informations de fuseau horaire sont ajoutées, elles peuvent être converties en un objet instantané ponctuel, qui peut être facilement converti en un java.util.Date à l'ancienne. le code s'affiche comme ci-dessous :

Instant instant = sylvester.atZone(ZoneId.systemDefault()).toInstant();
Date legacyDate = Date.from(instant); 
System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014

Le formatage de LocalDateTime est identique au formatage de l'heure et de la date. En plus d'utiliser les formats prédéfinis, nous pouvons également définir le format nous-mêmes :

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM dd, yyyy - HH:mm");
LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter); 
String string = formatter.format(parsed); 
System.out.println(string); // Nov 03, 2014 - 07:13

Contrairement à java.text.NumberFormat, le nouveau DateTimeFormatter est immuable, il est donc thread-safe.

10. Annotations

Plusieurs annotations sont prises en charge dans Java 8. Regardons un exemple pour comprendre ce que cela signifie. Définissez d’abord une annotation Hints de classe wrapper pour placer un ensemble spécifique d’annotations Hint :

@interface 
Hints { Hint[] value(); }
@Repeatable(Hints.class) 
@interface 
Hint { String value(); }

Java 8 nous permet d'utiliser plusieurs fois le même type d'annotation. Il suffit de marquer l'annotation avec @Repeatable.

Exemple 1 : utiliser une classe wrapper comme conteneur pour stocker plusieurs annotations (ancienne méthode) :

@Hints({@Hint("hint1"), @Hint("hint2")}) 
class Person {}

Exemple 2 : Utilisation de plusieurs annotations (nouvelle méthode) :

@Hint("hint1") @Hint("hint2") 
class Person {}

Dans le deuxième exemple, le compilateur Java aidera implicitement à définir l'annotation @Hints. Comprendre cela vous aidera à utiliser la réflexion pour obtenir ces informations :

Hint hint = Person.class.getAnnotation(Hint.class); 
System.out.println(hint); // null
Hints hints1 = Person.class.getAnnotation(Hints.class); 
System.out.println(hints1.value().length); // 2
Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class); 
System.out.println(hints2.length); // 2

Même si nous ne définissons pas d'annotations @Hints sur la classe Person, nous pouvons toujours obtenir des annotations @Hints via getAnnotation(Hints.class).Une méthode plus pratique consiste à utiliser getAnnotationsByType pour obtenir directement toutes les annotations @Hint. De plus, des annotations Java 8 ont été ajoutées à deux nouvelles cibles :

le code s'affiche comme ci-dessous :

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) 
@interface 
MyAnnotation {}
C'est tout pour écrire sur les nouvelles fonctionnalités de Java 8. Il y a certainement d'autres fonctionnalités à découvrir, telles que Arrays.parallelSort, StampedLock et CompletableFuture, etc.

Je suppose que tu aimes

Origine blog.csdn.net/DreamEhome/article/details/128812988
conseillé
Classement