More on this book
Community
Kindle Notes & Highlights
One important decision you'll have to make when implementing a toString method is whether to specify the format of the return value in the documentation. It is recommended that you do this for value classes, such as phone numbers or matrices. The advantage of specifying the format is that it serves as a standard, unambiguous, human-readable representation of the object. This representation can be used for input and output and in persistent human-readable data objects, such as XML documents. If you specify the format, it's usually a good idea to provide a matching static factory or constructor
...more
Beware, this is much less often true than it sounds. Enough so that I would make the recommendation: Do not plan to do this except for a few, well-thought-out exceptions...
`toString()` is for _developer_-friendliness. There are many different ways to even represent numbers, dates, phone numbers, (doesn't matter if those are going into XML or JSON), and those all deserve a 1st-class, purpose-built serialization format and method for transferring between systems (or even storing for your later self, whose needs do change). Further, there are often multiple formats in use (e.g., your input dates might be in one format, while your output is a different format), and it's best to have explicit serializers (whether classes or methods) for each of those.
This is just as true for user-friendliness, too. The context in which these strings might show up can vary a lot (maybe the area code should be smaller than the rest of the phone number. should the country code be part of the string?) and `toString()` simply isn't the best tool to ensure each of your UIs can show the right string the right way.
Among the exceptions, are those objects that represent things that are strings by definition, that have one well-defined, correct string representation. An example of that are URIs; a string-based specification for resource identification.
For example, consider the BigDecimal class, whose compareTo method is inconsistent with equals. If you create a HashSet instance and add new BigDecimal("1.0") and new BigDecimal("1.00"), the set will contain two elements because the two BigDecimal instances added to the set are unequal when compared using the equals method.
The compiler inserts invisible casts for you and guarantees that they won't fail (assuming, again, that all of your code was compiled with a generics-aware compiler and did not produce or suppress any warnings).
This glosses over the fact that Generics is only a language construct. That is, the compiled output (bytecode) would be identical for code that had generics and code that doesn't-but-is-otherwise-identical.
In other other words, Generics is only a coding & compile-time protection; it has no direct runtime impact.
A Favorites instance is typesafe: it will never return an Integer when you ask it for a String. It is also heterogeneous: unlike an ordinary map, all the keys are of different types. Therefore, we call Favorites a typesafe heterogeneous container.
I don't like this pattern. You're basically creating a path for consumer errors to be silent. That is, if the user put in a string then tried to get an int, it just returns 'null' rather than failing, which is likely to cause confusion or even go unnoticed (by the developer, but noticed by end users) longer.
In release 1.6, it became legal to use the Override annotation on method declarations that override declarations from interfaces as well as classes. In a concrete class that is declared to implement an interface, you needn't annotate methods that you believe to override interface methods, because the compiler will emit an error message if your class fails to implement every interface method. Again, you may choose to include these annotations simply to draw attention to interface methods, but it isn't strictly necessary.
But you should because it has the possibility of letting you know if the interface changes to no longer define that method.
Worst of all, the method could return normally but leave some object in a compromised state, causing an error at some unrelated point in the code at some undetermined time in the future.
I think this (does it "leave some object in a compromised state") should be the most significant decision factor. Otherwise I don't agree that you should _always_ check params for validity. It's not _always_ worth the extra code (and the complexity/maintenance it comes with) and runtime cost to protect a caller from themselves. (This is a little bit like StringBuilder or DateFormat: these are not safe for multi-threaded use, we don't introduce the overhead of making them so, and if you do use them with multiple threads that's your problem).
Another important decision factor: Does the caller need to know why the failure occurred?
think about whether the client-provided object is potentially mutable. If it is, think about whether your class could tolerate a change in the object after it was entered into the data structure. If the answer is no, you must defensively copy the object and enter the copy into the data structure in place of the original.
"Must" is a bit too strong here. Consider whether you need to be part of protecting the caller or the data under your purview. Often times, "caveat emptor" -- with documentation saying so -- should be enough. In other words, consider whether the performance and code complexity tradeoff is worth it.
Let's not blindly advise to always hand-hold the consumer.
As demonstrated by the CollectionClassifier example, overloading can easily confound these expectations.
I've found overloading to often lead to:
1. Questions about the omission of params (what's the behavior of the fewer-parameter variant; i.e., what's the behavior of the other params?)
2. Jumping between the various overloads' javadocs, resolving the differences, and
3. Resorting to inspecting source to see what the difference is.
When overloading is necessary, there's usually a much greater burden of clear and comprehensive documentation.
Don't retrofit every method that has a final array parameter; use varargs only when a call really operates on a variable-length sequence of values.
Perhaps a clearer/simpler way to say this: When the number of parameters varies at compile time from one call to another.
Jeremy liked this
Avoid strings where other types are more appropriate
An interesting exception (a specialized case) is lazy parsing, since parsing can be expensive. That is, you might want to read input into strings, and only if that field is accessed do you parse that into something else (e.g., an HTML string into an Android Spannable). Hibernate ORM can do this.
null;
Bad pattern here: initializing a variable (to null) that can only ever be accessed after being (re)assigned. I recommend starting by not assigning a default value. That will help ensure every path results in a value, and it's clearer to future developers that why (what conditions) results in null (or whatever default value), if any (often time all paths result in a value, and the default — which was misleading — is discarded).
To make this work, you have to take appropriate action if a newer class or method that you are attempting to access does not exist at runtime. Appropriate action might consist of using some alternate means to accomplish the same goal or operating with reduced functionality.
Zxing has an interesting approach to this for Android: it will load an implementation class (of an interface) using reflection based on the Android API level. You provide multiple implementations without having to touch reflection
There is little consensus as to whether acronyms should be uppercase or have only their first letter capitalized.
There's actually both stronger support for just the first letter, and that pattern has good justifications while the all-uppercase pattern does not.
Item 58: Use checked exceptions for recoverable conditions and runtime exceptions for programming errors
A special form of exception translation called exception chaining is appropriate in cases where the lower-level exception might be helpful to someone debugging the problem that caused the higher-level exception.
Rote chaining is really only useful for debugging (viz., logging). Too often the chain actually gets long, and you may not know how far in the chain to find something (rather, you shouldn't know about what the chain looks like beyond the first link). Expose causes through specific methods
This is done for performance, but as an added benefit, it ensures that the input list will be untouched if the sort fails.
Pro-tip: Be skeptical about actually counting on this behavior. The implementation could theoretically change so that the sort happens in-place if the list has the RandomAccess marker interface (avoid the two copy operations for efficiency).
Item 78: Consider serialization proxies instead of serialized instances
Even better, completely separate objects from their serialization/serializers. That is, have a separate object(s) that know how to take the serialized form and create/output the object (deserializer) and the reverse (serializer). This keeps your object itself nice and clean, and also allows you to more cleanly have several serialized forms of an object (e.g. JSON and XML).
I'd then say there's no global expectation (`Object` method to override) …