Google Guava : primitives support

Another interesting feature of Google Guava is Java's primitves support which allows us to work easiest with booleans, ints, longs or even bytes, signed as well as unsigned.

Primitives in Google Guava

Java contains itself the "object" support for primitive types as int, long, short. This support is based on wrapper classes as Integer, Long or Short. If you're looking at Google Guava's primitive classes, you can see that the naming convention is almost the same.

The first difference between Guava's and Java's primitives is that wrapper class names are written in plural (Ints, Longs, Shorts). The second difference is the support of signed and unsigned values through SignedBytes, UnsignedBytes, UnsignedInteger, UnsignedInts, UnsignedLong and UnsignedLongs classes. Another difference is the support for arrays. Thanks to methods like asList or toArray, we can quickly convert some of Java's primitives into List or arrays. It's not the case in Java's wrapper classes which are more like strict object representations of primitives rather than utility classes.

You are right if you think that Google Guava's primitive support is not called to replace Java's wrapper classes. The main feature is to provide some of programming shortcuts for some repetitive operations as secure parsing or dynamic collection creation.

Sample code with Google Guava's primitives

After previous introduction we can pass to code samples. They're presented as some of JUnit test cases. All explanations will be written inside test cases.

public class PrimitivesTest {

	@Test
	public void collectionArrayFeatures() {
		/**
		 * This test case will present how to work with arrays or collections of primitives thanks to Google Guava. Only one code line is enough to
		 * create a List of ints, booleans.. - all Guava's wrappers provide the possibility to convert primitives of some type to List of this type with asList method.
		 */
		List years = Ints.asList(1980, 1985, 1987, 1989, 1991, 2001);
		assertTrue("The 1st entry of the list should be 1980 but is "+years.get(0), years.get(0) == 1980);
		assertTrue("The 2nd entry of the list should be 1980 but is "+years.get(1), years.get(1) == 1985);
		assertTrue("The 3rd entry of the list should be 1980 but is "+years.get(2), years.get(2) == 1987);
		assertTrue("The 4th entry of the list should be 1980 but is "+years.get(3), years.get(3) == 1989);
		assertTrue("The 5th entry of the list should be 1980 but is "+years.get(4), years.get(4) == 1991);
		assertTrue("The 6th entry of the list should be 1980 but is "+years.get(5), years.get(5) == 2001);
		
		/**
		 * We can also made inverse, convert a Collection to an array through toArray method.
		 */
		List<Integer> intList = new ArrayList<Integer>();
		intList.add(5);
		intList.add(10);
		intList.add(15);
		int[] intArray = Ints.toArray(intList);
		assertTrue("The 1st entry of array should be 5 but is "+intArray[0], intArray[0] == 5);
		assertTrue("The 2nd entry of array should be 10 but is "+intArray[1], intArray[1] == 10);
		assertTrue("The 3rd entry of array should be 15 but is "+intArray[2], intArray[2] == 15);
		
		/**
		 * All numeric wrappers (Ints, Shorts, Longs, Chars) have the method to convert given number to 4-bytes array. According to Javadoc, this method is the equivalent for 
		 * ByteBuffer.allocate(4).putInt(value).array().
		 * 
		 * This byte[] is a big-endian representation of given value. It means that most significant bytes are stored at smallest address and the least significant bytes in the largest one.
		 */
		int intValue = 12131415;
		byte[] javasByteArray = ByteBuffer.allocate(4).putInt(intValue).array();
		byte[] byteArray = Ints.toByteArray(intValue);
		assertTrue("The length of both byte arrays should be the same but is not ("+javasByteArray.length+" for javasByteArray and "+byteArray.length+" for byteArray)", javasByteArray.length == byteArray.length);
		for (int i = 0; i < byteArray.length; i++) {
			assertTrue("byteArray[i] ("+byteArray[i]+") is not the same as javasByteArray[i] ("+javasByteArray[i]+")", byteArray[i] == javasByteArray[i]);
		}
		
		/**
		 * Another interesting feature is the possibility to check if given value is present in array (almost like PHP's in_array($searchedValue, $array) method). contains() method is 
		 * present in all Guava's wrappers (Ints, Doubles, Longs, Shorts, Booleans, Bytes, Floats and Chars).
		 */
		assertTrue("15 should be present in intArray created from this List: "+intList, Ints.contains(intArray, 15));
		assertFalse("3 should be absent in intArray created from this List: "+intList, Ints.contains(intArray, 3));
		
		/**
		 * Once again, a similarity with PHP. This time can get the key of searched value (like PHP's array_search($value, $array)).
		 */
		int indexOf15 = Ints.indexOf(intArray, 15);
		assertTrue("Index of 15 in intArray should be 2 but is "+indexOf15, indexOf15 == 2);
		// we can also get the last index of searched value
		long[] testLong = new long[] {30, 20, 10, 30, 20, 10};
		int lastIndex10 = Longs.lastIndexOf(testLong, 10);
		assertTrue("Last index of 10 in testLong array should be 5 but is "+lastIndex10, lastIndex10 == 5);
		
		/**
		 * Thanks to Google Guava we can also simply concatenate two or more arrays. Once again, this example shows the feature for arrays but it can be applied to all wrappers
		 * (Ints, Doubles, Longs, Shorts, Booleans, Bytes, Floats and Chars).
		 */
		int[] century18 = new int[]{1750, 1790};
		int[] century19 = new int[]{1850, 1890};
		int[] century20 = new int[]{1950, 1990};
		int[] allYears = Ints.concat(century18, century19, century20);
		assertTrue("allYears[0] should be 1750 but is "+allYears[0], allYears[0] == 1750);
		assertTrue("allYears[1] should be 1750 but is "+allYears[1], allYears[1] == 1790);
		assertTrue("allYears[2] should be 1750 but is "+allYears[2], allYears[2] == 1850);
		assertTrue("allYears[3] should be 1750 but is "+allYears[3], allYears[3] == 1890);
		assertTrue("allYears[4] should be 1750 but is "+allYears[4], allYears[4] == 1950);
		assertTrue("allYears[5] should be 1750 but is "+allYears[5], allYears[5] == 1990);
		
		/**
		 * We can also normalize arrays to, for example, simplify the debug. It can be made with join(String separator, (int|long|short|double|float|char|boolean)... array).
		 */
		double[] prices = new double[] {19.59d, 19d, 20.99d};
		String pricesOutput = Doubles.join(";", prices);
		String expectedOutput = "19.59;19.0;20.99";
		assertTrue("Prices output ("+pricesOutput+") is not the same as expected ("+expectedOutput+")", pricesOutput.equals(expectedOutput));
	}
	
