Java 中的面向对象编程(中级篇,1万字零基础详解)总

一、包

1. 基本概念

  • 包的作用:
    在这里插入图片描述
  • 包的本质分析:
    在这里插入图片描述
  • 包的命名规则:
    在这里插入图片描述

2. 基本用法

  • 常用的包:
  1. java.lang.* :lang 包是基本包,默认引入,不需要再引入;
  2. java.util.* :util 包,系统提供的工具包, 工具类,使用 Scanner ;
  3. java.net.* :网络包,网络开发 ;
  4. java.awt.* :是做 java 的界面开发,GUI;
  • 如何引入包:
    在这里插入图片描述

3. 注意事项和使用细节

  • 注意事项:
    在这里插入图片描述
  • 总结:
  1. 引入包的概念是为了区别同名的类;
  2. 一个类只能存在于一个包内;
  3. 一个包内的所有类,类名都是唯一的,不能重复;
  4. 不同的包内的类,可以取相同的类名;

二、访问修饰符

1. 基本概念

  • java 提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围):
  1. 公开级别:用 public 修饰,对外公开;
  2. 受保护级别:用 protected 修饰,对子类和同一个包中的类公开;
  3. 默认级别:没有修饰符号,向同一个包的类公开;
  4. 私有级别:用 private 修饰,只有类本身可以访问,不对外公开;
  • 访问范围:
    在这里插入图片描述
  • 注意:这里的子类指的是不同包下面的子类;和父类在同一个包下的子类,是属于同包关系的;
  • 对于访问修饰符的理解,可以类比一个人,他的个人信息,举例如下:
  • 有一部分是只有他自己知道的,这部分就是 private;他自身就相当于 同类/本类;
  • 有一部分是只有他和他的兄弟姐妹知道的,这部分就是 默认,兄弟姐妹就相当于 同一个包的的其他类;有个例外,就是同一个包下的子类,也相当于他的兄弟姐妹!
  • 有一部分是他和他的兄弟姐妹还有他的孩子知道的,这部分就是 protected,他的孩子就相当于 子类;
  • 剩下的一部分是所有人都知道的,这部分就是 public,所有人就相当于 不同包下面的全部类。

2. 注意事项和使用细节

  • 注意事项:
    在这里插入图片描述
  • 注意,protected 和 private 是不能修饰一个类的。
  • 修饰父类的两种修饰符的意义:
    • 第一种,父类的访问修饰符是 public,此时该父类可以被所有类继承;
    • 第二种,父类的访问修饰符是 默认,此时该父类只能被和他同一个包下面的所有类继承。

三、封装

1. 基本概念

  • 封装(encapsulation):就是把抽象出的数据(属性)和对数据的操作(方法)封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作(方法),才能对封装起来的数据进行操作。
  • 封装的好处:
    在这里插入图片描述

2. 基本使用

  • 封装的实现步骤:
    在这里插入图片描述
  • 案例入门:

设置程序事实现下面的要求:在这里插入图片描述

  • 代码实现:

public class Encapsulation01 {
    public static void main(String[] args) {

        Person person = new Person();
        //person.age = 30; 会报错,不能直接修改Person类private 属性
        person.setName("韩顺平");
        person.setAge(30);// 只能通过Person类提供的方法来修改其中的private 属性
        person.setSalary(30000);
        System.out.println(person.info());
        System.out.println(person.getSalary());
    }
}
/*
	不能随便查看人的年龄,工资等隐私,并对设置的年龄进行合理的验证。年龄合理就设置,否则给默认。
年龄, 必须在 1-120, 年龄, 工资不能直接查看 , name的长度在 2-6字符 之间
*/
class Person {
    public  String name; //名字公开
    private int age; //age 私有化
    private double salary; 

    public String getName() {
        return name;
    }

    public void setName(String name) {
        //加入对数据的校验,相当于增加了业务逻辑
        if(name.length() >= 2 && name.length() <=6 ) {
            this.name = name;
        }else {
            System.out.println("名字的长度不对,需要(2-6)个字符,默认名字");
            this.name = "无名人";
        }
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        //判断
        if(age >= 1 && age <= 120) {//如果是合理范围
            this.age = age;
        } else {
            System.out.println("你设置年龄不对,需要在 (1-120), 给默认年龄18 ");
            this.age = 18;//给一个默认年龄
        }
    }

    public double getSalary() {
        //可以这里增加对当前对象的权限判断
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
    //写一个方法,返回属性信息
    public String info() {
        return "信息为 name=" + name  + " age=" + age + " 薪水=" + salary;
    }
}

  • 思考:如果在Person 类中提供了构造器,那么在其他类中就可以直接给Person类中的private 变量赋值了,而且可以跳过Person类中的权限控制,这样封装不就没有用了吗?
    • 回答:确实可以直接使用Person类提供的构造器直接给private 变量赋值,但是我们可以在Person类的构造器中调用set 方法,便可以解决权限控制的问题了。
  • 代码如下:

public class Encapsulation01 {
    public static void main(String[] args) {
        //如果我们自己使用构造器指定属性
        Person smith = new Person("smith", 80, 50000);
        System.out.println("====smith的信息======");
        System.out.println(smith.info());
    }
}

class Person {
    public  String name; //名字公开
    private int age; //age 私有化
    private double salary; 

    //构造器 alt+insert
    public Person() {
    }

    //有三个属性的构造器
    public Person(String name, int age, double salary) {
        // this.name = name;
        // this.age = age;
        // this.salary = salary;
        // 我们可以将set方法写在构造器中,这样仍然可以验证
        setName(name);
        setAge(age);
        setSalary(salary);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        //加入对数据的校验,相当于增加了业务逻辑
        if(name.length() >= 2 && name.length() <=6 ) {
            this.name = name;
        }else {
            System.out.println("名字的长度不对,需要(2-6)个字符,默认名字");
            this.name = "无名人";
        }
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        //判断
        if(age >= 1 && age <= 120) {//如果是合理范围
            this.age = age;
        } else {
            System.out.println("你设置年龄不对,需要在 (1-120), 给默认年龄18 ");
            this.age = 18;//给一个默认年龄
        }
    }

    public double getSalary() {
        //可以这里增加对当前对象的权限判断
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
    //写一个方法,返回属性信息
    public String info() {
        return "信息为 name=" + name  + " age=" + age + " 薪水=" + salary;
    }
}

四、继承

1. 基本概念

  • 继承可以解决代码复用,让我们的编程更加靠近人类思维。
  • 当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,父类的所有子类不需要再重新定义这些属性和方法,只需要通过 extends 来声明继承父类即可。
  • 继承的示意图如下:
    在这里插入图片描述
  • 继承的好处:
    • 代码的复用性提高了 ;
    • 代码的扩展性和维护性提高了;

2.基本用法

  • 基本语法:
    在这里插入图片描述

3. 继承的细节(重要!)

  • 细节如下:
  1. 子类继承了父类所有的属性和方法,父类中的非私有属性和方法可以在子类直接访问, 但是父类中的私有属性和方法不能在子类直接访问,要通过父类提供的公共方法去访问 ;
  2. 子类调用构造器初始化时,必须调用父类的构造器, 完成父类的初始化 ;
  3. 当新创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类的无参构造器被覆盖了,则必须在子类的构造器中用 super 关键字 去指定使用父类另外的构造器完成对父类的初始化工作,否则,编译不会通过;
  4. 当新创建子类对象时,如果想要指定调用父类的某个构造器,则需要在子类的构造器中显式的调用 : super(参数列表) ;
  5. super 关键字在子类构造器中使用时,必须放在其构造器第一行,并且super 只能在构造器中使用 ;
  6. 由于super() 和 this() 都只能放在构造器的第一行,因此这两个方法不能同时存在于一个构造器中 ;
  7. java中的所有类都是 Object 类的子类, Object 是所有类的基类;
  8. 父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类) ;
  9. 子类最多只能继承一个父类(指直接继承),即 java 中是单继承机制; 思考:如何让 A 类继承 B 类和 C 类? 【答案:A 继承 B, B 继承 C】
  10. 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系;
  • 代码举例:

public class ExtendsDetail {
    public static void main(String[] args) {
        System.out.println("====对象====");
        Sub sub3 = new Sub("king", 10); //创建了子类对象 sub2
        //sub.sayOk();
    }
}

//子类
public class Sub extends Base {
		/*
        1. 调用父类的无参构造器super(), 或者什么都不写,默认就是调用super();
        	super();// 父类的无参构造器
        2. 调用父类的 Base(String name) 构造器;
        	super("hsp");
        3. 调用父类的 Base(String name, int age) 构造器;
        	super("king", 20);
		*/
		
    //细节: super在使用时,必须放在构造器第一行;
    //细节: super() 和 this() 都只能放在构造器第一行,因此这两个方法不能同时存在于一个构造器; 
    public Sub() {// 子类的无参构造器
    	this(String name, int age);// 指定调用了自己的有参构造器,其实父类的构造器就不会调用了;
        System.out.println("子类Sub()构造器被调用....");
    }
	public Sub(String name) {
        // 什么都不写,隐式地存在 super();
        System.out.println("子类Sub(String name)构造器被调用....");
    }
	
    public Sub(String name, int age) {//子类的有参构造器
        //super(); // 默认调用父类的无参构造器;
        super("smith", 10);// 指定调用父类的 Base(String name) 构造器;
        System.out.println("子类Sub(String name, int age)构造器被调用....");
    }
	
	// 子类的普通方法
    public void sayOk() {
        //细节:父类的非私有的属性和方法可以在子类直接访问
        //但是父类的私有属性和方法不能在子类直接访问
        System.out.println(n1 + " " + n2 + " " + n3);
        test100();
        test200();
        test300();
        //test400(); 报错
        //要通过父类提供公共的方法去访问;
        System.out.println("n4=" + getN4());// 可以访问私有属性n4了
        callTest400();// 可以访问父类的私有方法 test400()
    }
}
//父类
public class Base { 
    //4个属性
    public int n1 = 100;
    protected int n2 = 200;
    int n3 = 300;
    private int n4 = 400;
	
	// 3个不同的构造器:
    public Base() { //无参构造器
        System.out.println("父类Base()构造器被调用....");
    }
    public Base(String name, int age) { //有参构造器
        //默认super()
        System.out.println("父类Base(String name, int age)构造器被调用....");
    }
    public Base(String name) { //有参构造器
        System.out.println("父类Base(String name)构造器被调用....");
    }
    
    //父类提供一个public的方法,返回了n4;
    public int getN4() {
        return n4;
    }

	// 父类的call方法,调用了私有的test400()方法;
    public void callTest400() {
        test400();
    }
    
    // 4种不同权限的普通方法:
    public void test100() { // 公有
        System.out.println("test100");
    }
    protected void test200() { // 受保护
        System.out.println("test200");
    }
    void test300() { // 默认
        System.out.println("test300");
    }
    private void test400() { // 私有
        System.out.println("test400");
    }
}

4. 继承的本质分析

  • 案例分析:
    在这里插入图片描述
  • 代码如下:

public class ExtendsTheory {
    public static void main(String[] args) {
    
        Son son = new Son();
        System.out.println(son.name);//返回就是子类的name= 大头儿子
        // System.out.println(son.age);//会报错,访问受限
        System.out.println(son.getAge());//使用父类的公共get方法,返回的就是父类的age= 39
        System.out.println(son.hobby);//返回的就是爷类的hobby= 旅游
    }
}

//爷类
class GrandPa { 
    String name = "大头爷爷";
    String hobby = "旅游";
}

//父类
class Father extends GrandPa {
    String name = "大头爸爸";
    private int age = 39;

    public int getAge() {
        return age;
    }
}

//子类
class Son extends Father { 
    String name = "大头儿子";
}

  • 内存示意图如下:
    在这里插入图片描述
  • 内存布局解释:
  1. Son son = new Son(),首先在方法区中加载了Son 类和Son类继承的所有父类的信息,先查找Son 类的所有父类信息,从最顶层的父类开始加载,也就是Object -> GrandPa -> Father -> Son。
  2. 加载完类的信息后,其次在堆内存中开辟一个内存空间(地址),空间里面又划分了几个独立的类空间块(空间块个数由加载类的个数决定)。
  3. 接着给每个类空间块分配(初始化)属性,第一个是爷类空间块,分配爷类的属性;第二个是父类空间块,分配父类的属性;最后是子类空间块,分配子类的属性;由于各个类空间块是相互独立的,所以属性名相同也不会相互影响。
  4. 在分配完毕对象内存空间后,最后将内存地址返回给main 栈中的对象名 son,此时son 便指向了该对象内存,可以引用里面的信息。
  • 假设现在要用 son 输出某个属性,注意:要按照查找关系来返回信息,如下:
  1. 首先看子类是否有该属性;
  2. 如果子类有这个属性,并且可以访问,则返回信息;
  3. 如果子类没有这个属性,接着看直接的父类有没有这个属性:
    (1)如果父类有该属性,并且可以访问,就返回信息;
    (2)如果父类有该属性,但是不可以访问(访问修饰符限制),则直接返回访问受限的错误信息(报错),不会跳过父类接着访问上级的父类属性。
  4. 如果直接的父类没有这个属性,才可以按照 3 的规则,继续找上级父类,直到 Object类 结束,或者找不到这个属性返回错误信息;

五、super 关键字

1. 基本概念

  • super 关键字代表父类的引用,用于访问父类的属性、方法、构造器;
  • 基本语法:
    在这里插入图片描述
  • super的注意事项:
  1. super 虽然代表了父类的引用,但并不完全等同于父类,由于父类中私有变量/方法受到 private 的 限制,所以除了父类自身的方法外,其他所有类都不能访问。因此,super 关键字并不完全等同于父类自身;
  2. 而this 关键字代表了对象对自身的引用,类自身中的方法访问类中的私有变量/方法时不会受到 private 的 限制,因此this 关键字可以完全等同于对象自己。
  3. super 的访问不限于直接的父类,如果爷爷类和子类中有同名的成员,也可以使用 super 去访问爷爷类的成员;如果多个父类(上级类)中都有同名的成员,使用 super 访问时遵循就近原则。先是父类,再到爷类,再到祖宗类…
  • super的使用细节:
    在这里插入图片描述
  • 注意:
  1. 在子类的方法中访问父类的属性/方法时,若是使用 this.属性/方法,则会从子类的属性/方法开始查找,子类没有该属性/方法,再查找父类、爷类…
  2. 在子类的方法中访问父类的属性/方法时,若是使用 super.属性/方法,则会直接从父类的属性/方法开始查找,父类没有,再向爷类…查找;

2. super 和 this 的比较

  • 如下图所示:
    在这里插入图片描述

六、方法重写

1. 基本概念

  • 方法重写(覆盖):子类中有一个方法,和父类的某个方法的名称、返回类型、形参列表一样,就说子类的这个方法重写了父类的方法。
  • 为什么要引入方法重写:
  1. 当一个子类继承一个父类时,它同时继承了父类的属性和方法。子类可以直接使用父类的属性和方法,如果父类的方法不能满足子类的需求,则可以在子类中对父类的方法进行重写(或覆盖)。
  2. 在方法重写时,如果子类需要引用父类中原有的方法,可以使用 super 关键字。当子类重写父类方法后,在子类对象使用该同名方法时,会执行子类中重写的方法。

2. 注意事项和使用细节

如下图所示:
在这里插入图片描述

  • 对于第3点的解释:子类重写方法的修饰符 >= 父类方法的修饰符。

3. 方法重载和方法重写的比较

如下图所示:
在这里插入图片描述

七、多态

  • 多态:一个事物的多种形态;一个对象具有多种形态。多态是面向对象的第三大特征,多态是建立在封装和继承基础之上的。

1. 多态的具体体现

1.1 方法的多态

  • 方法重写和方法重载体现了方法的多态。
  • 代码举例:

public class PloyMethod {
    public static void main(String[] args) {
        //方法重载体现多态
        A a = new A();
        //这里我们传入不同的实参,就会调用不同sum 方法,就体现多态
        System.out.println(a.sum(10, 20));
        System.out.println(a.sum(10, 20, 30));

        //方法重写体现多态
        B b = new B();
        a.say();
        b.say();
    }
}

//父类
class B { 
    public void say() {
        System.out.println("B say() 方法被调用...");
    }
}

//子类
class A extends B {
	// 两个sum 方法构成方法重载
    public int sum(int n1, int n2){// 和下面sum 构成重载
        return n1 + n2;
    }
    public int sum(int n1, int n2, int n3){
        return n1 + n2 + n3;
    }
    
	// 重写了父类中的say 方法
    public void say() {
        System.out.println("A say() 方法被调用...");
    }
}

1.2 对象的多态(核心、重难点)

  • 重要概念:(要背)
    在这里插入图片描述
  • 说明:一个对象在创建时,有两种类型,一种是编译类型,另一种是运行类型;语法如下:
    • 编译类型 对象名 = new 运行类型();
  • 举例说明:
    在这里插入图片描述
  • 注意:若编译类型为父类,则对象名又称为 父类的引用,下面会使用后者以区分普通的创建创建对象。
  • 代码说明:
//体验对象多态特点
public class PolyObject {
    public static void main(String[] args) {
        
        //animal 编译类型为 Animal , 运行类型为 Dog
        Animal animal = new Dog();
        //因为运行时, 执行到下一行代码时,animal 的运行类型是 Dog,
        //所以调用的cry 方法就是Dog 的cry
        animal.cry(); //小狗汪汪叫

        //animal 编译类型为 Animal,运行类型为 Cat
        animal = new Cat();
        animal.cry(); //小猫喵喵叫
    }
}

class Animal {
    public void cry() {
        System.out.println("Animal cry() 动物在叫....");
    }
}

class Dog extends Animal {
    public void cry() {
        System.out.println("Dog cry() 小狗汪汪叫...");
    }
}

class Cat extends Animal {
    public void cry() {
        System.out.println("Cat cry() 小猫喵喵叫...");
    }
}

2. 多态的注意事项和细节

  • 实现多态的前提是:两个对象(类)存在继承关系。

2.1 多态的向上转型

  • 基本概念:
    在这里插入图片描述
  • 解释:以 Animal a = new Cat(); 为例
  1. 首先由于Animal、Cat 类拥有父子类的继承关系,在方法区中分别加载了两者的类信息;
  2. 其次,在堆内存中,仅开辟了一个 Cat类的对象空间,并没有开辟Animal 类的对象空间;
  3. 接着,在对象空间中进行初始化属性,父子类的属性都会初始化;
  4. 然后,将初始化完毕的对象空间地址返回给父类的引用;
  • 注意:
  1. 实质上父类的引用是指向了子类对象,由于继承关系,所以父类的引用可以调用父类中的所有成员(调用时需遵守访问权限);
  2. 由于编译类型不是子类本身,所以父类的引用不能调用子类中的特有成员(换句话说,父类没有而子类有的成员,不能调用;父类有而子类没有的成员,可以调用);
  3. 由于继承关系,在调用父子类中同名的方法时,根据运行类型来决定调用顺序(换句话说,运行类型是子类类型,所以优先调用子类的同名方法,任何情况都成立)。

2.2 多态的向下转型

  • 基本概念:
    在这里插入图片描述
  • 向上转型后有个不足之处,那就是父类的引用不能访问子类中特有的成员,怎么解决?即使用向下转型。
  • 解释:以 Animal a = new Cat(); 为例
  1. 向下转型语法:Cat cat = (Cat) a ;
  2. 父类的引用必须指向的是当前目标类型的对象,意思是说,在定义时父类的引用是指向Cat 类对象的,那么向下转型时父类的引用也只能转成Cat 类对象,而不能转成其他类的对象;
  3. 向下转型后,cat 可以调用其指向的对象空间的所有成员(注意访问权限),相当于 Cat cat = new Cat();
  • 代码解释:

public class PolyDetail {
    public static void main(String[] args) {

        //向上转型: 父类的引用指向了子类的对象
        //语法:父类类型引用名 = new 子类类型();
        Animal animal = new Cat();
        Object obj = new Cat();//可以吗? 可以,因为 Object 也是 Cat的父类

        //向上转型调用方法的规则如下:
        //(1)可以调用父类中的所有成员(需遵守访问权限)
        //(2)但是不能调用子类的特有的成员
        //(#)因为在编译阶段,能调用哪些成员,是由编译类型来决定的
        animal.catchMouse(); // 错误,子类特有的方法,不能调用
        
        //(4)最终运行效果看子类(运行类型)的具体实现, 
        //   即调用方法时,按照从子类(运行类型)开始查找方法,然后调用
        animal.eat();//猫吃鱼..调用了子类的eat方法;
        animal.run();//跑
        animal.show();//hello,你好
        animal.sleep();//睡

		//若想要调用Cat的 catchMouse方法
        //多态的向下转型
        //(1)语法:子类类型 引用名 =(子类类型)父类引用;
        Cat cat = (Cat) animal;
        cat.catchMouse();//猫抓老鼠,向下转型后可以输出
        
        //(2)要求父类的引用必须指向的是当前目标类型的对象
        Dog dog = (Dog) animal; //可以吗?不可以
    }
}

// 动物类
class Animal {
    String name = "动物";
    int age = 10;
    public void sleep(){
        System.out.println("睡");
    }
    public void run(){
        System.out.println("跑");
    }
    public void eat(){
        System.out.println("吃");
    }
    public void show(){
        System.out.println("hello,你好");
    }
}
// 猫类继承动物类
class Cat extends Animal {
    public void eat(){//方法重写
        System.out.println("猫吃鱼");
    }
    public void catchMouse(){//Cat特有方法
        System.out.println("猫抓老鼠");
    }
}
// 狗类继承动物类
class Dog extends Animal {
	//Dog是Animal的子类
}

2.3 多态中属性的调用

  1. 属性和方法不同,没有重写之说,因此调用属性由编译类型决定。
  2. 与继承的属性调用比较:继承中的编译类型和运行类型都是子类类型,所以直接调用属性时,是从子类开始查找的。
  • 代码解释:

public class PolyDetail02 {
    public static void main(String[] args) {
        //调用属性看编译类型
        Base base = new Sub();// 向上转型
        System.out.println(base.count);// 看编译类型 10
        Sub sub = new Sub();
        System.out.println(sub.count);// 20
    }
}

class Base { //父类
    int count = 10;//属性
}
class Sub extends Base {//子类
    int count = 20;//属性
}

2.4 instanceof 比较操作符

  • instanceof 比较操作符:用于判断对象的运行类型是否为 XX 类型或 XX 类型的子类型,返回类型为布尔值。
  • 代码解释:

public class PolyDetail03 {
    public static void main(String[] args) {
        BB bb = new BB();
        System.out.println(bb instanceof  BB);// true
        System.out.println(bb instanceof  AA);// true

        //aa 编译类型 AA, 运行类型是BB
        //BB是AA子类
        AA aa = new BB();
        System.out.println(aa instanceof AA);// true
        System.out.println(aa instanceof BB);// true

        Object obj = new Object();
        System.out.println(obj instanceof AA);//false
        String str = "hello";
        //System.out.println(str instanceof AA);// 不能比较
        System.out.println(str instanceof Object);//true
    }
}

class AA {} //父类
class BB extends AA {}//子类

3. Java 的动态绑定机制(非常非常重要!)

  • 当调用对象方法的时候,该方法会和对象的内存地址/运行类型绑定;
  • 当调用对象属性时,没有动态绑定机制,处在哪个类中,便调用该类的属性;
  • 举例说明:
    在这里插入图片描述
  • 思考:将上图的B 类中的sum 方法和sum1 方法注释掉后,main 方法中的输出语句结果是什么?
  • 代码说明:

public class DynamicBinding {
    public static void main(String[] args) {
        //a 的编译类型 A, 运行类型 B
        A a = new B();//向上转型
        System.out.println(a.sum());//  -> 30
        System.out.println(a.sum1());// -> 20
    }
}

class A {//父类
    public int i = 10;
    //动态绑定机制:

    public int sum() {//父类sum()
        return getI() + 10; // 20 + 10
    }

    public int sum1() {//父类sum1()
        return i + 10; // 10 + 10
    }

    public int getI() { // 父类getI
        return i;
    }
}

class B extends A {//子类
    public int i = 20;

//    public int sum() {
//        return i + 20;
//    }

    public int getI() {//子类getI()
        return i;
    }

//    public int sum1() {
//        return i + 10;
//    }
}
  • 解释:
  1. a.sum 语句: 子类B 中没有该方法,则调用父类A 的sum 方法,在sum 方法中又调用了getI 方法,根据动态绑定机制,调用对象方法时,该方法会和对象的运行类型绑定,因此,此时调用的是子类B 中的getI方法;在子类B 中的gatI 方法中,调用了属性 i,调用对象属性时,处在哪个类中,便调用该类的属性,因此此时调用的是子类B 中的属性 i;所以最后a.sum 返回的结果是 30。
  2. a.sum1 语句: 子类B 中没有该方法,则调用父类A 的sum1 方法,在子类A 中的sum1 方法中,调用了属性 i,调用对象属性时,处在哪个类中,便调用该类的属性,因此此时调用的是子类A 中的属性 i;所以最后a.sum1 返回的结果是 20。
  • 注意!对于对象属性的调用,是没有动态绑定机制的;对象属性的直接调用是由编译类型决定。但是在上例中,是在对象方法中调用了属性,因此调用时处在哪个类中,便调用该类的该属性。(这是我个人的理解)

4. 多态的应用

4.1 多态数组

  • 多态数组:数组的定义类型为父类类型,里面保存的实际元素类型为子类类型。
  • 应用实例:现有一个继承结构如下:要求创建 1 个 Person 对象、2 个 Student 对象和 2 个 Teacher 对象, 统一放在数组 中,并调用每个对象 say 方法。还需调用子类特有的方法,比如 Teacher 有一个 teach , Student 有一个 study 怎么调用?
  • 代码实现:

public class PloyArray {
    public static void main(String[] args) {
        //应用实例:现有一个继承结构如下:要求创建1个Person对象、
        // 2个Student 对象和2个Teacher对象, 统一放在数组中,并调用每个对象say方法

        Person[] persons = new Person[5];
        persons[0] = new Person("jack", 20);
        persons[1] = new Student("mary", 18, 100);
        persons[2] = new Student("smith", 19, 30.1);
        persons[3] = new Teacher("scott", 30, 20000);
        persons[4] = new Teacher("king", 50, 25000);

        //循环遍历多态数组,调用say
        for (int i = 0; i < persons.length; i++) {
            //老师提示: person[i] 编译类型是 Person ,运行类型是是根据实际情况有JVM来判断
            System.out.println(persons[i].say());//动态绑定机制
            // 使用 类型判断 + 向下转型.
            if(persons[i]  instanceof  Student) {//判断person[i] 的运行类型是不是Student
                Student student = (Student)persons[i];//向下转型
                student.study();
              //也可以使用一条语句 ((Student)persons[i]).study();
            } else if(persons[i] instanceof  Teacher) {
                Teacher teacher = (Teacher)persons[i];
                teacher.teach();
            } else if(persons[i] instanceof  Person){
                //System.out.println("你的类型有误, 请自己检查...");
            } else {
                System.out.println("你的类型有误, 请自己检查...");
            }
        }
    }
}


public class Person {//父类
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = 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;
    }

    public String say() {//返回名字和年龄
        return name + "\t" + age;
    }
}
public class Teacher extends Person {
    private double salary;

    public Teacher(String name, int age, double salary) {
        super(name, age);
        this.salary = salary;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
    //写重写父类的say方法
    @Override
    public String say() {
        return "老师 " + super.say() + " salary=" + salary;
    }
    //特有方法
    public void teach() {
        System.out.println("老师 " + getName() + " 正在讲java课程...");
    }
}
public class Student extends Person {
    private double score;

    public Student(String name, int age, double score) {
        super(name, age);
        this.score = score;
    }

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }
    //重写父类say
    @Override
    public String say() {
        return "学生 " + super.say() + " score=" + score;
    }
    
    //特有的方法
    public void study() {
        System.out.println("学生 " + getName() + " 正在学java...");
    }
}

4.2 多态参数

  • 多态参数:方法定义的形参类型为父类类型,传入的实参类型允许为子类类型。
  • 应用实例:
    在这里插入图片描述
  • 代码实现:

public class PloyParameter {
    public static void main(String[] args) {
        Worker tom = new Worker("tom", 2500);
        Manager milan = new Manager("milan", 5000, 200000);
        PloyParameter ployParameter = new PloyParameter();
        ployParameter.showEmpAnnual(tom);
        ployParameter.showEmpAnnual(milan);

        ployParameter.testWork(tom, milan);
        

    }

    //showEmpAnnual(Employee e)
    //实现获取任何员工对象的年工资,并在main方法中调用该方法 [e.getAnnual()]
    public void showEmpAnnual(Employee e) {
        System.out.println(e.getAnnual());//动态绑定机制.
    }
    //添加一个方法,testWork,如果是普通员工,则调用work方法,如果是经理,则调用manage方法,使用了可变参数
    public void testWork(Employee... e) {
        for (int i = 0; i < e.length; i++) {
            if (e[i] instanceof Worker) {
                ((Worker) e[i]).work();
            } else if (e[i] instanceof Manager) {
                ((Manager)e[i]).manage();
            }
        }
    }
}

public class Employee {
    private String name;
    private double salary;

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }
    //得到年工资的方法
    public double getAnnual() {
        return 12 * salary;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public double getSalary() {
        return salary;
    }
    public void setSalary(double salary) {
        this.salary = salary;
    }
}


public class Manager extends Employee{

    private double bonus;

    public Manager(String name, double salary, double bonus) {
        super(name, salary);
        this.bonus = bonus;
    }

    public double getBonus() {
        return bonus;
    }
    public void setBonus(double bonus) {
        this.bonus = bonus;
    }
    
    public void manage() {
        System.out.println("经理 " + getName() + " is managing");
    }
    
    //重写获取年薪方法
    @Override
    public double getAnnual() {
        return super.getAnnual() + bonus;
    }
}

public class Worker extends Employee {

    public Worker(String name, double salary) {
        super(name, salary);
    }
    public void work() {
        System.out.println("普通员工 " + getName() + " is working");
    }

    @Override
    public double getAnnual() {
    	//因为普通员工没有其它收入,则直接调用父类方法 
        return super.getAnnual();
    }
}

总结

  • 本文是小白博主在学习B站韩顺平老师的Java网课时整理总结的学习笔记,在这里感谢韩顺平老师的网课,如有有兴趣的小伙伴也可以去看看。
  • 面向对象编程中级部分的学习总结结束啦,下面接着学习面向对象编程高级部分的知识。
  • 最后,如果本文有什么错漏的地方,欢迎大家批评指正!一起加油!!我们下一篇博文见吧!
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林二月er

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值