1、了解继承
对象继承实际上就是一种“is - a”的关系,如上图的“PantherMan is a SuperHero?”,是,那么便属于继承的理解。
继承能避免大量重复的程序代码,同时非常利于程序扩展(子类可以在复用父类方法的同时定义额外的功能),也定义了共同的协议(确保子类拥有父类的方法)。
另外,值得注意的是,继承是无法继承下父类的private权限的属性和方法的。
2、多态的运行
Java中的多态表现为:
- 静态的多态:如方法重载,方法名相同,参数不同
- 动态的多态:如子类覆盖父类方法,将子类实例传于父类的引用,调用的是子类的方法;如实现接口的实例,将其传于接口的引用,调用的是实现类的方法。
另外,不光是引用,参数和返回类型也可以多态,但都限于子类到父类,以小到大,不能逆转。
子类继承父类之后,对于父类方法的覆盖时,访问权限不得降低,且方法头必须相同,即方法名,参数和返回类型相同,例外的是返回类型其实也可以是该类型的子类,必须要记住,子类对象得保证能够执行父类的一切。
3、深入多态
我们说继承可以帮助我们定义共同类型的对象,比如Animal父类,Cat和Dog作为子类;
当你new Cat我们知道出现了一只猫,当你new Dog我们知道出现了一只狗,当你new Animal?Animal是什么呢?
抽象类其实相对于接口不同的是,它可以有成员变量和具体的方法,甚至构造方法(尽管它不能被实例化),从上面这种抽象的解释来理解,其实也就比较容易了。它的最大的好处就是为了实现多态,如果某个方法我们的参数不使用父类,那么我们必须为每种子类各写一个相同内容仅仅是参数不同的方法,不光是大量重复的代码,一旦需要改动,那才是可怕。
你要问,那干脆把所有的类型都设置为Object不就行了?反正是终极父类,不是吗?这样,实际上你很可能会意外地要求对象去执行错误类型的动作,所以Java设置了一个对代码的保护机制,即“类型安全检查”,当某个对象以父类作为引用时,Java会把它当作父类类型实例,这代表你只能调用父类中声明的方法(即,编译器是根据引用类型来判断有哪些method可以调用,而不是根据你确实的类型),比如:
//编译器没有在Object中找到run()方法,所以第二行无法编译通过
//所以我们才有更多的范围更小的父类,而不是直接甩手用Object来处理
Object cat = new Cat();
cat.run(); // 编译无法通过
1
//编译器没有在Object中找到run()方法,所以第二行无法编译通过
2
//所以我们才有更多的范围更小的父类,而不是直接甩手用Object来处理
3
Object cat = new Cat();
4
cat.run(); // 编译无法通过
4、认识接口
接口的出现,解决了Java不能多重继承的特性,同时比较独特的是,接口相当于一个特殊的抽象类,它没有实例变量,也没有具体方法的实现,有的只是常量和抽象方法。
可是这样一来,我们无法使用接口来完成所谓的“继承”,因为我们没办法复用代码,那么接口的意义在哪里呢?多态,多态,多态,重要的事情说三遍。它会要求你必须履行你们之间的“合约”,也让Java能够放心地把其实现类当作接口类型去使用。
5、对象的生命周期
在Java中,对象生存在堆(heap)上,实例变量是声明在类中,所以实例变量放在所属对象中。需要注意的是,如果实例变量是基本数据类型,那么变量所需要的空间是在对象中。如果实例变量是引用数据类型,那对象只会留下引用量,而引用对应的真正对象本身所要用到的空间则到堆上去取;
而方法调用及其参数,还有局部变量都生存在栈(stack)上,它们的生命周期直到方法调用至执行结束(如果方法中存在对象局部变量,忘了我们在Java基础中说过的吗?这不过是放了个遥控器般作用的引用,真正的对象只会存在堆上)。
而对象生存在堆(heap)上,它们的生命周期随着引用的结束而结束。如果一个对象没有引用(哪怕一个也没有的时候),那么它就等待着被垃圾回收器给带走。
5.1 栈
方法在栈中被 “堆” 在一起,当你调用一个方法时,方法就会放在栈顶,逐步堆叠,然后再逐步执行。
5.2 堆
不管是实例变量还是局部变量,对象本身都会在堆上。而实例变量存在于对象所属的堆空间上。
如果实例变量是基本数据类型,那么Java会根据数据类型的大小留下对应大小的空间。如果实例变量是引用数据类型,是对象,再次记住,也只是留下了对象的引用量的空间,Java不会在这个对象中为其引用对象再留出空间的。
另外,实例变量是有默认值的,原始的默认值是 0 / 0.0 / false;引用的默认值是 null。而局部变量没有默认值。
6、构造函数和继承的关系
每个父类都有一个构造函数,且每个构造函数都会在子类对象创建时执行。并且会随着继承链不断调用父类构造函数,最终会调用Object的构造函数。为什么会有如此的一个构造链?想一想,小孩能在父母之前出生吗?
调用父类的构造函数,要使用 super(),如果我们没有编写,编译器也会帮我们自动加上调用。如果想要调用父类的代参构造函数,那么同样给super带参就可以了。
如果想要调用自己类中的其他重载的构造函数,使用this(),但是this和super只能择一,不能同时调用。