วิธีการเรียกใช้ getClass () จากวิธีการคงที่ใน Java?


350

ฉันมีคลาสที่ต้องมีวิธีการคงที่ ภายในวิธีการคงที่เหล่านี้ฉันต้องเรียกวิธี getClass () เพื่อโทรออกดังต่อไปนี้:

public static void startMusic() {
  URL songPath = getClass().getClassLoader().getResource("background.midi");
}

อย่างไรก็ตาม Eclipse บอกฉัน:

Cannot make a static reference to the non-static method getClass() 
from the type Object

วิธีที่เหมาะสมในการแก้ไขข้อผิดพลาดในเวลารวบรวมนี้คืออะไร?


การใช้getResource()ก่อนมีอินสแตนซ์ของคลาสที่ผู้ใช้กำหนด (เช่นที่ไม่ใช่ J2SE) บางครั้งจะล้มเหลว ปัญหาคือ JRE จะใช้ bootstrap class-loader ในระยะนั้นซึ่งจะไม่มีทรัพยากรแอปพลิเคชันบน class-path (ของ bootstrap loader)
Andrew Thompson

คำตอบ:


617

คำตอบ

เพียงแค่ใช้แทนTheClassName.classgetClass()

การประกาศตัวบันทึก

ตั้งแต่นี้ได้รับความสนใจมากสำหรับ usecase เฉพาะ - เพื่อให้วิธีที่ง่ายในการแทรกการประกาศบันทึก - ฉันคิดว่าฉันจะเพิ่มความคิดของฉันในที่ กรอบการทำงานของบันทึกมักจะคาดว่าการบันทึกจะถูก จำกัด กับบริบทบางอย่างพูดชื่อคลาสที่ผ่านการรับรองโดยสมบูรณ์ ดังนั้นจึงไม่สามารถคัดลอกได้โดยไม่มีการดัดแปลง คำแนะนำสำหรับการประกาศบันทึกการวางปลอดภัยมีไว้ในคำตอบอื่น ๆ แต่มีข้อเสียเช่นการขยาย bytecode หรือเพิ่มการวิปัสสนาแบบรันไทม์ ฉันไม่แนะนำสิ่งเหล่านี้ การคัดลอกวางเป็นปัญหาของบรรณาธิการดังนั้นวิธีแก้ไขจึงเหมาะสมที่สุด

ใน IntelliJ ฉันขอแนะนำให้เพิ่มเทมเพลตสด:

  • ใช้ "log" เป็นตัวย่อ
  • ใช้private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger($CLASS$.class);เป็นข้อความเทมเพลต
  • คลิกแก้ไขตัวแปรและเพิ่ม CLASS โดยใช้นิพจน์ className()
  • ทำเครื่องหมายที่ช่องเพื่อทำการฟอร์แมตและทำให้ชื่อ FQ สั้นลง
  • เปลี่ยนบริบทเป็น Java: declaration

ตอนนี้ถ้าคุณพิมพ์log<tab>มันจะขยายไปโดยอัตโนมัติ

private static final Logger logger = LoggerFactory.getLogger(ClassName.class);

และจัดรูปแบบโดยอัตโนมัติและเพิ่มประสิทธิภาพการนำเข้าสำหรับคุณ


6
สิ่งนี้จะส่งผลให้เกิดการคัดลอก / วางและผลลัพธ์ที่น่าเศร้าเมื่อรหัสที่ไม่ถูกต้องทำงานในคลาสอื่น
William Entriken

1
@WilliamEntriken: การใช้คลาสภายในแบบไม่ระบุชื่อนั้นไม่ใช่วิธีการแก้ปัญหาที่ยอดเยี่ยม แต่เพียงแค่ขยายโค้ดไบต์ของคุณด้วยคลาสไฟล์พิเศษเพื่อประหยัดเวลาในการพัฒนาสองสามวินาที ฉันไม่แน่ใจว่าสิ่งที่ผู้คนทำในที่ที่พวกเขาต้องคัดลอก / วางทั่วสถานที่ แต่ควรได้รับการแก้ไขด้วยการแก้ไขไม่ใช่แฮ็คภาษา เช่นหากใช้ IntelliJ ให้ใช้เทมเพลตสดที่สามารถแทรกชื่อคลาสได้
Mark Peters

“ ฉันไม่แน่ใจว่าสิ่งที่ผู้คนกำลังทำในสิ่งที่พวกเขาต้องคัดลอก / วางทั่วสถานที่” - บางอย่างเช่นการใช้Logใน Android ซึ่งต้องใช้แท็กที่จะให้มาโดยทั่วไปจะเป็นชื่อคลาส และอาจมีตัวอย่างอื่น ๆ แน่นอนคุณสามารถประกาศเขตข้อมูลสำหรับ (ซึ่งเป็นเรื่องธรรมดา) แต่ยังคงคัดลอกและวาง
user149408

@ user149408: ใช่มีการพูดถึงเรื่องนี้มากมาย แม้ว่า usecase นี้จะไม่มีอะไรเกี่ยวข้องกับคำถามเดิม แต่เป็นเหตุผลทั่วไปที่ผู้คนเข้าชมคำถามนี้ดังนั้นฉันจึงได้เพิ่มความคิดลงไปในคำตอบ
Mark Peters

1
คุณลืมสิ่งหนึ่งในการแก้ปัญหาของคุณแม่แบบสด: คลิก "แก้ไขตัวแปร" และในการแสดงออกสำหรับการพิมพ์CLASS className()นั่นจะทำให้แน่ใจว่า $ CLASS $ ถูกแทนที่ด้วยชื่อคลาสมิฉะนั้นจะว่างเปล่า
HerbCSO

122

สำหรับตัวอย่างรหัสในคำถามโซลูชันมาตรฐานคือการอ้างอิงคลาสอย่างชัดเจนโดยใช้ชื่อและสามารถทำได้โดยไม่ต้องgetClassLoader()โทร:

class MyClass {
  public static void startMusic() {
    URL songPath = MyClass.class.getResource("background.midi");
  }
}

วิธีการนี้ยังคงมีด้านหลังที่ไม่ปลอดภัยต่อข้อผิดพลาดในการคัดลอก / วางในกรณีที่คุณต้องการทำซ้ำรหัสนี้เป็นจำนวนคลาสที่คล้ายกัน

และสำหรับคำถามที่แน่นอนในพาดหัวมีเคล็ดลับโพสต์ในหัวข้อที่อยู่ติดกัน :

Class currentClass = new Object() { }.getClass().getEnclosingClass();

มันใช้Objectคลาสย่อยที่ไม่ระบุชื่อซ้อนเพื่อรับบริบทการดำเนินการ เคล็ดลับนี้มีประโยชน์ในการคัดลอก / วางอย่างปลอดภัย ...

ข้อควรระวังเมื่อใช้สิ่งนี้ในคลาสฐานที่คลาสอื่นสืบทอดมาจาก:

นอกจากนี้ยังเป็นที่น่าสังเกตว่าถ้าส่วนย่อยนี้มีรูปร่างเป็นวิธีคงที่ของคลาสฐานบางcurrentClassค่าจะอ้างอิงถึงคลาสฐานนั้นแทนคลาสย่อยใด ๆ ที่อาจใช้เมธอดนั้นเสมอ


23
โดยคำตอบที่ฉันโปรดปราน NameOfClass.class ชัดเจน แต่คุณต้องทราบชื่อคลาสของคุณ เคล็ดลับ getEnclosingClass ไม่เพียง แต่มีประโยชน์สำหรับการคัดลอก - วางยังมีประโยชน์สำหรับวิธีการเรียนฐานคงที่ที่ใช้ประโยชน์จากการวิปัสสนา
Domingo Ignacio

1
อนึ่งClass.getResource()อาจทำงานแตกต่างกันเล็กน้อยจากClassLoader.getResource(); ดูstackoverflow.com/a/676273
augurar

69

ใน Java7 + คุณสามารถทำได้ในเมธอด / ฟิลด์แบบสแตติก:

MethodHandles.lookup().lookupClass()

วิธีแก้ปัญหาที่ดีถ้าคุณต้องการเพียงรับgetSimpleName ()เพื่อพิมพ์ความช่วยเหลือจากวิธีการหลัก
Dmitrii Bulashevich

6
ฉันกำลังมองหาโซลูชัน 'copy paste' สำหรับเพิ่มตัวบันทึกทุกที่โดยไม่ต้องเปลี่ยนชื่อคลาสในแต่ละครั้ง คุณมอบให้ฉัน: private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
Julien Feniou

ทางออกที่ดีที่สุดที่รองรับคือข้อเสียคือ Android ไม่รองรับจนถึง API 26 เช่น Android 8
user149408

24

