Java Reflect Basics

I used to believe Reflect in Java was difficult because Core Java placed relevant chapters after Exceptions and Lambda. This is rather confusing. I am not ready for it until my senior in Vidar-Team, Ash , assigned a homework to figure out three ways to enable subclasses to get access private fields or methods of their superclass. All what I can think of is writing public getter/setter methods in superclass or making modifier private to protected. It was then Ash mentioned Reflect, which I almost forgot.

In fact, Reflect is used pretty common in Java, especially dealing with JDBC. Reflect is designed to fetch or revise details of a class when you have already compile it.

Every Reflect starts with a Class.forName();

When you’re writing Reflect, the first thing you do is to define which class you want to fetch. forName(String className) method returns a Class. Open your eye and watch it carefully. It is capitalized, meaning it’s a special type. So do Method, Field, and Constructor.

I’m tired of paraphrasing Reflect documentation, while copying and pasting Java documentation looks bored. Thus, I decide it to demonstrate the usage of **Reflect **through simple codes.

Student is a class with both public and private fields and several public methods.

package example.Reflect

public class Student {
    pubic String name;
    private int id;
    private int score; 

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public void getScore(){
        return score;
    }

    public void setScore(int score){
        this.score=score;
    }

    public void showAll(){
        System.out.println(this.name+"'s ID is "+this.id+".His score is"+this.score);
    }

    private int preID(int id){
        return (1819000+id);
    }


}

In order to test its Object-Oriented features, the main() method is written in another class)


