Is there a BigDecimal library with the basic operations of BigDecimal which allows null values?
Null should be treated as 0 for mathematical purpose.
I don't want to do all the null checks for possible null values.
You either never allow null values in database, application or view and initialize everything with new BigDecimal(0) or perform null checks on every usage for nullable values.
Something like:
public static BigDecimal add(final BigDecimal value, final BigDecimal augend)
{
if (value == null)
return augend;
else if (augend == null)
return value;
else
return value.add(augend);
}
public static BigDecimal multiply(final BigDecimal value, final BigDecimal multiplicand)
{
if (value == null || multiplicand == null)
return null;
return value.multiply(multiplicand);
}
Save the coding, just don't allow null values in the database. Make the default value zero.
As for new BigDecimal(0): no, use BigDecimal.ZERO.
I had a similar problem (not related to a database though, just needed to sum up a couple of nullable BigDecimals). Did not find any library, so had to write the following function myself:
public static BigDecimal add(BigDecimal... addends) {
BigDecimal sum = BigDecimal.ZERO;
if (addends != null) {
for (BigDecimal addend : addends) {
if (addend == null) {
addend = BigDecimal.ZERO;
}
sum = sum.add(addend);
}
}
return sum;
}
The same in Java 8:
public static BigDecimal add(BigDecimal... addends) {
if (addends == null) {
return BigDecimal.ZERO;
}
return Arrays.stream(addends)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
I guess I don't see the point of the library checking for null. Sure, the library won't throw a NPE, but the caller is eventually going to have to check for it. What is the caller of your above multiply going to do? It can't just use the output. It's going to have to check to see if the returned value is null at some point before it can do anything with the value.
Also, for any application I've ever written, a null is much different than zero. I wouldn't want to use one that treated a null as zero.
If your requirement is that nulls aren't allowed in your DB, I would check for nulls in your DAO layer before writing to the DB.
You can have a look on this library: https://github.com/MatWein/SCalc
It can handle every number (BigDecimal, Double, custom types, ...) and handles null values as zero.
If the list contains null elements, it doesn't equals null...
So here is a unit test that test all the cases and its implementation :
#ParameterizedTest
#MethodSource("shouldSumBigDecimalsParams")
void shouldSumBigDecimals(BigDecimal expectedSum, BigDecimal first, BigDecimal second) {
BigDecimal sum = bigDecimalsSum(first, second);
assertThat(sum).isEqualTo(expectedSum);
}
private static Stream<Arguments> shouldSumBigDecimalsParams() {
return Stream.of(
Arguments.of(new BigDecimal("1010"), new BigDecimal("1000"), BigDecimal.TEN),
Arguments.of(new BigDecimal("1000"), new BigDecimal("1000"), null),
Arguments.of(BigDecimal.TEN, null, BigDecimal.TEN),
Arguments.of(BigDecimal.ZERO, null, BigDecimal.ZERO),
Arguments.of(BigDecimal.ZERO, BigDecimal.ZERO, null),
Arguments.of(BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO),
Arguments.of(null, null, null)
);
}
The implementation :
static BigDecimal bigDecimalsSum(BigDecimal... bigdecimals) {
if (bigdecimals == null || Arrays.stream(bigdecimals).allMatch(Objects::isNull)) {
return null;
}
return Arrays.stream(bigdecimals)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
Related
I have a utility function which convert parseDouble value to string.
public static BigDecimal setValue(Object o) {
BigDecimal value = new BigDecimal(0);
if(o!= Null){
value=BigDecimal.valueOf(Double.parseDouble(o.toString()));
}
return value;
}
I have tried with (o!=null && !isEmpty(o)) and (o!="" && o!=null) but it is still throwing same error.
Transaction amount which is processing this utility function contains empty value.
Firstly I don't understand why you are taking object type as an input, however to resolve your issue you can do something like this. But I would strongly advice you to change the method signature it is misleading.
public static BigDecimal setValue(Object o) {
var value = new BigDecimal(0);
if (o != null) {
if(o instanceof String) {
if (((String) o).trim().length()>0) {
value = new BigDecimal((String) o);
}
}
}
return value;
}
I would change the method signature to BigDecimal setValue(String s). Your null check and length check code should then work fine.
Also the method name is misleading. The method does not set anything. Something like convertToBigDecimal would be clearer.
class Solution {
private Integer[][] memory = //whaterver, It doesn't matter.
public int leetcode(int[] array) {
return Math.max(dfs(0, 0), dfs(0, 1));
}
int dfs(int status1, int status2) {
if (status1 == Integer.MAX_VALUE || status2 == Integer.MAX_VALUE) {
return 0;
}
if (memory[status1][status2] != null) {
return memory[status1][status2];
} else {
memory[status1][status2] = calculate() + Math.max(dfs(status1 + 1, status2), dfs(status1 + 1, status2 + 1));
return memory[status1][status2];
}
}
Integer calculate() {
//...
}
}
As shown in the above java code, in java, you can use null to judge whether an element in the array has memorized a certain value. If memorized, you can use it directly. If not, you need to do some calculations and then store the calculated value.
In Kotlin, since IntArray does not accept null, is there any good way to achieve similar operations?
thanks a lot.
You can make a variable accept nulls by using ?
In Kotlin, the type system distinguishes between references that can hold null (nullable references) and those that cannot (non-null references). For example, a regular variable of type String cannot hold null:
var a: String = "abc" // Regular initialization means non-null by default
a = null // compilation error
To allow nulls, you can declare a variable as nullable string, written String?:
var b: String? = "abc" // can be set null
b = null // ok
print(b)
You want an Int array that accepts null so write:-
fun main() {
val emptyArray : Array<Int?> = arrayOfNulls(0)
println(emptyArray.size) // 0
}
Check this documentation on null safety for all the details.Comment for any follow up query
Hope you found this answer useful, if so please accept it by clicking the ✔(tick symbol) next to it. Have a nice day :)
I have a POJO looking like this:
public class Pojo implements Comparable<Pojo> {
private String type;
private String journalId;
private Date bookingDate;
private Long account;
private String description;
private BigDecimal debit;
private BigDecimal credit;
....
}
and I want to sort a list of these POJOs. Currently my compareTo method looks like this:
#Override
public int compareTo(EfdisJournal other) {
int i = this.type.compareTo(other.type);
if (i != 0)
return i;
if (this.bookingDate != null && other.bookingDate != null)
i = this.bookingDate.compareTo(other.bookingDate);
if (i != 0)
return i;
if (this.journalId != null && other.journalId != null)
i = this.journalId.compareTo(other.journalId);
if (i != 0)
return i;
return this.account.compareTo(other.account);
}
If I run a sort with this compareTo method, I get this java.lang.IllegalArgumentException: Comparison method violates its general contract error. I did google a bit and I think it happens because some of the fields are null on comparison. Yet I have no idea how to solve this or if I am right why that error appears.
The comparison should work like this: 1st compare by type, then compare by bookingDate, as 3rd compare by journalId and at last compare by account. All comparisons should be ascending.
type is never null
bookingDate may be null
journalId may be null
account is never null
EDIT:
Sadly I was not able to implement the method, so that the order is as needed. Yet, i solved the problem I had, because the stored procedure yielded 2 resultsets, of which the second was order as needed, so the only thing I had to do was to use the 2nd resultset instead of the first.
You need to deal with the case where one instance has a null bookingDate, and the other has a non-null bookingDate.
You should decide whether things with null bookingDate should be sorted before or after things with a non-null bookingDate, and write your compareTo appropriately. (And then journalId too.) Then you can get an order that sorts consistently.
For instance:
#Override
public int compareTo(EfdisJournal other) {
int i = this.type.compareTo(other.type);
if (i != 0) {
return i;
}
if ((this.bookingDate==null) ^ (other.bookingDate==null)) {
return (this.bookingDate==null ? -1 : 1);
}
if (this.bookingDate != null && other.bookingDate != null) {
i = this.bookingDate.compareTo(other.bookingDate);
}
if (i != 0) {
return i;
}
if ((this.journalId==null) ^ (other.journalId==null)) {
return (this.journalId==null ? -1 : 1);
}
if (this.journalId != null && other.journalId != null) {
i = this.journalId.compareTo(other.journalId);
}
if (i != 0) {
return i;
}
return this.account.compareTo(other.account);
}
You're ignoring situations where bookingDate and/or journalId is null with one and non-null with the other.
I'm totally new on java 8 streams and I'm trying to obtain the behavior below described:
class myVO {
Long id;
BigDecimal value;
Date date;
getter/setter
}
myVO method(Map<Long, myVO> inputMap) {
return inputMap.stream()
.filter(x -> x.getValue().compareTo(BigDecimal.ZERO) > 0)
.sorted(); //FIXME
}
I want to obtain only one myVO obj that is the SUM of BigDecimal values of the records with the same date (the lowest one).
e.g.
xL, 10, 2015/07/07
xL, 15, 2015/07/08
xL, 20, 2015/07/07
xL, 25, 2015/07/09
result
xL, 30, 2015/07/07
N.B. id (xL) is not important field.
UPDATE -- adopted solution (although not in single pass)
if(null != map && !map.isEmpty()) {
Date closestDate = map.values().stream()
.filter(t -> t.getDate() != null)
.map(MyVO::getDate)
.min(Comparator.naturalOrder()).orElse(null);
myVO.setDate(closestDate);
BigDecimal totalValue = map.values().stream()
.filter(x -> x.getValue() != null && x.getValue().signum() != 0)
.filter(t -> t.getDate().equals(closestDate))
.map(MyVO::getValue)
.reduce(BigDecimal::add).orElse(null);
myVO.setValue(totalValue != null ? totalValue.setScale(2, BigDecimal.ROUND_HALF_DOWN) : totalValue);
}
Considering inputMap has atleast one entry, it can be done like this :
myVO method(Map<Long, myVO> inputMap) {
Date minDate = inputMap.values().stream().map(myVO::getDate).min(Comparator.naturalOrder()).get();
BigDecimal sum = inputMap.values().stream().filter(t -> t.getDate().equals(minDate)).map(myVO::getValue).reduce(BigDecimal::add).get();
myVO myVOObj = new myVO();
myVOObj.setDate(minDate);
myVOObj.setValue(sum);
myVOObj.setId(??);
return myVOObj;
}
I wrote a custom collector to solve such tasks. It's available in my StreamEx library and called MoreCollectors.maxAll(downstream). Using it you can solve the task in single pass after some preparations.
First, your compareTo method is wrong. It never returns 0 and practically it violates the contract (a.compareTo(a) == -1 which violates reflexivity and antisymmetry properties). It can be easily fixed like this:
#Override
public int compareTo(myVO o) {
return o.getDate().compareTo(this.getDate());
}
Next, let's add a myVO.merge() method which can merge two myVO objects according to your requirements:
public myVO merge(myVO other) {
myVO result = new myVO();
result.setId(getId());
result.setDate(getDate());
result.setValue(getValue().add(other.getValue()));
return result;
}
Now the result can be found like this:
Optional<myVO> result = input.stream().filter(x -> x.getValue().signum() > 0)
.collect(MoreCollectors.maxAll(Collectors.reducing(myVO::merge)));
The resulting Optional will be empty if the input list is empty.
If you don't like to depend on third-party library, you can just check the source code of this collector and write something similar in your project.
The reduction isn’t that complicated if you think about it. You have to specify a reduction function which:
if the dates of two elements differ, returns the object with the lower date
if they match, create a result object containing the sum of the values
Compared to a two-step method, it may perform more add operations whose result will be dropped when a lower date appears in the stream, on the other hand, the number of performed date comparisons will be half.
MyVO method(Map<Long, MyVO> inputMap) {
return inputMap.values().stream()
.reduce((a,b)->{
int cmp=a.getDate().compareTo(b.getDate());
if(cmp==0)
{
MyVO r=new MyVO();
r.setDate(a.date);
r.setValue(a.value.add(b.value));
return r;
}
return cmp<0? a: b;
}).orElse(null);
}
The main reason it doesn’t look concise is that it has to create a new MyVO instance holding the sum in the case of a matching date as a reduction function must not modify the value objects. And you didn’t specify which constructors exist. If there is an appropriate constructor receiving a Date and BigDecimal, the function could be almost a one-liner.
Note that this method will return an original MyVO object if there is only a single one with a lowest date.
Alternatively you can use a mutable reduction, always creating a new MyVO instance holding the result but only creating one instance per thread and modifying that new instance during the reduction:
MyVO method(Map<Long, MyVO> inputMap) {
BiConsumer<MyVO, MyVO> c=(a,b)->{
Date date = a.getDate();
int cmp=date==null? 1: date.compareTo(b.getDate());
if(cmp==0) a.setValue(a.getValue().add(b.getValue()));
else if(cmp>0)
{
a.setValue(b.getValue());
a.setDate(b.getDate());
}
};
return inputMap.values().stream().collect(()->{
MyVO r = new MyVO();
r.setValue(BigDecimal.ZERO);
return r;
}, c, c);
}
Here, the Supplier could be a one-line if an appropriate constructor exists (or if the initial value is guaranteed to be the non-null BigDecimal.ZERO)…
If I want to total a list of accounts' current balances, I can do:
accountOverview.setCurrentBalance(account.stream().
filter(a -> a.getCurrentBalance() != null).
mapToLong(a -> a.getCurrentBalance()).
sum());
But this expression will return 0, even if all the balances are null. I would like it to return null if all the balances are null, 0 if there are non-null 0 balances, and the sum of the balances otherwise.
How can I do this with a lambda expression?
Many thanks
Once you filtered them from the stream, there's no way to know if all the balances were null (unless check what count() returns but then you won't be able to use the stream since it's a terminal operation).
Doing two passes over the data is probably the straight-forward solution, and I would probably go with that first:
boolean allNulls = account.stream().map(Account::getBalance).allMatch(Objects::isNull);
Long sum = allNulls ? null : account.stream().map(Account::getBalance).filter(Objects::nonNull).mapToLong(l -> l).sum();
You could get rid of the filtering step with your solution with reduce, although the readability maybe not be the best:
Long sum = account.stream()
.reduce(null, (l1, l2) -> l1 == null ? l2 :
l2 == null ? l1 : Long.valueOf(l1 + l2));
Notice the Long.valueOf call. It's to avoid that the type of the conditional expression is long, and hence a NPE on some edge cases.
Another solution would be to use the Optional API. First, create a Stream<Optional<Long>> from the balances' values and reduce them:
Optional<Long> opt = account.stream()
.map(Account::getBalance)
.flatMap(l -> Stream.of(Optional.ofNullable(l)))
.reduce(Optional.empty(),
(o1, o2) -> o1.isPresent() ? o1.map(l -> l + o2.orElse(0L)) : o2);
This will give you an Optional<Long> that will be empty if all the values were null, otherwise it'll give you the sum of the non-null values.
Or you might want to create a custom collector for this:
class SumIntoOptional {
private boolean allNull = true;
private long sum = 0L;
public SumIntoOptional() {}
public void add(Long value) {
if(value != null) {
allNull = false;
sum += value;
}
}
public void merge(SumIntoOptional other) {
if(!other.allNull) {
allNull = false;
sum += other.sum;
}
}
public OptionalLong getSum() {
return allNull ? OptionalLong.empty() : OptionalLong.of(sum);
}
}
and then:
OptionalLong opt = account.stream().map(Account::getBalance).collect(SumIntoOptional::new, SumIntoOptional::add, SumIntoOptional::merge).getSum();
As you can see, there are various ways to achieve this, so my advice would be: choose the most readable first. If performance problems arise with your solution, check if it could be improved (by either turning the stream in parallel or using another alternative). But measure, don't guess.
For now, I'm going with this. Thoughts?
accountOverview.setCurrentBalance(account.stream().
filter(a -> a.getCurrentBalance() != null).
map(a -> a.getCurrentBalance()).
reduce(null, (i,j) -> { if (i == null) { return j; } else { return i+j; } }));
Because I've filtered nulls already, I'm guaranteed not to hit any. By making the initial param to reduce 'null', I can ensure that I get null back on an empty list.
Feels a bit hard/confusing to read though. Would like a nicer solution..
EDIT Thanks to pbabcdefp, I've gone with this rather more respectable solution:
List<Account> filtered = account.stream().
filter(a -> a.getCurrentBalance() != null).
collect(Collectors.toList());
accountOverview.setCurrentBalance(filtered.size() == 0?null:
filtered.stream().mapToLong(a -> a.getCurrentBalance()).
sum());
You're trying to do two fundamentally contradicting things: filter out null elements (which is a local operation, based on a single element) and detect when all elements are null (which is a global operation, based on the entire list). Normally you should do these as two separate operations, that makes things a lot more readable.
Apart from the reduce() trick you've already found, you can also resort to underhand tricks, if you know that balance can never be negative for example, you can do something like
long sum = account.stream().
mapToLong(a -> a.getCurrentBalance() == null ? 0 : a.getCurrentBalance()+1).
sum() - account.size();
Long nullableSum = sum < 0 ? null : sum;
But you've got to ask yourself: is what you gain by only iterating across your collection once worth the cost of having written a piece of unreadable and fairly brittle code? In most cases the answer will be: no.