Java 面向对象简介

2018-07-31作者:李兴华, 编著编辑:Solomon

面向对象是现在最为流行的程序设计方法之一,现代的程序开发几乎都是以面向对象为基础。但是在面向对象设计之前,广泛采用的是面向过程,面向过程只是针对自己来解决问题。面向过程的操作是以程序的基本功能实现为主,实现之后就完成了,也不考虑修改的可能性; 面向对象,更多的是要进行子模块化的设计,每一个模块都需要单独存在,并且可以被重复利用,所以面向对象的开发更像是一个具备标准的开发模式。


面向过程与面向对象的区别

考虑到读者暂时还没有掌握面向对象的概念,所以本书先使用一些较为直白的方式帮助读者理解面向过程与面向对象的区别。例如,现在要想制造一把手枪,则可以有两种做法。


 做法一(面向过程):将制造手枪所需的材料准备好,由个人负责指定手枪的标准,如枪杆长度、扳机设置等,但是这样做出来的手枪完全只是为一把手枪的规格服务, 如果某个零件(如扳机坏了)需要更换的时候,那么就必须首先弄清楚这把手枪的制造规格,才可以进行生产,所以这种做法没有标准化和通用性;


 做法二(面向对象):首先由一个设计人员设计出手枪中各个零件的标准,并且不同的零件交给不同的制造部门,各个部门按照标准生产,最后统一由一个部门进行组装,这样即使某一个零件坏掉了,也可以轻易地进行维修,这样的设计更加具备 通用性与标准模块化设计要求。


对于面向对象的程序设计有 3 个主要的特性:封装性、继承性和多态性。下面为读者简单介绍一下这 3 种特性,在本书后面的内容中会对此三个方面进行完整的阐述。


1.封装性

封装是面向对象的方法所应遵循的一个重要原则,它有两个含义:一是指把对象的属性和行为看成一个密不可分的整体,将这两者“封装”在一个不可分割的独立单位(即对象)中。 另一层含义指“信息隐蔽”,把不需要让外界知道的信息隐藏起来,有些对象的属性及行为允许外界用户知道或使用,但不允许更改,而另一些属性或行为,则不允许外界知晓,或只允许使用对象的功能,而尽可能隐蔽对象的功能实现细节。


封装机制在程序设计中表现为,把描述对象属性的变量及实现对象功能的方法合在一起, 定义为一个程序单位,并保证外界不能任意更改其内部的属性值,也不能任意调动其内部的功能方法。

封装机制的另一个特点是,为封装在一个整体内的变量及方法规定了不同级别的可见性或访问权限。


2.继承性

继承是面向对象方法中的重要概念,并且是提高软件开发效率的重要手段。

首先拥有反映事物一般特性的类,然后在其基础上派生出反映特殊事物的类。如已有的汽 车的类,该类中描述了汽车的普遍属性和行为,进一步再产生轿车的类,轿车的类是继承于汽

车类,轿车类不但拥有汽车类的全部属性和行为,还增加轿车特有的属性和行为。


在 Java 程序设计中,已有的类可以是 Java 开发环境所提供的一批最基本的程序——类库。用 户开发的程序类是继承这些已有的类。这样,现在类所描述过的属性及行为,即已定义的变量和 方法,在继承产生的类中完全可以使用。被继承的类称为父类或超类,而经继承产生的类称为子

类或派生类。根据继承机制,派生类继承了超类的所有成员,并相应地增加了自己的一些新成员。 面向对象程序设计中的继承机制,大大增强了程序代码的可复用性,提高了软件的开发效

率,降低了程序产生错误的可能性,也为程序的修改扩充提供了便利。 若一个子类只允许继承一个父类,称为单继承;若允许继承多个父类,称为多继承。


目前许多面向对象程序设计语言不支持多继承,而 Java 语言通过接口(interface)的方式来弥补由于 Java 不支持多继承而带来的子类不能享用多个父类的成员的缺憾。


3.多态性

多态是面向对象程序设计的又一个重要特征,是允许程序中出现重名现象。Java 语言中含有方法重载与对象多态两种形式的多态。

 方法重载:在一个类中,允许多个方法使用同一个名字,但方法的参数不同,完成的功能也不同。

 对象多态:子类对象可以与父类对象进行相互的转换,而且根据其使用的子类的不同完成的功能也不同。

多态的特性使程序的抽象程度和简捷程度更高,有助于程序设计人员对程序的分组协同开发。


类与对象的基本概念


在面向对象中类和对象是最基本、最重要的组成单元,那么什么叫类呢?类实际上是表示 一个客观世界某类群体的一些基本特征的抽象,属于抽象的概念集合。而对象则是表示一个个具体的事物,如张三同学、李四账户、王五的汽车,这些都是可以使用的事物,那么就可以理 解为对象,所以对象表示的是一个个独立的个体。


例如,在现实生活中,人就可以表示为一个类,因为人本身属于一种广义的概念,并不是一个具体的个体描述。而某一个具体的人,如张三同学,就可以称为对象,可以通过各种信息完整地描述这个具体的人,如这个人的姓名、年龄、性别等信息,那么这些信息在面向对象的概念中就称为属性,当然人是可以吃饭、睡觉的,那么这些人的行为在类中就称为方法。也就是说如果要使用一个类,就一定有产生对象,每个对象 之间是靠各个属性的不同来进行区分的,而每个对象所 具备的操作就是类中规定好的方法。


类与对象的定义

从之前的概念中可以了解到,类是由属性和方法组成的。属性中定义类一个个的具体信息,实际上一个属性就是一个变量,而方法是一些操作的行为,但是在程序设计中,定义类也是要按照具体的语法要求完成的,如果要定义类需要使用 class 关键字,类的定义语法如下。

格式3-1:类的定义格式

class 类名称{

   数据类型 属性(变量)

   public 返回值的数据类型 方法名称(参数 1,参数 2...){

       程序语句 ;

       [return 表达式;]

    } 

}

范例3-1:定义一个 Person 类。

class Person {     //类名称首字母大写

   String name;   //定义类成员(属性)

   int age;          //定义类成员(属性)

   public void tell() {  //定义类方法,没有 static

   System.out.println("姓名:" + name + ",年龄:" + age);

}

本程序定义了一个 Person 类,里面有两个属性:“name(姓名,String 型)”和“age(年龄,int 型)”,而后又定义了一个 tell()方法,可以输出这两个属性的内容。


问:为什么 Person 类定义的 tell()方法没有加上 static?

方法定义的时候要求方法前必须加上 static,为什么在 Person 类定义的 tell()方法前不加 static?


答:调用形式不同。

方法的时候是这样要求的:“在主类中定义,并且由主方法直接调用的方 法必须加上 static”,但是现在的情况有些改变,因为 Person 类的 tell()方法将会由对象调用,与之前的调用形式不同,所以暂时没有加上,读者可以先这样简单理解:如果是由对象调用的方法定义时不加 static,如果不是由对象调用的方法才加上 static,而关于 static 关键字的使用,在本章的后面会为读者详细讲解。类定义完成之后,肯定无法直接使用,如果要使用,必须依靠对象,那么由于类属于引用数据类型,所以对象的产生格式如下。

格式3-2 :声明并实例化对象。

类名称 对象名称 = new 类名称 () ;

格式3-3 :分步完成。

声明对象       类名称 对象名称 = null ;

实例化对象     对象名称 = new 类名称 () ;

因为类属于引用数据类型,所以只要是引用数据类型的实例化操作,永远都必须使用关键字 new 进行分配空间后才可以使用。当一个实例化对象产生后,可以按照如下的方式进行类的操作。

 对象.属性:表示调用类之中的属性; 

 对象.方法():表示调用类之中的方法。

范例2-1 :使用对象操作类。

class Person { //类名称首字母大写

String name;   //定义类成员(属性)

int age;       //定义类成员(属性)

public void tell() {

  System.out.println("姓名:" + name + ",年龄:" + age); 

    }

}

public class TestDemo {

public static void main(String args[]) { 

Person per = new Person(); //声明并实例化对象 

per.name = "张三"; //对象.属性 

per.age = 30;     //对象.属性 

per.tell();       //对象.方法()

    }

}

程序运行结果:

姓名:张三,年龄:30

以上的程序使用了格式3-2完成,下面再使用格式3-3来完成同样功能的操作。

范例3-3 :使用另外一种格式操作类。

class Person { //类名称首字母大写 

     String name;//定义类成员(属性)

      int age;//定义类成员(属性)

     public void tell() {

 System.out.println("姓名:" + name + ",年龄:" + age);

     }

}

public class TestDemo {

public static void main(String args[]) {

Person per = null ;   //声明对象 

per = new Person() ; //实例化对象 

per.name = "张三";   //对象.属性

per.age = 30;          //对象.属性 

per.tell();                //对象.方法()

     } 

程序运行结果:

姓名:张三,年龄:30

本程序和范例3-2的功能完全一样,唯一的区别在于更改了另外一种对象实例化操作格式。

问:给出的两种不同的实例化方式有什么区别呢?

通过范例 3-2 和范例 3-3 的运行结果,没有发现任何的区别,而有了一个类之后,也肯定要通过对象才可以使用,那么给出这两种不同的格式有什么意义?


回:为了引入内存分析方便。

如果要想解释这个问题,那么首先需要解决的就是内存的关系理解(Java 是在 C++上开发 的,所以本次讲解的时候还是按照 C++的理论进行内存关系的讲解),首先给出两块内存空间。

 堆内存:保存对象的真正数据,都是每一个对象的属性内容;

 栈内存:保存的是一块堆内存的空间地址,可以把它想象成一个 int 型变量(每一 个 int 型变量只能存放一个数值),所以每一块栈内存只能够保留一块堆内存地址。但是为了方便理解,可以简单地将栈内存中保存的数据理解为对象的名称(Personper),就假设保存的是 per。

WechatIMG582.jpeg

按照这种方式理解,可以得出如图所示的内存关系。

按照这样的概念理解,范例 3-3 的程序就可以按如下的内存关系图表示出来,如图所示。而如果要想开辟堆内存空间,只能依靠关键字 new 来进行开辟。即只要看见了关键字 new,不管何种情况下,都表示要开辟新的堆内存空间。


sadasd.jpg


如图很好地解释了范例 3-3 每一步代码的内存操作,从此图图可以发现,对象一定需要一块对应的内存空间,而内存空间的开辟需要通过关键字 new 来完成,每一个对象在刚刚实例化之后,里面的所有属性的内容都是其对应数据类型的默认值,只有设置了属性内容之后,属性才可以保存内容。

 

对象使用前必须首先进行实例化操作。

引用数据类型在使用之前进行实例化操作,如果在开发之中出现了如下代码,那么肯定会在程序运行时产生异常。


范例 3-4:产生异常的代码。

class Person {//类名称首字母大写

 String name; //定义类成员(属性) 

  int age;        //定义类成员(属性)

public void tell() {

    System.out.println("姓名:" + name + ",年龄:" + age); 

   }

}

public class TestDemo {

public static void main(String args[]) { 

Person per = null ;//声明对象,却没有实例化对象 

per.name = "张三";//对象.属性,直接使用未实例化对象

 per.age = 30;  //对象.属性

per.tell();//对象.方法()

  } 

}

程序运行结果

Exception in thread "main" java.lang.NullPointerException at TestDemo.main(TestDemo.java:12)

这个异常信息表示的是 NullPointerException(空指向异常),这种异常只会在引用数据类型上产生,并且只要是进行项目的开发,都有可能会出现此类异常,而此类异常的唯一解决方法就是查找引用数据类型,并观察其是否被正确实例化。


之前的程序完成了单个对象的实例化操作,下面再编写一个程序,进行多个对象的实例化 操作。

范例 3-5:观察产生两个对象的操作。

class Person { //类名称首字母大写

String name; //定义类成员(属性) 

int age;//定义类成员(属性)

public void tell() {

 System.out.println("姓名:" + name + ",年龄:" + age); 

  }

}

public class TestDemo {

public static void main(String args[]) { 

Person per1 = null ;//声明对象

Person per2 = new Person() ; //声明并实例化对象 

per1 = new Person() ;//实例化对象 

per1.name= "张三" ;//对象.属性 

per1.age =  30 ;//对象.属性 

per2.name= "李四";//对象.属性 

per2.age = 20 ;//对象.属性 

per1.tell() ;//对象.方法() 

per2.tell() ;//对象.方法()

  } 

}

程序运行结果:

姓名:张三,年龄:30

姓名:李四,年龄:20 本程序分别使用了两种方式进行了对象的实例化操作,但是由于程序中出现了两次关键字new,所以会开辟两块不同的堆内存空间,而本程序的内存操作如图所示。


qqqqq.jpg


封装性初步


封装属于面向对象的第一大特性,但是本次所讲解的封装只是针对其中的一点进行讲解,而对于封装操作由于涉及的内容过多,以后会有完整的介绍。但是在讲解封装操作之前,首先要来解决一个问题:为什么要有封装?

范例 3-8:观察没有封装操作的情况。

class Person { //类名称首字母大写

String name;//定义类成员(属性)

int age;//定义类成员(属性)

public void tell() {

 System.out.println("姓名:" + name + ",年龄:" + age); 

    }

}

public class TestDemo {

public static void main(String args[]) { 

Person per = new Person() ; //声明并实例化对象

per.name = "张三" ;//直接通过对象操作属性 

per.age = -30 ;//将年龄设置为-30 岁,业务错误

per.tell() ;//对象.方法()

   } 

}

程序运行结果:

姓名:张三,年龄:-30

本程序属于最简单的对象操作。首先实例化对象,而后分别为对象的属性赋值,此时可以 发现在主类中(不是 Person 本类)可以直接使用对象调用类中的属性,并且此时所设置的人的年龄是“-30”岁,而这样的结果从代码编译上不会有问题,但是从实际来讲,一个人的年龄不可能是-30 岁,这属于业务逻辑出错。而造成这种错误的关键在于没有检查要设置的内容,就直接将内容赋予了属性,这样肯定是不安全的。就好比银行,每一个储户不可能自己直接去操作金钱,必须由银行业务人员依照业务标准进行金钱的操作,并且每一步操作都需要进行检查,而检查的第一步是需要让用户看不见操作的东西,那么在这种情况下,就可以使用 private 关键字,将类中的属性进行私有化操作。


范例 3-9:使用 private 封装类中属性。


class Person {//类名称首字母大写

private String name; //属性封装

private int age;  //属性封装

public void tell() {

 System.out.println("姓名:" + name + ",年龄:" + age); 

   }

}

public class TestDemo {

public static void main(String args[]) { 

Person per = new Person() ; //声明并实例化对象

per.name = "张三" ;//语法错误:无法访问私有属性 

per.age = -30 ;//语法错误:无法访问私有属性

per.tell() ;//对象.方法()

  } 

}

本程序在Person类中加入了private关键字之后,发现在Person类的外部将无法直接再利用对象调用类中的属性,并且在编译的时候就直接提示语法错误,所以,此时的属性是安全了,但是这些属性虽然封装,却依然需要被外部操作,所以就 Java 的开发标准而言,如果要访问类中的私有属性,需要按照如下形式定义操作方法。

