Tôi có thể lấy tên tham số phương thức bằng phản xạ Java không?


124

Nếu tôi có một lớp học như thế này:

public class Whatever
{
  public void aMethod(int aParam);
}

có cách nào để biết rằng aMethodsử dụng một tham số có tên aParam, đó là loại intkhông?


10
7 câu trả lời và không ai đề cập đến thuật ngữ "chữ ký phương pháp". Về cơ bản, đây là những gì bạn có thể xem xét bên trong với sự phản chiếu, có nghĩa là: không có tên tham số.
ewernli

3
có thể, xem câu trả lời của tôi dưới đây
các hệ thống kiểm soát bay

4
6 năm sau, nó bây giờ có thể thông qua phản ánh trong Java 8 , xem câu trả lời SO này
earcam

Câu trả lời:


90

Tóm lại:

  • nhận được tên tham số có thể nếu thông tin gỡ lỗi được bao gồm trong biên dịch. Xem câu trả lời này để biết thêm chi tiết
  • nếu không thì nhận được tên tham số là không thể
  • có được loại tham số, sử dụng method.getParameterTypes()

Vì lợi ích của việc viết chức năng tự động hoàn thành cho trình chỉnh sửa (như bạn đã nêu trong một trong các nhận xét), có một số tùy chọn:

  • sử dụng arg0, arg1,arg2 , vv
  • sử dụng intParam, stringParam,objectTypeParam vv
  • sử dụng kết hợp ở trên - cái trước cho các kiểu không nguyên thủy và cái sau cho các kiểu nguyên thủy.
  • hoàn toàn không hiển thị tên đối số - chỉ là các loại.

4
Điều này có khả thi với Giao diện không? Tôi vẫn không thể tìm thấy cách lấy tên tham số giao diện.
Cemo

@Cemo: bạn có thể tìm cách lấy tên thông số giao diện không?
Vaibhav Gupta

Chỉ cần nói thêm, đó là lý do tại sao spring cần chú thích @ConstructorProperties để tạo đối tượng từ các kiểu nguyên thủy một cách không mơ hồ.
Bharat

102

Trong Java 8, bạn có thể làm như sau:

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;

public final class Methods {

    public static List<String> getParameterNames(Method method) {
        Parameter[] parameters = method.getParameters();
        List<String> parameterNames = new ArrayList<>();

        for (Parameter parameter : parameters) {
            if(!parameter.isNamePresent()) {
                throw new IllegalArgumentException("Parameter names are not present!");
            }

            String parameterName = parameter.getName();
            parameterNames.add(parameterName);
        }

        return parameterNames;
    }

    private Methods(){}
}

Vì vậy, đối với lớp học của bạn, Whateverchúng tôi có thể thực hiện một bài kiểm tra thủ công:

import java.lang.reflect.Method;

public class ManualTest {
    public static void main(String[] args) {
        Method[] declaredMethods = Whatever.class.getDeclaredMethods();

        for (Method declaredMethod : declaredMethods) {
            if (declaredMethod.getName().equals("aMethod")) {
                System.out.println(Methods.getParameterNames(declaredMethod));
                break;
            }
        }
    }
}

cái nào sẽ in [aParam]nếu bạn đã vượt qua-parameters đối số vào trình biên dịch Java 8 của mình.

Đối với người dùng Maven:

<properties>
    <!-- PLUGIN VERSIONS -->
    <maven-compiler-plugin.version>3.1</maven-compiler-plugin.version>

    <!-- OTHER PROPERTIES -->
    <java.version>1.8</java.version>
</properties>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>${maven-compiler-plugin.version}</version>
            <configuration>
                <!-- Original answer -->
                <compilerArgument>-parameters</compilerArgument>
                <!-- Or, if you use the plugin version >= 3.6.2 -->
                <parameters>true</parameters>
                <testCompilerArgument>-parameters</testCompilerArgument>
                <source>${java.version}</source>
                <target>${java.version}</target>
            </configuration>
        </plugin>
    </plugins>
</build>

Để biết thêm thông tin, hãy xem các liên kết sau:

  1. Hướng dẫn Java chính thức: Lấy tên của các tham số phương thức
  2. JEP 118: Truy cập vào tên tham số trong thời gian chạy
  3. Javadoc cho lớp Tham số

