面向过程与面向对象
前言
本博主将用CSDN记录软件开发求学之路上亲身所得与所学的心得与知识,有兴趣的小伙伴可以关注博主!
也许一个人独行,可以走的很快,但是一群人结伴而行,才能走的更远!让我们在成长的道路上互相学习,欢迎关注!
上一篇:类和对象解析
一、面向过程(POP) 与 面向对象(OOP)
1. 两者的联系与区别
1.1 联系
二者都是一种思想,面向对象是相对于面向过程而言的,程序员从面向过程的执行者转化成了面向对象的指挥者。
1.2 区别
⭕ 面向过程,强调的是功能行为,以函数为最小单位,考虑怎么做。
⭕ 面向对象,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做,更加强调运用人类在日常的思维逻辑中采用的思想方法与原则,如抽象、分类、继承、聚合、多态等。
⭕ 面向对象分析方法分析问题的思路和步骤:
- 根据问题需要,选择问题所针对的现实世界中的实体。
- 从实体中寻找解决问题相关的属性和功能,这些属性和功能就形成了概念世界中的类。
- 把抽象的实体用计算机语言进行描述,形成计算机世界中类的定义,即借助某种程序语言,把类构造成计算机能够识别和处理的数据结构。
- 将类实例化成计算机世界中的对象。对象是计算机世界中解决问题的最终工具
1.3 图解举例
2. 面向对象的三大特征
(1)封装 (Encapsulation)
(2)继承 (Inheritance)
(3)多态 (Polymorphism)
二、面向对象的特征一:封装性
1. 概述
1.1 引入:为什么需要封装?
⭕原因一:
我要用洗衣机,只需要按一下开关和洗涤模式就可以了。有必要了解洗衣机内部的结构吗?有必要碰电动机吗?
答案是:NO!没必要!
⭕ 原因二:
当我们创建一个类的对象以后,我们可以通过”
对象.属性
“的方式,对对象的属性进行赋值。这里,赋值操作要受到属性的数据类型和存储范围的制约。除此之外,没有其它制约条件。
但是,在实际问题中,我们往往需要给属性赋值加入额外的限制条件。这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加,(比如:setLegs()
)。
同时,我们需要避免用户再使用”对象.属性
“的方式对属性进行赋值。则需要将属性声明为私有的(private
)。此时,针对于属性就体现了封装性。
1.2 特点
(1)高内聚 :类的内部数据操作细节自己完成,不允许外部干涉。
(2)低耦合 :仅对外暴露少量的方法用于使用。
(3)设计思想:隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露的暴露出来。
2. 使用
⭕ 封装性的体现,需要权限修饰符来配合。
四种修饰符的修饰范围:
修饰符 | 类内部 | 同一个包 | 不同包的子类 | 同一个工程 |
---|---|---|---|---|
private | Yes | |||
(缺省) | Yes | Yes | ||
protected | Yes | Yes | Yes | |
public | Yes | Yes | Yes | Yes |
⭕Java规定的4种权限(从小到大排列):
private
、缺省(什么都不写)
、protected
、public
。置于类的成员定义前,用来限定对象对该类成员的访问权限。
⭕Java中通过将数据声明为私有的(
private
),再提供公共的(public
)方法:getXxx()
和setXxx()
实现对该属性的操作,以实现下述目的:
- 隐藏一个类中不需要对外提供的实现细节。
- 使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑,限制对属性的不合理操作。
- 便于修改,增强代码的可维护性。
总结封装性:Java提供了4种权限修饰符来修饰类及类的内部结构,体现类及类的内部结构在被调用时的可见性的大小。
⭕ 封装性的体现:
- 如下代码演示
- 不对外暴露的私有的方法
- 单例模式 …
3. 注意
(1)4种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类。
(2)对于
class
的权限修饰只可以用public
和default(缺省)
。
- public类可以在任意地方被访问。
- default类只可以被同一个包内部的类访问。
4. 应用举例
public class AnimalTest {
public static void main(String[] args) {
Animal a = new Animal();
a.name = "大黄";
// a.age = 1;//The field Animal.age is not visible
// a.legs = 4;//The field Animal.legs is not visible
a.show();
// a.legs = -4;
// a.setLegs(6);
a.setLegs(-6);
// a.legs = -4;//The field Animal.legs is not visible
a.show();
System.out.println(a.name);
}
}
class Animal{
String name;
private int age;
private int legs;//腿的个数
//对属性的设置
public void setLegs(int l){
if(l >= 0 && l % 2 == 0){
legs = l;
}else{
legs = 0;
// 抛出一个异常(暂时没有讲)
}
}
//对属性的获取
public int getLegs(){
return legs;
}
public void eat(){
System.out.println("动物进食");
}
public void show(){
System.out.println("name = " + name + ",age = " + age + ",legs = " + legs);
}
//提供关于属性age的get和set方法
public int getAge(){
return age;
}
public void setAge(int a){
age = a;
}
}
三、面向对象特征之二:继承性
1. 概述
1.1 引入:为什么要有继承?
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承(
extends
)那个类即可。此处的多个类称为子类(派生类),单独的这个类称为父类(基类或超类)。可以理解为:“子类 is a 父类”
2.使用
2.1 类继承语法规则
class A extends B{ }
A: 子类、派生类、subclass
B: 父类、超类、基类、superclass
2.2 作用
(1)继承的出现减少了代码冗余,提高了代码的复用性。
(2)继承的出现,更有利于功能的扩展。
(3)继承的出现让类与类之间产生了关系,提供了多态的前提。
3. 注意
(1)一个类可以被多个子类继承。
(2)Java只支持单继承和多层继承,不允许多重继承
- 一个子类只能有一个父类。
- 一个父类可以派生出多个子类。
代码演示:
class SubDemo extends Demo{ } //ok
class SubDemo extends Demo1,Demo2...//error
图解:
(3)子类不能直接访问父类中私有的(
private
)的成员变量和方法,当子类继承父类以后,仍然认为获取了父类中私有的结构。只是因为封装性的影响,使得子类不能直接调用父类的结构而已,但子类仍可通过setter( )
和getter( )
的方法访问父类中私有的(private
)的成员变量和方法。
(4)子类和父类的关系,不同于子集和集合的关系。在Java中,继承的关键字用的是“
extends
”,即子类不是父类的子集,而是对父类的“扩展”。
(5)一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法,可以使用父类中定义的方法和属性,也可以创建新的数据和方法。
(6)不要仅为了获取其他类中某个功能而去继承。
(7)子父类是相对的概念。
(8)子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类。
(9)子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法。
(10)如果我们没有显式的声明一个类的父类的话,则此类继承于
java.lang.Object
类。
(11)所有的java类(除
java.lang.Object
类之外)都直接或间接的继承于java.lang.Object
类,意味着,所有的java类具有java.lang.Object
类声明的功能。
(12)子类继承父类
⭕ 若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中。
⭕ 对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量。
4. 应用举例
//为描述和处理个人信息,定义类Person:
class Person {
public String name;
public int age;
public Date birthDate;
public String getInfo() {
//...
}
}
//为描述和处理学生信息,定义类Student
class Student {
public String name;
public int age;
public Date birthDate;
public String school;
public String getInfo() {
// ...
}
}
//通过继承,简化Student类的定义:
class Person {
public String name;
public int age;
public Date birthDate;
public String getInfo() {
// ...
}
}
class Student extends Person {
public String school;
}
/*
Student类继承了父类Person的所有属性和方法,
并增加了一个属性school。Person中的属性和方法,Student都可以使用。
*/
四、面向对象特征之三:多态性
1. 概述
1.1 何为多态性?
对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)
2. 使用
2.1 作用
提高了代码的通用性,常称作接口重用。
2.2 虚拟方法调用
⭕ Java引用变量有两个类型:编译时类型和运行时类型。
- 编译时类型由声明该变量时使用的类型决定。
- 运行时类型由实际赋给该变量的对象决定。
总结:编译时,看左边;运行时,看右边。
- “看左边”:看的是父类的引用(父类中不具备子类特有的方法)
- “看右边”:看的是子类的对象(实际运行的是子类重写父类的方法).
⭕ 若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)。
⭕ 有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。
2.3 使用前提
⭕ 需要存在继承或者实现关系 。
⭕ 有方法的重写。
3. 注意
⭕ 对象的多态性,只适用于成员方法,不适用于成员变量,因为对于成员变量,编译和运行都是看左边。
- 成员方法:
- 编译时:要查看引用变量所声明的类中是否有所调用的方法。
- 运行时:调用实际
new
的对象所属的类中的重写方法。- 成员变量:
- 不具备多态性,只看引用变量所声明的类。
代码演示:
public class Test {
public static void main(String[] args){
Sub s = new Sub();
System.out.println(s.count);//20
s.display();//20
Base b = s;
System.out.println(b == s);//true
System.out.println(b.count);//10
b.display();//20
}
}
class Base {
int count = 10;
public void display() {
System.out.println(this.count);
}
}
class Sub extends Base {
int count = 20;
public void display() {
System.out.println(this.count);
}
}
⭕ 对象的多态===>在Java中,子类的对象可以替代父类的对象使用
- 一个变量只能有一种确定的数据类型。
- 一个引用类型变量可能指向(引用)多种不同类型的对象。
代码演示:
Person p = new Student();
Object o = new Person();//Object类型的变量o,指向Person类型的对象
o = new Student(); //Object类型的变量o,指向Student类型的对象
⭕ 子类可看做是特殊的父类,所以父类类型的引用可以指向子类的对象:向上转型(upcasting)
⭕ 一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的特有的属性和方法。因为有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。
代码演示:
Student m = new Student();
m.school = “pku”; //合法,Student类有school成员变量
Person e = new Student();
e.school = “pku”; //非法,Person类没有school成员变量
/*
属性是在编译时确定的,编译时e为Person类型,没有school成员变量,因而编译错误。
*/
4. 应用举例
(1)方法声明的形参类型为父类类型,可以使用子类的对象作为实参调用该方法
public class Test {
public void method(Person e) {
// ……
e.getInfo();
}
public static void main(Stirng args[]) {
Test t = new Test();
Student m = new Student();
t.method(m); // 子类的对象m传送给父类类型的参数e
}
}
(2)虚拟方法调用(Virtual Method Invocation)
⭕正常的方法调用 :
Person e = new Person();
e.getInfo();
Student e = new Student();
e.getInfo();
⭕虚拟方法调用(多态情况下) :
子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
Person e = new Student();
e.getInfo(); //调用Student类的getInfo()方法
/* 编译时类型和运行时类型
编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo()方法。——动态绑定
*/
5. 区分方法的重载与重写(从编译和运行的角度)
⭕ 重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。
⭕ Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定”;
⭕ 而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”。 引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚绑定,它就不是多态。”
6. 面试题:证明多态是编译时行为还是运行时行为?
//证明如下:
class Animal {
protected void eat() {
System.out.println("animal eat food");
}
}
class Cat extends Animal {
protected void eat() {
System.out.println("cat eat fish");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("Dog eat bone");
}
}
class Sheep extends Animal {
public void eat() {
System.out.println("Sheep eat grass");
}
}
public class InterviewTest {
public static Animal getInstance(int key) {
switch (key) {
case 0:
return new Cat ();
case 1:
return new Dog ();
default:
return new Sheep ();
}
}
public static void main(String[] args) {
int key = new Random().nextInt(3);
System.out.println(key);
Animal animal = getInstance(key);
animal.eat();
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/151107.html