Firstly I'm just adding some characters to your class names:
class Animal {
public void m() {System.out.println("AAA");}
}
class Bee extends Animal {
public void m() {System.out.println("BBB"); }
}
class Test{
public static void main(String [] args){
Animal a=new Animal();
Bee b=new Bee();
Animal x;
x=a; x.m(); // "AAA" a is an animal
x=b; x.m(); // "BBB" b is a Bee, so it's an Animal
a=b; a.m(); // "BBB" A Bee is an Animal
b=a; b.m(); // ERROR An Animal is a Bee? What if it's a Cat?
}}
In case is still not clear, let's create the class:
class Cat extends Animal{
public void m() {System.out.println("CAT"); }
public void foo() {System.out.println("FOO"); }
};
You could change in the previous code Animal a=new Animal();
by Animal a= new Cat();
and then you'll see b=a
is incorrect because a Bee is not a Cat.
UPDATE: I added two methods to the class Cat. Let's see how it works:
// This is the obvious part:
Cat c= new Cat();
c.m(); // "CAT"
c.foo(); // "FOO"
// Not so obvious
Animal a2= new Cat(); // a Cat is an animal
a2.m(); // "CAT"
a2.foo(); //Won't compile: ERROR
What happens here? Cat is an animal, and this ensures it has the method m. The behavior of the method is defined by the instance itself. a2 is a variable that points at a Cat instance, but we can only call the methods defined by Animal because a2 could also be any other animal and we don't know which methods it could have. Yes, it this case we know it's a cat, but let's say we have this method:
public Animal createAnAnimal() {
if (Random.nextBoolean()) {
return new Cat();
} else {
return new Bee();
}
}
Anyway, you should read about inheritance and interfaces, which add more complexity to this stuff.