2016-01-09 13 views
9

Mi sono appena imbattuto nella classe Optional in Java 8 - Mi piace molto l'approccio di sostituire alcuni controlli null (che letteralmente significa "è il valore presente?") Nel mio codice con il metodo isPresent() chiamate.Prestazioni di Java Opzionale

La mia domanda è: non causerebbe prestazioni inferiori del mio codice? Sto solo supponendo che i semplici assegni nulli potrebbero essere un po 'più economici e non sono ancora molto bravo nella lettura/interpretazione del codice byte, quindi sono davvero interessato ai tuoi pensieri su quell'argomento.

+3

Perché non lo confrontate? –

+2

non dovresti fare 'isPresent', invece usa' map' e 'orElse'. –

+1

@ Łukasz: Ciò richiede una giustificazione. A volte è vero, ma se vuoi eseguire un'operazione con effetti collaterali quando il valore è presente, cosa fai se non 'if (isPresent()) doSomething()'? Né la mappa né l'altro hanno senso lì. –

risposta

11

Optional<T> è solo una normale classe generica che contiene un riferimento di tipo T. Pertanto, aggiunge un singolo livello di riferimento indiretto. Anche le chiamate al metodo non saranno molto costose, dal momento che la classe è final e quindi l'invio dinamico può essere evitato.

L'unico posto in cui si possono avere problemi di prestazioni è quando si lavora con un numero molto elevato di tali istanze, ma anche in questo caso le prestazioni di qualcosa come un Stream<Optional<String>> non sono affatto male. Tuttavia, quando si lavora con grandi quantità di valori primitivi, si riscontra un problema di prestazioni utilizzando Stream<Integer> (o Integer[]) rispetto alla specializzazione primitiva IntStream (o int[]) a causa di questo livello di riferimento indiretto che richiede un'istanza molto frequente di oggetti . Tuttavia, questa è una penalità che già conosciamo e paghiamo quando si usano cose come ArrayList<Integer>.

Si potrebbe, ovviamente, verifica lo stesso successo con Stream<OptionalInt>/OptionalInt[], dal momento che un OptionalInt è fondamentalmente una classe con un campo int e una bandiera boolean per la presenza (a differenza di Optional<T> che può rendere fare con solo il campo T) e quindi abbastanza simile a Integer anche se di dimensioni maggiori. E, naturalmente, un Stream<Optional<Integer>> aggiungerebbe due livelli di riferimento indiretto, con la corrispondente penalità a doppia prestazione.

0

Abbiamo eseguito il benchmark con il seguente codice utilizzando openjdk.

sc.map(MYObject::getRequest) 
    .map(RequestDO::getMyInst) 
    .map(MyInstDO::getCar) 
    .map(CarDO::getId); 

if(id.isPresent()) 

O

if(null != MYObject.getRequest() && null != 
    MYObject.getRequest().getMyInst() && null != 
    MYObject.getRequest().getMyInst().getCar() && null != 
    MYObject.getRequest().getMyInst().getCar().getId()) 

Il risultato mostra opzionale è molto meglio di tradizionale controllo non nullo.

Benchmark      Mode  Cnt  Score Error Units 

JMHBMarkModes.measureNotNull thrpt 5   0.149 ± 0.036 ops/us 
JMHBMarkModes.measureOptional thrpt 5   11.418 ± 1.140 ops/us 
JMHBMarkModes.measureNotNull avgt  5   12.342 ± 8.334 us/op 
JMHBMarkModes.measureOptional avgt  5   0.088 ± 0.010 us/op 

Ma se il vostro caso d'uso è come (null != MYObject.getRequest()), allora non controllo nulla è meglio. Quindi le prestazioni opzionali dipendono dal caso d'uso che hai.

5

Ho eseguito alcuni test delle prestazioni utilizzando un algoritmo che utilizza pesantemente i controlli nulli e l'accesso a un campo potenzialmente nullable. Ho implementato un semplice algoritmo che rimuove l'elemento centrale dalla singola lista collegata.

Prima ho implementato due classi di nodi di elenchi collegati: sicuro - con Opzionale e non sicuro - senza.

Nodo sicuro

class Node<T> { 
    private final T data; 
    private Optional<Node<T>> next = Optional.empty(); 

    Node(T data) { 

     this.data = data; 
    } 

    Optional<Node<T>> getNext() { 
     return next; 
    } 

    void setNext(Node<T> next) { setNext(Optional.ofNullable(next)); } 

    void setNext(Optional<Node<T>> next) { this.next = next; } 
} 

Nodo Unsafe

class NodeUnsafe<T> { 
    private final T data; 
    private NodeUnsafe<T> next; 

    NodeUnsafe(T data) { 
     this.data = data; 
    } 

    NodeUnsafe<T> getNext() { 
     return next; 
    } 

    void setNext(NodeUnsafe<T> next) { 
     this.next = next; 
    } 
} 

Poi ho implementato due metodi simili con la sola differenza - utilizza prima Node<T> e il secondo utilizza NodeUsafe<T> class DeleteMiddle {

private static <T> T getLinkedList(int size, Function<Integer, T> supplier, BiConsumer<T, T> reducer) { 
     T head = supplier.apply(1); 
     IntStream.rangeClosed(2, size).mapToObj(supplier::apply).reduce(head,(a,b)->{ 
      reducer.accept(a,b); 
      return b; 
     }); 
     return head; 
    } 

    private static void deleteMiddle(Node<Integer> head){ 
     Optional<Node<Integer>> oneStep = Optional.of(head); 
     Optional<Node<Integer>> doubleStep = oneStep; 
     Optional<Node<Integer>> prevStep = Optional.empty(); 

     while (doubleStep.isPresent() && doubleStep.get().getNext().isPresent()){ 
      doubleStep = doubleStep.get().getNext().get().getNext(); 
      prevStep = oneStep; 
      oneStep = oneStep.get().getNext(); 
     } 

     final Optional<Node<Integer>> toDelete = oneStep; 
     prevStep.ifPresent(s->s.setNext(toDelete.flatMap(Node::getNext))); 
    } 

    private static void deleteMiddleUnsafe(NodeUnsafe<Integer> head){ 
     NodeUnsafe<Integer> oneStep = head; 
     NodeUnsafe<Integer> doubleStep = oneStep; 
     NodeUnsafe<Integer> prevStep = null; 

     while (doubleStep != null && doubleStep.getNext() != null){ 
      doubleStep = doubleStep.getNext().getNext(); 
      prevStep = oneStep; 
      oneStep = oneStep.getNext(); 
     } 
     if (prevStep != null) { 
      prevStep.setNext(oneStep.getNext()); 
     } 
    } 

    public static void main(String[] args) { 
     int size = 10000000; 
     Node<Integer> head = getLinkedList(size, Node::new, Node::setNext); 
     Long before = System.currentTimeMillis(); 
     deleteMiddle(head); 
     System.out.println("Safe: " +(System.currentTimeMillis() - before)); 

     NodeUnsafe<Integer> headUnsafe = getLinkedList(size, NodeUnsafe::new, NodeUnsafe::setNext); 
     before = System.currentTimeMillis(); 
     deleteMiddleUnsafe(headUnsafe); 
     System.out.println("Unsafe: " +(System.currentTimeMillis() - before)); 
    } 
} 

confronto tra due diversi percorsi con differenti dimensioni della lista dimostra che l'approccio con codice che utilizza Optional al meglio è due volte più lento di quello con nullables. Con piccole liste è 3 volte più lento.