目录
面向对象篇
- 面向对象
- 封装
- 继承
- 多态
1.面向对象
1.1.什么是面向对象?
面向对象是解决问题的一种思想,主要依靠对象之间的交互完成一件事情;人们常说的OOP思想就是面向对象思想。 用面向对象的思想来涉及程序,更符合人 们对事物的认知,对于大型程序的设计、扩展以及维护都非常友好。
1.2.面向对象与面向过程
假如你要买手机:
1.面向过程的买手机方式:注重的是买手机的过程,少了一个环节可能都不行
按照该种方式来写代码,将来扩展或者维护起来会比较麻烦。
2.而面向对象的买手机方式:就不关注买手机的过程,具体怎么买,怎么激活,这些都交给秘书去 处理,你只要通过对象之间的交互完成即可--->手机,秘书,使用
注意:面向过程和面相对象并不是一门语言,而是解决问题的方法,没有那个好坏之分,都有其专门的应用场景。
2.封装
2.1.什么是封装?
概念:在实现上采用private修饰成员变量和成员方法,这样在类外就不能直接访问了;达到了
对外隐藏实现的细节,只提供公开的方法,来操作你的这些封装好的成员变量和成员方法
简述:公开的方法 和 私有的属性之间结合
看代码:
class Cat {
//private修饰成员变量
private String name;
private int age;
private String color;
//提供公开的对应的Getter和Setter方法
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 getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
public class TestDemo {
public static void main(String[] args) {
Cat cat = new Cat();
cat.setName("喵喵");//设置值
String name = cat.getName();//获取值
System.out.println(name);
}
}
也可以用构造方法来达到这种效果。
封装的另一好处:降低代码的耦合度
在公司里,每个人负责自己的代码部分,如果这样写代码的饿话,某一天,你的同时心情很差,就把name改成myname了,那你的代码部分相应的地方也需要做修改,耦合度就很高,但是封装之后,改成员名就丝毫不影响你的代码,你只需要通过get,set来设置值,获取值即可。
3.继承
3.1.为什么需要继承?什么是继承?
Java 中使用类对现实世界中实体来进行描述,类经过实例化之后的产物对象,则可以用来表示现实中的实体,但是现实世界错综复杂,事物之间可能会存在一些关联,那在设计程序是就需要考虑。
假如我们要定义一个猫类和一个狗类:
那么它们都有名字,年龄,体重...它们都要吃饭,睡觉,打豆豆...
如果分开写代码的话,那我们写出来的代码,将会有很多重复的地方。
那能否将这些共性抽取呢?面相对象思想中提出了继承的概念,专门用来进行共性抽取,实现代 码复用。
再谈谈什么是继承:
继承 (inheritance) 机制 :是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能 ,这样产生新的类,称 派生类 。继承呈现了面向对象程序设计的层次结构, 体现了 由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用 。
上图所示:继承之后,你会发现,共同的成员变量只需要在父类中写好即可,子类中只需要关心新增的成员变量。
3.2.继承的语法
在 Java 中如果要表示类之间的继承关系,需要借助 extends 关键字,看代码:
注意:
1. 子类会将父类中的成员变量或者成员方法继承到子类中了
2. 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继 承了
3.3.重载
1.什么是重载:重载针对成员方法,方法重载的三个条件:
1.方法名相同; 2.方法的参数列表不相同(参数个数,参数类型,不同类型参数的顺序不同); 3.方法的返回值不做要求
重载的好处,比如:当我们要使用同一个自定义方法时, 我们要实现的逻辑相同,但是参数不同,或者参数类型和顺序不同的时候,使用方法的重载避免了再重新定义一个函数名,这样就很方便,而不会说像这样:两个参数的add方法,我给它定义成add1(), 而三个参数的方法,我给它定义成add2().........
3.4.父类成员的访问
1. 子类和父类不存在同名成员变量
直接访问就好了,继承过来的,可以直接拿来用。
2. 子类和父类成员变量同名
在子类方法中 或者 通过子类对象访问成员时:
- 如果访问的成员变量子类中有,优先访问自己的成员变量。
- 如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
- 如果访问的成员变量与父类中成员变量同名,则优先访问自己的(不管类型是否有差异),即:子类将父类同名成员隐藏了。
让我们来看一下运行结果证明以下以上三点说法:
从而得出结论:成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。
3.5. 父类成员方法的访问
1. 成员方法名字不同
父类有,就调用父类的,父类没有,就调用子类的,都没有,编译错误。都是可以直接访问的
2. 成员方法名字相同(这部分举例只涉及重载,后面讲重写的情况)
【说明】
- 通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。
- 通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法适传递的参数选择合适的方法访问,如果没有则报错;如果父类和子类同名方法的原型一致(重写-后面讲),则只能访问到子类的,父类的无法通过派生类对象直接访问到。
问题:如果子类中存在与父类中相同的成员时,如果不创建父类对象,那如何在子类中访问父 类相同名称的成员呢?这就涉及到super关键字。
3.6.super
由于设计不好,或者因场景需要,子类和父类中可能会存在相同名称的成员,如果要在子类方法中访问父类同名成员时,该如何操作?直接访问是无法做到的,Java提供了super关键字,该关键字主要作用:在子类方法中访问父类的成员
在子类方法中,如果想要明确访问父类中成员时,借助super关键字即可。
【注意事项】
1. 只能在非静态方法中使用
2. 在子类方法中,访问父类的成员变量和方法 。
3.7.super和this的区别
1.相同点:
a.都是Java中的关键字
b.只能在类的非静态方法中使用,用来访问非静态成员方法和字段
c.在构造方法中调用时,必须是 构造方法中的第一条语句,并且不能同时存在
2.不同点
a.this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父 类继承下来部分成员的引用
b.在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方 法和属性
c.this是非静态成员方法的一个隐藏参数,super不是隐藏参数
d.成员方法中直接访问本类成员时,编译之后会将this还原,即本类非静态成员都是通过this 来访问的;在子类中如果通过super访问父类成员,编译之后在字节码super实际是不存在的
e.在构造方法中:this(...)用于调用本类构造方法,super(...)用于调用父类构造方法,两种调用 不能同时在构造方法中存在
f.继承里面构造方法中一定会存在super(...)的调用,用户没有写,编译器也会增加,但是this(...) 用户不写则没有
3.7.final关键字
fifinal 关键可以用来修饰变量、成员方法以及类
1.修饰字段,表示常量(不能被修改)
2.修饰类,表示该类不能被继承(不能派生出子类),也叫密封类;
3.修饰方法,该方法就不能被重写
4.多态
多态:简而言之,就是一种事物,多种形态
4.1.多态实现条件
1.必须在继承体系下
2.子类必须要对父类中的方法进行重写
3.通过父类的引用调用重写的方法
多态的体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法
多态的思想:同一个方法,因为调用这个方法的引用,引用的对象不同,所执行的行为也不一样
4.2.方法的重写
【重写规则】:
1.方法名称相同;2.方法的返回值相同;3.方法的参数列表相同;
4.非静态方法,非private修饰的方法,非final方法;
5.子类的访问修饰限定符的权限要大于等于父类的;
6.JDK以后,被重写的方法返回值类型可以不同,但是必须是具体父子关系的;
【重写和重载的区别】
区别点 | 重载 | 重写(override) |
参数列表 | 必须修改 | 一定不能修改 |
返回类型 | 可以修改 | 一定不能修改 |
访问限定符 | 可以修改 | 一定不能做严格的限制(可以降低限制) |
即:方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
【重写的设计原则】
对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容,并且添加或者改动新的内容。
例如:若干年前的手机,只能打电话,发短信,来电显示只能显示号码,而今天的手机在来电显示的时候,不仅仅可以显示号码,还可以显示头像,地区等。在这个过程当中,我们不应该在原来老的类上进行修改,因为原来的 类,可能还在有用户使用 ,正确做法是: 新建一个新手机的类,对来电显示这个方法重写就好了,这样就达到了我 们当今的需求了 。
4.3.向上转型
什么是向上转型? --> 父类的引用引用了子类的对象
向上转型的三种方式:1.直接赋值 (上图就是直接赋值) 2.方法的参数 3.方法的返回值
2.方法的参数:
3.方法的返回值
4.4.动态绑定
什么是动态绑定:通过父类的引用,调用重写的方法,此时会发生动态绑定
动态绑定的三个条件:
1.在继承体系下;2.子类必须要对父类中方法进行重写;3.通过父类的引用调用重写的方法
【分析】
图中代码 animal.eat();为什么是调用子类的eat方法,实际上编译的时候,这里还是调用animal自己的eat方法,但是在运行的时候,就变了调用子类的eat方法了,所以动态绑定又叫运行时绑定
补充:既然有动态绑定,那就有静态绑定(编译期绑定),例如:重载就是静态绑定。
4.5.向下转型(了解即可)
向下转型:子类的引用引用父类(不安全)
如何解决不安全问题?利用关键字:instanceof
4.6.多态的优缺点
【使用多态的好处】
1. 能够降低代码的 "圈复杂度", 避免使用大量的 if - else
例如我们现在需要打印的不是一个形状了, 而是多个形状. 如果不基于多态, 实现代码如下:
如果使用使用多态 , 则不必写这么多的 if - else 分支语句 , 代码更简单
2.可扩展能力更强
如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低 .
【降低成本】
对于类的调用者来说 (drawShapes 方法 ), 只要创建一个新类的实例就可以了 , 改动成本很低 .
而对于不用多态的情况 , 就要把 drawShapes 中的 if - else 进行一定的修改 , 改动成本更高 .
多态缺陷:代码的运行效率降低 。
4.7.避免在构造方法中调用重写的方法
这里为什么是调用子类的func方法?
- 构造 D 对象的同时, 会调用 B 的构造方法
- B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func
-
此时 D 对象自身还没有构造 , 此时 num 处在未初始化的状态 , 值为 0.
【结论】
结论 : " 用尽量简单的方式使对象进入可工作状态 ", 尽量不要在构造器中调用方法 ( 如果这个方法被子类重写 , 就会触发动态绑定, 但是此时子类对象还没构造完成 ), 可能会出现一些隐藏的但是又极难发现的问题 .
本篇完!