	@Test
	public void conversion() {
		/**
		 * We can also convert objects from one type to another. Note that if parsing fails, they're no exceptions which is thrown but the returned result is
		 * null.
		 */
		Double price = Doubles.tryParse("19.30");
		assertTrue("Parsed price should be 19.30 but is "+ price.doubleValue(), price.doubleValue() == 19.30d);
		Integer age = Ints.tryParse("eighteen");
		assertTrue("'eighteen' shouldn't be parsed to Integer, null was expected but "+age+" was received", age == null);
		
		/**
		 * Another method to convert from String to Java's primitive is stringConverter() method from each wrapper. Note that, unlinke tryParse, it throws an exception if
		 * given String can't be parsed.
		 */
		Double normalPrice = Doubles.stringConverter().convert("19.32");
		assertTrue("normalPrice should be 19.32 but is "+normalPrice.doubleValue(), normalPrice.doubleValue() == 19.32d);
		try {
			// this test should fail
			Doubles.stringConverter().convert("xyz");
			fail("The test should not pass here: 'xyz' isn't convertissable String");
		} catch (Exception e) {
			assertTrue("Exception should be NumberFormatExceptions but is "+e.getClass(), e.getClass() == NumberFormatException.class);
		}
	}

	@Test
	public void unsignedNumerics() {
		/**
		 * Difference between signed and unsigned values is that unsigned doesn't accept negative numbers. So, if signed Integer range is from -2147483648 to 2147483648,
		 * the unsigned Integer range will be from 0 to 4294967295. The division by 2 of unsigned max value should give the max range of signed Integer.
		 * 
		 * Unsigned in Guava doesn't concern only Integers but also long and bytes (respectively UnsignedLong and UnsignedBytes).
		 */
		long divisionResult = (UnsignedInteger.MAX_VALUE.longValue()/2l);
		assertTrue("divisionResult should be "+Integer.MAX_VALUE+" but is "+divisionResult, Integer.MAX_VALUE == divisionResult);
		
		UnsignedInteger biggerThanInteger = UnsignedInteger.valueOf("3294967295");
		assertTrue(biggerThanInteger.longValue()+" should be equal to 3294967295", biggerThanInteger.longValue() == 3294967295l);
		try {
			Integer.valueOf("3294967295");
		} catch (Exception e) {
			assertTrue("Exception should be NumberFormatException.class but is "+e.getClass(), e.getClass() == NumberFormatException.class);
		}
		
		/**
		 * In additionally to the capability of operating of ints bigger than Integer.MAX_VALUE, we can also make some mathematical operations (plus, minus, mod, times, dividedBy).
		 */
		UnsignedInteger timesInt = UnsignedInteger.valueOf(25l);
		UnsignedInteger timesIntResult = timesInt.times(UnsignedInteger.valueOf(2l));
		assertTrue(timesIntResult.longValue()+" should be equal to 50 but it's not", timesIntResult.longValue() == 50l);
	}
	
	@Test
	public void numeric() {
		/**
		 * Sometimes we need to find the biggest and the smallest value of given array. With min() and max() methods of Ints we can achieve it simply.
		 * 
		 * Another numeric wrappers (Floats, Doubles, Longs, Shorts) and Chars also provide min and max methods.
		 */
		int[] testedInt = new int[]{1930, 1940, 1950, 1960, 1970, 1980, 1990, 2000};
		int min = Ints.min(testedInt);
		int max = Ints.max(testedInt);
		assertTrue("Expected min value is 1930 but "+min+" was returned", min == 1930);
		assertTrue("Expected max value is 2000 but "+max+" was returned", max == 2000);
		
		/**
		 * We can also work with saturated casts which returns the nearest number to the long specified in the method. We test with max Long value which is bigger than the Integer one
		 * (
		 */
		int intCasted = Ints.saturatedCast(Long.MAX_VALUE);
		assertTrue("intCates should be "+Integer.MAX_VALUE+" but is "+intCasted, intCasted == Integer.MAX_VALUE);

		/**
		 * They're also checked casts which allows to cast one value to another one. Some exceptions can be thrown here, for example when casted value is bigger than the
		 * value that can be accepted by to cast class.
		 * 
		 * checkedCast(long value) exists also for Shorts wrapper.
		 */
		try {
			Ints.checkedCast(Long.MAX_VALUE);
		} catch (Exception e) {
			assertTrue("Unexpected exceptions was thrown (IllegalArgumentException expected, "+e.getClass()+ " got)", e.getClass() == IllegalArgumentException.class && e.getMessage().contains("Out of range"));
		}
	}
	
	@Test
	public void floatingPoint() {
		/**
		 * Floats and Doubles define a method isFinite((double|float) value). Thanks to this method we can check if given floating point number is real number (
		 */
		float value = Float.POSITIVE_INFINITY+31;
		boolean notFinite = Floats.isFinite(value);
		assertFalse(value+" shouldn't be finite (bigger than Float.POSITIVE_INFINITY ="+Float.POSITIVE_INFINITY+")", notFinite);
		double doubleValue = 39.39d;
		boolean isFinite = Doubles.isFinite(doubleValue);
		assertTrue(value+" should be finite (value between Double.NEGATIVE_INFINITY ="+Double.NEGATIVE_INFINITY+" and Double.POSITIVE_INFINITY = "+Double.POSITIVE_INFINITY+")", isFinite);
	}
	
	@Test
	public void booleans() {
		/**
		 * Interesting feature to test if given array contains expected number of "trues". 
		 */
		boolean[] tested = new boolean[]{true, true, true, false};
		int expectedTrues = 3;
		int trueCounted = Booleans.countTrue(tested);
		assertTrue("expectedTrues ("+expectedTrues+") should be the same as counted trues ("+trueCounted+") but it's not", trueCounted == expectedTrues);
	}
}

This article shows that some of Google Guava's features, as join() or asList() methods, are susceptible to be used very often. Thanks to them, we can avoid to reinvent the wheel and accelerate programming process.

If you liked it, you should read:

The comments are moderated. I publish them when I answer, so don't worry if you don't see yours immediately :)

📚 Newsletter Get new posts, recommended reading and other exclusive information every week. SPAM free - no 3rd party ads, only the information about waitingforcode!