CSC/ECE 517 Fall 2010/ch1 1c NR: Difference between revisions
Line 75: | Line 75: | ||
</pre> | </pre> | ||
Output: | |||
<pre> | <pre> | ||
Revision as of 17:24, 22 September 2010
Introduction to Reflection
Reflection is a language feature that enables a program to examine itself at runtime and possibly change its behavior accordingly. It was introduced by Brian Cantwell Smith as a framework for language extension.There are two aspects to reflection :
- Introspection - the ability for a program to observe and reason about its own state.
- Intercession - the ability for a program to modify its own execution state or alter its own interpretation or meaning.
What is crucial here is that a given program can behave not only as a function, but also as a data structure that can be examined and manipulated to change its behavior. These properties lead to an easily extensible language since the structures used by the language implementation are accessible to the programmer. The programmer can now define programming constructs that would otherwise have been either impossible or extremely difficult to define [1].
Reflection is most commonly used in many dynamically typed languages such as Ruby,Smalltalk, Objective-c and scripting languages like Perl,PHP. Statically typed languages such as Java, ML or Haskell also support reflection. Reflective programming languages and platforms provides a comprehensive list of all languages and platforms supporting reflection.
Reflection as Language Feature vs Reflection as Package
For the sake of simplicity, we take Ruby and Java to represent languages that have Reflection as a language feature and reflection as a package respectively.
Reflection in Ruby
One of the languages that has reflection as a built-in language feature is Ruby. Objects in Ruby support reflection by default, hence it is not necessary to use any external or additional libraries. In essence, the programmer does not need to do anything special to start using reflection, it is a native part of the language. Additionally, Ruby's intuitive syntax and ease of use make it possible for someone who is not an expert to write Ruby code that uses reflection. Ruby's reflection functionality is made available by the Object class from which everything in Ruby is derived.
Some of the basic information obtained through reflection is described below:
Class/Superclass Type:
Using the <object>.class will display the name of the class the object belongs to. Whereas <object>.superclass will display the superclass of the object. <object>.ancestors will display the ancestral hierarchy for the object.
Example:
3.class # outputs Fixnum 3.class.superclass # outputs the superclass of 3 which is Fixnum 3.class.ancestors # [Fixnum, Integer, Numeric, Comparable, Object, Kernel,BasicObject]
Accessing Methods supported by the object
The <object>.method will return all the public methods defined on the object.
All the private methods in a class can be seen through <object>.private_methods.
Example:
str = "wikichapter" str.methods # returns an array of all the public methods that can be called on the object Class.private_method # returns an array of all private methods in the object Class. # [:inherited, :initialize, :initialize_copy,...]
Accessing Instance variables,class variables, method access level
We can access the instance variables in a class using <object>.instance_variable. Similarly class variables and constants defined in the class can be accessed via <object>.class_variables.
<object>.private_instance_methods(false) will return all the private instance methods defined in class. If we pass a true as the argument, it will display the private instance methods of its ancestors too.
The above Reflection API's are particularly useful when dealing with user defined classes.
Example: This example creates a new class and demonstrates the reflection API's covered so far.
irb(main):105:0> class Example irb(main):106:1> @@class_var = "hi" irb(main):107:1> def initialize(x,y) irb(main):108:2> @a,@b = x,y irb(main):109:2> end irb(main):110:1> def method1 irb(main):111:2> puts "In Method1" irb(main):112:2> end irb(main):113:1> private:method1 irb(main):114:1> private irb(main):115:1> def method2 irb(main):116:2> end irb(main):117:1> protected irb(main):118:1> def method3 irb(main):119:1> puts "In Method3" irb(main):120:2> end irb(main):121:1> public irb(main):122:1> def method4 irb(main):123:2> end irb(main):124:1> def method5 irb(main):125:2> end irb(main):126:1> protected:method5 irb(main):127:1> end => Example
Output:
irb(main):131:0> Example.class_variables => [:@@class_var] irb(main):132:0> Example.private_instance_methods => [:initialize, :method1, :method2, :default_src_encoding, :irb_binding, :initi alize_copy, :remove_instance_variable, :sprintf, :format, :Integer, :Float, :Str ing, :Array, :warn, :raise, :fail, :global_variables, :__method__, :__callee__, :eval, :local_variables, :iterator?, :block_given?, :catch, :throw, :loop, :call er, :trace_var, :untrace_var, :at_exit, :syscall, :open, :printf, :print, :putc, :puts, :gets, :readline, :select, :readlines, :`, :p, :test, :srand, :rand, :tr ap, :exec, :fork, :exit!, :system, :spawn, :sleep, :exit, :abort, :load, :requir e, :require_relative, :autoload, :autoload?, :proc, :lambda, :binding, :set_trac e_func, :Rational, :Complex, :gem_original_require, :gem, :singleton_method_adde d, :singleton_method_removed, :singleton_method_undefined, :method_missing] irb(main):133:0> Example.private_instance_methods(false) => [:initialize, :method1, :method2] irb(main):134:0> Example.public_instance_methods(false) => [:method4] irb(main):135:0> Example.protected_instance_methods(false) => [:method3, :method5] irb(main):140:0> example = Example.new(1,3) => #<Example:0x2b8da10 @a=1, @b=3> irb(main):141:0> example.class => Example irb(main):143:0> example.class.ancestors => [Example, Object, Kernel, BasicObject] irb(main):145:0> example.methods => [:method3, :method4, :method5, :nil?, :===, :=~, :!~, :eql?, :hash, :<=>, :cl ass, :singleton_class, :clone, :dup, :initialize_dup, :initialize_clone, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :freeze, :frozen?, :to_s, : inspect, :methods, :singleton_methods, :protected_methods, :private_methods, :pu blic_methods, :instance_variables, :instance_variable_get, :instance_variable_se t, :instance_variable_defined?, :instance_of?, :kind_of?, :is_a?, :tap, :send, : public_send, :respond_to?, :respond_to_missing?, :extend, :display, :method, :pu blic_method, :define_singleton_method, :__id__, :object_id, :to_enum, :enum_for, :==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__]
Invoking method dynamically
One way to invoke a method dynamically in ruby is to "send" a message to the object. This is done by calling <class_instance>.send on the instance of the class. The method to be called is passed as the argument to the send API.
Example: For the Example class in the previous section, send can be used to dynamically invoke method1 or method3 as follows:
irb(main):149:0> example.send(:method1) In Method1 => nil irb(main):150:0> example.send(:method3) In Method3 => nil
A second way is instantiate a Method object and then call it. Method objects represent a chunk of code and a context in which it executes similar to Proc object. The Method object is created, and it can be executed whenever desired by sending it the call message.
Example: We again use the Example class to illustrate this.
irb(main):152:0> method_obj = example.method(:method1) => #<Method: Example#method1> irb(main):153:0> method_obj.call In Method1
And the third way to invoke a method dynamically using reflection is to use the eval method. this is done by passing <class_instance>.<method_name> as a string to the eval method. The string of code can be stored as an object, and then evaluated at a later time.
Example:
irb(main):157:0> eval "example.method3" In Method3 => nil
Reflection in Java
Reflection in Java starts by at java.lang.Class. Before using reflection on an object, first it is necessary to map it to an instance of Class because java.lang.reflect does not have public constructors and can only be accessed by methods in java.lang.Class. It is important to note that this method only works for types that are derived from Object, not all types in Java are an object, e.g. boolean.
Basic API in java:
Accessing Class type
There are several methods to get a Class based in the information available.Listed below are the different ways of getting a Class object.
(1)If an instance of an object is known, getClass() method can be used to obtain its Class. Primitive types can not use this methods as they are not inherited from Object class.
Class cls= "Hello World".getClass(); //Returns Class for String
(2) If type of the object is known, the Class corresponding to the object can be obtained by appending .class to the type. This also works for primitive types as shown below.
Class cls = boolean.class; // Returns Class for boolean
(3)When a fully qualified class name is available, the corresponding Class can be obtained using .forName method. This again cannot be applied on primitive types.
Class cls = Class.forName("[[Ljava.lang.String;"); // Returns Class for primitive type multi-dimensional array for String
Once the Class object is obtained, any information about the Class can be obtained by applying methods on it.Here are some of the methods.
Example eobj = new Example(); Class cls = eobj.getClass(); cls.getSuperClass(); //returns names of all the super classes in the hierarchy till Object Class. cls.getClasses(); //returns all the classes, interfaces and enums of the class including inherited ones. cls.getMethods(); // returns all the methods present in the class, including inherited but excluding private methods. cls.getDeclaredMethods(); //returns all the declared methods including private excluding the inherited methods. cls.getDeclaredFields(); //returns the declared fields.
Accessing Method Type
To get information about Methods in detail like return type, parameters etc, the methods from java.lang.reflect.Methods class are used. Before using a Method class, an instance of type java.lang.reflect.Method has to created for the corresponding Class to gain access to methods in java.lang.reflect.Method.
Method[] mobj = cls.getDeclaredMethods; //returns an array of methods in cls. Class<?>[] p = mobj.getParameterTypes(); //returns an array of parameters present in a method. Class<?>[] e = mobj.getExceptionTypes(); //returns an array of Exceptions thrown by the method. mobj.invoke(obj,parameters); // Calls a method specified in the parameters dynamically.
Accessing Fields
The Fields in a Class are also dynamically accessible using an instance of type java.lang.reflect.Field.
Field fobj = cls.getField("<FieldName>"); fobj.getType(); //returns the type of the field specified. fobj.getModifiers(); //returns the modifier type.
Constructors in a Class are also dynamically accessible using which new instances of a class can also be created. This topic is not explained here in order to make the discussion simple.
Example Program
Java Program using some of the features discussed above:
import java.lang.reflect.*; public class BasicReflectionFeaturesJava { public void Method_1(){ System.out.println("In Method_1"); } public void Method_2(){ System.out.println("In Method_2"); } public static void main(String args[]) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{ BasicReflectionFeaturesJava obj = new BasicReflectionFeaturesJava(); Class Class_Instance = obj.getClass(); //Get Class type object for BasicReflectionFeaturesJava System.out.println("Class Name:" + Class_Instance.getName()); //Get Class Name System.out.println("Methods of the Class BasicReflectionFeaturesJava:"); for(Method m:Class_Instance.getDeclaredMethods()){ //Get all the Declared methods of the class(Doesnot give private methods) System.out.println(m.getName()); //Get Names of all the methods } System.out.println("All Methods of Classes in hierarchy from BasicReflectionFeaturesJava to Object:"); for(Method m:Class_Instance.getDeclaredMethods()){//Get all the Declared methods of the class(Doesnot give private methods) System.out.println(m.getName()); //Get Names of all the methods } System.out.println("Invoking a method at runtime:"); Method m = Class_Instance.getMethod("Method_1"); //Get object to a specified method at runtime. m.invoke(obj, null); //Invoke a specified method at runtime. } }
Output
The output of the program displays class name, methods declared in it(excluding private), all methods(including derived ones):
Class Name:Excercises.BasicReflectionFeaturesJava Methods of the Class BasicReflectionFeaturesJava: Method_1 Method_2 main All Methods of Classes in hierarchy from BasicReflectionFeaturesJava to Object: Method_1 Method_2 main Invoking a method at runtime: In Method_1
Ruby Reflection vs Java Reflection
- Using Reflection in Ruby requires comparatively less code to be written than in Java.
- Java throws exceptions while using method specific APIs, which creates overhead of handling the exceptions making the code more complicated compared to ruby again.
- Unlike java, thorough understanding of working and concepts is not required in ruby to implement reflection because of inherent capability of objects in ruby to support reflection.
The above three points can easily be illustrated by taking the example of invoking a method dynamically.
- It just requires one single line of code in ruby - .send()
- In java, to invoke a method, an object of type Class for its corresponding class has to created which is then used to create an object of type Method and that object is then used . In addition to this, exceptions like NoSuchMethodError or InvocationTargetException can be thrown and hence should be handled in the code.
Also, java.lang.reflect.Method package which has classes specific to Methods has to be included apart from java.lang.Class. Similarly, for field and constructor info, java.lang.reflect.Field and java.lang.reflect.Constructor have to imported respectively.
import java.lang.Class;
import java.lang.reflect.Method;Sample obj= new Sample();
Class cls = obj.getClass();
Try{Method m = cls.invoke(obj,null);
}
Catch(NoSuchMethodException e){
System.out.println(e);
}
Catch(InvocationTargetExecption ie) {
System.out.println(ie)
}
Conclusion
Even though we have taken Ruby and Java to represent languages that have built-in Reflection vs those using Reflection through Packages, the genereal consensus would still be same as built-in features are much easier to use than when we need to use external packages or libraries. Also the performance overhead for Reflection in languages like Java is higher due to the dynamically resolved types involved in reflection[2]. Though Reflection is a powerful tool, it should not be used indiscriminately. Operations which would be illegal in a non-reflective environment, such as accessing private fields and methods, are allowed in reflective code. Consequently, unexpected side-effects, which may render code dysfunctional and destroy portability, may result from the use of reflection.
References
- http://www2.parc.com/csl/groups/sda/publications/papers/Mendhekar-Reflect93/for-web.pdf
- http://en.wikipedia.org/wiki/Reflection_(computer_science)
- http://en.wikibooks.org/wiki/Ruby_Programming/Syntax/Classes
- The Ruby programming language By David Flanagan, Yukihiro Matsumoto
- Ruby Cookbook, By Lucas Carlson, Leonard Richardson
- Programming Ruby: The Pragmatic Programmers' Guide, Second Edition, By Dave Thomas, with Chad Fowler and Andy Hunt
- http://download.oracle.com/javase/tutorial/reflect/TOC.html
- http://www.ibm.com/developerworks/java/library/j-dyn0603/
- http://www.ruby-doc.org/ruby-1.9/index.html
- http://onestepback.org/articles/10things/moreresources.html
- The Complete Reference, Java, By Herbert Schildt