web-dev-qa-db-fra.com

Comment fonctionne le mot clé "final" dans Java? (Je peux toujours modifier un objet.)

Dans Java, nous utilisons final mot-clé avec des variables pour spécifier que ses valeurs ne doivent pas être modifiées. Mais je vois que vous pouvez changer la valeur dans le constructeur/méthodes de la classe. De nouveau, si la variable est static, il s'agit d'une erreur de compilation.

Voici le code:

import Java.util.ArrayList;
import Java.util.List;

class Test {
  private final List foo;

  public Test()
  {
      foo = new ArrayList();
      foo.add("foo"); // Modification-1
  }
  public static void main(String[] args) 
  {
      Test t = new Test();
      t.foo.add("bar"); // Modification-2
      System.out.println("print - " + t.foo);
  }
}

Le code ci-dessus fonctionne correctement et sans erreur.

Maintenant changez la variable comme static:

private static final List foo;

Maintenant c'est une erreur de compilation. Comment cette final fonctionne-t-elle vraiment?

448
G.S

Vous êtes toujours autorisé à initialiser une variable final. Le compilateur s'assure que vous ne pouvez le faire qu'une seule fois.

Notez que l'appel de méthodes sur un objet stocké dans une variable final n'a rien à voir avec la sémantique de final. En d'autres termes: final concerne uniquement la référence elle-même, et non le contenu de l'objet référencé.

Java n'a pas de concept d'immuabilité d'objet; Ceci est réalisé en concevant soigneusement l'objet, et est une entreprise loin d'être triviale.

484
Marko Topolnik

Ceci est un question d'entrevue préférée. À l'aide de ces questions, l'intervieweur essaie de déterminer votre compréhension du comportement des objets en ce qui concerne les constructeurs, les méthodes, les variables de classe (variables statiques) et les variables d'instance.

import Java.util.ArrayList;
import Java.util.List;

class Test {
    private final List foo;

    public Test() {
        foo = new ArrayList();
        foo.add("foo"); // Modification-1
    }

    public void setFoo(List foo) {
       //this.foo = foo; Results in compile time error.
    }
}

Dans le cas ci-dessus, nous avons défini un constructeur pour 'Test' et lui avons donné une méthode 'setFoo'.

À propos du constructeur: Le constructeur ne peut être appelé que n temps de création par objet à l'aide du mot clé new. Vous ne pouvez pas appeler le constructeur plusieurs fois, car le constructeur n'est pas conçu pour le faire.

À propos de la méthode: Une méthode peut être invoquée autant de fois que vous le souhaitez (même jamais) et le compilateur le sait.

Scénario 1

private final List foo;  // 1

foo est une variable instance. Lorsque nous créons un objet de classe Test, la variable d'instance foo sera alors copiée dans l'objet de la classe Test. Si nous assignons foo à l'intérieur du constructeur, le compilateur sait que le constructeur ne sera appelé qu'une seule fois. Il n'y a donc aucun problème à l'attribuer à l'intérieur du constructeur.

Si nous affectons foo dans une méthode, le compilateur sait qu’une méthode peut être appelée plusieurs fois, ce qui signifie que la valeur devra être modifiée plusieurs fois, ce qui n’est pas autorisé pour un final variable. Le compilateur décide donc que le constructeur est un bon choix! Vous pouvez attribuer une valeur à une variable finale une seule fois.

Scénario 2

private static final List foo = new ArrayList();

