【Java】一篇文章弄懂封装、继承、多态、抽象、接口、内部类

本文最后更新于:2022年1月8日晚上8点49分

前言

学习了一段时间的安全,开始意识到自己的Java基础薄弱,因此开始恶补Java,为以后学习shiro、springboot等等框架打好基础。

这篇文章就记录一下学习Java封装、继承、多态、抽象、接口、内部类、异常,如有纰漏,那就是因为我菜。

封装

封装介绍

封装的目的:防止该类的代码和数据被外部类定义的代码随机访问。

封装的优点

  1. 良好的封装能够减少耦合。

  2. 类内部的结构可以自由修改。

  3. 可以对成员变量进行更精确的控制。

  4. 隐藏信息,实现细节。

一句话:属性私有(private),想要设置一个类中的属性,需要通过内部方法(通常为getID/setID)来修改。

测试

接下来做实验阐述封装的作用:

在下面这个测试中一共有两个类,一个是Start.java,一个是Student.java

其中Start.java中放置启动代码,Student.java中放置一个学生类,并且有一个属性name

public类型的属性输出

具体代码如下:

image-20220101180036353

可以看到,这个时候Student中的name属性为public,此时可以在Start中新建一个对象(s1),并且对s1对象中的name属性进行赋值。

输出结果如下:

image-20220101180315108

private类型的属性无法直接输出

接下来将name属性修改为私有的(private)

image-20220101180409662

这个时候IDEA就报错了,因为此时Start新建的s1对象,不具备对name属性的修改权限,因此报错。

通过调用类中的方法修改属性

所以在平常开发中,通常会在Student中加入一个getName以及setName等这类方法,通过调用类中的方法来调用或者设置类中的属性。

在IDEA中,可以使用右键进行生成getName以及setName方法(Mac可以使用command+N的组合键)

image-20220101181127821

image-20220101181138209

然后选择相应的属性即可快速生成

image-20220101182414795

image-20220101181239504

这个时候我们就可以通过Student中的方法调用类中的属性以及修改类中的属性

image-20220101181447331

可以看到程序正常执行

image-20220101181512835

重载方法配置类中的属性

当然一般情况还会在setNamegetName中中添加判断的语句,可以用来检测传入的属性值是否合法等等。

除此之外,结合Java可以对方法进行重载,在setNamegetName处我们还可以加入一些重载方法。

例如有些人在输入姓名时,会将姓和名分开输入,这个时候只需要我们对setName方法进行重载,然后进行拼接,就可以同时满足用户输入一个字符串或者两个字符串的需求。

如下图:下方对setName的方法进行重载,并且将Lxxx分开作为L以及xxx传入

image-20220101182220541

此时运行程序,程序仍然可以通过getName方法将拼接后的属性值取出。

image-20220101182349211

继承

extends

继承介绍

继承的目的:将子类继承父类,缩短代码,提高代码的可维护性。

继承的注意点

  1. Java是不支持多继承,也就是说一个子类只能拥有一个父类,但是一个父类可以拥有很多子类
  2. Java支持多重继承,也就是一个子类除了有父类,还可以拥有子类,也就是会存在“子子类”以及“祖先类”
  3. 子类会继承父类中所有public的方法以及属性,无法继承父类中的私有(private)的方法以及属性(下方做测试)
  4. Object是所有类的祖先类(快捷键:Ctrl+H)
  5. 子类可以使用super访问父类中protected以及public属性以及方法(下方做测试)
  6. 在新建子类之前,子类的构造器会优先调用父类的构造器(下方做测试)
  7. 如果父类想要重写有参构造器,最好加上原始的无参构造(下方做测试)
  8. 子类可以重写父类的方法(下方做测试)
  9. 子类重写父类的方法,权限只能扩大不能缩小(下方做测试)

测试

子类会继承父类中所有public的方法以及属性

Person为Student的父类,在Student中使用关键字extends继承父类Person

测试代码如下:

image-20220101192527662

image-20220101192536472

可以看到,新建了一个由子类创建的对象s1,而此时s1可以直接调用父类(Person)中的run方法,并且可以输出父类中的money属性。

子类无法继承父类中private方法以及属性

将父类中的方法以及属性设置为private,这个时候IDEA将会报错

image-20220101193256260

因此得出结论:子类无法继承父类中private方法以及属性

