几乎每一本介绍Java语言的书中都会提到“面向对象”的这个概念,然而博主初学Java时看到这方面的内容一般都是草草地看一看,甚至是直接略过。原因很简单:考试基本不考,而且初学阶段写代码也很少用上。但事实上面向对象时Java中一个非常重要的内容,而且与代码这整体设计关系很大。越是具有丰富的编程经验,就越能体会到这个思想在实际代码结构设计中的重要性。在《Java编程思想》中,作者用了很大的篇幅来介绍面向对象的相关内容,其中不乏一些关于如何运用面向对象来优化程序结构,提高代码的可读性和可维护性的内容。这一章就来整理一下关于面向对象的相关内容。

什么是面向对象

《Java编程思想》一书,对于面向对象程序设计总结了一下五个特征:

  • 万物皆为对象。将对象视为奇特的变量,它可以存储数据,除此之外,你还可以要求它在自身上执行操作。理论上讲,你可以出去带求解问题的任何概念化构件(狗、建筑物、服务等),将其表示为程序中的对象。
  • 程序是对象的合集,它们通过发送消息来告知彼此所要做的。要想请求一个对象,就必须对该对象发送一条消息。更具体地说,可以把消息想象为对某个特定对象的方法调用请求。
  • 每个对象都有自己的由其他对象所构成的存储。换句话说,可以通过创建包含现有对象的包的方式来创建新类型的对象。因此,可以在程序中构建复杂的体系,同时将其复杂性隐藏在对象的简单性背后。
  • 每个对象都拥有其类型。按照通用的说法,“每个对象都是某个类(class)的一个实例(instance)”,这里“类”就是“类型”的同义词。每个类最重要的区别于其他类的特征就是“可以发送什么样的消息给它”。
  • 某些特定类型的所有对象都可以接受相同的消息。“圆形”类型的对象同时也是“几何形”类型的对象,所以一个“圆形”对象必定能够接受发送给“几何形”对象的消息。这意味着可以编写与“几何形”交互并自动处理所有与几何形性质相关的事物的代码。

为什么要面向对象

  讨论这个问题前要先说说“抽象”机制。所有的编程语言都提供抽象机制,所谓抽象就是用编程语言的元素来表达某些内容。不同的编程语言所能够抽象的对象是不一样的。汇编语言是对底层机器的轻微抽象,能够表达计算机底层结构以及操作。还有一些"命令式"的编程语言(例如C、FORTRAN等),这些语言是对汇编语言的抽象。这些语言能够更易于阅读,更方便地被人所理解。但这些语言依然是在基于计算机的底层结构进行抽象的,因此在解决问题时依然会受限制与计算机的机构。程序员在解决某个特定问题的时候,需要构建该问题与计算机机构模型之间的映射,这使得编写程序变得很困难。

  如果编程语言能够直接对所需要解决的问题进行抽象,能够直接表达问题中的元素,那么就能够省去计算机结构和问题之间的映射的工作。面向对象就是能够为程序员提供面向问题空间元素的一个工具。如上文所述,面向对象的语言中,万物皆为对象,且每个对象都有其类型,也就是类(class)。因此,程序员就可以自定义适用于问题空间的类来解决问题。

  面向对象程序设计能够降低各组件之间的耦合性,增加了代码的可维护性,复用性和可扩展性。

封装、继承和多态

  封装继承多态是面向对象程序设计的三大特性。在具体介绍这三者前,先对Java中的类进行简单介绍。类是一组具有相同特性(数据元素)和行为(功能)的对象的集合,一个类可以有很多个对象。Java的类包含两部分元素:属性(也叫字段)和方法(也叫成员函数)。属性可以使任何类型的对象,包括基本类型和引用类型;方法是该类的对象能够进行的操作,用OOP中的说法,方法决定了一个对象能够接受什么样的消息。方法的组成包括名称,参数,返回值和方法体几部分。类的基本形式如下:

class ClassTypeA{
int dataA;
double dataB;
ClassTypeB classTypeB; ReturnType methodName(/* Argument list */) {
/* Method body */
}
...
}

封装

  封装是指将类的内部实现隐藏起来,仅暴露出必要的接口给使用者。隐藏实现的第一个优点是可以避免类的使用者接触和操作到他们不应该接触的部分,避免由于使用者的粗心或错误操作产生程序bug。第二,隐藏具体实现,仅暴露接口给使用者,那么设计者在修改类的实现的时候就不用顾忌对使用者的影响,只需要保持对外的接口不变就可以。第三点,使用接口调用来连接设计者和使用者,可以降低系统的耦合性。

  在具体的开发过程中,设计者对于类中的不同元素的可访问情况会有不同的要求:对于类内部的关键性字段,设计者会希望其完全被隐藏,不希望被任何使用者访问或修改;而对于提供给外部使用的接口,一定是希望其能够被所有使用者访问;还有一些元素,设计者希望该类的子类能够访问和使用,而不会被其他的使用者接触到。Java用访问权限关键字来区别这种不同的可访问性。

  Java中一共有3中访问权限关键字:

  • public表示紧随其后的元素对任何人都是可用的;
  • private关键字表示除了类型的创建者和类型内部的方法以外,任何人都不能访问该元素。如果有人试图访问private成员,就会在编译时收到错误信息提示;
  • protected关键字与private相比,区别在于继承的类可以访问protected修饰的元素,而不能访问private修饰的元素。

  除了以上三种访问权限以外,Java还有一种默认的访问权限,在没有使用任何访问权限关键字的情况下,默认制定为这种控制权限,也被称为包控制权限,因为被其修饰的元素可以被同一个包中的其他类所访问。

  访问权限关键字的使用方式如下:

class accessDemo {
private int privateData;
protected int protectedData; public int publicMethod() {...} int defaultMethod() {...}
}

继承

  继承是面向对象程序设计中必不可少的组成部分。在Java中,使用extends关键字来表示类的继承关系。继承关系中,将已有的类成为父类(基类),由父类生成的新类称为子类(导出类)。如下面一段代码中,Animal类为父类,Dog类为它的子类。

class Animal {
public Animal(){ }
} class Dog extends Animal{
public Dog(){ }
}

  事实上在Java中创建一个类时,总是在继承,如果新建类时没有用extends指定继承自那个类,则就隐式地从Java的标准根类Object类进行继承。也就是说所有的类都继承自Object类。让所有类都继承自Object可以使所有的类都具有一些相同的特性,或者可以都可以进行某些操作。例如Java中所有的类都具有hashcode()方法,可以计算该类对象的哈希值。这是Java中HashMap等重要数据结构实现的基础,也是判断对象间是否相同重要依据。

  1、子类中的元素

  • 子类继承父类的成员变量和方法。当一个子类继承一个父类时,子类便可以具有父类中的一些成员变量和方法,但子类只能继承父类中public和protected修饰的成员变量和方法。
  • 子类可以定义自己的成员变量和方法。在定义自己的成员变量和父类的中的变量名一致时,就会发生隐藏的现象。即子类中的变量会掩盖父类的变量。同样的,在定义方法时,如果子类的方法和父类的方法的方法名、参数列表和返回值都相同,则子类的方法就会覆盖父类的方法。隐藏覆盖是有差别的,简单来说。隐藏适用于成员变脸和静态方法,是指在子类中不显示父类的成员变量和方法,如果将子类转换为父类,调用的还是父类的成员变量和方法;覆盖针对的是普通方法,如果将子类转换成父类,访问的还是子类的具体方法。

  2、构造器

  子类不会继承父类的构造器,但是既然子类能够继承父类中的成员变量,那么自然也需要对其成员变量进行必要的初始化,初始化的方法就是调用父类的构造器。

  • 无参数构造器。如果父类的构造器没有参数,那么在调用子类的构造器时,编译器会默认调用父类的构造器,完成相关初始化工作。如下面这段代码:
public class Animal {
public Animal(){
System.out.println("animal constructor");
}
public static void main(String[] args) {
Dog dog = new Dog();
}
} class Dog extends Animal{
public Dog(){
System.out.println("dog constructor");
}
}
/**
* 运行结果:
* animal constructor
* dog constructor
*/
  • 有参数构造器。如果父类只有有参数的构造器,那么在子类的构造器中必须显式地调用父类的构造器,并且要位于子类构造器的最开始。否则在编译时就会报错。原因是在没有调用父类构造器的情况下,编译器会默认调用父类的无参数构造器。但此时编译器会找不到父类的无参数构造器,从而报错。如下面代码所示:
public class Animal {
public Animal(int i){
System.out.println("animal constructor " + i);
}
public static void main(String[] args) {
Dog dog = new Dog(0);
}
} class Dog extends Animal{
public Dog(int i){
super(i);//调用父类构造器
System.out.println("dog constructor " + i);
}
}
/**
* 运行结果:
* animal constructor 0
* dog constructor 0
*/

