一、前言
继承是面向对象程序设计的基石之一。利用继承,人们可以基于已存在的一个类构造一个新的类,从而复用其中非私有的变量和方法,一定程度上避免了代码的冗余。
二、方法的覆盖和重载
1、一个类继承另一个类使用 extends关键字,声明 A类 extends B类 表示A类继承了B类。当A类继承了B类,会继承其中所有的非private修饰的的方法和变量,这些变量和方法可以在子类中直接使用。
创建一个类Father:
public class Father { public int age; private int weight; public Father(){} public Father(int age,int weight){ this.weight=weight; this.age=age; } public void sleep(){ System.out.println("躺shi中"); }
}
创建一个类Son并继承Father:
public class Son extends Father{ public Son(){} public static void main(String[] args) { Son son=new Son(); son.age=18; System.out.println("我今年"+son.age+"岁"); son.sleep(); } }
输出
2、方法的覆盖:如果子类继承了父类的方法,同时又自己声明了一个同名同参数的方法,则这个子类中声明的方法会覆盖从父类继承的这个同名同参方法,在子类调用这个方法时,实际调用的是子类中声明的同名同参数方法。
注:子类覆盖父类方法时,子类中声明的方法的参数和父类必须一致;子类中声明的方法的访问修饰符的访问权限范围,必须大于等于从父类中继承的原方法的访问权限范围,比如父类中方法为protected,则子类中声明的覆盖方法必须为protected或者public,因为权限范围 public>protected>default>private;子类声明的覆盖方法的返回值,需要和父类方法一致或者是父类方法返回值的子类。
改写上面的son类,覆盖父类继承的sleep方法:
public class Son extends Father{ public Son(){} @Override public void sleep(){ System.out.println("我不想睡"); } public static void main(String[] args) { Son son=new Son(); son.age=18; System.out.println("我今年"+son.age+"岁"); son.sleep(); } }
输出:
3、方法的重载:在一个类中定义多个同名不同参(参数长度不同,参数顺序不同,参数类型不同)的方法,称为方法的重载。这些同名不同参数的方法都属于不同的方法,对象实际会调用其中哪个方法,要看这个对象传入的参数列表的参数类型列表,是否和某个方法的参数类型列表一致,完全一致则调用。子类也可以定义和从父类继承的方法同名不同参数的方法,也称之为方法重载,此时这两个同名不同参的方法实际上是两个不同的方法,子类中的这个方法不算是从父类继承的,其返回值和访问权限修饰符也不受父类中同名方法的影响。
再次改写上面的Son类:
public class Son extends Father{ public Son(){} private int sleep(int i,int j){ System.out.println("我睡了"+i+"天"+j+"夜"); return -1; } public static void main(String[] args) { Son son=new Son(); son.age=18; System.out.println("我今年"+son.age+"岁"); son.sleep(); son.sleep(3,4); } }
测试结果:
注:可以用 参数类型... 参数名 来表示任意个这种参数类型的参数,也称之为变长参数。食用方式 method(其他非变长参数列表,参数类型... 参数名) ,注意变长参数在一个方法中有且只能有一个变长参数,且这个变长参数只能放在方法参数的最后面
三、super和this关键字
1、this关键字
this关键字不在构造器方法中时,代表调用这个方法的对象的引用;在构造器方法中,this是构造的这个类的对象的引用;也可以在构造器方法中使用 this(参数) 作为第一句(必须放构造器第一句!),来调用本类的某个构造器方法。下面实例中的son.sleep(i),实际上son对象也传入了自己的一个对象引用作为sleep方法的参数,相当于隐形的 sleep(son,i),在sleep方法中可以用this来指代调用了这个方法的对象son。
改写Son,示例代码:
public class Son extends Father{ public Son(int i){ System.out.println("成功了!"); } public Son(){ this(1); this.age=10; System.out.println("成功了你真棒~"); } private int sleep(int i){ this.age+=i; System.out.println("我睡了"+i+"年"); return i; } public static void main(String[] args) { Son son=new Son(); System.out.println("我今年"+son.age+"岁"); son.sleep(18); System.out.println("我今年"+son.age+"岁"); } }
输出:
2、super关键字
super关键字不在构造器方法中时,代表调用这个方法的类的父类对象的引用;在构造器方法中,super是构造的父类对象的引用;也可以在本类的构造器方法中使用 super(参数) 作为第一句(必须放构造器第一句!),来调用父类的某个构造器方法。
改写Father和Son,示例代码
Father类:
public class Father { public int age; private int weight; public Father(){ } public Father(int weight){ this.weight=weight; System.out.println("super(int i) 成功调用父类构造器 Father(int i)"); System.out.println("私有变量weight初始化为:"+this.weight); } public void sleep(){ System.out.println("Father睡觉"); } }
Son类和测试代码:
public class Son extends Father{ //int age; public Son(int i){ super(i); super.sleep(); } @Override public void sleep(){ System.out.println("Son睡觉"); super.age=10; } public static void main(String[] args) { Son son=new Son(200); son.age=100; son.sleep(); System.out.println("age="+son.age); } }
输出:
可以发现其中使用super.sleep()调用的父类的sleep()方法,son.sleep调用的是son类中覆盖了父类方法的sleep()方法。而对父类变量的修改直接修改了子类和父类共享的变量age,除非son中再定义一个age变量,否则对super.age的修改也是对son中继承的age变量的修改。
四、static和final关键字
以下static和final关键字相关内容全部来自这篇博文,总结的很好了,偷个懒搬个运稍微加点: https://blog.csdn.net/qq1623267754/article/details/36190715
1、final
final可以修饰:属性,方法,类,局部变量(方法中的变量)
final修饰的属性的初始化可以在编译期,也可以在运行期,初始化后不能被改变。
final修饰的属性跟具体对象有关,在运行期初始化的final属性,不同对象可以有不同的值。
final修饰的属性表明是一个常数(创建后不能被修改)。
final修饰的方法表示该方法在子类中不能被重写,final修饰的类表示该类不能被继承。
private方法默认属于final方法。
对于基本类型数据,final会将值变为一个常数(创建后不能被修改);但是对于对象句柄(亦可称作引用或者指针),final会将句柄变为一个常数(进行声明时,必须将句柄初始化到一个具体的对象。而且不能再将句柄指向另一个对象。但是,对象的本身是可以修改的。这一限制也适用于数组,数组也属于对象,数组本身也是可以修改的。方法参数中的final句柄,意味着在该方法内部,我们不能改变参数句柄指向的实际东西,也就是说在方法内部不能给形参句柄再另外赋值)。
2、static
static可以修饰:属性,方法,代码段,内部类(静态内部类或嵌套内部类)
static修饰的属性的初始化在编译期(类加载的时候),初始化后能改变。
static修饰的属性所有对象都只有一个值。
static修饰的属性强调它们只有一个。
static修饰的属性、方法、代码段跟该类的具体对象无关,不创建对象也能调用static修饰的属性、方法等。
static和“this、super”势不两立,static跟具体对象无关,而this、super正好跟具体对象有关。
static不可以修饰局部变量。
3、static final
static final和final static没什么区别,一般static写在前面。
static修饰的属性强调它们只有一个,final修饰的属性表明是一个常数(创建后不能被修改)。static final修饰的属性表示一旦给值,就不可修改,并且可以通过类名访问。
static final也可以修饰方法,表示该方法不能重写,可以在不new对象的情况下调用。
五、多态,一个指向不同子类对象的父类引用,调用方法时具体行为的动态绑定
在面向对象的的程序设计语言中,多态是继数据抽象和继承之后的第三种基本特征。多态通过分离做什么和怎么做,从另一角度讲接口和实现分离开来。多态不仅能够改善代码的组织结构和可读性,还能够创建可拓展的程序。封装通过合并特征和行为来创建新的数据类型。“实现隐藏”则通过将细节“私有化”把接口和实现分离开来。而多态的作用则是消除类型之间的耦合关系。继承允许将对象视为它自己本身的类型或者父类类型来加以处理。这种能力极为重要,因为它允许将多种类型(继承了同一个父类)视为同一类型来处理。而同一份代码也就可以毫无差别的运行在这些不同类型之上了。多态方法调用允许一种类型表现出与其他相似类型之间的区别,只要它们都是继承了同一个父类。这种区别是根据方法行为的不同而表示出来的,虽然这些方法都可以通过同一个父类对象引用来调用。 ——摘自《Java编程思想》
1、向上转型与向下转型
向上转型 把一个对象的引用指定为这个对象的父类对象的引用(在继承层次中子类向父类移动,无需强制转换)
向下转型 把一个对象的引用窄化为这个对象的子类对象的引用(在继承层次中父类向子类移动,需要使用(要转换的类型) 来强制转换,原对象子类和强转的目标对象子类可能会不一致,导致抛出ClassCastException)
2、静态绑定方法和动态绑定方法
静态绑定方法 在编译时就绑定(或者可以说确定)的方法,Java中所有的 private、final、static的方法和构造器都是静态绑定(也称为前期绑定),这些方法都不能被继承。在代码编译时,会通过调用这些方法的对象引用声明的类(实际可能指向一个子类的对象)中的方法和方法参数来确定要调用的方法。在程序运行前已经绑定了。
动态绑定方法 在程序运行时绑定的方法。Java除了构造器, private、final、static的方法之外的方法。会通过对象实际的所属的类和这个类从父辈爷爷辈祖宗辈继承下来的方法,以及这个类中定义的方法以及所有这些方法的参数,在程序运行时绑定正确的,或者说“最合适”的方法并运行这个方法(先找这个类的方法,在找这个类的父类,在找父类的父类......这些其实都被保存在这个类的方法表中)。当然,构造器内部使用的类和方法以及private、final、static修饰的方法中使用的类和方法可以存在多态。
测试代码:
public class Human { public void test(){ System.out.println("Human"); } }
public class Woman extends Human{ @Override public void test(){ System.out.println("Woman"); } public static void main(String[] args) { Human human=new Woman(); human.test(); } }
测试结果: