Read this: http://www.javaranch.com/campfire/StoryCups.jsp
Then read its follow-up: http://www.javaranch.com/campfire/StoryPassBy.jsp
But basically, you need to give it a type so that the compiler knows that type it is. Note that you could also do this, which is basically what you're talking about:
Object dogObj = new Dog();
But then you could only use the methods defined in Object on that variable, since the compiler has no way of guaranteeing whether some Object is a more specific type or not.
Edit: Let's say you have two classes, Dog and Cat. Dog has a bark() method, and Cat has a meow() method.
You can do this, because as you say, a reference is a reference:
Object dog = new Dog();
But then, you won't be able to do this:
dog.bark();
Even though the Dog class has a bark() method, and the dog variable is an instance of Dog, the compiler doesn't know that. In fact, the compiler can't know what. After all, you could also do this:
Object dog = new Dog();
dog = new Cat();
dog.bark(); //good thing this is a compiler error!
And you might think, why can't the compiler figure it out? The reason for that is code like this can happen:
Object dog = new Dog();
if(Math.random() < .5){
dog = new Cat();
}
//what type is dog now??
So the compiler can only guarantee that an Object is the type of the variable. That's why you need to give every variable a type.
Note that you can also cast your variable to explicitly tell the compiler what type something is:
Object dog = new Dog();
dog = new Cat();
((Cat)dog).meow(); //this will now work, since the compiler knows dog is a Cat!
But watch out, because if you're wrong, you'll get a runtime error:
Object dog = new Dog();
dog = new Cat();
((Dog)dog).bark(); //this won't work, since dog is actually a Cat!
I highly suggest putting together your own little test program that runs code similar to these examples, that way you can get a better idea of what's going on.