JavaRush /Java Blog /Random-IT /Dalle 8 alle 13: una panoramica completa delle versioni J...

Dalle 8 alle 13: una panoramica completa delle versioni Java. Parte 1

Pubblicato nel gruppo Random-IT
Gattini, ciao a tutti)) Quindi, oggi siamo nel 2020, e manca pochissimo al rilascio di Java 14. Dovresti aspettarti la versione finale il 17 marzo, analizzeremo cosa c'è di nuovo e interessante lì dopo, ma oggi vorrei rinfrescarmi la memoria sulle versioni precedenti di Java. Che novità ci hanno portato? Diamo un'occhiata. Iniziamo la revisione con Java 8, poiché è ancora abbastanza rilevante e viene utilizzato nella maggior parte dei progetti. Dalle 8 alle 13: una panoramica completa delle versioni Java.  Parte 1 - 1In precedenza, le nuove versioni venivano rilasciate ogni 3-5 anni, ma recentemente Oracle ha adottato un approccio diverso: "nuovo Java ogni sei mesi". E così, ogni sei mesi assistiamo al rilascio di funzionalità. Che sia un bene o un male, ognuno lo vede in modo diverso. Ad esempio, questo non mi piace molto, dal momento che le nuove versioni non hanno molte nuove funzionalità, ma allo stesso tempo le versioni crescono come funghi dopo la pioggia. Ho sbattuto le palpebre un paio di volte su un progetto con Java 8, e Java 16 era già stato rilasciato (ma quando esce raramente, le nuove funzionalità si accumulano, e alla fine questo evento è tanto atteso, come una vacanza: tutti discutono del nuove chicche e non puoi passarci accanto). Quindi iniziamo!

Giava8

Interfaccia funzionale

Cos'è questo? Un'interfaccia funzionale è un'interfaccia che contiene un metodo (astratto) non implementato. @FunctionalInterface è un'annotazione opzionale posizionata sopra tale interfaccia. Necessario per verificare se soddisfa i requisiti di un'interfaccia funzionale (avendo un solo metodo astratto). Ma come sempre, abbiamo alcuni avvertimenti: i metodi predefiniti e statici non rientrano in questi requisiti. Pertanto, possono esserci diversi metodi di questo tipo + uno astratto e l'interfaccia sarà funzionale. Può contenere anche metodi della classe Object che non influiscono sulla definizione dell'interfaccia come funzionale. Aggiungerò alcune parole sui metodi predefiniti e statici:
  1. I metodi con il modificatore predefinito consentono di aggiungere nuovi metodi alle interfacce senza interrompere l'implementazione esistente.

    public interface Something {
      default void someMethod {
          System.out.println("Some text......");
      }
    }

    Sì, sì, aggiungiamo il metodo implementato all'interfaccia e, quando si implementa questo metodo, non è possibile sovrascriverlo, ma utilizzarlo come ereditato. Ma se una classe implementa due interfacce con un determinato metodo, avremo un errore di compilazione, e se implementa interfacce ed eredita una classe con un determinato metodo identico, il metodo della classe genitore si sovrapporrà ai metodi dell'interfaccia e l'eccezione non funzionerà.

  2. i metodi statici in un'interfaccia funzionano allo stesso modo dei metodi statici in una classe. Non dimenticare: non puoi ereditare metodi statici , così come non puoi chiamare un metodo statico da una classe discendente.

Quindi, ancora qualche parola sulle interfacce funzionali e andiamo avanti. Ecco gli elenchi principali degli FI (il resto sono le loro varietà):

    Predicato: accetta un valore T come argomento e restituisce un valore booleano.

    Esempio:boolean someMethod(T t);

  • Consumatore: accetta un argomento di tipo T e non restituisce nulla (void).

    Esempio:void someMethod(T t);

  • Fornitore: non accetta nulla come input, ma restituisce un valore T.

    Esempio:T someMethod();

  • Funzione: accetta un parametro di tipo T come input, restituisce un valore di tipo R.

    Esempio:R someMethod(T t);

  • UnaryOperator: accetta un argomento T e restituisce un valore di tipo T.

    Esempio:T someMethod(T t);

Flusso

I flussi sono un modo per gestire le strutture dati in uno stile funzionale. In genere si tratta di raccolte (ma è possibile utilizzarle in altre situazioni meno comuni). In un linguaggio più comprensibile, Stream è un flusso di dati che elaboriamo come se lavorassimo con tutti i dati contemporaneamente e non con la forza bruta, come con for-each. Diamo un'occhiata a un piccolo esempio. Supponiamo di avere una serie di numeri che vogliamo filtrare (meno di 50), aumentare di 5 e inviare alla console i primi 4 numeri da quelli rimanenti. Come lo avremmo fatto prima:
List<Integer> list = Arrays.asList(46, 34, 24, 93, 91, 1, 34, 94);

