Java面向对象04

1.继承

继承由来

class Student
{
    String name;
    int age;
    void study()
    {
        System.out.println("study");
    }
}
class Worker
{
    String name;
    int age;
    void work()
    {
        System.out.println("work");
    }
}

通过代码演示描述多个事物。多个事物之间发现有共同的属性和行为。那么代码的复用性很差,那么怎么办呢?
可以将相同的代码进行抽取,抽取出来后放在单独的类中。

class Student
{
    void study()
    {
        System.out.println("study");
    }
}
class Worker
{
   
    void work()
    {
        System.out.println("work");
    }
}
class Person
{
    String name;
    int age;
}

代码抽取到了Person类中,但是Student和Worker类与Person类没有任何关系,那么Student和Worker类如何能使用到Person中的name和age属性。

为了让类与类之间能有关系,需要使用Java中提供的继承这种机制。继承需要用到关键字extends。

//将学生和工人的共性抽取到Person这个类中
class Person {
    String name;
    int age;
}
//学生通过extends继承Person,即Person为Student的父类
class Student extends Person{
    //学生类中的自己特有方法
    public void study(){
        System.out.println(name+"同学正在学习。。。。");
    }
}
//工人通过extends继承Person,即Person为工人的父类
class Worker extends Person{
    //工人类中的自己特有方法
    public void work(){
        System.out.println(name+"工人正在工作。。。。");
    }
}
//测试类
public  class Test{
    public static void main(String[] args) {
        Student s = new Student();
        s.name = "小明";
        s.study();
       
        Worker w = new Worker();
        w.name = "张三";
        w.work();
    }
}

继承的好处

  1. 继承的出现提高了代码的复用性,提高软件开发效率。
  2. 继承的出现让类与类之间产生了关系,提供了多态的前提。

2.单继承与多继承

继承让类与类之间产生了关系,那到底什么时候使用继承呢?
使用继承,必须保证类与类之间有所属(is a)关系,即xxx是zzz中的一种。例如:苹果是水果中的一种,狗是犬科中的一种
Java只支持单继承,不支持多继承。一个类只能有一个父类,不可以有多个父类。
class SubDemo extends Demo{} //ok
class SubDemo extends Demo1,Demo2…//error
Java支持多层继承(继承体系)
class A{}
class B extends A{}
class C extends B{}
定义继承需要注意
不要仅为了获取其他类中某个功能而去继承。
日常开发中小技巧
多层次继承出现的继承体系中,通常看父类中的功能,了解该体系的基本功能,建立子类对象即可使用该体系功能。
多继承虽然能使子类同时拥有多个父类的特征,但是其缺点也是很显著的,主要有两方面:
(1)如果在一个子类继承的多个父类中拥有相同名字的实例变量,子类在引用该变量时将产生歧义,无法判断应该使用哪个父类的变量。
(2)如果在一个子类继承的多个父类中拥有相同方法,子类中有没有覆盖该方法,那么调用该方法时将产生歧义,无法判断应该调用哪个父类的方法。

3.子父类中成员变量的特点

了解了继承给我们带来的好处,提高了代码的复用性。继承让类与类或者说对象与对象之间产生了关系。当继承出现后,类的成员之间产生了那些变化呢?
类的成员重点学习成员变量、成员函数和构造函数的变化。

成员变量:如果子类父类中出现不同名的成员变量,这时的访问是没有任何问题。

class Fu
{
    //Fu中的成员变量。
    int num = 5;
}
class Zi extends Fu
{
    //Zi中的成员变量
    int num2 = 6;
    void show()
    {
        //访问父类中的num
        System.out.println("Fu num="+num);
        //访问子类中的num2
        System.out.println("Zi num2="+num2);
    }
}
class Demo
{
    public static void main(String[] args)
    {
        Zi z = new Zi(); //创建子类对象
        z.show(); //调用子类中的show方法
    }
}

代码说明:Fu类中的成员变量是非私有的,子类中可以直接访问,若Fu类中的成员变量私有了,子类是不能直接访问的。
当子父类中出现了同名成员变量时,在子类中若要访问父类中的成员变量,必须使用关键字super来完成。

class Fu
{
    //Fu中的成员变量。
    int num = 5;
}
class Zi extends Fu
{
    //Zi中的成员变量
    int num = 6;
    void show()
    {
        //子父类中出现了同名的成员变量时
        //在子类中需要访问父类中非私有成员变量时,需要使用super关键字
        //访问父类中的num
        System.out.println("Fu num="+super.num);
        //访问子类中的num2
        System.out.println("Zi num2="+this.num);
    }
}
class Demo5
{
    public static void main(String[] args)
    {
        Zi z = new Zi(); //创建子类对象
        z.show(); //调用子类中的show方法
    }
}