ฉันปล้ำด้วยตัวเอง เคล็ดลับที่ดีคือใช้ใช้เธรดปัจจุบันเพื่อรับ ClassLoader เมื่ออยู่ในบริบทคงที่ นี้จะทำงานใน Hadoop MapReduce เช่นกัน วิธีการอื่น ๆ ทำงานเมื่อทำงานในเครื่อง แต่กลับ null InputStream เมื่อใช้ใน MapReduce

public static InputStream getResource(String resource) throws Exception {
   ClassLoader cl = Thread.currentThread().getContextClassLoader();
   InputStream is = cl.getResourceAsStream(resource);
   return is;
}


11

getClass() วิธีการที่ถูกกำหนดไว้ในคลาสวัตถุที่มีลายเซ็นต่อไปนี้:

คลาสสุดท้ายสาธารณะ getClass ()

เนื่องจากไม่ได้กำหนดเป็นstaticคุณจึงไม่สามารถเรียกมันได้ในบล็อกรหัสคงที่ ดูคำตอบเหล่านี้สำหรับข้อมูลเพิ่มเติม: Q1 , Q2 , Q3

หากคุณอยู่ในบริบทที่คงที่คุณต้องใช้นิพจน์ตัวอักษรของคลาสเพื่อรับคลาสดังนั้นคุณต้องทำดังนี้:

Foo.class

ประเภทของนิพจน์นี้เรียกว่าClass Literalsและอธิบายไว้ในJava Language Specification Bookดังต่อไปนี้:

คลาสตัวอักษรคือนิพจน์ที่ประกอบด้วยชื่อของคลาสอินเทอร์เฟซอาร์เรย์หรือชนิดดั้งเดิมตามด้วย ` และชั้นโทเค็น ชนิดของตัวอักษรคลาสคือคลาส มันประเมินให้กับวัตถุ Class สำหรับประเภทที่ระบุ (หรือโมฆะ) ตามที่กำหนดโดยการกำหนด class loader ของคลาสของอินสแตนซ์ปัจจุบัน

นอกจากนี้คุณยังสามารถค้นหาข้อมูลเกี่ยวกับเรื่องนี้ในเอกสาร APIสำหรับ Class


9

ลองมัน

Thread.currentThread().getStackTrace()[1].getClassName()

หรือ

Thread.currentThread().getStackTrace()[2].getClassName()

ความสวยงามของวิธีการนี้คือมันจะทำงานบน Android เวอร์ชันก่อนหน้า 8 (API 26) ระวังดัชนีที่[1]จะทำให้ข้อความบันทึกทั้งหมดรายงานThreadเป็นแหล่งที่มาของพวกเขาใน Android 4.4.4 [2]ดูเหมือนว่าจะให้ผลลัพธ์ที่ถูกต้องทั้งใน Android และ OpenJRE
user149408

0

สมมติว่ามีคลาส Utility แล้วรหัสตัวอย่างจะเป็น -

    URL url = Utility.class.getClassLoader().getResource("customLocation/".concat("abc.txt"));

CustomLocation - หากโครงสร้างโฟลเดอร์ใด ๆ ภายในทรัพยากรเป็นอย่างอื่นเอาสตริงตัวอักษรนี้


-2

ลองอะไรเช่นนี้ มันใช้งานได้สำหรับฉัน Logg (ชื่อคลาส)

    String level= "";

    Properties prop = new Properties();

    InputStream in =
            Logg.class.getResourceAsStream("resources\\config");

    if (in != null) {
        prop.load(in);
    } else {
        throw new FileNotFoundException("property file '" + in + "' not found in the classpath");
    }

    level = prop.getProperty("Level");

1
ไม่แน่ใจว่าทำไมคุณจึงให้คำตอบเดียวกับที่มีอยู่ในชุดข้อความนี้สามครั้ง: สองครั้งจาก 2011 ครั้งเดียวจาก 2013
Antares42

วิธีการแก้ปัญหานี้จะทำงานถ้า Logg คลาสนั้นถูกโหลดโดย classloader พร้อมการเข้าถึงโฟลเดอร์ "resources \\ config" บนเซิร์ฟเวอร์บางเครื่องที่มีหลาย classloaders (โดยตัวอย่างหนึ่ง classloader เพื่อโหลดโฟลเดอร์ lib และอื่น ๆ เพื่อโหลด libs และทรัพยากรแอป) ที่ไม่เป็นความจริง ด้วยเหตุนี้การแก้ปัญหาจึงก่อให้เกิดคำตอบนี้บางครั้งก็เป็นไปได้ด้วยความบังเอิญ!
มานูเอลโรมิโร
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.