2
Tôi không biết liệu họ có thay đổi các đối số cho plugin trình biên dịch hay không, nhưng với phiên bản mới nhất (3.5.1 tính đến hiện tại), tôi phải làm để sử dụng args trình biên dịch trong phần cấu hình: <configuration> <compilerArgs> <arg> - tham số </arg> </compilerArgs> </configuration>
tối đa

15

Thư viện Paranamer được tạo ra để giải quyết vấn đề tương tự.

Nó cố gắng xác định tên phương thức theo một vài cách khác nhau. Nếu lớp được biên dịch bằng cách gỡ lỗi, nó có thể trích xuất thông tin bằng cách đọc mã bytecode của lớp.

Một cách khác là nó đưa một thành viên tĩnh riêng tư vào bytecode của lớp sau khi nó được biên dịch, nhưng trước khi nó được đặt trong một jar. Sau đó, nó sử dụng phản chiếu để trích xuất thông tin này từ lớp trong thời gian chạy.

https://github.com/paul-hammant/paranamer

Tôi đã gặp sự cố khi sử dụng thư viện này, nhưng cuối cùng tôi đã làm cho nó hoạt động. Tôi hy vọng sẽ báo cáo sự cố cho người bảo trì.


Tôi đang cố gắng sử dụng paranamer bên trong apk Android. Nhưng tôi đang nhận đượcParameterNAmesNotFoundException
Rilwan

10

xem lớp org.springframework.core.DefaultParameterNameDiscoverer

DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
String[] params = discoverer.getParameterNames(MathUtils.class.getMethod("isPrime", Integer.class));

10

Đúng.
phải được biên dịch bằng trình biên dịch tuân thủ Java 8 với tùy chọn lưu trữ tên tham số chính thức được bật ( tùy chọn -parameters ).
Sau đó, đoạn mã này sẽ hoạt động:

Class<String> clz = String.class;
for (Method m : clz.getDeclaredMethods()) {
   System.err.println(m.getName());
   for (Parameter p : m.getParameters()) {
    System.err.println("  " + p.getName());
   }
}

Đã thử điều này và nó hoạt động! Tuy nhiên, có một mẹo là tôi phải xây dựng lại dự án của bạn để các cấu hình trình biên dịch này có hiệu lực.
ishmaelMakitla

5

Bạn có thể truy xuất phương thức với sự phản chiếu và phát hiện các loại đối số của nó. Kiểm tra http://java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html#getParameterTypes%28%29

Tuy nhiên, bạn không thể biết tên của đối số được sử dụng.


17
Thực sự đó là tất cả những gì có thể. Câu hỏi của anh ấy tuy nhiên rõ ràng về tên tham số . Kiểm tra chủ đề.
BalusC

1
Và tôi trích dẫn: "Tuy nhiên, bạn không thể nói tên của đối số được sử dụng." chỉ cần đọc câu trả lời của tôi -_-
Johnco

3
Câu hỏi không được xây dựng theo cách mà anh ta không biết về việc tìm được loại.
BalusC

3

Điều đó là có thể và Spring MVC 3 đã làm được điều đó, nhưng tôi không dành thời gian để xem chính xác cách thức.

Việc đối sánh tên tham số phương thức với tên biến Mẫu URI chỉ có thể được thực hiện nếu mã của bạn được biên dịch với tính năng gỡ lỗi được bật. Nếu bạn chưa bật gỡ lỗi, bạn phải chỉ định tên của tên biến Mẫu URI trong chú thích @PathVariable để liên kết giá trị đã phân giải của tên biến với tham số phương thức. Ví dụ:

Lấy từ tài liệu mùa xuân


org.springframework.core.Conventions.getVariableName () nhưng tôi giả sử rằng bạn phải có cglib như sự phụ thuộc
Radu Toader

3

Mặc dù không thể (như những người khác đã minh họa), bạn có thể sử dụng chú thích để chuyển tên tham số và nhận được thông số đó mặc dù phản ánh.

Không phải là giải pháp sạch nhất, nhưng nó sẽ hoàn thành công việc. Một số dịch vụ web thực sự làm điều này để giữ tên tham số (ví dụ: triển khai WS với cá thủy tinh).



