• Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Ron McLeod
  • Paul Clapham
  • Rob Spoor
  • Liutauras Vilda
Sheriffs:
  • Jeanne Boyarsky
  • Junilu Lacar
  • Tim Cooke
Saloon Keepers:
  • Tim Holloway
  • Piet Souris
  • Stephan van Hulst
  • Tim Moores
  • Carey Brown
Bartenders:
  • Frits Walraven
  • Himai Minh

no guarantees -- keep harping on this

 
Bartender
Posts: 1068
33
Eclipse IDE Postgres Database C++ Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I have seen quite a number of responses, some from more people than others (I am thinking of YOU, Campbell) where posters are warned against making assumptions that are not guaranteed to be true.

I normally say that I find working in Java to be much more relaxed and pleasant than trying to write Portable C++ code because there is so much Undefined Behavior to be on the watch for in C++, if you do something that is "not defined" you may or may not compile, may or may not link, may or may not blow up at runtime and as soon as you move to a different platform or compiler or compile options, rinse and repeat.

In my mind, compared to that world, you can just forget about Undefined Behaviors in Java, there is no comparison.

But paying more attention, they still exist, and worse still, a large percentage, I would say a majority of people teaching/tutoring/presenting on Java basically just ignore them.

I described the practice of ignoring the Very Important expository material in the Javadocs in favor of covering all the different method calls in classes and interfaces "The Race to the Bottom".

I now realize that the same sorts of popular materials also ignore various warnings even in the API descriptions for the methods themselves.  Like, totally completely ignore them.

The Javadocs contain the words "no guarantees" a surprisingly high number of times.

I first noticed it here:
public static <T> Collector<T,​?,​List<T>> toList()
Returns a Collector that accumulates the input elements into a new List. There are no guarantees on the type, mutability, serializability, or thread-safety of the List returned; if more control over the returned List is required, use toCollection(Supplier).

And then I realized that even being relatively aware of this phenomenon and calling others out on it, I've been writing sample code and doing exercises that often did make assumptions about "no guarantees" things.  Even me.

I know from watching presentations by the Java team members themselves, there are things that they have wanted to fix or change and then couldn't because too many boneheads were relying on behaviors that weren't guaranteed not to change but they, and more importantly code they'd written and left behind at other job sites, were relying on.  If this is widespread enough, it can retard improvements in certain areas of Java.  Otherwise, your code that depended on "no guarantees" things (I am not sure if this is the only category of Undefined Behavior in Java) may just break when you upgrade Java versions, or at other times that are hard to predict.

Mostly, I wanted to thank the people here who vigilantly warn against posters and readers doing this, not enough sources do so.

Also, I noticed some handy additions to Collectors in Java versions past 8, it was while looking at these I had the "Oh, snap!!  I am sometimes ignoring the warnings too, despite reminding others" ephiphany:

public static <T> Collector<T,​?,​List<T>> toUnmodifiableList()
Returns a Collector that accumulates the input elements into an unmodifiable List in encounter order. The returned Collector disallows null values and will throw NullPointerException if it is presented with a null value.  Since: Java 10

public static <T> Collector<T,​?,​Set<T>> toUnmodifiableSet()
Returns a Collector that accumulates the input elements into an unmodifiable Set. The returned Collector disallows null values and will throw NullPointerException if it is presented with a null value. If the input contains duplicate elements, an arbitrary element of the duplicates is preserved.  Since: Java 10

These little things like "Not presuming that something you get back from an API call is, or is not mutable unless that is guaranteed" are exactly the sort of things I think it is important to pay attention to in order to have good, robust code rather than just "Hey, it passed all the tests I wrote, nailed it!"

There really are more of these kinds of things lurking in Java than I'd thought of previously, any other watchwords besides "no guarantees" might be good to alert people to.

 
Marshal
Posts: 73767
332
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I think that part of what you are seeing is a better understanding of documentation comments and what sort of contract they make on behalf of the methods. Documentation comments have improved since Java8, and so have some of the parameter names. Look at the different names in this (older) and this (newer).
Another thing you might find in documentation comments is, “The current implementation . . . but this may be changed in future releases.” Obviously you can't change the semantics of a method without breaking dependent code, but you can change implementation details, which might be needed for overriding, as described in Joshua Bloch's Effective Java, 2/e page 87 or 3/e page 93.
Any information given in a documentation comment becomes part of the guarantees provided by that method, so saying, “...no guarantee...” is simply part of good practice.
 
Campbell Ritchie
Marshal
Posts: 73767
332
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Note that unmodifiable List as described by Collectors#toUnmodifiableList() is different from what Collections#unmodifiableList() does. It is called an unmodifiable view. The only difference since JDK1.3 is that “unmodifiabkle view” has changed from ordinary text to the link I quoted above.
You know, somebody ought to have a reminder to read the f***ing Javadoc documentation.
 
Saloon Keeper
Posts: 24214
167
Android Eclipse IDE Tomcat Server Redhat Java Linux
  • Likes 3
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
When you see a "no guarantees" warning, it's an indication that you should be treating the functionality in question as a "black box" and not try and design code based on its current implementation.

In the early days of computing, it was common to exploit undocumented features. For example, microchips were optimized by folding down their opcode space and - depending on the processor version, often useful, but undocumented, unsupported, and likely to not work properly in all cases or on all processor batches. And then there was the HCF undocumented instruction. Halt and Catch Fire, which, when executed, would likely lock up the CPU to the point that only a complete power cycle could re-enable it.

I fell for this myself once, exploiting some ROM code on my first machine (a Cromomenco Z-2D). I was calling it in application code when the system had a blowout and I had to get a new processor card from the manufacturer. But they'd updated the ROM and the logic I'd been calling didn't live there anymore. Broke several programs that way.

Closer to home, the traditional internal implementation of java.lang.String was an immutable array of 16-but Unicode values. But since somewhere around Java 8 or 9, String can now contain values in either of 2 very different formats - legacy 16-bit or UTF format (since, for example, emoticons aren't part of the base 16-bit codepage). Anything that relied on the older behavior being the only one would have broken.

Java is less susceptible than most languages to the problems of people mucking around with class internals, since most internals cannot be accessed except via the official methods. But that still leaves toom for radical alteration in execution time, storage requirements and implementation of inner classes.

The other way - probably  most common way - to fall afoul of the "no guarantees" warnings is to violate the Domain and/or Range aspects of a method/class. One of my biggest peeves about so many method JavaDocs is that they don't explicitly what the valid Domains and Ranges are or what's going to happen if you violate them. That, to me is an implicit "no guarantee", and it means that I cannot write certifiably reliable code based on those methods.
 
Jesse Silverman
Bartender
Posts: 1068
33
Eclipse IDE Postgres Database C++ Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Campbell Ritchie wrote:I think that part of what you are seeing is a better understanding of documentation comments and what sort of contract they make on behalf of the methods. Documentation comments have improved since Java8, and so have some of the parameter names. Look at the different names in this (older) and this (newer)..


Got that, and deeply appreciate it on the part of whoever is making that happen.  I notice better attention to giving people the inclusive/exclusive option and making it clear which is which in the difference between SortedSet (.headSet(), .tailSet(), .subSet() ) and NavigableSet for instance, where we now get .ceiling(), .floor(), .higher() and .lower() and the earlier entries can be inclusive or exclusive / closed/open on upper bounds.  So instead of "Remember the Rule" you get "Have it your way" and easy to see which it is when reading the code.  But I am sure there are other places besides the example you gave where the changes are just clear documentation, too.

Campbell Ritchie wrote:
Another thing you might find in documentation comments is, “The current implementation . . . but this may be changed in future releases.” Obviously you can't change the semantics of a method without breaking dependent code, but you can change implementation details, which might be needed for overriding, as described in Joshua Bloch's Effective Java, 2/e page 87 or 3/e page 93.
Any information given in a documentation comment becomes part of the guarantees provided by that method, so saying, “...no guarantee...” is simply part of good practice.



In the Old Days, back when I always treated Java as a second, third, or fourth language I noticed a lot of Careful Stuff warning about not assuming implementation details of a particular flavor of Java SE, because there were many choices of Compiler and JVM (not different numbered versions, but implementation from different sources).  We don't see much of that nowadays as OpenJDK has become so extremely pre-dominant, but similar warnings are appropriate to not create dependencies on the way something happened to work (in a not guaranteed way, just how it seemed to be) in say Java 8, that may not still happen to be that way in Java 11 or 17, etc. -- so the same advice to caution is still appropriate, for a slightly different reason.  One must note the extreme caution and pains taken by the Java Team to not break existing code, so clearer documentation may be part of their attempt to not get stuck in time with particular implementation details because lazy coders relied on them.

An interesting variation on Fuzz Testing https://en.wikipedia.org/wiki/Fuzzing could be a compiler/JVM variant that intentionally changes the behavior in every area that says "no guarantee".  If your code breaks during such a run, you depended on what I am calling "Undefined Behavior", if it still runs fine, you probably didn't.  This would have seemed really academic to me during most of the early parts of my career, because we were using over a dozen different C compilers on about twenty different platforms with varying compiler options and running the results against huge batches of tests.  So all our testing was #%$$^%$ Fuzz Testing.  All the time.  Always.  Lots and lots of Fuzzy Fun!
 
Jesse Silverman
Bartender
Posts: 1068
33
Eclipse IDE Postgres Database C++ Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Tim Holloway wrote:When you see a "no guarantees" warning, it's an indication that you should be treating the functionality in question as a "black box" and not try and design code based on its current implementation.


Agreed, but I will distinguish two related phenomena.  1. "Too Clever".  Something isn't guaranteed, but the "too clever" programmer runs a bunch of detailed experiments, decides they know how it works, and writes code that relies on and exploits that to great present effect and later peril.  2. "What Me Worry?" programmers barely read the docs except to find a method overload that sounds like it does what they want and compiles, (hopefully) run a few tests and check it in.  Both are anti-patterns but what you describe is case 1 of the two variants.  I see a lot of Alfred E. Neumann code and tutorials.

Tim Holloway wrote:
Closer to home, the traditional internal implementation of java.lang.String was an immutable array of 16-but Unicode values. But since somewhere around Java 8 or 9, String can now contain values in either of 2 very different formats - legacy 16-bit or UTF format (since, for example, emoticons aren't part of the base 16-bit codepage). Anything that relied on the older behavior being the only one would have broken.


I'd already gone way beyond OCJP-scope on Strings in other threads, but .getBytes() and .getChars() should always still work the same, right?  Not sure how easy it is to break your code in the light of this change, but I will Be Aware that someone may have managed to somehow.

Tim Holloway wrote:
Java is less susceptible than most languages to the problems of people mucking around with class internals, since most internals cannot be accessed except via the official methods. But that still leaves room for radical alteration in execution time, storage requirements and implementation of inner classes.


Definitely, and while most users probably would never even notice the difference, let alone mind, Oracle is Very Concerned about the Really High Performance-Needs Customers who would totally scream at them, or worse yet, jump ship.  We hear about this stuff all the time as the answer to "Why don't you just (do this thing I would like)?"

Tim Holloway wrote:
The other way - probably  most common way - to fall afoul of the "no guarantees" warnings is to violate the Domain and/or Range aspects of a method/class. One of my biggest peeves about so many method JavaDocs is that they don't explicitly what the valid Domains and Ranges are or what's going to happen if you violate them. That, to me is an implicit "no guarantee", and it means that I cannot write certifiably reliable code based on those methods.


Agreed, and on that same general theme, one of the things I had overlooked upon first "coming back to Java" was all of these methods in Math like:
public static int addExact​(int x, int y)

Which just makes it feel like they have a different attitude towards making Java safe for Space Missions, Killer Robot Controls, Power Plants, etc. -- I am sure there are a lot of other changes in making it simpler to write Truly Reliable code in the way you are talking about that I am not aware of yet.
 
Campbell Ritchie
Marshal
Posts: 73767
332
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Jesse Silverman wrote: . . . many choices of Compiler and JVM (not different numbered versions, but implementation from different sources). . . .

But one Language Specification (=the JLS), which they all have to comply with.
 
Ranch Hand
Posts: 92
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Tim Holloway wrote:When you see a "no guarantees" warning, it's an indication that you should be treating the functionality in question as a "black box" and not try and design code based on its current implementation.



This seems to line up with the definition of "undefined behavior" in C and C++.

C certainly has many more undefined behaviors than Java. I for one, as we all did (or else we wouldn't be on this forum), appreciate that simplification!
 
Campbell Ritchie
Marshal
Posts: 73767
332
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Damon McNeill wrote:. . . This seems to line up with the definition of "undefined behavior" in C and C++. . . .

Please explain; I don't think what Tim H was talking about undefined behaviour at all.
 
Tim Holloway
Saloon Keeper
Posts: 24214
167
Android Eclipse IDE Tomcat Server Redhat Java Linux
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Campbell Ritchie wrote:

Jesse Silverman wrote: . . . many choices of Compiler and JVM (not different numbered versions, but implementation from different sources). . . .

But one Language Specification (=the JLS), which they all have to comply with.



The devil, of course, is in the details.

Incidentally, long ago I worked in the Mortgage Industry. Our company had established a de facto standard data interchange record format. At one time, I was working in a department that would asses the present and future values of the mortgage portfolios of their clients and if their clients were customers of the company's main product, that generally meant that they used that record format as a means of providing us with what we needed to work with.

The first step in adopting a new client was to set up a custom ETL system that would convert their "standard" data to our in-house version of the "standard" data. Because everyone had their own way of interpreting the standard.

Incidentally, I now regret having worked in that department. Once a client knew the "value" of their mortgages, they'd do one or both of the following: use is as ammunition in a merger/acquisition or issue mortgage-backed securities against them. If the term "mortgage-backed securities" sounds familiar, it's because that's the root of the collapse that brought on the Great Recession. The M&A part, of course, helped in building banks that were Too Big to Fail. Thus, I unwittingly helped in building the house of cards that has had consequences down to the present day world-wide. Sorry, millennials.

Ironically, our service was a flat rate based per-client, and since we were actively helping clients buy or be bought by other banks who were often also clients, every year we ended up with about half as much income as the year before. The department was reduced and sold off at the end - though this was before the global meltdown.



Most of the "gotchas" I know of in C have to do with users failing to pay attention to uninitialized objects. But I did have a lot of grief attempting to create a C++ compiler because its overloading mechanism was a lot more complex than Java's and there were cases where it wasn't possible for me to disambiguate, but at the same time I don't think I was allowed to indicate an outright error. Hopefully they fixed that at some point, but I no longer follow the standard that closely.
 
Jesse Silverman
Bartender
Posts: 1068
33
Eclipse IDE Postgres Database C++ Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Campbell Ritchie wrote:

Damon McNeill wrote:. . . This seems to line up with the definition of "undefined behavior" in C and C++. . . .

Please explain; I don't think what Tim H was talking about undefined behaviour at all.



Here's what is in common, Campbell.

There is behavior that is not defined and can not be relied on.

You can run an experiment or two and say, look at the generated code and say "Great, I like this, my code depends on it."

Your code will compile link and run as you expect, but may run completely differently, crash, or give nutso results on a different compiler or even different compiler settings.

If you "know" what one of these no-guarantees things does because you run some experiments on your Java 8, 32-bit windows JDK/JVM, you may decide you "know what it does" and write code that depends on those results.

then you move to Java 10, or 11, or 17, or run in 64-bit or...something.

Now your code doesn't work.

It isn't identical, but is sort of similar.
 
Campbell Ritchie
Marshal
Posts: 73767
332
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I don't think that is what Tim meant about black boxes. In a black box you specify the input and the output and nothing in between. The implementation might be hidden, but the behaviour is well defined. I think Tim said the functionality is fixed, as far as it is documented. He also showed that relying on undocumented features is a recipe for disaster later on.
 
Jesse Silverman
Bartender
Posts: 1068
33
Eclipse IDE Postgres Database C++ Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Subtle but good point on the difference.
 
Water proof donuts! Eat them while reading this tiny ad:
the value of filler advertising in 2021
https://coderanch.com/t/730886/filler-advertising
reply
    Bookmark Topic Watch Topic
  • New Topic