多态

  多态的定义是允许不同的对象对同一消息进行相应,即同一消息可以根据对象的不同而进行不同的行为,这里的行为就是指方法的效用。多态的意义在于分离“做什么”和“怎么做”,从而消除类型之间的耦合性,改善代码的组织结构和可读性,创造可扩展的程序。我们通过以下这个程序来具体说明:

 public class Animal {
public static void takeFood(Animal animal){
System.out.println("===start eat food===");
animal.eat();
System.out.println("====end eat food====");
} public void eat() {
} public static void main(String[] args) {
Animal animal = new Dog();
takeFood(animal);
animal = new Cat();
takeFood(animal);
} } class Dog extends Animal{
public void eat() {
System.out.println("dog eat food");
}
} class Cat extends Animal{
public void eat() {
System.out.println("cat eat food");
}
}
/**
* 运行结果:
* ===start eat food===
* dog eat food
* ====end eat food====
* ===start eat food===
* cat eat food
* ====end eat food====
*/

  在上述代码中,Dog类和Cat类都继承自Animal类,并且各自重写了eat()方法。在代码的12行中创建了一个Dog类对象,但是却把它付给了一个Dog类的父类引用,第14行同样这么做。takeFood()方法中的传入参数为一个父类对象,但是这并不影响第13行和15行中,将一个指向子类对象的Animal引用作为参数传入。并且在takeFood()方法中调用传入对象内部的方法时,实际调用的是子类中的方法。

  通过以上代码我们可以总结,多态存在的三个必要条件是:1)继承;2)重写;3)父类引用指向子类对象。

  在上述代码中,takeFood()方法体中的内容决定了“做什么”,具体“怎么做”却决定于参入对象的eat()方法。而传入的对象中如何定义eat()方法与takeFood()的内容并不相关,因此便实现了两者的解耦。当我们将传入takeFood()的参数由Dog类对象改为Cat类对象时,只需要修改引用的指向即可。这也就增强了代码的可替换性;当需要新增加一个新的动物Pig并进行相同的操作时,我们只需要新建一个Pig类,重写其中eat()方法,然后将Pig类对象传入takeFood()方法即可,这就是增强了代码的可扩展性

动态绑定

  你可能会想知道,在takeFood()方法中,调用的是Animal类的eat()方法,而具体执行的方法主体却是子类中的方法。那么编译器是怎么知道调用一个方法时具体应该执行哪个方法主体呢?

  我们把方法调用和一个方法主体的关联起来成为绑定。如果方法调用和方法主体在程序执行前就能关联起来,则称为前期绑定(静态绑定),反之,如果必须到程序运行时才能知道方法调用具体应该执行哪一个方法,则称为后期绑定(动态绑定)。如果类B是类A的子类,A中定义了方法func(String s),B中重写了方法func(String s),那么此方法就需要使用动态绑定。如果x是B的一个实例,通过x.func(str)调用方法时,Java虚拟机会先在B中寻找此方法,如果B类中有对应方法,则直接调用它,否则就在B的父类A中寻找此方法。

  在Java中,除了用static方法、final方法、private方法和构造方法以外,其他方法均采用动态绑定。这四种方法中:  

  • private方法无法被继承,那么自然无法被重写,所以在编译时就可以确定具体调用的方法
  • static方法可以被继承,可以被子类隐藏,但是不能被子类重写。所以也可以在编译时确定
  • final方法可以被继承,但是不能被子类重写
  • 构造方法也不能被子类继承。子类的构造方法有两种:使用系统自动生成的无参数构造方法;调用父类的构造方法(包括自己定义构造方法并在其中调用父类的构造方法)

  由以上分析我们可以看出,上述四种方法可以使用静态绑定的最终原因都是:不会出现方法重写,不会产生子类与父类具有信息(方法名,参数个数,参数类型,返回类型等)完全相同的方法。

参考文献

[1]. https://blog.csdn.net/u012340794/article/details/73194674

[2].Java编程思想 第四版

  

  

Java课堂笔记(二):面向对象的更多相关文章

  1. Java学习笔记(二) 面向对象---构造函数

    面向对象---构造函数 特点 函数名与类名相同 不用定义返回值类型 不写return语句 作用 对象一建立,就对象进行初始化. 具体使用情况 class Student { Student(){ Sy ...

  2. Java学习笔记之---面向对象

    Java学习笔记之---面向对象 (一)封装 (1)封装的优点 良好的封装能够减少耦合. 类内部的结构可以自由修改. 可以对成员变量进行更精确的控制. 隐藏信息,实现细节. (2)实现封装的步骤 1. ...

  3. Java课堂笔记(零):内容索引

    回想自己学习和使用Java的时间也是很长了.本科期间课堂上浅尝辄止地学习了点皮毛,后来也是搁置不用,未曾深入研究.研究生期间因为项目和实习的原因,基本算是重新拾起Java这门语言,并且接触到了Spri ...

  4. 线程(java课堂笔记)

    1.两种方式的差异 2.线程的生命周期 3.线程控制(线程的方法) 4.线程同步 5.线程同步锁 一. 两种方式的差异 A extends Thread :简单 不能再继承其他类了(Java单继承)同 ...

  5. Java 学习笔记(4)——面向对象

    现在一般的语言都支持面向对象,而java更是将其做到很过分的地步,java是强制使用面向对象的写法,简单的写一个Hello Word都必须使用面向对象,这也是当初我很反感它的一点,当然现在也是很不喜欢 ...

  6. Java学习笔记二十九:一个Java面向对象的小练习

    一个Java面向对象的小练习 一:项目需求与解决思路: 学习了这么长时间的面向对象,我们只是对面向对象有了一个简单的认识,我们现在来做一个小练习,这个例子可以使大家更好的掌握面向对象的特性: 1.人类 ...

  7. Java学习笔记二十五:Java面向对象的三大特性之多态

    Java面向对象的三大特性之多态 一:什么是多态: 多态是同一个行为具有多个不同表现形式或形态的能力. 多态就是同一个接口,使用不同的实例而执行不同操作. 多态性是对象多种表现形式的体现. 现实中,比 ...

  8. Java学习笔记二十一:Java面向对象的三大特性之继承

    Java面向对象的三大特性之继承 一:继承的概念: 继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类. 继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方 ...

  9. Java基础学习笔记(二) - 面向对象基础

    面向对象 一.面向对象概述 面向对象思想就是在计算机程序设计过程中,参照现实事物,将事物的属性特征.行为特征抽象出来,描述成计算机时间的设计思想.面向对象思想区别于面向过程思想,强调的是通过调用对象的 ...

随机推荐

  1. 企业网站的SSL签证生产测试以及https配置方法

    这一次要做企业网站怎么获得安全的数字证书,没有数字证书的话,在浏览器访问网站的时候会跳出不安全界面,而且钓鱼网站也会让用户进去个假网站,一般企业可以去阿里云去买数字证书,买好之后浏览器便会加载这个数字 ...

  2. Django中使用djangorestframework产生Token

    修改settings.py: INSTALLED_APPS添加rest_framework 产生Token from rest_framework.authtoken.models import To ...

  3. git log混乱之混乱操作

    好几个分支 然后就混乱了 git log信息一坨屎 git 删除某次指定的提交 git reset只是在本地仓库中回退版本,而远程仓库的版本不会变化. 以删除master分支为例 #新建一个备份的分支 ...

  4. django搭建一个小型的服务器运维网站-查看服务器中的日志与前端的datatable的利用

    目录 项目介绍和源码: 拿来即用的bootstrap模板: 服务器SSH服务配置与python中paramiko的使用: 用户登陆与session; 最简单的实践之修改服务器时间: 查看和修改服务器配 ...

  5. VUE+Ionic,项目搭建&打包成APK

    安装Vue&创建Vue工程 1.安装Vue CLI: npm install -g vue-cli 2.创建新的Vue项目,创建最后一步会提醒是否使用npm install 自动安装,如果选择 ...

  6. 第八周作业—N42-虚怀若谷

    一.显示统计占用系统内存最多的进程,并排序 [root@centos7 ~]# ps -eo uid,pid,ppid,tty,c,time,cmd,%mem --sort=-%mem UID PID ...

  7. python+selenium实现经京东登录+购物+支付

    import json from time import sleep from selenium import webdriver import chardet from selenium.webdr ...

  8. kafka broker

    在server.properties文件中配置: 1.broker.id kafka集群是由多个节点组成的,每个节点称为一个broker,中文翻译是代理.每个broker都有一个不同的brokerId ...

  9. R Seurat 单细胞处理pipline 代码

    options(stringsAsFactors = F ) rm(list = ls()) library(Seurat) library(dplyr) library(ggplot2) libra ...

  10. 170815-关于Session知识点

    Cookie:实际上就是一个头.               服务器会创建Cookie,并且将Cookie以一个响应头的形式发送给浏览器               浏览器收到Cookie以后,会保存 ...