 setter(以“private String name”属性为例):public void setName(String n); 

 getter(以“private String name”属性为例):public String getName()。


 范例 3-10:编写 setter()和 getter()方法。

class Person {//类名称首字母大写

private String name; //属性封装

private int age;  //属性封装

public void tell() {

 System.out.println("姓名:" + name + ",年龄:" + age); 

   }

}

public void setName(String n){//setter:设置 name 属性内容

  name = n;

}

public void setAge(int a){//setter:设置 age 属性内容

    if(a>=0 && a<=250){//增加检查

        age =a ;//满足条件赋值

        }

}

public String getName(){

   return name; //getter:取得age属性内容

}

public int getAge(){

   return age;   //getter:取得age属性内容

}

public class TestDemo {

public static void main(String args[]) {

Person per = new Person() ; //声明并实例化对象

per.setName("张三") ; //语法错误:无法访问私有属性

per.setAge(-30) ;//语法错误:无法访问私有属性

per.tell() ;//对象.方法()

   } 

}

程序运行结果:

姓名:张三,年龄:0

//getter:取得 name 属性内容 //getter:取得 age 属性内容

//声明并实例化对象 //语法错误:无法访问私有属性 //语法错误:无法访问私有属性 //对象.方法()

本程序在定义 setAge()方法时增加了数据的验证操作,所以当用户设置的年龄非法时,age 的内容将保持其默认值“0”。


关注微信公号“书问”,快去免费领取符合你目标的图书吧!

内容来源:书问

作者李兴华
出版清华大学出版社
定价69.8元
书籍比价

分享到

扫描二维码 ×

参与讨论

电子纸书

Java面向对象编程基础教程

信必优技术学院研发部编著
清华大学出版社[2009] ¥13

面向对象的现代工业控制系统实用设计技术

祝(王献)冰 著
清华大学出版社[2009] ¥12

Java编程手记——从实践中学习Java

欧二强, 等编著
清华大学出版社[2013] ¥40

Java入门1·2·3——一个老鸟的Java学习心得

臧萌,编著
清华大学出版社[2010] ¥28

面向在线社交网络的企业管理决策研究

齐佳音, 傅湘玲, 著
清华大学出版社[2017] ¥41

面向本质安全化的化工过程设计:多稳态及其稳定性分析

王杭州, 陈丙珍, 赵劲松, 邱彤, 著
清华大学出版社[2017] ¥38

网络化治理——面向中国地方政府的理论与实践

刘波,李娜 著
清华大学出版社[2014] ¥27

面向集成电路电阻电容提取的高级场求解器技术

喻文健, (美) 王习仁, 著
清华大学出版社[2014] ¥41

开源魅力:面向Web开源技术整合开发与实战应用

周相兵, 马洪江, 佘堃, 编著
清华大学出版社[2013] ¥40

管理创新成功之路——面向中国企业的全过程精益管理创新

齐二石,刘亮
清华大学出版社[2013] ¥18

出版业领先的TMT平台

使用社交账号直接登陆

Copyright © 2018 BookAsk 书问   |   京ICP证160134号


注册书问

一键登录

Copyright © 2018 BookAsk 书问   |   京ICP证160134号