Not so long ago, I wrote a small class for automatic caching and updating arbitrary values. It was very easy to use it - it was enough just to create an anonymous class with the overloaded update method, and then, when necessary, call functions to mark the value as obsolete and to get the value itself:
public static void main(String[] args)
{
LazyValue<Integer> ultimateQuestionOfLife = new LazyValue<Integer>()
{
@Override
protected Integer update()
{
return findNewUltimateAnswer();
}
};
//
ultimateQuestionOfLife.invalidate();
// update()
System. out .println( "Answer is: " + ultimateQuestionOfLife.get());
// update() ,
System. out .println( "Answer is: " + ultimateQuestionOfLife.get());
//
ultimateQuestionOfLife.invalidate();
// update()
System. out .println( "Answer is: " + ultimateQuestionOfLife.get());
}
Tests in a small sketch program were successful, whereas in a large working project using this class, a stack overflow error began to appear - the values ​​caused each other to update without having another source of information. This error would not occur if the recalculation occurred every time, but not at the last moment.
private static LazyValue<Integer> lv1 = new LazyValue<Integer>()
{
@Override
protected Integer update()
{
return lv2.get(); //
}
};
private static LazyValue<Integer> lv2 = new LazyValue<Integer>()
{
@Override
protected Integer update()
{
return lv1.get() + 1;
}
};
public static void main(String[] args)
{
// StackOverflowException
System. out .println(lv2.get());
}
')
Pretty quickly, I was rather tired of inserting crutches into the code to prevent recursion here and there, and I decided to embed a recursion detector into the caching class, which would work, unwind the stack to the “instigator” of recursion and return some default value thereby preventing StackOverflowException. Here is the implementation:
private static LazyValue<Integer> lv1 = new RSLazyValue<Integer>()
{
@Override
protected Integer update()
{
return lv2.get();
}
@Override
protected Integer getDefault()
{
return 0;
}
};
private static LazyValue<Integer> lv2 = new RSLazyValue<Integer>()
{
@Override
protected Integer update()
{
return lv1.get() + 1;
}
@Override
protected Integer getDefault()
{
return 10;
}
};
public static void main(String[] args)
{
// 10
System. out .println(lv2.get());
}
All the magic lies in the RSLazyValue class (RS is Recursion-Safe). At the moment when the update has begun, a special flag is put, which then necessarily at the end of the update is removed. If we just went to the same function, and the update is in progress, then we caught the recursion by the tail and we need to do something with it. Solution in the forehead - immediately return the default value. However, this is not the best way, because then the processed value will return to the first function call and it will return a value based on its own, only processed from the outside. The most suitable option is to throw an exception that unwinds the stack of calls to the previous “incarnation” of the same function and returns the default value from there. Here's how it works:
public ValueType get()
{
// update() ,
// , get()
// update()
if (updateInProgress)
throw new RecursionDetectedException( this );
// update()
if (isInvalid)
{
// update()
updateInProgress = true ;
try
{
//
value = update();
// isInvalid false update()
//
isInvalid = false ;
}
catch (RecursionDetectedException e)
{
if (e.getRecursionStarter() != this )
throw e; // ,
else
return getDefault(); // -
}
finally
{
// , get()
updateInProgress = false ;
}
}
return value;
}
The solution allowed in a large project to save a lot of time. Both programmer and processor time. Saving performance is that the data is calculated only on request, and if the request did not happen, then this data was needed by anyone and they do not need to be counted. I hope these 2 small classes will save your time. I will be glad to constructive criticism and cases in which "nothing works."
Full code of classes:
LazyValue.javaRSLazyValue.java