public static void main( String[] args ){ try { Class c = Class.forName("example.Reflect.Student"); Field[] field = c.getFields(); for(Field f:field){ System.out.println(f); } System.out.println("##########") Methods[] method=c.getMethods(); for(Method m:method){ System.out.println(m); } } catch (ClassNotFoundException e) { e.printStackTrace(); //IDE will remind you if you forget to throw this exception } catch(NoSuchFieldException e){ e.printStackTrace(); }catch(NoSuchMethodException e){ e.printStackTrace(); } } /*result: public String example.Reflect.Student.name ########## public java.lang.String example.Reflect.Student.getScore() public int example.Reflect.Student.getID() public void example.Reflect.Student.setScore() public void exmaple.Reflect.Student.setID() public void example.Reflect.Student.showAll() public final void java.lang.Object.wait() throws java.lang.InterruptedException public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException public boolean java.lang.Object.equals(java.lang.Object) public java.lang.String java.lang.Object.toString() public native int java.lang.Object.hashCode() public final native java.lang.Class java.lang.Object.getClass() public final native void java.lang.Object.notify() public final native void java.lang.Object.notifyAll() */

As you can see, only public field is printed, and a whole bunch of methods printed belongs to java.lang.Object, the superclass of all other classes.

So, how to fetch both public and private methods and fields of Student, leaving those in Object aside?

Easy, change getFields() to getDeclaredFields, and similarly, getMethods() to getDeclaredMethods().

Rerun:

/*result:
private int example.Reflect.Student.id
private int example.Reflect.Student.score
public java.lang.String example.Reflect.name
private int example.Reflect.Student.preID(int id) <---
public java.lang.String example.Reflect.Student.getScore()
public int example.Reflect.Student.getID()
public void example.Reflect.Student.setScore()
public void exmaple.Reflect.Student.setID()
*/
How to Create a New Instance of a class using Reflect?

For those classes that only posses default constructor, newing an instance is fairly easy.

public static void main(String[] args){
    try{
        Class c=Class.forName("example.Reflect.Student");
        Student student=(Student)c.newInstance();
        //student's name uninitalized.
        student.setId(100);
        student.setScore(100);
        student.showAll();
    }catch(ClassNotFoundException e){
        e.printStackTrace();
    }
}//result: null's ID is 100.His score is 100

Only thing to mention is that newInstance() returns an Object, so you have to cast it to the particular one.

What if the target class has a constructor with parameters? Introducing Constructor.

Still taking class Student as example, after I rewrite the constructor into one with parameters, other parts remaining the same:

class Student{
    ....
    public Student(String name,int id, int score){
        this.name=name;
        this.id=id;
        this.score=score;
    }
    ...
}

In main() method:

public static void main(String[] args){
    try{
        Class c=Class.forName("example.Reflect.Student");
        //Student student=(Student)c.newInstance();<-- throw NoSuchMethodException(no default constructor!)

        Constructor constructor=c.getConstructor(String.class,int.class,int.class);
        Student student=(Student)constructor.newInstance("Michael",100,99);
        student.showAll();


    }catch(ClassNotFoundException e){
        //exception forName
        e.printStackTrace();
    }catch(NoSuchMethodException e){
        e.printStackTrace();
    }catch(IllegalAccessException e){
        //Some methods or constructors may be private or protected,or inaccessible
        e.printStackTrace();
    }
    catch(InvocationTargetException e){
        //Exceptions when newing with Constructor(with capital C)
        e.printStackTrace();
    }catch(InstantiationException e){
        e.printStackTrace();
    }


}//result:Michael's ID is 100.His score is 99

Do remember java.lang.reflect.* should be imported automatically by IDE or by you yourself, and don't forget to throw these exceptions.

Back to the Very Beginning: How can a Subclass Get Access to Private Fields or Methods of its Parent?

Ladies and gentleman, hereby I, with honor, introduce you setAccessible() .

To make it easy to understand, I'll rewrite class Student all over again, and then write another subclass of it.

package example.Reflect

public class Student {
    private String name;
    private int id;
    private int score; 

    public Student(String name,int id,int score){
        this.name=name;
        this.id=IDProcess(id);
        this.score=score;
    }

    private int IDProcess(int id){
        return (1801000+id);
    }

}

I devise of a Mechanic class that extends Student.

package example.Reflect

public class Mechanic extends Student{

    public Mechanic(String name,int id,int score){
        super(name,id,score);
    }

}

OK, here comes a problem. What if I want to add a method in Mechanic instead of Student, printing out all the information of a Mechanic instance without writing any getter/setter in Student, and no changes to the modifier private ?

1.Fetching Fields

I don't know haven't you tried, but it is really frustrating that neither this.name nor super.name works in this case. Without any public setter/getter, the work seems impossible. Of coarse you can copy and paste all the fields of Student to Mechanic, but that would be stupid.

It is time for to invite our protagonist today out. Reflect!Showing the code probably gives you a better understanding.

public void showName() throws  ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException{
    Class c=Class.forName("example.Reflect.Student");
    Field field=c.getDeclaredField("name");
    //Coming!
    field.setAccessible(true);
    String name=(String)field.get(this);//must cast or it returns an Object
    System.out.println(name);
}

setAccessible()has one boolean parameter. It is set false as default until the programmer set it true.

Test it in main()

public static void main(String[] args){
   Mechanic mstudent=new Mechanic("Michael",100,100);
   try {
            mstudent.showName();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}//result: Michael

As seen in showName(), field.get()method is used to retrieve the value of the field of a specified instance. Similarly, field.set() method is used to set the value of the field of the instance.

For example:

public void setName(String name) throws ...{
    ...//Class.forName().. etc,just like showName()
    field.setAccessible(true);
    field.set(this,name);
}//..works exactly the same like writing setter in parent class.

It works all the same with other fields like ID and score. Due to limited space, I'll skip them.

2.Calling Methods

Almost resemble the codes above:

public void showID(int id) throws  ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException{
    Class c=Class.forName("example.Reflect.Student");
    Methods method=c.getDeclaredMethod("IDProcess",int.class);
    //inside the parentheses:methodName and its parameters(param can be one or more)
    method.setAccessible(true);
    System.out.println((int)method.invoke(this,id));
    //Don't forget to cast, or it'll return something like example.Reflect.Mechanics@10b28a4
}

Test it in main():

public static void main(String[] args){
   Mechanic mstudent=new Mechanic("Michael",100,100);
   try {
            mstudent.showID(100);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }catch(InvocationTargetException e){
            e.printStackTrace();
        }
    }
}//result: 1801100

As always, import java.lang.reflect.*. You could either wrap your thing in try/catch, or throw exceptions.

At the End of the Day

Above are just the very very basic usage of Java's Reflect. Michael is still learning and coding, on the arduous journey to become those who he admires and his mentors. Should any mistakes, even trivial typos, exists, please write your comment below to let Michael know. He'll be humbly taking your precious advise.

发表评论

电子邮件地址不会被公开。 必填项已用*标注