Lấy tên lớp từ một phương thức tĩnh trong Java


244

Làm thế nào người ta có thể lấy tên của lớp từ một phương thức tĩnh trong lớp đó. Ví dụ

public class MyClass {
    public static String getClassName() {
        String name = ????; // what goes here so the string "MyClass" is returned
        return name;
    }
}

Để đặt nó trong ngữ cảnh, tôi thực sự muốn trả lại tên lớp như một phần của một thông báo trong một ngoại lệ.


try{ throw new RuntimeEsception();} catch(RuntimeEcxeption e){return e.getstackTrace()[1].getClassName();}
arminvanbuuren

Câu trả lời:


231

Để hỗ trợ tái cấu trúc một cách chính xác (đổi tên lớp), sau đó bạn nên sử dụng một trong hai cách sau:

 MyClass.class.getName(); // full name with package

hoặc (cảm ơn @James Van Huis ):

 MyClass.class.getSimpleName(); // class name and no more

136
Nếu bạn sẽ hiểu mã cứng về kiến ​​thức về MyClass như vậy, thì bạn cũng có thể chỉ cần thực hiện String name = "MyClass"; !
John Topley

111
Nhưng sau đó, cấu trúc lại tên lớp trong IDE của bạn sẽ không hoạt động đúng.
James Van Huis

9
Thật. Mặc dù MyClass. Class sẽ đảm bảo dòng này không bị lãng quên với tái cấu trúc 'thay đổi tên lớp'
bộ công cụ

19
Tôi muốn "cái này" hoạt động trong một ngữ cảnh tĩnh có nghĩa là Lớp hiện tại trong Java, rằng "class.xxx" được cho phép trong cả thể hiện hoặc mã tĩnh có nghĩa là lớp này! Vấn đề với điều này là MyClass dài dòng và dư thừa, trong bối cảnh. Nhưng sau đó, nhiều như tôi thích Java, nó dường như nghiêng về tính dài dòng.
Lawrence Dol

51
Điều gì xảy ra nếu tôi gọi phương thức tĩnh trong một lớp con và tôi muốn tên lớp con?
Edward Falk

120

Làm những gì bộ công cụ nói. Đừng làm bất cứ điều gì như thế này:

return new Object() { }.getClass().getEnclosingClass();

7
Nếu lớp mở rộng một lớp khác, điều này không trả về lớp thực tế, chỉ có lớp cơ sở.
Luis Soeiro

1
@LuisSoeiro Tôi tin rằng nó trả về lớp mà phương thức được định nghĩa. Tôi không chắc cách các lớp cơ sở đưa vào bối cảnh tĩnh.
Tom Hawtin - tackline

8
Tôi không hiểu tại sao getClass () không thể tĩnh. "Thành ngữ" này sau đó sẽ không cần thiết.
mmirwaldt

1
@mmirwaldt getClasstrả về kiểu thời gian chạy, vì vậy không thể tĩnh.
Tom Hawtin - tackline

5
Đây là câu trả lời thực sự. Bởi vì bạn không cần phải tự viết tên của lớp trong mã của mình.
Bobs

99

Trong Java 7+, bạn có thể làm điều này trong các phương thức / trường tĩnh:

MethodHandles.lookup().lookupClass()

Tôi sẽ nói Reflection.getCallerClass(). Nhưng nó đưa ra một cảnh báo về việc ở trong các gói 'mặt trời'. Vì vậy, đây có thể là một giải pháp tốt hơn.
Foumpie

1
@Foumpie: Java 9 sẽ giới thiệu một API chính thức sẽ thay thế điều không chính thức Reflection.getCallerClass()này. Nó hơi phức tạp đối với hoạt động tầm thường của anh ta, tức là Optional<Class<?>> myself = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE) .walk(s -> s.map(StackWalker.StackFrame::getDeclaringClass) .findFirst());, nhưng tất nhiên, điều đó có liên quan đến thực tế là nó sẽ mạnh hơn nhiều.
Holger

6
Đây dễ dàng là giải pháp tốt nhất. Nó tránh sự cần thiết phải chỉ định tên lớp thực tế, nó không tối nghĩa, nó không phải là hack và theo bài đăng của Artyom Krivolapov bên dưới nó cũng là cách tiếp cận nhanh nhất.
skomisa

@Rein Có cách nào để có được lớp thời gian chạy nếu điều này được gọi trong lớp cơ sở không?
Lướt

59

Vì vậy, chúng ta có một tình huống khi chúng ta cần lấy tĩnh đối tượng lớp hoặc một tên đầy đủ / đơn giản của lớp mà không sử dụng MyClass.classcú pháp rõ ràng .

Nó có thể thực sự tiện dụng trong một số trường hợp, ví dụ như logger cho các hàm cấp trên (trong trường hợp này kotlin tạo ra một lớp Java tĩnh không thể truy cập được từ mã kotlin).

Chúng tôi có một vài biến thể khác nhau để có được thông tin này:

  1. new Object(){}.getClass().getEnclosingClass();
    lưu ý bởi Tom Hawtin - tackline

  2. getClassContext()[0].getName();từ SecurityManager
    ghi chú của Christoffer

  3. new Throwable().getStackTrace()[0].getClassName();
    bằng cách đếm ludwig

  4. Thread.currentThread().getStackTrace()[1].getClassName();
    từ Keksi

  5. và cuối cùng là tuyệt vời
    MethodHandles.lookup().lookupClass();
    từ Rein


Tôi đã chuẩn bị một điểm chuẩn cho tất cả các biến thể và kết quả là:

# Run complete. Total time: 00:04:18

Benchmark                                                      Mode  Cnt      Score     Error  Units
StaticClassLookup.MethodHandles_lookup_lookupClass             avgt   30      3.630 ±   0.024  ns/op
StaticClassLookup.AnonymousObject_getClass_enclosingClass      avgt   30    282.486 ±   1.980  ns/op
StaticClassLookup.SecurityManager_classContext_1               avgt   30    680.385 ±  21.665  ns/op
StaticClassLookup.Thread_currentThread_stackTrace_1_className  avgt   30  11179.460 ± 286.293  ns/op
StaticClassLookup.Throwable_stackTrace_0_className             avgt   30  10221.209 ± 176.847  ns/op


Kết luận

  1. Biến thể tốt nhất để sử dụng , khá sạch sẽ và nhanh chóng khủng khiếp.
    Chỉ khả dụng kể từ Java 7 và Android API 26!
 MethodHandles.lookup().lookupClass();
  1. Trong trường hợp bạn cần chức năng này cho Android hoặc Java 6, bạn có thể sử dụng biến thể tốt thứ hai. Nó cũng khá nhanh, nhưng tạo ra một lớp ẩn danh ở mỗi nơi sử dụng :(
 new Object(){}.getClass().getEnclosingClass();
  1. Nếu bạn cần nó ở nhiều nơi và không muốn mã byte của bạn phình to do hàng tấn các lớp ẩn danh - SecurityManagerlà bạn của bạn (tùy chọn tốt thứ ba).

    Nhưng bạn không thể gọi getClassContext()- nó được bảo vệ trong SecurityManagerlớp. Bạn sẽ cần một số lớp trợ giúp như thế này:

 // Helper class
 public final class CallerClassGetter extends SecurityManager
 {
    private static final CallerClassGetter INSTANCE = new CallerClassGetter();
    private CallerClassGetter() {}

    public static Class<?> getCallerClass() {
        return INSTANCE.getClassContext()[1];
    }
 }

 // Usage example:
 class FooBar
 {
    static final Logger LOGGER = LoggerFactory.getLogger(CallerClassGetter.getCallerClass())
 }
  1. Bạn có thể không bao giờ cần sử dụng hai biến thể cuối cùng dựa trên getStackTrace()ngoại lệ hoặc Thread.currentThread(). Rất không hiệu quả và chỉ có thể trả về tên lớp là a String, không phải là Class<*>thể hiện.


PS

Nếu bạn muốn tạo một cá thể logger cho các tiện ích kotlin tĩnh (như tôi :), bạn có thể sử dụng trình trợ giúp này:

import org.slf4j.Logger
import org.slf4j.LoggerFactory

// Should be inlined to get an actual class instead of the one where this helper declared
// Will work only since Java 7 and Android API 26!
@Suppress("NOTHING_TO_INLINE")
inline fun loggerFactoryStatic(): Logger
    = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass())

