/** * @return partition between 0 inclusive * and partitions exclusive */ int partition(Track track, int partitions) { assert track != null; assert partitions > 0; return track.hashCode() % partitions; }
What is wrong with the Track objects partitioning algorithm by servers?
hashCode()
method is redefined in the Track
class or the implementation from the Object
class is used.hashCode()
method is redefined. Javadoc looks quite convincing, but we are used to trusting javadocs, right? ;) So, Vadim had to take care of the negative values of hashCode
(or someone had Flaky tests ).Object.hashCode()
method is used. The best way to say this is javadoc to this method (a key fragment is highlighted):Whenever it is invokedThe latter means that the
an execution of a Java application, thehashCode
method
must consistently return the same integer, provided no information
used inequals
comparisons.
This integer need not remain consistent
application to another execution of the same application.
hashCode
value of the same object after restarting the application is likely to be different. The object is the same in the context of business logic (for example, obtained from the database). But this is still half the trouble: as a result, on different servers at one point in time, hashes will probably also be different.hashCode
and the likelihood of negative values can cause Flaky tests . Roughly speaking, these are tests that on one codebase for unknown reasons become red, and at the time of their study (or by themselves) suddenly turn green again.hashCode()
method relies on the hashCode
business data (primitives and strings). This case is the most common and no less interesting!hashCode()
and equals()
methods for a simple data class: public class Address { private final String street; private final Integer house; }
@Override public int hashCode() { int result = street != null ? street.hashCode() : 0; result = 31 * result + (house != null ? house.hashCode() : 0); return result; }
public final class String { /** * Returns a hash code for this string. The hash code for a * {@code String} object is computed as * <blockquote><pre> * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] * </pre></blockquote> * using {@code int} arithmetic, where {@code s[i]} is the * <i>i</i>th character of the string, {@code n} is the length of * the string, and {@code ^} indicates exponentiation. * (The hash value of the empty string is zero.) * * @return a hash code value for this object. */ @Override public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; } } public final class Integer { /** * Returns a hash code for this {@code Integer}. * * @return a hash code value for this object, equal to the * primitive {@code int} value represented by this * {@code Integer} object. */ @Override public int hashCode() { return Integer.hashCode(value); } /** * Returns a hash code for a {@code int} value; compatible with * {@code Integer.hashCode()}. * * @param value the value to hash * @since 1.8 * * @return a hash code value for a {@code int} value. */ public static int hashCode(int value) { return value; } }
String.hashCode()
holds to a lesser extent. As was rightly noted in the comments, changing the algorithm will make a ton of compiled code invalid, which will cause resentment of the entire Java community. switch(s) { case "s": System.out.println("S"); break; case "b": System.out.println("B"); break; }
int var_ = -1; switch(s.hashCode()) { case 98: if(s.equals("b")) { var_ = 1; } break; case 115: if(s.equals("s")) { var_ = 0; } } switch(var_) { case 0: System.out.println("S"); break; case 1: System.out.println("B"); }
assert
. This is about using the assert statement ( JLS 14.10 ).assert condition [: msg];
condition == false
, an AssertionError
error will be thrown. In this case, if the expression msg
not specified, then we get an error without a detail message , which, in my opinion, is unacceptable . In the second case, the AssetionError(String detailMessage)
constructor will be called. public class Main { public static void main(String[] args) { assert args.length != 0; assert args.length == 0; System.out.println("OMG! Is it possible?"); } }
OMG! Is it possible?
will be displayed on the console OMG! Is it possible?
OMG! Is it possible?
because assert
not enabled by default:java -ea | -enableassertions Main
java -ea | -enableassertions Main
.Error
: /** * An {@code Error} is a subclass of {@code Throwable} * that indicates serious problems that a reasonable application * should not try to catch. Most such errors are abnormal conditions. * The {@code ThreadDeath} error, though a "normal" condition, * is also a subclass of {@code Error} because most applications * should not try to catch it. * <p> * A method is not required to declare in its {@code throws} * clause any subclasses of {@code Error} that might be thrown * during the execution of the method but not caught, since these * errors are abnormal conditions that should never occur. * * That is, {@code Error} and its subclasses are regarded as unchecked * exceptions for the purposes of compile-time checking of exceptions. * * @author Frank Yellin * @see java.lang.ThreadDeath * @jls 11.2 Compile-Time Checking of Exceptions * @since JDK1.0 */ public class Error extends Throwable { /* code omitted */ }
Error
indicates there is no problem to try to catch. Most such errors are abnormal conditions.
In my view , Error
should not be used by the user code as business exceptions , and their appearance should indicate a bug. In the code from the partitions == 0
task, it looks more like a business check than a code error.partition()
method be responsible for handling this case? Rather, no, but that is another question.IllegalArgumentException
.assert
adhere to the position that it is not necessary to transfer the tests in fact to the production code.Source: https://habr.com/ru/post/353034/
All Articles