Làm thế nào để bạn tìm thấy tất cả các lớp con của một lớp nhất định trong Java?


207

Làm thế nào để một người đi và cố gắng tìm tất cả các lớp con của một lớp nhất định (hoặc tất cả những người triển khai một giao diện đã cho) trong Java? Đến bây giờ, tôi có một phương pháp để làm điều này, nhưng tôi thấy nó khá kém hiệu quả (phải nói là ít nhất). Phương pháp là:

  1. Lấy danh sách tất cả các tên lớp tồn tại trên đường dẫn lớp
  2. Tải từng lớp và kiểm tra xem nó là lớp con hay người triển khai của lớp hoặc giao diện mong muốn

Trong Eclipse, có một tính năng hay được gọi là Phân cấp kiểu quản lý để hiển thị điều này khá hiệu quả. Làm thế nào để một người đi về và làm nó lập trình?


1
Mặc dù giải pháp dựa trên Reflection và Spring có vẻ thú vị, tôi cần một số giải pháp đơn giản không có sự phụ thuộc. Có vẻ như mã gốc của tôi (với một số điều chỉnh) là cách để đi.
Avrom

1
Chắc chắn bạn có thể sử dụng phương thức getSupeClass một cách đệ quy?

Tôi đặc biệt tìm kiếm tất cả các lớp con của một lớp nhất định. getSuperClass sẽ không cho bạn biết một lớp con nào có, chỉ nhận siêu lớp ngay lập tức cho một lớp con cụ thể. Ngoài ra, phương thức isAssignableFrom trên Class phù hợp hơn với những gì bạn đề xuất (không cần đệ quy).
Avrom

Câu hỏi này được liên kết từ nhiều bản sao khác, nhưng nó không chứa bất kỳ câu trả lời Java đơn giản, hữu ích nào. Thở dài ...
Eric Duminil

Câu trả lời:


77

Không có cách nào khác để làm điều đó ngoài những gì bạn mô tả. Hãy suy nghĩ về nó - làm thế nào mọi người có thể biết những lớp nào mở rộng ClassX mà không quét từng lớp trên đường dẫn lớp?

Eclipse chỉ có thể cho bạn biết về siêu và các lớp con trong khoảng thời gian dường như là "hiệu quả" vì nó đã có tất cả dữ liệu loại được tải tại điểm mà bạn nhấn nút "Hiển thị theo kiểu phân cấp" (vì nó là liên tục biên dịch các lớp của bạn, biết về mọi thứ trên đường dẫn, v.v.).


23
Hiện tại có một thư viện đơn giản gọi là org.reflections giúp thực hiện điều này và các tác vụ phản ánh phổ biến khác. Với thư viện này, bạn chỉ cần gọi reflections.getSubTypesOf(aClazz)) liên kết
Đã đăng ký vào

@matt b - nếu nó phải quét tất cả các lớp, điều này có nghĩa là có sự suy giảm hiệu năng khi bạn có nhiều lớp trong dự án của mình, mặc dù chỉ có một vài trong số chúng là lớp phụ của bạn?
LeTex

Chính xác. Nó chỉ chạm vào tất cả các lớp. Bạn có thể định nghĩa máy quét của riêng mình, bạn có thể tăng tốc nó như loại trừ các gói nhất định mà bạn biết lớp của bạn sẽ không được mở rộng hoặc chỉ mở tệp lớp và kiểm tra tìm tên lớp trong phần không đổi của lớp để tránh cho trình quét phản chiếu đọc nhiều thông tin hơn về các lớp thậm chí không chứa tham chiếu bắt buộc đối với siêu lớp (trực tiếp) của bạn Một cách gián tiếp bạn cần quét thêm. Vì vậy, tốt nhất là có tại thời điểm này.
Martin Kersten

Câu trả lời của fforw đã làm việc cho tôi và nên được đánh dấu là câu trả lời đúng. Rõ ràng điều này là có thể với quét đường dẫn.
Farrukh Najmi

Bạn đã sai, hãy xem phản hồi bên dưới về thư viện

127

Quét các lớp không dễ dàng với Java thuần túy.

Khung công tác mùa xuân cung cấp một lớp có tên ClassPathScanningCandidateComponentProvider có thể làm những gì bạn cần. Ví dụ sau sẽ tìm thấy tất cả các lớp con của MyClass trong gói org.example.package

ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
provider.addIncludeFilter(new AssignableTypeFilter(MyClass.class));

