Blocs d'initialisation statiques

265

Pour autant que je sache, le "bloc d'initialisation statique" est utilisé pour définir les valeurs du champ statique s'il ne peut pas être effectué sur une seule ligne.

Mais je ne comprends pas pourquoi nous avons besoin d'un bloc spécial pour cela. Par exemple, nous déclarons un champ statique (sans attribution de valeur). Ensuite, écrivez plusieurs lignes du code qui génèrent et affectent une valeur au champ statique déclaré ci-dessus.

Pourquoi avons-nous besoin de ces lignes dans un bloc spécial comme static {...}:?

romain
la source
6
Rétroaction mineure, mais il serait utile que vous puissiez énoncer clairement vos hypothèses et, par conséquent, préciser quelle réponse est correcte. quand je lis d' abord votre question, mal compris i et pensé que vous connaissiez la différence entre {...}vs static {...}. (auquel cas Jon Skeet a définitivement mieux répondu à votre question)
David T.
1
Cette question n'est pas très claire; vous avez les répondeurs brouillant et faisant beaucoup de conjectures de longue haleine sur ce que vous vouliez dire. Que diriez-vous d'écrire explicitement l'exemple de bloc d'initialisation statique que vous avez en tête et votre alternative, afin que les gens aient quelque chose de clair à répondre?
Don Hatch

Réponses:

431

Le bloc non statique:

{
    // Do Something...
}

Obtient appelé chaque fois qu'une instance de la classe est construite. Le bloc statique n'est appelé qu'une seule fois , lorsque la classe elle-même est initialisée, quel que soit le nombre d'objets de ce type que vous créez.

Exemple:

public class Test {

    static{
        System.out.println("Static");
    }

    {
        System.out.println("Non-static block");
    }

    public static void main(String[] args) {
        Test t = new Test();
        Test t2 = new Test();
    }
}

Cela imprime:

Static
Non-static block
Non-static block
Frederik Wordenskjold
la source
107
Pourquoi est-ce la réponse acceptée? Cela ne répond même pas à la question.
Paul Bellora
43
Il répond à la question: "Ceci est appelé à chaque fois que la classe est construite. Le bloc statique n'est appelé qu'une seule fois, quel que soit le nombre d'objets de ce type que vous créez."
Adam Arold
83
Pour le lecteur curieux, le bloc non statique est en fait copié par le compilateur Java dans chaque constructeur de la classe ( source ). C'est donc toujours le travail du constructeur d'initialiser les champs.
Martin Andersson
2
La réponse acceptée doit être celle-ci: stackoverflow.com/a/2420404/363573 . Cette réponse présente un exemple réel où vous avez besoin de blocs statiques.
Stephan
16
Pourquoi cette réponse est-elle soudainement rejetée? Vous pourriez être en désaccord sur le fait que ce soit la réponse acceptée, mais ce n'est certainement en aucun cas faux ou trompeur. Il essaie simplement d'aider à la compréhension de ces constructions de langage avec un exemple simple.
Frederik Wordenskjold
133

S'ils n'étaient pas dans un bloc d'initialisation statique, où seraient-ils? Comment déclareriez-vous une variable qui était uniquement destinée à être locale à des fins d'initialisation et la distingueriez-vous d'un champ? Par exemple, comment voudriez- vous écrire:

public class Foo {
    private static final int widgets;

    static {
        int first = Widgets.getFirstCount();
        int second = Widgets.getSecondCount();
        // Imagine more complex logic here which really used first/second
        widgets = first + second;
    }
}

Si firstet secondn'étaient pas dans un bloc, ils ressembleraient à des champs. S'ils étaient dans un bloc sans staticen face de lui, cela compterait comme un bloc d'initialisation d'instance au lieu d'un bloc d'initialisation statique, il serait donc exécuté une fois par instance construite plutôt qu'une fois au total.

Maintenant, dans ce cas particulier, vous pouvez utiliser une méthode statique à la place:

public class Foo {
    private static final int widgets = getWidgets();

    static int getWidgets() {
        int first = Widgets.getFirstCount();
        int second = Widgets.getSecondCount();
        // Imagine more complex logic here which really used first/second
        return first + second;
    }
}

... mais cela ne fonctionne pas lorsqu'il y a plusieurs variables que vous souhaitez affecter dans le même bloc, ou aucune (par exemple si vous voulez simplement enregistrer quelque chose - ou peut-être initialiser une bibliothèque native).

Jon Skeet
la source
1
Le bloc statique se produit-il avant que les variables statiques soient affectées ou après? private static int widgets = 0; static{widgets = 2;}
Weishi Zeng
1
Était curieux de savoir si le bloc statique se produit avant que les variables statiques soient affectées ou après. Par exemple, private static int widgets = 0; static{widgets = 2;}j'ai découvert que l'affectation «=» se produit dans l'ordre, ce qui signifie que le «=» mis en premier sera attribué en premier. L'exemple ci-dessus donnera aux 'widgets' une valeur de 2. (PS ne savait pas que les commentaires ne peuvent être modifiés qu'en 5 min ...)
Weishi Zeng
@WeishiZeng: Oui, c'est comme documenté dans docs.oracle.com/javase/specs/jls/se8/html/… - point 9.
Jon Skeet
Mais ne pourriez-vous pas également utiliser une méthode statique privée qui a exactement le même code que le bloc d'initialisation statique et affecter des widgets à la méthode statique privée?
Zachary Kraus
1
@Zachary: Voulez-vous dire renvoyer la valeur et affecter le résultat de l'appel de méthode? Si oui, oui - lorsque vous êtes assignant à exactement une variable à la suite du bloc. Éditera ma réponse avec des détails dans environ 7 heures ...
Jon Skeet
103

Voici un exemple:

  private static final HashMap<String, String> MAP = new HashMap<String, String>();
  static {
    MAP.put("banana", "honey");
    MAP.put("peanut butter", "jelly");
    MAP.put("rice", "beans");
  }

Le code de la ou des sections "statiques" sera exécuté au moment du chargement de la classe, avant que des instances de la classe ne soient construites (et avant que des méthodes statiques soient appelées ailleurs). De cette façon, vous pouvez vous assurer que les ressources de classe sont toutes prêtes à être utilisées.

Il est également possible d'avoir des blocs d'initialisation non statiques. Celles-ci agissent comme des extensions de l'ensemble des méthodes constructeurs définies pour la classe. Ils ressemblent à des blocs d'initialisation statiques, sauf que le mot clé "statique" est laissé de côté.

Pointu
la source
4
Pour cet exemple particulier, le motif à double accolade a parfois été "abusé" :)
BalusC
Il peut être abusé, mais d'un autre côté, il nettoie certains désordres et rend certains types de code un peu plus "solides". Je programme à Erlang pour le plaisir, et tu deviens accro à ne pas avoir besoin de variables locales :-)
Pointy
1
<< Le code de la ou des sections "statiques" sera exécuté au moment du chargement de la classe, avant que toutes les instances de la classe soient construites (et avant d'appeler des méthodes statiques d'ailleurs). De cette façon, vous pouvez vous assurer que les ressources de classe sont toutes prêtes à être utilisées. >> (Quel "Pointy" mentionné dans la réponse ci-dessus) c'est un point très important à noter quand il s'agit de l'exécution de blocs statiques.
apprenant
Pouvons-nous le faire en utilisant InitializingBean dans la méthode after afterPropertiesSet?
egemen
48

Il est également utile lorsque vous ne voulez en fait affecter la valeur à rien, comme le chargement d'une classe une seule fois pendant l'exécution.

Par exemple

static {
    try {
        Class.forName("com.example.jdbc.Driver");
    } catch (ClassNotFoundException e) {
        throw new ExceptionInInitializerError("Cannot load JDBC driver.", e);
    }
}