Ví dụ sử dụng:

private val LOGGER = loggerFactoryStatic()

/**
 * Returns a pseudo-random, uniformly distributed value between the
 * given least value (inclusive) and bound (exclusive).
 *
 * @param min the least value returned
 * @param max the upper bound (exclusive)
 *
 * @return the next value
 * @throws IllegalArgumentException if least greater than or equal to bound
 * @see java.util.concurrent.ThreadLocalRandom.nextDouble(double, double)
 */
fun Random.nextDouble(min: Double = .0, max: Double = 1.0): Double {
    if (min >= max) {
        if (min == max) return max
        LOGGER.warn("nextDouble: min $min > max $max")
        return min
    }
    return nextDouble() * (max - min) + min
}

38

Hướng dẫn này hoạt động tốt:

Thread.currentThread().getStackTrace()[1].getClassName();

5
Hãy cẩn thận, rằng nó có thể thực sự chậm. Tuy nhiên bạn có thể sao chép dán nó.
Gábor Lipták

1
Điều này có thêm lợi ích của việc không phải tạo Đối tượng hoặc Chủ đề mỗi khi bạn sử dụng.
Erel Segal-Halevi

5
@ErelSegalHalevi Nó tạo ra rất nhiều StackTraceEuity trong nền mặc dù :(
Navin

4
Nếu bạn nhìn vào mã nguồn của Thread.getStackTrace()bạn sẽ thấy rằng nó không có gì khác ngoài return (new Exception()).getStackTrace();trường hợp được gọi trên currentThread(). Vì vậy, giải pháp của @count ludwig là cách trực tiếp hơn để đạt được điều tương tự.
T-Bull

34

Bạn có thể làm điều gì đó thực sự ngọt ngào bằng cách sử dụng JNI như thế này:

MyObject.java:

public class MyObject
{
    static
    {
        System.loadLibrary( "classname" );
    }

    public static native String getClassName();

    public static void main( String[] args )
    {
        System.out.println( getClassName() );
    }
}

sau đó:

javac MyObject.java
javah -jni MyObject

sau đó:

MyObject.c:

#include "MyObject.h"

JNIEXPORT jstring JNICALL Java_MyObject_getClassName( JNIEnv *env, jclass cls )
{
    jclass javaLangClass = (*env)->FindClass( env, "java/lang/Class" );
    jmethodID getName = (*env)->GetMethodID( env, javaLangClass, "getName",
        "()Ljava/lang/String;" );
    return (*env)->CallObjectMethod( env, cls, getName );
}

Sau đó biên dịch C thành một thư viện chia sẻ được gọi libclassname.sovà chạy java!

*cười thầm


Tại sao điều này không được xây dựng trong?
ggb667

Khéo léo, và tôi đánh giá cao sự hài hước. Con ruồi trong thuốc mỡ là tên mặc định của hàm C Java_MyObject_getClassNamecó tên được nhúng. Cách xung quanh đó là sử dụng JNI RegisterNatives. Tất nhiên, bạn phải nuôi nó bằng JNI FindClass(env, 'com/example/MyObject'), vì vậy cũng không có chiến thắng nào.
Đổi mới

2
@Renate, vậy toàn bộ câu trả lời này có thực sự là một trò đùa? Nếu vậy, xin vui lòng làm cho nó rất rõ ràng bởi vì bạn biết, chúng tôi có nghĩa vụ phải giúp đỡ người khác, vì vậy chúng ta đừng đẩy những người vô tội vào bẫy.
Stéphane Gourichon

Chà, đó không phải là trò đùa của tôi và tôi đã chỉ ra rằng đó là một trò đùa. Trường hợp sử dụng cho kịch bản này thường là để xác định lớp trong nhật ký. Tước đi tất cả sự phức tạp, nó thường tập trung vào việc thực hiện: private static final String TAG = "MyClass"hoặc private static final String TAG = MyClass.class.getSimpleName();Thứ hai thân thiện hơn với việc đổi tên lớp toàn cầu bằng IDE.
Đổi mới

20

Tôi sử dụng điều này để khởi tạo Log4j Logger ở đầu các lớp học của tôi (hoặc chú thích).

PRO: throwable đã được tải và bạn có thể tiết kiệm tài nguyên bằng cách không sử dụng SecurityManager "IO heavy".

CON: Một số câu hỏi liệu điều này có hoạt động cho tất cả các JVM không.

// Log4j . Logger --- Get class name in static context by creating an anonymous Throwable and 
// getting the top of its stack-trace. 
// NOTE you must use: getClassName() because getClass() just returns StackTraceElement.class 
static final Logger logger = Logger.getLogger(new Throwable() .getStackTrace()[0].getClassName()); 

Tạo lớp ngoại lệ riêng của bạn để JVM wont phiền: jhocr.googlecode.com/svn/trunk/src/main/java/com/googlecode/...
4F2E4A2E

1
Nếu bạn định đề xuất một cái gì đó khủng khiếp như giải pháp này, ít nhất vui lòng liên hệ ưu điểm của bạn với giải pháp tự nhiên bằng cách sử dụng MyClass. Class.getName (), thay vì một giải pháp khủng khiếp khác như lạm dụng SecurityManager.
Søren Boisen

Thêm khuyết điểm: quá dài dòng; chậm (đây thực sự là một điểm nhỏ vì nó chỉ chạy một lần, khi lớp được tải).
toolforger

13

Lạm dụng Trình quản lý bảo mật

System.getSecurityManager().getClassContext()[0].getName();

Hoặc, nếu không được đặt, hãy sử dụng một lớp bên trong mở rộng nó (ví dụ bên dưới được sao chép một cách đáng xấu hổ từ HowTo của Real ):

public static class CurrentClassGetter extends SecurityManager {
    public String getClassName() {
        return getClassContext()[1].getName(); 
    }
}

9

Nếu bạn muốn toàn bộ tên gói với nó, hãy gọi:

String name = MyClass.class.getCanonicalName();

Nếu bạn chỉ muốn phần tử cuối cùng, hãy gọi:

String name = MyClass.class.getSimpleName();

5

Việc sử dụng nguyên văn lớp người gọi giống như MyClass.class.getName()thực sự thực hiện công việc, nhưng dễ bị sao chép / dán lỗi nếu bạn truyền mã này đến nhiều lớp / lớp con nơi bạn cần tên lớp này.

công thức của Tom Hawtin thực tế không tệ, người ta chỉ cần nấu nó đúng cách :)