// scan in org.example.package
Set<BeanDefinition> components = provider.findCandidateComponents("org/example/package");
for (BeanDefinition component : components)
{
    Class cls = Class.forName(component.getBeanClassName());
    // use class cls found
}

Phương pháp này có lợi ích bổ sung là sử dụng bộ phân tích mã byte để tìm các ứng cử viên có nghĩa là nó sẽ không tải tất cả các lớp mà nó quét.


23
Sai phải được truyền vào dưới dạng tham số khi tạo ClassPathScanningCandidateComponentProvider để tắt các bộ lọc mặc định. Các bộ lọc mặc định sẽ khớp với các loại lớp khác, ví dụ: mọi thứ được chú thích bằng @Component. Chúng tôi chỉ muốn AssignableTypeFilter hoạt động ở đây.
MCDS

Bạn nói nó không dễ, nhưng chúng ta sẽ làm như thế nào nếu chúng ta muốn làm điều đó với java thuần túy?
Aequitas

49

Điều này không thể thực hiện được khi chỉ sử dụng API phản chiếu Java tích hợp.

Một dự án tồn tại có chức năng quét và lập chỉ mục cần thiết cho đường dẫn lớp của bạn để bạn có thể truy cập thông tin này ...

Những phản ánh

Một phân tích siêu dữ liệu thời gian chạy Java, theo tinh thần của Scannotations

Reflection quét đường dẫn lớp của bạn, lập chỉ mục siêu dữ liệu, cho phép bạn truy vấn nó trong thời gian chạy và có thể lưu và thu thập thông tin đó cho nhiều mô-đun trong dự án của bạn.

Sử dụng Reflection bạn có thể truy vấn siêu dữ liệu của mình cho:

  • nhận được tất cả các kiểu con của một số loại
  • nhận được tất cả các loại chú thích với một số chú thích
  • nhận tất cả các loại chú thích với một số chú thích, bao gồm cả các tham số chú thích phù hợp
  • nhận được tất cả các phương thức chú thích với một số

(từ chối trách nhiệm: Tôi chưa sử dụng nó, nhưng mô tả của dự án dường như phù hợp chính xác với nhu cầu của bạn.)


1
Hấp dẫn. Dự án dường như có một số phụ thuộc mà tài liệu của họ dường như không đề cập đến. Cụ thể (những cái tôi tìm thấy cho đến nay): javaassist, log4J, XStream
Avrom

3
Tôi đã bao gồm projekt này với maven và nó hoạt động tốt. Bắt các lớp con thực sự là ví dụ mã nguồn đầu tiên và dài hai dòng :-)
KarlsFriend

Có phải là không thể chỉ sử dụng API phản chiếu Java tích hợp hoặc chỉ rất bất tiện khi làm như vậy?
Lưu lượng

1
Hãy cẩn thận khi bạn sẽ sử dụng Reflection và sau đó triển khai ứng dụng WAR của bạn lên GlassFish! Có một xung đột trong thư viện Guava và việc triển khai sẽ thất bại với lỗi triển khai CDI không thành công: WELD-001408 - vui lòng xem GLASSFISH-20579 để biết thêm chi tiết. FastClasspathScanner là một giải pháp trong trường hợp này.
lu_ko

Tôi chỉ thử dự án này và nó hoạt động. Tôi chỉ sử dụng nó để nâng cao mẫu thiết kế chiến lược và nhận được tất cả các lớp cụ thể chiến lược (lớp con) tôi sẽ chia sẻ bản demo sau.
Xin Mạnh

10

Đừng quên rằng Javadoc được tạo cho một lớp sẽ bao gồm một danh sách các lớp con đã biết (và cho các giao diện, các lớp triển khai đã biết).


3
Điều này là hoàn toàn không chính xác, siêu lớp không nên phụ thuộc vào các lớp con của chúng, thậm chí không có trong javadoc hoặc bình luận.
thợ săn

@hunter tôi không đồng ý. Điều hoàn toàn chính xác là JavaDoc bao gồm một danh sách các lớp con đã biết . Tất nhiên, "được biết" có thể không bao gồm lớp bạn đang tìm kiếm, nhưng đối với một số trường hợp sử dụng, nó sẽ đủ.
Qw3ry

Và bạn có thể bỏ lỡ một số lớp trong mọi trường hợp: Tôi có thể tải một jar mới vào đường dẫn lớp (trong thời gian chạy) và mọi phát hiện xảy ra trước đó sẽ thất bại.
Qw3ry