3

Vì vậy, bạn sẽ có thể làm:

Whatever.declaredMethods
        .find { it.name == 'aMethod' }
        .parameters
        .collect { "$it.type : $it.name" }

Nhưng có thể bạn sẽ nhận được một danh sách như vậy:

["int : arg0"]

Tôi tin rằng điều này sẽ được khắc phục trong Groovy 2.5+

Vì vậy, hiện tại, câu trả lời là:

  • Nếu đó là một lớp Groovy, thì không, bạn không thể lấy tên, nhưng bạn sẽ có thể làm như vậy trong tương lai.
  • Nếu đó là một lớp Java được biên dịch theo Java 8, bạn sẽ có thể.

Xem thêm:


Đối với mọi phương pháp, sau đó một cái gì đó như:

Whatever.declaredMethods
        .findAll { !it.synthetic }
        .collect { method -> 
            println method
            method.name + " -> " + method.parameters.collect { "[$it.type : $it.name]" }.join(';')
        }
        .each {
            println it
        }

Tôi cũng không muốn chỉ định tên của một phương thức aMethod. Tôi muốn lấy nó cho tất cả các phương thức trong một lớp.
snoop ngày

@snoop thêm một ví dụ về nhận mọi phương pháp phi tổng hợp
tim_yates

Chúng tôi không thể sử dụng antlrđể lấy tên tham số cho điều này?
snoop ngày

2

nếu bạn sử dụng nhật thực, hãy xem hình ảnh bên dưới để cho phép trình biên dịch lưu trữ thông tin về các tham số phương thức

nhập mô tả hình ảnh ở đây


2

Như @Bozho đã nêu, bạn có thể thực hiện điều đó nếu thông tin gỡ lỗi được đưa vào trong quá trình biên dịch. Có một câu trả lời hay ở đây ...

Làm cách nào để lấy tên tham số của các hàm tạo của một đối tượng (phản chiếu)? bởi @AdamPaynter

... sử dụng thư viện ASM. Tôi đưa ra một ví dụ cho thấy cách bạn có thể đạt được mục tiêu của mình.

Trước hết, hãy bắt đầu với một pom.xml với những phụ thuộc này.

<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm-all</artifactId>
    <version>5.2</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

Sau đó, lớp này sẽ làm những gì bạn muốn. Chỉ cần gọi phương thức tĩnh getParameterNames().

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.MethodNode;

public class ArgumentReflection {
    /**
     * Returns a list containing one parameter name for each argument accepted
     * by the given constructor. If the class was compiled with debugging
     * symbols, the parameter names will match those provided in the Java source
     * code. Otherwise, a generic "arg" parameter name is generated ("arg0" for
     * the first argument, "arg1" for the second...).
     * 
     * This method relies on the constructor's class loader to locate the
     * bytecode resource that defined its class.
     * 
     * @param theMethod
     * @return
     * @throws IOException
     */
    public static List<String> getParameterNames(Method theMethod) throws IOException {
        Class<?> declaringClass = theMethod.getDeclaringClass();
        ClassLoader declaringClassLoader = declaringClass.getClassLoader();

        Type declaringType = Type.getType(declaringClass);
        String constructorDescriptor = Type.getMethodDescriptor(theMethod);
        String url = declaringType.getInternalName() + ".class";

        InputStream classFileInputStream = declaringClassLoader.getResourceAsStream(url);
        if (classFileInputStream == null) {
            throw new IllegalArgumentException(
                    "The constructor's class loader cannot find the bytecode that defined the constructor's class (URL: "
                            + url + ")");
        }

        ClassNode classNode;
        try {
            classNode = new ClassNode();
            ClassReader classReader = new ClassReader(classFileInputStream);
            classReader.accept(classNode, 0);
        } finally {
            classFileInputStream.close();
        }

        @SuppressWarnings("unchecked")
        List<MethodNode> methods = classNode.methods;
        for (MethodNode method : methods) {
            if (method.name.equals(theMethod.getName()) && method.desc.equals(constructorDescriptor)) {
                Type[] argumentTypes = Type.getArgumentTypes(method.desc);
                List<String> parameterNames = new ArrayList<String>(argumentTypes.length);

                @SuppressWarnings("unchecked")
                List<LocalVariableNode> localVariables = method.localVariables;
                for (int i = 1; i <= argumentTypes.length; i++) {
                    // The first local variable actually represents the "this"
                    // object if the method is not static!
                    parameterNames.add(localVariables.get(i).name);
                }

                return parameterNames;
            }
        }

        return null;
    }
}