foo est maintenant une variable statique. Lorsque nous créons une instance de Test class, foo ne sera pas copié dans l'objet car foo est statique. Maintenant foo n'est pas une propriété indépendante de chaque objet. C'est une propriété de la classe Test. Mais foo peut être vu par plusieurs objets et si chaque objet est créé à l'aide du mot clé new qui appellera finalement le constructeur Test qui modifie la valeur lors de la création de plusieurs objets. (Rappelez-vous que static foo n'est pas copié dans chaque objet, mais partagé entre plusieurs objets.)

scénario

t.foo.add("bar"); // Modification-2

Ci-dessus Modification-2 est tiré de votre question. Dans le cas ci-dessus, vous ne modifiez pas le premier objet référencé, mais vous ajoutez du contenu dans foo qui est autorisé. Le compilateur se plaint si vous essayez d'affecter une new ArrayList() à la variable de référence foo.
Rule Si vous avez initialisé une variable final, vous ne pouvez pas la changer pour faire référence à un objet différent. (Dans ce cas ArrayList)

final les classes ne peuvent pas être sous-classées
final les méthodes ne peuvent pas être remplacées. (Cette méthode est en superclasse)
final méthodes peuvent remplacer. (Lisez ceci de manière grammaticale. Cette méthode est dans une sous-classe)

552
AmitG

Final mot-clé a de nombreuses façons d'utiliser:

  • Une finale classe ne peut pas être sous-classée.
  • Une finale méthode ne peut pas être remplacée par des sous-classes
  • Une finale variable ne peut être initialisée qu'une seule fois

Autre utilisation:

  • Lorsqu'une classe interne anonyme est définie dans le corps d'une méthode, toutes les variables déclarées finales dans le cadre de cette méthode sont accessibles depuis la classe interne

Une variable de classe statique existera dès le début de la machine virtuelle Java et doit être initialisée dans la classe. Le message d'erreur n'apparaîtra pas si vous faites cela.

200
czupe

Le mot clé final peut être interprété de deux manières différentes, en fonction de son utilisation:

Types de valeur: Pour ints, doubles, etc., cela garantira que la valeur ne peut pas changer,

Types de référence: Pour les références aux objets, final garantit que la référence ne changera jamais, ce qui signifie qu'elle fera toujours référence au même objet. Il ne fait aucune garantie que les valeurs à l'intérieur de l'objet référencé restent les mêmes.

En tant que tel, final List<Whatever> foo; garantit que foo fait toujours référence à la même liste , mais le contenu de cette liste peut évoluer dans le temps.

49
Smallhacker

Si vous faites foo static, vous devez l'initialiser dans le constructeur de la classe (ou inline où vous le définissez), comme dans les exemples suivants.

Constructeur de classe (pas d'instance):

private static final List foo;

static
{
   foo = new ArrayList();
}

Inline:

private static final List foo = new ArrayList();

Le problème ici n'est pas de savoir comment fonctionne le modificateur final, mais plutôt comment fonctionne le modificateur static.

Le modificateur final impose l’initialisation de votre référence au moment où l’appel de votre constructeur s’achève (c’est-à-dire que vous devez l’initialiser dans le constructeur).

Lorsque vous initialisez un attribut en ligne, il est initialisé avant que le code que vous avez défini pour le constructeur ne soit exécuté. Vous obtenez donc les résultats suivants:

  • si foo est static, foo = new ArrayList() sera exécuté avant l'exécution du constructeur static{} que vous avez défini pour votre classe.
  • si foo n'est pas static, foo = new ArrayList() sera exécuté avant l'exécution de votre constructeur

Lorsque vous n'initialisez pas d'attribut en ligne, le modificateur final vous oblige à l'initialiser et à le faire dans le constructeur. Si vous avez également un modificateur static, le constructeur dans lequel vous devrez initialiser l'attribut est le bloc d'initialisation de la classe: static{}.

L'erreur que vous obtenez dans votre code provient du fait que static{} est exécuté lorsque la classe est chargée, avant le moment où vous instanciez un objet de cette classe. Ainsi, vous n'aurez pas initialisé foo lors de la création de la classe.

Considérez le bloc static{} comme un constructeur d'objet de type Class. C’est là que vous devez initialiser vos attributs de classe static final (si cela n’est pas fait en ligne).

Note latérale:

Le modificateur final assure const-ness uniquement pour les types primitifs et les références.

Lorsque vous déclarez un objet final, vous obtenez un finalréférence à cet objet, mais l'objet lui-même n'est pas constant.

Ce que vous accomplissez réellement lorsque vous déclarez un attribut final, c’est que, une fois que vous déclarez un objet pour votre objectif spécifique (comme le final List que vous avez déclaré), seul cet objet sera utilisé à cette fin. but: vous ne pourrez pas changer List foo en un autre List, mais vous pourrez toujours modifier votre List en ajoutant/supprimant des éléments (le List que vous utiliserez sera le même, seulement avec son contenu modifié).

22
lucian.pantelimon

C'est une très bonne question d'entrevue. Parfois, ils peuvent même vous demander quelle est la différence entre un objet final et un objet immuable.

1) Lorsque quelqu'un mentionne un objet final, cela signifie que la référence ne peut pas être modifiée, mais que son état (variables d'instance) peut être modifié.

2) Un objet immuable est un objet dont l'état peut pas être changé, mais sa référence peut être changée. Ex:

    String x = new String("abc"); 
    x = "BCG";

la variable ref x peut être modifiée pour pointer une autre chaîne, mais la valeur "abc" ne peut pas être modifiée.

3) Les variables d'instance (champs non statiques) sont initialisées lorsqu'un constructeur est appelé. Vous pouvez donc initialiser des valeurs sur vos variables dans un constructeur.

4) "Mais je vois que vous pouvez changer la valeur dans le constructeur/méthodes de la classe". - Vous ne pouvez pas le changer dans une méthode.

5) Une variable statique est initialisée pendant le chargement de la classe. Donc, vous ne pouvez pas initialiser à l'intérieur d'un constructeur, cela doit être fait même avant. Vous devez donc affecter des valeurs à une variable statique pendant déclaration elle-même.

7
user892871

Il convient de mentionner quelques définitions simples:

Classes/Méthodes

Vous pouvez déclarer certaines ou toutes les méthodes d'une classe sous la forme final, afin d'indiquer que la méthode ne peut pas être remplacée par des sous-classes.

Variables

Une fois qu'une variable final a été initialisée, elle contient toujours la même valeur.

final essentiellement éviter d’écraser/de remplacer par quoi que ce soit (sous-classes, variable "réaffecter"), selon le cas.

6
ivanleoncz

Le mot clé final dans Java est utilisé pour restreindre l'utilisateur. Le mot clé Java final peut être utilisé dans de nombreux contextes. La finale peut être:

  1. variable
  2. méthode
  3. classe

Le mot clé final peut être appliqué aux variables. Une variable final qui n'a pas de valeur est appelée vierge final variable ou non initialisée final variable. Il ne peut être initialisé que dans le constructeur. La variable vierge final peut être static également être initialisée dans le bloc static uniquement.

Variable finale Java:

Si vous créez une variable sous la forme final, vous ne pouvez pas modifier la valeur de la variable final (elle sera constante).

Exemple de final variable

Il existe une variable finale speedlimit, nous allons modifier la valeur de cette variable, mais elle ne peut pas être modifiée car la variable finale une fois qu’une valeur est affectée ne peut jamais être modifiée.

class Bike9{  
    final int speedlimit=90;//final variable  
    void run(){  
        speedlimit=400;  // this will make error
    }  

    public static void main(String args[]){  
    Bike9 obj=new  Bike9();  
    obj.run();  
    }  
}//end of class  

Classe finale Java:

Si vous faites une classe comme final, vous ne pourrez pas l'étendre .

Exemple de cours final

final class Bike{}  

class Honda1 extends Bike{    //cannot inherit from final Bike,this will make error
  void run(){
      System.out.println("running safely with 100kmph");
   }  

  public static void main(String args[]){  
      Honda1 honda= new Honda();  
      honda.run();  
      }  
  }  

Méthode finale Java:

Si vous définissez une méthode comme finale, vous ne pourrez pas la remplacer .

Exemple de méthode final (run () dans Honda ne peut pas remplacer run () dans Bike)

class Bike{  
  final void run(){System.out.println("running");}  
}  

class Honda extends Bike{  
   void run(){System.out.println("running safely with 100kmph");}  

   public static void main(String args[]){  
   Honda honda= new Honda();  
   honda.run();  
   }  
}  

partagé depuis: http://www.javatpoint.com/final-keyword

4
Ali Ziaee

final est un mot clé réservé dans Java pour restreindre l'utilisateur. Il peut être appliqué aux variables membres, méthodes, classes et variables locales. Les variables finales sont souvent déclarées avec le mot clé static dans Java et sont traitées comme des constantes. Par exemple:

public static final String hello = "Hello";

Lorsque nous utilisons le mot clé final avec une déclaration de variable, la valeur stockée dans cette variable ne peut pas être modifiée ultérieurement.

Par exemple:

public class ClassDemo {
  private final int var1 = 3;
  public ClassDemo() {
    ...
  }
}

Remarque : Une classe déclarée comme finale ne peut être ni étendue ni héritée (c'est-à-dire qu'il ne peut pas y avoir de sous-classe de la super classe). Il convient également de noter que les méthodes déclarées comme finales ne peuvent pas être remplacées par des sous-classes.