10

Hãy thử ClassGraph . (Tuyên bố miễn trừ trách nhiệm, tôi là tác giả). ClassGraph hỗ trợ quét các lớp con của một lớp nhất định, trong thời gian chạy hoặc tại thời gian xây dựng, nhưng cũng nhiều hơn nữa. ClassGraph có thể xây dựng một biểu diễn trừu tượng của toàn bộ biểu đồ lớp (tất cả các lớp, chú thích, phương thức, tham số phương thức và trường) trong bộ nhớ, cho tất cả các lớp trên đường dẫn lớp hoặc cho các lớp trong các gói trong danh sách trắng, tuy nhiên bạn có thể truy vấn biểu đồ lớp đó bạn muốn. ClassGraph hỗ trợ nhiều cơ chế đặc tả và trình tải lớp hơn bất kỳ trình quét nào khác và cũng hoạt động trơn tru với hệ thống mô-đun JPMS mới, vì vậy nếu bạn dựa vào mã của mình trên ClassGraph, mã của bạn sẽ có khả năng di động tối đa. Xem API tại đây.


9

Tôi đã làm điều này vài năm trước. Cách đáng tin cậy nhất để làm điều này (tức là với các API Java chính thức và không có phụ thuộc bên ngoài) là viết một doclet tùy chỉnh để tạo ra một danh sách có thể đọc được khi chạy.

Bạn có thể chạy nó từ dòng lệnh như thế này:

javadoc -d build -doclet com.example.ObjectListDoclet -sourcepath java/src -subpackages com.example

hoặc chạy nó từ con kiến ​​như thế này:

<javadoc sourcepath="${src}" packagenames="*" >
  <doclet name="com.example.ObjectListDoclet" path="${build}"/>
</javadoc>

Đây là mã cơ bản:

public final class ObjectListDoclet {
    public static final String TOP_CLASS_NAME =  "com.example.MyClass";        

    /** Doclet entry point. */
    public static boolean start(RootDoc root) throws Exception {
        try {
            ClassDoc topClassDoc = root.classNamed(TOP_CLASS_NAME);
            for (ClassDoc classDoc : root.classes()) {
                if (classDoc.subclassOf(topClassDoc)) {
                    System.out.println(classDoc);
                }
            }
            return true;
        }
        catch (Exception ex) {
            ex.printStackTrace();
            return false;
        }
    }
}

Để đơn giản, tôi đã xóa phân tích đối số dòng lệnh và tôi đang viết vào System.out chứ không phải là một tệp.


Trong khi sử dụng chương trình này có thể là khó khăn, tôi phải nói - một cách tiếp cận thông minh !
Janaka Bandara

8

Tôi biết tôi đến bữa tiệc muộn vài năm, nhưng tôi đã gặp câu hỏi này khi cố gắng giải quyết vấn đề tương tự. Bạn có thể sử dụng tìm kiếm nội bộ của Eclipse theo chương trình, nếu bạn đang viết một Trình cắm Eclipse (và do đó tận dụng bộ nhớ đệm của họ, v.v.), để tìm các lớp thực hiện giao diện. Đây là lần cắt đầu tiên (rất thô) của tôi:

  protected void listImplementingClasses( String iface ) throws CoreException
  {
    final IJavaProject project = <get your project here>;
    try
    {
      final IType ifaceType = project.findType( iface );
      final SearchPattern ifacePattern = SearchPattern.createPattern( ifaceType, IJavaSearchConstants.IMPLEMENTORS );
      final IJavaSearchScope scope = SearchEngine.createWorkspaceScope();
      final SearchEngine searchEngine = new SearchEngine();
      final LinkedList<SearchMatch> results = new LinkedList<SearchMatch>();
      searchEngine.search( ifacePattern, 
      new SearchParticipant[]{ SearchEngine.getDefaultSearchParticipant() }, scope, new SearchRequestor() {

        @Override
        public void acceptSearchMatch( SearchMatch match ) throws CoreException
        {
          results.add( match );
        }

      }, new IProgressMonitor() {

        @Override
        public void beginTask( String name, int totalWork )
        {
        }

        @Override
        public void done()
        {
          System.out.println( results );
        }

        @Override
        public void internalWorked( double work )
        {
        }

        @Override
        public boolean isCanceled()
        {
          return false;
        }

        @Override
        public void setCanceled( boolean value )
        {
        }

        @Override
        public void setTaskName( String name )
        {
        }

        @Override
        public void subTask( String name )
        {
        }

        @Override
        public void worked( int work )
        {
        }

      });

    } catch( JavaModelException e )
    {
      e.printStackTrace();
    }
  }

