首页 > 其他分享 >类学习笔记——【类的 封装、继承和多态】

类学习笔记——【类的 封装、继承和多态】

时间:2024-02-28 22:37:05浏览次数:23  
标签:封装 name 子类 多态 笔记 Person 父类 public String

@

目录


源码:
Gitee https://gitee.com/drip123456/java-se
GIthub https://github.com/Drip123456/JavaSE

专栏: JavaSE笔记专栏

封装、继承和多态

封装、继承和多态是面向对象编程的三大特性。

封装,把对象的属性和方法结合成一个独立的整体,隐藏实现细节,并提供对外访问的接口。

继承,从已知的一个类中派生出一个新的类,叫子类。子类实现了父类所有非私有化的属性和方法,并根据实际需求扩展出新的行为。

多态,多个不同的对象对同一消息作出响应,同一消息根据不同的对象而采用各种不同的方法。

正是这三大特性,让我们的Java程序更加生动形象。

类的封装

封装的目的是为了保证变量的安全性,使用者不必在意具体实现细节,而只是通过外部接口即可访问类的成员,如果不进行封装,类中的实例变量可以直接查看和修改,可能给整个代码带来不好的影响,因此在编写类时一般将成员变量私有化,外部类需要使用Getter和Setter方法来查看和设置变量。

我们可以将之前的类进行改进:

public class Person {
    private String name;    //现在类的属性只能被自己直接访问
    private int age;
    private String sex;
  
  	public Person(String name, int age, String sex) {   //构造方法也要声明为公共,否则对象都构造不了
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    public String getName() {
        return name;    //想要知道这个对象的名字,必须通过getName()方法来获取,并且得到的只是名字值,外部无法修改
    }

    public String getSex() {
        return sex;
    }

    public int getAge() {
        return age;
    }
}

我们可以来试一下:

public static void main(String[] args) {
    Person person = new Person("小明", 18, "男");
    System.out.println(person.getName());    //只能通过调用getName()方法来获取名字
}

也就是说,外部现在只能通过调用我定义的方法来获取成员属性,而我们可以在这个方法中进行一些额外的操作,比如小明可以修改名字,但是名字中不能包含"小"这个字:

public void setName(String name) {
    if(name.contains("小")) return;
    this.name = name;
}

我们甚至还可以将构造方法改成私有的,需要通过我们的内部的方式来构造对象:

public class Person {
    private String name;
    private int age;
    private String sex;

    private Person(){}   //不允许外部使用new关键字创建对象
    
    public static Person getInstance() {   //而是需要使用我们的独特方法来生成对象并返回
        return new Person();
    }
}

通过这种方式,我们可以实现单例模式:

public class Test {
 private static Test instance;

 private Test(){}

 public static Test getInstance() {
     if(instance == null) 
         instance = new Test();
     return instance;
 }
}

单例模式就是全局只能使用这一个对象,不能创建更多的对象,我们就可以封装成这样,关于单例模式的详细介绍,还请各位小伙伴在《Java设计模式》视频教程中再进行学习。

封装思想其实就是把实现细节给隐藏了,外部只需知道这个方法是什么作用,而无需关心实现,要用什么由类自己来做,不需要外面来操作类内部的东西去完成,封装就是通过访问权限控制来实现的。

类的继承

前面我们介绍了类的封装,我们接着来看一个非常重要特性:继承。

在定义不同类的时候存在一些相同属性,为了方便使用可以将这些共同属性抽象成一个父类,在定义其他子类时可以继承自该父类,减少代码的重复定义,子类可以使用父类中非私有的成员。

比如说我们一开始使用的人类,那么实际上人类根据职业划分,所掌握的技能也会不同,比如画家会画画,歌手会唱,舞者会跳,Rapper会rap,运动员会篮球,我们可以将人类这个大类根据职业进一步地细分出来:

30724024159.png?origin_url=https%3A%2F%2Fs2.loli.net%2F2022%2F09%2F21%2FzlZ9JXAjvxpawPF.png&pos_id=img-SgrGJ9MA-1706281839192)

实际上这些划分出来的类,本质上还是人类,也就是说人类具有的属性,这些划分出来的类同样具有,但是,这些划分出来的类同时也会拥有他们自己独特的技能。在Java中,我们可以创建一个类的子类来实现上面的这种效果:

public class Person {   //先定义一个父类
    String name;
    int age;
    String sex;
}

接着我们可以创建各种各样的子类,想要继承一个类,我们只需要使用extends关键字即可:

public class Worker extends Person{    //工人类
    
}
public class Student extends Person{   //学生类

}

类的继承可以不断向下,但是同时只能继承一个类,同时,标记为final的类不允许被继承:

public final class Person {  //class前面添加final关键字表示这个类已经是最终形态,不能继承
  
}

