Java yansımasını kullanarak yöntem parametresi adını alabilir miyim?


124

Böyle bir dersim varsa:

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

aMethodadlı bir parametre kullandığını bilmenin herhangi bir yolu var mı aParam, bu türden int?


10
7 cevap ve kimse "yöntem imzası" teriminden bahsetmedi. Bu temelde yansıma ile iç gözlem yapabileceğiniz şeydir, yani: parametre adı yok.
ewernli

3
o ise mümkün aşağıya benim cevap bakınız
fly-by-wire

4
6 yıl sonra, artık Java 8'de yansıma yoluyla mümkün , bu SO cevabına bakın
earcam

Yanıtlar:


90

Özetlemek:

  • Derleme sırasında hata ayıklama bilgisi dahil edilmişse parametre adlarının alınması mümkündür. Daha fazla ayrıntı için bu yanıta bakın
  • aksi takdirde parametre isimlerini almak mümkün değildir
  • kullanarak parametre türünü almak mümkündür method.getParameterTypes()

Bir düzenleyici için otomatik tamamlama işlevini yazmak adına (yorumlardan birinde belirttiğiniz gibi) birkaç seçenek vardır:

  • Kullanım arg0, arg1, arg2vb
  • Kullanım intParam, stringParam, objectTypeParamvb
  • Yukarıdakilerin bir kombinasyonunu kullanın - ilki ilkel olmayan türler için ve ikincisi ilkel türler için.
  • argüman isimlerini hiç gösterme - sadece türleri.

4
Arayüzlerle bu mümkün mü? Hala arayüz parametre adlarını almanın bir yolunu bulamadım.
Cemo

@Cemo: Arayüz parametre isimlerini almanın bir yolunu bulabildiniz mi?
Vaibhav Gupta

Eklemek gerekirse, baharın ilkel türlerden belirsiz olmayan bir şekilde nesne oluşturmak için @ConstructorProperties ek açıklamasına ihtiyaç duymasının nedeni budur.
Bharat

102

Java 8'de şunları yapabilirsiniz:

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

Yani sınıfınız Whateveriçin manuel bir test yapabiliriz:

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

hangi yazdırmalısınız [aParam]sen geçtiyseniz -parametersJava 8 derleyici argüman.

Maven kullanıcıları için:

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

Daha fazla bilgi için aşağıdaki bağlantılara bakın:

  1. Resmi Java Eğitimi: Yöntem Parametrelerinin Adlarını Alma
  2. JEP 118: Çalışma Süresinde Parametre Adlarına Erişim
  3. Parametre sınıfı için Javadoc

2
Derleyici eklentisinin argümanlarını değiştirip değiştirmediklerini bilmiyorum, ancak en son (3.5.1 itibariyle) yapılandırma bölümünde derleyici değiştirgelerini kullanmak zorunda kaldım: <configuration> <compilerArgs> <arg> - parametreler </arg> </compilerArgs> </configuration>
en fazla

15

Paranamer kütüphanesi aynı sorunu çözmek için oluşturuldu.

Birkaç farklı yolla yöntem adlarını belirlemeye çalışır. Sınıf, hata ayıklama ile derlenmişse, sınıfın bayt kodunu okuyarak bilgileri çıkarabilir.

Diğer bir yol da, derlendikten sonra, ancak bir kavanoz içine yerleştirilmeden önce sınıfın bayt koduna özel bir statik üye enjekte etmesidir. Daha sonra bu bilgiyi çalışma zamanında sınıftan çıkarmak için yansıma kullanır.

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

Bu kütüphaneyi kullanırken sorun yaşadım ama sonunda onu çalıştırdım. Sorunları bakıcıya bildirmeyi umuyorum.


Bir android apk içinde paranamer kullanmaya çalışıyorum. Ama alıyorumParameterNAmesNotFoundException
Rilwan

10

org.springframework.core.DefaultParameterNameDiscoverer sınıfına bakın

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

10

Evet.
Kod , resmi parametre adlarını saklama seçeneği açık ( -parametreler seçeneği) ile Java 8 uyumlu derleyici ile derlenmelidir .
Daha sonra bu kod parçacığı çalışmalıdır:

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

Bunu denedim ve işe yaradı! Yine de bir ipucu, bu derleyici yapılandırmalarının yürürlüğe girmesi için projenizi yeniden oluşturmam gerekti.
ishmaelMakitla

5

Yöntemi yansıma ile alabilir ve argüman türlerini tespit edebilirsiniz. Http://java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html#getParameterTypes%28%29 adresini kontrol edin

Ancak, kullanılan argümanın adını söyleyemezsiniz.


17
Gerçekten bu mümkün. Onun sorusu ancak oldu açıkça ilgili parametre adı . Konu başlığını kontrol edin.
BalusC

1
Ve alıntı yapıyorum: "Ancak, kullanılan argümanın adını söyleyemezsiniz." sadece cevabımı oku -_-
Johnco

3
Soru, türü elde etmeyi bilmediği şekilde formüle edilmedi.
BalusC

3

Mümkün ve Spring MVC 3 bunu yapıyor, ancak tam olarak nasıl olduğunu görmek için zaman ayırmadım.

Yöntem parametre adlarının URI Şablon değişken adlarıyla eşleştirilmesi, yalnızca kodunuz hata ayıklama etkinken derlenmişse yapılabilir. Hata ayıklamayı etkinleştirmediyseniz, değişken adının çözümlenmiş değerini bir yöntem parametresine bağlamak için @PathVariable ek açıklamasında URI Şablon değişken adını belirtmeniz gerekir. Örneğin:

Alındığı yay belgelerinde


org.springframework.core.Conventions.getVariableName () ancak bağımlılık olarak cglib'e sahip olmanız gerektiğini düşünüyorum
Radu Toader

3

Mümkün olmasa da (diğerlerinin de gösterdiği gibi) siz olabilir parametre adı üzerinde taşımak için bir şerh kullanın ve yansıma olsa o elde ederiz.

En temiz çözüm değil, ama işi hallediyor. Bazı web hizmetleri bunu parametre adlarını korumak için yapar (yani: WS'leri cam balığı ile dağıtmak).



3

Yani şunları yapabilmelisiniz:

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

Ama muhtemelen şöyle bir liste alacaksınız:

["int : arg0"]

Bunun Groovy 2.5+ sürümünde düzeltileceğine inanıyorum

Şu anda cevap şu:

  • Bu bir Groovy sınıfı ise, o zaman hayır, adını alamazsınız, ancak gelecekte alabilmelisiniz.
  • Java 8 altında derlenmiş bir Java sınıfı ise, yapabilmeniz gerekir.

Ayrıca bakınız:


Her yöntem için, o zaman şöyle bir şey:

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

Ayrıca bir yöntemin adını belirtmek istemiyorum aMethod. Bir sınıftaki tüm yöntemler için almak istiyorum.
snoop

@snoop, sentetik olmayan her yöntemi elde etmenin bir örneğini ekledi
tim_yates

Bunun antlriçin parametre isimlerini almak için kullanamaz mıyız ?
snoop

2

tutulmayı kullanırsanız, derleyicinin yöntem parametreleri hakkındaki bilgileri saklamasına izin vermek için aşağıdaki resme bakın.

görüntü açıklamasını buraya girin


2

@Bozho'nun belirttiği gibi, derleme sırasında hata ayıklama bilgisi dahil edilirse bunu yapmak mümkündür. Burada güzel bir cevap var ...

Bir nesnenin kurucularının (yansıma) parametre adları nasıl alınır? @AdamPaynter tarafından

... ASM kitaplığını kullanarak. Hedefinize nasıl ulaşabileceğinizi gösteren bir örnek oluşturdum.

Öncelikle, bu bağımlılıkları içeren bir pom.xml ile başlayın.

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

O zaman bu sınıf istediğinizi yapmalıdır. Sadece statik yöntemi çağırın 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;
    }
}

İşte bir birim testi örneği.

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

    }
}

Tam örneği GitHub'da bulabilirsiniz

Uyarılar

  • Metotlar için çalışmasını sağlamak için orijinal çözümü @AdamPaynter'den biraz değiştirdim. Düzgün anladıysam, çözümü yalnızca kurucularla çalışır.
  • Bu çözüm staticyöntemlerle çalışmaz . Bu, bu durumda ASM tarafından döndürülen argümanların sayısının farklı olmasından kaynaklanır, ancak bu kolayca düzeltilebilecek bir şeydir.

0

Parametre adları yalnızca derleyici için yararlıdır. Derleyici bir sınıf dosyası oluşturduğunda, parametre adları dahil edilmez - bir yöntemin argüman listesi yalnızca argümanlarının sayısı ve türlerinden oluşur. Dolayısıyla, yansıma kullanarak parametre adını almak imkansız olacaktır (sorunuzda etiketlendiği gibi) - hiçbir yerde mevcut değildir.

Ancak, yansıma kullanımı zor bir gereklilik değilse, bu bilgiyi doğrudan kaynak kodundan alabilirsiniz (elinizde olduğunu varsayarak).


2
Parametre adı sadece bir derleyici için yararlı değildir, aynı zamanda bir kütüphanenin tüketicisine de bilgi aktarırlar, eğer bunu kullanmaya gittiyseniz ve bir yöntem bulursanız add (int gün, int saat, int dakika) metoduna sahip bir StrangeDate sınıfı yazdığımı varsayalım. add (int arg0, int arg1, int arg2) bu ne kadar yararlı olurdu?
David Waters

Üzgünüm - cevabımı tamamen yanlış anladınız. Lütfen soru bağlamında tekrar okuyun.
danben

2
Parametrelerin adlarını elde etmek, bu yöntemi yansıtarak çağırmak için büyük bir fayda sağlar. Soru bağlamında bile sadece derleyici için yararlı değildir.
corsiKa

Parameter names are only useful to the compiler.Yanlış. Retrofit kitaplığına bakın. REST API istekleri oluşturmak için dinamik Arayüzler kullanır. Özelliklerinden biri, URL yollarında yer tutucu adlarını tanımlama ve bu yer tutucuları karşılık gelen parametre adlarıyla değiştirme yeteneğidir.
TheRealChx101

0

2 sentimi eklemek için; parametre bilgisi, kaynağı derlemek için javac -g kullandığınızda "hata ayıklama için" bir sınıf dosyasında mevcuttur. Ve APT tarafından kullanılabilir, ancak bir ek açıklamaya ihtiyacınız olacak, bu yüzden size faydası yok. (Biri 4-5 yıl önce benzer bir şeyi burada tartıştı: http://forums.java.net/jive/thread.jspa?messageID=13467&tstart=0 )

Genel olarak kısaca, Kaynak dosyalar üzerinde doğrudan çalışmadığınız sürece elde edemezsiniz (APT'nin derleme zamanında yaptığı gibi).

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.