Hello, I am Waiwai.
A few days ago, a friend sent me a screenshot like this:
He said he didn't understand why he didn't report an error like this.
I said I didn’t understand either. Assigning a boolean type to the int type, how could there be no error? Then I asked him: Where did this code screenshot come from?
He said it was automatically generated by Lombok's @Data annotation.
It's a coincidence. I have a little understanding of Lombok before, so the moment I heard the answer to this, I seemed to understand something in a flash: because Lombok uses bytecode enhancement technology to directly operate the bytecode file, can it directly bypass the problem of variable type mismatch?
But soon I thought about it, it was impossible: If all this thing can be bypassed, Java will also play with yarn.
So I decided to study it and finally found that this matter is actually very simple: it is a bug in idea. I used the plugin again with
, so I quickly recreated it locally.
source file is like this, I just added @Data annotation:
The class file compiled by Maven install is like this:
You can see that @Data annotation has helped us do a lot of things: it generates parameterless constructors, get/set methods for name fields, equals methods, toStrong methods, and hashCode methods.
Actually, you click on the source code of the @Data annotation, and it also explains it to you. This is a composite annotation:
Therefore, the annotation that really generates the hashCode method should be @EqualsAndHashCode.
So, in order to eliminate interference items, I will focus on the hashCode method, I replaced the @Data annotation with @EqualsAndHashCode:
The result is still the same, but the default generated methods are much less, and I don't care about those methods.
Now, it is true. Why is the first line of code in the hashCode method here like this:
int PRIME = true;
Intuition tells me that there must be a trick here.
I first thought of another decompilation tool, jd-gui, just it:
Sure enough, after dragging the class file into jd-gui, the hashCode method is as follows:
is the number 59, not true.
However, this PRIME variable seems to be useless in the hashCode method. There is no hurry to do this problem, throw it out and put it here first, and let it talk about it later.
In addition, I also thought of a way to view the bytecode directly:
You can see the integer stacking instruction used in the first command of the hashCode method that you see in this way. Bipush number 59.
After jd-gui and bytecode verification, I have reason to suspect that int PRIME = true is displayed in idea! right! yes! BUG!
is happy and found a bug again. Isn’t the material here?
I was so happy at that time, just like the expression of the child below.
clues
So I searched online and didn't find any information on this, and I didn't get any gains. The inner OS is: "Ah, it must be that my posture is wrong, do it again."
expanded the search range and searched again.
"Why is there still no clue? It doesn't make sense! No, there must be clues."
So I looked around again.
"Well, there is really no clue. Wasting me for a few hours, garbage, that's it.”
I spent all my life learning and looked through the Internet. I really couldn’t find any idea why it is displayed here int PRIME = true Such a line of code.
The only relevant question I found is this:
https://stackoverflow.com/questions/70824467/lombok-hashcode-1-why-good-2-why-no-compile-error/70824612#70824612
In this question, the guy who asked the question said why he saw int result = true There is no compilation error in this code?
is a bit similar to what I saw, but it is not exactly the same. I found that his Test class has no parameters, and the UserInfo I tested has a name parameter.
So I also made a non-parameter look:
I have no problem here, it shows int result = 1.
Then someone asked if it is because your Test class has no fields, just take a look at it.
When it adds two fields, the compiled class The file is the same as what I saw:
But there is only one valid answer to this question below:
The guy who answered this answer said: You see the hashCode method like this, maybe it is because of a problem with the tool you use to generate bytecode.
Inside the tool you use, boolean true and false are represented by 1 and 0 respectively.
Some bytecode decompilers blindly translate 0 into false and translate 1 into true, this may be the situation you encountered.
The guy who wants to express is also: this is the bug of the tool.
Although I always think it is almost the same, let’s not talk about what’s wrong. Press the table, let’s continue to read it first.
In this answer, a feature of lombok is also mentioned delombok. I want to talk about this first:
delombok
What is this?
tells you a scenario. Suppose you like to use Lombok annotations, so you provide the API you provide to the outside world. Relevant annotations are used in the package.
, but the classmate who quotes your API package does not like Lombok annotations, nor has he done related dependencies and configurations. Then you provide the past API package and others will definitely not be able to use it.
So what should I do?
delombok comes in handy.
can directly generate java that has parsed lombok annotations Source code. The description of this on the official website of
The following is:
https://projectlombok.org/features/delombok
In other words, you can use it to see what the java file generated by lombok looks like.
I'll take you to take a look at what it looks like.
From the description on the official website, you can see delombok There are many different ways to open:
For us, the easiest solution is to use maven plugin directly.
https://github.com/awhitford/lombok.maven
Just paste this configuration into the project's pom.xml.
But it should be noted that there is another paragraph below this configuration, and the first sentence at the beginning is very important:
Place the java source code with lombok annotations in src/main/lombok (instead of src/main/java).
Place the java source code with lombok annotations in src/main/lombok path instead of src/main/java.
So, I created a lombok folder and moved the UserInfo.java file inside.
and then execute the install operation of maven. You can see that there is an additional UserInfo.java file under the target/generated-sources/delombok path:
This file is a java file processed by the delombok plug-in. You can directly put it into the API and provide it with it if the other party does not use the lombok plug-in.
Then we take a look at this file. I got this file mainly to see what the hashCode method looks like:
sees it, int PRIME = true in the hashCode method is gone, and instead final int PRIME = 59.
This is already a java file. If this place is still true, then it will be a complete compilation error:
And the source code generated through delombok also answered my previous question:
When looking at the class file, I felt that the PRIME variable has not been used, so what is its meaning?
But looking at the java file obtained after delombok compiled, I understand. PRIME is actually used:
So why does PRIME become true?
Looking at the source code generated by delombok, my eyes suddenly lit up, what do you think is this:
This is the final type of local variable .
Note: Yes! Final! kind! type!
In order to better introduce the probability I want to talk about below, I will first write you a very simple thing:
Did you see it? Why and mx have become true, which is equivalent to directly modifying the test method to this:
public int test() {return 3;}
Show you the bytecode may be more intuitive:
The left side is not added to final, and the right side is final.
You can see that after adding final, there is no iload operation to access local variables at all. What is this thing called
?
This is "constant folding".
I was fortunate to see JVM a long time ago. The big guy R-master interpreted this phenomenon. I thought it was very interesting at the time, so I had a little impression.
When I see final int PRIME = 59, the memory is ignited.
So I found the link I read before:
https://www.zhihu.com/question/21762917/answer/19239387
In the R big answer, there is such a small paragraph, I will give you a screenshot:
At the same time, I will show you the constant variable. This thing is in Java Definition in the language specification:
A variable of primitive type or type String, that is final and initialized with a compile-time constant expression , is called a constant variable.
A variable of primitive type or String type. If it is modified by final, it will be initialized at compile time, which is called constant variable (constant variable).
So final int PRIME = 59 The PRIME in is a constant variable.
Since String is mentioned here, I will give you an example:
You see the test2 method, using final, and in the final class file, the string after the splicing is completed.
Why?
Don’t ask, ask is the rule.
https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.28
I'm just here to show you a way out. If you are interested, you can go and read it yourself.
In addition, it also confirmed that the following display of the class file is indeed an idea bug, and it has nothing to do with lombok, because I don't use it at all here lombok:
At the same time, there is also a related discussion about the above problem in lombok's github:
https://github.com/projectlombok/lombok/issues/523
https://github.com/projectlombok/lombok/issues/523
https://github.com/projectlombok/lombok/issues/523
https://github.com/projectlombok/lombok/issues/523
https://github.com/projectlombok/lombok/issues/523
https://github.com/projectlombok/lombok/issues/523
https://github.com/projectlombok/lombok/issues/523
https://github.com/projectlombok/lombok/issues/523
Variables seem to be useless code, because they have not been used in this local method.
The official answer is: Brother, I suspect you are seeing an optimization of javac. If you look at the code generated by delombok, you will see that PRIME is being used. It should be that javac is inlined this constant. Why is
59
We once again focused our attention on the hashCode method generated by delombok:
Why is 59 used here? Shouldn't the factors in hashCode be used brainlessly 31?
I think there is a story here, so I dug it up again.
My idea of digging clues is like this.
First I found how the number 59 comes from? It must be from a file in lombok.
Then I pulled down the source code of lombok and checked the submission or changes for this value in the corresponding file. Under normal circumstances, this magic value will not come for no reason. When submitting the code, there is a high probability that the value will be explained.
I just need to find that paragraph of instructions.
First, I found this class based on the place where @EqualsAndHashCode calls:
lombok.javac.handlers.HandleEqualsAndHashCode
Then in this class, we can see the familiar "PRIME":
Then, searching for this keyword, I found this place:
The method here is 59 Source:
lombok.core.handlers.HandlerUtil#primeForHashcode
The first step is completed, and then we have to check out the submission records of the HandlerUtil class in lombok:
The result is very smooth. The second submission of this class is about why it is not useful 31.
According to the commit information, the one you should have used before was 31, and the reason for using this number is because "Effective Java" is recommended to use. But according to the point of view in issue#625, maybe 277 is a better value.
It can also be seen from the submitted code that 31 was indeed used before, and it was written directly:
In this submission, it was modified to 277 and mentioned a constant of HandlerUtil:
However, this is not the 59 I want to find, so I continued to look for.
Soon, I found this change from 277 to 59:
also pointed to issue#625.
Waiting for me to hum a song and sing, and when I was about to go to issue#625 to find out, I was stunned:
https://github.com/projectlombok/lombok/issues/625
issue#625 What I said has nothing to do with hashCode.
And this question was only proposed on July 15, 2015, but the code was submitted in January 2014.
So lombok's issues must have lost a large part, which has led to me not being able to match the account now.
This behavior is poisoned in the code, and I am a poisoned person.
Things look like they are walking into a dead end.
But soon, the loop turned around because another possible answer flashed through my little cerebral, that is changelog:
https://projectlombok.org/changelog
As a matter of course, in the changelog, I found a new clue issue#660:
Open issue#660 At first glance, well, this time, I should have gone the wrong way:
https://github.com/projectlombok/lombok/issues/660
In this issues, first Maaartinus gave a piece of code, and then he explained:
In my example, if the hashCode method generated by lombok uses this factor 31, for 256 generated objects, there are only 64 unique hash values, which means that it will produce a lot of collisions.
But if lombok uses a better factor, this number will increase to 144, which is relatively better.
and almost any odd number will do. Using 31 is one of the few bad choices. After seeing the official report, I quickly replied:
After reading it, I thought what I said makes sense. I used 31 before, because this is what I suggested in "Effective Java" and I didn't think too much about it.
In addition, I decided to use the number 277 instead of 31 as a new factor. Why is
277?
Don’t ask, it’s very lucky!
77 is the lucky winner
So why did you change it from 277 to 59 in the end?
Because of using such a "huge" factor of 227, there will be a performance loss of about 1-2%. So you need to change a number.
finally decided to choose 59, although there was no specific reason:
But combined with changelog, I have reason to guess one of the reasons is to choose a number less than 127, because -128 to 127 is within the cache range of Integer:
IDEA
Speaking of IDEA's bug, I stepped on an impressive "BUG" in my early years.
was debugging ConcurrentLinkedQueue before, and it made the mentality crash.
You may encounter a huge pit, for example, our test code is as follows:
public class Test {public static void main(String[] args) {ConcurrentLinkedQueueObject queue = new ConcurrentLinkedQueue();queue.offer(new Object());}}
is very simple, add an element to the queue.
Due to initialization head=tail=new Node(null):
So the item pointing in the link list structure after the add method is called should be like this:
We add several output statements to the offer method:
The log after execution is like this:
Why is the output log not after the last line of output? Where is null-@723279cf?
Because this method will call the first method to obtain the real head node, that is, the node whose item is not null:
Everything is normal here. However, it is different when you operate in debug mode: the item of the head node of
is no longer null! The next node of the head node is null, so a null pointer exception is thrown.
The result of the code running directly in the case of a single thread is inconsistent with the result of the Debug running ! Isn't this a ghost?
I checked it online and found that there are still many netizens who have encountered ghosts.
Finally found this place:
https://stackoverflow.com/questions/55889152/why-my-object-has-been-changed-by-intellij-ideas-debugger-soundlessly
The problem this buddy encountered was exactly the same as we did:
There is only one answer below this question:
Do you know who the buddy answered this question? Product Manager of
IDEA, presented my respect.
The final solution is to close these two configurations of IDEA:
Because IDEA will actively call the toString method in Debug mode, and in the toString method, the iterator will be called.
And the CLQ iterator will trigger the first method. This will modify the head element:
. The truth is revealed.
And the problem in this article:
I have reason to be sure that it is IDEA's problem, but I have not found the authentication of authoritative people like the problem in this section.
So what I said before is almost meaningful, that's what I mean.
--- This article was first published on the official account why technology.