Sự khác biệt giữa khối mã khởi tạo tĩnh và không tĩnh là gì


357

Câu hỏi của tôi là về một cách sử dụng cụ thể của từ khóa tĩnh. Có thể sử dụng statictừ khóa để bao gồm một khối mã trong một lớp không thuộc về bất kỳ chức năng nào. Ví dụ: biên dịch mã sau đây:

public class Test {
    private static final int a;    
    static {
        a = 5;
        doSomething(a);
    }
    private static int doSomething(int x) {
        return (x+5);
    }
}

Nếu bạn loại bỏ statictừ khóa, nó sẽ phàn nàn vì biến afinal. Tuy nhiên, có thể loại bỏ cả hai finalstatictừ khóa và làm cho nó biên dịch.

Đó là khó hiểu cho tôi theo cả hai cách. Làm thế nào tôi có thể có một phần mã không thuộc về phương thức nào? Làm thế nào nó có thể gọi nó? Nói chung, mục đích của việc sử dụng này là gì? Hoặc tốt hơn, tôi có thể tìm tài liệu về điều này ở đâu?

Câu trả lời:


403

Khối mã với bộ sửa đổi tĩnh biểu thị một trình khởi tạo lớp ; không có bộ sửa đổi tĩnh, khối mã là một bộ khởi tạo cá thể .

Các trình khởi tạo lớp được thực hiện theo thứ tự chúng được xác định (từ trên xuống, giống như các trình khởi tạo biến đơn giản) khi lớp được tải (thực ra, khi nó được giải quyết, nhưng đó là một kỹ thuật).

Bộ khởi tạo sơ thẩm được thực thi theo thứ tự được xác định khi lớp được khởi tạo, ngay trước khi mã xây dựng được thực thi, ngay sau khi gọi siêu cấu trúc.

Nếu bạn loại bỏ statickhỏi int a, nó sẽ trở thành một biến thể hiện mà bạn không thể truy cập từ khối khởi tạo tĩnh. Điều này sẽ không thể biên dịch với lỗi "biến không tĩnh a không thể được tham chiếu từ ngữ cảnh tĩnh".

Nếu bạn cũng loại bỏ statickhỏi khối khởi tạo, thì nó sẽ trở thành một trình khởi tạo cá thể và do đó int ađược khởi tạo khi xây dựng.


Bộ khởi tạo tĩnh thực sự được gọi sau này, khi lớp được khởi tạo, sau khi nó được tải và liên kết. Điều đó xảy ra khi bạn khởi tạo một đối tượng của một lớp hoặc truy cập một biến hoặc phương thức tĩnh trên lớp. Trong thực tế nếu bạn có một lớp với một trình khởi tạo tĩnh và một phương thức public static void staticMethod(){}, nếu bạn thực thi TestStatic.class.getMethod("staticMethod");. Bộ khởi tạo tĩnh sẽ không được gọi. Thêm thông tin ở đây docs.oracle.com/javase/specs/jvms/se10/html/iêu
Totò

@ Totò: Vâng, đó là những gì độ phân giải của lớp đòi hỏi (ít nhất họ đã sử dụng để gọi nó là liên kết + init như "độ phân giải" khi trở lại). Tôi không ngạc nhiên khi bạn có thể sử dụng sự phản chiếu để khám phá những điều về một lớp mà không giải quyết nó.
Lawrence Dol

166

Uff! Khởi tạo tĩnh là gì?

Trình khởi tạo tĩnh là một static {}khối mã bên trong lớp java và chỉ chạy một lần trước khi hàm tạo hoặc phương thức chính được gọi.

ĐỒNG Ý! Nói cho tôi biết thêm ...

  • là một khối mã static { ... }bên trong bất kỳ lớp java nào. và được thực thi bởi máy ảo khi lớp được gọi.
  • Không có returntuyên bố được hỗ trợ.
  • Không có đối số được hỗ trợ.
  • Không thishoặc superđược hỗ trợ.

Hmm tôi có thể sử dụng nó ở đâu?

Có thể được sử dụng bất cứ nơi nào bạn cảm thấy ok :) đơn giản. Nhưng tôi thấy hầu hết thời gian nó được sử dụng khi thực hiện kết nối cơ sở dữ liệu, API init, Ghi nhật ký, v.v.

Đừng chỉ sủa! ví dụ ở đâu

package com.example.learnjava;

import java.util.ArrayList;

public class Fruit {

    static {
        System.out.println("Inside Static Initializer.");

        // fruits array
        ArrayList<String> fruits = new ArrayList<>();
        fruits.add("Apple");
        fruits.add("Orange");
        fruits.add("Pear");

        // print fruits
        for (String fruit : fruits) {
            System.out.println(fruit);
        }
        System.out.println("End Static Initializer.\n");
    }