当程序执行new Zi();这时会加载Zi类字节码文件,但由于Zi类继承了Fu类,因此需要将父类字节码文件加载进方法区。当Zi和Fu的字节码加载完毕后。会执行new Zi();即在堆内存中创建Zi类对象,并为其分配内存地址0x99;对象的内存空间分配结束之后,开始成员变量默认初始化,此时需要注意Fu的num同样也会在此对象中。紧接着开始zi构造函数压栈,在zi的构造函数中有隐式的super()语句,此时Fu的构造函数也会压栈,Fu的构造函数压栈后会将Fu的num显示初始化。接着Fu构造函数弹栈,执行Zi的构造函数,Zi的num显示初始化。接着Zi构造函数弹栈。此时Zi对象在堆中创建完成,并将内存地址0x99赋值给
main方法中的z引用。接着调用zi的show方法,show方法压栈。

注意:super和this的用法相似,this:代表本类的对象引用。super:代表的父类内存空间,而不是父类的引用。子父类中同名的成员变量,这种情况开发中不用,因为父类一旦描述完成了属性,子类直接使用就可以了。

4.子父类中成员函数的特点

当在程序中通过对象调用方法时,会先在子类中查找有没有对应的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法

class Fu{
    public void show(){
        System.out.println("Fu类中的show方法执行");
    }
}
class Zi extends Fu{
    public void show2(){
        System.out.println("Zi类中的show2方法执行");
    }
}
public  class Test{
    public static void main(String[] args) {
        Zi z = new Zi();
        z.show(); //子类中没有show方法,但是可以找到父类方法去执行
        z.show2();
    }
}

成员方法特殊情况——覆盖
子类中出现与父类一模一样的方法时,会出现覆盖操作,也称为override重写、复写或者覆盖。

class Fu
{
    void show()
    {
        System.out.println("Fu show");
    }
}
class Zi extends Fu
{
    //子类复写了父类的show方法
    void show()
    {
        System.out.println("Zi show");
    }
}

覆盖的应用
当子类需要父类的功能,而功能主体子类有自己特有内容时,可以复写父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的内容。

举例:比如手机,当描述一个手机时,它具有发短信,打电话,显示来电号码功能,后期由于手机需要在来电显示功能中增加显示姓名和头像,这时可以重新定义一个类描述手机,并继续原有的描述手机的类。并在新定义的类中覆盖来电显示功能,在其中增加显示姓名和头像功能。

public class Test {
    public static void main(String[] args) {
        new NewPhone().showNum();
    }
}
class Phone{
    public void sendMessage(){
        System.out.println("发短信");
    }
    public void call(){
        System.out.println("打电话");
    }
    public void showNum(){
        System.out.println("来电显示号码");
    }
}
class NewPhone extends Phone{
   
    //覆盖父类的来电显示号码功能,并增加自己的显示姓名和图片功能
    public void showNum(){
        //调用父类已经存在的功能使用super
        super.showNum();
        //增加自己特有显示姓名和图片功能
        System.out.println("显示来电姓名");
        System.out.println("显示头像");
    }
}

5.重写的注意事项

重写需要注意的细节问题

  1. 子类方法覆盖父类方法,必须要保证权限大于等于父类权限
  2. 静态只能覆盖静态,或者被静态覆盖,意思就是 静态函数不存在重写!
  3. 写法上稍微注意:必须一模一样:函数的返回值类型 函数名 参数列表都要一样
    总结:
    当一个类是另一个类中的一种时,可以通过继承,来扩展功能;
    如果父类具备的功能内容需要子类特殊定义时,使用重写。

6.子父类中构造函数的特点

在创建子类对象时,父类的构造函数会先执行,因为子类中所有构造函数的第一行有默认的隐式super();语句,调用本类中的构造函数用this(实参列表)语句,调用父类中的构造函数用super(实参列表)。
为什么子类对象初始化都要访问父类中的构造函数?因为子类继承了父类的内容,所以创建对象时必须要先看父类是如何对内容进行初始化的。

public class Test {
    public static void main(String[] args) {
        new Zi();
    }
}
class Fu{
    int num ;
    Fu(){
        System.out.println("Fu构造函数"+num);
        num = 4;
    }
}
class Zi extends Fu{
    Zi(){
        System.out.println("Zi构造函数"+num);
    }
}

执行结果:
Fu构造函数0
Zi构造函数4
子类中的构造函数为什么有一句隐式的super()呢?
原因:子类会继承父类中的内容,所以子类在初始化时,必须先到父类中去执行父类的初始化动作。才可以更方便的使用父类中的内容。
当父类中没有空参数构造函数时,子类的构造函数必须有显示的super语句指定要访问的父类中的构造函数。

7.子类实例化过程的细节

如果子类的构造函数第一行写了this调用了本类其他构造函数,那么super调用父类的语句还有吗?
这时是没有的,因为this()或者super(),只能定义在构造函数的第一行,因为初始化动作要先执行。
父类构造函数中是否有隐式的super呢?也是有的。记住:只要是构造函数默认第一行都是super();
父类的父类是谁呢?super调用的到底是谁的构造函数呢?
Java体系在设计,定义了一个所有对象的父类Object 。
注意:
类中的构造函数默认第一行都有隐式的super()语句,在访问父类中的构造函数。所以父类的构造函数既可以给自己的对象初始化,也可以给自己的子类对象初始化。如果默认的隐式super语句没有对应的构造函数,必须在构造函数中通过this或者super的形式明确调用的构造函数。

8.final关键字

继承的出现提高了代码的复用性,并方便开发。但随之也有问题,有些类在描述完之后,不想被继承,或者有些类在中的部分方法功能是固定的,子类不能重写。可是当子类继承了这些特殊类之后,就可以对其中的方法进行重写,那怎么解决呢?
要解决上述的这些问题,需要使用到一个关键字final,final的意思为最终,不可变。
final是个修饰符,它可以修饰类,类的成员,以及局部变量。
● final修饰类不可以被继承,但是可以继承其他类
● final修饰的方法不可以被覆盖,但父类中没有被final修饰方法,子类覆盖后可以加final
● final修饰的变量称为常量,这些变量只能赋值一次,定义的时候必须有初始值
● final修饰的引用类型变量,表示该引用变量的引用不能变,而不是引用所指的对象中的数据还是可以变化的
什么时候会在程序中定义final常量呢?
当程序中一个数据是固定不变的,这时为了增加阅读性,可以给该数据起个名字。为了保证这个变量的值不被修改,加上final修饰,变量就为阅读性很强的常量。书写规范,被final修饰的常量名所有的字母都是大写的。如果由多个单词组成单词间通过 _ 连接。
通常规范中:常量名称所有字母大写,若有多个单词组成,单词间使用下划线连接。
public static final修饰的常量称为全局常量;
public static final double PI = 3.14159265358979323846;
public static final String APP_SEPARATOR = “/”;

9.抽象类

抽象类由来
当编写一个类时,我们往往会为该类定义一些方法,这些方法是用来描述该类的行为方式,那么这些方法都有具体的方法体。

但是有的时候,某个父类只是知道子类应该包含怎么样的方法,但是无法准确知道子类如何实现这些方法。比如一个图形类应该有一个求周长的方法,但是不同的图形求周长的算法不一样。那该怎么办呢?

分析事物时,发现了共性内容,就出现向上抽取。会有这样一种特殊情况,就是功能声明相同,但功能主体不同。那么这时也可以抽取,但只抽取方法声明,不抽取方法主体。那么此方法就是一个抽象方法。

描述狗:行为:吼叫
描述狼:行为:吼叫
狗和狼之间有共性,可以进行向上抽取。抽取它们的所属共性类型:犬科。由于狗和狼都具有吼叫功能,但是他们具体怎么吼叫却不一样。这时在描述犬科时,发现了有些功能(吼叫)不具体,这些不具体的功能,需要在类中标识出来,通过java中的关键字abstract(抽象)。

当定义了抽象函数的类也必须被abstract关键字修饰,被abstract关键字修饰的类是抽象类。

abstract class 犬科
{
    static abstract void 吼叫();//抽象函数。需要abstract修饰,并分
号;结束
}
class Dog extends 犬科
{
    void 吼叫()
    {
        System.out.println("汪汪汪汪");
    }
}
class Wolf extends 犬科
{
    void 吼叫()
    {
        System.out.println("嗷嗷嗷嗷");
    }
}

抽象类的特点

  1. 抽象类和抽象方法都需要被abstract修饰。抽象方法一定要定义在抽象类中。
  2. 抽象类不可以创建实例,原因:调用抽象方法没有意义。
  3. 只有覆盖了抽象类中所有的抽象方法后,其子类才可以实例化。否则该子类还是一个抽象类。

