Question

J'ai écrit sept cas de test pour comprendre le comportement du finally bloc.Quelle est la logique derrière comment finally travaux?

package core;

public class Test {
    public static void main(String[] args) {
        new Test().testFinally();
    }

    public void testFinally() {
        System.out.println("One = " + tryOne());
        System.out.println("Two = " + tryTwo());
        System.out.println("Three = " + tryThree());
        System.out.println("Four = " + tryFour());
        System.out.println("Five = " + tryFive());
        System.out.println("Six = " + trySix());
        System.out.println("Seven = " + trySeven());
    }

    protected StringBuilder tryOne() {
        StringBuilder builder = new StringBuilder();
        try {
            builder.append("Cool");
            return builder.append("Return");
        }
        finally {
            builder = null;
        }
    }

    protected String tryTwo() {
        String builder = "Cool";
        try {
            return builder += "Return";
        }
        finally {
            builder = null;
        }
    }

    protected int tryThree() {
        int builder = 99;
        try {
            return builder += 1;
        }
        finally {
            builder = 0;
        }
    }

    protected StringBuilder tryFour() {
        StringBuilder builder = new StringBuilder();
        try {
            builder.append("Cool");
            return builder.append("Return");
        }
        finally {
            builder.append("+1");
        }
    }

    protected int tryFive() {
        int count = 0;
        try {
            count = 99;
        }
        finally {
            count++;
        }
        return count;
    }

    protected int trySix() {
        int count = 0;
        try {
            count = 99;
        }
        finally {
            count = 1;
        }
        return count;
    }

    protected int trySeven() {
        int count = 0;
        try {
            count = 99;
            return count;
        }
        finally {
            count++;
        }
    }
}

Pourquoi builder = null ne fonctionne pas?

Pourquoi builder.append("+1") travailler alors que count++( dans essayezSeven()) fait pas travail?

Était-ce utile?

La solution

Une fois que vous avez effectué le retour, la seule façon de contourner ce problème est d'effectuer un autre retour (comme indiqué dans De retour d'un bloc final en Java, c'est presque toujours une mauvaise idée), ou se termine brusquement.Vos tests ne reviennent jamais d'un final.

JLS §14.1 définit l'achèvement brutal.L’un des types d’achèvement brutal est le retour.Les blocs try en 1,2,3,4 et 7 se terminent brusquement en raison des retours.Comme expliqué par §14.20.2, si le bloc try se termine brusquement pour une raison R autre qu'un lancer, le bloc final est immédiatement exécuté.

Si le bloc final se termine normalement (ce qui implique, entre autres, aucun retour), "l'instruction try se termine brusquement pour la raison R.".En d’autres termes, le retour initié par l’essai reste intact ;cela s'applique à tous vos tests.Si vous revenez enfin, "L'instruction TRY est brusquement pour la raison (et la raison r est rejetée)." (Est ici le nouveau retour primordial).

Donc dans tryOne, si vous l'avez fait :

finally {
            builder = null;
            return builder;
        }

ce nouveau retour S remplacerait le retour original R.

Pour builder.append("+1") dans tryFour, gardez à l'esprit que StringBuilder est mutable, vous renvoyez donc toujours une référence au même objet spécifié dans le try.Vous faites juste une mutation de dernière minute.

tryFive et trySix sont simples.Puisqu'il n'y a pas de retour dans le try, le try et le final se terminent tous deux normalement, et il s'exécute de la même manière que s'il n'y avait pas de try-finally.

Autres conseils

Commençons par le cas d'utilisation que vous verrez plus souvent : vous disposez d'une ressource que vous doit fermer pour éviter une fuite.

public void deleteRows(Connection conn) throws SQLException {
    Statement statement = conn.createStatement();
    try {
        statement.execute("DELETE * FROM foo");
    } finally {
        statement.close();
    }
}

Dans ce cas, nous devons fermer l'instruction lorsque nous avons terminé, afin de ne pas divulguer les ressources de la base de données.Cela garantira que dans le cas d'une exception levée, nous fermerons toujours notre instruction avant la fin de la fonction.

essayer { ...} enfin { ...} les blocs sont destinés à garantir que quelque chose s'exécutera toujours à la fin de la méthode.C'est plus utile pour les cas d'exception.Si vous vous retrouvez à faire quelque chose comme ceci :

public String thisShouldBeRefactored(List<String> foo) {
    try { 
        if(foo == null) {
            return null;
        } else if(foo.length == 1) {
            return foo.get(0);
        } else {
            return foo.get(1);
        }
    } finally {
        System.out.println("Exiting function!");
    }
}

