子类与继承
所有类都是Object的子孙类。
子类继承了父类的成员变量和方法,就好像是在子类中直接定义了一样。
如果子类和父类在同一个包中,子类自然地继承了父类中不是private的成员变量和方法。
如果子类和父类不在同一个包中,父类中的private和友好访问权限的成员变量不会被子类继承,子类只继承父类中的protected和public访问权限的成员变量和方法。
如果用D类在D本身中创建了一个对象,那么该对象总是可以通过“.”运算符访问继承的或自己定义的protected变量和protected方法的。
但是如果在另一个类如Other类中用D类创建了一个对象object,该对象通过“.”运算符访问protected变量和protected方法的权限如下:
(1)对于子类D自己声明的protected变量和方法,只要Other类和D类在同一个包中,object对象就可以访问这些protected成员变量和方法。
(2)对于子类D从父类继承的protected成员变量或protected方法,需要追溯到D的祖先,如A类,如果A类和Other类在同一个包中,那么object对象可以访问D类继承的protected变量和protected方法。
1.子类和对象
当用子类的构造方法创建一个子类的对象时,不仅子类中的声明的成员变量被分配了内存,而且父类的成员变量也都分配了内存空间,但只将其中一部分,即子类继承的那部分成员变量作为分配个子类对象的变量。也就是说,父类中的private成员变量尽管也分配了内存空间,但是却不作为子类对象的变量。不在同一个包中的protected变量也是同理。
那些不被子类继承的成员变量也分配了内存空间,这部分内存并不是没用的,因为子类从父类继承的方法中可能存在对这部分未继承的变量操作的方法。
instanceof是java特有的双目运算符。
2.成员变量的隐藏和方法重写
在编写子类的时候所声明的成员变量的名字可以和从父类继承来的成员变量名相同(声明的类型可以不同),在这种情况下,子类会隐藏所继承的成员变量。其特点如下:
(1)子类对象以及子类自己定义的方法操作的与父类同名的成员变量一定是子类重新声明的这个成员变量。
(2)子类对象仍然可以调用从父类继承的方法操作被子类隐藏的成员变量,也就是说,子类继承的方法所操作的成员变量一定是被子类继承或者隐藏的成员变量。
子类继承的方法只能操作被子类继承或者被子类隐藏的成员变量。子类新定义的方法可以操作子类继承和子类新声明的成员变量,但无法操作子类隐藏的成员变量。
方法重写的最基本要求,方法的名字、参数个数、参数类型和父类的方法相同(方法返回类型也要相同否则会产生编译错误)。这是父类继承的方法会被子类重写的方法所覆盖。
当方法重写后,子类所调用的方法一定是被重写之后的方法。
重写方法既可以操作继承的成员变量、调用继承的方法,也可以操作子类新声明的成员变量、调用新定义的方法,但无法操作被子类隐藏的成员变量和方法。
重写需要注意的是,不允许降低方法的访问权限,但可以提高方法的访问权限,访问权限的高低顺序是:public,protected,友好的,private。
比如父类中的方法f()的访问权限是protected,在重写f()时,访问权限一定要大于等于protected,如果不写就会出错。
子类一旦隐藏了继承的成员变量,那么子类创建的对象就不在拥有该变量,该变量归关键字super所拥有,同样子类一旦隐藏了继承的方法,子类将不能再调用隐藏的方法,该方法的调用由关键字super负责。比如super.x、super.play()就是访问和调用被子类隐藏的变量和方法。
当用子类的构造方法创建一个子类的对象时,子类的构造方法总是先调用父类的某个构造方法,也就是说,如果子类的构造方法没有明显地指明使用父类的哪个构造方法,子类调用父类的不带参数的构造方法。
由于子类不继承父类的构造方法,因此子类在其构造方法中需使用super来调用父类的构造方法,而且super必须是子类构造方法中的第一条语句,即如果在子类的构造方法中没有明显地写出super关键字来调用父类的某个构造方法,那么默认地有:
super();
在父类中定义多个构造方法时,应当包括一个不带参数的构造方法,以防止省略super时出现错误。
3.final关键字
final关键字可以修饰类、成员变量和方法中的局部变量。
final类不能被继承,即不能有子类。有时候出于安全性的考虑,将一些类修饰为final类,如String类,java不允许用户扩展String类,因此java将它声明为final。
final修饰的方法不能被子类重写,不允许子类隐藏父类的final方法。、
如果成员变量或者局部变量被修饰为final,那它就是常量,所以final变量在声明时没有默认值,必须赋值。
4.对象的上转型对象
我们经常说“老虎是动物”,父类与子类的关系是“is-a”,这种思维和java语言中的上转型对象很类似。
假设Animal类时Tiger类的父类,当用子类创建一个对象,并把这个对象的引用放到父类的对象中时,比如
Animal a;
a = new Tiger();
或
Animal a;
Tiger b = new Tiger();
a = b;
这时,称对象a是对象b的上转型对象。
对象的上转型对象的实体是子类负责创建的,但上转型对象会失去原对象的一些属性和行为(上转型对象相当于子类对象的一个“简化”对象)。上转型对象具有如下特点:
(1)上转型对象不能操作子类新增的成员变量(失掉了这部分属性),不能调用子类新增的方法(失掉了一些行为)。
(2)上转型对象可以访问子类继承或隐藏的成员变量,也可以调用子类继承的方法或子类重写的实例方法。上转型对象操作子类继承的方法或子类重写的实例方法,其作用等价于子类对象去调用这些方法。因此,如果子类重写了父类的某个实例方法后当对象的上转型对象调用这个实例方法时一定调用了子类重写的实例方法。
1.不要将父类创建的对象和子类对象的上转型对象混淆。
2.可以将对象的上转型对象再强制转换到一个子类对象,这时,该子类对象又具备了子类所有的属性和行为。
3.不可以将父类创建的对象的引用赋值个子类声明的对象(不能说“人是美国人”)(除非它之前就是上转型对象)。
如果子类重写了父类的静态方法,那么子类对象的上转型对象不能调用子类重写的静态方法,只能调用父类的静态方法。
多态性就是指父类的某个方法被其子类重写时,可以各自产生自己的功能行为。
用关键字abstract修饰的类称为abstract类(抽象类)。
用关键字abstract修饰的类称为abstract方法。对于abstract方法,只允许声明,不允许实现(没有方法体),而且不允许使用final和abstract同时修饰一个方法或类,也不允许使用static修饰abstract方法,即abstract方法必须是实例方法。
和普通类相比,abstract类中可以有abstract方法(非abstract类中不可以有abstract方法),也可以有非abstract方法。
abstract类不能用new运算符创建对象。如果一个非抽象类是某个抽象类的子类,那么它必须重写父类的抽象方法,给出方法体,这就是为什么不允许使用final和abstract同时修饰一个方法。
可以使用abstract类声明对象,尽管不能使用new运算符创建该对象,但该对象可以成为其子类对象的上转型对象,那么该对象就可以调用子类重写的方法。
abstract类也可以没有abstract方法。如果一个abstract类是abstract类的子类,它可以重写父类的abstract方法,也可以继承父类的abstract方法。
使用多态进行程序设计的核心技术之一是使用上转型对象,即将abstract类声明的对象作为其子类对象的上转型对象,那么这个上转型对象就可以调用子类重写的方法。
使用上转型对象可以使对象调用不同子类重写的方法,从而实现多态。
所谓面向抽象编程,是指当设计某种重要的类时,不让该类面向具体的类,而是面向抽象类,即所设计类中的重要数据是抽象类声明的对象,而不是具体类声明的对象。
面向抽象编程的核心是让类中每种可能的变化对应地交给抽象类的一个子类去负责,从而让该类的设计者不去关心具体实现,避免所设计的类依赖于具体的实现。