The Dockerfile provides caching. When you tweak a few things, you don't have to re-run the whole script, which is great if there are some expansive steps (e.g. downloading and installing Java, compiling a big library, etc.)
Of course, you can achieve something similar with shell scripts, e.g. "if Java is not installed, install it, otherwise go to the next step". But if you get it wrong, or if there is some side-effect, the end result (after running the script multiple times) won't be exactly the same. The Dockerfile (with snapshots at each step) ensures consistency.
It's not a big deal if you are a shell expert and write that kind of script for breakfast, but it's not the case for everyone :)