Đây là một ví dụ với bài kiểm tra đơn vị.

public class ArgumentReflectionTest {

    @Test
    public void shouldExtractTheNamesOfTheParameters3() throws NoSuchMethodException, SecurityException, IOException {

        List<String> parameterNames = ArgumentReflection
                .getParameterNames(Clazz.class.getMethod("callMe", String.class, String.class));
        assertEquals("firstName", parameterNames.get(0));
        assertEquals("lastName", parameterNames.get(1));
        assertEquals(2, parameterNames.size());

    }

    public static final class Clazz {

        public void callMe(String firstName, String lastName) {
        }

    }
}

Bạn có thể tìm thấy ví dụ đầy đủ trên GitHub

Cảnh báo

  • Tôi đã thay đổi một chút giải pháp ban đầu từ @AdamPaynter để làm cho nó hoạt động cho các Phương thức. Nếu tôi hiểu đúng, giải pháp của anh ấy chỉ hoạt động với các constructor.
  • Giải pháp này không hoạt động với staticcác phương pháp. Đây là điều cần thiết trong trường hợp này, số lượng các đối số được trả về bởi ASM là khác nhau, nhưng nó là thứ có thể dễ dàng sửa chữa.

0

Tên tham số chỉ hữu ích cho trình biên dịch. Khi trình biên dịch tạo một tệp lớp, tên tham số không được bao gồm - danh sách đối số của một phương thức chỉ bao gồm số lượng và kiểu đối số của nó. Vì vậy, sẽ không thể truy xuất tên tham số bằng cách sử dụng phản chiếu (như được gắn thẻ trong câu hỏi của bạn) - nó không tồn tại ở bất kỳ đâu.

Tuy nhiên, nếu việc sử dụng phản xạ không phải là một yêu cầu khó, bạn có thể lấy thông tin này trực tiếp từ mã nguồn (giả sử bạn có nó).


2
Tên tham số không chỉ hữu ích đối với trình biên dịch mà chúng còn truyền tải thông tin đến người dùng thư viện, giả sử tôi đã viết một lớp StrangeDate có thêm phương thức (int day, int giờ, int phút) nếu bạn sử dụng nó và tìm thấy một phương thức add (int arg0, int arg1, int arg2) điều đó sẽ hữu ích như thế nào?
David Waters

Tôi xin lỗi - bạn đã hiểu sai hoàn toàn câu trả lời của tôi. Vui lòng đọc lại nó trong ngữ cảnh của câu hỏi.
danben

2
Có được tên của các tham số là một lợi ích lớn để gọi phương thức đó một cách phản ánh. Nó không chỉ hữu ích cho trình biên dịch, ngay cả trong ngữ cảnh của câu hỏi.
corsiKa

Parameter names are only useful to the compiler.Sai lầm. Nhìn vào thư viện Retrofit. Nó sử dụng các Giao diện động để tạo các yêu cầu API REST. Một trong những tính năng của nó là khả năng xác định tên trình giữ chỗ trong đường dẫn URL và thay thế những trình giữ chỗ đó bằng tên tham số tương ứng của chúng.
TheRealChx101

0

Để thêm 2 xu của tôi; thông tin tham số có sẵn trong tệp lớp "để gỡ lỗi" khi bạn sử dụng javac -g để biên dịch nguồn. Và nó có sẵn cho APT nhưng bạn sẽ cần một chú thích để không sử dụng cho bạn. (Ai đó đã thảo luận điều gì đó tương tự cách đây 4-5 năm tại đây: http://forums.java.net/jive/thread.jspa?messageID=13467&tstart=0 )

Nói tóm lại, bạn không thể lấy nó trừ khi bạn làm việc trực tiếp trên các tệp Nguồn (tương tự như những gì APT thực hiện tại thời điểm biên dịch).

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.