Les avantages de l'utilisation du mot clé final sont décrits dans ce fil .

4

Supposons que vous ayez deux tirelires, rouge et blanche. Vous n'affectez à ces tirelires que deux enfants et ils ne sont pas autorisés à échanger leurs boîtes. Donc, vous avez des tirelires rouges ou blanches (finales), vous ne pouvez pas modifier la boîte, mais vous pouvez mettre de l'argent sur votre boîte. Personne ne s'en soucie (Modification-2).

3
huseyin

Lisez toutes les réponses.

Il existe un autre cas d’utilisateur où le mot clé final peut être utilisé, par exemple dans un argument de méthode:

public void showCaseFinalArgumentVariable(final int someFinalInt){

   someFinalInt = 9; // won't compile as the argument is final

}

Peut être utilisé pour une variable qui ne devrait pas être changée.

2
Pritam Banerjee

J'ai pensé écrire une réponse mise à jour et détaillée ici.

Le mot clé final peut être utilisé à plusieurs endroits.

  1. des classes

Un final class signifie que no autre classe peut étendre cette classe finale. Lorsque Java Run Time (JRE) sait qu'une référence d'objet est dans le type d'une classe finale (disons F), il sait que la valeur de cette référence ne peut être que dans le type de F.

Ex:

F myF;
myF = new F();    //ok
myF = someOther;  //someOther cannot be in type of a child class of F.
                  //because F cannot be extended.

Ainsi, lorsqu'il exécute une méthode de cet objet, cette méthode n'a pas besoin d'être résolue à exécution en utilisant un table virtuelle. c'est-à-dire que le polymorphisme au moment de l'exécution ne peut pas être appliqué. Donc, le temps d'exécution ne se préoccupe pas de cela. Ce qui signifie un gain de temps de traitement et une amélioration des performances.

  1. les méthodes

Un final method de toute classe signifie que toute classe enfant d'extension de cette classe ne peut pas écraser si méthode (s) finale (s). Par conséquent, le comportement à l'exécution dans ce scénario est également identique au comportement précédent mentionné pour les classes.

  1. champs, variables locales, paramètres de méthode

Si l’on spécifie final comme ci-dessus, cela signifie que la valeur est déjà finalisée et que la valeur ne peut pas être modifiée .

Ex:

Pour les champs, paramètres locaux

final FinalClass fc = someFC; //need to assign straight away. otherwise compile error.
final FinalClass fc; //compile error, need assignment (initialization inside a constructor Ok, constructor can be called only once)
final FinalClass fc = new FinalClass(); //ok
fc = someOtherFC; //compile error
fc.someMethod(); //no problem
someOtherFC.someMethod(); //no problem

Pour les paramètres de méthode

void someMethod(final String s){
    s = someOtherString; //compile error
}

Cela signifie simplement que la valeur de la valeur de référence final ne peut pas être modifiée. c'est-à-dire qu'une seule initialisation est autorisée. Dans ce scénario, au moment de l’exécution, puisque JRE sait que les valeurs ne peuvent pas être modifiées, charge toutes ces valeurs finalisées (des références finales) dans cache L1 . Parce que n'a pas besoin de pour charge en arrière encore et encore de mémoire principale . Sinon, il est chargé dans le cache N2 et chargé de temps en temps depuis la mémoire principale. C'est donc aussi une amélioration de la performance.

Donc, dans tous les 3 scénarios ci-dessus, quand nous n'avons pas spécifié le mot clé final à des endroits utilisables, nous n'avons pas à nous inquiéter, optimisations du compilateur le fera pour nous. L’optimisation du compilateur a également beaucoup d’activités. :)

1
Supun Wijerathne
  1. Comme la variable finale est non statique, elle peut être initialisée dans le constructeur. Mais si vous le rendez statique, il ne peut pas être initialisé par constructeur (car les constructeurs ne sont pas statiques).
  2. L'addition à la liste ne devrait pas s'arrêter en rendant la liste finale. final lie simplement la référence à un objet particulier. Vous êtes libre de changer "l'état" de cet objet, mais pas l'objet lui-même.
1
Ankit

Tout d’abord, l’endroit où vous êtes en train d’initialiser (c’est-à-dire assigner pour la première fois) foo est ici:

foo = new ArrayList();

