Phản ánh là gì và tại sao nó hữu ích?


2124

Phản ánh là gì và tại sao nó hữu ích?

Tôi đặc biệt quan tâm đến Java, nhưng tôi cho rằng các nguyên tắc là giống nhau trong bất kỳ ngôn ngữ nào.


9
Đối với tôi đó là một cách để có được các tên lớp trong thời gian chạy và tạo các đối tượng của lớp đó.
Nabin

64
bởi vì đây là một câu hỏi phổ biến mà tôi muốn chỉ ra rằng sự phản chiếu (Không có chú thích) sẽ là công cụ cuối cùng bạn tìm đến khi giải quyết vấn đề. Tôi sử dụng nó và yêu thích nó, nhưng nó loại bỏ tất cả các lợi thế của việc gõ tĩnh của Java. Nếu bạn cần nó, hãy cách ly nó với diện tích nhỏ nhất có thể (Một phương thức hoặc một lớp). Việc sử dụng nó trong các thử nghiệm sẽ dễ chấp nhận hơn so với mã sản xuất. Với các chú thích, nó sẽ ổn - Điểm chính là không chỉ định tên lớp hoặc tên phương thức là "Chuỗi" nếu bạn có thể tránh nó.
Bill K


4
Ngoài nhận xét của @ BillK: Sự phản chiếu rất mạnh mẽ, tôi gọi đó là phép thuật. Với sức mạnh lớn đến trách nhiệm lớn. Chỉ sử dụng nó nếu bạn biết những gì bạn đang làm.
MC Hoàng đế

Bạn có thể tránh được nhiều cạm bẫy liên quan đến sự phản chiếu bằng Manifold @Jailbreak. Nó cung cấp quyền truy cập trực tiếp, an toàn kiểu vào các trường riêng, phương thức, v.v. Hãy để trình biên dịch Java xác minh an toàn mã của bạn và để Manifold tạo quyền truy cập phản ánh cơ bản cho bạn. Tìm hiểu thêm: manifold.systems/docs.html#type-safe-reflection
Scott

Câu trả lời:


1716

Sự phản chiếu tên được sử dụng để mô tả mã có thể kiểm tra mã khác trong cùng hệ thống (hoặc chính nó).

Ví dụ: giả sử bạn có một đối tượng thuộc loại không xác định trong Java và bạn muốn gọi một phương thức 'doS Something' trên nó nếu nó tồn tại. Hệ thống gõ tĩnh của Java không thực sự được thiết kế để hỗ trợ điều này trừ khi đối tượng tuân theo giao diện đã biết, nhưng bằng cách sử dụng sự phản chiếu, mã của bạn có thể nhìn vào đối tượng và tìm hiểu xem nó có phương thức gọi là 'doS Something' không và sau đó gọi nó nếu bạn muốn.

Vì vậy, để cung cấp cho bạn một ví dụ mã về điều này trong Java (hãy tưởng tượng đối tượng trong câu hỏi là foo):

Method method = foo.getClass().getMethod("doSomething", null);
method.invoke(foo, null);

Một trường hợp sử dụng rất phổ biến trong Java là việc sử dụng với các chú thích. Ví dụ, JUnit 4 sẽ sử dụng sự phản chiếu để xem qua các lớp của bạn để biết các phương thức được gắn thẻ với chú thích @Test và sau đó sẽ gọi chúng khi chạy thử nghiệm đơn vị.

Có một số ví dụ phản ánh tốt để giúp bạn bắt đầu tại http://docs.oracle.com/javase/tutorial/reflect/index.html

Và cuối cùng, vâng, các khái niệm này khá giống nhau trong các ngôn ngữ được gõ tĩnh khác hỗ trợ sự phản chiếu (như C #). Trong các ngôn ngữ được gõ động, trường hợp sử dụng được mô tả ở trên là không cần thiết (vì trình biên dịch sẽ cho phép bất kỳ phương thức nào được gọi trên bất kỳ đối tượng nào, không chạy trong thời gian chạy nếu nó không tồn tại), nhưng trường hợp thứ hai tìm kiếm các phương thức được đánh dấu hoặc làm việc theo một cách nhất định vẫn còn phổ biến.

Cập nhật từ một bình luận:

Khả năng kiểm tra mã trong hệ thống và xem các loại đối tượng không phải là sự phản chiếu, mà là Kiểu nội quan. Phản xạ sau đó là khả năng thực hiện sửa đổi trong thời gian chạy bằng cách sử dụng nội quan. Sự khác biệt là cần thiết ở đây vì một số ngôn ngữ hỗ trợ hướng nội, nhưng không hỗ trợ sự phản chiếu. Một ví dụ như vậy là C ++


32
Bạn có thể giải thích tầm quan trọng của tham số null đó trong dòng này Phương thức phương thức = foo.getClass (). getMethod ("doS Something", null);
Krsna Chaitanya

54
Null cho biết không có tham số nào được truyền cho phương thức foo. Xem docs.oracle.com/javase/6/docs/api/java/lang/reflect/iêu , java.lang.Object ...) để biết chi tiết.
Matt Sheppard

791
Chỉ cần làm rõ vì điều này có rất nhiều upvote. Khả năng kiểm tra mã trong hệ thống và xem các loại đối tượng không phải là sự phản chiếu, mà là Kiểu nội quan. Phản xạ sau đó là khả năng thực hiện sửa đổi trong thời gian chạy bằng cách sử dụng nội quan. Sự khác biệt là cần thiết ở đây vì một số ngôn ngữ hỗ trợ hướng nội, nhưng không hỗ trợ sự phản chiếu. Một ví dụ như vậy là C ++.
bigtunacan

39
Tôi thích sự phản chiếu nhưng nếu bạn có quyền kiểm soát mã thì sử dụng sự phản chiếu như được chỉ định trong câu trả lời này là không hợp lý và do đó lạm dụng - Bạn nên sử dụng Kiểu Introspection (instanceof) và các loại mạnh. Nếu có bất kỳ cách nào ngoài sự phản ánh để làm một cái gì đó, đó là cách nó nên được thực hiện. Phản xạ gây ra đau lòng nghiêm trọng vì bạn mất tất cả các lợi thế của việc sử dụng một ngôn ngữ gõ tĩnh. Nếu bạn cần nó, bạn cần nó, tuy nhiên ngay cả sau đó tôi sẽ xem xét một giải pháp đóng gói sẵn như Spring hoặc một cái gì đó gói gọn hoàn toàn sự phản chiếu cần thiết - IE: hãy để người khác đau đầu.
Bill K

6
@bigtunacan Bạn lấy thông tin đó từ đâu? Tôi thấy thuật ngữ "phản chiếu" được sử dụng trong tài liệu Java chính thức từ Oracle để mô tả không chỉ khả năng thực hiện thay đổi khi chạy mà còn có khả năng nhìn thấy loại đối tượng. Chưa kể rằng hầu hết cái gọi là "loại mẫn" lớp học có liên quan (ví dụ: Method, Constructor, Modifier, Field, Member, về cơ bản có vẻ như tất cả ngoại trừ Class) nằm trong java.lang.*reflect*gói. Có lẽ khái niệm "phản chiếu" một cách toàn diện bao gồm cả "hướng nội loại" và sửa đổi trong thời gian chạy?
RestInPeace

246

Reflection là khả năng của ngôn ngữ để kiểm tra và tự động gọi các lớp, phương thức, thuộc tính, v.v. trong thời gian chạy.

Ví dụ, tất cả các đối tượng trong Java đều có phương thức getClass(), cho phép bạn xác định lớp của đối tượng ngay cả khi bạn không biết nó vào thời gian biên dịch (ví dụ: nếu bạn khai báo nó là một Object) - điều này có vẻ tầm thường, nhưng sự phản chiếu như vậy là không thể trong các ngôn ngữ ít năng động hơn như C++. Sử dụng nâng cao hơn cho phép bạn liệt kê và gọi các phương thức, hàm tạo, v.v.

Sự phản chiếu rất quan trọng vì nó cho phép bạn viết các chương trình không phải "biết" mọi thứ trong thời gian biên dịch, khiến chúng trở nên năng động hơn, vì chúng có thể được gắn với nhau khi chạy. Mã có thể được viết dựa trên các giao diện đã biết, nhưng các lớp thực tế được sử dụng có thể được khởi tạo bằng cách sử dụng sự phản chiếu từ các tệp cấu hình.

Rất nhiều khung hiện đại sử dụng sự phản chiếu rộng rãi vì lý do này. Hầu hết các ngôn ngữ hiện đại khác cũng sử dụng sự phản chiếu và trong các ngôn ngữ kịch bản (như Python), chúng thậm chí còn được tích hợp chặt chẽ hơn, vì nó cảm thấy tự nhiên hơn trong mô hình lập trình chung của các ngôn ngữ đó.


2
Vì vậy, nói cách khác, bạn có thể tạo một cá thể từ tên đủ điều kiện của nó và trình biên dịch sẽ không phàn nàn về nó (vì nói rằng bạn chỉ sử dụng một Chuỗi cho tên lớp). Sau đó, vào thời gian chạy, nếu lớp đó không có mặt, bạn sẽ có một ngoại lệ. Bạn loại bỏ qua trình biên dịch trong trường hợp này. Bạn có thể cho tôi một số trường hợp sử dụng cụ thể cho việc này? Tôi chỉ không thể hình dung khi tôi sẽ chọn nó.
Fernando Gabrieli

3
@FernandoGabrieli trong khi sự thật là rất dễ tạo ra lỗi thời gian chạy với sự phản chiếu, nó cũng hoàn toàn có thể sử dụng sự phản chiếu mà không gặp rủi ro ngoại lệ thời gian chạy. Như được gợi ý trong câu trả lời của tôi, việc sử dụng phổ biến sự phản chiếu là dành cho các thư viện hoặc khung, mà rõ ràng không thể biết cấu trúc của ứng dụng tại thời điểm biên dịch, vì chúng được biên dịch tách biệt khỏi ứng dụng. Bất kỳ thư viện nào sử dụng "mã theo quy ước" đều có khả năng sử dụng sự phản chiếu, nhưng không nhất thiết phải sử dụng chuỗi ma thuật.
Liedman

C++không có thông tin loại thời gian chạy. RTTI
Ayxan

114

Một trong những cách sử dụng phản chiếu yêu thích của tôi là phương thức kết xuất Java bên dưới. Nó lấy bất kỳ đối tượng nào làm tham số và sử dụng API phản chiếu Java để in ra mọi tên và giá trị của trường.

