簡體   English   中英

將“this”傳遞給 Java 中的私有最終成員

[英]Passing "this" to private final members in Java

基於什么是“不完全構造的對象”? 以及JVM 的隱式 memory 障礙在鏈接構造函數時如何表現? 在構造函數中使用this被認為是不好的做法,應該避免。 初始化private final成員也是如此嗎?

例如下面的代碼,將this傳遞給Chapter是否被認為是不好的做法? 我知道如果Book也以與章節相同的方式初始化,那么這個問題將不會出現,問題是book.titleChapter構造函數中的null

public class Book {
    protected final String title; // = "Title" 

    private class Chapter {
        private final Book book;
        Chapter(Book b) {
            book = b; // Here book.title is null
        }
    }
    private final Chapter chapter1 = new Chapter(this); // Bad Practice or valid in some cases? 
    private final Chapter chapter2 = new Chapter(this);
    Book() {
        title = "Book: 2 Chapters";
    }
}

是的,不好的做法。 從某種意義上說,它與在構造函數中傳遞this的情況實際上是相同的。

During the initialization of an object, there's a rather complex rigamarole that the runtime has to go through, considering that [A] the class itself must be available first (and this static initialization which is also quite complicated), [B] the constructor必須運行,但為了做到這一點,必須首先運行父級的調用構造函數,並且 [C] 構造函數不是全部,還有實例初始化程序塊!

每當您有一個分配了值的(非靜態)字段時,只有兩個選項:

A.那個值是一個編譯時間常數,比如private int x = 5; . 常量很少——基本上是原語和字符串。

B. 其他任何事情。

按照“A”的常數直接存儲在 class 文件中。 但是其他任何東西都不能“僅僅”直接存儲在 class 文件中(列出字段的塊對於 state 有空間,它的值必須從常量池初始化為特定常量,但是System.currentTimeMillis()new Chapter(this)顯然不是你可以堅持在常量池中的東西)。 在 class 文件級別,這基本上只是為您移動到構造函數中。 一個實際的方法是用一個時髦的名字( <init> ,它不是一個有效的標識符,所以它不可能與一個名為<init>的實際方法沖突,因為那不是有效的java)並被調用。 您可以自己編寫初始化程序,使用 java 語法的奇異位:只需在 class 定義中直接扔大括號:

class Test {
  public Test() {
    hello(30);
  }

  int foo = 5; // constant
  int bar = hello(10);
  { System.out.println("Instance initializer"); }
  int baz = hello(20);

  static int hello(int in) {
    System.out.println("Hello: " + in);
    return in;
  }

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

你可以編譯並運行上面的,你會得到:

Hello: 10
Instance initializer
Hello: 20
Hello: 30

它向您展示了它是如何工作的:所有“實例初始化”都以“詞法順序”運行(按照它們在源文件中出現的順序),並且private int bar = hello(10);之間沒有區別{hello(20);} - 它們都是實例初始化器。

大喊“不好的做法!!!!”的問題是編程對於那種死記硬背的規則應用來說太難了 如果就這么簡單,我們就讓計算機自己編程。

不,嚴格遵守一系列不良做法只是意味着你最終會得到更糟糕的代碼。 你不能用“經驗法則”來清理代碼('clean' 定義為:易於閱讀(這反過來意味着:你很快就會對代碼的作用有了初步印象,並且這個初始印象不會誤導),易於測試,靈活(定義為:當預期的更改請求出現時,處理這些請求並不特別困難,並且不需要對與此代碼交互的代碼進行很多更改,即很少或沒有 API 更改例子))。

因此,您必須首先了解為什么在構造函數中傳遞this是“不好的做法”。 不理解為什么意味着該規則對您毫無用處。

原因是 - 您已完成初始化,變量可能還沒有值。 讓我們看看它的實際效果!

class Test {
  final int bar = hello(this, 10);
  final int baz = hello(this, 20);
  final int foo = hello(this, 30);

  public static int hello(Test t, int v) {
    System.out.println("BAZ: " + t.baz);
    return v;
  }

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

運行此打印:

BAZ: 0
BAZ: 0
BAZ: 20

完全是,離牆,堅果 final變量是如何變化的?!?!!!

這就是為什么這是一個非常糟糕的主意的最大原因。 還有很多,但這個很容易理解,足以說明為什么你永遠不應該這樣做。 該行為通常屬於“令人難以置信的令人困惑的黑客”類別。 這意味着如果你必須訴諸這樣的事情,你必須添加大量關於為什么、將會發生什么以及你期望的前置和后置條件的評論。 通常,無論您花多少時間編寫和維護這些評論(請記住,維護評論真的很困難,因為您無法對它們進行單元測試)。 當然,無論您認為這樣做是為了實現什么捷徑,這都不值得,但是,嘿,如果真的值得,那么一定要這樣做。 破解。 不要忘記那些評論。

EXERCISE FOR THE READER: If you want to learn more, I suggest you write a more extensive example which also involves static initializers (non-constant expressions that initialize static fields, as well as static { codeGoesHere(); } , just like that in一個class定義,例如class Foo { static { System.out.println("HELLO;"); }} ),以及類型的層次結構。 對於加分,讓父類型的構造函數調用一個不是最終的方法,然后在您的子類中覆蓋該方法並讓該方法與字段交互。 例子:

class Parent {
  Parent() {
    System.out.println(thisIsWeird());
  }

  String thisIsWeird() {
    return "Parent";
  }
}

class Child extends Parent {
  private final String field = "Simple".toLowerCase();

  @Override String thisIsWeird() {
    return field;
  }
}

然后運行它,開始得出結論(然后准備忘記所有這些細節,只記住結果,即:不要做這種瘋狂的事情,這不值得)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM