Java基础- 面向对象1

Java基础- 面向对象1,第1张

Java基础- 面向对象1 案例

大象装进冰箱

面向过程: 

  1. [打开] 冰箱
  2. [存储] 大象
  3. [关上] 冰箱

对于面向过程思想, 强调的是过程(动作/ 函数), 怎么打开冰箱,关闭冰箱都需要我们去定义,  定义[功能]-->执行[功能]

典型的面向过程语言:  C语言
面向对象:

对于面向对象来说, 大象和冰箱都是对象(实体), 而冰箱本身已经具备了打开, 关闭, 存储这些功能. 也就说,冰箱这个对象本身, 封装了这些功能, 我要用到冰箱时, 不需要去考虑怎么打开,关闭冰箱, 直接使用这些已经封装好的功能就可以了

  1. [冰箱] 打开
  2. [冰箱] 存储
  3. [冰箱] 关闭

对于面向对象思想, 强调的是对象(实体), 尽管在真正的编程中,我们需要自己设计封装对象的一些功能, 但是在用到这个对象时, 因为已经封装好了功能, 我们就可以将注意力放在对象身上, 在需要用到对象的某些功能时,我们不需要再去考虑每一步的动作,过程, 我们只需要使用这些已经存在的方法就可以了

典型的面向对象语言: C++   Java  C#

面向对象的特点:

  1. 面向对象就是一种常见的思想, 符合人们的思考习惯
  2. 面向对象的出现,将复杂的问题简单化
  3. 面向对象的出现,让曾经在过程中的执行者, 变成了对象中的指挥者

  加端端老师免费领取更多Java资料

类与对象之间的关系

类:  用Java语言对现实生活中的事物进行描述, 通过类的形式来体现

怎么描述呢?

对于事物描述,通常只关注两方面, 一个是属性,  一个是行为

只要明确该事物的属性和行为, 并定义在类中即可.

对象:  就是该类事物实实在在存在的个体

类与对象之间的关系?

类: 事物的描述

对象: 该类事物的实例,  在Java中通过 new 这个关键字来创建

类与对象的体现
//分析:
//1.属性:  轮胎数, 颜色
//2.行为:  运行
class Car{
    int num;
    //java中所有关键字都是小写的,String比较特殊,不是java的关键字,是一个类
    String color;

    void run(){
        System.out.println(num+".."+color);
    }
}

class CarDemo{
    public static void main(String[] args){
        //在计算机中创建一个car的实例, 通过new关键字来实现
        Car c=new Car();//c就是一个类类型的引用变量,指向了该类的对象,括号表示创建car对象时,需要指定一些内容,不指定则留空
        c.num=4;//给成员变量赋值叫做显式初始化
        c.color="red";
        c.run();//要使用对象中的内容, 可以通过 对象.成员 的形式来完成调用
        //事物的组成部分就叫成员, 分为两类
        //成员变量   成员函数
    }
}

定义类其实就是在定义类中的成员

成员:  成员变量<--->属性   成员函数<--->行为

成员变量与局部变量的区别
  1. 成员变量定义在类中, 整个类都可以访问;  局部变量定义在函数, 语句, 局部代码块中, 只在所属的区域有效

  2. 成员变量存储在堆内存的对象中;  局部变量存储在栈内存的方法中

  3. 成员变量随着对象的创建而存在, 随着对象的消失而消失;  局部变量随着所属区域的执行而存在, 随着所属区域的结束而释放

  4. 成员变量都有默认的初始化值;  局部变量没有默认初始值

  5. 当局部变量与成员变量重名时, 可以使用关键字this来区分

类类型参数
class CarDemo2{
    public static void main(String[] args){
                Car2 c1=new Car2();
                Car2 c2=new Car2();
                show(c1);
                show(c2);
    }

        public static void show(Car2 c){//类类型的变量一定指向对象, 要不就是null
    c.num =3;
    c.color="red";
        System.out.println(c.num+"..."+c.color);
        }
}

class Car2{
        int num;
        String color;

        void run(){
                System.out.println(num+"..."+color);
        }
}
匿名对象

顾名思义, 匿名对象就是没有名字的对象

Car c= new Car();//定义了一个名字叫c的Car类型的对象
new Car();//匿名对象, 对象被创建了,但是没有给他赋予名字
//匿名对象其实就是定义对象的简写格式
new Car().run();
//当对对象 方法 仅进行一次调用的时候, 就可以简化成匿名对象
//匿名对象可以作为实际参数进行传递
shwo(new Car());
参数传递
//基本数据类型参数传递
class Demo{
    public static void main(String[] args){
        int x=3;
        show(x);
        System.out.print("x="+x);
    }
    public static void show(int x){
        x = 4;
    }
}
//最终输出结果:  x=3
//局部变量存储于栈内存中,随着所属作用域的结束而在内存中释放, 
//因此,在show方法中,x=4的赋值 *** 作,在main函数中执行show方法后,
//这个为4的x就被释放了.而在main函数中,int x=3的作用域是整个main函数,
//main函数所属的栈的x此时始终是3,并没有被改变,
//x=4只是在show方法所属的栈中存在,且随着show方法执行结束而被释放
//在栈内存中放入变量叫做压栈, 变量被释放叫做d栈. 可以看做是一个d夹
//引用数据类型参数传递
class Demo2{
    int x= 3;
    public static void main(String[] args){
        Demo2 d=new Demo2();
        d.x=9;
        show(d);
        System.out.println(d.x);
    }
    public static void show(Demo2 d){
        d.x=4;
    }
}
//最终结果: x= 4
//对象储存于堆内存中, Demo2这个类所描述的对象Demo2有一个成员变量x(初始化为0), 
//且为x赋了值为3, 在main函数中Demo2 d=new Demo2();
//创建了一个类型是Demo2名称为d的对象,这个对象存储于堆内存中,
//在程序执行到d.x=9时,为对象d的成员变量x(值为3)赋值为9,
//在执行到show(d)时,此时堆内存中对象的成员变量x的值为9,
//show方法引用了Demo2所描述的对象, 
//此时对象中的成员变量x经过d.x=9的赋值 *** 作依据变成了9, 
//在show方法中执行了d.x=4, 为Demo2所描述的对象的成员变量x重新赋值了4, 
//此时,Demo2所描述的对象中的成员变量值变为了4, 
//因此,在执行输出语句System.out.println(d.x);时, x为4
面向对象的特征

封装

封装: 是指隐藏对象的属性和实现细节, 仅对外提供公共访问方式

好处:

将变化隔离
便于使用
提高重用性
提高安全性

封装原则:

  • 将不需要对外提供的内容都隐藏起来
  • 把属性都隐藏, 提供公共方法对其访问
    //描述一个人
    //属性: 姓名, 年龄, 升高
    //行为: 奔跑, 吃
    class Person{
        String name;
        int age;
        int height;
        void run(){
            System.out.print(name+"奔跑");
        }
        void eat(){
            System.out.print(name+"吃");
        }
    }
    class personDemo{
        public static void main(String[] args){
            Person p = new Person();
            p.name = "sunwul";
            p.run();
                    p.eat();
        }
    }
    //以上,对人进行了一个简单的封装Person,并在personDemo类中使用了Person, 
    //但这种封装方式并没有将对象中成员变量的变化隔离,程序直接使用并改变了成员变量的值,
    //这样也不安全,所以,在日常的编程中, 我们往往会使用另一种更加安全,更便于使用,
    //且能将对象中成员变量的变化隔离的方式来对对象进行封装
    class Person{
        private String name;
        private int age;
        private int height;
        public String getName(){
            return name;
        }
        public void setName(String name){
            this.name = name
        }
        public int getAge(){
            return age;
        }
        public void setAge(int age){
            this.age = age;
        }
        public int getHeight(){
            return height;
        }
        public void setHeight(int height){
            this.height=height;
        }
    }
    //这种封装方式把属性都隐藏起来了, 只提供公共方法对其访问,极大的提高了安全性, 
    //是大家公认的封装方式

    java中最小的封装体就是函数,

    封装不一定需要对属性方法进行私有, 不私有一样能进行封装. 私有仅仅是封装的一种体现

    私有就是封装,这是对的,  封装就是私有,这是错误的

继承

一个类继承另一个类, 这个类叫做子类, 被继承的类叫做父类/超类/基类

所有的类都直接或间接的继承Object类,Object类也被称为根类

继承的好处:

  1. 提高了代码的复用性
  2. 让类与类之间产生了关系, 给第三个特征多态提供了前提

java中 支持单继承, 不直接支持多继承, 但对C++中的多继承机制进行改良

单继承: 一个子类只能有一个直接父类

多继承: 一个子类可以有多个直接父类(java中不直接支持,进行改良, 在java中是通过"多实现"的方式来体现)

class A{}
class B{}
class C extends A{}//单继承
class C extends A,B{}//多继承,类C可以同时拥有A与B的内容--java不直接支持

//java中不直接支持多继承的原因: 当多个父类中有相同成员, 会产生调用的不确定性
class A{
    void show(){
        System.out.println("A")
    }
}
class B{
    void show(){
        System.out.println("B")
    }
}
class C extends A,B{}//多继承
new C().show();//由于C同时继承了A与B,当父类中有相同的方法时,此时调用方法会产生不确定性,程序不知道应该调用哪个父类中的show方法

java支持多重(多层)继承: C继承B, B继承A 

使用多重继承会出现继承体系, 当要使用一个继承体系时:

  1. 查看该体系中的顶层类, 了解该体系的基本功能
  2. 创建体系中的最子类对象, 完成功能的使用

什么时候定义继承

当类与类之间存在着所属关系的时候, 就定义继承. X是Y中的一种, X extends Y

子类与父类中, 成员特点的体现

  1. 成员变量

当本类的成员和局部变量同名, 用this区分

当子父类中的成员变量同名用super区分父类

class fu{
    int num = 4;
}
class zi extends fu{
    int num = 5;
    void show(){
        //使用this来区分本类的成员, 使用super来区分父类的成员
        System.out.println(this.num+"..."+super.num);
    }
}
class ExtendsDemo{
    public static void main(String[] args){
        zi z=new zi();
        z.show();
    }
}

子类不能直接访问父类中私有的成员

  1. 成员函数

当子父类中出现成员函数一模一样的情况, 会运行子类的函数, 这种现象称为==覆盖 *** 作==, 这是函数在子父类中的特性

函数的两个特性:

  1. 重载  overload   同一个类中, 允许同名函数存在,多个同名函数以参数列表(个数or类型)区分

  2. 覆盖  override   子类中, 覆盖也称为重写, 覆写.   子类与父类有同名函数,子类中的函数会覆盖父类中的函数,当执行子类中这个同名函数时,会运行子类的函数

    > 覆盖 *** 作的注意事项:
    >
    > 1. 必须体现出子类与父类的函数一模一样(返回值类型,函数名,参数列表)
    > 2. 子类方法覆盖父类方法时, 子类权限必须大于等于父类权限
    > 3. 当父类函数权限为private时,因为是私有的,所以不能被外部类所发现,因此也不能被覆盖
    > 4. 当父类的函数被静态static修饰时,也不能覆盖,只有子类也和父类一模一样被静态修饰才能覆盖(==静态只能覆盖静态或被静态覆盖==)
    >
    > > class fu{ >     //当在父类show方法加上public权限修饰符时,编译报错 >      >          void show(){ >                 System.out.println("fu show run"); >         } > } > class zi extends fu{ >         void show(){ >                 System.out.println("zi show run"); >         } > } > class OverrideDemo{ >  public static void main(String[] args){ >      zi z=new zi(); >          //子类覆盖父类方法 >      z.show(); >  } > } > //子类与父类函数一模一样,子类想要覆盖父类方法,要注意子类权限必须大于等于父类权限 > //当父类函数权限为private时,因为是私有的,所以不能被外部类所发现,因此也不能被覆盖 > //当父类的函数被静态static修饰时,也不能覆盖,只有子类函数也是静态的才能覆盖(静态只能覆盖静态或被静态覆盖) >
    >
    > 什么时候使用覆盖 *** 作:
    >
    > 1. 当对一个类进行扩展时,子类需要保留父类的功能声明,但是要定义子类中该功能的特有内容时, 就使用覆盖 *** 作完成

构造函数

子父类中的构造函数的特点:

在子类构造对象时, 发现访问子类构造函数时,父类也运行了

​
class fu{
        fu(){
                System.out.println("fu show run");
        }
}
class zi extends fu{
        zi(){
                System.out.println("zi show run");
        }
}
class ExtendsDemo{
 public static void main(String[] args){
     new zi();
 }
}

​

原因是: 在子类的构造函数中第一行就有一个默认的隐式语句--super(),
与this()代表本类的空参数构造函数一样,super()代表继承的父类的空参数构造函数

子类的实例化过程: 子类中所有的构造函数默认都会访问父类中的空参数的构造函数

为什么子类实例化的时候要访问父类中的构造函数?

因为子类继承了父类, 获取到了父类中的内容(属性), 所以在使用父类内容之前,
要先看父类是如何对自己的内容进行初始化的;
所以子类在构造对象时, 必须访问父类的构造函数,
为了完成这个必须的动作, 就在子类的构造函数中加入了super()语句

如果父类中没有定义空参数构造函数,
那么子类的构造函数必须用super明确要调用父类中哪个构造函数,
同时子类构造函数中如果调用了本类构造函数, 那么super就没有了,
因为super和this都只能定义在第一行, 所以只能有一个,
但是可以保证的是: 子类中肯定会有其他的构造函数访问父类的构造函数

注意:  super语句必须要定义在子类构造函数的第一行, 因为父类的初始化动作要先完成

一个对象的实例化过程

Person  p=new Person();

  1. JVM 会先读取指定的路径下的Person.class文件, 并加载进内存, 会先加载Person的父类(如果有直接的父类的情况下)
  2. 在堆内存中开辟空间,分配地址
  3. 在对象空间中, 对对象的属性进行默认初始化
  4. 调用对应的构造函数进行初始化
  5. 在构造函数中, 第一行会先调用父类中的构造函数进行初始化
  6. 父类初始化完毕后, 再对子类的属性进行显式初始化
  7. 再进行子类构造函数的特定初始化
  8. 初始化完毕后, 将地址值赋值给引用变量

抽象

当一个事物中有不确定的成员时,那么这个事物也不具体,不确定,因此当事物中有不确定成员时,这个事物也是不确定的,这就是抽象类的由来,即当一个类中有抽象的成员变量或函数时,这个类也是抽象的

当一个类描述事物时,没有足够的信息来描述事物,这个类就是抽象类

特点

  1. 方法只有声明没有实现时, 该方法就是抽象方法, 需要被abstract修饰, 抽象方法必须定义在抽象类中, 该类也必须被abstract修饰

  2. 抽象类不可以被实例化, 因为调用抽象方法没有意义(抽象方法没有方法体[没有实现,没有大括号])

    abstract class demo{
       void show1();//没有方法体
       void show2(){};//有方法体,只不过方法体没有内容,是一个空方法体
    }

  3. 抽象类必须有其子类覆盖了所有的抽象方法后,该子类才可以被实例化, 否则这个子类还是抽象类

细节

  1. 抽象类中有构造函数吗?

    ==有, 用于给子类对象进行初始化==

  2. 抽象类可以不定义抽象方法吗?

    ==可以, 但是很少见, 目的就是不让该类创建对象.==

    ==通常这个类中的方法有方法体,但是却没有内容==

  3. 抽象关键字不可以和哪些关键字共存?

    1. private(私有): 因为在子父类中,当父类方法被private修饰时, 子类继承不能发现该方法, 而抽象类必须有其子类覆盖了所有的抽象方法后,该子类才可以被实例化
    2. static(静态): 当一个方法被static修饰时,不需要对象,其它类就能通过该方法所在的类名直接调用此静态方法, 而抽象方法没有方法体, 调用后也无意义
    3. final(最终): 抽象类必须有其子类覆盖了所有的抽象方法后,该子类才可以被实例化, 而被final修饰的函数无法被覆盖, 因此会产生冲突
  4. 抽象类和一般类的异同点

    1. 抽象类和一般类都是用来描述事物的,都在内部定义了成员,只是一般类有足够的信息来描述事物,而抽象类描述事物的信息有可能不足
    2. 一般类中不能定义抽象方法, 只能定义非抽象方法, 抽象类中可以定义抽象方法, 同时也可以定义非抽象方法
    3. 一般类可以被实例化, 抽象类不可以被实例化
  5. 抽象类一定是一个父类吗?
    是的, 抽象类想要被使用,必须有子类覆盖其方法才可以对子类进行实例化

示例

//需求: 公司中程序员有姓名,工号,薪水,工作内容; 项目经理除了有姓名,工号,薪水,工作内容外,还有奖金, 对给出的需求进行数据建模
//分析: 想找出涉及的对象, 通过名词提炼法:
//程序员: 属性(姓名,工号,薪水) 行为(工作)
//项目经理: 属性(姓名,工号,薪水,奖金) 行为(工作)
//程序员和项目经理不存在直接继承关系,但是有着共性内容,因此向上抽取建立体系

abstract class Employee{
    private String name;
    private int id;
    private double pay;
    //初始化雇员
    Employee(String name,int id,double pay){
        this.name=name;
        this.id=id;
        this.pay=pay;
    }
    //工作内容是不具体的, 抽象
    public abstract void work();
}

class Programmer extends Employee{
    //初始化程序员
    Programmer(String name,int id,double pay){
        //父类中已经将功能定义完成,子类只需要将功能直接拿过来使用就可以了
        //父类中已经存在对属性的赋值动作,子类直接调用父类的功能
        super(name,id,pay);
    }
    //想要使用父类的功能必须覆盖父类的抽象方法
    public void work(){
        System.out.println("code...");
    }
}

class Manager extends Employee{
    private int bonus;
    Manager(String name,int id,double pay,int bonus){
        super(name,id,pay);
        this.bonus=bonus;
    }
    //想要使用父类的功能必须覆盖父类的抽象方法
    public void work(){
        System.out.println("manage...");
    }
}

接口

interface

当一个抽象类中的方法都是抽象的时候,这时可以将该抽象类用另一种形式定义和表示, 就是接口 interface

==接口类是抽象类, 接口中的方法都是抽象的==

==接口不可以实例化, 只能有实现了接口的子类并覆盖了接口中的所有抽象方法后,该子类才可以实例化, 否则这个子类就是一个抽象类==

定义接口使用的关键字不是class, 是interface

对于接口当中常见的成员(这些成员都有固定的修饰符):

  1. 全局常量(public static final)
  2. 抽象方法(public abstract)

由此得出结论: 接口中的成员都是公共的权限

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

接口实现

implements

==类与类之间是继承关系, 类与接口之间是实现关系==

class DemoImpl implements Demo{
    void show1(){
             System.out.println("...");   
    }
    void show2(){}
}

在java中不直接支持多继承, 因为会产生调用的不确定性. 所以java将多继承机制进行改良, 变成了多实现

==一个类可以实现多个接口==, 解决了java中不直接支持多继承的问题

interface A{
    public void show();
}
interface B{
    public void show();
}
class Test implements A,B{
    //接口都是抽象类,子类覆盖了接口中的所有抽象方法后才能实例化
    public void show(){//此时Test中的show方法同时覆盖了A和B中的show方法,不存在不确定性

    }
}

==一个类在继承另一个类的同时, 还可以实现多个接口==

接口的出现避免了单继承的局限性

class Car{
        public void run();
}
interface A{
     void show();
}
interface B{
    void show();
}
//一个类在继承另一个类的同时, 还可以实现多个接口
class Test extends Car implements A,B{
    //接口都是抽象类,子类覆盖了接口中的所有抽象方法后才能实例化
    public void show(){//此时Test中的show方法同时覆盖了A和B中的show方法,不存在不确定性

    }
}

==接口与接口之间可以存在继承关系, 而且接口可以多继承(原理在于方法体是否存在)==

当一个接口同时继承多个有相同成员的接口时, 因为接口不能被实例化, 想要使用功能就必须要有子类覆盖所有抽象方法, 因此当多个接口存在继承关系时, 实现子类接口的类会覆盖所有接口的相同成员, 不存在不确定性.

java不直接支持多继承的原因就是因为方法体导致的调用不确定性, 而接口中的方法都是抽象方法,没有方法体, 因此接口与接口之间可以多继承

interface A{
    void show();
}
interface B{
    void method();
}
interface C extends A,B{//接口与接口之间可以存在继承关系, 而且接口可以多继承
    void function();
}
class D implements C{
    //接口C继承了A和B,当D要实现接口C时,除了要覆盖C的抽象方法外,还要覆盖A和B的抽象方法
    //需要覆盖3个方法
    public void show();
    public void method();
    public void function();
}

接口的特点

  • 接口是==对外暴露的规则==
  • 接口是程序的==功能扩展==
  • 接口的出现==降低耦合性==
  • 接口可以用来多实现
  • 类与接口之间是实现关系,而类可以继承一个类的同时实现多个接口
  • 接口与接口之间可以有继承关系,且可以多继承

接口与抽象的异同点

  • 共性: 都是不断抽取出来的抽象的概念

  • 区别1: 抽象类需要被继承, 且只能单继承

    ​                   接口需要被实现, 可以多实现

  • 区别2: 抽象类的继承, 是 " is a "关系, 在定义该体系的基本共性内容
    接口的实现, 是 " like a "关系, 在定义体系的额外功能

  • 区别3: 抽象类中可以定义非抽象方法, 供子类直接使用
    接口的方法都是抽象的, 接口中的成员独有固定修饰符(全局常量(public static final);
    抽象方法(public abstract))

引出多态的概念

interface USB{//对外暴露的规则
    public void open();
    public void close();
}
class UPan implements USB{//实现规则
    public void open(){
        System.out.println("UPan...open"); 
    }
    public void close(){
        System.out.println("UPan...close"); 
    }
}
class BookPC{
    public static void main(String[] args){
        useUSB(new UPan());
    }
    //使用规则
    public static void useUSB(USB u){//接口类型的引用, 用于接收(指向)接口的子类对象--多态
        u.open();
        u.close();
    }
}

多态

某一类事物的多种存在形态(对象的多态性)

class 动物{}
class 猫 extends 动物{}
class 狗 extends 动物{}
猫 x= new 猫();
动物 x= new 猫();//父类引用指向子类对象,对象的多态性
//猫这类事物既具备猫的形态,又具备动物的形态, 这就是对象的多态性
//简单来说: 就是一个对象对应着不同类型

多态在代码中的体现: ==父类或者接口的引用指向其子类的对象==

多态的好处: 提高了代码的扩展性, 前期定义的代码可以使用后期的内容

多态的弊端: 前期定义的内容不能使用(调用)后期子类的特有内容

多态的前提: 必须有关系: 继承,实现    要有覆盖

abstract class Animal{
    abstract void eat();
}
class Dog extends Animal{
    void eat(){
        System.out.println("啃骨头..");
    }
    void lookHome(){
        System.out.println("看家..");
    }
}
class Cat extends Animal{
    void eat(){
        System.out.println("吃鱼..");
    }
    void catchMouse(){
        System.out.println("抓老鼠..");
    }
}
class DuoTaiDemo{
    public static void main(String[] args){
        Cat c=new Cat();
        Dog d=new Dog();
        method(c);
        method(d);
    }
    public static void method(Animal a){//Animal a= new 子类();
        //动物都具备吃的功能,直接在这里指挥动物吃, 此函数的参数传入哪种动物,哪种动物就执行吃的动作
        a.eat();
    }
}

转型

Animal a=new Cat();//自动类型提升, 猫对象提升为动物类型, 但是猫特有功能无法访问
//作用就是限制对子类特有功能的访问, 专业术语: 向上转型
//如果还想用具体动物猫的特有功能, 可以将该对象进行向下转型
Cat c=(Cat)a;//向下转型的目的是为了使用子类中的特有方法


Animal a=new Animal();
Cat a=(Cat)a;//非法,此时a是父类对象,不能转型

==对于转型,自始至终都是子类对象在做着类型的变化==

类型判断

instanceof  :  用于判断对象的具体类型, 只能用于引用数据类型的判断

abstract class Animal{
    abstract void eat();
}
class Dog extends Animal{
    void eat(){
        System.out.println("啃骨头..");
    }
    void lookHome(){
        System.out.println("看家..");
    }
}
class Cat extends Animal{
    void eat(){
        System.out.println("吃鱼..");
    }
    void catchMouse(){
        System.out.println("抓老鼠..");
    }
}
class DuoTaiDemo{
    public static void main(String[] args){
        Cat c=new Cat();
        Dog d=new Dog();
        method(c);
        method(d);
    }
    public static void method(Animal a){//Animal a= new 子类();
        a.eat();
        //instanceof: 通常在向下转型前用于代码健壮性的判断
        if(a instanceof Cat){//如果函数接收的对象是Cat
            Cat c=(Cat)a;//将此对象向下转型
            c.catchMouse();//向下转型后可以使用对象的特有方法
        }else if(a instanceof Dog){
            Dog d=(Dog)a;
            d.lookHome();
        }
    }
}

多态中的成员特点

1.成员变量

  • 编译时: 参考引用型变量所属的类中是否有调用的成员变量, 有: 编译通过;  没有: 编译失败
  • 运行时: 参考引用型变量所属的类中是否有调用的成员变量, 并运行该所属类中的成员变量
  • 简单来说, 编译和运行都参考创建对象时等号的左边的所属类
  • 一般来说,不会出现这种情况, 当父类有成员时,子类不需要在创建同样的成员,直接使用父类的成员即可
    class Fu{
        int num=3;
    }
    class Zi extends Fu{
        int num=4;
    }
    class DuoTaiDemo{
        public static void main(String[] args){
            Fu f=new Zi();//参考创建对象时等号的左边的所属类,Fu中已经有num这个变量了
            System.out.println(f.num);//此时输出的是Fu中的num值:3
        }
    }

    2.成员函数(非静态)

  • 编译时: 参考引用型变量所属的类中是否有调用的函数, 有: 编译通过;  没有: 编译失败
  • 运行时: 参考的是对象所属的类中是否有调用的函数, ==动态绑定==到指定对象中运行
  • 简单来说, 编译时看创建对象时等号的左边所属类是否有调用的函数, 运行时看创建对象时等号的右边所属类是否有调用的函数.(编译看左边,运行看右边)
    class Fu{
        void show(){
            System.out.println("fu show");
        }
    }
    class Zi extends Fu{
        void show(){
            System.out.println("zi show");
        }
    }
    class DuoTaiDemo{
        public static void main(String[] args){
            Fu f=new Zi();//此时f指向的内存地址中存储的是Zi这个类所描述的对象
            //show方法本身是非静态的,依赖于对象,要调用show必须动态绑定到指定对象中运行
            //此时f指向的对象是Zi,因此,当运行f.show()时,运行的是Zi对象中的show方法,输出zi show
            f.show();//输出: zi show
        }
    }

    3.静态函数

  • 编译时: 参考引用型变量所属的类中是否有调用的静态函数
  • 运行时: 参考引用型变量所属的类中是否有调用的静态函数
  • 简单来说, 编译和运行都参考创建对象时等号的左边的所属类
  • 对于静态函数, 是不需要对象的, 直接用类名调用即可
    class Fu{
        static void show(){
            System.out.println("fu show");
        }
    }
    class Zi extends Fu{
        static void show(){//重写父类静态方法必须和父类一模一样
            System.out.println("zi show");
        }
    }
    class DuoTaiDemo{
        public static void main(String[] args){
            //show方法是静态的,不依赖对象,依赖于类
            //此时,什么类调用这个show方法,这个show绑定到什么类里面
            Fu f=new Zi();
            //f是Fu类型,此时show方法绑定的是Fu类型,因此会执行Fu中的show方法
            f.show();//输出: fu show
            //以此来看,静态函数其实不涉及到多态, 因为对象的多态性, 有对象才存在
            //静态函数可以直接用类来调用,不需要用对象,因此当使用静态函数时,直接用类来调用就可以了,不需要再浪费内存去创建对象
            //如果非要用对象来调用静态函数,则和成员变量一样,编译和运行时都参考引用型变量所属的类中是否有调用的静态函数
        }
    }

    4.总结

    成员变量和静态函数编译和运行都参考创建对象时等号的左边的所属类, 只有非静态函数不同, 编译看创建对象时等号的左边的所属类, 运行看创建对象时等号的右边的所属类, 因为非静态函数需要被对象调用,==动态绑定==到指定对象中运行


    构造函数

    构建创造对象时调用的函数

    特点

    1. 函数名与类名相同
    2. 不用定义返回值类型, 因为没有具体的返回值
    3. 一个类可以有多个构造函数

    作用

    给对象进行初始化, 使对象在一创建时就具备某些内容

    注意

    1. 创建对象都必须要通过构造函数初始化
    2. 多个构造函数是以重载的形式存在的
    3. 一个类如果没有定义过构造函数, 那么该类会有一个默认的构造函数; 如果定义了指定的构造函数, 那么类中的构造函数就没有了
    4. 构造函数也需要进入栈内存(压栈), 执行完毕后被释放(d栈)
    5. 构造函数执行到最后也有return;只是一般不写,编译器也会默认加上
    class Person{
        private String name;
        private int age;
        public String getName(){
            return name;
        }
        public void setName(String name){
            this.name = name
        }
        public int getAge(){
            return age;
        }
        public void setAge(int age){
            this.age = age;
        }
        //构造函数
        Person(){
            System.out.print("这是一个构造函数,在创建对象时会被立即调用");
        }
    }
    //创建对象都必须要通过构造函数来初始化
    class Car{
        //即使一个类中, 没有定义任何属性与方法, 也会有默认构造函数
        Car(){}
    }
    //如果定义了指定的构造函数, 那么类中的构造函数就没有了
    class Car{
        //编译器在编译时,当检查到类中没有指定构造函数,会生成一个默认构造函数
        //当检查到类中指定了构造函数时, 编译器就会处理这个构造函数, 不会再生成默认构造函数
        Car(){System.out.print("构造函数");}
    }
    //多个构造函数是以重载的形式存在的
    class Car{
        //重载: 函数名相同,参数个数与类型不同
        Car(String name,int age){System.out.print(name+"..."+age);}
        Car(int age,String name){System.out.print(name+"..."+age);}
    }

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/4997161.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-14
下一篇 2022-11-14

发表评论

登录后才能评论

评论列表(0条)

保存