Java学习笔记------面向对象之封装、继承、多态(三)

该学习笔记是根据阿玮老师《黑马程序员Java零基础视频教程》总结出来的,非常感谢阿玮老师。

一. 封装

  1. 封装:把对象的属性和方法结合成一个独立的整体,隐藏实现细节,并提供对外访问的接口。对象代表什么,就得封装对应的数据,并提供数据对应的行为(JavaBean类可以理解为封装类)。封装可以看成是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。如果要访问该类的代码和数据,必须通过特定的方式(接口控制)。
  2. 封装的步骤:首先使用关键字private修饰成员变量;然后对成员变量提供setXxx方法和getXxx方法。使用setXxx和getXxx方法就可以访问成员变量,并且能够对成员变量进行输入限制。

二. 继承

  1. 继承:java中提供一个关键字extends,用这个关键字可以让一个类和另外一个类之间建立联系,继承中有子类(派生类)和父类(基类和超类)。
    使用继承的优点:(1)可以把多个子类中重复的代码抽取到父类中,提高代码的复用性;(2)子类可以在父类的基础上增加其它的功能,使子类更强大;(3)当类与类之间存在相同(共性)的内容,并满足子类是父类中的一种,就可以考虑使用继承来优化代码。使用继承的格式:public class 子类 extends 父类 {}
  2. 继承的特点:Java只支持单继承,不支持多继承,但支持多层继承。
    (1)其中支持单继承表示一个子类只能继承一个父类(一个儿子只有一个亲生的爸爸);
    (2)不支持多继承表示子类不能同时继承多个父类(儿子不能有多个亲生爸爸);
    (3)支持多层继承表示子类A可以继承父类B,父类B可以继承父类C(儿子、爸爸、爷爷、太爷爷…,爸爸是儿子的直接父类,爷爷是儿子的间接父类)。
    (4)JAVA中每一个类都直接或者间接的继承于Object类,例如public class A extends B{}表示子类A继承于父类B,public class A{} 等价于 public class A extends Object{}表示A在默认情况下继承于Object类,也就是说JVM(java虚拟机)会查看当前的类是否有父类,如果有就继承当前存在的父类,如果没有JVM会给该子类一个默认的父类Object。
    (5)Object类在整个Java类继承体系中处于绝对顶层的位置,它是所有类的根、祖宗。所谓继承就是把共有的数据和行为抽取到父类中,这样所有子类都会自动具备,从而达到复用性。因此Object类的所有方法都是共有性最强的方法,只要是Java对象就肯定具备这些行为。
  3. 子类到底能继承父类中的那些内容
    父类主要由三部分组成:构造方法、成员变量、成员方法,这三部分都可以使用private修饰符(私有访问)来修饰。其中,
    (1)父类的构造方法无论是否使用private来修饰,子类都无法继承父类的构造方法;
    (2)父类的成员变量无论是否使用private来修饰,子类都可以继承父类的成员变量。但对于父类中的私有成员变量(private修饰的成员变量),子类只能继承,不能直接调用;如果想调用,那么需要使用对应的getXxx、setXxx方法调用;
    (3)父类的成员方法如果没有被private修饰(非私有的),子类可以继承并调用,如果被private修饰(私有的),子类不能继承。
  4. 关于构造方法是否可以被继承的代码:
public class Test {
    public static void main(String[] args) {
        ZiLei z1 = new ZiLei(); //利用空参构造方法的代码没问题
/*
ZiLei z2 = new ZiLei("张三",22);利用带参数的构造方法会报错,因为ZiLei没有带参构造方法,而且ZiLei也无法继承FuLei中的带参构造方法. 
 */
    }
}
// 在一个java文件中可以存在多个类,但这么多类中只能有一个类被public修饰,而被public修饰的类名必须和文件名保持一致.
class FuLei{
    String name;
    int age;

    public FuLei() {
    }

    public FuLei(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

class ZiLei extends FuLei{
// ZiLei中没有显示的写构造方法,此时表示空参构造方法。
}
  1. 关于成员变量是否可以被继承的代码:
public class Test {
    public static void main(String[] args) {
        ZiLei z1 = new ZiLei(); 
/*
在ZiLei中通过关键字new实例化对象的时候,会在对象(内存块)中存放ZiLei的成员变量game和FuLei的成员变量name和age.
因此子类可以继承父类的成员变量,但父类中的成员变量能不能直接调用就要看成员变量是私有的还是非私有的.
*/

        z1.game = "王者荣耀";
        z1.name = "张三"; //父类中的name是非私有成员变量,因此可以直接调用赋值;
//z1.age = 23; 父类中age是私有成员变量,因此ZiLei只能继承,不能直接调用;如果非要调用只能使用setAge、getAge方法。
        z1.setAge(23);
        System.out.println(z1.name+","+z1.getAge()+","+z1.game);
    }
}

class FuLei {
    String name; //非私有成员变量
    private int age; //私有成员变量

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

class ZiLei extends FuLei {
    String game;
}
/*
输出:
张三,23,王者荣耀
*/
  1. 关于成员方法是否可以被继承的代码:
public class Test {
    public static void main(String[] args) {
// 虚方法表:非private(非私有的)、非static(非静态的)、非final,满足这三个条件就称为虚方法表。
        ZiLei z1 = new ZiLei();
//此时ZiLei的虚方法表中共有2个虚方法:ziShou(自己的)、fuShow1(父类的)
        z1.ziShou();
        z1.fuShow1();
// z1.fuShow2();此时会报错,这是因为父类中的fuShow2是私有成员方法,子类无法继承.
    }
}

class FuLei{
    public void fuShow1(){
        System.out.println("public ----- fuShow1");
    }

    private void fuShow2(){
        System.out.println("private ----- fuShow2");
    }
}

class ZiLei extends FuLei{
    public void ziShou(){
        System.out.println("public ----- ziShow1");
    }
}
/*
输出:
public ----- ziShow1
public ----- fuShow1
*/
  1. 继承成员变量的访问特点
    继承的成员变量访问特点:采用就近原则,谁距离我近,我就用谁;即先在局部位置找,然后在本类(子类)成员变量位置找,最后去父类成员变量位置找,反正就是逐级向上找。代码如下:
public class Test {
    public static void main(String[] args) {
        ZiLei z1 = new ZiLei();
        z1.ziShow();
    }
}

class FuLei{
    String name = "FuName";
}

class ZiLei extends FuLei{
   String name = "ZiName";
   public void ziShow(){
       String name = "Name in ZiLei";
       System.out.println(name); //就近原则,打印距离最近的name; 优先级:"Name in ZiLei">"ZiName">"FuName"
       System.out.println(this.name); // 加关键字this表示该对象中的成员变量"ZiName";
       System.out.println(super.name); // 加关键字super表示父类中的成员变量FuName";
   }
}
/*
输出:
Name in ZiLei
ZiName
FuName
*/
  1. 继承成员方法的访问特点
    继承的成员方法访问特点:直接调用满足就近原则–谁距离我近,我就调用谁;super调用,直接访问父类。
public class Test {
    public static void main(String[] args) {
        Student stu1 = new Student();
        stu1.Show();
    }
}

class Person{
   public void eat(){
       System.out.println("父类吃饭");
   }

   public void drink(){
       System.out.println("父类喝水");
   }
}

class Student extends Person{
    // 方法的重写:在继承体系中,子类出现了和父类中一模一样的成员方法,我们就称子类的这个成员方法是重写的方法.
    // 1 @Override重写注解:@Override应放在重写方法上面,用于检查子类重写的语法是否正确.你只要看到@Override,
    // 就表示下面的方法是重写的,可以在父类中找到和它一模一样的方法.建议重写的方法都加上@Override,这样的代码清晰优雅.
    // 2 方法重写注意事项和要求:
    // (1) 重写方法的名称、形参列表必须与父类中的一致;(2)重写的方法尽量和父类中的方法保持一致;
    // (3) 子类重写父类方法时,访问权限子类必须大于等于父类的访问权限(访问权限:空着不写<protected<public);
    // (4) 子类重写父类方法时,返回值类型子类必须小于等于父类;
    // (5) 私有方法不能被重写,子类不能重写父类的静态(static修饰的)方法,如果重写会报错.换句话说,只有被添加到虚方法表中的
    // 方法才能被重写.
    @Override
    public void eat(){
       System.out.println("子类吃饭");
   }

    public void Show(){
       eat(); // 前面有一个隐含的this:this.eat();就近原则:"子类吃饭"
       drink(); // 前面有一个隐含的this:this.drink(); //子类没有drink方法,因此去父类中找.
       super.eat(); //通过super直接调用父类中的eat方法,此时和子类的eat方法没有关系.
   }
}
/*
输出:
子类吃饭
父类喝水
父类吃饭
*/
  1. 继承中的构造方法
    构造方法的访问特点:父类中的构造方法不会被子类继承;子类中所有的构造方法默认先访问父类中的无参构造,再执行自己的构造方法。为什么子类需要先访问父类的构造方法呢?这是因为子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据,因此子类在初始化之前,一定要调用父类构造方法先完成父类数据空间的初始化。那怎么调用父类构造方法呢?子类构造方法的第一行语句默认都是:super()(不写也存在);且必须在第一行。如果想要调用父类的有参构造,必须手动写super()进行调用。代码如下:
public class Test {
    public static void main(String[] args) {
        Student stu1 = new Student();
        Student stu2 = new Student("张三",25);
        System.out.println(stu2.name+","+stu2.age);
    }
}

class Person{
    String name;
    int age;

    public Person() {
        System.out.println("父类的无参构造方法");
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

class Student extends Person{

    public Student(){
        // 子类构造方法中隐藏的super()会去访问父类的无参构造
//        super(); //可以不写
        System.out.println("子类的无参构造方法");
    }

    public Student(String name, int age){
        super(name,age); // 手动访问父类的带参构造方法。
    }
}
/*
输出:
父类的无参构造方法
子类的无参构造方法
张三,25
*/
  1. this、super使用总结
    • this:指的是当前对象的引用,表示当前方法或者变量调用者的地址值,和python中self类似;
    • super:代表当前对象里面的父类对象的引用;super 是用来访问父类实例属性和方法的。
    • super必须放在构造方法中的第一条语句,如果一个构造方法的第一条语句不是this(),也不是super(),系统会默认添加 super()。
    • this 和 super 不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有 super 语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
    • this() 和 super() 都指的是对象,所以,均不可以在 static 环境中使用。包括:static 变量、static 方法、static 语句块。
    • 从本质上讲,this 是一个指向本对象的指针, 然而 super 是一个 Java 关键字。
      this和super
public class Test {
    public static void main(String[] args) {
        Student stu = new Student();
        System.out.println(stu.school);
        System.out.println(stu.name);
        System.out.println(stu.age);
    }
}

class Student{
    String name;
    int age;
    String school;

    public Student() {
        this(null,0,"清华大学"); //this(...)表示调用本类其它构造方法,也就是往this里面传入一下参数.
    }

    public Student(String name, int age, String school) {
        this.name = name;
        this.age = age;
        this.school = school;
    }
}
/*
输出:
清华大学
null
0
*/

三. 多态

  1. 多态就是指同类型对象表现出的不同形态,也就是对象的多种形态。多态的表现形式:父类类型 对象名称 = 子类对象 或者 父类 引用变量 = new 子类();。例如:Person是一个父类、Student是一个子类,学生的形态Student s = new Student();,人的形态Person p = new Student();,具体的同学张三既是学生也是人,即出现两种形态。把子类的实例化对象赋值给父类的变量。
  2. 多态的前提:(1)有继承关系、实现关系;(2)有父类引用指向子类对象(这句话的意思用代码表示就是:Person p = new Student;);(3)有方法重写。使用父类的引用变量作为参数,可以接收所有子类对象,体现多态的扩展性与便利性。
public class Test {
    public static void main(String[] args) {
        Student stu = new Student();
        stu.setName("张三");
        stu.setAge(18);

        Teacher teacher = new Teacher();
        teacher.setName("李四");
        teacher.setAge(35);

        register(stu);
        register(teacher);

    }
    // 需要方法register既能接收老师又能接收学生的信息,因此只能把参数写成这两个类父类定义的变量(父类的引用变量)
    public static void register(Person p){
        p.show();
    }
}

class Student extends Person{
    @Override
    public void show(){
        System.out.println("学生的信息为:"+getName()+","+getAge());
    }
}

class Teacher extends Person{
    @Override
    public void show(){
        System.out.println("老师的信息:"+getName()+","+getAge());
    }
}

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;
    }

    public void show(){
        System.out.println(name+","+age);
    }
}
/*
输出:
学生的信息为:张三,18
老师的信息:李四,35
*/
  1. 多态调用成员的特点
    • 成员变量的调用:编译看左边,运行也看左边;
    • 成员方法的调用:编译看左边,运行看右边。
      下面用代码解释上面两句话的意思
public class Test {
    public static void main(String[] args) {
        // 通过多态方式创建对象:父类 引用变量 = new 子类();
        Animal a = new Dog();

        // 调用成员变量:编译看左边,运行也看左边.编译看左边:javac编译代码的时候,会看左边父类中有没有这个变量,如果有编译
        // 成功,如果没有编译失败.运行也看左边:java运行代码的时候,实际获取的就是左边父类中成员变量的值;
        System.out.println(a.name);

        // 调用成员方法:编译看左边,运行看右边.编译看左边:javac编译代码的时候,会看左边父类中有没有这个成员方法,如果有编译
        // 成功,如果没有编译失败.运行看右边:java运行代码的时候,实际上运行的是子类中的成员方法.
        a.show();

        // 成员变量只能调用父类,成员方法会调用子类,因为方法是重写的。
        // 理解:Animal a = new Dog(); a是Animal类型的引用变量,所以默认都会从Animal这个类中去找成员变量.由于在子类中对
        // 成员方法show()进行了重写,所以会覆盖父类Animal中的show()方法,因此调用的就是子类中show()方法.
    }
}

class Animal{
    String name = "动物";

    public void show(){
        System.out.println("这是Animal类的show方法");
    }
}

class Dog extends Animal{
    String name = "狗";

    @Override
    public void show(){
        System.out.println("这是Dog类的show方法");
    }
}
/*
输出:
动物
这是Dog类的show方法
*/
  1. 多态的优势和弊端
    • (1)在多态形式下,右边对象可以实现解耦合,便于扩展和维护;
    • (2)定义方法的时候,使用父类引用变量作为形参,可以接收所有子类对象,体现多态的扩展性与便利性。
    • 弊端:不能调用子类的特有成员方法,代码解释如下:
public class Test {
    public static void main(String[] args) {
        // 通过多态方式创建对象:父类 引用变量 = new 子类();
        Animal a = new Dog();
        a.eat();
// a.lookHome();会报错,因为父类声明的变量a不能调用子类特有的成员方法.但是为什么a不能调用子类特有的成员方法呢?
// 因为调用成员方法的时候,编译看左边,运行看右边;那么编译的时候就先检查左边父类中有没有这个lookHome方法,如果没有直接报错.
// 那有没有什么解决方案啊?有的有的.解决方案就是强制类型转换,变为子类的引用变量就可以了.
        Dog d = (Dog) a;
        d.lookHome();
    }
}

class Animal{
    String name = "动物";

    public void eat(){
        System.out.println("动物在吃东西");
    }
}

class Dog extends Animal{
    String name = "狗";

    public void lookHome(){
        System.out.println("狗能看家");
    }
    @Override
    public void eat(){
        System.out.println("狗在啃骨头");
    }
}
/*
输出:
狗在啃骨头
狗能看家
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值