当一个类继承另一个类时,属性会被继承,可以直接访问父类中定义的属性,除非父类中将属性的访问权限修改为private,那么子类将无法访问(但是依然是继承了这个属性的):

public class Student extends Person{
    public void study(){
        System.out.println("我的名字是 "+name+",我在学习!");   //可以直接访问父类中定义的name属性
    }
}

同样的,在父类中定义的方法同样会被子类继承:

public class Person {
    String name;
    int age;
    String sex;

    public void hello(){
        System.out.println("我叫 "+name+",今年 "+age+" 岁了!");
    }
}

子类直接获得了此方法,当我们创建一个子类对象时就可以直接使用这个方法:

public static void main(String[] args) {
    Student student = new Student();
    student.study();    //子类不仅有自己的独特技能
    student.hello();    //还继承了父类的全部技能
}

是不是感觉非常人性化,子类继承了父类的全部能力,同时还可以扩展自己的独特能力,就像一句话说的: 龙生龙凤生凤,老鼠儿子会打洞。

如果父类存在一个有参构造方法,子类必须在构造方法中调用:

public class Person {
    protected String name;   //因为子类需要用这些属性,所以说我们就将这些变成protected,外部不允许访问
    protected int age;
    protected String sex;
    protected String profession;

  	//构造方法也改成protected,只能子类用
    protected Person(String name, int age, String sex, String profession) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.profession = profession;
    }

    public void hello(){
        System.out.println("["+profession+"] 我叫 "+name+",今年 "+age+" 岁了!");
    }
}

可以看到,此时两个子类都报错了:

在这里插入图片描述

因为子类在构造时,不仅要初始化子类的属性,还需要初始化父类的属性,所以说在默认情况下,子类其实是调用了父类的构造方法的,只是在无参的情况下可以省略,但是现在父类构造方法需要参数,那么我们就需要手动指定了:

既然现在父类需要三个参数才能构造,那么子类需要按照同样的方式调用父类的构造方法:

public class Student extends Person{
    public Student(String name, int age, String sex) {    //因为学生职业已经确定,所以说学生直接填写就可以了
        super(name, age, sex, "学生");   //使用super代表父类,父类的构造方法就是super()
    }

    public void study(){
        System.out.println("我的名字是 "+name+",我在学习!");
    }
}
public class Worker extends Person{
    public Worker(String name, int age, String sex) {
        super(name, age, sex, "工人");    //父类构造调用必须在最前面
        System.out.println("工人构造成功!");    //注意,在调用父类构造方法之前,不允许执行任何代码,只能在之后执行
    }
}

我们在使用子类时,可以将其当做父类来使用:

public static void main(String[] args) {
    Person person = new Student("小明", 18, "男");    //这里使用父类类型的变量,去引用一个子类对象(向上转型)
    person.hello();    //父类对象的引用相当于当做父类来使用,只能访问父类对象的内容
}

虽然我们这里使用的是父类类型引用的对象,但是这并不代表子类就彻底变成父类了,这里仅仅只是当做父类使用而已。

我们也可以使用强制类型转换,将一个被当做父类使用的子类对象,转换回子类:

public static void main(String[] args) {
    Person person = new Student("小明", 18, "男");
    Student student = (Student) person;   //使用强制类型转换(向下转型)
    student.study();
}

但是注意,这种方式只适用于这个对象本身就是对应的子类才可以,如果本身都不是这个子类,或者说就是父类,那么会出现问题:

public static void main(String[] args) {
    Person person = new Worker("小明", 18, "男");   //实际创建的是Work类型的对象
    Student student = (Student) person;
    student.study();
}

在这里插入图片描述

此时直接出现了类型转换异常,因为本身不是这个类型,强转也没用。

那么如果我们想要判断一下某个变量所引用的对象到底是什么类,那么该怎么办呢?

public static void main(String[] args) {
    Person person = new Student("小明", 18, "男");
    if(person instanceof Student) {   //我们可以使用instanceof关键字来对类型进行判断
        System.out.println("对象是 Student 类型的");
    }
    if(person instanceof Person) {
        System.out.println("对象是 Person 类型的");
    }
}

如果变量所引用的对象是对应类型或是对应类型的子类,那么instanceof都会返回true,否则返回false

最后我们需要来特别说明一下,子类是可以定义和父类同名的属性的:

public class Worker extends Person{
    protected String name;   //子类中同样可以定义name属性
    
    public Worker(String name, int age, String sex) {
        super(name, age, sex, "工人");
    }
}

此时父类的name属性和子类的name属性是同时存在的,那么当我们在子类中直接使用时:

public void work(){
    System.out.println("我是 "+name+",我在工作!");   //这里的name,依然是作用域最近的哪一个,也就是在当前子类中定义的name属性,而不是父类的name属性
}

所以说,我们在使用时,实际上这里得到的结果为null

