2015-02-08 17 views
14

Tôi muốn nhận tất cả các phương thức của một lớp, bao gồm các phương thức công cộng, được bảo vệ, gói và riêng và bao gồm cả các phương thức kế thừa.Cách mới để nhận tất cả các phương thức của một lớp, bao gồm các phương thức mặc định kế thừa của Java 8 là gì?

Hãy nhớ rằng:

  • Class.getDeclaredMethods() bị công cộng, bảo vệ, đóng gói và phương pháp riêng, nhưng không bao gồm kế thừa phương pháp.
  • Class.getMethods là các phương pháp được kế thừa, nhưng chỉ các phương thức công khai.

Trước Java 8 chúng ta có thể làm điều gì đó dọc theo dòng:

Collection<Method> found = new ArrayList<Method>(); 
while (clazz != null) { 
    for (Method m1 : clazz.getDeclaredMethods()) { 
     boolean overridden = false; 

     for (Method m2 : found) { 
      if (m2.getName().equals(m1.getName()) 
       && Arrays.deepEquals(m1.getParameterTypes(), m2 
        .getParameterTypes())) { 
      overridden = true; 
      break; 
      } 
     } 
     if (!overridden) found.add(m1); 
    } 

    clazz = clazz.getSuperclass(); 
} 
return found; 

Nhưng bây giờ, nếu các lớp thực hiện một số giao diện với phương pháp mặc định mà không được ghi đè bởi superclasses bê tông, các phương pháp này sẽ thoát khỏi phát hiện trên. Bên cạnh đó, hiện nay có các quy tắc liên quan đến các phương thức mặc định có cùng tên và các quy tắc này cũng phải được xem xét.

Câu hỏi: cách khuyến cáo hiện tại của nhận tất cả method của một class là gì:

Định nghĩa phổ biến nhất của "tất cả" nên là phương pháp có thể được truy cập trực tiếp bên trong một phương pháp thể hiện của lớp học, mà không cần sử dụng super hoặc tên lớp:

  • Bao gồm các phương thức công khai, được bảo vệ, gói và riêng tư được khai báo trong chính lớp đó.
  • Bao gồm các phương thức được bảo vệ của các siêu lớp của nó.
  • Bao gồm các phương thức gói của các siêu lớp của cùng một gói.
  • Bao gồm các phương pháp mặc định của giao diện của nó (những giao diện không bị ghi đè/ẩn, xem herehere).
  • Bao gồm các phương thức tĩnh (lớp và siêu lớp) với khả năng truy cập thích hợp.
  • Không bao gồm các phương thức riêng của siêu lớp.
  • Không bao gồm các phương pháp ghi đè.
  • Không bao gồm các phương thức ẩn (đặc biệt, không bao gồm các phương thức tĩnh ẩn).
  • Không bao gồm các phương pháp tổng hợp/cầu.
  • Không bao gồm các phương thức không được Java cho phép, ngay cả khi JVM cho phép chúng.

Vì vậy, định nghĩa ở trên phù hợp với chữ ký sau khi cả hai cờ boolean là false:

public Collection<Method> getAllMethods(Class clazz, 
           boolean includeAllPackageAndPrivateMethodsOfSuperclasses, 
           boolean includeOverridenAndHidden) 

Các lý tưởng, câu trả lời kinh điển, nên cho phép những lá cờ boolean.

+0

Bạn có chắc chắn rằng cách nhận tất cả các phương pháp của một lớp đã hoạt động cho đến khi java 7 không còn đủ nữa? Bạn có một ví dụ mà nó không? –

+1

@MikeNakis, hãy tự mình thử. Các phương thức mặc định (không triển khai ghi đè) sẽ không hiển thị. – aioobe

+0

tốt, họ không thực sự tuyên bố phương pháp, vì vậy tôi sẽ không mong đợi getDeclartedMethods() để lấy chúng, nhưng không getMethods() lấy chúng? –

Trả lời

11

