web-dev-qa-db-fra.com

Blocs d'initialisation statiques

Autant que je sache, le "bloc d'initialisation statique" est utilisé pour définir les valeurs d'un 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 comme statique (sans attribution de valeur). Et écrivez ensuite plusieurs lignes du code qui génèrent et attribuent une valeur au champ statique déclaré ci-dessus.

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

246
Roman

Le bloc non statique:

{
    // Do Something...
}

S'appelle à chaque fois une instance de la classe est construite. Le bloc statique est uniquement appelé ne 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
402

S'ils n'étaient pas dans un bloc d'initialisation statique, où seraient-ils? Comment déclareriez-vous une variable qui n'était censée être locale que pour les besoins de l'initialisation et la distinguiez-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 first et second n'étaient pas dans un bloc, ils ressembleraient à des champs. S'ils se trouvaient dans un bloc sans static devant, cela compterait comme un bloc d'initialisation d'instance au lieu d'un bloc d'initialisation statique, de sorte qu'il serait exécuté une fois par instance construite plutôt que 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 lorsque vous souhaitez attribuer plusieurs variables dans le même bloc, voire aucune (par exemple, si vous souhaitez simplement enregistrer quelque chose - ou peut-être initialiser une bibliothèque native).

124
Jon Skeet

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 dans la (les) section (s) "statique (s)" sera exécuté au moment du chargement de la classe, avant que toutes les instances de la classe ne soient construites (et avant que toutes les méthodes statiques ne 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 à l'ensemble des méthodes de constructeur définies pour la classe. Ils ressemblent à des blocs d’initialisation statiques, sauf que le mot clé "statique" est laissé.

98
Pointy

C'est également utile lorsque vous ne voulez pas affecter la valeur, telle que 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 que getStuff() jette ici un Exception qui appartient réellement à un bloc catch:

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

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

Un autre exemple est de faire des choses après, ce qui ne peut pas être fait lors de l'affectation:

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 correct utilise lui-même l'initialiseur static pour s'enregistrer dans le DriverManager. Voir aussi this et this répondre.

47
BalusC

Je dirais que static block n'est qu'un sucre syntaxique. Vous ne pouvez rien faire avec le bloc static et rien d’autre.

Pour réutiliser quelques exemples postés ici.

Ce morceau de code pourrait être ré-écrit sans utiliser static initializer.

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;
}
11
user1508893

Il existe quelques raisons pour lesquelles il est nécessaire d’exister:

  1. initialisation de static final membres dont l'initialisation peut générer une exception
  2. initialisation de static final membres avec des valeurs calculées

Les gens ont tendance à utiliser les blocs static {} comme moyen pratique d'initialiser les éléments dont dépend la classe au cours de l'exécution - par exemple, en s'assurant que cette classe est chargée (par exemple, les pilotes JDBC). Cela peut être fait autrement. Cependant, les deux choses que je viens de mentionner ne peuvent être faites qu'avec une construction comme le bloc static {}.

10
D.Shawley

Vous pouvez exécuter des bits de code une fois pour une classe avant qu'un objet ne soit construit 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());
  }
}

C'est une idée fausse commune de penser qu'un bloc statique n'a qu'un accès aux champs statiques. Pour cela, j'aimerais montrer ci-dessous un morceau de code que j'utilise assez souvent dans des projets réels (copié partiellement à partir de 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 gérer un index (ALIAS_MAP), pour mapper un ensemble d'alias sur le type enum d'origine. Il est conçu comme une extension de la méthode intégrée valueOf fournie par le Enum lui-même.

Comme vous pouvez le constater, l’initialiseur statique accède même au champ privatealiases. Il est important de comprendre que le bloc static a déjà accès aux instances de valeur Enum (par exemple, ENGLISH). Ceci est dû au fait que ordre d'initialisation et d'exécution dans le cas de Enum types , comme si les champs static private avaient été initialisés avec des instances précédant les blocs static ont été appelés:

  1. Les constantes Enum qui sont des champs statiques implicites. Cela nécessite le constructeur et les blocs d'instance Enum, ainsi que l'initialisation de l'instance.
  2. static bloc et initialisation des champs statiques dans l'ordre d'apparition.

Il est important de noter cette initialisation hors d’ordre (constructeur avant bloc static). Cela se produit également lorsque nous initialisons les champs statiques avec les instances de la même manière qu'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

Clairement, c’est que l’initialisation statique peut réellement se produire avant le constructeur, et même après:

Le simple fait d’accéder à Foo dans la méthode principale entraîne 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 pour les champs statiques, après quoi l'initialisation statique reprend et complète le constructeur appelé à partir de la méthode principale. Situation assez complexe pour laquelle j'espère que dans le codage normal nous n'aurons pas à nous occuper.

Pour plus d'informations à ce sujet, voir le livre " Effective Java ".

7
YoYo

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

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

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

3
Marcus Leon

Vous avez donc un champ statique (également 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 voulez manipuler ce champ statique, vous pouvez le faire de trois manières:

1- Initialisez-le simplement 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 voudriez-vous utiliser un bloc d’initialisation statique au lieu de méthodes statiques?

C'est vraiment ce dont vous avez besoin dans votre programme. Mais vous devez savoir que le bloc d’initialisation statique est appelé une fois et que le seul avantage de la méthode de classe est qu’ils peuvent être réutilisés ultérieurement si vous devez réinitialiser la variable de classe.

disons que vous avez un tableau complexe dans votre programme. Vous l'initialisez (à l'aide de la boucle for par exemple), puis les valeurs de ce tableau changeront tout au long du programme, mais vous souhaiterez ensuite le réinitialiser (revenir à la valeur initiale). Dans ce cas, vous pouvez appeler la méthode statique privée. Au cas où vous n'auriez pas besoin de réinitialiser les valeurs dans votre programme, vous pouvez simplement utiliser le bloc statique sans avoir recours à 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
 }

}
2
Randa Sbeity

Vous devez d’abord comprendre que vos classes d’application sont elles-mêmes instanciées en objets Java.class.Class lors de l’exécution. C'est à ce moment que vos blocs statiques sont exécutés. Donc, vous pouvez 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 cela afficherait "myInt is 1" sur la console. Notez que je n'ai instancié aucune classe.

0
eosimosu

En complément, comme @Pointy dit

Le code dans la (les) section (s) "statique (s)" sera exécuté au moment du chargement de la classe, avant que toutes les instances de la classe ne soient construites (et avant que toutes les méthodes statiques ne soient appelées ailleurs).

Il est supposé ajouter System.loadLibrary("I_am_native_library") dans un bloc statique.

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

Cela 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, les appels suivants et suivants sont ignorés.

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

0
Gearon
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");
    } 
}
0
Vid