The main reason is performance: ByteBuffers and the other NIO classes enable accelerated operations when interfacing with native code (typically by avoiding the need to copy data into a temporary buffer).
This is pretty important if you are doing a lot of OpenGL rendering calls for example.
The reason for creating a ByteBuffer first is that you want to use the allocateDirect call to create a direct byte buffer, which benefits from the accelerated operations. You then create a FloatBuffer from this that shares the same memory. The FloatBuffer doesn’t itself have an allocateDirect method for some reason, which is why you have to go via ByteBuffer.