Nice buy! That's the book that got me started!
The theory of double buffering is that you draw to an off screen image (= your backbuffer image) instead of directly to the screen using the Graphics object you get in your update method. You currently have two objects named g2d. One is the Graphics2D object that draws to your backbuffer that you defined in the beginning of the class definition (Graphics2d g2d;) and later created in your init() method (g2d = (Graphics2D)backbuffer.getGraphics()
. The other is the Graphics object you got from the update() method, which you cast to a Graphics2D. You are currently not drawing anything to your backbuffer.
Gbeebe's double buffering code looks good. The book likes to keep the game drawing code in the update() method though, with the paint() method only containing g.drawImage(backbuffer, 0, 0, null);. Your initialization code works fine at the moment. It's just your drawing code that's the problem.
Finally, your g2d keeps an internal AffineTransform that handles rotation, translation and scaling. If you use g2d.translate(), g2d.rotate() or g2d.scale(), you are modifying the transform permanently for this Graphics2D object. That means that every frame, your modifications will "stack up", and you most like won't see anything as after a few frames what you're drawing will most likely be placed outside the screen. You need to reset the transform manually before modifying it. Your commented out AffineTransform identity = new AffineTransform(); line does half the trick, but you also need to assign it to the Graphics2d object using g2d.setTransform(identity);. Fun fact: The reason it's called "identity" is because it represent an identity matrix, which is a matrix which does nothing (like translating by (0, 0) and/or scaling by 1).