Onze perles cachées de Java 11

Java 11 n'a introduit aucune fonctionnalité innovante, mais il contient plusieurs joyaux dont vous n'avez peut-être pas encore entendu parler. Vous avez déjà regardé les dernières nouveautés en matière de String , Optional , de Collection et autres chevaux de bataille? Sinon, alors vous êtes à l'adresse: aujourd'hui, nous allons voir 11 joyaux cachés de Java 11!


Inférence de type pour les paramètres lambda


Lors de l'écriture d'une expression lambda, vous pouvez choisir entre spécifier explicitement des types et les ignorer:


 Function<String, String> append = string -> string + " "; Function<String, String> append = (String s) -> s + " "; 

Java 10 a introduit var , mais il ne pouvait pas être utilisé dans lambdas:


 //    Java 10 Function<String, String> append = (var string) -> string + " "; 

En Java 11, c'est déjà possible. Mais pourquoi? Il ne semble pas que var donné plus qu'une simple passe de type. Bien que ce soit le cas, l'utilisation de var présente deux avantages mineurs:


  • rend l'utilisation de var plus universelle en supprimant l'exception à la règle
  • vous permet d'ajouter des annotations au type de paramètre sans avoir à utiliser son nom complet

Voici un exemple du deuxième cas:


 List<EnterpriseGradeType<With, Generics>> types = /*...*/; types .stream() // ,     @Nonnull   .filter(type -> check(type)) //  Java 10    ~>  .filter((@Nonnull EnterpriseGradeType<With, Generics> type) -> check(type)) //  Java 11    ~>   .filter((@Nonnull var type) -> check(type)) 

Bien que le mélange de types dérivés, explicites et implicites dans des expressions lambda de la forme (var type, String option, index) -> ... puisse être implémenté, mais ( dans le cadre de JEP-323 ) ce travail n'a pas été effectué. Par conséquent, il est nécessaire de choisir l'une des trois approches et d'y adhérer pour tous les paramètres de l'expression lambda. La nécessité de spécifier var pour tous les paramètres afin d'ajouter une annotation pour l'un d'eux peut être légèrement ennuyeuse, mais généralement tolérable.


Traitement de flux de chaînes avec 'String::lines'


Vous avez une chaîne multi-lignes? Vous voulez faire quelque chose avec chaque ligne? Alors String::lines est le bon choix:


 var multiline = "\r\n\r\n\r\n"; multiline .lines() //Stream<String> .map(line -> "// " + line) .forEach(System.out::println); // : //  //  //  //  

Notez que la ligne d'origine utilise les délimiteurs Windows \r\n , et bien que je sois sous Linux, lines() quand même cassé. Cela est dû au fait que, malgré le système d'exploitation actuel, cette méthode interprète \r , \n et \r\n comme des sauts de ligne - même s'ils sont mélangés sur la même ligne.


Un flux de lignes ne contient jamais les séparateurs de lignes eux-mêmes. Les lignes peuvent être vides ( "\n\n \n\n" , qui contient 5 lignes), mais la dernière ligne de la ligne d'origine est ignorée si elle est vide ( "\n\n" ; 2 lignes). (Remarque du traducteur: il est pratique pour eux d'avoir une line , mais d'avoir une string , et nous avons les deux.)