import java.lang.reflect.Array;
import java.lang.reflect.Field;

public static String dump(Object o, int callCount) {
    callCount++;
    StringBuffer tabs = new StringBuffer();
    for (int k = 0; k < callCount; k++) {
        tabs.append("\t");
    }
    StringBuffer buffer = new StringBuffer();
    Class oClass = o.getClass();
    if (oClass.isArray()) {
        buffer.append("\n");
        buffer.append(tabs.toString());
        buffer.append("[");
        for (int i = 0; i < Array.getLength(o); i++) {
            if (i < 0)
                buffer.append(",");
            Object value = Array.get(o, i);
            if (value.getClass().isPrimitive() ||
                    value.getClass() == java.lang.Long.class ||
                    value.getClass() == java.lang.String.class ||
                    value.getClass() == java.lang.Integer.class ||
                    value.getClass() == java.lang.Boolean.class
                    ) {
                buffer.append(value);
            } else {
                buffer.append(dump(value, callCount));
            }
        }
        buffer.append(tabs.toString());
        buffer.append("]\n");
    } else {
        buffer.append("\n");
        buffer.append(tabs.toString());
        buffer.append("{\n");
        while (oClass != null) {
            Field[] fields = oClass.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                buffer.append(tabs.toString());
                fields[i].setAccessible(true);
                buffer.append(fields[i].getName());
                buffer.append("=");
                try {
                    Object value = fields[i].get(o);
                    if (value != null) {
                        if (value.getClass().isPrimitive() ||
                                value.getClass() == java.lang.Long.class ||
                                value.getClass() == java.lang.String.class ||
                                value.getClass() == java.lang.Integer.class ||
                                value.getClass() == java.lang.Boolean.class
                                ) {
                            buffer.append(value);
                        } else {
                            buffer.append(dump(value, callCount));
                        }
                    }
                } catch (IllegalAccessException e) {
                    buffer.append(e.getMessage());
                }
                buffer.append("\n");
            }
            oClass = oClass.getSuperclass();
        }
        buffer.append(tabs.toString());
        buffer.append("}\n");
    }
    return buffer.toString();
}

8
Callcount nên được đặt thành gì?
Tom

8
Tôi đã có một Ngoại lệ trong luồng "AWT-EventQueue-0" java.lang.StackOverflowError khi tôi chạy nó.
Tom

3
@Tom callCountnên được đặt thành không. Giá trị của nó được sử dụng để xác định có bao nhiêu tab trước mỗi dòng đầu ra: mỗi lần kết xuất cần đổ một "tiểu dự án", đầu ra sẽ được in dưới dạng lồng trong cha mẹ. Phương pháp đó chứng tỏ hữu ích khi được bọc trong một cái khác. Hãy xem xét printDump(Object obj){ System.out.println(dump(obj, 0)); }.
fny

1
Java.lang.StackOverflowError có thể được tạo trong trường hợp tham chiếu vòng tròn, vì đệ quy không được kiểm tra: buffer.append (dump (value, callCount))
Arnaud P

4
Bạn có thể đặc biệt phát hành mã của mình sang Miền công cộng không?
stolsvik

84

Công dụng của suy ngẫm

Sự phản chiếu thường được sử dụng bởi các chương trình yêu cầu khả năng kiểm tra hoặc sửa đổi hành vi thời gian chạy của các ứng dụng đang chạy trong máy ảo Java. Đây là một tính năng tương đối tiên tiến và chỉ nên được sử dụng bởi các nhà phát triển có hiểu biết sâu sắc về các nguyên tắc cơ bản của ngôn ngữ. Với suy nghĩ đó, phản xạ là một kỹ thuật mạnh mẽ và có thể cho phép các ứng dụng thực hiện các hoạt động mà nếu không thì không thể.

Tính năng mở rộng

Một ứng dụng có thể sử dụng các lớp bên ngoài do người dùng định nghĩa bằng cách tạo các thể hiện của các đối tượng mở rộng bằng cách sử dụng các tên đủ điều kiện của chúng. Trình duyệt lớp và môi trường phát triển trực quan Một trình duyệt lớp cần có khả năng liệt kê các thành viên của lớp. Các môi trường phát triển trực quan có thể được hưởng lợi từ việc sử dụng thông tin loại có sẵn trong phản chiếu để hỗ trợ nhà phát triển viết mã chính xác. Trình gỡ lỗi và Công cụ kiểm tra Trình gỡ lỗi cần có khả năng kiểm tra các thành viên tư nhân trong các lớp học. Các khai thác kiểm tra có thể sử dụng sự phản chiếu để gọi một cách có hệ thống các API tập hợp có thể khám phá được xác định trên một lớp, để đảm bảo mức độ bao phủ mã cao trong một bộ kiểm tra.

Hạn chế của sự phản ánh

Phản xạ là mạnh mẽ, nhưng không nên được sử dụng một cách bừa bãi. Nếu có thể thực hiện một thao tác mà không sử dụng sự phản chiếu, thì tốt nhất là tránh sử dụng nó. Cần lưu ý các mối quan tâm sau đây khi truy cập mã thông qua sự phản chiếu.

  • Chi phí hoạt động