Vấn đề đầu tiên tôi thấy cho đến nay là tôi chỉ bắt các lớp trực tiếp thực hiện giao diện, không phải tất cả các lớp con của chúng - nhưng một đệ quy nhỏ không bao giờ làm tổn thương bất cứ ai.


3
HOẶC, hóa ra bạn không phải thực hiện tìm kiếm của riêng bạn. Bạn chỉ có thể nhận được một ITypeHVELy ​​trực tiếp từ IType của mình bằng cách gọi .newTypeHVELy ​​() trên đó: dev.eclipse.org/newslists/news.eclipse.tools.jdt/msg05036.html
Curtis

7

Hãy ghi nhớ những hạn chế được đề cập trong các câu trả lời khác, bạn cũng có thể sử dụng openpojoPojoClassFactory ( có sẵn trên Maven ) theo cách sau:

for(PojoClass pojoClass : PojoClassFactory.enumerateClassesByExtendingType(packageRoot, Superclass.class, null)) {
    System.out.println(pojoClass.getClazz());
}

Trong trường hợp packageRootlà chuỗi gốc của các gói mà bạn muốn tìm kiếm trong (ví dụ "com.mycompany"hoặc thậm chí chỉ "com"), và Superclasslà siêu kiểu của bạn (công trình này trên giao diện cũng).


Cho đến nay, giải pháp nhanh nhất và thanh lịch nhất từ ​​những người được đề xuất.
KidCrippler

4

Tôi chỉ viết một bản demo đơn giản để sử dụng org.reflections.Reflectionsđể có được các lớp con của lớp trừu tượng:

https://github.com/xmeng1/ReflectionsDemo


Sẽ hiệu quả hơn nếu bạn đăng một mẫu mã ở đây thay vì liên kết với rất nhiều lớp để đào.
JesseBoyd

4

Tùy thuộc vào các yêu cầu cụ thể của bạn, trong một số trường hợp, cơ chế trình tải dịch vụ của Java có thể đạt được những gì bạn đang theo đuổi.

Nói tóm lại, nó cho phép các nhà phát triển tuyên bố rõ ràng rằng một lớp con phân lớp một số lớp khác (hoặc thực hiện một số giao diện) bằng cách liệt kê nó trong một tệp trong thư mục của tệp JAR / WAR META-INF/services. Sau đó, nó có thể được phát hiện bằng cách sử dụng java.util.ServiceLoaderlớp, khi được cung cấp một Classđối tượng, sẽ tạo ra các thể hiện của tất cả các lớp con được khai báo của lớp đó (hoặc, nếu Classđại diện cho một giao diện, tất cả các lớp thực hiện giao diện đó).

Ưu điểm chính của cách tiếp cận này là không cần quét thủ công toàn bộ đường dẫn cho các lớp con - tất cả logic khám phá được chứa trong ServiceLoaderlớp và nó chỉ tải các lớp được khai báo rõ ràng trong META-INF/servicesthư mục (không phải mọi lớp trên đường dẫn lớp) .

Tuy nhiên, có một số nhược điểm:

  • Nó sẽ không tìm thấy tất cả các lớp con, chỉ những lớp được khai báo rõ ràng. Như vậy, nếu bạn cần thực sự tìm thấy tất cả các lớp con, cách tiếp cận này có thể không đủ.
  • Nó yêu cầu nhà phát triển khai báo rõ ràng lớp trong META-INF/servicesthư mục. Đây là một gánh nặng bổ sung cho nhà phát triển và có thể dễ bị lỗi.
  • Các ServiceLoader.iterator()trường hợp tạo lớp con, không phải Classđối tượng của họ . Điều này gây ra hai vấn đề:
    • Bạn không có ý kiến ​​gì về cách các lớp con được xây dựng - hàm tạo không có đối số được sử dụng để tạo các thể hiện.
    • Như vậy, các lớp con phải có một hàm tạo mặc định hoặc phải khai báo một hàm tạo không có đối số.

Rõ ràng Java 9 sẽ giải quyết một số thiếu sót này (đặc biệt là những thiếu sót liên quan đến việc khởi tạo các lớp con).