Contrairement à split("\R") , les lines() paresseuses et, je cite , "offrent de meilleures performances [...] en recherchant plus rapidement de nouveaux sauts de ligne". (Si quelqu'un veut déposer un benchmark sur JMH pour vérification, faites le moi savoir). Il reflète également mieux l'algorithme de traitement et utilise une structure de données plus pratique (flux au lieu d'un tableau).


Suppression d'espaces avec 'String::strip' , etc.


Initialement, String avait une méthode de découpage pour supprimer les espaces blancs, ce qui était considéré comme tout avec des codes jusqu'à U+0020 . Oui, BACKSPACE ( U+0008) est un espace blanc comme BELL ( U+0007 ), mais LINE SEPARATOR ( U+2028 ) n'est plus considéré comme tel.


Java 11 a introduit la méthode strip , dont l'approche a plus de nuances. Il utilise la Character::isWhitespace de Java 5 pour déterminer exactement ce qui doit être supprimé. D'après sa documentation, il est clair que ceci:


  • SPACE SEPARATOR , LINE SEPARATOR , PARAGRAPH SEPARATOR , mais pas un espace inextricable
  • HORIZONTAL TABULATION ( U+0009 ), LINE FEED U+000A LINE FEED ( U+000A ), VERTICAL TABULATION ( U+000B ), FORM FEED ( U+000C ), CARRIAGE RETURN ( U+000D )
  • FILE SEPARATOR U+001C ( U+001C ), U+001C GROUP SEPARATOR ( U+001D ), U+001D RECORD SEPARATOR ( U+001E ), U+001E UNIT SEPARATOR ( U+001F )

Avec la même logique, il existe deux autres méthodes de nettoyage, stripLeading et stripTailing , qui font exactement ce que l'on attend d'eux.


Et enfin, si vous avez juste besoin de savoir si la ligne devient vide après avoir supprimé les espaces, alors il n'est pas nécessaire de vraiment les supprimer - utilisez simplement isBlank :


 " ".isBlank(); //  ~> true " ".isBlank(); //   ~> false 

Répétition de chaînes avec 'String::repeat'


Saisissez l'idée:


Étape 1: Surveiller JDK

Garder un œil sur le développement JDK


Étape 2: Recherche de questions liées à StackOverflow

Vous recherchez des questions connexes sur Stackoverflow


Étape 3: arriver avec une nouvelle réponse basée sur les changements futurs

Entrez avec une nouvelle réponse basée sur les changements à venir


Étape 4: ????

Étape 4: Profit

¯ \ _ (ツ) _ / ¯


Comme vous pouvez l'imaginer, String a une nouvelle méthode de repeat(int) . Cela fonctionne exactement conformément aux attentes, et il y a peu de choses à discuter.


Créer des chemins avec 'Path::of'


J'aime vraiment l'API Path , mais convertir des chemins entre différentes vues (comme Path , File , URL , URI et String ) est toujours ennuyeux. Ce point est devenu moins déroutant en Java 11 en copiant deux méthodes Paths::get dans Path::of methods:


 Path tmp = Path.of("/home/nipa", "tmp"); Path codefx = Path.of(URI.create("http://codefx.org")); 

Ils peuvent être considérés comme canoniques, car les deux anciennes méthodes Paths::get utilisent de nouvelles options.


Lire et écrire des fichiers avec 'Files::readString' et 'Files::writeString'


Si j'ai besoin de lire à partir d'un fichier volumineux, j'utilise généralement Files::lines pour obtenir un flux paresseux de ses lignes. De même, pour enregistrer une grande quantité de données qui peuvent ne pas être entièrement stockées en mémoire, j'utilise Files::write passant comme Iterable<String> .


Mais qu'en est-il du cas simple lorsque je veux traiter le contenu d'un fichier sur une seule ligne? Ce n'est pas très pratique, car Files::readAllBytes et les variantes appropriées de Files::write fonctionnent sur des tableaux d'octets.


Et puis Java 11 apparaît, ajoutant readString et writeString aux Files :


 String haiku = Files.readString(Path.of("haiku.txt")); String modified = modify(haiku); Files.writeString(Path.of("haiku-mod.txt"), modified); 

Clair et facile à utiliser. Si nécessaire, vous pouvez passer le jeu de Charset à readString , et dans writeString également un tableau OpenOptions .


E / S vides avec 'Reader::nullReader' , etc.


Besoin d'un OutputStream qui n'écrit nulle part? Ou un InputStream vide? Et Reader et Writer qui ne font rien? Java 11 a tout pour plaire:


 InputStream input = InputStream.nullInputStream(); OutputStream output = OutputStream.nullOutputStream(); Reader reader = Reader.nullReader(); Writer writer = Writer.nullWriter(); 

(Note du traducteur: dans commons-io ces classes existent depuis environ 2014.)


Cependant, je suis surpris - null est-il vraiment le meilleur préfixe? Je n'aime pas comment cela est utilisé pour signifier "absence intentionnelle" ... Peut-être serait-il préférable d'utiliser noOp ? (Note du traducteur: ce préfixe a probablement été choisi en raison de l'utilisation courante de /dev/null .)


{ } ~> [ ] avec 'Collection::toArray'


Comment convertir des collections en tableaux?


 //  Java 11 List<String> list = /*...*/; Object[] objects = list.toArray(); String[] strings_0 = list.toArray(new String[0]); String[] strings_size = list.toArray(new String[list.size()]); 

La première option, les objects , perd toutes les informations sur les types, elle est donc en vol. Et le reste? Les deux sont volumineux, mais le premier est plus court. Ce dernier crée un tableau de la taille requise, de sorte qu'il semble plus productif (c'est-à-dire qu'il "semble plus productif", voir crédibilité ). Mais est-ce vraiment plus productif? Non, au contraire, c'est plus lent (pour le moment).


Mais pourquoi devrais-je m'en soucier? N'y a-t-il pas une meilleure façon de procéder? Dans Java 11, il y a:


 String[] strings_fun = list.toArray(String[]::new); 

Une nouvelle variante de Collection::toArray , qui accepte IntFunction<T[]> , c'est-à-dire une fonction qui reçoit la taille du tableau et renvoie un tableau de la taille requise. Il peut être brièvement exprimé en référence à un constructeur de la forme T[]::new (pour un T bien connu).


Fait intéressant, l'implémentation par défaut de Collection#toArray(IntFunction<T[]>) passe toujours 0 au générateur de tableau. Au début, j'ai décidé que cette solution était basée sur les meilleures performances pour les tableaux de longueur nulle, mais maintenant je pense que la raison peut être que pour certaines collections, le calcul de la taille peut être une opération très coûteuse et vous ne devez pas utiliser cette approche dans l'implémentation par défaut de Collection . Cependant, des implémentations de collections spécifiques, telles que ArrayList , peuvent changer cette approche, mais elles ne changent pas dans Java 11. Pas la peine, je suppose.


'Optional::isEmpty' absence avec 'Optional::isEmpty'


Avec l'utilisation abondante d' Optional , en particulier dans les grands projets, où vous rencontrez souvent une approche non Optional , vous devez souvent vérifier si elle a une valeur. Il existe une méthode Optional::isPresent pour cela. Mais tout aussi souvent, vous devez savoir le contraire - cette Optional vide. Pas de problème, utilisez simplement !opt.isPresent() , non?


Bien sûr, il peut en être ainsi, mais il est presque toujours plus facile de comprendre la logique if sa condition n'est pas inversée. Et parfois, Optional apparaît à la fin d'une longue chaîne d'appels et si vous avez besoin de le vérifier pour rien, alors vous devez parier ! au tout début:


 public boolean needsToCompleteAddress(User user) { return !getAddressRepository() .findAddressFor(user) .map(this::canonicalize) .filter(Address::isComplete) .isPresent(); } 

Dans ce cas, sautez-le ! très facile. À partir de Java 11, il existe une meilleure option:


 public boolean needsToCompleteAddress(User user) { return getAddressRepository() .findAddressFor(user) .map(this::canonicalize) .filter(Address::isComplete) .isEmpty(); } 

Inverser les prédicats avec 'Predicate::not'


En parlant d'inverser ... L'interface Predicate a une negate instance de negate : elle retourne un nouveau prédicat qui effectue le même contrôle, mais inverse son résultat. Malheureusement, j'arrive rarement à l'utiliser ...


 //      Stream .of("a", "b", "", "c") // ,  ~>        .filter(s -> !s.isBlank()) //          ~>  .filter((String::isBlank).negate()) // ,  ~>       .filter(((Predicate<String>) String::isBlank).negate()) .forEach(System.out::println); 

Le problème est que j'ai rarement accès à l'instance de Predicate . Plus souvent, je veux obtenir une telle instance via un lien vers une méthode (et l'inverser), mais pour que cela fonctionne, le compilateur doit savoir à quoi apporter la référence à la méthode - sans lui, il ne peut rien faire. Et c'est exactement ce qui se passe si vous utilisez la construction (String::isBlank).negate() : le compilateur ne sait plus ce que String::isBlank devrait être là-dessus et abandonne. Une caste correctement spécifiée corrige cela, mais à quel prix?


Bien qu'il existe une solution simple. N'utilisez pas la negate instance de negate , mais utilisez la nouvelle méthode statique Predicate.not(Predicate<T>) de Java 11:


 Stream .of("a", "b", "", "c") //   `java.util.function.Predicate.not` .filter(not(String::isBlank)) .forEach(System.out::println); 

Déjà mieux!


Expressions régulières en tant que prédicat avec 'Pattern::asMatchPredicate'


Y a-t-il une expression régulière? Besoin de filtrer les données dessus? Que diriez-vous de ceci:


 Pattern nonWordCharacter = Pattern.compile("\\W"); Stream .of("Metallica", "Motörhead") .filter(nonWordCharacter.asPredicate()) .forEach(System.out::println); 

J'étais très heureux de trouver cette méthode! Il vaut la peine d'ajouter qu'il s'agit d'une méthode de Java 8. Oups, je l'ai ratée alors. Java 11 a ajouté une autre méthode similaire: Pattern::asMatchPredicate . Quelle est la différence?


  • asPredicate vérifie que la chaîne ou une partie de la chaîne correspond au modèle (fonctionne comme s -> this.matcher(s).find() )
  • asMatchPredicate vérifie que la chaîne entière correspond au modèle (fonctionne comme s -> this.matcher(s).matches() )

Par exemple, nous avons une expression régulière qui vérifie les numéros de téléphone, mais elle ne contient pas ^ et $ pour suivre le début et la fin d'une ligne. Ensuite, le code suivant ne fonctionnera pas comme vous pouvez vous y attendre:


 prospectivePhoneNumbers .stream() .filter(phoneNumberPatter.asPredicate()) .forEach(this::robocall); 

Avez-vous remarqué une erreur? Une ligne comme " -152 ? +1-202-456-1414" sera filtrée, car elle contient un numéro de téléphone valide. D'un autre côté, Pattern::asMatchPredicate ne le permettra pas, car la chaîne entière ne correspondra plus au modèle.


Autotest


Voici un aperçu de toutes les onze perles - vous souvenez-vous toujours de ce que fait chaque méthode? Si oui, vous avez réussi le test.


  • en String :
    • Stream<String> lines()
    • String strip()
    • String stripLeading()
    • String stripTrailing()
    • boolean isBlank()
    • String repeat(int)
  • dans Path :
    • static Path of(String, String...)
    • static Path of(URI)
  • dans les Files :
    • String readString(Path) throws IOException
    • Path writeString(Path, CharSequence, OpenOption...) throws IOException
    • Path writeString(Path, CharSequence, Charset, OpenOption...) throws IOException
  • dans InputStream : static InputStream nullInputStream()
  • dans OutputStream : static OutputStream nullOutputStream()
  • dans Reader : Reader static Reader nullReader()
  • dans Writer : static Writer nullWriter()
  • dans Collection : T[] toArray(IntFunction<T[]>)
  • en Optional : boolean isEmpty()
  • dans le Predicate : Predicate static Predicate<T> not(Predicate<T>)
  • dans le Pattern : Predicate<String> asMatchPredicate()

Amusez-vous avec Java 11!

Source: https://habr.com/ru/post/fr441154/


All Articles