Vì sự phản chiếu liên quan đến các loại được giải quyết động, nên việc tối ưu hóa máy ảo Java nhất định có thể được thực hiện. Do đó, các hoạt động phản chiếu có hiệu suất chậm hơn so với các đối tác không phản chiếu của chúng và nên tránh trong các phần của mã được gọi là thường xuyên trong các ứng dụng nhạy cảm với hiệu suất.

  • Hạn chế bảo mật

Sự phản chiếu đòi hỏi phải có sự cho phép trong thời gian chạy mà có thể không có khi chạy dưới trình quản lý bảo mật. Đây là một xem xét quan trọng đối với mã phải chạy trong bối cảnh bảo mật bị hạn chế, chẳng hạn như trong Applet.

  • Tiếp xúc với nội bộ

Vì sự phản chiếu cho phép mã thực hiện các hoạt động bất hợp pháp trong mã không phản chiếu, chẳng hạn như truy cập các trường và phương thức riêng tư, việc sử dụng phản xạ có thể dẫn đến các tác dụng phụ không mong muốn, có thể khiến mã bị rối loạn và có thể phá hủy tính di động. Mã phản chiếu phá vỡ sự trừu tượng và do đó có thể thay đổi hành vi với các bản nâng cấp của nền tảng.

nguồn: API phản chiếu


44

Sự phản chiếu là một cơ chế chính để cho phép một ứng dụng hoặc khung làm việc với mã mà thậm chí chưa được viết!

Lấy ví dụ về tệp web.xml điển hình của bạn. Điều này sẽ chứa một danh sách các phần tử servlet, chứa các phần tử lớp servlet lồng nhau. Bộ chứa servlet sẽ xử lý tệp web.xml và tạo một phiên bản mới của mỗi lớp servlet thông qua sự phản chiếu.

Một ví dụ khác là API Java cho phân tích cú pháp XML (JAXP) . Trong đó nhà cung cấp trình phân tích cú pháp XML được 'cắm vào' thông qua các thuộc tính hệ thống nổi tiếng, được sử dụng để xây dựng các cá thể mới thông qua sự phản chiếu.

Và cuối cùng, ví dụ toàn diện nhất là Spring sử dụng sự phản chiếu để tạo ra các hạt của nó và cho việc sử dụng rất nhiều proxy của nó


36

Không phải mọi ngôn ngữ đều hỗ trợ sự phản chiếu nhưng các nguyên tắc thường giống nhau trong các ngôn ngữ hỗ trợ nó.

Reflection là khả năng "phản ánh" về cấu trúc chương trình của bạn. Hoặc cụ thể hơn. Để xem xét các đối tượng và các lớp bạn có và lập trình lấy lại thông tin về các phương thức, các trường và giao diện mà chúng thực hiện. Bạn cũng có thể nhìn vào những thứ như chú thích.

Nó hữu ích trong rất nhiều tình huống. Ở mọi nơi bạn muốn có thể tự động cắm các lớp vào mã của mình. Rất nhiều người lập bản đồ quan hệ đối tượng sử dụng sự phản chiếu để có thể khởi tạo các đối tượng từ cơ sở dữ liệu mà không cần biết trước những đối tượng họ sẽ sử dụng. Kiến trúc trình cắm là một nơi khác mà sự phản chiếu là hữu ích. Khả năng tải mã động và xác định xem có loại nào triển khai giao diện phù hợp để sử dụng làm plugin hay không là điều quan trọng trong các tình huống đó.


Tôi cần khởi tạo các đối tượng dựa trên dữ liệu có trong DB. Tôi tin rằng đây là những gì bạn đang nói về. Mã mẫu sẽ giúp tôi rất nhiều. Cảm ơn trước.
Atom

34

Sự phản chiếu cho phép khởi tạo các đối tượng mới, gọi các phương thức và các hoạt động get / set trên các biến lớp một cách linh hoạt trong thời gian chạy mà không cần có kiến ​​thức trước về việc thực hiện nó.

Class myObjectClass = MyObject.class;
Method[] method = myObjectClass.getMethods();

//Here the method takes a string parameter if there is no param, put null.
Method method = aClass.getMethod("method_name", String.class); 

Object returnValue = method.invoke(null, "parameter-value1");

Trong ví dụ trên, tham số null là đối tượng bạn muốn gọi phương thức trên. Nếu phương thức là tĩnh, bạn cung cấp null. Nếu phương thức không tĩnh, thì trong khi gọi, bạn cần cung cấp một thể hiện MyObject hợp lệ thay vì null.

Reflection cũng cho phép bạn truy cập thành viên / phương thức riêng tư của một lớp:

public class A{

  private String str= null;

  public A(String str) {
  this.str= str;
  }
}

.

A obj= new A("Some value");

Field privateStringField = A.class.getDeclaredField("privateString");

//Turn off access check for this field
privateStringField.setAccessible(true);

String fieldValue = (String) privateStringField.get(obj);
System.out.println("fieldValue = " + fieldValue);
  • Để kiểm tra các lớp (còn được gọi là hướng nội), bạn không cần nhập gói phản chiếu ( java.lang.reflect). Lớp siêu dữ liệu có thể được truy cập thông qua java.lang.Class.

Reflection là một API rất mạnh nhưng nó có thể làm chậm ứng dụng nếu sử dụng quá mức, vì nó giải quyết tất cả các loại khi chạy.


21

