Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't java have .field and .method reflection keywords like .class?

Let's say we have this class:

public abstract class A {
  int x;
  public int foo();
  public int foo(int x);
}

As we all know, A.class has a distinct advantage over Class.forName("A"): it will still work if the name of A is changed by refactoring or obfuscation.

However, there is no way to get this advantage with fields and methods. Have you ever wished that you could do this: (see a better proposed syntax in the edit below!)

Field xField = A.x.field;
Method fooMethod = A.foo().method;
Method fooIntMethod = A.foo(int).method;

Instead of this?

Field xField = A.getField("x");
Method fooMethod = A.getMethod("foo");
Method fooIntMethod = A.getMethod("foo", int.class);

So here's my question: does anyone know if this feature has been planned or discussed or if Sun/Oracle specifically decided against it for some reason?

EDIT: How about this syntax? It avoids problems people have mentioned:

Field xField = A..x;
Method fooMethod = A..foo();
Method fooIntMethod = A..foo(int);

Example use case

I recently created an AbstractTableModel class called EasyTableModel that allows you to define your own POJO row type. Its getValueAt(...) and setValueAt(...) etc. use reflection to get/set the values of the fields in the POJO.

public class EasyTableModel<T> extends AbstractTableModel {
    private RowFormat<T>                prototypeFormat;

    private final ArrayList<T>          rows                = new ArrayList<T>();

    ...

    public static interface RowFormat<T> {
        Object getValueAt(T row, int columnIndex);

        void setValueAt(T row, Object value, int columnIndex);

        ...
    }

    ...

    public static class ReflectionRowFormat<T> implements RowFormat<T> {
        private Field[]             fields;

        ...

        public Object getValueAt(T row, int column) {
            try {
                return fields[column].get(row);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public void setValueAt(T row, Object value, Field field) {
            if (!field.getDeclaringClass().isInstance(this)) {
                throw new IllegalArgumentException("field is not a member of this class");
            }
            setValueAt(row, value, getColumnIndex(field));
        }

        ...
    }

    ...

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        return getRowFormat(rowIndex).getValueAt(rows.get(rowIndex), columnIndex);
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        getRowFormat(rowIndex).setValueAt(rows.get(rowIndex), aValue, columnIndex);
        fireTableRowsUpdated(rowIndex, rowIndex);
    }

    public void fireTableCellUpdated(T row, String columnName) {
        fireTableCellUpdated(row, indexOfColumn(columnName));
    }

    public void fireTableCellUpdated(T row, Field field) {
        fireTableCellUpdated(row, indexOfColumn(field));
    }
}

Using this base class, it's extremely easy to create your table:

public abstract class QuoteMonitorTableModel<R extends QuoteMonitorTableModel<R>.Row> extends EasyTableModel<R> {
    ...

    protected static final String   NUM_QUOTES_RECEIVED = "# Quotes Received";
    protected static final String   LAST_QUOTE_TIME     = "Last Quote Time";

    public class Row {
        public Row() {

        }

        @ColumnName(NUM_QUOTES_RECEIVED)
        private Integer numQuotesReceived;

        @ColumnName(LAST_QUOTE_TIME)
        private Long    lastQuoteTimeMillis;

        public Integer getNumQuotesReceived() {
            return numQuotesReceived;
        }

        public void setNumQuotesReceived(Integer numQuotesReceived) {
            this.numQuotesReceived = numQuotesReceived;
            fireTableCellUpdated((R) this, NUM_QUOTES_RECEIVED);
        }

        public Long getLastQuoteTimeMillis() {
            return lastQuoteTimeMillis;
        }

        public void setLastQuoteTimeMillis(Long lastQuoteTimeMillis) {
            this.lastQuoteTimeMillis = lastQuoteTimeMillis;
            fireTableCellUpdated((R) this, LAST_QUOTE_TIME);
        }
    }
}

What are the advantages of all this?

  • You can set contents of the table through your own POJO row class without having to worry about column indices
  • Using your own POJO row class to set values is type-safe, unlike getValueAt(...) and setValueAt(...)
  • You could easily create a table using some prexisting POJO class for the row format

If you think this is an abuse of reflection, then you would consider many well-used libraries like Google GSON as an abuse of reflection as well.

Now, notice how the derived class indicates which field has changed when firing events by a String, rather than the Field:

    public void setNumQuotesReceived(Integer numQuotesReceived) {
        this.numQuotesReceived = numQuotesReceived;
        fireTableCellUpdated((R) this, NUM_QUOTES_RECEIVED);
    }

It would be nice if we could just use the fields. But doing it with getDeclaredField() would suck:

public void setNumQuotesReceived(Integer numQuotesReceived) {
    this.numQuotesReceived = numQuotesReceived;
    try {
        // what if obfuscation changes the name of the numQuotesReceived field?
        fireTableCellUpdated((R) this, getClass().getDeclaredField("numQuotesReceived"));
    } catch (NoSuchFieldException e) {
    }
}

However, with the feature I am proposing, it would be easy as cake:

public void setNumQuotesReceived(Integer numQuotesReceived) {
    this.numQuotesReceived = numQuotesReceived;
    // if obfuscation changes the name of the numQuotesReceived it will
    // not break the compiled form of this code
    fireTableCellUpdated((R) this, QuoteMonitorTableModel..numQuotesReceived);
}

If you think this feature wouldn't open up a world of possibilities for useful programming tools, you're lacking imagination ;)

like image 258
Andy Avatar asked Feb 12 '14 20:02

Andy


People also ask

Does Java have reflection?

Reflection is a feature in the Java programming language. It allows an executing Java program to examine or "introspect" upon itself, and manipulate internal properties of the program. For example, it's possible for a Java class to obtain the names of all its members and display them.

How do you reflect a class in Java?

In order to reflect a Java class, we first need to create an object of Class . And, using the object we can call various methods to get information about methods, fields, and constructors present in a class. class Dog {...} // create object of Class // to reflect the Dog class Class a = Class.

Can class name be a Java reserved keyword?

Yes, you can use reserved words.


1 Answers

It would be difficult to introduce keywords in Java, however you can introduce new symbol combinations. For example in Java 8 you can write

 MyClass::myMethod

To get a Method Reference, something you can't do in Java 7.

You could do something similar with fields and there have been suggestions to support property references.

You can even write

 HashSet<String>::new

to get a reference to a constructor ALA new HashSet<String>()

You can turn a Method into a MethodHandle with MethodHandles.Lookup.unreflect(Method) and you can turn a Constructor into a MethodHandle with MethodHandles.Lookup.unreflectConstructor(Constructor)

Once you have a MethodHandle you can set an object for it to call on.

like image 183
Peter Lawrey Avatar answered Nov 01 '22 00:11

Peter Lawrey