Java Review (六、面向对象----类和对象)

@目录


面向对象


在目前的软件开发领域有两种主流的开发方法:结构化开发方法面向对象开发方法。早期的编程 语言如C、Basic、Pascal等都是结构化编程语言;随着软件开发技术的逐渐发展,人们发现面向对象可 以提供更好的可重用性、可扩展性和可维护性,于是催生了大量的面向对象的编程语言,如Java、 C#和 Ruby 等。


结构化开发方法概述

结构化程序设计方法主张按功能来分析系统需求,其主要原则可概括为自顶向下、逐步求精、模块化等。结构化程序设计首先采用结构化分析(Structured Analysis, SA)方法对系统进行需求分析,然后使用结构化设计(Structured Design, SD)方法对系统进行概要设计、详细设计,最后釆用结构化编 程(Structured Program, SP)方法来实现系统。使用这种SA、SD和SP的方式可以较好地保证软件系 统的开发进度和质量。

因为结构化程序设计方法主张按功能把软件系统逐步细分,因此这种方法也被称为面向功能的程序设计方法;结构化程序设计的每个功能都负责对数据进行一次处理,每个功能都接受一些数据,处理完后输出一些数据,这种处理方式也被称为面向数据流的处理方式。

结构化程序设计里最小的程序单元是函数,每个函数都负责完成一个功能,用以接收一些输入数据, 函数对这些输入数据进行处理,处理结束后输出一些数据。整个软件系统由一个个函数组成,其中作为 程序入口的函数被称为主函数,主函数依次调用其他普通函数,普通函数之间依次调用,从而完成整个软件系统的功能。

结构化软件的逻辑结构示意图

Java Review (六、面向对象----类和对象)


从图中可以看出,结构化设计需要采用自顶向下的设计方式,在设计阶段就需要考虑每个模块应该 分解成哪些子模块,每个子模块又分解成哪些更小的模块……依此类推,直至将模块细化成一个个函数。

每个函数都是具有输入、输出的子系统,函数的输入数据包括函数形参、全局变量和常量等,函数的输出数据包括函数返回值以及传出参数等。结构化程序设计方式有如下两个局限性。

  • 设计不够直观,与人类习惯思维不一致。釆用结构化程序分析、设计时,开发者需要将客观世 界模型分解成一个个功能,每个功能用以完成一定的数据处理。
  • 适应性差,可扩展性不强。由于结构化设计采用自顶向下的设计方式,所以当用户的需求发生 改变,或需要修改现有的实现方式时,都需要自顶向下地修改模块结构,这种方式的维护成本 相当高。

采用结构化方式设计的软件系统,整个软件系统就由一个个函数组成,这个软件的运行入口往往由一个“主函数”代表,而主函数负责把系统中的所有函数“串起来”。


面向对象程序设计简介


面向对象是一种更优秀的程序设计方法,它的基本思想是使用对象继承封装消息等基本概念进行程序设计。它从现实世界中客观存在的事物(即对象)出发来构造软件系统,并在系统构造中尽可能运用人类的自然思维方式,强调直接以现实世界中的事物(即对象)为中心来思考,认识问题, 并根据这些事物的本质特点,把它们抽象地表示为系统中的类,作为系统的基本构成单元(而不是用一 些与现实世界中的事物相关比较远,并且没有对应关系的过程来构造系统),这使得软件系统的组件可 以直接映像到客观世界,并保持客观世界中事物及其相互关系的本来面貌。

面向对象方式开发的软件的逻辑结构示意图

Java Review (六、面向对象----类和对象)


从图中看出,面向对象的软件系统由多个类组成,类代表了客观世界中具有某种特征的一类事物,比如汽车,汽车内部存在一些状态数据(Field),比如颜色、款式。

面向对象的语言不仅使用类来封装一类事物的内部状态数据,这种状态数据就对应于图中的Field(成员变量),而且类会提供操作这些状态数据的方法,还会为这类事物的行为特征提供相应的实现,这种实现也是方法。因此可以得到如下基本等式:

成员变量(状态数据)+方法(行为)=类定义

从这个等式来看,面向对象比面向过程的编程粒度要大:面向对象的程序单位是类;而面向过程的程序单位是函数(相当于方法),因此面向对象比面向过程更简单、易用。

>
面向过程与面向对象对比

Java Review (六、面向对象----类和对象)

