1、初识继承
面向对象三大特征之一,继承(关键字:extends)
从字面上我们也能知晓继承的大致意思,即现实生活中出现的实例,子辈从父辈处获得财产等物质财富或者是基因等从父辈处所得到的,当然这部分的东西会被深深的烙印上父辈的色彩。
在Java中继承有上述的内涵同时也有另外一层意思即 is-a(an)的含义
例如下面的代码:
//------------------------------------父类
public class Animal {
private String name;
private String type;
}
//------------------------------------子类
public class Dog extends Animal {
}
//------------------------------------子类
public class Tiger extends Animal {
}
在上述的代码中我们定义了Animal类、Dog类和Tiger类,并且使用了extends关键字
也就是说在上述的代码中我们让这三个原本独立的类产生了联系
即 Dog is an Animal;
Tiger is an Animal;
下面是继承的语句格式:
class 父类{
java语句
...
}
class 子类名 enxtends 父类名{
java语句
...
}
...
从上述代码的表述中我们可以发现,父类可以有多个子类
那么子类可以继承多个父类吗?显然在人类社会的道德伦理中这就是不可以的,在Java的世界中同样是不可以的
也就是说,父类可以被多个子类继承,但是子类只能继承一个父类
2、继承了什么?
关于继承,在Java的世界中,子类从父类那里继承了什么?
子类继承了父类的成员属性、成员方法(private修饰的除外)和静态方法(private修饰的除外),其中父类中private修饰的成员属性子类无法直接访问和操作需要通过父类的方法来进行访问和操作。
之前我们有说过一个类中有成员属性、成员方法和构造方法
那么子类继承了父类的构造方法吗?
答案是否定的
下面展示一段代码:
//------------------------------------父类
public class Animal {
private String name;
private String type;
}
//------------------------------------子类
public class Dog extends Animal {
}
//------------------------------------测试类
public class Test {
public static void main(String[] args) {
Animal dog1 = new Dog();
Dog dog2 = new Animal();//编译无法通过
}
}
我们知道每个类中在没有定义无参构造方法之前都有系统给定默认的无参构造器(方法),即"类名()"
同时我们在上面也说了子类可以调用父类中非private修饰的方法,而构造方法显然是没有被private修饰的
也就是说在之前的逻辑下子类可以利用父类的构造器,但是在上述的代码中子类无法使用父类的构造器
也就是说子类没有继承父类的构造器(构造方法),因为继承之后上述的代码就不会报错!
这也体现了继承中一个重要的关系:
父类型的引用指向子类型的对象,是可行的,就是说Dog is an Animal,是被大家认可的
子类型的引用指向父类型的对象,这样是不可以的,即Animal is a Dog ,是不成立的。
下面是父类型的引用指向子类型的对象在JVM中实现的概念图:
//------------------------------------父类
public class Animal {
private String name;
private String type;
public Animal(){
System.out.println("父类的无参构造");
}
public Animal(String name,String type){
System.out.println("父类的有参构造");
}
}
//------------------------------------子类
public class Dog extends Animal {
public Dog(){
System.out.println("子类的无参构造");
}
public Dog(String name,String type){
System.out.println("子类的有参构造");
}
}
//------------------------------------测试类
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
}
}
/*执行结果
父类的无参构造
子类的无参构造
*/
在上述的代码和执行结果我们可以知道在构造一个子类对象的实现过程中,我们是先执行了父类的构造方法然后再执行子类的构造方法,但是我们的代码中并没有出现对父类构造方法的调用!
难道是Java不讲武德?
答案是否定的
众所周知,孩子的出生离不开父亲的存在
所以在上述的实时中我们可以知道,在构造子类的过程中,子类的构造方法调用了父类的无参构造,而这个调用显然是隐式的,并且一定是位于子类构造器中的第一条Java语句。
下面我们再来看看子类的有参的构造是否有同样的现象
//------------------------------------父类
public class Animal {
private String name;
private String type;
public Animal(){
System.out.println("父类的无参构造");
}
public Animal(String name,String type){
System.out.println("父类的有参构造");
}
}
//------------------------------------子类
public class Dog extends Animal {
public Dog(){
System.out.println("子类的无参构造");
}
public Dog(String name,String type){
System.out.println("子类的有参构造");
}
}
//------------------------------------测试类
public class Test {
public static void main(String[] args) {
Dog dog = new Dog("阿黄","京巴");
}
}
/*执行结果
父类的无参构造
子类的有参构造
*/
也就是说子类在有参构造的条件下,子类的有参构造器被调用后做的第一件事是调用了父类的无参构造器,遵循了“有儿先有爹”的普世观念
那么在构造子类对象的时候有先构造出一个父类对象吗?
没有(具体看后面,有解释)
但是在从上述的代码中我们大致可以看出这样一种模型
//------------------------------------父类
class SuperClass{
private String name;//假设的一个成员属性,也可以是其他的
public SuperClass(){}
public SuperClass(String name){
this.name = name;
}
}
//------------------------------------子类
class SubClass extends SuperClass (){
public Subclass(){
SuperClass();
}
public Subclass(String name){
SuperClass();
}
}
即子类的构造方法中无论是否有参数,都会在构造子类对象的时候调用父类的无参构造器
补充:再出现继承的关系时,父类中一定要将父类的无参构造器显示出来,因为一旦父类中的无参构造器不存在,整个程序在编译时是不能通过的。
造成这种现象的原因是什么?
super关键字!
3、super关键字
//------------------------------------父类
public class Animal {
private String name;
private String type;
public Animal(){
System.out.println("父类的无参构造");
}
public Animal(String name,String type){
System.out.println("父类的有参构造");
}
}
//------------------------------------子类
public class Dog extends Animal {
public Dog(){
//super();
System.out.println("子类的无参构造");
}
public Dog(String name,String type){
//super();
System.out.println("子类的有参构造");
}
}
//------------------------------------测试类
public class Test {
public static void main(String[] args) {
Dog dog1 = new Dog();
Dog dog2 = new Dog("阿黄","京巴");
}
}
/*输出结果为
父类的无参构造
子类的无参构造
父类的无参构造
子类的有参构造
*/
//------------------------------------子类
public class Dog extends Animal {
public Dog(){
super();
System.out.println("子类的无参构造");
}
public Dog(String name,String type){
super();
System.out.println("子类的有参构造");
}
}
/*输出结果为
父类的无参构造
子类的无参构造
父类的无参构造
子类的有参构造
*/
两者的输出结果是一样的
且与我们之前构想的隐藏的父类构造方法一样super()只能位于子类构造器的第一句
也就是说那个被隐藏的调用入口不是隐藏的父类构造方法,而是super();
在这里的super和非静态方法中存在于参数列表中的this一样,是可以省略的(在无参的子类中可以,有参的省略就达不到想要的结果),当然也可以不省略。是不是就是说明super()就是父类的构造器呢?
当然不是,如果子类中存在父类的构造器,并且在子类产生对象的时候调用了父类的构造器岂不是等同于在“儿子”的操作下生了个“爹”,所以super()并不是父类的构造器
3.1 super()和super.xxx();
关于super():super()不是父类的构造器,而是通过super关键字完成了对父类特征的重现。
super()的用法:
1、作为构造方法出现在子类构造器中,只能出现一次;
2、出现在子类的构造器中,只能是第一条Java语句
3、super()只能出现在构造器中,父类也行(没有显示父类的类也有一个隐藏的父类,即Object类)关于super关键字的其他内容即super.xxx();
super作为关键字可以在成员方法中使用,因为前面说过我们通过隐藏的关键字super完成了对父类特征的重现,即super代表的是父类特征,且这些特征归子类所拥有,而这里的父类特征指的是父类所具有的成员属性,成员方法,包括父类的类名,但是super不代表类名。所以我们可以通过super直接访问子类所继承的父类中的方法,包括static修饰的静态方法。关于super使用时的省略,当我们在子类中调用父类的成员方法时,如果在子类中存在对于该方法的重写(override)那我们为了区分这两个方法我们就不能省略super关键字,当然在子类下可以省略this,因为this一直都在(没有static)的地方等候着。
下面是一段为了区分的代码
//------------------------------------父类
public class Animal {
private String name;
private String type;
public Animal(){
System.out.println("父类的无参构造");
}
public Animal(String name,String type){
System.out.println("父类的有参构造");
}
public void eat(){
System.out.println("需要吃东西");
}
}
//------------------------------------子类
public class Dog extends Animal {
public Dog(){
super();
System.out.println("子类的无参构造");
}
public Dog(String name,String type){
super();
System.out.println("子类的有参构造");
}
public void eat(){
System.out.println("需要吃东西1");
}
public void test(){
super.eat();
this.eat();
}
}
//------------------------------------测试类
public class Test {
public static void main(String[] args) {
Dog dog1 = new Dog();
Dog dog2 = new Dog("阿黄","京巴");
dog1.test();
}
}
/*输出结果是
父类的无参构造
子类的无参构造
父类的无参构造
子类的有参构造
需要吃东西//父类eat()方法执行后输出的语句
需要吃东西1//子类eat()方法执行后输出的语句
*/
上述在子类和父类所具有相同的方法名的操作叫做方法的重写(override),下次更新博客的时候再描述
未完待续,嘿嘿