A few days ago, Sister Ali published an article “reward! 5 questions to collect the top 3% of the code industry super king” — I was very excited to see this title, because I can finally prove that my skills are very awesome.

Unfortunately, with 8 years of Java development experience, I found myself solving all five problems wrong! Once again, the hard way, I was one of the 97 percent of engineers who were killed.

But at least I have a thick skin. Although I have done everything wrong, I still dare to face myself.

01, primitive type float

The first one looks like this, with the following code:

public class FloatPrimitiveTest {
    public static void main(String[] args) {
        float a = 1.0 f - 0.9 f;
        float b = 0.9 f - 0.8 f;
        if (a == b) {
            System.out.println("true");
        } else {
            System.out.println("false"); }}}Copy the code

At first glance, this problem is too easy, right?

1.0f-0.9f is 0.1f, 0.9f-0.8f is 0.1f, 0.9f-0.8f is 0.1f, 0.9f-0.8f is 0.1f, 0.9f-0.8f is 0.1f.

But the actual result turns out not to be like this, too hurt ego.

float a = 1.0 f - 0.9 f;
System.out.println(a); / / 0.100000024
float b = 0.9 f - 0.8 f;
System.out.println(b); / / 0.099999964
Copy the code

After adding two print statements, I realized that there was a precision problem.

The Java language supports two basic floating point types: float and double, and their corresponding wrapper classes float and Double. They are all based on the IEEE 754 standard, which uses scientific notation to represent floating-point numbers with a base 2 decimal.

But floating-point operations are rarely accurate. While some numbers can be accurately expressed as binary decimals, such as 0.5, it equals 2-1; But some numbers, such as 0.1, are less precise. As a result, floating point operations can result in rounding errors, producing results that are close to but not equal to what we want.

So, we see two similar floating-point values for 0.1, 0.100000024, which is slightly larger than 0.1, and 0.099999964, which is slightly smaller than 0.1.

Java rounds any floating-point literal to the nearest value it can represent, and defaults to even precedence when that value is equidistant from the two representable floating-point values — which is why we see two floating-point values that both end in f4s.

02, Wrapper type Float

Let’s look at the second problem. The code is as follows:

public class FloatWrapperTest {
    public static void main(String[] args) {
        Float a = Float.valueOf(1.0 f - 0.9 f);
        Float b = Float.valueOf(0.9 f - 0.8 f);
        if (a.equals(b)) {
            System.out.println("true");
        } else {
            System.out.println("false"); }}}Copy the code

At first glance, it’s not that hard, right? This is done by converting the original type float to the wrapper type float and using equals instead of ==.

This time, I assumed the wrapper would take care of the accuracy problem, so I assumed the output would be true. But then I hit my face again — even though I had a thick skin, I could still feel my face turning slightly red.

Float a = Float.valueOf(1.0 f - 0.9 f);
System.out.println(a); / / 0.100000024
Float b = Float.valueOf(0.9 f - 0.8 f);
System.out.println(b); / / 0.099999964
Copy the code

After adding two print statements, I see that the original wrapper does not solve the accuracy problem.

private final float value;
public Float(float value) {
    this.value = value;
}
public static Float valueOf(float f) {
    return new Float(f);
}
public boolean equals(Object obj) {
    return (obj instanceof Float)
           && (floatToIntBits(((Float)obj).value) == floatToIntBits(value));
}
Copy the code

As you can see from the source, the Float wrapper does not really do anything with precision, and the equals method still uses == internally.

03, switch Determines the null value string

Let’s look at problem 3. The code is as follows:

public class SwitchTest {
    public static void main(String[] args) {
        String param = null;
        switch (param) {
            case "null":
                System.out.println("null");
                break;
            default:
                System.out.println("default"); }}}Copy the code

This one is kind of confusing to me.

As we all know, switch is a very efficient judgment statement, which is really much faster than if/else. Especially after JDK 1.7, case conditions for a switch can be char, byte, short, int, Character, byte, short, Integer, String, or enum.

If the value of param is null, null and “null” must not match. I think the program should go into default and print default.

Only to be slapped in the face again! The program throws an exception:

Exception in thread "main" java.lang.NullPointerException
	at com.cmower.java_demo.Test.main(Test.java:7)
Copy the code

That is, null is not allowed in parentheses of switch (). Why is that?

I was looking through the OFFICIAL JDK documentation, and there was this description, so I’ll just take a look at it and you’ll understand.

When the switch statement is executed, first the Expression is evaluated. If the Expression evaluates to null, a NullPointerException is thrown and the entire switch statement completes abruptly for that reason. Otherwise, if the result is of a reference type, it is subject to unboxing conversion.

When a switch statement is executed, a switch () expression is executed first. If the expression is null, a NullPointerException is thrown.

So why is that?

public static void main(String args[])
{
    String param = null;
    String s;
    switch((s = param).hashCode())
    {
    case 3392903: 
        if(s.equals("null"))
        {
            System.out.println("null");
            break;
        }
        // fall through

    default:
        System.out.println("default");
        break; }}Copy the code

Using jad, we decompile the switch bytecode as shown above. (s = param).hashCode(); if param is null, s is null. Calling hashCode() naturally throws a NullPointerException.

Assignment for BigDecimal

Let’s look at problem 4. The code is as follows:

public class BigDecimalTest {
    public static void main(String[] args) {
        BigDecimal a = new BigDecimal(0.1);
        System.out.println(a);
        BigDecimal b = new BigDecimal("0.1"); System.out.println(b); }}Copy the code

The only difference between A and B is that WHEN A calls the BigDecimal constructor assignment, it passes in a float, while B passes in a string. Both a and B should result in 0.1, so I think the assignment is the same.

In fact, the output was completely unexpected:

BigDecimal a = new BigDecimal(0.1);
System.out.println(a); / / 0.1000000000000000055511151231257827021181583404541015625
BigDecimal b = new BigDecimal("0.1");
System.out.println(b); / / 0.1
Copy the code

What’s going on here?

It is time to move out of the JavaDoc town building of BigDecimal(Double Val).

  1. The results of this constructor can be somewhat unpredictable. One might assume that writing new BigDecimal(0.1) in Java Creates a BigDecimal which is exactly equal to 0.1 (an unscaled value of 1, with a scale of 1), But it is later equal to 0.1000000000000000055511151231257827021181583404541015625. This is because always be 0.1 represented exactly as a double (or, for that matter, as a binary fraction of any finite length). Thus, The value that is being passed in to the constructor is not exactly equal to 0.1, appearances notwithstanding.

Explanation: when using double pass and can produce unpredictable results, such as the actual value is 0.1000000000000000055511151231257827021181583404541015625, 0.1 in plain English, this is precision problem. (In that case, why not scrap it?)

  1. The String constructor, on the other hand, is perfectly predictable: writing new BigDecimal(“0.1”) creates a BigDecimal which is exactly equal to 0.1, as one would expect. Therefore, it is generally recommended that the String constructor be used in preference to this one.

Explanation: Passing parameters using strings yields the expected result, such as the actual result of new BigDecimal(“0.1”), which is 0.1.

  1. When a double must be used as a source for a BigDecimal, note that this constructor provides an exact conversion; it does not give the same result as converting the double to a String using the Double.toString(double) method and then using the BigDecimal(String) constructor. To get that result, use the static valueOf(double) method.

Explanation: If you must pass a double to BigDecimal as an argument, it is recommended that you pass the string value that double matches. There are two ways:

double a = 0.1;
System.out.println(new BigDecimal(String.valueOf(a))); / / 0.1
System.out.println(BigDecimal.valueOf(a)); / / 0.1
Copy the code

First, we use string.valueof () to convert a double to a String.

Second, use the valueOf() method, which internally calls double-.toString () to convert a Double to a string.

public static BigDecimal valueOf(double val) {
    Reminder: A zero double returns '0.0', so we cannot fastpath
    // to use the constant ZERO. This might be important enough to
    // justify a factory approach, a cache, or a few private
    // constants, later.
    return new BigDecimal(Double.toString(val));
}
Copy the code

05, already

The last problem, problem 5, looks like this:

public class LockTest {
    private final static Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        try {
            lock.tryLock();
        } catch (Exception e) {
            e.printStackTrace();
        } finally{ lock.unlock(); }}}Copy the code

The questions are as follows:

A: lock is an unfair lock. B: finally block does not throw an exception. C: tryLock fails to obtain the lock

I’m ashamed to say I don’t know if ReentrantLock is a fair lock; We don’t know if the finally block will throw an exception; It is not known whether tryLock will proceed directly if it fails to acquire the lock. There is no answer.

Five consecutive questions can not be solved, although I have a very thick skin, but also feel burning on the face, like being severely slapped in the face.

Let me look into it.

1) Lock is an unfair lock

A ReentrantLock is a very frequently used lock that supports reentrancy and the ability to repeatedly lock a shared resource, meaning that the current thread does not block when it acquires the lock again.

ReentrantLock is both a fair lock and an unfair lock. Call constructor is not fair lock, source code as follows:

public ReentrantLock(a) {
    sync = new NonfairSync();
}
Copy the code

[A] The lock is unfair.

ReentrantLock also provides another constructor, source code as follows:

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
Copy the code

A fair lock when passed true, an unfair lock when passed false.

So what’s the difference between a fair lock and an unfair lock?

A fair lock can ensure the absolute order of resource requests in time, but an unfair lock may cause other threads to never acquire the lock, resulting in the phenomenon of “starvation”.

Fair lock To ensure the absolute order in time, frequent context switches are required. However, non-fair lock reduces some context switches, and the performance cost is relatively small, which ensures higher throughput of the system.

2) Finally blocks do not throw exceptions

The Lock objects in call unlock method, invoked AbstractQueuedSynchronizer tryRelease method, if the current thread does not have a Lock, it throws IllegalMonitorStateException anomalies.

Therefore, it is recommended that the sample code in this case be optimized to the following form (check whether the current thread holds the lock before entering the business code block) :

boolean isLocked = lock.tryLock();
if (isLocked) {
    try {
        // doSomething();
    } catch (Exception e) {
        e.printStackTrace();
    } finally{ lock.unlock(); }}Copy the code

3) If tryLock fails to obtain the lock, proceed directly

The Javadoc for the tryLock() method is as follows:

Acquires the lock if it is available and returns immediately with the value true. If the lock is not available then this method will return immediately with the value false.

If the lock is available, the lock is acquired and returns true immediately. If the lock is unavailable, the lock is immediately returned false.

In this case, the finally block is executed when tryLock fails to acquire the lock.

6, the last

Ali sister out of the five questions or quite deep, I believe that many friends in the actual project application has encountered. It is said that the solution behind the five questions will be disclosed for the first time in the Java Development Manual.

PS: Welcome to “Silent King ii” public account, background reply keyword “Java” can be obtained.