Thursday 10 September 2009

JDK 7 - Method suggestions

Joe Darcy has opened up a call for methods to add to the core JDK 7 lang/util package. The idea is to add methods that are "commonly-written utility methods".

JDK 7 utility methods

There is lots of prior art in the field of general utility method libraries for Java. Perhaps the best known is Apache Commons Lang. Lets choose some methods (other than string utilities) that might be appropriate for the JDK from ObjectUtils.

1) Null-safe equals check.
boolean Objects.equals(Object object1, Object object2)
Returns true if both are null, or both are non-null and equal.

2) Null-safe hash code.
int Objects.hashCode(Object object)
Returns the hash code of the object, or zero if the object is null.

3) Null-safe toString.
String Objects.toString(Object object)
Returns the toString of the object, or "" if the object is null.

4) Null-safe toString with specified default.
String Objects.toString(Object object, String defaultStr)
Returns the toString of the object, or defaultStr if the object is null.

5) Null-safe object defaulting.
T Objects.defaultNull(T object, T defaultValue)
Returns the object, or defaultValue if the object is null.
(Of course, the Elvis operator is a much clearer approach for this common case...)

6) Get maximum and minimum.
T Objects.max(T comparable1, T comparable2)
T Objects.min(T comparable1, T comparable2)
Returns the maximum/minimum object, returning the non-null object if either is null, or null if both are null.
Generics allow the return type to check the input is also a Comparable.

These would appear to be the obvious methods to add to an Objects utility class.

The following are missing methods on Integer/Long:

7) Compare two primitives.
int Byte.compare(byte value1, byte value2)
int Short.compare(short value1, short value2)
int Integer.compare(int value1, int value2)
int Long.compare(long value1, long value2)
Safely returns the comparison (-1/0/1) indicator for two primitives. (These methods already exist on Float/Double).

The methods from SystemUtils can be very useful in reducing arbitrary strings in code:

8) Get common environment variables.
File System.getJavaIoTempDir()
File System.getJavaHomeDir()
File System.getUserHomeDir()
File System.getUserDir()
Returns the java environment variables. There are lots more in SystemUtils that could be added.

Some more null handling methods for primitives - again, Elvis would be a better solution:

9) Convert wrapper to primitive avoiding NPE.
boolean Boolean.booleanValue(Boolean obj, boolean defaultValue)
char Character.charValue(Character obj, char defaultValue)
byte Byte.byteValue(Byte obj)
byte Byte.byteValue(Byte obj, byte defaultValue)
short Short.shortValue(Short obj)
short Short.shortValue(Short obj, short defaultValue)
int Integer.intValue(Integer obj)
int Integer.intValue(Integer obj, int defaultValue)
long Long.longValue(Long obj)
long Long.longValue(Long obj, long defaultValue)
float Float.floatValue(Float obj)
float Float.floatValue(Float obj, float defaultValue)
double Double.doubleValue(Double obj)
double Double.doubleValue(Double obj, double defaultValue)
Safe ways to convert a wrapper to a primitive.
The numeric ones return zero if the default argument isn't specified.
There is also a case for methods to convert arrays of wrappers to arrays of primitives.

These should probably be on a Booleans utility class as per BooleanUtils.

10) Boolean methods for clearer code.
boolean Booleans.isTrue(Boolean booleanObj)
boolean Booleans.isFalse(Boolean booleanObj)
Return true as per the method name, false if null.
boolean Booleans.isNotTrue(Boolean booleanObj)
boolean Booleans.isNotFalse(Boolean booleanObj)
Return true as per the method name, true if null.

11) Negate handling null.
Boolean Booleans.negate(Boolean booleanObj)
TRUE returns FALSE, FALSE returns TRUE, null returns null.

11) Boolean arithmetic.
boolean Booleans.and(boolean[] array)
boolean Booleans.or(boolean[] array)
boolean Booleans.xor(boolean[] array)
boolean Booleans.and(Boolean[] array)
boolean Booleans.or(Boolean[] array)
boolean Booleans.xor(Boolean[] array)
Performs the stated binary maths.

12) Character comparison.
boolean Character.equalsIgnoreCase(char ch1, char ch2)
Compares two characters ignoring case.

These should be constants for empty arrays on common classes:

13) Empty array constants.
Boolean[] Boolean.EMPTY_ARRAY
boolean[] Boolean.EMPTY_PRIMITIVE_ARRAY
Character[] Character.EMPTY_ARRAY
char[] Character.EMPTY_PRIMITIVE_ARRAY
Byte[] Byte.EMPTY_ARRAY
byte[] Byte.EMPTY_PRIMITIVE_ARRAY
Short[] Short.EMPTY_ARRAY
short[] Short.EMPTY_PRIMITIVE_ARRAY
Integer[] Integer.EMPTY_ARRAY
int[] Integer.EMPTY_PRIMITIVE_ARRAY
Long[] Long.EMPTY_ARRAY
long[] Long.EMPTY_PRIMITIVE_ARRAY
Float[] Float.EMPTY_ARRAY
float[] Float.EMPTY_PRIMITIVE_ARRAY
Double[] Double.EMPTY_ARRAY
double[] Double.EMPTY_PRIMITIVE_ARRAY
String[] String.EMPTY_ARRAY
Class[] Class.EMPTY_ARRAY
Object[] Objects.EMPTY_OBJECT_ARRAY
Return true as per the method name, true if null.

Or a better solution might be a method on Class:
T[] Class.emptyArray();
This allows code like:
Boolean.class.emptyArray();

14) Array size.
boolean Arrays.isEmpty(Object[] array)
int Arrays.size(Object[] array)
Where a null array is empty/size-zero.

15) Collection size.
boolean Collections.isEmpty(Collection coll)
int Collections.size(Collection coll)
boolean Collections.isEmpty(Map map)
int Collections.size(Map map)
Where a null collection is empty/size-zero.

Or, even better, add a new interface Sized with a single method to get the size. This would be retrofitted to Collection, Map, String and arrays (of course, technically, thats a language change...).

And some Class utilities for less NPEs when outputting the class name in debugging:

16) NPE safe way to get class name.
String Class.getName(Class cls)
String Class.getSimpleName(Class cls)
String Class.getPackageName(Class cls)
Returns the class name, or null if the input is null.
Again, the null-safe operators would avoid this kind of specific method.

Locale could do with some love, as per LocaleUtils:

17) Parse a locale string.
Locale parse(String localeStr)
Parses the locale string to a locale.

16) Country/Language lists.
List Locale.countriesByLanguage(String langaugeStr)
List Locale.languagesByCountry(String countryStr)
Extracts just countries or languages.

A better solution would be two new classes Country and Language. These would be useful in other places in the JDK, where a locale is used and it should be a country or language.

And the big one that I've missed - safe maths, as per Joda-Time FieldUtils:

18) Add/subtract/multiply/cast safely.
int Math.safeToInt(long value)
int Math.safeNegate(int value)
long Math.safeNegate(long value)
int Math.safeAdd(int value1, int value2)
long Math.safeAdd(long value1, int value2)
long Math.safeAdd(long value1, long value2)
int Math.safeSubtract(int value1, int value2)
long Math.safeSubtract(long value1, int value2)
long Math.safeSubtract(long value1, long value2)
int Math.safeMultiply(int value1, int value2)
long Math.safeMultiply(long value1, int value2)
long Math.safeMultiply(long value1, long value2)
These perform the specified mathematical operation, but throw ArithmeticException, rather than failing quietly, if overflow occurs.

I should also note that there are a whole heap of BigDecimal, BigInteger and String utilities that could be added.

Finally, of course, there are lots of other utility class libraries apart from Commons-Lang. Its simply that Commons-Lang provides a good point to start the discussion.

Feel free to (a) complain about my choices, and (b) suggest your own ideas. Remember to focus on non-string core classes for now.

34 comments:

  1. Seems like 90% of these would be unnecessary if Java had Groovy's null safe dereference (foo?.bar()) and default value (foo ?: bar) operators.

    ReplyDelete
  2. String Collections.toString(Collection[?] c, String separator)

    [T, U] Iterable[U] Iterables.map(Iterable[T] it, F[T, U] f)

    [T] Iterable[T] Iterables.filter(Iterable[T] it, F[T, Boolean] f)

    [T, R] R Iterables.foldLeft(Iterable[T] it, R r, F2[T, R, R] f)

    [T, U] Iterable[U] Iterables.cast(Iterable[T] it, Class[U] clazz) //convenience equivalent to Iterables.map

    interface Equalable[T] { boolean areEqual(T t, T u); int hashFor(T t); }

    new HashSet[T](Equalable[T] equalable)

    java.util.immutable.List (effectively a cons pair, as found in the javac source and the Scala standard library)

    new java.util.ArrayList[T](F[Integer, Integer] growthFunction)

    java.util.Pair[A, B] //immutable pair of values, like Map.Entry, but without the overly specific names.

    Pair would have methods .first(), .second() and T Pair.[T]extract(F[A, T] fa, F[B, T] fb)

    If you need any of these fleshing out let me know, but I think they're pretty obvious by their signatures.

    ReplyDelete
  3. The boolean set operations in 11) would be nice with varargs

    ReplyDelete
  4. #13 is my favourite!

    I don't like any of the null-masking stuff. There just aren't many places where careful programs need to worry about null! If a value could be null, canonicalize it to an empty or false and get on with life.

    ReplyDelete
  5. I agree with Jesse Wilson: except for ObjectUtils methods, I don't see the point of all these null-masking stuff. It doesn't prevent any NPE, it just moves them somewhere else...

    I would also recommend checking google-collections in addition to commons-collections, they have a set of utility classes with many such really useful methods.

    ReplyDelete
  6. I also dislike the null-masking stuff for the reason mentioned above. It just moves NPEs to somewhere else.

    On the other side, after diving so deep into Haskell (e.g. Maybe monad) and Scala (e.g. Option case class) in past months, I don't even try to think about improvements in Java anymore. Suddenly I see all the "good" solutions, but all of them are just to complicated (verbose) with Java syntax :( And my colleages will soon kill me, when they discover my latest scala-like code contributions.

    ReplyDelete
  7. I really hope that the Sized interface is made part of the JDK. Would make things a lot more consistent.

    ReplyDelete
  8. I am in favour of the Objects.equals method, but not the other null masking methods.

    I think Character.equalsIgnoreCase is questionable as it encourages people to write code that does not correctly handle some languages (e.g. German).

    ReplyDelete
  9. Stephen Colebourne10 September 2009 at 11:22

    Jesse, Francois, Ygor, Mark - Do you really work in a world of perfect programs? Where you never see a null value or a NPE? Where you have a team of only A-grade developers? In every piece of code and team I've seen or dealt with, nulls are everywhere. Thats why Elvis/Null-safe are good additions to Java, and if we can't have those, why null-handling methods are next on the list.

    On Character.equalsIgnoreCase, most users would just compare upper to upper, or lower to lower, and not both. As such, the method has value.

    ReplyDelete
  10. I too am against most of the null-masking stuff except for the tedious-boiler-plate-eliminating Objects.equals(). Much better to continue to educate people to THINK about null/empty return values. Almost every one of my methods and values has a comment ends with "...; not null." or " ...; never null but may be empty." etc as it's so important to generating robust and optimisable code that can avoid redundant pipeline-breaking conditionals...

    I'll take your equalsIgnoresCase() is I've just found myself rewriting a bunch of String methods to work more generically on CharSequence...

    Rgds

    Damon

    ReplyDelete
  11. Nice list.

    I'm also against the null-handling methods though. I think that my main argument would be that code using them may actually become less reliable, since if you are going to get an unwanted NPE, it is better to get it near it's source so you can fix the bug.

    We don't use null's here (at least, not exposed outside a method body, and preferably not even then). We just handle them at the bounderies. Usually this can be done with an Option-like class, e.g.

    Option.maybe(map.get(key))

    you can then do things like (with static import):

    maybe(map.get(key)).getOrElse(defaultValue)

    ReplyDelete
  12. "On Character.equalsIgnoreCase, most users would just compare upper to upper, or lower to lower, and not both. As such, the method has value."

    They should be encouraged to use String.equalsIgnoreCase or an appropriate Collator. Using a Character.equalsIgnoreCase will inevitably lead to anomalous results in some locales.

    ReplyDelete
  13. I'd be a lot more interested in utility classes if we had extension methods as a mechanism to bring these into proper context.
    Apart from StringUtils and DateUtils (which JSR-310 will fix?!) all I could want is:

    T ObjectUtils.deepCopy(T t)
    long ObjectUtils.approxSize(T t)
    String ObjectUtils.dynamicToString(T t)
    Number NumberUtils.round(Number value, int precision)
    Number NumberUtils.roundUp(Number value, int precision)
    Number NumberUtils.roundDown(Number value, int precision)
    Number NumberUtils.random(Number min, Number max)

    ...and a System.restart() but that's not exactly a utility method.

    ReplyDelete
  14. With regard to null handling I certainly don't live in a perfect world, I simply do not agree that the null handling methods are the right response (equals excepted).

    ReplyDelete
  15. Here is my submission, some additional String conversion methods for the String class, similar to "String.toLowerCase()" and "String.toUpperCase()"

    1. String.toTitleCase() / String.capitalize()

    This String conversion method should be and should make all first letters of the Words in a String capitalized and the rest lowercased

    2. String.toSentenceCase()

    This String conversion method should make only the first letter of group words ending with a period capitalized and the rest lowercased. The implementation can use a String.split() to split the sentence into segments based on "." (periods) and then reconstruct it with the first letters of the sentences capitalized.

    3. String.toAlternateCase() (can be renamed)

    This should make all LowerCased letters UpperCased, and vice versa

    ReplyDelete
  16. I'm NOT against all null-masking, because some utility methods would support values that may be legally null. For one thing, when working with primitive arrays I often use (or at least support) null instead of empty arrays; I know the "best practice" of preferring empty arrays, but nulls are often more efficient and convenient.

    Now my $.02, pulled from the 'Util' class that I've been maintaining for >10 years and would love to deprecate with JDK7 :)

    - double parseDouble (String param) (and similar methods for other numeric types and also boolean): Parses param, but handles nulls (returning 0) and trims the input (ignoring spaces); API methods like Double.parseDouble() would raise exceptions in both cases.

    - String stringOf (char c, int count); String stringOf (String s, int count): Returns a string with count times some character or substring. Optimized for the common case of c/str = ' ' and small values of count (just return a substring of a static constant string with many spaces). Would ideally be new String constructors.

    - T[] subarray (T[] arr, int first, int last, boolean forceNew); T[] arrayadd (T[] arr1, T[] arr2, boolean forceNew); T[] arrayadd (T[] arr1, int first1, int last1, T[] arr2, int first2, int last2, boolean forceNew); T[] arrayadd (T[] arr, int pos, T elem); T[] arrayremove (T[] arr, int pos); T[] arrayremove (T[] arr, int first, int last, boolean forceNew): Operations on primitive arrays. Optimized for several cases like a subarray that returns the entire original array (0 .. length-1), a concatenation with at least one empty array, etc.; in these cases, the parameter forceNew==true forces to return a new array, if the caller wants to avoid aliasing (important for APIs doing defensive copying). Support smart defaults like null => [] and last* == -1 => length - 1 of the corresponding array.

    - toString (Object[] arr): Formats the array as "[a, b, c, ...]"; includes handling of subarrays and nulls.

    Another suggestion: whatever is the set of new methods, we could have automatic static-import of several important classes of this type (bundles of static utility methods), like: System, all the wrappers, Collections, Arrays, Console, and the new Objects class. Yeah, just put all that stuff in the global scope so I don't need to import such basic things as println() (that are language-level primitives in many scripting languages). Do this automatic import only for -source 1.7, to the benefit of legacy code that could have some conflict.

    ReplyDelete
  17. I like most of the suggestions, even the null safe ones. Like anything I think there are places where they can be useful and places where they will cause trouble.

    I wrote a class called "Each". It converts iterators to iterables with the "of" static method.
    Iterator stringIterator ...

    for(String item : Each.of(stringIterator)){
    ...
    }

    You could also write one that takes the old Enumeration class.

    Also I was always disappointed that I can't create an ArrayList from an array. You would think that ArrayList would have a constructor that took and array.

    I'll post more as I think of them.

    ReplyDelete
  18. @Damon Hart-Davis

    Nope Arrays.asList() returns a java.util.Arrays.ArrayList not a java.util.ArrayList. The java.tuil.Arrays.ArrayList class throws an UnsupportedOperationException if you try to add or remove anything from it.

    ReplyDelete
  19. (Not that we should necessarily continue this discussion here, but when I want to do the sort of thing I think you are, I wrap my raw array with asList() and pass it as an arg to an ArrayList constructor to make a mutable copy...)

    ReplyDelete
  20. @Collin
    I have always wondered why the for-each construction introduced in Java 5 cannot be used directly with Iterators intead of Iterables?

    I worked around this with a solution similar to yours but more complete because my class can also convert Enumeration and Iterator in both ways anywhere an API accept one and not the other (it implements all 3 interfaces Iterable, Iterator and Enumeration).

    Iterator iterator = new EnumerationIterator(enumeration);

    Enumeration enumeration = new EnumerationIterator(iterator);

    for (String s: new EnumerationIterator(stringIterator) {}
    or with a static factory:
    for (String s: EnumerationIterator.iterate(stringIterator) {} //you can shorten to just iterate(stringIterator) with static import EnumerationIterator.iterate

    ReplyDelete
  21. @efl

    I think a lot of people would have liked it if the for-each loop worked with more then itererabls and arrays. I think this code has been written a number of times and is a good candidate for the JDK. Our are very similar. I just used a static "factory" method to keep the typing down.

    ReplyDelete
  22. Thanks to Stephen for contributing his methods both here and on the OpenJDK core-libs-dev list.

    Commenters on this blog should be aware that an informed discussion is happening on the core-libs-dev list. That is where decisions are made.

    If you want to make a "submission" or give "your $0.02" in a comment on this or any other blog, that is your choice. Just know that nothing will happen as a result.

    ReplyDelete
  23. Stephen Colebourne11 September 2009 at 00:49

    @Kieron, Not sure whether to congratulate you or commiserate you for trying to use Option/Maybe like functionality. I'd strongly argue that beyond a few experts this kind of approach is never used in Java (Java isn't a functional language, and trying to apply functional patterns is just a bad idea IMO).

    @Casper, Any deepCopy or similar for hashcode/toString is fraught with risk due to object graph cycles and reflection. These are best avoided in the JDK lang/util area.

    @Collin, Your Each.of method is similar to Ricky's Iterables. I think some Iterable methods could be a good inclusion, although again they tend to encourage a functional style which is inappropriate for Java.

    I also agree on a direct way to create an ArrayList - however, Project Coin is providing that :-)

    @Osvaldo, I thought about including the list-like methods for arrays, but decided against. I believe that in general if you need a list then you should use a list.

    @Alex, Your point is well made, however a forum like this allows more people to get involved (as is being ably demonstrated). Whether anything happens as a result of the discussion is partly up to the individual authors.

    ReplyDelete
  24. @Stephen: Completely agree that this forum lets people get involved easily, and that some good ideas will probably come out of that.

    Just wanted to emphasize that someone with specific methods in mind already has a clear course of action if they are serious: join core-libs-dev.

    If they don't join core-libs-dev, then they're relying on someone else to read their comments here and in turn communicate them to core-libs-dev.

    ReplyDelete
  25. I would like to get a java.text.Format for all boxed primitive types (not sure I want them localized), like Boolean, Integer (and not Number) etc.

    And I would like to easily get the boxed type for a primitive type and converse (boolean.class<->Boolean.class).
    These are the most boring piece of infrastructure to write when dealing with introspection. And I think we all deal a lot with it nowadays.

    and maybe a
    static T[] Collections.asArray(Iterable, Class)
    based on java.lang.reflect.Array.newInstance() .

    ReplyDelete
  26. So... much... boilerplate... ... eyes... bleeding.

    All necessary in Java, alas.

    WBJUS.

    ReplyDelete
  27. "No" from me to most of the null-masking methods. This encourages careless null propagation, which forces defensive coding, which leads to even more boilerplate.

    In my experience, educating people by requiring explicit @Nullable annotations for api parameters, backed up by automated static analylis (jsr308 checker framework) works much better. It still gives people freedom to return/pass null, but makes the conceptual cost explicit.

    ReplyDelete
  28. Perhaps:

    Arrays.reverse(T[]);

    and its primitive counterparts?

    ReplyDelete
  29. If individuals think that pasting pages of code into a comment on a private blog is the way to contribute to the JDK, then I give up.

    ReplyDelete
  30. I wasn't trying to paste the code, just the signatures. Soooorry.

    ReplyDelete
  31. I wasn't trying to paste the code, just the signatures. Soooorry.

    ReplyDelete
  32. BTW i think the readInto, writeObjects, readObjects (if it can made to return multiple arguments, like a iterator that autocloses or something when its done), and xml variants are nice. Not to speak of the close method that will be adressed by ARM.

    ReplyDelete
  33. I come down in the camp of not adding many null-handling methods. What is the single most commonly thrown exception in Java? NullPointerException, easily. Why? I would argue that null has been overloaded in Java, and is in use to mean "not initialized" "no value" "error condition" "empty object" and other things besides.

    My current practice is to try to use null *only* as an error condition, and never pass null to signify anything else. I think that many of the additions you propose would encourage using null as an object placeholder, which I think undesirable, and in fact to be one of the largest problems facing the Java programmer in practice.

    ReplyDelete

Please be aware that by commenting you provide consent to associate your selected profile with your comment. Long comments or those with excessive links may be deleted by Blogger (not me!). All spam will be deleted.