ที่รันไทม์ค้นหาคลาสทั้งหมดในแอ็พพลิเคชัน Java ที่ขยายคลาสฐาน


147

ฉันต้องการทำสิ่งนี้:

List<Animal> animals = new ArrayList<Animal>();

for( Class c: list_of_all_classes_available_to_my_app() )
   if (c is Animal)
      animals.add( new c() );

ดังนั้นฉันต้องการดูคลาสทั้งหมดในแอปพลิเคชันจักรวาลของฉันและเมื่อฉันพบสิ่งที่สืบทอดมาจาก Animal ฉันต้องการสร้างวัตถุใหม่ของประเภทนั้นและเพิ่มลงในรายการ สิ่งนี้ทำให้ฉันสามารถเพิ่มฟังก์ชันการทำงานโดยไม่ต้องอัปเดตรายการสิ่งต่าง ๆ ฉันสามารถหลีกเลี่ยงสิ่งต่อไปนี้:

List<Animal> animals = new ArrayList<Animal>();
animals.add( new Dog() );
animals.add( new Cat() );
animals.add( new Donkey() );
...

ด้วยวิธีการข้างต้นฉันสามารถสร้างคลาสใหม่ที่ขยายขอบเขตสัตว์และมันจะถูกเลือกโดยอัตโนมัติ

ปรับปรุง: 10/16/2008 9:00 น. เวลามาตรฐานแปซิฟิก:

คำถามนี้ได้สร้างคำตอบที่ดีมากขอบคุณ จากคำตอบและการวิจัยของฉันฉันพบว่าสิ่งที่ฉันต้องการทำจริงๆนั้นเป็นไปไม่ได้ภายใต้ Java มีวิธีการเช่นกลไก ServiceLoader ของ ddimitrov ที่สามารถทำงานได้ - แต่มันหนักมากสำหรับสิ่งที่ฉันต้องการและฉันเชื่อว่าฉันเพียงแค่ย้ายปัญหาจากรหัส Java ไปยังไฟล์การกำหนดค่าภายนอก อัปเดต 5/10/19 (11 ปีต่อมา!) ตอนนี้มีห้องสมุดหลายแห่งที่สามารถช่วยเหลือสิ่งนี้ได้ตามคำตอบ org.reflectionsของ @ IvanNik ที่ดูดี นอกจากนี้ยังClassGraphจาก @Luke ฮัทชิสันคำตอบที่ดูน่าสนใจ มีความเป็นไปได้อีกมากมายในคำตอบเช่นกัน

อีกวิธีหนึ่งในการระบุสิ่งที่ฉันต้องการ: ฟังก์ชั่นคงที่ในคลาส Animal ของฉันค้นหาและสร้างคลาสทั้งหมดที่สืบทอดจาก Animal - โดยไม่ต้องมีการกำหนดค่า / การเข้ารหัสเพิ่มเติมใด ๆ ถ้าฉันต้องกำหนดค่าฉันก็อาจยกตัวอย่างพวกมันในคลาสสัตว์ต่อไป ฉันเข้าใจว่าเนื่องจากโปรแกรม Java เป็นเพียงการรวมกลุ่มของไฟล์. class ที่หลวม

ที่น่าสนใจดูเหมือนว่านี่เป็นเรื่องเล็กน้อยใน C #


1
หลังจากค้นหาสักครู่ดูเหมือนว่านี่เป็นเรื่องยากที่จะแตกในจาวา นี่คือเธรดที่มีข้อมูลบางอย่าง: forums.sun.com/thread.jspa?threadID=341935&start=15 การติดตั้งใช้งานมากกว่าที่ฉันต้องการฉันคิดว่าฉันจะติดกับการใช้งานครั้งที่สองในตอนนี้
JohnnyLambada

ใส่เป็นคำตอบและให้ผู้อ่านลงคะแนนในนั้น
VonC

3
ลิงก์สำหรับทำสิ่งนี้ใน C # จะไม่แสดงสิ่งใดเป็นพิเศษ AC # Assembly เทียบเท่ากับไฟล์ Java JAR มันเป็นชุดของไฟล์คลาสที่รวบรวม (และทรัพยากร) ลิงก์แสดงวิธีนำคลาสออกจากชุดประกอบเดียว คุณสามารถทำได้ง่ายๆด้วย Java ปัญหาสำหรับคุณคือคุณต้องตรวจสอบไฟล์ JAR ทั้งหมดและไฟล์ Loose จริง (ไดเรกทอรี); คุณจะต้องทำเช่นเดียวกันกับ. NET (ค้นหา PATH บางชนิดหรือหลายชนิด)
Kevin Brock

คำตอบ:


156

ฉันใช้org.reflections :

Reflections reflections = new Reflections("com.mycompany");    
Set<Class<? extends MyInterface>> classes = reflections.getSubTypesOf(MyInterface.class);

ตัวอย่างอื่น:

public static void main(String[] args) throws IllegalAccessException, InstantiationException {
    Reflections reflections = new Reflections("java.util");
    Set<Class<? extends List>> classes = reflections.getSubTypesOf(java.util.List.class);
    for (Class<? extends List> aClass : classes) {
        System.out.println(aClass.getName());
        if(aClass == ArrayList.class) {
            List list = aClass.newInstance();
            list.add("test");
            System.out.println(list.getClass().getName() + ": " + list.size());
        }
    }
}

6
ขอบคุณสำหรับลิงค์เกี่ยวกับ org.reflections ฉันคิดว่านี่คงเป็นเรื่องง่ายเหมือนในโลก. Net และคำตอบนี้ช่วยฉันได้มาก
akmad

1
ขอบคุณสำหรับลิงก์ที่มีประโยชน์นี้ไปยังแพ็คเกจสุดยอด "org.reflections"! ในที่สุดฉันก็พบทางออกที่เป็นไปได้และเรียบร้อยสำหรับปัญหาของฉัน
Hartmut P.

3
มีวิธีในการรับการสะท้อนกลับทั้งหมดหรือไม่โดยไม่ได้สังเกตแพ็คเกจเฉพาะ แต่โหลดคลาสที่มีทั้งหมดหรือไม่
Joey Baruch