Một ví dụ

Giả sử bạn quan tâm đến việc tìm các lớp thực hiện giao diện com.example.Example:

package com.example;

public interface Example {
    public String getStr();
}

Lớp com.example.ExampleImplthực hiện giao diện đó:

package com.example;

public class ExampleImpl implements Example {
    public String getStr() {
        return "ExampleImpl's string.";
    }
}

Bạn sẽ khai báo lớp ExampleImpllà một triển khai Examplebằng cách tạo một tệp META-INF/services/com.example.Examplecó chứa văn bản com.example.ExampleImpl.

Sau đó, bạn có thể có được một phiên bản của mỗi lần thực hiện Example(bao gồm một thể hiện của ExampleImpl) như sau:

ServiceLoader<Example> loader = ServiceLoader.load(Example.class)
for (Example example : loader) {
    System.out.println(example.getStr());
}

// Prints "ExampleImpl's string.", plus whatever is returned
// by other declared implementations of com.example.Example.

3

Cũng cần lưu ý rằng điều này tất nhiên sẽ chỉ tìm thấy tất cả các lớp con tồn tại trên đường dẫn hiện tại của bạn. Có lẽ điều này là ổn đối với những gì bạn đang xem, và rất có thể bạn đã xem xét điều này, nhưng nếu tại bất kỳ thời điểm nào bạn phát hành một người không thuộc finaltầng lớp hoang dã (đối với các mức độ "hoang dã" khác nhau) thì điều đó hoàn toàn khả thi ai đó đã viết lớp con của riêng họ mà bạn sẽ không biết.

Do đó, nếu bạn tình cờ muốn xem tất cả các lớp con vì bạn muốn thực hiện thay đổi và sẽ xem nó ảnh hưởng đến hành vi của lớp con như thế nào - thì hãy lưu ý đến các lớp con mà bạn không thể thấy. Lý tưởng nhất là tất cả các phương thức không riêng tư của bạn và bản thân lớp nên được ghi chép đầy đủ; thực hiện thay đổi theo tài liệu này mà không thay đổi ngữ nghĩa của các phương thức / trường không riêng tư và các thay đổi của bạn phải tương thích ngược, đối với bất kỳ lớp con nào tuân theo định nghĩa của siêu lớp ít nhất.


3

Lý do bạn thấy sự khác biệt giữa triển khai của bạn và Eclipse là vì bạn quét mỗi lần, trong khi Eclipse (và các công cụ khác) chỉ quét một lần (trong khi tải dự án hầu hết thời gian) và tạo một chỉ mục. Lần tới khi bạn yêu cầu dữ liệu nó không quét lại, nhưng hãy nhìn vào chỉ mục.


3

Tôi đang sử dụng lib phản chiếu, quét đường dẫn lớp của bạn cho tất cả các lớp con: https://github.com/ronmamo/reflections

Đây là cách nó sẽ được thực hiện:

Reflections reflections = new Reflections("my.project");
Set<Class<? extends SomeType>> subTypes = reflections.getSubTypesOf(SomeType.class);

2

Thêm chúng vào một bản đồ tĩnh bên trong (this.getClass (). GetName ()) hàm tạo của lớp cha (hoặc tạo một mặc định) nhưng điều này sẽ được cập nhật trong thời gian chạy. Nếu lười khởi tạo là một tùy chọn, bạn có thể thử phương pháp này.



0

Tôi cần phải làm điều này như một trường hợp thử nghiệm, để xem các lớp mới đã được thêm vào mã chưa. Đây là những gì tôi đã làm

final static File rootFolder = new File(SuperClass.class.getProtectionDomain().getCodeSource().getLocation().getPath());
private static ArrayList<String> files = new ArrayList<String>();
listFilesForFolder(rootFolder); 

@Test(timeout = 1000)
public void testNumberOfSubclasses(){
    ArrayList<String> listSubclasses = new ArrayList<>(files);
    listSubclasses.removeIf(s -> !s.contains("Superclass.class"));
    for(String subclass : listSubclasses){
        System.out.println(subclass);
    }
    assertTrue("You did not create a new subclass!", listSubclasses.size() >1);     
}

public static void listFilesForFolder(final File folder) {
    for (final File fileEntry : folder.listFiles()) {
        if (fileEntry.isDirectory()) {
            listFilesForFolder(fileEntry);
        } else {
            files.add(fileEntry.getName().toString());
        }
    }
}
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.