    public static void main(String[] args) {
        System.out.println("Inside Main Method.");
    }
}

Đầu ra ???

Bên trong khởi tạo tĩnh.

táo

trái cam

Kết thúc khởi tạo tĩnh.

Phương pháp chính bên trong.

Hi vọng điêu nay co ich!


Cảm ơn Madan! Khối tĩnh có thể được sử dụng thay cho afterPropertiesSet()các InitializingBean?
Alexander Suraphel

3
Vâng, bạn có thể! Trình khởi tạo tĩnh được gọi khi lớp được tải bởi jvm. Vì vậy, đây là giai đoạn thực sự đầu tiên mà mã được thực thi. Nếu bạn cũng có một hàm tạo, thứ tự sẽ là: trình khởi tạo tĩnh, hàm tạo, afterProperationsset
Martin Baumgartner

57

Các statickhối là một "initializer tĩnh".

Nó tự động được gọi khi lớp được tải và không có cách nào khác để gọi nó (thậm chí không thông qua Reflection).

Cá nhân tôi chỉ từng sử dụng nó khi viết mã JNI:

class JNIGlue {
    static {
        System.loadLibrary("foo");
    }
}

6
Không, không có cách rõ ràng để gọi nó, trình khởi tạo lớp không bao giờ được đại diện bởi một Methodcá thể mà chỉ được gọi bởi máy ảo Java.
Rafael Winterhalter

46

Đây là trực tiếp từ http://www.programcalet.com/2011/10/java- class-instance -initators /

1. Lệnh thi hành án

Nhìn vào lớp sau, bạn có biết cái nào được thực hiện trước không?

public class Foo {

    //instance variable initializer
    String s = "abc";

    //constructor
    public Foo() {
        System.out.println("constructor called");
    }

    //static initializer
    static {
        System.out.println("static initializer called");
    }

    //instance initializer
    {
        System.out.println("instance initializer called");
    }

    public static void main(String[] args) {
        new Foo();
        new Foo();
    }
}

Đầu ra:

khởi tạo tĩnh được gọi là

trình khởi tạo cá thể được gọi là

nhà xây dựng được gọi là

trình khởi tạo cá thể được gọi là

nhà xây dựng được gọi là

2. Trình khởi tạo cá thể Java hoạt động như thế nào?

Trình khởi tạo cá thể ở trên có chứa một câu lệnh println. Để hiểu cách thức hoạt động của nó, chúng ta có thể coi nó như một câu lệnh gán biến, ví dụ : b = 0. Điều này có thể làm cho nó rõ ràng hơn để hiểu.

Thay vì

int b = 0, bạn có thể viết

int b;
b = 0;

Do đó, bộ khởi tạo cá thể và bộ khởi tạo biến thể hiện khá giống nhau.

3. Khi nào thì khởi tạo cá thể hữu ích?

Việc sử dụng các trình khởi tạo cá thể rất hiếm, nhưng nó vẫn có thể là một sự thay thế hữu ích cho các trình khởi tạo biến thể hiện nếu:

  1. Mã khởi tạo phải xử lý các ngoại lệ
  2. Thực hiện các phép tính không thể biểu thị bằng trình khởi tạo biến thể hiện.

Tất nhiên, mã như vậy có thể được viết trong các nhà xây dựng. Nhưng nếu một lớp có nhiều hàm tạo, bạn sẽ phải lặp lại mã trong mỗi hàm tạo.

Với một trình khởi tạo cá thể, bạn chỉ có thể viết mã một lần và nó sẽ được thực thi bất kể hàm tạo nào được sử dụng để tạo đối tượng. (Tôi đoán đây chỉ là một khái niệm và nó không được sử dụng thường xuyên.)

Một trường hợp khác trong đó các trình khởi tạo cá thể hữu ích là các lớp bên trong ẩn danh, không thể khai báo bất kỳ hàm tạo nào. (Đây có phải là nơi tốt để đặt chức năng ghi nhật ký không?)

Cảm ơn Derhein.

Cũng lưu ý rằng các lớp ẩn danh thực hiện giao diện [1] không có hàm tạo. Do đó, khởi tạo cá thể là cần thiết để thực hiện bất kỳ loại biểu thức nào tại thời điểm xây dựng.


12

"Final" đảm bảo rằng một biến phải được khởi tạo trước khi kết thúc mã khởi tạo đối tượng. Tương tự như vậy "tĩnh cuối" đảm bảo rằng một biến sẽ được khởi tạo vào cuối mã khởi tạo lớp. Việc bỏ "tĩnh" khỏi mã khởi tạo của bạn sẽ biến nó thành mã khởi tạo đối tượng; do đó biến của bạn không còn thỏa mãn các đảm bảo của nó.


8