Hé, il y a un autre avantage, vous pouvez l'utiliser pour gérer les exceptions. Imaginez getStuff()qu'ici lance un Exceptionqui appartient vraiment à un bloc catch:

private static Object stuff = getStuff(); // Won't compile: unhandled exception.

puis un static initialiseur est utile ici. Vous pouvez gérer l'exception ici.

Un autre exemple est de faire ensuite des choses qui ne peuvent pas être faites pendant l'attribution:

private static Properties config = new Properties();

static {
    try { 
        config.load(Thread.currentThread().getClassLoader().getResourceAsStream("config.properties");
    } catch (IOException e) {
        throw new ExceptionInInitializerError("Cannot load properties file.", e);
    }
}

Pour revenir à l'exemple du pilote JDBC, tout pilote JDBC décent utilise lui-même l' staticinitialiseur pour s'enregistrer dans le DriverManager. Voir aussi ceci et cette réponse.

BalusC
la source
2
C'est là que réside le vaudou dangereux ... les initialiseurs statiques sont exécutés dans la méthode synthétique clinit (), qui est implicitement synchronisée . Cela signifie que la JVM va acquérir un verrou sur le fichier de classe en question. Cela peut entraîner un blocage dans les environnements multithread si deux classes tentent de se charger mutuellement et que chacune commence à se charger dans un thread différent. Voir www-01.ibm.com/support/docview.wss?uid=swg1IV48872
Ajax
@Ajax: Je considérerais cela comme un bogue dans le pilote JDBC en question ou dans le code d'application chargé de le charger. Habituellement, dans le cas de pilotes JDBC décents, tant que vous ne le chargez qu'une seule fois dans toute l'application au démarrage de l'application, il n'y a rien à faire.
BalusC
Ce serait certainement un bogue, mais pas entièrement la faute du pilote JDBC, cependant. Peut-être que le pilote a innocemment ses propres initialiseurs statiques, et peut-être que vous initialisez innocemment cette classe avec d'autres dans votre application, et, oh non, certaines classes inattendues se chargent de manière cyclique, et maintenant les blocages de votre application. J'ai découvert cela grâce à l'impasse entre java.awt.AWTEvent et sun.util.logging.PlatformLogger. J'ai seulement touché AWTEvent pour lui dire de fonctionner sans tête, et une autre lib a fini par charger PlatformLogger ... qu'AWTEvent charge également.
Ajax
1
Les deux classes se sont synchronisées sur différents threads, et ma version est bloquée sur 1/150 exécutions. Donc, je fais maintenant beaucoup plus attention au chargement de classe dans des blocs statiques. Dans le cas que j'ai mentionné ci-dessus, en utilisant un modèle de fournisseur différé où je pouvais créer une classe de fournisseur provisoire immédiatement (sans risque de blocage), initialiser le champ, puis quand il est réellement accédé (dans un accès au champ non synchronisé), alors je charge en fait les classes qui peuvent provoquer le blocage.
Ajax
11

Je dirais que static blockc'est juste du sucre syntaxique. Il n'y a rien que vous puissiez faire avec un staticbloc et rien d'autre.

Pour réutiliser certains exemples publiés ici.

Ce morceau de code pourrait être réécrit sans utiliser d' staticinitialiseur.

Méthode n ° 1: avec static

private static final HashMap<String, String> MAP;
static {
    MAP.put("banana", "honey");
    MAP.put("peanut butter", "jelly");
    MAP.put("rice", "beans");
  }

Méthode n ° 2: sans static

private static final HashMap<String, String> MAP = getMap();
private static HashMap<String, String> getMap()
{
    HashMap<String, String> ret = new HashMap<>();
    ret.put("banana", "honey");
    ret.put("peanut butter", "jelly");
    ret.put("rice", "beans");
    return ret;
}
user1508893
la source
10

Il y a quelques raisons réelles pour lesquelles il doit exister:

  1. initialisation des static finalmembres dont l'initialisation pourrait lever une exception
  2. initialisation des static finalmembres avec des valeurs calculées

Les gens ont tendance à utiliser les static {}blocs comme un moyen pratique d'initialiser les éléments dont la classe dépend également au cours de l'exécution - comme s'assurer qu'une classe particulière est chargée (par exemple, les pilotes JDBC). Cela peut se faire par d'autres moyens; cependant, les deux choses que je mentionne ci-dessus ne peuvent être faites qu'avec une construction comme le static {}bloc.

D.Shawley
la source
8

Vous pouvez exécuter des bits de code une fois pour une classe avant de construire un objet dans les blocs statiques.

Par exemple

class A {
  static int var1 = 6;
  static int var2 = 9;
  static int var3;
  static long var4;

  static Date date1;
  static Date date2;

  static {
    date1 = new Date();

    for(int cnt = 0; cnt < var2; cnt++){
      var3 += var1;
    }

    System.out.println("End first static init: " + new Date());
  }
}
Pierre-Antoine LaFayette
la source
7

Il est faux de penser qu'un bloc statique n'a accès qu'aux champs statiques. Pour cela, je voudrais montrer ci-dessous un morceau de code que j'utilise assez souvent dans des projets réels (copié partiellement à partir d' une autre réponse dans un contexte légèrement différent):

public enum Language { 
  ENGLISH("eng", "en", "en_GB", "en_US"),   
  GERMAN("de", "ge"),   
  CROATIAN("hr", "cro"),   
  RUSSIAN("ru"),
  BELGIAN("be",";-)");

  static final private Map<String,Language> ALIAS_MAP = new HashMap<String,Language>(); 
  static { 
    for (Language l:Language.values()) { 
      // ignoring the case by normalizing to uppercase
      ALIAS_MAP.put(l.name().toUpperCase(),l); 
      for (String alias:l.aliases) ALIAS_MAP.put(alias.toUpperCase(),l); 
    } 
  } 

  static public boolean has(String value) { 
    // ignoring the case by normalizing to uppercase
    return ALIAS_MAP.containsKey(value.toUpper()); 
  } 

  static public Language fromString(String value) { 
    if (value == null) throw new NullPointerException("alias null"); 
    Language l = ALIAS_MAP.get(value); 
    if (l == null) throw new IllegalArgumentException("Not an alias: "+value); 
    return l; 
  } 

  private List<String> aliases; 
  private Language(String... aliases) { 
    this.aliases = Arrays.asList(aliases); 
  } 
} 

Ici, l'initialiseur est utilisé pour maintenir un index ( ALIAS_MAP), pour mapper un ensemble d'alias au type d'énumération d'origine. Il est conçu comme une extension de la méthode intégrée valueOf fournie par leEnum lui même.

Comme vous pouvez le voir, l'initialiseur statique accède même au privatechamp aliases. Il est important de comprendre que le staticbloc a déjà accès aux Enuminstances de valeur (par exemple ENGLISH). En effet, l' ordre d'initialisation et d'exécution dans le cas des Enumtypes , comme si les static privatechamps avaient été initialisés avec des instances avant l' staticappel des blocs:

  1. Les Enumconstantes qui sont des champs statiques implicites. Cela nécessite le constructeur Enum et les blocs d'instance, et l'initialisation d'instance doit également se produire en premier.
  2. static bloc et initialisation des champs statiques dans l'ordre d'apparition.

Cette initialisation désordonnée (constructeur avant staticbloc) est importante à noter. Cela se produit également lorsque nous initialisons des champs statiques avec les instances de manière similaire à un singleton (simplifications apportées):

public class Foo {
  static { System.out.println("Static Block 1"); }
  public static final Foo FOO = new Foo();
  static { System.out.println("Static Block 2"); }
  public Foo() { System.out.println("Constructor"); }
  static public void main(String p[]) {
    System.out.println("In Main");
    new Foo();
  }
}

Ce que nous voyons est la sortie suivante:

Static Block 1
Constructor
Static Block 2
In Main
Constructor

Il est clair que l'initialisation statique peut réellement se produire avant le constructeur, et même après:

Le simple accès à Foo dans la méthode principale provoque le chargement de la classe et le démarrage de l'initialisation statique. Mais dans le cadre de l'initialisation statique, nous appelons à nouveau les constructeurs des champs statiques, après quoi il reprend l'initialisation statique et termine le constructeur appelé depuis la méthode principale. Situation assez complexe pour laquelle j'espère qu'en codage normal nous n'aurions pas à faire face.

Pour plus d'informations à ce sujet, consultez le livre " Java efficace ".

Yo-yo
la source
1
Avoir accès à aliasesne signifie pas que le bloc statique peut accéder aux membres non statiques. aliasesest accessible via les Languagevaleurs renvoyées par la values()méthode / static / . Comme vous le mentionnez, le fait que les variables enum soient déjà disponibles à ce stade est le bit inhabituel - les membres non statiques des classes régulières ne seraient pas accessibles dans cette situation.
Ignazio
Le bloc statique n'accède toujours qu'aux champs statiques (dans le cas de votre enum ANGLAIS, ALLEMAND, ...) qui dans ce cas sont des objets. Étant donné que les champs statiques sont des objets eux-mêmes, vous pouvez accéder au champ d'instance de l'objet statique.
Swami PR
1
class Foo { static final Foo Inst1; static final Foo Inst2; static{ Inst1 = new Foo("Inst1"); Inst2 = new Foo("Inst2"); } static { System.out.println("Inst1: " + Inst1.member); System.out.println("Inst2: " + Inst2.member); } private final String member; private Foo(String member){ this.member = member; } } Le code ci-dessus n'est pas différent de l'exemple enum et permet toujours l'accès à la variable d'instance à l'intérieur du bloc statique
Swami PR
@SwamiPR en effet, il compile, à ma grande surprise, et je dois admettre que le code n'est en principe pas différent. Je dois relire la spécification Java, je sens qu'il y a quelque chose qui m'a manqué. Bonne réponse, merci.
YoYo
@SwamiPR Le problème est vraiment que nous devrions utiliser un Enum. C'est le meilleur moyen de garantir que nous pointons vers des instances singulières »- voir ici . Et à vos points, j'ai fait plusieurs mises à jour.
YoYo
3

Si vos variables statiques doivent être définies lors de l'exécution, un static {...} bloc est très utile.

Par exemple, si vous devez définir le membre statique sur une valeur qui est stockée dans un fichier de configuration ou une base de données.

Également utile lorsque vous souhaitez ajouter des valeurs à un Mapmembre statique car vous ne pouvez pas ajouter ces valeurs dans la déclaration de membre initiale.

Marcus Leon
la source
3

Vous avez donc un champ statique (il est aussi appelé "variable de classe" car il appartient à la classe plutôt qu'à une instance de la classe; en d'autres termes, il est associé à la classe plutôt qu'à n'importe quel objet) et vous souhaitez l'initialiser. Donc, si vous ne voulez PAS créer une instance de cette classe et que vous souhaitez manipuler ce champ statique, vous pouvez le faire de trois manières:

1- Il suffit de l'initialiser lorsque vous déclarez la variable:

static int x = 3;

2- Avoir un bloc d'initialisation statique:

static int x;

static {
 x=3;
}

3- Avoir une méthode de classe (méthode statique) qui accède à la variable de classe et l'initialise: c'est l'alternative au bloc statique ci-dessus; vous pouvez écrire une méthode statique privée:

public static int x=initializeX();

private static int initializeX(){
 return 3;
}

Maintenant, pourquoi utiliseriez-vous un bloc d'initialisation statique au lieu de méthodes statiques?

Cela dépend vraiment de ce dont vous avez besoin dans votre programme. Mais vous devez savoir que le bloc d'initialisation statique est appelé une fois et le seul avantage de la méthode de classe est qu'il peut être réutilisé plus tard si vous devez réinitialiser la variable de classe.

disons que vous avez un tableau complexe dans votre programme. Vous l'initialisez (en utilisant pour loop par exemple), puis les valeurs de ce tableau changeront tout au long du programme, mais à un moment donné, vous voudrez le réinitialiser (revenir à la valeur initiale). Dans ce cas, vous pouvez appeler la méthode statique privée. Dans le cas où vous n'avez pas besoin de réinitialiser les valeurs dans votre programme, vous pouvez simplement utiliser le bloc statique et pas besoin d'une méthode statique car vous ne l'utiliserez pas plus tard dans le programme.

Remarque: les blocs statiques sont appelés dans l'ordre dans lequel ils apparaissent dans le code.

Exemple 1:

class A{
 public static int a =f();

// this is a static method
 private static int f(){
  return 3;
 }

// this is a static block
 static {
  a=5;
 }

 public static void main(String args[]) {
// As I mentioned, you do not need to create an instance of the class to use the class variable
  System.out.print(A.a); // this will print 5
 }

}

Exemple 2:

class A{
 static {
  a=5;
 }
 public static int a =f();

 private static int f(){
  return 3;
 }

 public static void main(String args[]) {
  System.out.print(A.a); // this will print 3
 }

}
Randa Sbeity
la source
0

Comme supplémentaire, comme l'a dit @Pointy

Le code de la ou des sections "statiques" sera exécuté au moment du chargement de la classe, avant que des instances de la classe ne soient construites (et avant que des méthodes statiques soient appelées ailleurs).

Il est censé s'ajouter System.loadLibrary("I_am_native_library")au bloc statique.

static{
    System.loadLibrary("I_am_a_library");
}

Il garantira qu'aucune méthode native ne sera appelée avant que la bibliothèque associée ne soit chargée en mémoire.

Selon loadLibrary d'Oracle :

Si cette méthode est appelée plusieurs fois avec le même nom de bibliothèque, le deuxième appel et les appels suivants sont ignorés.

Donc, de manière assez inattendue, mettre System.loadLibrary n'est pas utilisé pour éviter que la bibliothèque soit chargée plusieurs fois.

Eugène
la source
0

Vous devez d'abord comprendre que vos classes d'application elles-mêmes sont instanciées aux java.class.Classobjets pendant l'exécution. C'est à ce moment que vos blocs statiques sont exécutés. Vous pouvez donc réellement faire ceci:

public class Main {

    private static int myInt;

    static {
        myInt = 1;
        System.out.println("myInt is 1");
    }

    //  needed only to run this class
    public static void main(String[] args) {
    }

}

et il afficherait "myInt is 1" sur la console. Notez que je n'ai instancié aucune classe.

Emmanuel Osimosu
la source
0
static int B,H;
static boolean flag = true;
static{
    Scanner scan = new Scanner(System.in);
    B = scan.nextInt();
    scan.nextLine();
    H = scan.nextInt();

    if(B < 0 || H < 0){
        flag = false;
        System.out.println("java.lang.Exception: Breadth and height must be positive");
    } 
}
Vid
la source
-1

le bloc statique est utilisé pour toute technologie pour initialiser un membre de données statiques de manière dynamique, ou nous pouvons dire que pour l'initialisation dynamique du membre de données statiques, un bloc statique est utilisé. tout endroit où nous pouvons initialiser dynamiquement un membre de données statiques

Eg:-class Solution{
         // static int x=10;
           static int x;
       static{
        try{
          x=System.out.println();
          }
         catch(Exception e){}
        }
       }

     class Solution1{
      public static void main(String a[]){
      System.out.println(Solution.x);
        }
        }

Maintenant, mon int x statique s'initialisera dynamiquement .. Bcoz lorsque le compilateur ira à Solution.x il chargera la classe de solution et la charge de bloc statique au moment du chargement de la classe .. Nous pouvons donc initialiser dynamiquement ce membre de données statiques ..

}

Arun
la source