Java 面向对象三大特征(封装,继承,多态)

1. 类和对象的理解

类是对事物的一种描述,对象则为具体存在的事物

面向对象三大特征(封装,继承,多态

2. 类的定义

类的组成是由属性和行为两部分组成

  • 属性:在类中通过成员变量来体现(类中方法外的变量)

  • 行为:在类中通过成员方法来体现(和前面的方法相比去掉static关键字即可)

3. 对象的使用

3.1 创建对象的格式

    • 类名 对象名 = new 类名();

3.2 调用成员的格式

    • 对象名.成员变量

    • 对象名.成员方法()

4. 对象内存图

4.1 单个对象内存图

成员变量使用过程

 Java 面向对象三大特征(封装,继承,多态) 随笔

成员方法调用过程

 Java 面向对象三大特征(封装,继承,多态) 随笔

4.2 多个对象内存图

成员变量使用过程

 Java 面向对象三大特征(封装,继承,多态) 随笔

成员方法调用过程

 Java 面向对象三大特征(封装,继承,多态) 随笔

5. 成员变量和局部变量的区别

  • 类中位置不同:成员变量(类中方法外)局部变量(方法内部或方法声明上)

  • 内存中位置不同:成员变量(堆内存)局部变量(栈内存)

  • 生命周期不同:成员变量(随着对象的存在而存在,随着对象的消失而消失)局部变量(随着方法的调用而存在,随着方法的调用完毕而消失)

  • 初始化值不同:成员变量(有默认初始化值)局部变量(没有默认初始化值,必须先定义,赋值才能使用)

6. 封装

6.1 封装概述

对象代表什么,就得封装对应的数据,并提供数据对应的行为。

6.2 封装代码实现

将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问成员变量private,提供对应的getXxx() / setXxx()方法。

6.3 private关键字

private是一个修饰符,可以用来修饰成员(成员变量,成员方法);

被private修饰的成员,只能在本类进行访问;

针对private修饰的成员变量,如果需要被其他类使用,提供相应的getXxx() / setXxx()方法操作;

  • 提供“get变量名()”方法,用于获取成员变量的值,方法用public修饰

  • 提供“set变量名(参数)”方法,用于设置成员变量的值,方法用public修饰

/*    学生类 */class Student {
    //成员变量    private String name;
    private int age;

    //get/set方法    public void setName(String n) {
        name = n;
    }

    public String getName() {
        return name;
    }

    public void setAge(int a) {
        age = a;
    }

    public int getAge() {
        return age;
    }

    public void show() {
        System.out.println(name + "," + age);
    }}/*    学生测试类 */public class StudentDemo {
    public static void main(String[] args) {
        //创建对象        Student s = new Student();

        //使用set方法给成员变量赋值        s.setName("林青霞");
        s.setAge(30);

        s.show();

        //使用get方法获取成员变量的值        System.out.println(s.getName() + "---" + s.getAge());
        System.out.println(s.getName() + "," + s.getAge());
    }}

6.4 this关键字

this代表所在类的当前对象的引用(地址值),即代表当前对象,其主要作用是(区分局部变量和成员变量的重名问题

    • 方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量

    • 方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量

public class Student {
    private String name;
    private int age;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }}

6.5 构造方法

  • 构造方法的创建

如果没有定义构造方法,系统将给出一个默认的无参数构造方法;

如果定义了构造方法,系统将不再提供默认的构造方法

  • 构造方法的重载

如果自定义了带参构造方法,还要使用无参数构造方法,就必须再写一个无参数构造方法

  • 推荐的使用方式

无论是否使用,无参数构造方法和有参构造方法都写

  • 重要功能!!!

可以使用带参构造,为成员变量进行初始化

/*    学生类 */class Student {
    private String name;
    private int age;

    public Student() {}

    public Student(String name) {
        this.name = name;
    }

    public Student(int age) {
        this.age = age;
    }

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

    public void show() {
        System.out.println(name + "," + age);
    }}/*    测试类 */public class StudentDemo {
    public static void main(String[] args) {
        //创建对象        Student s1 = new Student();
        s1.show();

        //public Student(String name)        Student s2 = new Student("林青霞");
        s2.show();

        //public Student(int age)        Student s3 = new Student(30);
        s3.show();

        //public Student(String name,int age)        Student s4 = new Student("林青霞",30);
        s4.show();
    }}

7. 继承

7.1 继承的简介

  • 子类继承父类的属性行为,使得子类对象可以直接具有与父类相同的属性、相同的行为。

  • 子类可以直接访问父类中的非私有的属性和行为。

7.2 继承的好处

  • 提高代码的复用性(减少代码冗余,相同代码重复利用)。

  • 使类与类之间产生了关系。

7.3 继承的格式

class 父类 {
	...
}

class 子类 extends 父类 {
	...
}

注意点:Java是单继承的,一个类只能继承一个直接父类,跟现实世界很像,但是Java中的子类是更加强大的。

7.4 子类不能继承的内容

  • 子类不能继承父类的构造方法。

  • 值得注意的是子类可以继承父类的私有成员(成员变量,方法),只是子类无法直接访问而已,子类可以通过getter/setter方法访问父类的private成员变量

7.5 继承后的成员变量的特点

  • 成员变量不重名

如果子类与父类中不出现重名的成员变量,这时的访问是没有影响的

  • 成员变量重名

子父类中出现了同名的成员变量时,子类会优先访问自己对象中的成员变量。如果此时想访问父类成员变量如何解决呢?我们可以使用super关键字

7.6 继承后的成员方法的特点

  • 成员方法不重名

如果子类父类中出现不重名的成员方法,这时的调用是没有影响的

  • 成员方法重名

如果子类父类中出现重名的成员方法,则创建子类对象调用该方法的时候,子类对象会优先调用自己的方法(方法重写)。

7.7 继承后构造方法的特点

1.引入

  • 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。

  • 构造方法的作用是初始化对象成员变量数据的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。(先有爸爸,才能有儿子

public class Student extends Person{

    public Student() {
        //子类构造方法中隐藏的super()去访问父类的无参构造        super(); //必须是第一行        System.out.println("子类的无参构造");
    }

    public Student(String name, int age) {
        super(name, age);
    }}

2. super( )详解

  • 子类构造方法的第一行都隐含了一个super()去调用父类无参数构造方法,super()可以省略不写

  • 子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。

  • super( ) 和 this( ) 都必须是在构造方法的第一行,所以不能同时出现

  • super( )是根据参数去确定调用父类哪个构造方法的。

super( )图解:

 Java 面向对象三大特征(封装,继承,多态) 随笔

3. this( ) 详解

默认是去找本类中的其他构造方法,根据参数来确定具体调用哪一个构造方法。

public Student() {
        // 可以调用本类中的其他构造方法:Student(String name, int age, char sex)        this("徐干",21,'男');
    }

    public Student(String name, int age, char sex) {
        this.name = name ;
        this.age = age   ;
        this.sex = sex   ;
    }

7.8 方法重写

1. 简介

子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现

2. 使用场景

发生在子父类之间的关系。子类继承了父类的方法,但是子类觉得父类的这方法不足以满足自己的需求,子类重新写了一个与父类同名的方法,以便覆盖父类的该方法。

3. @Override重写注解

  • 这个注解标记的方法,就说明这个方法必须是重写父类的方法,否则编译阶段报错。

  • 建议重写都加上这个注解,一方面可以提高代码的可读性,一方面可以防止重写出错。

public class Cat extends Animal {
    @Override
    public void cry(){
        System.out.println("我们一起学猫叫,喵喵喵!喵的非常好听!");
    }}

4. 注意点

  • 方法重写是发生在子、父类之间的关系。

  • 子类方法覆盖父类方法,必须要保证权限大于等于父类权限。

  • 子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样

8. 多态

8.1 多态定义与形式

多态: 是指同一行为,具有多个不同表现形式。

多态是继封装、继承之后,面向对象的第三大特性。

多态是出现在继承或者实现关系中的

多态体现的格式:

 父类类型 变量名 = new 子类/实现类构造器;
 变量名.方法名();

多态的前提:有继承关系,子类对象是可以赋值给父类类型的变量。

8.2 使用场景

如果没有多态,在下图中register方法只能传递学生对象,其他的Teacher和administrator对象是无法传递给register方法方法的,在这种情况下,只能定义三个不同的register方法分别接收学生,老师和管理员。

 Java 面向对象三大特征(封装,继承,多态) 随笔

有了多态之后,方法的形参就可以定义为共同的父类Person。

 Java 面向对象三大特征(封装,继承,多态) 随笔

要注意的是:

  • 当一个方法的形参是一个类,我们可以传递这个类所有的子类对象。

  • 当一个方法的形参是一个接口,我们可以传递这个接口所有的实现类对象。

  • 而且多态还可以根据传递的不同对象来调用不同类中的方法。

代码实例:

public class Test {
    public static void main(String[] args) {
        //创建三个对象,并调用register方法
        Student s = new Student();
        s.setName("张三");
        s.setAge(18);

        Teacher t = new Teacher();
        t.setName("王建国");
        t.setAge(30);

        Admin admin = new Admin();
        admin.setName("管理员");
        admin.setAge(35);

        register(s);
        register(t);
        register(admin);
    }
    public static void register(Person p){
        p.show();
    }}


public class Person {

    private String name;
    private int age;

    public Person() {
    }

    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 void show(){
        System.out.println("show");
    }}


public class Student extends Person{
    @Override
    public void show(){
        System.out.println("showStudent");
    }}


public class Teacher extends Person{
    @Override
    public void show() {
        System.out.println("showTeacher");
    }}


public class Admin extends Person{
    @Override
    public void show(){
        System.out.println("showAdmin");
    }}

8.3 多态的运行特点

调用成员变量时:编译看左边,运行看左边

调用成员方法时:编译看左边,运行看右边

Fu f = new Zi();//编译看左边的父类中有没有name这个属性,没有就报错//在实际运行的时候,把父类name属性的值打印出来System.out.println(f.name);//编译看左边的父类中有没有show这个方法,没有就报错//在实际运行的时候,运行的是子类中的show方法f.show();

这也就导致了多态的弊端:

多态编译阶段是看左边父类类型的,如果子类有些独有的功能,此时多态的写法就无法访问子类独有功能了

解决此弊端方法:类型转换,想要调用子类特有的方法,必须做向下转型

8.4 引用类型转换

回顾基本数据类型转换

  • 自动转换:范围小的赋值给范围大的,自动完成:double d = 5;

  • 强制转换:范围大的赋值给范围小的,强制转换:int i = (int)3.14

多态的转型分为向上转型(自动转换)与向下转型(强制转换)两种。

1.向上转型(自动转换)

  • 向上型:多态本身是子类类型向父类类型向上转换(自动转换)的过程,这个过程是默认的。 当父类引用指向一个子类对象时,便是向上转型。 使用格式:

 父类类型  变量名 = new 子类类型();
 如:Animal a = new Cat();

原因是:父类类型相对与子类来说是大范围的类型,Animal是动物类,是父类类型。Cat是猫类,是子类类型。Animal类型的范围当然很大,包含一切动物。所以子类范围小可以直接自动转型给父类类型的变量。

2.向下转型(强制转换)

  • 向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。 一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。

使用格式:

 子类类型 变量名 = (子类类型) 父类变量名;
 如:Aniaml a = new Cat();
    Cat c =(Cat) a;

8.5 转型的异常

转型的过程中,一不小心就会遇到这样的问题,请看如下代码:

 public class Test {
     public static void main(String[] args) {
         // 向上转型  
         Animal a = new Cat();  
         a.eat();               // 调用的是 Cat 的 eat 
         // 向下转型  
         Dog d = (Dog)a;       
         d.watchHouse();        // 调用的是 Dog 的 watchHouse 【运行报错】     }  
 }

这段代码可以通过编译,但是运行时,却报出了 ClassCastException ,类型转换异常!这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。

解决方法:利用instanceof关键字

8.6 instanceof关键字

为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验,格式如下:

 变量名 instanceof 数据类型 
 如果变量属于该数据类型或者其子类类型,返回true。
 如果变量不属于该数据类型或者其子类类型,返回false。

所以,转换前,我们最好先做一个判断,代码如下:

 public class Test {
     public static void main(String[] args) {
         // 向上转型  
         Animal a = new Cat();  
         a.eat();               // 调用的是 Cat 的 eat 
         // 向下转型  
         if (a instanceof Cat){
             Cat c = (Cat)a;       
             c.catchMouse();        // 调用的是 Cat 的 catchMouse         } else if (a instanceof Dog){
             Dog d = (Dog)a;       
             d.watchHouse();       // 调用的是 Dog 的 watchHouse         }
     }  
 }

instanceof 新特性

JDK14的时候提出了新特性,把 判断 和 强转 合并成了一行

 //新特性 //先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d //如果不是,则不强转,结果直接是false if(a instanceof Dog d){
     d.lookHome();
 }else if(a instanceof Cat c){
     c.catchMouse();
 }else{
     System.out.println("没有这个类型,无法转换");
 }

转载自:https://zhuanlan.zhihu.com/p/634423088

本文标题:《Java 面向对象三大特征(封装,继承,多态)》作者:Scar
原文链接:https://cxk.me/post/19.html
特别注明外均为原创,转载请注明。

分享到微信

扫描二维码

可在微信查看或分享至朋友圈。

上一篇: Users Rule Users

相关文章

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。