假设需要组装一台电脑,如果拿到手的是主板、CPU、内存条、硬盘等这种大粒度的组件,可以很轻松地组装成一台电脑;但如果拿到手的是一些二极管、三极管、 集成电路等小粒度的组件,要想把它们组装成电脑,就不简单了。如果把数据以及操j 作数据的方法都封装成对象,这就相当于提供了大粒度的组件,因此编程更容易。

从面向对象的眼光,开发者希望从自然的认识、使用角度来定义和使用类。也就是说,开发者希望直接对客观世界进行模拟:

  • 定义一个类,对应客观世界的某种事物
  • 业务需要关心这个事物的哪些状态, 程序就为这些状态定义成员变量
  • 业务需要关心这个事物的哪些行为,程序就为这些行为定义方法

不仅如此,面向对象程序设计与人类习惯的思维方法有较好的一致性,比如希望完成“猪八戒吃西瓜”:

在面向过程的程序世界里,一切以函数为中心,函数最大,因此这件事情会用如下语句来表达:

吃(猪八戒,西瓜);

在面向对象的程序世界里,一切以对象为中心,对象最大,因此这件事情会用如下语句来表达:

 猪八戒.吃(西瓜);

对比两条语句不难发现,面向对象的语句更接近自然语言的语法:主语、谓语、宾语一目了然,十 分直观,因此程序员更易理解。

面向对象基本特征


面向对象方法具有三个基本特征: 封装 (Encapsulation)继承(Inheritance)多态(Polymorphism)

封装指的是将对象的实现细节隐藏起来,然后通过一些公用方法来暴露该对象的功能;
继承是面向向对象实现软件复用的重要手段,当子类继承父类后,子类作为一种特殊的父类,将直接获得父类的属性和方法;
多态指的是子类对象可以直接赋给父类变量,但运行时依然表现岀子类的行为特征,这意味着 同一个类型的对象在执行同一个方法时,可能表现出多种行为特征。

除此之外,抽象也是面向对象的重要部分,抽象就是忽略一个主题中与当前目标无关的那些方面, 以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是考虑部分问题。例如, 需要考察Person对象时,不可能在程序中把Person的所有细节都定义出来,通常只能定义Person的部 分数据、部分行为特征——而这些数据、行为特征是软件系统所关心的部分。
虽然抽象是面向对象的重要部分,但它不是面向对象的特征之一,因为所有的编程语都需要抽象.当开发者进行抽象时应该考虑哪些特征是软件系统所需要的,那么这些特征就应该使用程序记录并表现出来。因此,需要抽象哪些特征没有必然的规定,而是取决于软件系统的功能需求。


类( class) 是对某种事物的抽象,是构造对象的模板或蓝图。我们可以将类想象成月饼模具,将对象想象为月饼。由类构造(construct) 对象的过程称为创建类的实例 (instance )。

定义类

语法:

[修饰符]class类名{
    零个到多个构造器定义..
    零个到多个成员变量 
    零个到多个方法...
}

说明:

  • 修饰符可以是public, final、abstract,或者完全省略这三个修饰符
  • 类名理论上只要是一个合法的标识符即可,但从程序的可读性方面来看,Java 类名必须是由一个或多个有意义的单词连缀而成的,每个单词首字母大写,其他字母全部小写,单词与 单词之间不要使用任何分隔符
  • 三种最常见的成员:构造器、成员变量和方法,三种成员都可以定义零个或多个,如果三种成员都只定义零个,就是定义了一个空类,这没有太大的实际意义。
一个类实例
public class Dog{
 //成员变量
  String breed;
  private  int age;
  String color;
  //方法
  void barking(){
  }
  
 public  void hungry(){
  }
 
 private void sleeping(){
  }
}

类型变量

一个类可以包含以下类型变量:

  • 局部变量:在方法、构造方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁。
  • 成员变量:成员变量是定义在类中,方法体之外的变量。这种变量在创建对象的时候实例化。成员变量可以被类中方法、构造方法和特定类的语句块访问。
  • 类变量:类变量也声明在类中,方法体之外,但必须声明为static类型。

static是一个特殊的关键字,它可用于修饰方法、成员变量等成员。static修饰的成员表明它属于这个类本身,而不属于该类的单个实例,因为通常把static修饰的成员变量和方法也称为类变量类方法。 不使用static修饰的普通方法、成员变量则属于该类的单个实例,而不属于该类。因为通常把不使用static 修饰的成员变量和方法也称为实例变量、实例方法。

由于static的英文直译就是静态的意思,因此有时也把static修饰的成员变量和方法称为静态变量静态方法,把不使用static修饰的成员变量和方法称为非静态变量和非静态方法。静态成员不能直接访问非静态成员。

提示:虽然绝大部分资料都喜欢把static称为静态,但实际上这种说法很模糊,完全无法说明static的真正作用。static的真正作用就是用于区分成员变量、方法、内部类、初始化块

构造方法

每个类都有构造方法。如果没有显式地为类定义构造方法,Java编译器将会为该类提供一个默认构造方法。

在创建一个对象的时候,至少要调用一个构造方法。构造方法的名称必须与类同名,一个类可以有多个构造方法。

构造方法实例
public class Puppy{
   
   //无参构造方法
    public Puppy(){
    }
 
   //有参构造方法
    public Puppy(String name){
        // 这个构造器有一个参数:name
    }
}

对象

对象是类的一个实例。
对象的三个主要特性:

  • 对象的行为(behavior)—可以对对象施加哪些操作,或可以对对象施加哪些方法
  • 对象的状态(state )—当施加那些方法时,对象如何响应
  • 对象标识(identity )—如何辨别具有相同行为与状态的不同对象

创建对象

对象是根据类创建的。在Java中,使用关键字new来创建一个新的对象。创建对象需要以下三步:

  • 声明:声明一个对象,包括对象名称和对象类型。
  • 实例化:使用关键字new来创建一个对象。
  • 初始化:使用new创建对象时,会调用构造方法初始化对象
创建对象实例
public class Puppy{
   public Puppy(String name){
      //这个构造器仅有一个参数:name
      System.out.println("小狗的名字是 : " + name ); 
   }
   public static void main(String[] args){
      // 下面的语句将创建一个Puppy对象
      Puppy myPuppy = new Puppy( "tommy" );
   }
}

访问实例变量和方法

实例
public class Puppy{
   int age;
   String name;
   public Puppy(String name){
      // 这个构造器有一个参数:name
      this.name=name;
      System.out.println("小狗的名字是 : " + name ); 
   }
 //无参构造方法
    public Puppy(){
    }
 
   public void setAge( int age ){
       this.age = age;
   }
 
   public int getAge( ){
       System.out.println("小狗的年龄为 : " + puppyAge ); 
       return puppyAge;
   }
 
   public static void main(String[] args){
      /* 创建对象 */
      Puppy myPuppy = new Puppy( "tommy" );
      /* 通过方法来设定age */
      myPuppy.setAge( 2 );
      /* 调用另一个方法获取age */
      myPuppy.getAge( );
      /*可以像下面这样访问成员变量 */
      System.out.println("变量值 : " + myPuppy.puppyAge ); 
   }
}

对象、引用和指针

创建对象语句:

 Puppy p = new Puppy( "tommy" );

在这行代码中实际产生了两个东西:一个是p变量, 一个是Puppy对象。

从Puppy类定义来看,Puppy对象应包含1个实例变量,而变量是需要内存来存储的。因此,当创建Puppy对象时,必然需要有对应的内存来存储Puppy对象的实例变量。

Puppy对象内存存储示意图

Java Review (六、面向对象----类和对象)

从图中可以看出,Puppy对象由多块内存组成,不同内存块分别存储了 Puppy对象的不同成员变量。当把这个Puppy对象赋值给一个引用变量时,系统如何处理呢?难道系统会把这个Puppy对象 在内存里重新复制一份吗?显然不会,Java没有这么笨,Java让引用变量指向这个对象即可。也就是说,引用变量里存放的仅仅是一个引用,它指向实际的对象。
与前面介绍的数组类型类似,类也是一种引用数据类型,因此程序中定义的Puppy类型的变量实 际上是一个引用,它被存放在栈(stack)内存里,指向实际的Puppy对象;而真正的Puppy对象则存放在堆(heap) 内存中。图5.2显示了将Person对象赋给一个引用变量的示意图。

引用变量指向实际变量示意图示意图

Java Review (六、面向对象----类和对象)

栈内存里的引用变量并未真正存储对象的成员变量,对象的成员变量数据实际存放在堆内存里;而引用变量只是指向该堆内存里的对象。从这个角度来看,引用变量与C语言里的指针很像,它们都是存储一个地址值,通过这个地址来引用到实际对象。实际上,Java里的引用就是C里的指针,只是Java 语言把这个指针封装起来,避免开发者进行烦琐的指针操作。

