readlink -f will:
canonicalize a path by following every symlink in every component of the given name recursively; all but the last component must exist
which will search:
for an executable or script in the directories listed in the environment variable PATH using the same algorithm as bash(1)
which doesn't care whether what it finds is a symlink or not: just that it's executable. It guarantees that the path it prints will always be inside one of the directories in
On your system,
/usr/bin/java is a symlink to
/usr/lib/jvm/java-1.6.0-openjdk-18.104.22.168.x86_64/jre/bin/java. When you combine the two commands together like this you substitute the output of
which into the command line of
readlink -f to create:
readlink -f /usr/bin/java
which has found where the first executable file called
java is in your
PATH, and the shell has inserted that path as an argument to
readlink then looks up the path and finds that it's a symbolic link, and so it resolves that link (and any others it finds) to produce a complete direct path to the actual file itself.
For almost all purposes, these paths will be interchangeable to you - the symlink
java will be automatically resolved to the real path when you use it, and modifications to the file itself will be made by your package manager, rather than you, so you never have to see it. You could run the program from either path, or with just
java, and the result would be exactly the same, because it's the same actual executable that runs in the end.
The package manager will be using a symlink rather than putting the actual file inside
/usr/bin because the JRE has a whole set of files it likes to have next to each other in unusual configurations, and a symlink lets the package manager present a normal-looking arrangement to you as the user. There will be many other files inside the
/usr/lib/jvm/java-1.6.0-openjdk-22.214.171.124.x86_64 that you'd never have any reason to deal with, and that don't participate in the system's ordinary library arrangements.