一、继承与重写
1.继承
简单的说,继承就是在一个现有类型的基础上,通过增加新的方法或者重定义已有方法(下面会讲到,这种方式叫重写)的方式,产生一个新的类型。继承是面向对象的三个基本特征–封装、继承、多态的其中之一,我们在使用JAVA时编写的每一个类都是在继承,因为在JAVA语言中,java.lang.Object类是所有类最根本的基类(或者叫父类、超类),如果我们新定义的一个类没有明确地指定继承自哪个基类,那么JAVA 就会默认为它是继承自Object类的。
我们可以把JAVA中的类分为以下三种:
类:使用class定义且不含有抽象方法的类。
抽象类:使用abstract class定义的类,它可以含有,也可以不含有抽象方法。
接口:使用interface定义的类。
在这三种类型之间存在下面的继承规律:
类可以继承(extends)类,可以继承(extends)抽象类,可以继承(implements)接口。
抽象类可以继承(extends)类,可以继承(extends)抽象类,可以继承(implements)接口。
接口只能继承(extends)接口。
请注意上面三条规律中每种继承情况下使用的不同的关键字extends和implements,它们是不可以随意替换的。大家知道,一个普通类继承一个接口后,必须实现这个接口中定义的所有方法,否则就只能被定义为抽象类。我在这里之所以没有对implements关键字使用“实现”这种说法是因为从概念上来说它也是表示一种继承关系,而且对于抽象类implements接口的情况下,它并不是一定要实现这个接口定义的任何方法,因此使用继承的说法更为合理一些。
以上三条规律同时遵守下面这些约束:
类和抽象类都只能最多继承一个类,或者最多继承一个抽象类,并且这两种情况是互斥的,也就是说它们要么继承一个类,要么继承一个抽象类。
类、抽象类和接口在继承接口时,不受数量的约束,理论上可以继承无限多个接口。当然,对于类来说,它必须实现它所继承的所有接口中定义的全部方法。
抽象类继承抽象类,或者实现接口时,可以部分、全部或者完全不实现父类抽象类的抽象(abstract)方法,或者父类接口中定义的接口。
类继承抽象类,或者实现接口时,必须全部实现父类抽象类的全部抽象(abstract)方法,或者父类接口中定义的全部接口。
继承给我们的编程带来的好处就是对原有类的复用(重用)。就像模块的复用一样,类的复用可以提高我们的开发效率,实际上,模块的复用是大量类的复用叠加后的效果。
2.重写
重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
在面向对象原则里,重写意味着可以重写任何现有方法。实例如下:
class Animal{
public void move(){
System.out.println("动物可以移动");
}
}
class Dog extends Animal{
public void move(){
System.out.println("狗可以跑和走");
}
}
public class TestDog{
public static void main(String args[]){
Animal a = new Animal(); // Animal 对象
Animal b = new Dog(); // Dog 对象
a.move();// 执行 Animal 类的方法
b.move();//执行 Dog 类的方法
}
}
运行结果:
动物可以移动
狗可以跑和走
方法的重写规则:
(1): 参数列表必须完全与被重写方法的相同;
(2):返回类型必须完全与被重写方法的返回类型相同;
(3):访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。
(4):父类的成员方法只能被它的子类重写。
(5):声明为final的方法不能被重写。
(6):声明为static的方法不能被重写,当是能够被再次声明。
(7):子类和父类在同一个包中,那么子类可以重写父类所有除了声明为private和final的方法。
(8):子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法。
(9):重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
(10):构造方法不能被重写。如果不能继承一个方法,则不能重写这个方法。
3.方法重写和方法重载的区别
方法重写:子类和父类中方法相同,两个类之间的关系,函数的返回值类型、函数名、参数列表都一样
方法重载:指在同一个类中,多个方法名相同,他们的参数列表不同(个数不同,数据类型不同)
子类的方法各个修饰符
二、子类与父类构造方法总结
如何继承父类构造器
某种意义上可以说继承。
子类的构造器必须在第一行调用父类的构造(super),如果无默认的无参构造器,则必须显示调用super指定继承的构造器。
实例化过程
实例初始化就是执行()方法
()方法可能重载有多个,有几个构造器就有几个方法
()方法由非静态实例变量显示赋值代码和非静态代码块、对应构造器代码组成
非静态实例变量显示赋值代码和非静态代码块代码从上到下顺序执行,而对应构造器的代码最后执行
每次创建实例对象,调用对应构造器,执行的就是对应的方法
方法的首行是super()或super(实参列表),即对应父类的方法
父类
package test.t05new;
public class A {
public String name;
{
System.out.println("父类的 代码块begin");
}
static{
System.out.println("父类的 静态代码块begin");
}
public A(){
System.out.println("父类的无参数构造方法");
}
public A(String str){
this.name=str;
System.out.println("父类的有参数构造方法str");
}
public A(int str){
this.name=String.valueOf(str);
System.out.println("父类的有参数构造方法int");
}
public A(long str){
this.name=String.valueOf(str);
System.out.println("父类的有参数构造方法long");
}
{
System.out.println("父类的 代码块end");
}
static{
System.out.println("父类的 静态代码块end");
}
}
子类
package test.t05new;
public class S extends A {
{
System.out.println("子类的 代码块begin");
}
static{
System.out.println("子类的 静态代码块begin");
}
public S(){
System.out.println("子类的无参数构造方法");
}
public S(String str){
super(str);//想调用父类的有参数构造方法,必须写在第一行
System.out.println("子类的有参数构造方法str");
}
public S(int str){
//super(str);
System.out.println("子类的有参数构造方法int");
}
public S(long str){
super((int)str);
System.out.println("子类的有参数构造方法long");
}
{
System.out.println("子类的 代码块end");
}
static{
System.out.println("子类的 静态代码块begin");
}
public static void main(String[] args) {
S s1 = new S();
System.out.println(s1.name);
S s2 = new S("dddd");
System.out.println(s2.name);
S s3 = new S(123);
System.out.println(s3.name);
S s4 = new S(123L);
System.out.println(s4.name);
}
}
调用子类无参构造器
S s1 = new S();
System.out.println(s1.name);
父类的 静态代码块begin
父类的 静态代码块end
子类的 静态代码块begin
子类的 静态代码块begin
父类的 代码块begin
父类的 代码块end
父类的无参数构造方法
子类的 代码块begin
子类的 代码块end
子类的无参数构造方法
null
可以看到,先初始化类,再实例化类。
初始化类,先初始父类,再初始子类(调用静态代码块和静态变量,按照代码中声明的顺序)
实例化类,先实例化父类,再实例化子类。
实例化父类,如果没有显式调用父类构造器,默认调用父类的无参构造器(此处没有显式调用,但是默认隐式调用)
调用构造器前,先执行实例变量和代码块,按照代码中声明的顺序,再执行构造器!
实例化子类,就是先执行实例变量和代码块,按照代码中声明的顺序,再执行构造器
调用子类有参构造器,显式调用父类构造器
S s2 = new S(“dddd”);
System.out.println(s2.name);
父类的 代码块begin
父类的 代码块end
父类的有参数构造方法str
子类的 代码块begin
子类的 代码块end
子类的有参数构造方法str
dddd
可以看到显式调用父类构造器时,确实调用了父类的有参数构造方法
注意:显式调用父类构造器时,必须把super.xxx写在子类构造器的第一行
调用子类有参构造器,无显示调用父类构造器
S s3 = new S(123);
System.out.println(s3.name);
父类的 代码块begin
父类的 代码块end
父类的无参数构造方法
子类的 代码块begin
子类的 代码块end
子类的有参数构造方法int
null
可以看到如果没有显式调用父类构造器,默认调用父类的无参构造器,即使子类调用有参构造器!!!
调用子类有参构造器,显示调用父类其他有参构造器
S s4 = new S(123L);
System.out.println(s4.name);
内部方法
public S(long str){
super((int)str);
System.out.println("子类的有参数构造方法long");
}
父类的 代码块begin
父类的 代码块end
父类的有参数构造方法int
子类的 代码块begin
子类的 代码块end
子类的有参数构造方法long
123
可以看到如果显式调用父类构造器,就会调用该构造器,即使子类构造器的参数是其他类型!!!
如果父类没有无参构造器
/public A(){
System.out.println(“父类的无参数构造方法”);
}/
将父类无参构造方法注释了,只剩下public A(String str)
调用S s1 = new S();
public S(){
System.out.println("子类的无参数构造方法");
}
会报错
Exception in thread “main” java.lang.Error: Unresolved compilation problems:
Implicit super constructor A() is undefined. Must explicitly invoke another constructor
Implicit super constructor A() is undefined. Must explicitly invoke another constructor
at test.t05new.S.<init>(S.java:14)
at test.t05new.S.main(S.java:41)
父类的 静态代码块begin
父类的 静态代码块end
子类的 静态代码块begin
子类的 静态代码块begin
表明子类构造函数内必须调用父类的构造函数,可以是显示的,可以是隐式的(调用无参父类),但如果父类没有无参构造函数,必须显示调用父类构造函数
三、子类调用父类成员、方法
继承的特殊说明
这里说道继承是拥有父类的全部属性和方法,但我们发现父类的私有属性,子类是不可以使用的。这违背了继承的定义吗,拥有父类的全部属性与行为。答案是没有。用数学去描述子类与父类的话,应该是子类大于等于父类。
在一个子类被创建时,首先会在内存中创建一个父类对象,然后在父类对象外部放上子类独有的属性,两者结合形成子类的对象。所以父类的全部属性和行为子类会拥有,但是对于父类对象中的私有属性和方法,子类是无法访问到的,只是拥有,但也仅仅是拥有(I like you, But just like you!)。这也是关键字private,protected,public的意义所在。
调用范围
1、能够访问标为public protected的成员变量和方法;
2、如果子类与父类在同一包内,还能访问默认(无修饰符)的成员变量与方法。
3、不能访问标为private的成员。
访问原则
/*
看程序写结果:
A:访问成员变量的原则:就近原则。
B:this和super的问题:
this 访问本类的成员
super 访问父类的成员(可以理解为的)
C:子类的所有构造方法执行前默认先执行父类的无参构造方法。
D:一个类的初始化过程:
成员变量进行初始化过程如下:
默认初始化
显示初始化
构造方法初始化
输出的结果是:
fu
zi
30
20
10
*/
class Fu {
public int num = 10;
public Fu() {
System.out.println("fu");
}
}
class Zi extends Fu {
public int num = 20;
public Zi() {
System.out.println("zi");
}
public void show() {
int num = 30;
System.out.println(num); //30
System.out.println(this.num); //20
System.out.println(super.num); //10
}
}
class ExtendsTest {
public static void main(String[] args) {
Zi z = new Zi();
z.show();
}
}
可以看到在能够访问的前提下,访问同一个名字的变量的原则是就近原则:
先寻找方法中的局部变量num,没找到再找本类中的num,还没找到就找父类中的num。
当就近原则访问不到想要的变量时,例如父类子类都有num,可以使用this.num或者super.num
三、子类和父类方法的重载
方法重载 overload,方法重写override。方法重载是发生在同一个类中多个同名法之间。方法重写是发生在父类和子类的同名方法之间。
子类拥有父类所有的方法,如果子类也定义了和父类方法名形同,但形参列表不同的方法,那么就发生了子类方法和父类方法的重载。代码如下:
public class ExtendsTest {
public static void main(String [] args){
SubClass sub = new SubClass();
sub.print("ABC");
sub.print("ABC", "DEF");
}
}
class SuperClass{
public void print(String s1){
System.out.println(s1);
}
}
class SubClass extends SuperClass{
public void print(String s1, String s2){
System.out.println(s1+"、"+s2);
}
}
课堂练习
package jyg09a1;
public class Assistant extends Employee {
public void methodChildren() {
System.out.println("我是子类--助教的方法");
System.out.println("我要改作业");
}
}
package jyg09a1;
public class Employee {
String name;
int age;
public void method() {
System.out.println("我是父类--雇员的方法");
}
}
package jyg09a1;
public class Jyg {
public static void main(String[] args) {
Teacher teacher=new Teacher();
teacher.method();
teacher.methodChildren();
Assistant assistant=new Assistant();
assistant.method();
assistant.methodChildren();
}
}
package jyg09a1;
public class Teacher extends Employee {
public void methodChildren() {
System.out.println("我是子类--老师的方法");
System.out.println("我要讲课");
}
}