13 Aug 2019
Today’s topic is about Map
and misuses I’ve seen during many code reviews.
The idea with a Map
is to do whatever you need by doing as less hashing as possible.
A hash occurs each time you access the Map
(e.g. get
, containsKey
, put
).
In Java 8, some useful new methods were added.
Let’s say you want to check if something is in a Map
:
- If it is, return it
- If it’s not, add it and return it
The classical way to do it is:
if (map.containsKey(key)) { // one hash
return map.get(key); // two hash
}
List<String> list = new ArrayList<>();
map.put(key, list); // three hash
return list;
It is also the slowest.
A better way is:
List<String> list = map.get(key); // one hash
if(list == null) {
list = new ArrayList<>();
map.put(key, list); // two hash
}
return list;
This is already much better. You save one hash.
Important: This isn’t valid if the value might be null
.
But I highly recommend you to never have null values
But since Java 8, you have three better solutions.
The first one is:
map.putIfAbsent(key, new ArrayList<>()); // one hash
return map.get(key); // two hash
It is better but not much. You still have two hashes.
And the ArrayList
is instantiated even if it is already in the map.
You can improve with the longer:
List<String> list = new ArrayList<>();
List<String> result = map.putIfAbsent(key, list); // one hash only!
if(result == null) {
return list;
}
return result;
Now we’re talking, only one hash! But still the ArrayList
is instantiated uselessly.
Which brings us to another Java 8 method that does the trick.
return map.computeIfAbsent(key, unused -> new ArrayList<>()); // one hash only!
Job done.
One line and the fastest we can get.
The ArrayList
will be instantiated only when needed.
Important: Do not do map.computeIfAbsent(key, ArrayList::new)
.
computeIfAbsent
takes a Function<KEY, VALUE>
in parameter.
So this will in general not compile unless the KEY
matches the parameter of one of the ArrayList constructors.
An example is when the KEY
is an Integer.
Passing a constructor method reference will actually call new ArrayList(KEY)
… which is obviously not what you want.
In order to convince you that it’s the best solution, I have made a little benchmark using JMH.
Here are the results:
Benchmark Mode Cnt Score Error Units
MapBenchmark.computeIfAbsent_there thrpt 40 25134018.341 ± 687925.885 ops/s (the best!)
MapBenchmark.containsPut_there thrpt 40 21459978.028 ± 401003.399 ops/s
MapBenchmark.getPut_there thrpt 40 24268773.005 ± 690893.070 ops/s
MapBenchmark.putIfAbsentGet_there thrpt 40 18230032.343 ± 238803.546 ops/s
MapBenchmark.putIfAbsent_there thrpt 40 20579085.677 ± 527246.125 ops/s
MapBenchmark.computeIfAbsent_notThere thrpt 40 8229212.547 ± 341295.641 ops/s (the best!)
MapBenchmark.containsPut_notThere thrpt 40 6996790.450 ± 191176.603 ops/s
MapBenchmark.getPut_notThere thrpt 40 8009163.041 ± 288765.384 ops/s
MapBenchmark.putIfAbsentGet_notThere thrpt 40 6212712.165 ± 333023.068 ops/s
MapBenchmark.putIfAbsent_notThere thrpt 40 7227880.072 ± 289581.816 ops/s
Til next time: Happy mapping.
29 Jul 2019
A long time ago (think 2000), all classes in Java used to have an interface.
You first started with MyInterface
then added a MyInterfaceImpl
.
This caused a lot of boilerplating and debugging annoyance.
I used to have code generator to make it easier.
Why were we doing that?
Two reasons. A bad and a good one.
The bad reason is decoupling
The idea was that if you depend on an interface you can swap the implementation if ever needed.
This is a “you might need it later” issue.
Every sentence with “might” and “later” in it should be rephrased as “I don’t care”.
Because most of the time, “later” never occurs and you are just wasting time an energy right now just in case.
Whatever happens later should be dealt with later.
That said, you might argue that “yes, but it will be much more painful to deal with it later”.
Ok. Let’s check.
Let’s say you have some cheese
public class Cheese {
private final String name;
public Cheese(String name) {
this.name = Objects.requireNonNull(name);
}
public String getName() {
return name;
}
}
Then you want to retrieve the cheese from a database.
public class CheeseDao {
private final Database database;
public Cheese findByName(String name) {
return database.names()
.filter(name::equals)
.reduce((a, b) -> {
throw new IllegalStateException("More than one entry found for " + name);
})
.map(Cheese::new)
.orElse(null);
}
}
And then you have a REST resource depending on the CheeseDAO
.
public class CheeseResource {
private final CheeseDAO cheeseDAO;
public CheeseResource(CheeseDAO cheeseDAO) {
this.cheeseDAO = cheeseDAO;
}
public Cheese get(String name) {
return cheeseDAO.findByName(name);
}
}
Since you are an efficient human being, you decided that no interface was needed for the CheeseDAO
.
It has only one implementation so far and you have not building a cheese open source library.
All this code is into your little cheese application.
But one day, some requirements arrive and you actually do need another implementation.
“Later” actually happened.
So you now turn CheeseDAO
into an interface.
public interface CheeseDao {
Cheese findByName(String name);
}
public class CheeseDatabaseDao implements CheeseDao {
private final Database database;
public Cheese findByName(String name) {
return database.names()
.filter(name::equals)
.reduce((a, b) -> {
throw new IllegalStateException("More than one entry found for " + name);
})
.map(Cheese::new)
.orElse(null);
}
}
And now, off you go to fix compilation errors on all the classes depending on CheeseDAO
.
For instance, you modify CheeseResource
to this:
public class CheeseResource {
private final CheeseDAO cheeseDAO;
public CheeseResource(CheeseDAO cheeseDAO) {
this.cheeseDAO = cheeseDAO;
}
public Cheese get(String name) {
return cheeseDAO.findByName(name);
}
}
I’ll leave you 5 seconds. 1, 2, 3, 4, 5.
Yes, I’m messing with you.
Nothing has changed.
Not a single character.
Turning a class into an interface “later” wasn’t painful after all.
Which is why I call it a bad reason.
Doing it is painful now and has no benefit later.
Now, the good reason: Testing
The problem with a concrete class is that you need to instantiate.
In a testing context, you want to mock dependencies.
In order to mock a concrete class, you need two things
- Extend the class to be able to mock the behavior
- Instantiate the class
The first requirement is easy, the second is trickier.
If the class is simple and has a simple constructor to call, everything is alright.
If the class is quite annoying to instantiate, you have a problem.
This is where I step in.
The coolest trick would be to instantiate the class without calling any constructor.
Fortunately, Java allows that.
Because serialization does it all the time.
You just need to sneak under the hood a little.
Originally, I got involved in open source to solve that problem specifically.
Most mocking framework today are using Objenesis to perform this task.
I talked a bit about it in a previous post.
So, since 2003, you don’t need to be afraid to use concrete classes as dependencies.
You can mock them just as any interface.
02 Nov 2018
Being an Oracle Groundbreaker Ambassador, I get to use the Oracle Cloud.
They have added support for Kubernetes lately. I must say I was pleasantly surprised about it.
It works perfectly.
So, here is a little tutorial if you want to play with it.
It uses Terraform. Oracle Cloud has developed a connector to it. It makes everything easier and command line.
OCI Prerequisites
The first step is to configure correctly your Oracle Cloud Infrastructure (OCI). You can more or less follow this.
I will comment and summarize here
- Install Terraform (
brew install terraform
, you probably have brew already on a mac so no need to install it and do the chown
)
- Generate a ssh key for oci. Follow the instructions. You could use an existing key. But other scripts are assuming you have a key in the
.oci
directory so it’s just easier to create a new one
- Add the public key on the Oracle console.
Your life will be easier if you log yourself before clicking on all the links
- Create a
env-vars.sh
. I haven’t added it in my .bash_profile
. I just do a source env-vars.sh
when needed. There are 2 fun values to find: TF_VAR_tenancy_ocid
and TF_VAR_user_ocid
.
The tenancy is here.
The user is here.
You can, of course, use the region you prefer.
Done!
Create the OKE
Now we get serious and we will create the Oracle Kubernetes Engine (OKE). This is explained here.
Again, the steps with my comments
- Get the git repository for oke:
git clone https://github.com/cloud-partners/oke-how-to.git
. You might want to fork and commit since you will tweak it
- Init terraform:
terraform init
- Generate the plan to make sure it works:
terracotta plan
. You might want to modify terraform/variables.tf
first.
This file contains the name of your cluster, the number of nodes per subnet you want, the server instance type and the OKE version used.
- You can then apply the plan to create your cluster;
terracotta apply
. It should work magically. I had one problem on my side though. I think it’s because I have an old account. My OKE limit was at 0. So I couldn’t create a cluster. I had to ask the support to fix it. Which was done pretty quickly.
One thing I am not sure about is if you will need to add some policies.
Just in case, here are mine (I’m in the group Administrators
):
- ListandGetVCNs: Allow group Administrators to manage vcn in tenancy
- ListGetsubnets: Allow group Administrators to manage virtual-network-family in tenancy
- OKE: Allow service OKE to manage all-resources in tenancy
- PSM-root-policy: PSM managed compartment root policy
- Tenant Admin Policy: Tenant Admin Policy
I haven’t mastered the policy system yet to I’m not quite sure what is doing what.
Deploy on the cluster
You now have a running cluster so let’s deploy some stuff on it.
- For that you need
kubectl
: brew install kubectl kubernetes-helm
- Add the kube config for the cluster. The doc will tell you to use the
config
file generated by Terraform. It works but in general you want to keep the configuration of other clusters (e.g docker-for-desktop-cluster or minikube). So you will probably prefer to give it another name. You can then switch from one context to another using kubectl config use-context oci
Then just deploy whatever you want to deploy.
Destroy your cluster
Really important when you are done: terraform destroy
30 Oct 2018
This release adds a support of Java 9, 10 and 11. It also drops support of Java 6 and 7. So it is now a Java 8+. This
brought easier maintenance and some performance improvement.
Modules are partly supported with an automatic module name.
If also changes the way EasyMock will determine the type of the returned mock. The idea is to solve a heavily annoying
problem most mocking frameworks have with generics.
To be clear, starting now List<String> list = mock(List.class);
will compile perfectly without
any “unchecked” warning.
However, String s = mock(List.class);
will also compile.
But I’m expecting you not to be crazy enough to do such thing.
It will do a ClassCastException
at runtime anyway.
The only side effect is that in rare cases, the compiler might fail to infer the return type and give a compilation error.
If it ever happen, the solution is to use a type witness, e.g. foo(EasyMock.<List<String>>mock(List.class)
.
It should solve the problem nicely, and, again, without a warning.
Change log for Version 4.0.1 (2018-10-30)
- Upgrade to cglib 3.2.9 to support Java 11 (#234)
- Upgrade TestNG to version 7 (#233)
- Update to ASM 7.0 for full Java 11 support (#232)
Change log for Version 4.0 (2018-10-27)
- Remove most long time deprecated methods (#231)
- Relax typing for the mocking result (#229)
- Upgrade Objenesis to 3.0.1 (#228)
- Update cglib to 3.2.8 and asm to 6.2.1 (#225)
- Java 11 Compatibility check: EasyMock (#224)
- easymock 3.6 can’t work with JDK11 EA kit (#218)
- update testng to 6.14.3 (#216)
23 Oct 2018
This release adds a support of Java 9, 10 and 11. It also drops support of Java 6 and 7. So it is now a Java 8+. This
brought easier maintenance and some performance improvement.
Modules are partly supported with an automatic module name.
Change log for Version 3.0.1 (2018-10-18)
- No Automatic-Module-Name in objenesis (#66)
Change log for Version 3.0 (2018-10-07)
- Drop JRockit support (#64)
- Move lower support to Java 1.8 (#63)
- Replace findbugs by spotbugs (#62)
- ClassDefinitionUtils doesn’t compile with Java 11 (#61)
- update pom.xml for maven plugins (#60)
- Test errors with Java 10 (#59)
- Please remove the hidden .mvn directory from the source tarball (#57)
- Move Android TCK API 26 because objenesis now requires it (#65)
24 Sep 2018
About a month ago, I was preparing my OracleOne talk and
tripped on a slide. I was trying to explain the new delivery process and how long the support of each version will last.
It wasn’t that clear at all.
So, I asked my fellow Java Champions
about it. It triggered a discussion about the fact that it is indeed quite misunderstood.
We got together. Or to be honest, Martijn led the way and wrote an article to clarify the situation.
We then made multiple suggestions and corrections. Representatives of the main OpenJDK providers were also involved.
- AdoptOpenJDK
- Amazon
- Azul (actual supporter of )
- BellSoft
- IBM
- jClarity
- Oracle (obviously)
- RedHat (actual supporter of Java 8 and 11)
- SAP
So I now consider the document a must read for anyone interested in Java.
Is Java still free? Yes, it is.
29 Aug 2018
After years missing it, I finally went to JCrete last year for the first time. I went with my
family and we had a great time. This year, I went back again but alone.
For those who never went, it is hard to describe the event. The boring way would be to say that it is a Java unconference.
But it is a really bad way to describe it.
Here is my current explanation.
Close your eyes. Imagine you can gather all the almighty experts in your field and put them in one place. Then imagine
this place has multiple beaches, great (cheap!) food and good wine. That everyone is friendly and wants to help each other without judging.
And that you will talk will all these experts all day, in formal classrooms and on the beach. You will gather a tremendous amount of knowledge.
This is JCrete.
This was my experience last year. This was my experience this year. It almost feels surreal.
I still don’t know how it happened. Is it Crete? Is it Heinz and Kirk’s magical touch?
Is it the Java community that is awesome? Probably all of the above sincerely.
Then, if you have never heard of JCrete let me explain a bit more what it is about.
First, it’s an unconference with a limited number of participants. Those participants are selected for their accomplishments.
It means that you are well-known in the Java community for something. Of course, a lot of Java Champions are
attending. To prevent too much inbreeding, a little amount of entropy coming from random senior lead developers is added.
However, the rule is that anyone attending should be able to propose a subject and talk about it. You can see it as a conference
with only speakers talking to one another.
Then, a typical day looks like that:
- Waking up
- Go jogging with some of the attendees (chatting about interesting stuff)
- Shower
- Get breakfast with the attendees (chatting about interesting stuff)
- Gather in the main room
- Morning briefing and subject proposition for the day
- Attend sessions with subjects you care about. Learn
- Lunch (chatting about interesting stuff)
- Go to some beach (chatting about interesting stuff)
- Hack a bit. When stuck, there are high chances that one of the lead committers on the thing you are stuck on is attending too
- Get unstuck by the lead committer (this year it was Matt Raible)
- Go dinner to some restaurant or at an evening event (chatting about interesting stuff, around a bottle of wine)
- Go to bed happy
Last year, this article when out of it. This year, some cool stuff that I will
talk about when it’s ready.
See you in JCrete! Or possibly one of its clones.
22 Apr 2018
Edited 2021-01-04: I do not use this trick anymore.
It was freezing my computer once in a while.
I haven’t retried for a while.
If you are successful using it, please tell me.
In a previous post, I wrote about the UI using I had on a Mac. A Mac lover was fairly
confident he could save me (sadly, no).
Today, thanks to BetterTouchTool and its developers,
I can strike one item on my list.
Cmd+ù
should behave as Cmd+`
when using a ca-fr keyboard.
The solution is to add in BTT a shortcut to Cmd+ù
. Then bind it to a predefined action “Run Apple Script”.
tell application "System Events"
keystroke "`" using command down
end tell
Voilà!
08 Apr 2018
This release adds a better support to Java 9 and Java 10 and fixes an issue for interface default methods.
- Java 10 support through an update of ASM and cglib
- Add Java 9 automodule
- Allow mocking interface default methods on a partial mock
Change log for Version 3.6 (2019-08-05)
- Add Java 9 automodule (#212)
- Update asm, cglib and surefire for Java 10 support (#211)
- Mocking interface default methods (#203)
13 Sep 2017
Here is the long awaited 3.5 version. It contains many bug fixes and some improvement. We allowed ourselves to possibly break
the compatibility with older versions for the greater good. So please read these notes thoroughly.
- Java 5 is no longer supported. I dearly hope this won’t harm anyone
- Java 9 is supported
- TestNG support is added. Have a look at
EasyMockListener
- Class Mocking now works correctly for cross bundle mocking
verify()
now checks for unexpected calls in case an AssertionError was swallowed during the test. It is in general
what you want but you can use verifyRecording()
to bring back the old behavior
- Default matcher for an array argument is now
aryEq
instead of eq
. This should as well make sense for everyone and should
allow you to remove tons of aryEq
all over your code. If you don’t like it, you can specify eq
explicitly for the array
argument
Change log for Version 3.5 (2017-09-12)
- isNull and notNull with generic class parameter (#93)
- Return a meaningful error when null on a primitive (#92)
- Create opportunity to disable SingleThread checks (#88)
- slightly more intuitive error message (#80)
- Enhancement for andAnswer / andStubAnswer (#79)
- Make easymock and easymock-ce OSGi-ready (#78)
- Enable Multiple Captures (#77)
- Improve multithreading error report in MocksBehavior (#73)
- Stack trace clobbered when exception thrown by IAnswer impl (#34)
- Possible bug with captures() (#30)
- Actual value in byte array failure is not helpful (#29)
- Regression caused by new threadsafe API and defaults (#27)
- Capturing parameters from single argument methods (#24)
- NPE with varargs in record state (#22)
- capture(Capture) only captures last method call (#21)