int count = 0;

for (int x : list) {

  if (x >= 50) continue;

  x += 5;

  count++;

  if (count > 4) break;

  System.out.print(x);

}
Non sembra esserci molto codice e la logica è già un po' confusa. Vediamo come apparirà utilizzando lo stream:
Stream.of(46, 34, 24, 93, 91, 1, 34, 94)

      .filter(x -> x < 50)

      .map(x -> x + 5)

      .limit(4)

      .forEach(System.out::print);
Gli stream semplificano notevolmente la vita riducendo la quantità di codice e rendendolo più leggibile. Per chi volesse approfondire l'argomento, ecco un buon (direi addirittura ottimo) articolo sull'argomento .

Lambda

Forse la caratteristica più importante e tanto attesa è l'apparizione delle lambda. Cos'è la lambda? Si tratta di un blocco di codice che può essere passato in posti diversi in modo da poter essere eseguito in seguito tutte le volte necessarie. Sembra piuttosto confuso, vero? In poche parole, utilizzando lambda, puoi implementare un metodo di un'interfaccia funzionale (una sorta di implementazione di una classe anonima):
Runnable runnable = () -> { System.out.println("I'm running !");};

new Thread(runnable).start();
Abbiamo implementato il metodo run() in modo rapido e senza inutili formalità burocratiche. E sì: Runnable è un'interfaccia funzionale. Utilizzo anche lambda quando lavoro con i flussi (come negli esempi con i flussi sopra). Non andremo troppo in profondità, dato che puoi immergerti abbastanza in profondità, lascerò un paio di link in modo che i ragazzi che sono ancora scavatori nel cuore possano scavare più a fondo:

per ciascuno

Java 8 ha un nuovo foreach che funziona con un flusso di dati come un flusso. Ecco un esempio:
List<Integer> someList = Arrays.asList(1, 3, 5, 7, 9);

someList.forEach(x -> System.out.println(x));
(analogo a someList.stream().foreach(…))

Riferimento al metodo

I metodi di riferimento sono una nuova e utile sintassi progettata per fare riferimento a metodi esistenti o costruttori di classi o oggetti Java tramite :: I riferimenti ai metodi sono disponibili in quattro tipi:
  1. Link al progettista:

    SomeObject obj = SomeObject::new

  2. Riferimento al metodo statico:

    SomeObject::someStaticMethod

  3. Un riferimento a un metodo non statico di un oggetto di un certo tipo:

    SomeObject::someMethod

  4. Un riferimento a un metodo regolare (non statico) di un oggetto specifico

    obj::someMethod

Spesso, i riferimenti ai metodi vengono utilizzati nei flussi anziché nei lambda (i metodi di riferimento sono più veloci dei lambda, ma hanno una leggibilità inferiore).
someList.stream()

        .map(String::toUpperCase)

      .forEach(System.out::println);
Per chi desidera maggiori informazioni sui metodi di riferimento:

Orario dell'API

C'è una nuova libreria per lavorare con date e orari: java.time. Dalle 8 alle 13: una panoramica completa delle versioni Java.  Parte 1 - 2La nuova API è simile a qualsiasi Joda-Time. Le sezioni più significative di questa API sono:
  • LocalDate è una data specifica, ad esempio: 2010-01-09;
  • LocalTime - ora che tiene conto del fuso orario - 19:45:55 (analogo a LocalDate);
  • LocalDateTime - combo LocalDate + LocalTime - 2020-01-04 15:37:47;
  • ZoneId : rappresenta i fusi orari;
  • Orologio : utilizzando questo tipo è possibile accedere all'ora e alla data correnti.
Ecco un paio di articoli davvero interessanti su questo argomento:

Opzionale

Questa è una nuova classe nel pacchetto java.util , un wrapper di valori il cui trucco è che può contenere tranquillamente anche null . Ricezione opzionale: se passiamo nullOptional<String> someOptional = Optional.of("Something"); in Optional.of , otterremo la nostra NullPointerException preferita . Per questi casi usano: - in questo metodo non devi aver paura del nulla. Successivamente, crea un oggetto inizialmente vuoto Opzionale: Per verificare se è vuoto, usa: ci restituirà true o false. Esegui una determinata azione se è presente un valore e non fai nulla se non è presente alcun valore: un metodo inverso che restituisce il valore passato se Opzionale è vuoto (una sorta di piano di backup): puoi continuare per un tempo molto, molto lungo ( fortunatamente l’Optional ha aggiunto metodi con entrambe le mani generose), ma non ci soffermeremo su questo. È meglio per me lasciare un paio di link per cominciare: Optional<String> someOptional = Optional.ofNullable("Something");Optional<String> someOptional = Optional.empty();someOptional.isPresent();someOptional.ifPresent(System.out::println);System.out.println(someOptional.orElse("Some default content")); Abbiamo esaminato le innovazioni più famose di Java 8: non è tutto. Se vuoi saperne di più, allora ti ho lasciato questo:

Giava9

Quindi, il 21 settembre 2017, il mondo ha visto JDK 9. Questo Java 9 è dotato di un ricco set di funzionalità. Sebbene non siano presenti nuovi concetti linguistici, le nuove API e i comandi diagnostici saranno sicuramente di interesse per gli sviluppatori. Dalle 8 alle 13: una panoramica completa delle versioni Java.  Parte 1 - 4

JShell (REPL - ciclo lettura-valutazione-stampa)

Si tratta di un'implementazione Java di una console interattiva utilizzata per testare la funzionalità e utilizzare diversi costrutti nella console, come interfacce, classi, enumerazioni, operatori, ecc. Per avviare JShell, devi solo scrivere jshell nel terminale. Quindi possiamo scrivere tutto ciò che la nostra immaginazione consente: Dalle 8 alle 13: una panoramica completa delle versioni Java.  Parte 1 - 5utilizzando JShell, puoi creare metodi di primo livello e utilizzarli all'interno della stessa sessione. I metodi funzioneranno proprio come i metodi statici, tranne per il fatto che la parola chiave static può essere omessa. Maggiori informazioni nel manuale Java 9. REPL (JShell) .

Privato

A partire dalla versione 9 di Java, abbiamo l'opportunità di utilizzare metodi privati ​​nelle interfacce (metodi predefiniti e statici, poiché semplicemente non possiamo sovrascriverne altri a causa di un accesso insufficiente). private static void someMethod(){} try-with-resources La capacità di gestire le eccezioni Try-With-Resources è stata aggiornata:
BufferedReader reader = new BufferedReader(new FileReader("....."));
  try (reader2) {
  ....
}

Modularità ( Jigsaw )

Un modulo è un gruppo di pacchetti e risorse correlati insieme a un nuovo file descrittore del modulo. Questo approccio viene utilizzato per allentare l'accoppiamento del codice. L'accoppiamento lento è un fattore chiave per la manutenibilità e l'estensibilità del codice. La modularità è implementata a diversi livelli:
  1. Linguaggio di programmazione.
  2. Macchina virtuale.
  3. API Java standard.
JDK 9 viene fornito con 92 moduli: possiamo usarli o crearne uno nostro. Ecco un paio di link per uno sguardo più approfondito:

Collezione immutabile

In Java 9 è diventato possibile creare e riempire una raccolta con una riga, rendendola immutabile (in precedenza, per creare una raccolta immutabile, dovevamo creare una raccolta, riempirla con dati e chiamare un metodo, ad esempio, Collections.unmodifyingList). Un esempio di tale creazione: List someList = List.of("first","second","third");

Altre innovazioni:

  • ampliato Opzionale (nuovi metodi aggiunti);
  • le interfacce ProcessHandle e ProcessHandle sembravano controllare le azioni del sistema operativo;
  • G1: Garbage Collector predefinito;
  • Client HTTP con supporto sia per i protocolli HTTP/2 che per WebSocket;
  • flusso espanso;
  • aggiunto il framework API Reactive Streams (per la programmazione reattiva);
Per un'immersione più completa in Java 9, ti consiglio di leggere:

Giava10

Così, sei mesi dopo il rilascio di Java 9, nel marzo 2018 (me lo ricordo come se fosse ieri), è entrato in scena Java 10. Dalle 8 alle 13: una panoramica completa delle versioni Java.  Parte 1 - 6

var

Ora non dobbiamo fornire un tipo di dati. Contrassegniamo il messaggio come var e il compilatore determina il tipo del messaggio in base al tipo dell'inizializzatore presente a destra. Questa funzionalità è disponibile solo per variabili locali con un inizializzatore: non può essere utilizzata per argomenti di metodi, tipi restituiti, ecc., poiché non esiste un inizializzatore per poter definire il tipo. Esempio var (per tipo String):
var message = "Some message…..";
System.out.println(message);
var non è una parola chiave: è essenzialmente un nome di tipo riservato, proprio come int . Il vantaggio di var è grande: le dichiarazioni di tipo richiedono molta attenzione senza apportare alcun vantaggio e questa funzionalità farà risparmiare tempo. Ma allo stesso tempo, se una variabile viene ottenuta da una lunga catena di metodi, il codice diventa meno leggibile, poiché non è immediatamente chiaro che tipo di oggetto si trovi lì. Dedicato a chi vuole prendere confidenza con questa funzionalità:

Compilatore JIT (GraalVM)

Senza ulteriori indugi, lascia che ti ricordi che quando esegui il comando javac, l'applicazione Java viene compilata dal codice Java nel bytecode JVM, che è la rappresentazione binaria dell'applicazione. Ma un normale processore per computer non può semplicemente eseguire il bytecode JVM. Affinché il tuo programma JVM funzioni, hai bisogno di un altro compilatore per questo bytecode, che viene convertito in codice macchina che il processore è già in grado di utilizzare. Rispetto a Javac, questo compilatore è molto più complesso, ma produce anche codice macchina di qualità superiore. Attualmente OpenJDK contiene la macchina virtuale HotSpot, che a sua volta ha due compilatori JIT principali. Il primo, C1 ( compilatore client ), è progettato per operazioni a velocità più elevate, ma l'ottimizzazione del codice ne risente. Il secondo è C2 (compilatore server). La velocità di esecuzione ne risente, ma il codice è più ottimizzato. Quando viene utilizzato quale? C1 è ottimo per le applicazioni desktop in cui lunghe pause JIT non sono desiderabili e C2 è ottimo per programmi server di lunga durata in cui dedicare più tempo alla compilazione è abbastanza sopportabile. La compilazione multilivello avviene quando la compilazione passa prima attraverso C1 e il risultato passa attraverso C2 (utilizzato per una maggiore ottimizzazione). GraalVM è un progetto creato per sostituire completamente HotSpot. Possiamo pensare a Graal come a diversi progetti correlati: un nuovo compilatore JIT per HotSpot e una nuova macchina virtuale poliglotta. La particolarità di questo compilatore JIT è che è scritto in Java. Il vantaggio del compilatore Graal è la sicurezza, cioè non arresti anomali, ma eccezioni, non perdite di memoria. Avremo anche un buon supporto IDE e potremo utilizzare debugger, profiler o altri strumenti utili. Inoltre, il compilatore potrebbe essere indipendente da HotSpot e sarà in grado di creare una versione di se stesso compilata JIT più veloce. Per gli scavatori:

Parallelo G1

Il garbage collector G1 è certamente interessante, non ci sono dubbi, ma ha anche un punto debole: esegue un ciclo GC completo a thread singolo. In un momento in cui è necessaria tutta la potenza dell'hardware disponibile per trovare oggetti inutilizzati, siamo limitati a un singolo thread. Java 10 ha risolto questo problema. Ora il GC ora funziona con tutte le risorse che gli aggiungiamo (ovvero diventa multi-thread). Per raggiungere questo obiettivo, gli sviluppatori del linguaggio hanno migliorato l'isolamento delle fonti principali da GC, creando un'interfaccia gradevole e pulita per GC. Gli sviluppatori di questa carineria, OpenJDK, hanno dovuto ripulire in modo specifico il dump nel codice non solo per semplificare il più possibile la creazione di nuovi GC, ma anche per consentire di disabilitare rapidamente i GC non necessari dall'assembly. Uno dei principali criteri di successo è l'assenza di un calo della velocità operativa dopo tutti questi miglioramenti. Diamo un'occhiata anche: Altre innovazioni:
  1. Viene introdotta un'interfaccia Garbage-Collector pulita. Ciò migliora l'isolamento del codice sorgente da diversi garbage collector, rendendo possibile l'integrazione di raccoglitori alternativi in ​​modo rapido e indolore;
  2. Combinazione di sorgenti JDK in un unico repository;
  3. Le raccolte hanno ricevuto un nuovo metodo: copyOf (Collection) , che restituisce una copia immutabile di questa raccolta;
  4. Opzionale (e le sue varianti) ha un nuovo metodo .orElseThrow() ;
  5. D'ora in poi, le JVM saranno consapevoli di essere in esecuzione in un contenitore Docker e recupereranno la configurazione specifica del contenitore anziché interrogare il sistema operativo stesso.
Ecco alcuni materiali aggiuntivi per un'introduzione più dettagliata a Java 10: Ero molto confuso dal fatto che alcune versioni di Java si chiamassero 1.x. Vorrei essere chiaro: le versioni Java precedenti alla 9 avevano semplicemente uno schema di denominazione diverso. Ad esempio, Java 8 può anche essere chiamato 1.8 , Java 5 - 1.5 , ecc. E ora vediamo che con il passaggio alle versioni da Java 9, anche lo schema di denominazione è cambiato e le versioni Java non hanno più il prefisso 1.x . Questa è la fine della prima parte: abbiamo esaminato le nuove interessanti funzionalità di Java 8-10. Continuiamo la nostra conoscenza delle ultime novità nel prossimo post .
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION