Trong thời gian chạy, tìm tất cả các lớp trong một ứng dụng Java mở rộng một lớp cơ sở


147

Tôi muốn làm một cái gì đó như thế này:

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() );

Vì vậy, tôi muốn xem xét tất cả các lớp trong vũ trụ của ứng dụng của mình và khi tôi tìm thấy một lớp xuất phát từ Animal, tôi muốn tạo một đối tượng mới thuộc loại đó và thêm nó vào danh sách. Điều này cho phép tôi thêm chức năng mà không phải cập nhật danh sách các thứ. Tôi có thể tránh những điều sau đây:

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

Với cách tiếp cận trên, tôi có thể chỉ cần tạo một lớp mới mở rộng Animal và nó sẽ tự động được chọn.

CẬP NHẬT: 16/10/2008 9:00 sáng Giờ chuẩn Thái Bình Dương:

Câu hỏi này đã tạo ra rất nhiều câu trả lời tuyệt vời - cảm ơn bạn. Từ các phản hồi và nghiên cứu của tôi, tôi đã thấy rằng những gì tôi thực sự muốn làm là không thể thực hiện được trong Java. Có nhiều cách tiếp cận, chẳng hạn như cơ chế ServiceLoader của ddimitrov có thể hoạt động - nhưng chúng rất nặng đối với những gì tôi muốn và tôi tin rằng tôi chỉ đơn giản chuyển vấn đề từ mã Java sang tệp cấu hình bên ngoài. Cập nhật 5/10/19 (11 năm sau!) Hiện tại có một số thư viện có thể trợ giúp việc này theo câu trả lời org.reflections của @ IvanNik có vẻ tốt. Ngoài ra ClassGraph từ câu trả lời của @Luke Hutchison có vẻ thú vị. Có nhiều khả năng hơn trong các câu trả lời là tốt.

Một cách khác để nói lên những gì tôi muốn: một hàm tĩnh trong lớp Animal của tôi tìm và khởi tạo tất cả các lớp kế thừa từ Animal - mà không cần thêm bất kỳ cấu hình / mã hóa nào. Nếu tôi phải cấu hình, tôi cũng có thể khởi tạo chúng trong lớp Animal. Tôi hiểu điều đó bởi vì một chương trình Java chỉ là một liên kết lỏng lẻo của các tệp. Class, chính là như vậy.

Thật thú vị, có vẻ như điều này khá tầm thường trong C #.


1
Sau khi tìm kiếm một lúc, có vẻ như đây là một hạt khó bẻ khóa trong Java. Đây là một chủ đề có một số thông tin: forum.sun.com/thread.jspa?threadID=341935&start=15 Việc triển khai trong đó là nhiều hơn tôi cần, tôi đoán bây giờ tôi sẽ chỉ sử dụng triển khai thứ hai.
JohnnyLambada

đặt câu hỏi đó làm câu trả lời và để độc giả bình chọn
VonC

3
Liên kết để làm điều này trong C # không hiển thị gì đặc biệt. AC # hội tương đương với tệp Java JAR. Nó là một tập hợp các tệp lớp được biên dịch (và tài nguyên). Liên kết cho thấy làm thế nào để đưa các lớp ra khỏi một hội đồng duy nhất. Bạn có thể làm điều đó gần như dễ dàng với Java. Vấn đề cho bạn là bạn cần xem qua tất cả các tệp JAR và các tệp (thư mục) thực sự lỏng lẻo; bạn sẽ cần phải làm tương tự với .NET (tìm kiếm một số loại hoặc nhiều loại khác nhau).
Kevin Brock

Câu trả lời:


156

Tôi sử dụng org.reflections :

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

Một vi dụ khac:

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
Cảm ơn các liên kết về org.reflections. Tôi đã nhầm tưởng rằng điều này sẽ dễ dàng như trong thế giới .Net và câu trả lời này đã giúp tôi tiết kiệm rất nhiều thời gian.
akmad

1
Cảm ơn bạn thực sự cho liên kết hữu ích này với gói "org.reflections" tuyệt vời! Với điều đó cuối cùng tôi đã tìm thấy một giải pháp thực tế và gọn gàng cho vấn đề của mình.
Hartmut P.

3
Có cách nào để có được tất cả các phản xạ, tức là, không cần lưu ý một gói cụ thể, nhưng tải tất cả các lớp có sẵn?
Joey Baruch