Ngay cả đối với trường hợp “Trước Java 8”, đoạn mã của bạn không chính xác.Tuy nhiên, việc thu thập tất cả các phương pháp không phải là một kịch bản thông thường, vì bạn thường cần các phương pháp liên quan đến một ngữ cảnh nhất định, ví dụ: bạn có thể muốn biết phương thức nào là có thể truy cập cho một ngữ cảnh nhất định, không bao gồm tất cả các phương pháp, ngay cả khi bạn xem xét các phương pháp không phải là public. Nếu bạn thực sự muốn tất cả các phương thức, bạn phải nhớ rằng các phương pháp privatestatic không bao giờ bị ghi đè và các phương thức riêng tư của gói chỉ được ghi đè khi được khai báo trong cùng một package. Vì vậy, không đúng để lọc mọi chữ ký phương thức gặp phải.

Điều gì làm cho vấn đề tồi tệ hơn là các phương pháp có thể bị ghi đè với các công cụ sửa đổi khác nhau. Cách thứ hai có thể được giải quyết bằng cách giữ nguyên ý tưởng để bắt đầu ở lớp thực tế và sử dụng Class.getMethods() để nhận tất cả phương thức public bao gồm các phương thức default và đi qua phân cấp siêu lớp theo hướng java.lang.Object để ghi đè đã gặp phải.

Như một lưu ý phụ, việc lồng vòng tìm kiếm tuyến tính không bao giờ là ý tưởng hay. Bạn sẽ sớm kết thúc với sự phức tạp bậc hai hoặc tệ hơn.

Bạn có thể thu thập các phương pháp sử dụng các phương pháp sau đây:

public static Set<Method> getAllMethods(Class<?> cl) { 
    Set<Method> methods=new LinkedHashSet<>(); 
    Collections.addAll(methods, cl.getMethods()); 
    Map<Object,Set<Package>> types=new HashMap<>(); 
    final Set<Package> pkgIndependent = Collections.emptySet(); 
    for(Method m: methods) types.put(methodKey(m), pkgIndependent); 
    for(Class<?> current=cl; current!=null; current=current.getSuperclass()) { 
     for(Method m: current.getDeclaredMethods()) { 
      final int mod = m.getModifiers(), 
       access=Modifier.PUBLIC|Modifier.PROTECTED|Modifier.PRIVATE; 
      if(!Modifier.isStatic(mod)) switch(mod&access) { 
       case Modifier.PUBLIC: continue; 
       default: 
        Set<Package> pkg= 
         types.computeIfAbsent(methodKey(m), key -> new HashSet<>()); 
        if(pkg!=pkgIndependent && pkg.add(current.getPackage())) break; 
        else continue; 
       case Modifier.PROTECTED: 
        if(types.putIfAbsent(methodKey(m), pkgIndependent)!=null) continue; 
        // otherwise fall-through 
       case Modifier.PRIVATE: 
      } 
      methods.add(m); 
     } 
    } 
    return methods; 
} 

private static Object methodKey(Method m) { 
    return Arrays.asList(m.getName(), 
     MethodType.methodType(m.getReturnType(), m.getParameterTypes())); 
} 

Nhưng như đã nói, nó có thể là trường hợp đó nó không phải là thích hợp cho bất cứ điều gì bạn muốn làm. Bạn nên tự hỏi mình những câu hỏi sau đây đầu tiên:

  • Bạn đang tìm kiếm phương pháp tạo nên API (đó là thường public và chỉ protected)?
  • Hoặc bạn có muốn thực sự thấy các phương thức có thể truy cập được cho một ngữ cảnh class/package nhất định không?
  • Sẽ bao gồm các phương pháp static?
  • Phải bao gồm các phương pháp tổng hợp/cầu?
  • , vv

Dưới đây là phương pháp điều chỉnh phù hợp với yêu cầu cụ thể hơn của bạn:

public static Collection<Method> getAllMethods(Class clazz, 
       boolean includeAllPackageAndPrivateMethodsOfSuperclasses, 
       boolean includeOverridenAndHidden) { 

    Predicate<Method> include = m -> !m.isBridge() && !m.isSynthetic() && 
     Character.isJavaIdentifierStart(m.getName().charAt(0)) 
     && m.getName().chars().skip(1).allMatch(Character::isJavaIdentifierPart); 

    Set<Method> methods = new LinkedHashSet<>(); 
    Collections.addAll(methods, clazz.getMethods()); 
    methods.removeIf(include.negate()); 
    Stream.of(clazz.getDeclaredMethods()).filter(include).forEach(methods::add); 

    final int access=Modifier.PUBLIC|Modifier.PROTECTED|Modifier.PRIVATE; 

    Package p = clazz.getPackage(); 
    if(!includeAllPackageAndPrivateMethodsOfSuperclasses) { 
     int pass = includeOverridenAndHidden? 
      Modifier.PUBLIC|Modifier.PROTECTED: Modifier.PROTECTED; 
     include = include.and(m -> { int mod = m.getModifiers(); 
      return (mod&pass)!=0 
       || (mod&access)==0 && m.getDeclaringClass().getPackage()==p; 
     }); 
    } 
    if(!includeOverridenAndHidden) { 
     Map<Object,Set<Package>> types = new HashMap<>(); 
     final Set<Package> pkgIndependent = Collections.emptySet(); 
     for(Method m: methods) { 
      int acc=m.getModifiers()&access; 
      if(acc==Modifier.PRIVATE) continue; 
      if(acc!=0) types.put(methodKey(m), pkgIndependent); 
      else types.computeIfAbsent(methodKey(m),x->new HashSet<>()).add(p); 
     } 
     include = include.and(m -> { int acc = m.getModifiers()&access; 
      return acc!=0? acc==Modifier.PRIVATE 
        || types.putIfAbsent(methodKey(m), pkgIndependent)==null: 
       noPkgOverride(m, types, pkgIndependent); 
     }); 
    } 
    for(clazz=clazz.getSuperclass(); clazz!=null; clazz=clazz.getSuperclass()) 
     Stream.of(clazz.getDeclaredMethods()).filter(include).forEach(methods::add); 
    return methods; 
} 
static boolean noPkgOverride(
     Method m, Map<Object,Set<Package>> types, Set<Package> pkgIndependent) { 
    Set<Package> pkg = types.computeIfAbsent(methodKey(m), key -> new HashSet<>()); 
    return pkg!=pkgIndependent && pkg.add(m.getDeclaringClass().getPackage()); 
} 
private static Object methodKey(Method m) { 
    return Arrays.asList(m.getName(), 
     MethodType.methodType(m.getReturnType(), m.getParameterTypes())); 
} 
+0

Vâng, tôi biết đoạn mã không chính xác, đó là lý do tại sao tôi nói "điều gì đó dọc theo dòng". Dù sao, định nghĩa về "tất cả các phương pháp" phải thực sự chính xác hơn cho một câu trả lời để tạo ra bất kỳ ý nghĩa nào. Tôi đã chỉnh sửa câu hỏi của mình để thêm độ chính xác này, để câu trả lời chính xác cho câu hỏi này có thể hữu ích cho hầu hết mọi người. Vui lòng đề xuất sửa đổi nếu bạn cho rằng định nghĩa "tất cả các phương pháp" vẫn chưa đủ chính xác. – MarcG

+1

Xin lỗi vì đã quá lâu để chấp nhận câu trả lời của bạn. Đối với lễ hội Brazil, tôi đã đến một hòn đảo nhỏ được cho là có kết nối internet nhưng không, và tôi chỉ trở lại Rio hôm qua. – MarcG

1

tôi đã không thể để biên dịch câu trả lời Holger trong một môi trường Android kể từ MethodType được thêm vào trong API cấp 26 và Android Studio hỗ trợ một tập hợp con các Tính năng ngôn ngữ Java 8. Thêm vào đó, mã của Holger chứa nhiều lambdas và suối, tôi coi đó là con người không thể đọc được. Vì vậy, tôi quyết định viết một mã dễ đọc hơn hoạt động trong bất kỳ môi trường Java nào. Nhưng nó không phải là một giải pháp lý tưởng vì tôi không bao gồm các lá cờ.

Dưới đoạn làm việc giống như nếu bạn gọi getAllMethods(clazz, false, false)