Vous n'utilisez finalement pas vraiment correctement.Il y a une pénalité de performance à cela.Continuez à l'utiliser lorsque vous avez des cas d'exception que vous devez nettoyer.Essayez de refactoriser ce qui précède comme suit :

public String thisShouldBeRefactored(List<String> foo) {
    final String result;

    if(foo == null) {
        result = null;
    } else if(foo.length == 1) {
        result = foo.get(0);
    } else {
        result = foo.get(1);
    }

    System.out.println("Exiting function!");

    return result;
}

Le bloc final est exécuté lorsque vous quittez le bloc try.L'instruction "return" fait deux choses, premièrement, elle définit la valeur de retour de la fonction et deuxièmement, elle quitte la fonction.Normalement, cela ressemblerait à une opération atomique, mais dans un bloc try, le bloc final s'exécuterait après que la valeur de retour ait été définie et avant la fin de la fonction.

Exécution du retour :

  1. Attribuer une valeur de retour
  2. courir enfin bloque
  3. fonction de sortie

Premier exemple (primitif) :

int count = 1;//Assign local primitive count to 1
try{
  return count; //Assign primitive return value to count (1)
}finally{
  count++ //Updates count but not return value
}

Deuxième exemple (référence) :

StringBuilder sb = new StringBuilder();//Assign sb a new StringBuilder
try{
    return sb;//return a reference to StringBuilder
}finally{
    sb.append("hello");//modifies the returned StringBuilder
}

Troisième exemple (référence) :

   StringBuilder sb = new StringBuilder();//Assign sb a new StringBuilder
   try{
      return sb;//return a reference to StringBuilder
   }finally{
      sb = null;//Update local reference sb not return value
   }

Exemple quatre (retour) :

   int count = 1;   //assign count
   try{
      return count; //return current value of count (1)
   }finally{
      count++;      //update count to two but not return value
      return count; //return current value of count (2) 
                    //replaces old return value and exits the finally block
   }

builder = null et builder.append("+1") sont fonctionnement.C'est juste qu'ils n'affectent pas ce que vous retournez.La fonction renvoie ce que le return déclaration a, indépendamment de ce qui se passe par la suite.

La raison pour laquelle il y a une différence est que builder est passé par référence. builder=null change le locale copie de builder. builder.append("+1") affecte la copie détenue par le parent.

Pourquoi builder = null ne fonctionne pas?
Parce que vous définissez la référence locale sur null, ce qui ne modifiera pas le contenu de la mémoire.Donc cela fonctionne, si vous essayez d'accéder au constructeur après avoir finalement bloqué, vous obtiendrez null.
Pourquoi builder.append("+1") work?
Parce que vous modifiez le contenu de la mémoire à l'aide de la référence, c'est pourquoi cela devrait fonctionner.
Pourquoi count++ ne fonctionne pas dans testFive() ?
Cela fonctionne bien avec moi.Il en produit 100 comme prévu.

Considérez ce que fait réellement le compilateur pour l'instruction return, par exemple dans tryOne() :il copie une référence à builder retour à l'environnement de la fonction appelante.Une fois cela fait, mais avant que le contrôle ne revienne à la fonction appelante, le bloc final s'exécute.Vous avez donc quelque chose comme ceci, en pratique :

protected StringBuilder tryOne() {
    StringBuilder builder = new StringBuilder();
    try {
        builder.append("Cool");
        builder.append("Return");
        StringBuilder temp = builder;
        return temp;
    } finally {
        builder = null;
    }
}

Ou, en termes d'ordre dans lequel les instructions sont réellement exécutées (en ignorant les exceptions possibles, bien sûr), cela ressemble plus à ceci :

protected StringBuilder tryOne() {
    StringBuilder builder = new StringBuilder();
    builder.append("Cool");
    builder.append("Return");
    StringBuilder temp = builder;
    builder = null;
    return temp;
}

Donc réglage builder = null fonctionne, il ne fait tout simplement rien d'utile.Cependant, en courant builder.append("something") volonté avoir un effet visible, puisque temp et builder font référence au même objet (mutable).

De même, ce qui se passe réellement dans trySeven() ressemble davantage à ceci :

protected int trySeven() {
    int count = 0;
    count = 99;
    int temp = count;
    count++;
    return temp;
}

Dans ce cas, puisqu'il s'agit d'un int, les copies sont indépendantes, donc l'incrémentation de l'une n'affecte pas l'autre.

Cela dit, il n'en demeure pas moins que placer les instructions return dans un bloc try-finally est clairement déroutant, donc si vous avez le choix en la matière, vous feriez mieux de réécrire les choses pour que toutes vos instructions return sont en dehors de tout bloc try-finally.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top