子类可以使用super访问父类中protected以及public属性以及方法

如下图所示:子类可以通过super关键字访问父类中protected权限的money属性

image-20220101193842847

image-20220101193849904

但是子类无法访问父类的private权限的属性以及方法(这个还是比较好理解的)

在新建子类之前,子类的构造器会优先调用父类的构造器

首先需要明确的是:一个类即使什么都不写,Java默认会有一个同名的方法,也就是这个类的构造器,当然也可以将其显性的写出来

如下图,我们在Person类构造器中输出一句话,同样在Student类的构造器中输出一句话,代码如下

image-20220101194444675

接下来观察一下结果:

image-20220101194648547

可以看到,程序优先输出父类,然后再输出子类。

也就是说,在子类(Student)中的输出代码前,还有一句隐藏的代码,就是super();,如下图所示:

image-20220101194819110

并且这一句隐藏代码必须放在构造器的第一行!如果不放在第一行,那么程序就会报错!

image-20220101194916656

此时IDEA告诉我们,super();这条语句必须是构造函数主体中的第一条语句!

如果父类想要重写有参构造器,最好加上原始的无参构造

这一点稍微有些绕,写几个测试样例演示一下:

假设父类重写了一个有参构造器,此时父类的无参构造器就消失了(如果不手动补充),那么这个时候子类的无参构造器就有可能无法顺利构造。

下方两个测试样例就是子类无法顺利构造,因为父类重写有参构造器时,没有手动补充无参构造器。

image-20220101195342502

image-20220101195846206

解决办法有两种:

第一种如下:

在子类中手动调用父类中的有参构造器,这样程序就不会调用无参构造器,此时程序可以正常运行

image-20220101195944132

输出结果如下:

image-20220101200238007

第二种如下:

手动补充父类中的无参构造器

image-20220101200139476

不过这个时候程序默认调用父类中的Person()无参构造器,所以就不会输出Person(name)中需要打印的字符串

image-20220101200213030

子类可以重写父类的方法

首先下图给出一个测试样例,注意这个时候子类(Student)中有一个say方法,父类(Person)中有一个test方法,此时在main主函数中,定义了一个Person类型的引用,指向Student类型的对象,然后再调用这个对象的test方法。

image-20220101201608221

得出结果

image-20220101201742269

此时是父类Person被调用,这个还是比较好理解的。

不过接下来!将子类的say方法改为和父类同名的test方法,这个时候代码如下:

image-20220101201848760

运行结果如下:

image-20220101201904307

可以看到这个时候,main函数调用的test方法不是父类中的test方法,而是子类中的test方法,因为这个时候父类中的test方法被子类中的test方法重写了。

子类重写父类的方法,权限只能扩大不能缩小

权限大小(从大到小):public > protected > default > private

还是上方的例子,此时父类(Person)的权限为public,子类(Student)我们从public修改为protected,如下图:

这个地方被IDEA遮挡了,父类的权限为public,子类的权限为protected

可以看到,此时子类重写的方法权限比父类小,这是不允许的!IDEA直接报错。

当然,重写父类的方法可以变大,也就是说,父类中为protected,子类可以为public,这个是没有问题的

image-20220101202804714

这个时候是可以的,因为父类为protected可继承,并且子类重写的方法为public,比protect权限大。

但是,当父类的方法为private的时候,子类是无法重写的,即使设置为public也是不行的!因为当父类的方法为private的时候,就意味着这个方法是子类不可以继承的,此时子类也就无法重写父类的方法。

总结一句话:子类重写父类方法,权限必须比父类的方法大,父类为private的方法无法重写

多态

多态介绍

多态的定义:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。

多态存在的必要条件

  1. 继承
  2. 重写
  3. 父类引用指向子类对象。