private static Collection<Method> getAllMethods(Class<?> target) { 
    Class<?> clazz = target; 
    Collection<MethodSignature> methodSignatures = new ArrayList<>(); 
    for(Method method : clazz.getDeclaredMethods()) { 
     addIfAbsentAndNonSynthetic(methodSignatures, method); 
    } 
    for(Method method : clazz.getMethods()) { 
     addIfAbsentAndNonSynthetic(methodSignatures, method); 
    } 
    Package pkg = clazz.getPackage(); 
    clazz = clazz.getSuperclass(); 
    while(clazz != null) { 
     for(Method method : clazz.getDeclaredMethods()) { 
      int modifier = method.getModifiers(); 
      if(Modifier.isPrivate(modifier)) { 
       continue; 
      } 
      if(Modifier.isPublic(modifier) || Modifier.isProtected(modifier)) { 
       addIfAbsentAndNonSynthetic(methodSignatures, method); 
      } 
      else if((pkg != null && pkg.equals(clazz.getPackage())) || (pkg == null 
        && clazz.getPackage() == null)) { 
       addIfAbsentAndNonSynthetic(methodSignatures, method); 
      } 
     } 
     clazz = clazz.getSuperclass(); 
    } 
    Collection<Method> allMethods = new ArrayList<>(methodSignatures.size()); 
    for(MethodSignature methodSignature : methodSignatures) { 
     allMethods.add(methodSignature.getMethod()); 
    } 
    return allMethods; 
} 

private static void addIfAbsentAndNonSynthetic(Collection<MethodSignature> collection, 
     Method method) { 
    MethodSignature methodSignature = new MethodSignature(method); 
    if(!method.isSynthetic() && !collection.contains(methodSignature)) { 
     collection.add(methodSignature); 
    } 
} 

Hai trong số các thành phần của một phương pháp kê khai bao gồm các phương pháp chữ ký: Tên phương pháp và các loại tham số. Trình biên dịch không xem xét kiểu trả về khi phân biệt các phương thức, do đó bạn không thể khai báo hai phương thức có cùng chữ ký ngay cả khi chúng có kiểu trả về khác. Vì vậy, MethodSignature lớp không giữ bất kỳ tham chiếu đến kiểu trả về của phương thức của nó.

Nhưng khi bạn gọi getDeclaredMethods hoặc getMethods, bạn có thể nhận nhiều phương thức được khai báo có cùng tên và loại thông số, nhưng các loại trả lại khác nhau. Điều này có nghĩa là trình biên dịch tạo ra một phương thức tổng hợp, được gọi là phương thức cầu nối. Để giải quyết vấn đề này, hãy gọi method.isSynthetic() trên phương thức, nếu nó trả về true thì bỏ qua nó. Vì nó là một phương pháp tổng hợp sẽ có một phương pháp không tổng hợp với cùng một chữ ký nhưng kiểu trả về khác nhau.

public class MethodSignature { 
    private final Method mMethod; 
    private final String mName; 
    private final Class<?>[] mParameterTypes; 

    public MethodSignature(Method method) { 
     mMethod = method; 
     mName = mMethod.getName(); 
     mParameterTypes = mMethod.getParameterTypes(); 
    } 

    public Method getMethod() { 
     return mMethod; 
    } 

    public String getName() { 
     return mName; 
    } 

    public Class<?>[] getParameterTypes() { 
     return mParameterTypes; 
    } 

    @Override 
    public boolean equals(Object object) { 
     if(this == object) { 
      return true; 
     } 
     if(object == null) { 
      return false; 
     } 
     if(!getClass().equals(object.getClass())) { 
      return false; 
     } 
     MethodSignature obj = (MethodSignature) object; 
     if(hashCode() != obj.hashCode()) { 
      return false; 
     } 
     return mName.equals(obj.getName()) && Arrays 
       .equals(mParameterTypes, obj.getParameterTypes()); 
    } 

    @Override 
    public int hashCode() { 
     int hash = 11; 
     hash = 37 * hash + Objects.hash(mName, Arrays.hashCode(mParameterTypes)); 
     return hash; 
    } 
} 
Các vấn đề liên quan