在这里插入图片描述

那么,在子类存在同名变量的情况下,怎么去访问父类的呢?我们同样可以使用super关键字来表示父类:

public void work(){
    System.out.println("我是 "+super.name+",我在工作!");   //这里使用super.name来表示需要的是父类的name变量
}

这样得到的结果就不一样了:

在这里插入图片描述

但是注意,没有super.super这种用法,也就是说如果存在多级继承的话,那么最多只能通过这种方法访问到父类的属性(包括继承下来的属性)

标签:封装,name,子类,多态,笔记,Person,父类,public,String
From: https://www.cnblogs.com/drip3775/p/18042138

相关文章

  • 构建之法阅读笔记3
    第六章敏捷流程敏捷流程是一系列价值观方法论的集合,它要求:尽早并持续地交付有价值的软件以满足顾客需求。敏捷流程欢迎需求的变化,并利用这种变化来提高用户的竞争优势。经常发布可用的软件,发布间隔可以从几周到几个月,能短则短。业务人员和开发人员在项目开发过程中应该每天......
  • 平衡树学习笔记(替罪羊)
    替罪羊应该是所有平衡树中最简单的了(但这东西是真的恶心),它的主要思想是在发现子树不平衡时把子树拍平重建。首先我们考虑什么时候我们认为这个子树是不平衡的。我们可以设置一个常量\(eps\),当有一棵子树的大小超过了它父节点子树大小乘\(eps\),那么我们就可以重建这棵子树了。......
  • 基础线段树笔记
    作为学会的第一个高级数据结构,当然要提早记录啦(虽然好像已经拖了一学期了)线段树的主要用途是针对一些较复杂的区间问题,如:给你一个长度为\(n\)的序列,还有\(m\)次操作。对于每次操作,让你将一个位置\(x\)加\(y\),或查询区间\(\left[L,R\right]\)的和。首先,如果只要求......
  • 构建之法阅读笔记1
    第一章作者谈到了软件开发的过程,过程包括玩具阶段、业余爱好阶段、探索阶段、成熟的产业阶段。我觉得自己处在业余爱好者的阶段(上学期数据库大作业要求写一个图书馆里系统,于是就写了一个图书管理网站,当时做完的时候感觉挺有成就感的,虽然过程十分痛苦),在讨论商业软件和爱好者的程序......
  • CF836F 做题笔记
    传送门非常好题目,使我原地旋转。首先数据这么小显然直接暴力求出每个\(A_i\)的取值范围。由于每个\(A_i\)只能有一个取值,所以源点先给所有\(A_i\)连一个限流为\(1\),费用为\(0\)的边。同时显然还要给每个值域点(不是\(A_i\))向汇点连限为\(inf\),费用为\(0\)的边......
  • 洛谷P2762 太空飞行计划问题 笔记
    传送门神奇的题目。正解就是源点向实验连边,边权为收益。然后仪器向汇点连边,边权为代价。然后答案就是所有实验收益之和-最小割。考虑证明。首先所有实验收益之和显然对应了做所有的实验。然后考虑割掉一条边。如果割掉的是源点->实验,那么就是不做这个实验。如果割了仪器->汇......
  • 置换群 / Polya 原理 / Burnside 引理 学习笔记
    置换群/Polya原理/Burnside引理学习笔记在GJOI上做手链强化,经过长达三小时的OEIS和手推无果后开摆,喜提rnk12,故开始学习置换群相关内容。笔记主要以Polya原理和Burnside引理的应用为主,所以会非常简单,很大一部分的群论概念和证明不会写,因为我不会。基础群论定......
  • Go语言精进之路读书笔记第38条——尽量优化反复出现的if err != nil
    Go在最初设计时就有意识地选择了使用显式错误结果和显式错误检查38.1两种观点显式的错误处理方式让Go程序员首先考虑失败情况,这将引导Go程序员在编写代码时处理故障,而不是在程序部署并运行在生产环境后再处理。而为反复出现的代码片段iferr!=nil{...}所付出的成本已基本被......
  • 课堂笔记1
    1、计算最小公倍数我的第一想法:两个数的最小公倍数只能是两个数其中的一个、两个数的乘积、或者小于两个数的乘积。通过一个for循环(限制条件为:i=0;i<两个数的乘积),然后用if语句判断i是否能整除i且i小于两个数的乘积,是就输出i否则输出两个数的乘积。写出来发现结果是0,看了一下......
  • 《Decoupling Representation and Classifier for Long-Tailed Recognition》阅读笔记
    论文标题《DecouplingRepresentationandClassifierforLong-TailedRecognition》用于长尾识别的解耦表示和分类器作者BingyiKang、SainingXie、MarcusRohrbach、ZhichengYan、AlbertGordo、JiashiFeng和YannisKalantidis来自FacebookAI和新加坡国立大学......