Bạn sẽ không viết mã vào một khối tĩnh cần được gọi ở bất cứ đâu trong chương trình của bạn. Nếu mục đích của mã là được gọi thì bạn phải đặt nó trong một phương thức.

Bạn có thể viết các khối khởi tạo tĩnh để khởi tạo các biến tĩnh khi lớp được tải nhưng mã này có thể phức tạp hơn ..

Một khối khởi tạo tĩnh trông giống như một phương thức không có tên, không có đối số và không có kiểu trả về. Vì bạn không bao giờ gọi nó nên nó không cần tên. Lần duy nhất nó được gọi là khi máy ảo tải lớp.


6

khi nhà phát triển sử dụng khối khởi tạo, Trình biên dịch Java sao chép trình khởi tạo vào từng hàm tạo của lớp hiện tại.

Thí dụ:

đoạn mã sau:

class MyClass {

    private int myField = 3;
    {
        myField = myField + 2;
        //myField is worth 5 for all instance
    }

    public MyClass() {
        myField = myField * 4;
        //myField is worth 20 for all instance initialized with this construtor
    }

    public MyClass(int _myParam) {
        if (_myParam > 0) {
            myField = myField * 4;
            //myField is worth 20 for all instance initialized with this construtor
            //if _myParam is greater than 0
        } else {
            myField = myField + 5;
            //myField is worth 10 for all instance initialized with this construtor
            //if _myParam is lower than 0 or if _myParam is worth 0
        }
    }

    public void setMyField(int _myField) {
        myField = _myField;
    }


    public int getMyField() {
        return myField;
    }
}

public class MainClass{

    public static void main(String[] args) {
        MyClass myFirstInstance_ = new MyClass();
        System.out.println(myFirstInstance_.getMyField());//20
        MyClass mySecondInstance_ = new MyClass(1);
        System.out.println(mySecondInstance_.getMyField());//20
        MyClass myThirdInstance_ = new MyClass(-1);
        System.out.println(myThirdInstance_.getMyField());//10
    }
}

tương đương với:

class MyClass {

    private int myField = 3;

    public MyClass() {
        myField = myField + 2;
        myField = myField * 4;
        //myField is worth 20 for all instance initialized with this construtor
    }

    public MyClass(int _myParam) {
        myField = myField + 2;
        if (_myParam > 0) {
            myField = myField * 4;
            //myField is worth 20 for all instance initialized with this construtor
            //if _myParam is greater than 0
        } else {
            myField = myField + 5;
            //myField is worth 10 for all instance initialized with this construtor
            //if _myParam is lower than 0 or if _myParam is worth 0
        }
    }

    public void setMyField(int _myField) {
        myField = _myField;
    }


    public int getMyField() {
        return myField;
    }
}

public class MainClass{

    public static void main(String[] args) {
        MyClass myFirstInstance_ = new MyClass();
        System.out.println(myFirstInstance_.getMyField());//20
        MyClass mySecondInstance_ = new MyClass(1);
        System.out.println(mySecondInstance_.getMyField());//20
        MyClass myThirdInstance_ = new MyClass(-1);
        System.out.println(myThirdInstance_.getMyField());//10
    }
}

Tôi hy vọng ví dụ của tôi được hiểu bởi các nhà phát triển.


4

Khối mã tĩnh có thể được sử dụng để khởi tạo hoặc khởi tạo các biến lớp (trái ngược với các biến đối tượng). Vì vậy, khai báo "a" có nghĩa là chỉ một chia sẻ cho tất cả các đối tượng Kiểm tra và khối mã tĩnh chỉ khởi tạo "a" một lần, khi lớp Kiểm tra được tải lần đầu tiên, bất kể có bao nhiêu đối tượng Kiểm tra được tạo.


Theo dõi, nếu tôi không tạo một thể hiện của đối tượng mà thay vào đó tôi gọi một hàm tĩnh công khai. Liệu nó có nghĩa là khối này được đảm bảo thực thi trước khi gọi hàm công khai này? Cảm ơn.
Szere Dyeri

Nếu bạn gọi một hàm tĩnh công khai của lớp, thì lớp này cần được tải trước, vì vậy, bộ khởi tạo tĩnh sẽ thực thi trước.
Paul Tomblin

Trừ khi đó là khởi tạo lớp mà (gián tiếp) gọi là mã đang cố sử dụng nó. IFYSWIM. Phụ thuộc thông tư và tất cả những điều đó.
Tom Hawtin - tackline

1
@Tom đã đúng - có thể viết một cái gì đó trong đó một trình khởi tạo tĩnh gọi một phương thức tĩnh trước khi một trình khởi tạo tĩnh khác được gọi, nhưng tâm trí tôi nhớ lại suy nghĩ nên tôi không bao giờ xem xét nó.
Paul Tomblin
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.