โปรดจำไว้ว่าการหาชั้นเรียนจะไม่แก้ปัญหาของคุณ instantiating พวกเขา (สาย 5 animals.add( new c() );ของรหัสของคุณ) เพราะในขณะที่Class.newInstance()มีอยู่คุณต้องรับประกันว่าเรียนเฉพาะมี constructor parameterless ไม่มี (C # ช่วยให้คุณสามารถกำหนดให้ในทั่วไป)
usr-local-ΕΨΗΕΛΩΝ

ดูเพิ่มเติม ClassGraph: github.com/classgraph/classgraph (ข้อจำกัดความรับผิดชอบฉันเป็นผู้เขียน) ฉันจะยกตัวอย่างรหัสในคำตอบแยกต่างหาก
ลุคฮัทชิสัน

34

วิธี Java ในการทำสิ่งที่คุณต้องการคือการใช้กลไกServiceLoader

ผู้คนจำนวนมากกลิ้งตัวเองโดยมีไฟล์ในตำแหน่ง classpath ที่รู้จักกันดี (เช่น /META-INF/services/myplugin.properties) จากนั้นใช้ClassLoader.getResources ()เพื่อระบุไฟล์ทั้งหมดที่มีชื่อนี้จากขวดทั้งหมด สิ่งนี้อนุญาตให้แต่ละ jar ส่งออกผู้ให้บริการของตนเองและคุณสามารถสร้างอินสแตนซ์ของพวกมันได้โดยใช้ Class.forName ()


1
สำหรับการสร้างแฟ้มบริการ META-INF ตามได้อย่างง่ายดายบนคำอธิบายประกอบในชั้นเรียนที่คุณสามารถตรวจสอบ: metainf-services.kohsuke.orgหรือcode.google.com/p/spi
elek

Bleah เพียงเพิ่มระดับความซับซ้อน แต่ไม่ลดความจำเป็นในการสร้างคลาสแล้วลงทะเบียนในพื้นที่ที่ห่างไกล
วินไคลน์

โปรดดูคำตอบด้านล่างด้วย 26 upvotes ดีกว่ามาก
SobiborTreblinka

4
แน่นอนถ้าคุณต้องการวิธีแก้ปัญหาการทำงานที่ชัดเจน Reflections / Scannotations เป็นตัวเลือกที่ดี แต่ทั้งคู่ก็ต้องพึ่งพาภายนอกไม่ใช่การกำหนดขึ้นและไม่ได้รับการสนับสนุนจาก Java Community Process นอกจากนี้ฉันต้องการเน้นว่าคุณไม่สามารถเชื่อถือได้ทุกคลาสที่ใช้อินเทอร์เฟซเนื่องจากมันเป็นเรื่องไม่สำคัญที่จะสร้างขึ้นมาใหม่จากอากาศบางตลอดเวลา สำหรับหูดทั้งหมดจาวาเซอร์วิสของ Java นั้นง่ายต่อการเข้าใจและใช้งาน ฉันจะอยู่ห่างจากมันด้วยเหตุผลที่แตกต่างกัน แต่การสแกนคลาสพา ธ นั้นแย่กว่านั้น: stackoverflow.com/a/7237152/18187
ddimitrov

11

คิดเกี่ยวกับสิ่งนี้จากมุมมองที่มุ่งเน้นในแง่มุม สิ่งที่คุณต้องการจะทำจริง ๆ ก็คือรู้ว่าทุกชั้นเรียนที่รันไทม์ที่มีการขยายชั้นเรียนสัตว์ (ฉันคิดว่านั่นเป็นคำอธิบายปัญหาของคุณที่แม่นยำกว่าชื่อของคุณเล็กน้อยมิฉะนั้นฉันไม่คิดว่าคุณจะมีคำถามแบบรันไทม์)

ดังนั้นสิ่งที่ฉันคิดว่าคุณต้องการคือการสร้างตัวสร้างคลาสพื้นฐานของคุณ (สัตว์) ซึ่งเพิ่มไปยังอาเรย์แบบคงที่ของคุณ (ฉันชอบ ArrayLists ตัวเอง แต่สำหรับแต่ละตัวเอง) ประเภทของคลาสปัจจุบันที่กำลังอินสแตนซ์

ดังนั้นประมาณ;

public abstract class Animal
    {
    private static ArrayList<Class> instantiatedDerivedTypes;
    public Animal() {
        Class derivedClass = this.getClass();
        if (!instantiatedDerivedClass.contains(derivedClass)) {
            instantiatedDerivedClass.Add(derivedClass);
        }
    }

แน่นอนคุณจะต้องมีคอนสตรัคเตอร์แบบคงที่ใน Animal เพื่อเริ่มต้น instantiateDerivedClass ... ฉันคิดว่านี่จะทำสิ่งที่คุณต้องการ โปรดทราบว่าสิ่งนี้ขึ้นอยู่กับเส้นทางการดำเนินการ หากคุณมีคลาสสุนัขที่มาจากสัตว์ที่ไม่เคยถูกเรียกคุณจะไม่ได้ในรายการ Animal Class


6

น่าเสียดายที่สิ่งนี้ไม่สามารถทำได้โดยสิ้นเชิงเนื่องจาก ClassLoader จะไม่บอกคุณว่ามีคลาสใดบ้าง อย่างไรก็ตามคุณสามารถทำสิ่งนี้ได้อย่างใกล้ชิด:

for (String classpathEntry : System.getProperty("java.class.path").split(System.getProperty("path.separator"))) {
    if (classpathEntry.endsWith(".jar")) {
        File jar = new File(classpathEntry);

        JarInputStream is = new JarInputStream(new FileInputStream(jar));

        JarEntry entry;
        while( (entry = is.getNextJarEntry()) != null) {
            if(entry.getName().endsWith(".class")) {
                // Class.forName(entry.getName()) and check
                //   for implementation of the interface
            }
        }
    }
}

แก้ไข: johnstok ถูกต้อง (ในความคิดเห็น) ซึ่งใช้งานได้กับแอปพลิเคชัน Java แบบสแตนด์อโลนเท่านั้นและจะไม่ทำงานภายใต้แอปพลิเคชันเซิร์ฟเวอร์


สิ่งนี้ใช้งานไม่ได้เมื่อโหลดคลาสด้วยวิธีอื่นตัวอย่างเช่นในเว็บหรือคอนเทนเนอร์ J2EE
johnstok

คุณอาจทำผลงานได้ดีขึ้นแม้ในคอนเทนเนอร์หากคุณสอบถามเส้นทาง (บ่อยครั้งURL[]แต่ไม่เสมอไปดังนั้นอาจไม่สามารถทำได้) จากลำดับชั้นของ ClassLoader บ่อยครั้งที่คุณต้องได้รับอนุญาตเพราะโดยปกติแล้วคุณจะต้องSecurityManagerโหลดภายใน JVM
Kevin Brock

ทำงานเหมือนเสน่ห์สำหรับฉัน! ฉันกลัวว่านี่จะมีน้ำหนักมากและช้าเกินไปจะผ่านทุกคลาสใน classpath แต่จริงๆแล้วฉัน จำกัด ไฟล์ที่ฉันตรวจสอบกับไฟล์ที่มี "-ejb-" หรือชื่อไฟล์ที่รู้จักอื่น ๆ และยังเร็วกว่าการเริ่มต้น 100 ครั้ง glassfish ที่ฝังตัวหรือ EJBContainer ในสถานการณ์เฉพาะของฉันฉันได้วิเคราะห์คลาสที่มองหาคำอธิบายประกอบ "ไร้สัญชาติ", "Stateful" และ "Singleton" และถ้าฉันเห็นพวกเขาฉันเพิ่มชื่อ JNDI Mapped ไปยัง MockInitialContextFactory ของฉัน ขอบคุณอีกครั้ง!
cs94njw

4

คุณสามารถใช้ResolverUtil ( แหล่งข้อมูลดิบ ) จากFrameworkes Stripes
หากคุณต้องการบางสิ่งที่ง่ายและรวดเร็วโดยไม่ต้องเปลี่ยนรหัสที่มีอยู่

นี่เป็นตัวอย่างง่ายๆที่ไม่ได้โหลดคลาสใด ๆ :

package test;

import java.util.Set;
import net.sourceforge.stripes.util.ResolverUtil;

public class BaseClassTest {
    public static void main(String[] args) throws Exception {
        ResolverUtil<Animal> resolver = new ResolverUtil<Animal>();
        resolver.findImplementations(Animal.class, "test");
        Set<Class<? extends Animal>> classes = resolver.getClasses();

        for (Class<? extends Animal> clazz : classes) {
            System.out.println(clazz);
        }
    }
}

class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
class Donkey extends Animal {}

สิ่งนี้ยังใช้งานได้ในแอปพลิเคชันเซิร์ฟเวอร์เช่นกันเนื่องจากเป็นที่ที่มันถูกออกแบบมาให้ทำงาน;)

รหัสโดยทั่วไปแล้วจะทำสิ่งต่อไปนี้:

  • วนซ้ำทรัพยากรทั้งหมดในแพ็คเกจที่คุณระบุ
  • เก็บเฉพาะทรัพยากรที่ลงท้ายด้วย. class
  • โหลดคลาสเหล่านั้นโดยใช้ ClassLoader#loadClass(String fullyQualifiedName)
  • ตรวจสอบว่า Animal.class.isAssignableFrom(loadedClass);

4

กลไกที่แข็งแกร่งที่สุดสำหรับการแสดงรายการคลาสย่อยทั้งหมดของคลาสที่กำหนดอยู่ในขณะนี้ ClassGraphเนื่องจากมันจัดการกับอาร์เรย์ของข้อมูลจำเพาะคลาสพา ธ ที่กว้างที่สุดเท่าที่จะเป็นไปได้รวมถึงระบบโมดูล JPMS ใหม่ (ฉันเป็นผู้เขียน)

List<Class<Animal>> animals;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("com.zoo.animals")
        .enableClassInfo().scan()) {
    animals = scanResult
        .getSubclasses(Animal.class.getName())
        .loadClasses(Animal.class);
}

ผลลัพธ์เป็นชนิดรายการ <Class <Animal>>
hiaclibe

2

Java โหลดคลาสแบบไดนามิกดังนั้นจักรวาลของคลาสของคุณจะเป็นเฉพาะคลาสที่โหลดแล้ว (และยังไม่ได้ยกเลิกการโหลด) บางทีคุณสามารถทำอะไรกับตัวโหลดคลาสแบบกำหนดเองที่สามารถตรวจสอบชนิดของแต่ละคลาสที่โหลด ฉันไม่คิดว่าจะมี API เพื่อสอบถามชุดคลาสที่โหลด


2

ใช้สิ่งนี้

public static Set<Class> getExtendedClasses(Class superClass)
{
    try
    {
        ResolverUtil resolver = new ResolverUtil();
        resolver.findImplementations(superClass, superClass.getPackage().getName());
        return resolver.getClasses();  
    }
    catch(Exception e)
    {Log.d("Log:", " Err: getExtendedClasses() ");}

    return null;
}

getExtendedClasses(Animals.class);

แก้ไข:

  • ไลบรารี่สำหรับ (ResolverUtil): Stripes

2
คุณต้องพูดเพิ่มเติมเกี่ยวกับ ResolverUtil อีกเล็กน้อย มันคืออะไร? มันมาจากห้องสมุดอะไร
JohnnyLambada

1

ขอบคุณทุกคนที่ตอบคำถามนี้

ดูเหมือนว่านี่จะเป็นถั่วที่แข็งแกร่งที่จะร้าว ฉันสิ้นสุดการยอมแพ้และสร้างอาร์เรย์แบบสแตติกและทะเยอทะยานใน baseclass ของฉัน

public abstract class Animal{
    private static Animal[] animals= null;
    public static Animal[] getAnimals(){
        if (animals==null){
            animals = new Animal[]{
                new Dog(),
                new Cat(),
                new Lion()
            };
        }
        return animals;
    }
}

ดูเหมือนว่า Java ไม่ได้ตั้งค่าสำหรับการค้นพบตัวเองในแบบที่ C # เป็น ฉันคิดว่าปัญหาคือเนื่องจากแอพ Java เป็นเพียงชุดของไฟล์. class ในไฟล์ directory / jar ที่ไหนสักแห่งรันไทม์ไม่ทราบเกี่ยวกับชั้นเรียนจนกว่าจะมีการอ้างอิง ในเวลานั้นโหลดโหลด - สิ่งที่ฉันพยายามทำคือค้นพบก่อนที่ฉันจะอ้างอิงซึ่งไม่สามารถทำได้โดยไม่ต้องออกไปที่ระบบไฟล์และค้นหา

ฉันมักจะชอบโค้ดที่สามารถค้นพบตัวเองแทนที่จะบอกฉันเกี่ยวกับตัวเอง แต่ก็ใช้งานได้เช่นกัน

ขอบคุณอีกครั้ง!


1
Java ถูกตั้งค่าสำหรับการค้นพบตัวเอง คุณแค่ต้องทำงานเพิ่มอีกนิด ดูคำตอบของฉันสำหรับรายละเอียด
Dave Jarvis

1

การใช้OpenPojoคุณสามารถทำสิ่งต่อไปนี้:

String package = "com.mycompany";
List<Animal> animals = new ArrayList<Animal>();

for(PojoClass pojoClass : PojoClassFactory.enumerateClassesByExtendingType(package, Animal.class, null) {
  animals.add((Animal) InstanceFactory.getInstance(pojoClass));
}

0

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


0

วิธีหนึ่งคือการทำให้คลาสใช้ initializers คงที่ ... ฉันไม่คิดว่าสิ่งเหล่านี้จะสืบทอดมา (มันจะไม่ทำงานหากเป็น):

public class Dog extends Animal{

static
{
   Animal a = new Dog();
   //add a to the List
}

มันต้องการให้คุณเพิ่มรหัสนี้ลงในคลาสที่เกี่ยวข้องทั้งหมด แต่มันก็เป็นการหลีกเลี่ยงการวนรอบที่น่าเกลียดมาก ๆ ทดสอบกับทุกชั้นเพื่อค้นหาเด็กสัตว์


3
กรณีนี้ใช้ไม่ได้ เนื่องจากคลาสไม่ถูกอ้างอิงที่ใดก็ไม่เคยถูกโหลดดังนั้น initializer แบบสแตติกจะไม่ถูกเรียก ดูเหมือนว่าเป็น initializer คงที่ถูกเรียกก่อนทุกอย่างอื่นเมื่อชั้นมีการโหลด - แต่ในกรณีของฉันชั้นไม่เคยโหลด
JohnnyLambada

0

ฉันแก้ไขปัญหานี้ได้อย่างสวยงามโดยใช้คำอธิบายประกอบระดับแพคเกจแล้วทำให้คำอธิบายประกอบนั้นเป็นอาร์กิวเมนต์ของรายการคลาส

ค้นหาคลาส Java ที่ใช้อินเตอร์เฟส

การติดตั้งใช้งานเพียงแค่สร้าง package-info.java และใส่หมายเหตุประกอบแบบเวทมนต์ลงในรายการคลาสที่ต้องการสนับสนุน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.