Variable Identity

Identity Methods

Java classes support what may be termed identity methods such as equals, hashCode and compareTo. The MJ variable classes implement these methods using either MJ identity, which relies on the underlying value of the variable, or MAPPER identity, which utilizes the displayed value of the variable. The nuances between MJ identity and MAPPER identity can be a bit esoteric and are described below. However, the default implementations of equals, hashCode and compareTo are such that an in-depth understanding isn't required to use MJ variables effectively.

MJ Identity: MJString Example

By default, identity methods of an MJ variable use rules similar to those employed by MAPPER @IF, where those rules don't conflict with well-established, Java semantics. For lack of a better name, call this MJ identity.

For example, consider the MJString variables of different MAPPER types below. The equals, hashCode and compareTo methods strip trailing spaces (but not trailing tabs or leading spaces) before comparing per MAPPER rules. However, comparison is case-sensitive ala Java practice; Java provides the compareToIgnoreCase method for case-insensitive comparison, which is also implemented by MJString.

// @LDV <str1A>a16='abcdef7890ABCDEF' .
MJString str1A = new MJString(VariableScope.LOCAL, MaprptVariableType.ALPHANUMERIC,
  16, EnumSet.of(LoadOption.TRUNCATE), "abcdef7890ABCDEF");
assert !str1A.isUsingMapperObjectIdentity();

// @LDV <str2S>s20='Abcdef7890aBCDEF' .
MJString str2S = new MJString(VariableScope.LOCAL, MaprptVariableType.STRING,
  20, EnumSet.of(LoadOption.TRUNCATE), "Abcdef7890aBCDEF");
assert !str2S.isUsingMapperObjectIdentity();

// @LDV <str3H>h18='abcdef7890ABCDEF' .
MJString str3H = new MJString(VariableScope.LOCAL, MaprptVariableType.HOLLERITH,
  18, EnumSet.of(LoadOption.TRUNCATE), "abcdef7890ABCDEF");
assert !str3H.isUsingMapperObjectIdentity();

// Values are not equal: "abcdef7890ABCDEF".equals("Abcdef7890aBCDEF") == false
assert !str1A.equals(str2S);
assert str2S.compareTo(str1A) != 0;
assert str1A.hashCode() != str2S.hashCode();

// Values are equal: "abcdef7890ABCDEF".equals("abcdef7890ABCDEF") == true
assert str1A.equals(str3H);
assert str3H.compareTo(str1A) == 0;
assert str3H.hashCode() == str1A.hashCode();

MAPPER Identity: MJString Example

Like other MJ variable classes, MJString also supports MAPPER identity  which uses the formatted, display value of the variable. When enabled, the equals, hashCode and compareTo methods do not strip trailing spaces (or other whitespace). However, comparison is still case-sensitive per Java convention:

str1A.useMapperObjectIdentity(true);
assert str1A.isUsingMapperObjectIdentity();
str2S.useMapperObjectIdentity(true);
assert str2S.isUsingMapperObjectIdentity();
str3H.useMapperObjectIdentity(true);
assert str3H.isUsingMapperObjectIdentity();

// Values still not equal: "abcdef7890ABCDEF".equals("Abcdef7890aBCDEF    ") == false
assert !str1A.equals(str2S);
assert str2S.compareTo(str1A) != 0;
assert str1A.hashCode() != str2S.hashCode();

// Values are not equal: "abcdef7890ABCDEF".equals("abcdef7890ABCDEF  ") == false
assert !str1A.equals(str3H);
assert str3H.compareTo(str2S) != 0;
assert str3H.hashCode() != str1A.hashCode();

MJ Identity: MJDecimal Example

Like MJString, the MJ numeric variable classes support identity methods that further illustrate the difference between MJ identity and MAPPER identity. The MJDecimal variables below contain distinct BigDecimal values (the basis for MJ identity) but display the same value (the basis for MAPPER identity):

// @LDV <myNum>f3.2=1.23
// See CHG statement below: <mySum> displays 1.67; MAPPER stores <myNum> as 1.23
// @CHG <mySum>f9.2 <myNum> + 0.44 .
//
MJDecimal dec1 = new MJDecimal(VariableScope.LOCAL, 3, 2, EnumSet.noneOf(LoadOption.class), "1.23");
assert !dec1.isUsingMapperObjectIdentity();
assert dec1.decimalValue().unscaledValue().longValue() == 123;
assert dec1.decimalValue().scale() == 2;

// The display value is based on the "MAPPER number" (the raw, BigDecimal value
// 1.23 above is rounded down to fit F3.2 format) and is "1.2".
//
assert ((BigDecimal) dec1.toMapperNumber()).unscaledValue().longValue() == 120;
assert ((BigDecimal) dec1.toMapperNumber()).scale() == 2;
assert "1.2".equals(dec1.formatter().toFormatted());

// @LDV <myNum>f3.2=1.19
// See CHG statement below: <mySum> displays 1.63; MAPPER stores <myNum> as 1.19
// @CHG <mySum>f9.2 <myNum> + 0.44 .
//
MJDecimal dec2 = new MJDecimal(VariableScope.LOCAL, 3, 2, EnumSet.noneOf(LoadOption.class), "1.19");
assert !dec2.isUsingMapperObjectIdentity();
assert dec2.decimalValue().unscaledValue().longValue() == 119;
assert dec2.decimalValue().scale() == 2;

// The display value is based on the "MAPPER number" (the raw, BigDecimal value
// 1.19 above is rounded up to fit F3.2 format) and is "1.2".
//
assert ((BigDecimal) dec2.toMapperNumber()).unscaledValue().longValue() == 120;
assert ((BigDecimal) dec2.toMapperNumber()).scale() == 2;
assert "1.2".equals(dec2.formatter().toFormatted());

// Values are not equal: BigDecimal.valueOf(123,2).equals(BigDecimal.valueOf(119,2)) == false
assert !dec1.equals(dec2);
assert dec2.compareTo(dec1) != 0;
assert dec1.hashCode() != dec2.hashCode();

MAPPER Identity: MJDecimal Example

Instead of the raw value of the underlying BigDecimal instance, MJDecimal identity methods utilize the numeric, display value of a variable when MAPPER identity is in effect:

dec1.useMapperObjectIdentity(true);
assert dec1.isUsingMapperObjectIdentity();
dec2.useMapperObjectIdentity(true);
assert dec2.isUsingMapperObjectIdentity();

// Values are equal: BigDecimal.valueOf(12,1).equals(BigDecimal.valueOf(12,1)) == false
assert dec1.equals(dec1);
assert dec2.compareTo(dec1) == 0;
assert dec1.hashCode() == dec2.hashCode();

To summarize, MAPPER identity can be useful in situations where results appear most accurate when variables are collated or organized by their display values.