之所以继承,更多的是在思想,是面对共性类型操作会更简单。
细节问题

  1. 抽象类一定是个父类?
    是的,因为不断抽取而来的。
  2. 抽象类是否有构造函数?
    有,虽然不能给自己的对象初始化,但是可以给自己的子类对象初始化。
    抽象类和一般类的异同点:
    相同:
    ● 它们都是用来描述事物的。
    ● 它们之中都可以定义属性和行为。
    不同:
    ● 一般类可以具体的描述事物。抽象类描述事物的信息不具体
    ● 抽象类中可以多定义一个成员:抽象函数。
    ● 一般类可以创建对象,而抽象类不能创建对象。
  3. 抽象类中是否可以不定义抽象方法。
    是可以的,那这个抽象类的存在到底有什么意义呢?仅仅是不让该类创建对象。
  4. 抽象关键字abstract不可以和哪些关键字共存?
    ● final:fianl修饰的类是无法被继承的,而abstract修饰的类一定要有子类.final修饰的方法无法被覆盖,但是abstract修饰的方法必须要被子类去实现的。
    ● static:静态修饰的方法属于类的,它存在与静态区中,和对象就没关系了。而抽象方法没有方法体,使用类名调用它没有任何意义。
    ● private:私有的方法子类是无法继承到的,也不存在覆盖,而abstract和private一起使用修饰方法,abstract既要子类去实现这个方法,而private修饰子类根本无法得到父类这个方法。互相矛盾。

10.接口

当一个抽象类中的所有方法都是抽象方法时,那么这个抽象类就可以使用另外一种接口这种机制来体现。
接口怎么定义呢?定义普通的类或者抽象类可以使用class关键字,定义接口必须interface关键字完成。

interface class Demo
{
        abstract void show1();
        abstract void show2();
}

接口中成员的特点

  1. 接口中可以定义变量,但是变量必须有固定的修饰符修饰,public static final 所以接口中的变量也称之为常量。
  2. 接口中可以定义方法,方法也有固定的修饰符,public abstract
  3. 接口中的成员都是公共的。
  4. 接口不可以创建对象。
  5. 子类必须覆盖掉接口中所有的抽象方法后,子类才可以实例化。否则子类是一个抽象类。
interface Demo//定义一个名称为Demo的接口。
{
    public static final int NUM = 3;
    public abstract void show1();
    public abstract void show2();
}
//定义子类去覆盖接口中的方法。子类必须和接口产生关系,类与类的关系是继承,
类与接口之间的关系是 实现。通过 关键字 implements
class DemoImpl implements Demo//子类实现Demo接口。
{
    //重写接口中的方法。
    public void show1(){}
    public void show2(){}
}

接口最重要的体现:解决多继承的弊端。将多继承这种机制在java中通过多实现完成了。

interface A
{
        void show1();
}
interface B
{
        void show2();
}
class C implements A,B// 多实现。同时实现多个接口。
{
        public void show1(){}
        public void show2(){}
}

怎么解决多继承的弊端呢?
弊端:多继承时,当多个父类中有相同功能时,子类调用会产生不确定性。
其实核心原因就是在于多继承父类中功能有主体,而导致调用运行时,不确定运行哪个主体内容。
为什么多实现就解决了呢?
因为接口中的功能都没有方法体,由子类来明确。
类继承类同时实现接口
子类通过继承父类扩展功能,通过继承扩展的功能都是子类应该具备的基础功能。如果子类想要继续扩展其他类中的功能呢?这时通过实现接口来完成。

class Fu
{
        public void show(){}
}
interface Inter
{
        pulbic void show1();
}
class Zi extends Fu implements Inter
{
        public void show1()
        {
              
        }
}

接口的出现避免了单继承的局限性。父类中定义的事物的基本功能。接口中定义的事物的扩展功能
接口多继承
多个接口之间可以使用extends进行继承。

interface A{
        void show();
}
interface B{
        void show1();
}
interface C{
        void show2();
}
interface D extends A,B,C{
        void show3();
}

在开发中如果多个接口中存在相同方法,这时若有个类实现了这些接口,那么就要实现接口中的方法,由于接口中的方法是抽象方法,子类实现后也不会发生调用的不确定性。
没有抽象方法的抽象类的由来
在开发中,若一个接口中有多个抽象方法,但是实现这个接口只使用其中某些方法时,这时我们仍然需要将其他不使用的方法实现,这样明显不符合我们的要求,但不实现这些方法又不行。那么怎么办呢?可以使用一个抽象类,作为过度,而这个抽象类实现这个接口,所有方法都以空实现存在。这就是没有抽象方法的抽象类的存在价值。我们只要继承这个抽象类,覆盖其中需要使用的方法即可。

//拥有多个方法的接口
interface Inter{
        void show();
        void show1();
        void show2();
        void show3();
}
//作为过度的抽象类,此类将接口的所有方法都实现,这里的实现是空实现
abstract class AbsInter implements Inter{
        public void show(){}
        public void show1(){}
        public void show2(){}
        public void show3(){}
      
}
/*
//此类直接实现Inter,但只使用其他show和show2方法,这样导致其他两个方法也要
实现
//不符合我们的要求
class SubInter2 implements Inter{
        public void show() {
                System.out.println("show");
        }
        public void show1() {
        }
        public void show2() {
                System.out.println("show2");
              
        }
        public void show3() {
        }
}
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值