Hãy nhớ rằng việc tìm các lớp sẽ không giải quyết được vấn đề khởi tạo chúng (dòng 5 animals.add( new c() );mã của bạn), bởi vì trong khi Class.newInstance()tồn tại, bạn không đảm bảo rằng lớp cụ thể có hàm tạo không tham số (C # cho phép bạn yêu cầu điều đó chung chung)
usr-local-

Xem thêm ClassGraph: github.com/ classgraph / classgraph (Tuyên bố miễn trừ trách nhiệm, tôi là tác giả). Tôi sẽ đưa ra một ví dụ mã trong một câu trả lời riêng biệt.
Luke Hutchison

34

Cách Java để làm những gì bạn muốn là sử dụng cơ chế ServiceLoader .

Ngoài ra, nhiều người tự cuộn mình bằng cách có một tệp ở một vị trí đường dẫn nổi tiếng (ví dụ /META-INF/service/myplugin.properies) và sau đó sử dụng ClassLoader.getResource () để liệt kê tất cả các tệp có tên này từ tất cả các tệp. Điều này cho phép mỗi jar xuất các nhà cung cấp riêng của mình và bạn có thể khởi tạo chúng bằng cách phản chiếu bằng Class.forName ()


1
Để tạo tệp dịch vụ META-INF dễ dàng dựa trên các chú thích trên các lớp, bạn có thể kiểm tra: metainf-service.kohsuke.org hoặc code.google.com/p/spi
elek

Bleah. Chỉ cần thêm một mức độ phức tạp, nhưng không loại bỏ sự cần thiết phải tạo một lớp và sau đó đăng ký nó ở một vùng đất xa xôi.
kevin cline

Vui lòng xem câu trả lời dưới đây với 26 lượt
upvote

4
Thật vậy, nếu bạn cần một loại giải pháp hoạt động, Reflection / Scannotations là một lựa chọn tốt, nhưng cả hai đều áp đặt một phụ thuộc bên ngoài, không mang tính quyết định và không được Quy trình cộng đồng Java hỗ trợ. Ngoài ra, tôi muốn nhấn mạnh rằng bạn không bao giờ có thể nhận được TẤT CẢ các lớp thực hiện giao diện một cách đáng tin cậy, vì việc sản xuất một giao diện mới ra khỏi không khí bất cứ lúc nào là không đáng kể. Đối với tất cả các mụn cóc của nó, trình tải dịch vụ của Java rất dễ hiểu và dễ sử dụng. Tôi sẽ tránh xa nó vì những lý do khác nhau, nhưng sau đó quét đường dẫn thậm chí còn tệ hơn: stackoverflow.com/a/7237152/18187
ddimitrov

11

Hãy suy nghĩ về điều này từ quan điểm định hướng theo khía cạnh; những gì bạn muốn làm, thực sự, là biết tất cả các lớp trong thời gian chạy đã mở rộng lớp Animal. (Tôi nghĩ đó là một mô tả chính xác hơn một chút về vấn đề của bạn so với tiêu đề của bạn; nếu không, tôi không nghĩ bạn có câu hỏi về thời gian chạy.)

Vì vậy, điều tôi nghĩ rằng bạn muốn là tạo một hàm tạo của lớp cơ sở (Animal) để thêm vào mảng tĩnh của bạn (tôi thích ArrayLists, bản thân tôi, nhưng với mỗi lớp của chúng) loại Lớp hiện tại đang được khởi tạo.

Vì vậy, đại khái;

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

Tất nhiên, bạn sẽ cần một hàm tạo tĩnh trên Animal để khởi tạo tức thờiDeriveClass ... Tôi nghĩ rằng điều này sẽ làm những gì bạn có thể muốn. Lưu ý rằng đây là phụ thuộc đường dẫn thực hiện; nếu bạn có một lớp Dog xuất phát từ Animal mà không bao giờ được gọi, bạn sẽ không có nó trong danh sách Lớp Thú của bạn.


6

Thật không may, điều này không hoàn toàn khả thi vì ClassLoader sẽ không cho bạn biết những lớp nào có sẵn. Tuy nhiên, bạn có thể khá gần gũi khi làm một cái gì đó như thế này:

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
            }
        }
    }
}

Chỉnh sửa: johnstok là chính xác (trong các bình luận) rằng điều này chỉ hoạt động cho các ứng dụng Java độc lập và sẽ không hoạt động trong một máy chủ ứng dụng.


Điều này không hoạt động tốt khi các lớp được tải bằng các phương tiện khác, Ví dụ, trong một thùng chứa web hoặc J2EE.
johnstok

Bạn có thể có thể thực hiện công việc tốt hơn, ngay cả trong một thùng chứa, nếu bạn truy vấn các đường dẫn (thường URL[]nhưng không phải lúc nào cũng có thể không thực hiện được) từ hệ thống phân cấp ClassLoader. Thông thường mặc dù bạn phải có quyền vì thông thường bạn sẽ có một SecurityManagertải trong JVM.
Kevin Brock

Làm việc như một cơ duyên cho tôi! Tôi sợ rằng nó sẽ quá nặng và chậm, đi qua tất cả các lớp trong đường dẫn lớp, nhưng thực sự tôi đã giới hạn các tệp tôi đã kiểm tra ở những tệp có chứa "-ejb-" hoặc các tên tệp dễ nhận biết khác và nó vẫn nhanh hơn 100 lần so với khởi động một con cá thủy tinh nhúng hoặc EJBContainer. Trong tình huống cụ thể của tôi, sau đó tôi đã phân tích các lớp tìm kiếm các chú thích "Không trạng thái", "Trạng thái" và "Singleton" và nếu tôi thấy chúng, tôi đã thêm tên JNDI Mapped vào MockInitialContextFactory của tôi. Cảm ơn một lần nữa!
cs94njw

4

Bạn có thể sử dụng ResolverUtil ( nguồn thô ) từ Khung sọc
nếu bạn cần một cái gì đó đơn giản và nhanh chóng mà không cần cấu trúc lại bất kỳ mã hiện có nào.

Đây là một ví dụ đơn giản không tải bất kỳ lớp nào:

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 {}

Điều này cũng hoạt động trong một máy chủ ứng dụng vì đó là nơi nó được thiết kế để hoạt động;)

Mã về cơ bản thực hiện như sau:

  • Lặp lại tất cả các tài nguyên trong (các) gói bạn chỉ định
  • chỉ giữ lại các tài nguyên kết thúc bằng. class
  • Tải các lớp đó bằng cách sử dụng ClassLoader#loadClass(String fullyQualifiedName)
  • Kiểm tra nếu Animal.class.isAssignableFrom(loadedClass);

4

Cơ chế mạnh nhất để liệt kê tất cả các lớp con của một lớp nhất định hiện tại là ClassGraph , vì nó xử lý mảng rộng nhất có thể của các cơ chế đặc tả đường dẫn lớp , bao gồm cả hệ thống mô-đun JPMS mới. (Tôi là tác giả.)

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

Kết quả là loại Danh sách <Lớp <Động vật >>
hiaclibe

2

Java tự động tải các lớp, vì vậy vũ trụ của các lớp sẽ chỉ là các lớp đã được tải (và chưa được tải). Có lẽ bạn có thể làm một cái gì đó với một trình nạp lớp tùy chỉnh có thể kiểm tra các siêu kiểu của mỗi lớp được tải. Tôi không nghĩ rằng có một API để truy vấn tập hợp các lớp được tải.


2

dùng cái này

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);

Biên tập:

  • thư viện cho (ResolverUtil): Sọc

2
Bạn sẽ cần nói thêm một chút về ResolverUtil. Nó là gì? Nó đến từ thư viện nào?
JohnnyLambada

1

Cảm ơn tất cả những người đã trả lời câu hỏi này.

Có vẻ như đây thực sự là một hạt cứng để crack. Tôi đã kết thúc việc từ bỏ và tạo ra một mảng tĩnh và getter trong lớp cơ sở của tôi.

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

Có vẻ như Java không được thiết lập để tự khám phá theo cách của C #. Tôi cho rằng vấn đề là vì một ứng dụng Java chỉ là một tập hợp các tệp. Class trong một thư mục / tệp jar ở đâu đó, nên thời gian chạy không biết về một lớp cho đến khi nó được tham chiếu. Vào thời điểm đó, trình tải tải nó - điều tôi đang cố gắng khám phá nó trước khi tôi tham chiếu nó, điều này là không thể nếu không đi ra hệ thống tập tin và tìm kiếm.

Tôi luôn thích mã có thể tự khám phá thay vì tôi phải nói về nó, nhưng than ôi điều này cũng hoạt động.

Cảm ơn một lần nữa!


1
Java được thiết lập để tự khám phá. Bạn chỉ cần làm thêm một chút công việc. Xem câu trả lời của tôi để biết chi tiết.
Dave Jarvis

1

Sử dụng OpenPojo bạn có thể làm như sau:

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

Đây là một vấn đề khó khăn và bạn sẽ cần tìm hiểu thông tin này bằng phân tích tĩnh, nó không có sẵn dễ dàng khi chạy. Về cơ bản có được đường dẫn lớp của ứng dụng của bạn và quét qua các lớp có sẵn và đọc thông tin mã byte của một lớp mà lớp mà nó kế thừa. Lưu ý rằng một con chó lớp có thể không được thừa kế trực tiếp từ Động vật nhưng có thể thừa hưởng từ Pet, lần lượt được thừa hưởng từ Động vật, vì vậy bạn sẽ cần theo dõi thứ bậc đó.


0

Một cách là làm cho các lớp sử dụng một bộ khởi tạo tĩnh ... Tôi không nghĩ chúng được kế thừa (nó sẽ không hoạt động nếu có):

public class Dog extends Animal{

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

Nó yêu cầu bạn thêm mã này vào tất cả các lớp liên quan. Nhưng nó tránh được một vòng lặp xấu xí lớn ở đâu đó, kiểm tra mọi lớp học tìm kiếm trẻ em của Animal.


3
Điều này không hoạt động trong trường hợp của tôi. Vì lớp không được tham chiếu ở bất cứ đâu nên nó không bao giờ được tải nên trình khởi tạo tĩnh không bao giờ được gọi. Có vẻ như một trình khởi tạo tĩnh được gọi trước mọi thứ khác khi lớp được tải - nhưng trong trường hợp của tôi, lớp không bao giờ được tải.
JohnnyLambada

0

Tôi đã giải quyết vấn đề này một cách khá thanh lịch bằng cách sử dụng Chú thích cấp độ gói và sau đó làm cho chú thích đó có một danh sách các lớp.

Tìm các lớp Java thực hiện một giao diện

Việc triển khai chỉ cần tạo một gói-info.java và đặt chú thích ma thuật vào danh sách các lớp mà chúng muốn hỗ trợ.

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.