foo est un objet (avec le type List), donc c'est un type référence, pas un valeur (comme int). En tant que tel, il contient une référence à un emplacement de mémoire (par exemple 0xA7D2A834) où vos éléments de liste sont stockés. Des lignes comme ça

foo.add("foo"); // Modification-1

ne changez pas la valeur de foo (qui, encore une fois, n'est qu'une référence à un emplacement de mémoire). Au lieu de cela, ils ajoutent simplement des éléments dans cet emplacement de mémoire référencé. Pour violer le mot clé final, vous devrez essayer de réaffecter foo comme suit:

foo = new ArrayList();

Ce serait vous donner une erreur de compilation.


À présent, réfléchissez à ce qui se passe lorsque vous ajoutez le mot clé statique.

Lorsque vous n'avez PAS le mot-clé static, chaque objet qui instancie la classe a sa propre copie de foo. Par conséquent, le constructeur assigne une valeur à une copie vierge et fraîche de la variable foo, ce qui est parfaitement correct.

Cependant, lorsque vous avez le mot-clé static, il ne reste qu'un seul toto en mémoire associé à la classe. Si vous deviez créer deux objets ou plus, le constructeur tenterait de réaffecter ce foo à chaque fois, en violation du mot clé final.

1
Niko Bellic

Lorsque vous le rendez final final, il doit être initialisé dans un bloc d’initialisation statique.

    private static final List foo;

    static {
        foo = new ArrayList();
    }

    public Test()
    {
//      foo = new ArrayList();
        foo.add("foo"); // Modification-1
    }
1
Evgeniy Dorofeev

Le mot clé final indique qu'une variable ne peut être initialisée qu'une seule fois. Dans votre code, vous n'effectuez qu'une initialisation de final afin que les termes soient satisfaits. Cette instruction effectue l'initialisation unique de foo. Notez que final! = Immuable, cela signifie simplement que la référence ne peut pas changer.

foo = new ArrayList();

Lorsque vous déclarez foo comme static final, la variable doit être initialisée lorsque la classe est chargée et ne peut pas compter sur l'instanciation (appel de constructeur) pour initialiser foo, car les champs statiques doivent être disponibles sans instance d'une classe. Rien ne garantit que le constructeur aura été appelé avant d'utiliser le champ statique.

Lorsque vous exécutez votre méthode dans le scénario static final, la classe Test est chargée avant l'instanciation de t à ce moment-là, il n'y a pas d'instanciation de foo, ce qui signifie qu'elle n'a pas été initialisée. foo est défini sur la valeur par défaut pour tous les objets, à savoir null. À ce stade, je suppose que votre code génère une NullPointerException lorsque vous essayez d’ajouter un élément à la liste.

1
Kevin Bowersox

Vous trouverez ci-dessous différents contextes dans lesquels final est utilisé.

Variables finales Une variable finale ne peut être affectée qu'une seule fois. Si la variable est une référence, cela signifie que la variable ne peut pas être liée à un autre objet.

class Main {
   public static void main(String args[]){
      final int i = 20;
      i = 30; //Compiler Error:cannot assign a value to final variable i twice
   }
}

la variable finale peut être affectée ultérieurement (il n'est pas obligatoire d'attribuer une valeur lorsqu'elle est déclarée), mais une seule fois.

classes finales Une classe finale ne peut pas être étendue (héritée)

final class Base { }
class Derived extends Base { } //Compiler Error:cannot inherit from final Base

public class Main {
   public static void main(String args[]) {
   }
}

Méthodes finales Une méthode finale ne peut pas être remplacée par des sous-classes.

//Error in following program as we are trying to override a final method.
class Base {
  public final void show() {
       System.out.println("Base::show() called");
    }
}     
class Derived extends Base {
    public void show() {  //Compiler Error: show() in Derived cannot override
       System.out.println("Derived::show() called");
    }
}     
public class Main {
    public static void main(String[] args) {
        Base b = new Derived();;
        b.show();
    }
}
0
roottraveller

Surtout sont corrects. De plus, si vous ne voulez pas que les autres créent des sous-classes à partir de votre classe, déclarez-la comme finale. Il devient alors le niveau feuille de votre hiérarchie d’arborescence de classe que personne ne peut l’étendre davantage. C'est une bonne pratique d'éviter une grande hiérarchie de classes.

0
Shehan Simen