Java Reflection khá mạnh mẽ và có thể rất hữu ích. Java Reflection cho phép kiểm tra các lớp, giao diện, trường và phương thức trong thời gian chạy mà không cần biết tên của các lớp, phương thức, v.v. tại thời điểm biên dịch. Cũng có thể khởi tạo các đối tượng mới, gọi các phương thức và nhận / thiết lập các giá trị trường bằng cách sử dụng sự phản chiếu.

Một ví dụ về Reflection Java nhanh để cho bạn thấy việc sử dụng sự phản chiếu trông như thế nào:

Method[] methods = MyObject.class.getMethods();

    for(Method method : methods){
        System.out.println("method = " + method.getName());
    }

Ví dụ này thu được đối tượng Class từ lớp có tên MyObject. Sử dụng đối tượng lớp, ví dụ này nhận được một danh sách các phương thức trong lớp đó, lặp lại các phương thức và in ra tên của chúng.

Chính xác làm thế nào tất cả điều này hoạt động được giải thích ở đây

Chỉnh sửa : Sau gần 1 năm tôi đang chỉnh sửa câu trả lời này vì trong khi đọc về sự phản chiếu, tôi đã sử dụng thêm Reflection.

  • Spring sử dụng cấu hình bean như:


<bean id="someID" class="com.example.Foo">
    <property name="someField" value="someValue" />
</bean>

Khi bối cảnh Spring xử lý phần tử <bean> này, nó sẽ sử dụng Class.forName (String) với đối số "com.example.Foo" để khởi tạo Class đó.

Sau đó, nó sẽ lại sử dụng sự phản chiếu để có được bộ setter thích hợp cho phần tử <property> và đặt giá trị của nó thành giá trị được chỉ định.

  • Junit sử dụng Reflection đặc biệt để thử nghiệm các phương pháp Riêng tư / Được bảo vệ.

Đối với phương thức riêng tư,

Method method = targetClass.getDeclaredMethod(methodName, argClasses);
method.setAccessible(true);
return method.invoke(targetObject, argObjects);

Đối với các lĩnh vực tư nhân,

Field field = targetClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);

21

Thí dụ:

Lấy ví dụ một ứng dụng từ xa cung cấp cho ứng dụng của bạn một đối tượng mà bạn có được bằng cách sử dụng Phương thức API của chúng. Bây giờ dựa trên đối tượng bạn có thể cần phải thực hiện một số tính toán.

Nhà cung cấp đảm bảo rằng đối tượng có thể có 3 loại và chúng tôi cần thực hiện tính toán dựa trên loại đối tượng nào.

Vì vậy, chúng tôi có thể triển khai trong 3 lớp, mỗi lớp chứa một logic khác nhau. Rõ ràng thông tin đối tượng có sẵn trong thời gian chạy để bạn không thể mã tĩnh để thực hiện tính toán do đó phản xạ được sử dụng để khởi tạo đối tượng của lớp mà bạn yêu cầu thực hiện tính toán dựa trên đối tượng nhận được từ nhà cung cấp.


Tôi cần một cái gì đó tương tự .. Một ví dụ sẽ giúp tôi rất nhiều vì tôi chưa quen với các khái niệm phản chiếu ..
Atom

2
Tôi bối rối: bạn không thể sử dụng instanceofđể xác định loại đối tượng trong thời gian chạy?
ndm13

19

Ví dụ đơn giản cho sự phản ánh. Trong một ván cờ, bạn không biết người dùng sẽ di chuyển cái gì khi chạy. sự phản chiếu có thể được sử dụng để gọi các phương thức đã được thực hiện trong thời gian chạy:

public class Test {

    public void firstMoveChoice(){
        System.out.println("First Move");
    } 
    public void secondMOveChoice(){
        System.out.println("Second Move");
    }
    public void thirdMoveChoice(){
        System.out.println("Third Move");
    }

    public static void main(String[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { 
        Test test = new Test();
        Method[] method = test.getClass().getMethods();
        //firstMoveChoice
        method[0].invoke(test, null);
        //secondMoveChoice
        method[1].invoke(test, null);
        //thirdMoveChoice
        method[2].invoke(test, null);
    }

}

18

Reflection là một API được sử dụng để kiểm tra hoặc sửa đổi hành vi của các phương thức, lớp, giao diện khi chạy.

  1. Các lớp cần thiết cho sự phản ánh được cung cấp dưới java.lang.reflect package.
  2. Sự phản chiếu cung cấp cho chúng ta thông tin về lớp mà một đối tượng thuộc về và cả các phương thức của lớp đó có thể được thực thi bằng cách sử dụng đối tượng.
  3. Thông qua sự phản chiếu, chúng ta có thể gọi các phương thức trong thời gian chạy bất kể bộ xác định truy cập được sử dụng với chúng.

Các gói java.langjava.lang.reflectcung cấp các lớp để phản ánh java.

Sự phản chiếu có thể được sử dụng để có được thông tin về -

  1. Lớp Các getClass()phương pháp được sử dụng để có được tên của lớp mà một đối tượng thuộc.

  2. Constructors Các getConstructors()phương pháp được sử dụng để có được các nhà thầu nào của lớp mà một đối tượng thuộc.

  3. Phương pháp Các getMethods()phương pháp được sử dụng để có được các phương thức public của lớp mà một đối tượng thuộc.

Các API Reflection chủ yếu được sử dụng trong:

IDE (Môi trường phát triển tích hợp), ví dụ như Eclipse, MyEclipse, NetBeans, v.v.
Trình gỡ lỗi và Công cụ kiểm tra, v.v.

Ưu điểm của việc sử dụng Reflection:

Các tính năng mở rộng: Một ứng dụng có thể sử dụng các lớp bên ngoài do người dùng định nghĩa bằng cách tạo các thể hiện của các đối tượng mở rộng bằng cách sử dụng các tên đủ điều kiện của chúng.

Công cụ gỡ lỗi và kiểm tra: Trình gỡ lỗi sử dụng thuộc tính phản chiếu để kiểm tra các thành viên tư nhân trên các lớp.

Hạn chế:

Chi phí hoạt động : Các hoạt động phản xạ có hiệu suất chậm hơn so với các đối tác không phản chiếu của chúng và nên tránh trong các phần của mã được gọi là thường xuyên trong các ứng dụng nhạy cảm với hiệu suất.

Phơi bày của Quốc tế: Mã phản chiếu phá vỡ sự trừu tượng và do đó có thể thay đổi hành vi với các nâng cấp của nền tảng.

Tham chiếu: Phản chiếu Java javarevisited.blogspot.in


4
Tôi sẽ thêm vào nhược điểm " Nó phá vỡ tái cấu trúc ". Đối với tôi đó là lý do chính để tránh phản xạ càng nhiều càng tốt.
SantiBailors

Vì vậy, nó cho phép chúng tôi (ví dụ), kiểm tra các lớp chúng tôi có (cho dù chúng tôi có phiên bản của chúng hay không), đúng không? Ý tôi là, lấy các phương thức hoặc hàm tạo của chúng và sử dụng chúng để tạo các thể hiện mới / gọi chúng. Tại sao chúng ta nói "thay đổi hành vi chương trình" nếu hành vi đã có nhưng với mã khác nhau? Tại sao lại gọi là "phản chiếu"? Cảm ơn
Fernando Gabrieli

15

Theo sự hiểu biết của tôi:

Reflection cho phép lập trình viên truy cập các thực thể trong chương trình một cách linh hoạt. tức là trong khi mã hóa một ứng dụng nếu lập trình viên không biết về một lớp hoặc các phương thức của nó, anh ta có thể sử dụng lớp đó một cách linh hoạt (trong thời gian chạy) bằng cách sử dụng sự phản chiếu.

Nó thường được sử dụng trong các tình huống trong đó một tên lớp thay đổi thường xuyên. Nếu một tình huống như vậy phát sinh, thì lập trình viên sẽ phải viết lại ứng dụng và thay đổi tên của lớp một lần nữa.

Thay vào đó, bằng cách sử dụng sự phản chiếu, cần phải lo lắng về một tên lớp có thể thay đổi.


15

Reflection là một tập hợp các chức năng cho phép bạn truy cập thông tin thời gian chạy của chương trình và sửa đổi hành vi của nó (với một số hạn chế).

Nó hữu ích vì nó cho phép bạn thay đổi hành vi thời gian chạy tùy thuộc vào thông tin meta của chương trình của bạn, nghĩa là bạn có thể kiểm tra kiểu trả về của hàm và thay đổi cách bạn xử lý tình huống.

Ví dụ, trong C #, bạn có thể tải một cụm (một) trong thời gian chạy kiểm tra nó, điều hướng qua các lớp và thực hiện các hành động theo những gì bạn tìm thấy. Nó cũng cho phép bạn tạo một thể hiện của một lớp trong thời gian chạy, gọi phương thức của nó, v.v.

Nó có thể hữu ích ở đâu? Không hữu ích mọi lúc nhưng cho các tình huống cụ thể. Ví dụ: bạn có thể sử dụng nó để lấy tên của lớp cho mục đích ghi nhật ký, để tự động tạo trình xử lý cho các sự kiện theo những gì được chỉ định trên tệp cấu hình, v.v.


11

Tôi chỉ muốn thêm một số điểm cho tất cả những gì đã được liệt kê.

Với API Reflection, bạn có thể viết toString()phương thức phổ quát cho bất kỳ đối tượng nào.

Nó rất hữu ích trong việc gỡ lỗi.

Đây là một số ví dụ:

class ObjectAnalyzer {

   private ArrayList<Object> visited = new ArrayList<Object>();

   /**
    * Converts an object to a string representation that lists all fields.
    * @param obj an object
    * @return a string with the object's class name and all field names and
    * values
    */
   public String toString(Object obj) {
      if (obj == null) return "null";
      if (visited.contains(obj)) return "...";
      visited.add(obj);
      Class cl = obj.getClass();
      if (cl == String.class) return (String) obj;
      if (cl.isArray()) {
         String r = cl.getComponentType() + "[]{";
         for (int i = 0; i < Array.getLength(obj); i++) {
            if (i > 0) r += ",";
            Object val = Array.get(obj, i);
            if (cl.getComponentType().isPrimitive()) r += val;
            else r += toString(val);
         }
         return r + "}";
      }

      String r = cl.getName();
      // inspect the fields of this class and all superclasses
      do {
         r += "[";
         Field[] fields = cl.getDeclaredFields();
         AccessibleObject.setAccessible(fields, true);
         // get the names and values of all fields
         for (Field f : fields) {
            if (!Modifier.isStatic(f.getModifiers())) {
               if (!r.endsWith("[")) r += ",";
               r += f.getName() + "=";
               try {
                  Class t = f.getType();
                  Object val = f.get(obj);
                  if (t.isPrimitive()) r += val;
                  else r += toString(val);
               } catch (Exception e) {
                  e.printStackTrace();
               }
            }
         }
         r += "]";
         cl = cl.getSuperclass();
      } while (cl != null);

      return r;
   }    
}

11

Phản ánh là để cho đối tượng nhìn thấy sự xuất hiện của họ. Lập luận này dường như không có gì để làm với sự phản ánh. Trên thực tế, đây là khả năng "tự xác định".

Reflection chính nó là một từ cho các ngôn ngữ như vậy thiếu khả năng tự hiểu biết và tự cảm nhận như Java và C #. Bởi vì họ không có khả năng tự hiểu biết, khi chúng ta muốn quan sát nó trông như thế nào, chúng ta phải có một điều khác để phản ánh về việc nó trông như thế nào. Các ngôn ngữ động tuyệt vời như Ruby và Python có thể cảm nhận được sự phản chiếu của chính chúng mà không cần sự giúp đỡ của các cá nhân khác. Chúng ta có thể nói rằng đối tượng của Java không thể cảm nhận được nó trông như thế nào nếu không có gương, là đối tượng của lớp phản chiếu, nhưng một đối tượng trong Python có thể cảm nhận nó mà không cần gương. Vì vậy, đó là lý do tại sao chúng ta cần sự phản chiếu trong Java.


loại (), isinstance (), có thể gọi (), dir () và getattr (). .... đây là những cuộc gọi phản xạ pythonic
AnthonyJClink

9

Từ trang tài liệu java

java.lang.reflectgói cung cấp các lớp và giao diện để có được thông tin phản ánh về các lớp và các đối tượng. Sự phản chiếu cho phép truy cập theo chương trình vào thông tin về các trường, phương thức và hàm tạo của các lớp được tải và việc sử dụng các trường, phương thức và hàm tạo được phản ánh để hoạt động trên các đối tác cơ bản của chúng, trong các hạn chế bảo mật.

AccessibleObjectcho phép ngăn chặn kiểm tra truy cập nếu cần thiết ReflectPermissioncó sẵn.

Các lớp trong gói này, cùng với java.lang.Classcác ứng dụng chứa như trình gỡ lỗi, trình thông dịch, trình kiểm tra đối tượng, trình duyệt lớp và các dịch vụ như Object SerializationJavaBeans cần truy cập vào các thành viên công khai của đối tượng đích (dựa trên lớp thời gian chạy của nó) hoặc các thành viên được khai báo bởi một lớp nhất định

Nó bao gồm các chức năng sau.

  1. Lấy các đối tượng Class,
  2. Kiểm tra các thuộc tính của một lớp (các trường, các phương thức, các hàm tạo),
  3. Đặt và nhận giá trị trường,
  4. Gọi phương thức,
  5. Tạo các thể hiện mới của các đối tượng.

Có một cái nhìn vào liên kết tài liệu này cho các phương thức được hiển thị bởi Classlớp.

Từ bài viết này (của Dennis Sosnoski, Chủ tịch, Sosnoski Software Solutions, Inc) và bài viết này (bảo mật-khám phá pdf):

Tôi có thể thấy những hạn chế đáng kể so với việc sử dụng Reflection

Người dùng phản ánh:

  1. Nó cung cấp cách rất linh hoạt của các thành phần chương trình liên kết động
  2. Nó rất hữu ích để tạo các thư viện làm việc với các đối tượng theo những cách rất chung chung

Hạn chế của sự phản ánh:

  1. Sự phản chiếu chậm hơn nhiều so với mã trực tiếp khi được sử dụng để truy cập trường và phương thức.
  2. Nó có thể che khuất những gì thực sự xảy ra bên trong mã của bạn
  3. Nó bỏ qua mã nguồn có thể tạo ra vấn đề bảo trì
  4. Mã phản chiếu cũng phức tạp hơn mã trực tiếp tương ứng
  5. Nó cho phép vi phạm các ràng buộc bảo mật Java chính như bảo vệ truy cập dữ liệu và an toàn kiểu

Lạm dụng chung:

  1. Đang tải các lớp bị hạn chế,
  2. Có được các tham chiếu đến các hàm tạo, các phương thức hoặc các trường của một lớp bị hạn chế,
  3. Tạo các thể hiện đối tượng mới, gọi phương thức, nhận hoặc thiết lập các giá trị trường của một lớp bị hạn chế.

Hãy xem câu hỏi SE này liên quan đến việc lạm dụng tính năng phản chiếu:

Làm cách nào để đọc một trường riêng trong Java?

Tóm lược:

Việc sử dụng không an toàn các chức năng của nó được thực hiện từ bên trong mã hệ thống cũng có thể dễ dàng dẫn đến sự thỏa hiệp của chế độ bảo mật Java l. Vì vậy, hãy sử dụng tính năng này một cách tiết kiệm


Một cách để tránh các vấn đề về hiệu suất của Reflection trong một số trường hợp là yêu cầu một lớp Woozlekiểm tra các lớp khác khi khởi động để xem cái nào có RegisterAsWoozleHelper()phương thức tĩnh và gọi tất cả các phương thức mà nó tìm thấy với một cuộc gọi lại mà chúng có thể sử dụng để tự nói Woozlevề mình, tránh cần sử dụng Reflection trong khi ví dụ khử dữ liệu.
supercat

9

Như chính tên cho thấy nó phản ánh những gì nó giữ cho phương thức lớp, v.v. ngoài việc cung cấp tính năng để gọi phương thức tạo cá thể động trong thời gian chạy.

Nó được sử dụng bởi nhiều khung và ứng dụng dưới gỗ để gọi các dịch vụ mà không thực sự biết mã.


7

Reflection cung cấp cho bạn khả năng viết mã chung hơn. Nó cho phép bạn tạo một đối tượng trong thời gian chạy và gọi phương thức của nó khi chạy. Do đó chương trình có thể được thực hiện tham số cao. Nó cũng cho phép hướng nội đối tượng và lớp để phát hiện các biến và phương thức của nó tiếp xúc với thế giới bên ngoài.


6

Reflectioncó nhiều công dụng . Một cái tôi quen thuộc hơn, là có thể tạo mã nhanh chóng.

IE: các lớp động, hàm, hàm tạo - dựa trên bất kỳ dữ liệu nào (kết quả xml / mảng / sql / hardcoding / etc ..)


1
Câu trả lời này sẽ tốt hơn rất nhiều nếu bạn chỉ đưa ra một ví dụ bất thường về mã được tạo từ kết quả SQL hoặc tệp XML, v.v.
ThisClark

Không vấn đề gì. Tôi đã sử dụng sự phản chiếu trong một ứng dụng windows tự động tạo giao diện dựa trên XML được lấy từ cơ sở dữ liệu.
Ess Kay

Về cơ bản, có một lớp tôi đã tạo để hiển thị cho người dùng báo cáo. Báo cáo này có các tham số như id ngày (từ đến) hoặc bất cứ điều gì khác. Thông tin này được lưu trữ trong xml. Vì vậy, đầu tiên chúng tôi có một lựa chọn báo cáo. Dựa trên báo cáo được chọn, biểu mẫu nhận được xml. Khi xml được lấy, nó sử dụng sự phản chiếu để tạo một lớp với các trường dựa trên các kiểu được phản ánh. Khi bạn thay đổi thành một báo cáo khác, bảng sẽ được xóa sạch và các trường mới được tạo dựa trên xml. Vì vậy, về cơ bản nó là một hình thức năng động dựa trên sự phản ánh. Tôi cũng đã sử dụng theo những cách khác nhưng điều này sẽ trở nên hữu ích Hy vọng có ích
Ess Kay

3

Tôi muốn trả lời câu hỏi này bằng ví dụ. Trước hết, Hibernatedự án sử dụng Reflection APIđể tạo ra các CRUDcâu lệnh để thu hẹp khoảng cách giữa ứng dụng đang chạy và kho lưu trữ bền vững. Khi mọi thứ thay đổi trong miền, họ Hibernatephải biết về chúng để duy trì chúng đến kho lưu trữ dữ liệu và ngược lại.

Ngoài ra hoạt động Lombok Project. Nó chỉ tiêm mã vào thời gian biên dịch, dẫn đến mã được chèn vào các lớp miền của bạn. (Tôi nghĩ rằng nó ổn đối với getters và setters)

Hibernateđã chọn reflectionvì nó có tác động tối thiểu đến quá trình xây dựng cho một ứng dụng.

Và từ Java 7 chúng ta có MethodHandles, hoạt động như Reflection API. Trong các dự án, để làm việc với loggers, chúng tôi chỉ cần sao chép-dán mã tiếp theo:

Logger LOGGER = Logger.getLogger(MethodHandles.lookup().lookupClass().getName());

Bởi vì rất khó để mắc lỗi đánh máy trong trường hợp này.


3

Theo tôi thấy tốt nhất là giải thích bằng ví dụ và dường như không có câu trả lời nào làm được điều đó ...

Một ví dụ thực tế về việc sử dụng phản xạ sẽ là Máy chủ ngôn ngữ Java được viết bằng Java hoặc Máy chủ ngôn ngữ PHP được viết bằng PHP, v.v. Máy chủ ngôn ngữ cung cấp các khả năng IDE của bạn như tự động hoàn thành, chuyển sang định nghĩa, trợ giúp ngữ cảnh, các loại gợi ý và hơn thế nữa. Để có tất cả các tên thẻ (các từ có thể tự động hoàn thành) để hiển thị tất cả các kết quả khớp có thể có khi bạn nhập Máy chủ ngôn ngữ phải kiểm tra mọi thứ về lớp bao gồm các khối tài liệu và các thành viên riêng. Cho rằng nó cần một sự phản ánh của lớp nói.

Một ví dụ khác sẽ là một bài kiểm tra đơn vị của một phương thức riêng. Một cách để làm như vậy là tạo sự phản chiếu và thay đổi phạm vi của phương thức thành công khai trong giai đoạn thiết lập thử nghiệm. Tất nhiên người ta có thể lập luận rằng các phương pháp riêng tư không nên được kiểm tra trực tiếp nhưng đó không phải là vấn đề.

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.