当一个对象被创建成功以后,这个对象将保存在堆内存中,Java程序不允许直接访问堆内存中的对象,只能通过该对象的引用操作该对象。也就是说,不管是数组还是对象,都只能通过引用来访问它们。

在示例中,p引用变量本身只存储了一个地址值,并未包含任何实际数据,但它指向实际的Person 对象,当访问p引用变量的成员变量和方法时,实际上是访问p所引用对象的成员变量和方法。

不管是数组还是对象,当程序访问引用变量的成员变量或方法时,实际上是访问该引 变量所引用的数组、对象的成员变量或方法。

堆内存里的对象可以有多个引用,即多个引用变量指向同一个对象,如:

//将p变量的值赋值给p2变量
Puppy p2 = p;

上面代码把p变量的值赋值给p2变量,也就是将p变量保存的地址值赋给p2变量,这样p2变量和 p变量将指向堆内存里的同一个Puppy对象。不管访问p2变量的成员变量和方法,还是访问p变量的成员变量和方法,它们实际上是访问同一个Puppy对象的成员变量和方法,将会返回相同的访问结果。

如果堆内存里的对象没有任何变量指向该对象,那么程序将无法再访问该对象,这个对象也就变成 了垃圾,Java的垃圾回收机制将回收该对象,释放该对象所占的内存区。
因此,如果希望通知垃圾回收机制回收某个对象,只需切断该对象的所有引用变量和它之间的关系 即可,也就是把这些引用变量赋值为null。


this关键字

Java提供了一个this关键字,this关键字总是指向调用该方法的对象。根据this出现位置的不同, this作为对象的默认引用有两种情形。

  • 构造器中引用该构造器正在初始化的对象。
  • 在方法中引用调用该方法的对象。

this关键字最大的作用就是让类中一个方法,访问该类里的另一个方法或实例变量。

public class DogTest{

  //定义一个jump()方法
  public void jump(){
    System.out.println("正在执jump方法”);
  } 
  
  //定义一个run()方法,run()方法需要借助jump()方法
  public void run()(
    //使用this引用调用run()方法的对象
    this.jump();
    System.out.println("正在执行 run 方法”);
  }
}

大部分时候,普通方法访问其他方法、成员变量时无须使用this前缀,但如果方法里有个局部变量 和成员变量同名,但程序又需要在该方法里访问这个被覆盖的成员变量,则必须使用this前缀。


Java 允许使用包( package ) 将类组织起来。借助于包可以方便地组织自己的代码,并将自己的代码与别人提供的代码库分开管理。
Java Review (六、面向对象----类和对象)

标准的 Java 类库分布在多个包中,包括 java.lang、java.util 和java.net 等。标准的 Java包具有一个层次结构。如同硬盘的目录嵌套一样,也可以使用嵌套层次组织包。所有标准的

Java 包都处于java 和 javax 包层次中。

使用包的主要原因是确保类名的唯一性。假如两个程序员不约而同地建立了 Employee类。只要将这些类放置在不同的包中, 就不会产生冲突。事实上,为了保证包名的绝对唯一性, Sun 公司建议将公司的因特网域名(这显然是独一无二的) 以逆序的形式作为包名,并且对于不同的项目使用不同的子包.

从编译器的角度来看, 嵌套的包之间没有任何关系。例如,java.util 包与java.util.jar 包毫无关系。每一个都拥有独立的类集合。

类的导入

一个类可以使用所属包中的所有类, 以及其他包中的公有类( public class。) 我们可以采用两种方式访问另一个包中的公有类。
第一种方式:在每个类名之前添加完整的包名。
例如:

java.tiie.LocalDate today = java.tine.Local Date.now();

第二种方式:
可以使用 import 语句导人一个特定的类或者整个包。import 语句应该位于源文件的顶部
(但位于 package 语句的后面)。
例如, 可以使用下面这条语句导人 java.util 包中所有的类。

import java.util .*;

然后, 就可以使用

LocalDate today = Local Date.now();
import java.time.LocalDate;






参考

【1】:《Java疯狂讲义》
【2】:《Java核心技术 卷一》
【3】:https://www.runoob.com/java/java-object-classes.html

上一篇:Java Review (二、Java基本数据类型)


下一篇:Java Review (十、面向对象----继承)