Trong trường hợp bạn có một lớp cơ sở với một phương thức tĩnh có thể được gọi từ các lớp con và phương thức tĩnh này cần biết lớp trình gọi thực tế, điều này có thể đạt được như sau:

class BaseClass {
  static sharedStaticMethod (String callerClassName, Object... otherArgs) {
    useCallerClassNameAsYouWish (callerClassName);
    // and direct use of 'new Object() { }.getClass().getEnclosingClass().getName()'
    // instead of 'callerClassName' is not going to help here,
    // as it returns "BaseClass"
  }
}

class SubClass1 extends BaseClass {
  static someSubclassStaticMethod () {
    // this call of the shared method is prone to copy/paste errors
    sharedStaticMethod (SubClass1.class.getName(),
                        other_arguments);
    // and this call is safe to copy/paste
    sharedStaticMethod (new Object() { }.getClass().getEnclosingClass().getName(),
                        other_arguments);
  }
}

4

Một giải pháp tái cấu trúc an toàn, cắt và dán an toàn tránh định nghĩa về các lớp đặc biệt dưới đây.

Viết một phương thức tĩnh phục hồi tên lớp cần quan tâm để bao gồm tên lớp trong tên phương thức:

private static String getMyClassName(){
  return MyClass.class.getName();
}

