Recursive function for getters/setters?
I am looking for an algorithm in Java that creates an object thats attributes are set to the first not-null value of a string of objects. Consider this array (I will use JSON syntax to represent the array for the sake of simplicity):
{
"objects": [
{
"id": 1,
"val1": null,
"val2": null,
"val3": 2.0
},
{
"id": 2,
"val1": null,
"val2": 3.8,
"val3": 6.0
},
{
"id": 3,
"val1": 1.98,
"val2": 1.8,
"val3": 9.0
}
]
}
In the end, I want one object that looks like this:
{
"id": 1,
"val1": 1.98,
"val2": 3.8,
"val3": 2.0
}
Where val1
comes from the third object, val2
from the secound and val3
and id
from t开发者_如何学运维he first, because these are the first objects found where the attribute isn't null.
What I have done so far in Java and what works really fine is this:
// Java code that proceeds a deserialized representation of the
// above mentioned array
int k = 0;
while (bs.getVal1() == null) {
k++;
bs.setVal1(objectArray.get(k).getVal1());
}
However, I am not satisfied, because I would have to write this code four times (getId
, getVal1
, getVal2
, getVal3
). I am sure there must be a rather generic approach. Any Java guru who could give a Java beginner an advice?
Before getting to your actual question, here's a better way of writing your existing loop: (replace Object
with whatever the actual type is)
for (Object o : objectArray) {
Double d = o.getVal1();
if (d != null) {
bs.setVal1(d);
break;
}
}
Considering the way your objects are laid out now, there isn't a better way to do it. But you can do better if you change the structure of your objects.
One way is to put your different value fields (val1, val2, ...
) into an array:
Double val[] = new Double[3]; // for 3 val fields
public Double getVal(int index) {
return val[index];
}
public void setVal(int index, Double value) {
val[index] = value;
}
Then you can simply access the fields by their index and set all fields of bs
in one iteration of the object array:
for (Object o : objectArray) {
for (int i = 0; i < 3; i++) {
Double d = o.getVal(i);
if (d != null) {
bs.setVal(i, d);
break;
}
}
}
+1 - Code repetition is a problem that can sometimes be hard to overcome in Java.
One solution is to create an Iterable class which allows you to iterate over the values in one of those objects as if they were in an array. This takes away some of the repetition from your code without sacraficing the legibility benefits of named variables.
Note that In my code below, I've created a separate iterable class, but you could also simply make the POD class iterable (which one of these is the best option for you depends on details you didn't cover with your example code):
(warning - not tested yet)
import java.util.Iterator;
public class Main {
static class POD{
Integer id; Double val1; Double val2; Double val3;
public POD(Integer i, Double v1, Double v2, Double v3){
id=i; val1=v1; val2=v2; val3=v3;
}
public POD(){ }
}
static class PODFields implements Iterable<Number>{
private POD pod;
public PODFields(POD pod){
this.pod=pod;
}
public PODFieldsIterator iterator() {
return new PODFieldsIterator(pod);
}
}
static class PODFieldsIterator implements Iterator<Number>{
int cur=0;
POD pod;
public PODFieldsIterator(POD pod) {
this.pod=pod;
}
public boolean hasNext() { return cur<4; }
public Number next() {
switch(cur++){
case 0:
return pod.id;
case 1:
return pod.val1;
case 2:
return pod.val2;
case 3:
return pod.val3;
}
return null;//(there are better ways to handle this case, but whatever)
}
public void remove() { throw new UnsupportedOperationException("You cannot remove a POD field."); }
}
public static void main(String[] args) {
POD [] objectArray = {new POD(1,null,null,2.0),
new POD(1,null,null,2.0),
new POD(1,null,null,2.0),
new POD(1,null,null,2.0)};
POD finalObject=new POD();
for (POD cur : objectArray){
PODFieldsIterator curFields = new PODFields(cur).iterator();
for (Number finalValue : new PODFields(finalObject)){
Number curValue = curFields.next();
if (finalValue==null)
finalValue=curValue;
}
}
for (Number finalValue : new PODFields(finalObject))
System.out.println(finalValue);
}
}
Edit: Oops - looks like I forgot Numbers are immutable. I suppose you could overcome this by having the iterator return functors or something, but that's possibly going a bit overboard.
Whenever you want to eliminate code duplication, one of the first things you look for is whether you can extract a reusable method. Reflection helps you call arbitrary methods in a reusable way. Its not the prettiest thing in the world, but this method works for you:
@SuppressWarnings("unchecked")
public <T> T firstNonNull(String methodName, TestObject... objs) {
try {
Method m = TestObject.class.getMethod(methodName, (Class[])null);
for (TestObject testObj : objs) {
T retVal = (T)m.invoke(testObj, (Object[])null);
if (retVal != null) return retVal;
}
return null;
} catch (Exception e) {
//log, at a minimum
return null;
}
}
Testing with a class like this:
public class TestObject {
Integer id;
String val1;
Map<String, Boolean> val2;
public int getId() {
return id;
}
public String getVal1() {
return val1;
}
public Map<String, Boolean> getVal2() {
return val2;
}
}
This JUnit test demonstrates its usage:
@org.junit.Test
public void testFirstNonNull() {
TestObject t1 = new TestObject();
t1.id = 1;
t1.val1 = "Hello";
t1.val2 = null;
TestObject t2 = new TestObject();
Map<String, Boolean> map = new HashMap<String, Boolean>();
t2.id = null;
t2.val1 = "World";
t2.val2 = map;
TestObject result = new TestObject();
result.id = firstNonNull("getId", t1, t2);
result.val1 = firstNonNull("getVal1", t1, t2);
result.val2 = firstNonNull("getVal2", t1, t2);
Assert.assertEquals(result.id, (Integer)1);
Assert.assertEquals(result.val1, "Hello");
Assert.assertSame(result.val2, map);
}
精彩评论