前面向大家介绍了面向对象中的封装性,今天再来向大家介绍面向对象的继承和多态的两大特性。
1.继承
1.1 为什么需要继承?
在java语言中,我们用类来描述世间万物,虽然万物非常复杂,但总有一些共同点,如果将这些共同点抽取出来,便能更好的提高我们写代码的效率。所以在面向对象的语言中提出了继承的概念,用来抽取事物的共性,实现代码的复用。
1.2 继承的概念
继承机制:在面向对象程序设计中,继承可以让程序员实现代码的复用,也可以让程序员在原有类(父类、基类、超累)的基础上进行属性和供能的扩增,从而产生出新的类,这些新的类被称为派生类,同时也被称为子类。继承实现的主要功能为抽取共性,实现代码的复用。
1.3 继承的语法
在Java中如果要用到继承来表示类之间的继承关系,我们要用到extend关键字。
语法格式:
修饰符 class 子类名 extends 父类名{
}
接下来我们来举一个例子。
以猫和狗为例
我们知道猫和狗有一些共同点,如名字,年龄,颜色,这些都是动物的共性。我们就可以将这些共同点抽取出来,放在一个类中,这个类就是父类。
接着我们分别将猫和狗分别设置为两个类,分别继承父类。然后分别在猫和狗的类中各自实现自己的功能与属性,如猫要吃猫粮,狗要吃狗粮。
代码实现
父类
public class Animal {
//共性的抽取
public String name;
public int age;
public String color;
}
狗类
public class Dog extends Animal{
public void eat(){
System.out.println(this.name+"在吃狗粮");
}
}
猫类
public class Cat extends Animal{
public void eat(){
System.out.println(this.name+"在吃猫粮");
}
}
测试类
public class Test {
public static void main(String[] args) {
Dog dog=new Dog();
System.out.println(dog.name = "旺财");
System.out.println(dog.age = 6);
System.out.println(dog.color = "black");
dog.eat();
Cat cat=new Cat();
System.out.println(cat.name = "咪咪");
System.out.println(cat.age = 7);
System.out.println(cat.color = "white");
cat.eat();
}
}
通过测试类我们发现,即使我们没有在Dog类和Cat类中定义了name、age和color的成员变量,但由于都继承了Animal这个父类,所以我们通过Dog类和Cat类创建的对象也可以使用name、age和color这三个成员变量。
运行代码
小总结
父类的成员方法和成员变量会被继承到子类中。
2. 访问成员变量和成员方法
在继承这个机制中,成员变量和成员的方法遵循就近原则。在子类的方法中或者通过子类的对象去访问成员方法或者成员变量时,优先在子类中寻找使用。如果在子类当中没有找到对应的成员变量或者成员方法,在去父类中寻找使用,如果父类和子类中都没有找到,则编译会报错。
2.1 访问成员变量
父类的成员变量和子类成员变量不同名
优先到子类中找对应的成员变量,如果在子类中没找到,再去父类中找,都没找到,就会报错。
class Base{
public int a=10;
}
class Derive extends Base{
public int b=9;
}
public class Test {
public static void main(String[] args) {
Derive derive=new Derive();
System.out.println(derive.b);
}
}
父类的成员变量和子类的的成员变量同名
一样遵循就近原则
class Base{
public int a=10;
}
class Derive extends Base{
public int a=9;
}
public class Test {
public static void main(String[] args) {
Derive derive=new Derive();
System.out.println(derive.a);
System.out.println(derive.a);
}
}
2.2 访问成员方法
父类的成员方法与子类的成员方法不同名字
遵循就近原则
class Base{
public int a=10;
public void Base(){
System.out.println("Base()被调用......");
}
}
class Derive extends Base{
public int a=9;
public void Derive(){
System.out.println("Derive()被调用。。。。。。");
}
}
public class Test {
public static void main(String[] args) {
Derive derive=new Derive();
derive.Derive();
derive.Base();
}
}
父类的成员方法与子类的成员方法同名
这里涉及到了方法的重写和动态绑定,后面讲解多态的时候会介绍,这里也先理解为就近原则。
class Base{
public void Test(){
System.out.println("父类的Test()......被调用");
}
}
class Derive extends Base{
public void Test(){
System.out.println("子类的Test()......被调用");
}
}
public class Test {
public static void main(String[] args) {
Derive derive=new Derive();
derive.Test();
}
}
注意事项:
如果我们通过子类创建的对象去访问父类和子类同名的成员方法时,但是由于父类和子类同名的成员方法的参数列表不同(重载),会根据调用方法时的参数去子类和父类中去寻找合适的成员方法。
class Base{
public void Test2(int a){
System.out.println("父类Test2(int a)被调用......");
}
public void Test1(){
System.out.println("父类的Test1()被调用......");
}
}
class Derive extends Base{
public void Test2(){
System.out.println("子类的Test2()被调用......");
}
public void Test1(int a){
System.out.println("子类的Test1(int a)被调用......");
}
}
public class Test {
public static void main(String[] args) {
Derive derive=new Derive();
int a=10;
derive.Test1(a); //调用子类的Test1(int a);
derive.Test1(); //调用父类的Test1();
derive.Test2(a); //调用父类的Test2(int a);
derive.Test2(); //调用子类的Test2();
}
}
运行代码
3.super关键字
如果我们要想在子类中直接去访问父类的去访问符类的成员变量和成员方法,要如何操作呢?
答案就是super关键字。通过super关键字我们就能直接在子类中去访问父类的成员变量和成员方法。
class Base{
int a=10;
public void Test2(int a){
System.out.println("父类Test2(int a)被调用......");
}
public void Test1(){
System.out.println("父类的Test1()被调用......");
}
}
class Derive extends Base{
int a=9;
public void Test2(){
System.out.println("子类的Test2()被调用......");
}
public void Test1(int a){
System.out.println("子类的Test1(int a)被调用......");
}
public void Test(){
System.out.println(super.a);//调用父类的a
super.Test2(a);//调用父类的Test2(int a)
super.Test1();//调用父类的Test1()
}
}
public class Test {
public static void main(String[] args) {
Derive derive=new Derive();
derive.Test();
}
}
注意事项:
super关键字只能在非静态的函数(不被static修饰的函数)中使用。
4.子类构造方法
常言道,先有爸爸再有儿子,所以在调用子类的构造方法时,我们要在子类的构造方法中用super关键字去调用父类的构造方法,否则编译器会报错。
class Base{
public Base(){
System.out.println("调用了父类的构造方法");
}
}
class Derive extends Base{
public Derive(){
super();
System.out.println("子类的构造方法");
}
}
public class Test {
public static void main(String[] args) {
Derive derive=new Derive();
}
}
如果父类和子类的构造方法都构成重载,我们会跟据创建对象时传的参数列表,进入子类中对应的构造函数,然后根据子类的构造函数中的super(......) 括号里的参数列表去调用父类中合适的构造方法。
class Base{
public int a;
public int b;
public Base(){
System.out.println("调用了父类的构造方法");
}
public Base(int a,int b){
this.a=a;
this.b=b;
System.out.println("调用了父类中的有参构造方法");
}
}
class Derive extends Base{
public Derive(){
super();
System.out.println("调用了子类的构造方法");
}
public Derive(int a,int b){
super(a,b);
System.out.println("调用了子类的有参构造方法");
}
}
public class Test {
public static void main(String[] args) {
Derive derive=new Derive(10,20);
System.out.println("==================");
Derive derive1=new Derive();
}
}
运行代码
注意事项:
1.如果我们没有在子类的构造方法中去调用父类的构造函数,编译器会自动默认子类的构造方法中调用了super()。
class Base{
public int a;
public int b;
public Base(){
System.out.println("调用了父类的构造方法");
}
}
class Derive extends Base{
public Derive(){
//super(); 没有写,但会默认有
System.out.println("调用了子类的构造方法");
}
}
public class Test {
public static void main(String[] args) {
Derive derive1=new Derive();
}
}
2.super(......)关键字必须位于子类的构造函数的第一行。
3.super(......)只能在子类的构造函数中出现一次,并且不能和this(......)同时出现。
小总结----super与this的异同点
相同点:
1.super和this都是Java中的关键字。
2.都只能再非静态方法中使用,只能用来访问非静态成员变量和成员方法。
3.在构造方法中使用this和super关键字调用构造方法时,只能位于第一行,并且this(...)和super(...)不能同时存在。
不同点:
1.this关键字当前对象的引用,当前对象即本类实例化的对象,super关键字是子类从父类继承的属性和方法的引用。
如下图:
2.this关键字使用来调用本类的成员方法和成员变量,super关键字是用来调用父类的成员变量和成员方法。
3.在继承机制的构造方法中,一定存在super调用的构造方法,如果没写,编译器也会默认有。但是如果this(...)没写,那就没有。
5.再谈初始化
在Java中,代码块运行的顺序为静态代码块到实例化代码块再到构造代码块。
那父类和子类这3种代码块的运行先后是怎么样的呢?
我们直接来看代码
class Father{
public int age;
public Father(){
System.out.println("父类的构造代码块");
}
{
System.out.println("父类实例化代码块");
}
static {
System.out.println("父类的静态代码块");
}
}
class Son extends Father{
public Son(){
super();
System.out.println("子类的构造代码块");
}
{
System.out.println("子类的实例化代码块");
}
static {
System.out.println("子类的静态代码块");
}
}
public class Test {
public static void main(String[] args) {
Son son1=new Son();
System.out.println("=======");
Son Son2=new Son();
}
}
运行代码
得出结论:
1.先执行父类的静态代码块,再执行子类的静态代码块,并且只执行一次,因为静态代码块是再类的加载成功时运行的,而类的加载只进行一次。
2.执行完静态代码块,再先后执行父类的实例化代码块,父类的构造代码块,最后再分别先后执行子类的实例化代码块和构造代码块。
6.继承方式
再Java中,我们支持单继承,多层继承和不同的多个类继同时承一个类。但是不能一个类同时继承多个类。
如图
7.final关键字
1.final修饰的成员变量无法被修改
2.final修饰的类无法被继承
3.final修饰的方法无法被重写