多态的注意点

  1. 多态是方法的多态,属性是没有多态的
  2. 多态必须具备父子之间的关系
  3. 父类引用需要指向子类的对象(Father s = new Son();
  4. static静态方法无法重写
  5. 私有(private)无法重写
  6. 常量(final)无法重写
  7. 某个对象能执行哪些方法,取决于对象左边的类型
  8. 如果不做类型转换,则父类无法调用子类中独有的方法(下方做测试)
  9. 子类重写父类的方法,则调用子类的方法(下方做测试)

测试

如果不做类型转换,则父类无法调用子类中独有的方法

代码如下图所示:父类和子类都有一个say方法,但是子类(Student)独有一个test方法,我们在main主函数中创建一个父类的对象指向子类Student,可以看到IDEA直接报错,因为这个时候s1对象的类型为父类(Person),自然也无法调用子类(Student)中独有的test方法

image-20220101212732060

不过如果我们做类型转换,将父类(Person)转换为子类,如下图所示,那么程序就可以正常编译运行。

image-20220101212613423

子类重写父类的方法,则调用子类的方法

如下图所示,子类和父类都有一样的say方法,此时在主函数中新建一个父类(Person)的对象,并将对象指向子类(Student),此时让对象(s1)执行say方法,我们看一下运行结果

image-20220101213121403

image-20220101213241720

可以看到执行的结果为子类的方法被调用,而父类的方法没有被调用

这就印证了我们的结论:子类重写父类的方法,则调用子类的方法

抽象

抽象介绍

抽象类的定义:抽象的抽象

抽象的注意点:

  1. 不能new一个抽象类,只能利用子类去实现抽象类(下方测试)
  2. 抽象类中可以写普通方法(下方测试)
  3. 抽象方法必须在抽象类中(下方测试)
  4. 抽象类中存在构造器(下方测试)

测试

不能new一个抽象类,只能利用子类去实现抽象类

如下图所示,抽象类无法使用new进行实例化

image-20220108142700140

但是可以使用子类重写抽象类的方法

image-20220108143121675

抽象类中可以写普通方法

如下图所示,可以在抽象类中写一个普通方法(hello)

image-20220108142949149

抽象方法必须在抽象类中

如下图所示,抽象方法必须存在抽象类中,否则会报错。

image-20220108143223836

抽象类中存在构造器

如图所示,抽象类中同样存在构造器,并且在实例化构造器的一个子类的时候,会调用抽象类的构造器。

image-20220108143638939

接口

接口介绍

接口的定义:接口的成员没有执行体,是由全局常量和公共的抽象方法所组成。

接口的注意点:

  1. 实现接口的时候,方法需要被重写(下方测试)
  2. 接口没有构造方法,不能被实例化(下方测试)
  3. 接口中定义的变量必须初始化,默认为public static final
  4. 接口中的方法默认为public abstruct
  5. 接口可以多继承(下方测试)

实现接口的时候,方法需要被重写

image-20220108150040380

接口没有构造方法,不能被实例化

如下图,接口没有构造方法,同样它也不能被实例化。

image-20220108150641502

接口可以多继承

如下图,PersonImpl类多继承了Person以及Animals接口

image-20220108151408277

内部类

内部类介绍

内部类的定义

所谓内部类就是在一个类内部进行其他类结构的嵌套操作。

内部类的注意点:

  1. 创建非静态内部类:Outer.Inner in = new Outer().new Inner();
  2. 创建静态内部类:Outer.Inner in = new Outer.Inner();
  3. 在外部类的内部创建内部类:Inner in = new Inner();
  4. 非静态内部类可以调用外部类的非静态属性以及方法(下方测试)
  5. 静态内部类不可以调用外部类的非static修饰的属性以及方法(下方测试)
  6. 方法内部类不可以使用访问权限符进行限制(下方测试)
  7. 匿名内部类(下方测试)

非静态内部类可以调用外部类的非静态属性以及方法

非静态内部类可以调用外部类的非静态属性以及方法,包括private的属性以及方法

image-20220108180023770

静态内部类不可以调用外部类的非static修饰的属性以及方法

如下图所示,静态内部类不可以调用外部类的非static修饰的属性以及方法,其实中age是用static修饰的,name属性以及say方法不由static修饰,因此这两个报错,而age是正常的。

image-20220108180106346

方法内部类不可以使用访问权限符进行限制

如下图所示:此时内部类Inner没有被访问权限符修饰,程序正常

image-20220108181315322

当我们使用public去修饰的时候,发现IDEA帮助我们指出了错误,因此方法内部类不可以使用访问权限符进行限制。

image-20220108181405318

匿名内部类

如下图所示,此时可以直接调用A类中的say方法,而不需要变量名

image-20220108181806678

方法重写以及重载的区别

附:

粗略地学习了一下Java基础,文章中肯定有纰漏,等到学有所成之后再修正。

2022.1.8 20:33