问题描述:
有两种获取对象属性值的方法,第一种是直接获取,比如stu.age; 另一种方法是根据对象属性的名字或者索引获取,比如getValue(stu, “age”);
比如一个类的对象:
class Stu{
String name;
int age;
}
如今有一个Stu的对象,并且知道一个属性名“name”,如何获取name所对应的值呢?(这里跟使用student.name来获取是两种不同的意思)。如果要统一定义一个获取属性值的接口, 通过该接口统一获取对象中某一属性的值,那么是:
//obj是对象,fieldName是该对象中某个属性的名字
T getValue(Type obj, String fieldName);
//或者
//fieldIndex是某一个属性的索引
T getValue(Type obj, int fieldIndex);
问题分析:
平时我们获取一个对象的属性的值是通过直接指定的,代码写好之后固定不变了。比如上面的Stu类,可以直接通过对象获取值,即student.name。但是有时候我们不确定该对象的具体类型,只知道一个对象的引用以及它的field的名字和类型,这样的话是不能这样直接取值的。也就是需要动态取值。
问题解决:
1. 反射
在java中,可以直接使用反射来获取。但是速度有点慢。
2. 统一接口 (Apach Flink的做法)
不使用反射。定义一个抽象类, 方法中提供统一根据索引或者名字获取属性的值的方法。
public abstract class Tuple {
private static final long serialVersionUID = 1L;
public abstract <T> T getField(int pos);
public abstract <T> void setField(T value, int pos);
/**
* Gets the number of field in the tuple (the tuple arity).
*
* @return The number of fields in the tuple.
*/
public abstract int getArity();
}
那么如何实现Tuple类呢?如果子类中只有一个属性值,那么实现类为:
public class Tuple1<T0> extends Tuple {
private static final long serialVersionUID = 1L;
/** Field 0 of the tuple. */
public T0 f0;
/**
* Creates a new tuple where all fields are null.
*/
public Tuple1() {}
public Tuple1(T0 value0) {
this.f0 = value0;
}
@Override
public int getArity() { return 1; }
@Override
@SuppressWarnings("unchecked")
public <T> T getField(int pos) {
switch(pos) {
case 0: return (T) this.f0;
default: throw new IndexOutOfBoundsException(String.valueOf(pos));
}
}
@Override
@SuppressWarnings("unchecked")
public <T> void setField(T value, int pos) {
switch(pos) {
case 0:
this.f0 = (T0) value;
break;
default: throw new IndexOutOfBoundsException(String.valueOf(pos));
}
}
public void setFields(T0 value0) {
this.f0 = value0;
}
}
如果子类中只有两个属性值,那么实现类为:
public class Tuple2<T0, T1> extends Tuple {
private static final long serialVersionUID = 1L;
/** Field 0 of the tuple. */
public T0 f0;
/** Field 1 of the tuple. */
public T1 f1;
/**
* Creates a new tuple where all fields are null.
*/
public Tuple2() {}
/**
* Creates a new tuple and assigns the given values to the tuple's fields.
*
* @param value0 The value for field 0
* @param value1 The value for field 1
*/
public Tuple2(T0 value0, T1 value1) {
this.f0 = value0;
this.f1 = value1;
}
@Override
public int getArity() { return 2; }
@Override
@SuppressWarnings("unchecked")
public <T> T getField(int pos) {
switch(pos) {
case 0: return (T) this.f0;
case 1: return (T) this.f1;
default: throw new IndexOutOfBoundsException(String.valueOf(pos));
}
}
@Override
@SuppressWarnings("unchecked")
public <T> void setField(T value, int pos) {
switch(pos) {
case 0:
this.f0 = (T0) value;
break;
case 1:
this.f1 = (T1) value;
break;
default: throw new IndexOutOfBoundsException(String.valueOf(pos));
}
}
/**
* Sets new values to all fields of the tuple.
*
* @param value0 The value for field 0
* @param value1 The value for field 1
*/
public void setFields(T0 value0, T1 value1) {
this.f0 = value0;
this.f1 = value1;
}
}
如果有三个或者更多的属性,实现方法类似。
该方法的速度比反射快很多。基本没有额外的内存占用。所以它算是比较好的方法。但是,需要为每一种数量属性的情况实现一个子类。一般情况先,一个对象的属性数量不会很多。
3. 使用函数指针
该方法可以使用在C++中。因为它不支持反射但支持函数指针。具体为:
定义一个接口,接口的方法和上面一样。但在每一个子类中,增加一个map属性,其key是其他field的名字,value为对应field的getter方法的指针。所有每一个field都应该有getter方法。
现在如果知道类的对象和其中的属性名字,可以通过到map中查到getter函数指针,调用该函数获取相应的值。
该方法的缺点显而易见,很麻烦而且占用了很多额外的内存空间。速度方面比反射快。
结束!