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


    @


    面向对象


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


    结构化开发方法概述

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

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

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

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

    在这里插入图片描述


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

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

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

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


    面向对象程序设计简介


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

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

    在这里插入图片描述


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

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

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

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

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

    在这里插入图片描述

    假设需要组装一台电脑,如果拿到手的是主板、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对象内存存储示意图

    在这里插入图片描述

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

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

    在这里插入图片描述

    栈内存里的引用变量并未真正存储对象的成员变量,对象的成员变量数据实际存放在堆内存里;而引用变量只是指向该堆内存里的对象。从这个角度来看,引用变量与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 类库分布在多个包中,包括 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

  • 相关阅读:
    Tensorflow中实现BN为什么需要加入这个额外依赖?见CS231N作业源码
    为何神经网络权重初始化要随机初始化,不能以0为初始化
    Batch Normalization&Dropout浅析
    Git版本回退和撤销修改的区别
    linux下安装git提示”无法打开锁文件 /var/lib/dpkg/lock
    数据特征选择法
    深度学习笔记整理
    全面掌握IO(输入/输出流)
    startActivity时报错Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVI
    LitePal——Android数据库框架完整使用手册
  • 原文地址:https://www.cnblogs.com/three-fighter/p/13052511.html
Copyright © 2020-2023  润新知