sau đó gọi lại nó trong phương thức tĩnh của bạn:

public static void myMethod(){
  Tracer.debug(getMyClassName(), "message");
}

Tái cấu trúc an toàn được đưa ra bằng cách tránh sử dụng chuỗi, cắt và dán an toàn được cấp vì nếu bạn cắt và dán phương thức người gọi, bạn sẽ không tìm thấy getMyClassName () trong lớp "MyClass2" đích, vì vậy bạn sẽ buộc phải xác định lại và cập nhật nó.


3

Vì câu hỏi Một cái gì đó như `this. Class` thay vì` ClassName. Class`? được đánh dấu là một bản sao cho cái này (có thể tranh cãi vì câu hỏi đó là về lớp chứ không phải tên lớp), tôi đang đăng câu trả lời ở đây:

class MyService {
    private static Class thisClass = MyService.class;
    // or:
    //private static Class thisClass = new Object() { }.getClass().getEnclosingClass();
    ...
    static void startService(Context context) {
        Intent i = new Intent(context, thisClass);
        context.startService(i);
    }
}

Điều quan trọng là định nghĩa thisClassriêng tư vì:
1) nó không được kế thừa: các lớp dẫn xuất phải tự xác định thisClasshoặc tạo thông báo lỗi
2) các tham chiếu từ các lớp khác nên được thực hiện ClassName.classthay vì ClassName.thisClass.

Với thisClassđịnh nghĩa, truy cập vào tên lớp trở thành:

thisClass.getName()

1

Tôi cần tên lớp trong các phương thức tĩnh của nhiều lớp vì vậy tôi đã triển khai Lớp JavaUtil với phương thức sau:

public static String getClassName() {
    String className = Thread.currentThread().getStackTrace()[2].getClassName();
    int lastIndex = className.lastIndexOf('.');
    return className.substring(lastIndex + 1);
}

Hy vọng nó sẽ giúp!


1
Không chỉ tệ khi sử dụng điều này vì phép thuật số 2 (có thể dễ dàng dẫn đến một NullPulumException), mà bạn còn phụ thuộc rất nhiều vào độ chính xác của máy ảo. Từ javadoc của phương thức: * Một số máy ảo có thể, trong một số trường hợp, có thể bỏ qua một hoặc nhiều khung ngăn xếp từ theo dõi ngăn xếp. Trong trường hợp cực đoan, một máy ảo không có thông tin theo dõi ngăn xếp liên quan đến luồng này được phép trả về một mảng có độ dài bằng không từ phương thức này. *
Shotgun

0

Tôi đã sử dụng hai cách tiếp cận này cho cả hai staticnon statickịch bản:

Lớp chính:

//For non static approach
public AndroidLogger(Object classObject) {
    mClassName = classObject.getClass().getSimpleName();
}

//For static approach
public AndroidLogger(String className) {
    mClassName = className;
}

Cách cung cấp tên lớp:

cách không tĩnh:

private AndroidLogger mLogger = new AndroidLogger(this);

Cách tĩnh:

private static AndroidLogger mLogger = new AndroidLogger(Myclass.class.getSimpleName());

-1

Nếu bạn đang sử dụng sự phản chiếu, bạn có thể lấy đối tượng Phương thức và sau đó:

method.getDeclaringClass().getName()

Để có được Phương thức, bạn có thể sử dụng:

Class<?> c = Class.forName("class name");
Method  method = c.getDeclaredMethod ("method name", parameterTypes)

3
Và làm thế nào bạn sẽ biết thế nào là: "tên lớp"? :)
alfasin

1
Đã Class.forName("class name")cung cấp cho bạn một lớp học. Tại sao bạn muốn lấy nó thông qua Phương thức?
Serge Irisov
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.