《Java编程思想》(第4版)学习总结

2023-11-09

        一次偶然的机会让我接触到了《Java编程思想》这本书,之前他们说是Thinking in Java 我当时并不知道,后来查资料的时候才知道是这本经典的书籍,于是就忍不住开始阅读了。开始感觉这本书讲解的东西不太理解,然后经过反复的思考才感觉有所收获,也许我对书中的知识理解有误差甚至误解,在这里也希望大家能批评指正,也欢迎大家补充,谢谢大家。

        下面就是我对每一章的学习总结:

                         第一章:对象导论

1.1、抽象过程(有了面向对象)
        面向对象程序设计方式:
  • 万物皆为对象
  • 程序是对象的集合,它们通过发送消息来告知彼此所要做的。具体的说,可以把消息想象为对某个特定的对象的方法的调用请求。
  • 每个对象都有自己的由其他对象所构成的存储。
  • 每个对象都拥有其类型。每个对象都是某个类(class)的实例(instance)。这里的“类”就是“类型”的同义词。
  • 某一特定类型的所有对象都可以接收同样的消息。比如几何图形的方法,可以被它的子类圆形来调用(即发送给“几何图形”对象消息)。
1.2、每个对象都有一个接口
        当一个类被创建的时候(类是描述了具有相同特性(数据元素)和行为(功能)的对象集合),就可以随心所欲的创建对象,但是我们创建对象应该要有实际意义才行,比如某个对象完成某件事情,而这件事情的具体动作就是接口。之前看到书上讲过接口其实是对动作的抽象,也就说明接口中的方法其实可以对应各个动作。
1.3、每个对象都提供服务
       可以将对象看成“服务员”,程序本身将向用户提供服务,它将通过调用其他对象的方法来实现用户需要的服务(功能)。
1.4、被隐藏的具体实现
        将程序开发人员按角色分为类创建者(那些创建新数据类型的程序员)和客户端程序员(那些在其他应用中使用数据类型的类消费者)是大有裨益的。
        类创建者提供给客户端程序员部分代码,其他的薄弱部分要隐藏起来,避免被别人修改从而减少程序bug。就好像我们在使用类库的时候调用别人的方法都是公共的,但是还有隐藏的,只是我们看不到而已。
1.5、复用具体实现
      
         一旦类被创建并被测试完,那么它就应该(在理想状况下)代表一个有用的代码单元。事实证明,这种复用性并不容易达到我们所希望的程度,产生一个可复用的对象设计需要丰富的经验和敏锐的洞察力。但一旦有了这样的设计就可供复用。代码复用是面向对象程序设计语言所提供的最了不起的优点之一。
       最简单地复用某个类的方式就是直接使用该类的一个对象,此外也可以将那个类的对象置于某个新的类中。我们称其为“创建一个成员对象”。新类可以由任意数量、类型的其他对象以任意可以实现新的类中想要的功能的方式所组成。因为是在使用现有的类合成新的类,这种概念被称为组合。
1.6、继承
       在现实生活中当创建了一个类之后,即使另一个新类与其有相似的功能,你还得重新创建一个新类。如果我们能够以现有的类为基础,复制它,然后通过添加和修改这个副本来创建新类那就好多了。通过继承就可以达到这样的效果,不过需要注意的是,当源类(被称为基类、超类或父类)发生变动时,被修改的“副本”(被称为导出类、继承类或子类)也会反映出这些变动。
      有两种方法可以使基类与导出类产生差异:
  •  第一种方法很直接:直接在导出类中添加新方法,这些新方法并不是基类接口的一部分。这意味着基类不能直接满足你的所有要求,因此添加更多的方法。虽然这种方法有时候是一直完美的解决方式,但是,你应该仔细考虑基类是否要添加这些额外方法的可能性。
  • 第二种方法是改变现有基类的方法的行为,这被称之为覆盖。要想覆盖某个方法,可以直接在导出类中创建该方法的新定义即可,
      可以理解为:此时,我正在使用相同的接口方法,但是我想在新类型中做些不同的事情。
 
1.6.1、“是一个”与“像是一个”的关系
           因为继承有两种情况:
  • 导出类和基类是完全相同的类型,因为它们具有完全相同的接口。通常称之为替代原则。在某种意义上,这是一直处理继承的理想方式。这种情况的基类与导出类的关系称为is-a(是一个)的关系,例如,“一个圆形就是一个几何形状”。判断是否继承,就是要确定是否可以用is-a来描述类之间的关系,并使之具有实际意义。
  • 有时候到处类型中会添加新的接口元素(功能),这样也就扩展了接口。这个新类也可以代替基类,但这种代替不完美了,因为导出类有新的方法了,基类无法访问这些新方法。这种情况称之为is-like-a(像是一个)关系。  
1.7、 伴随多态的可互换对象
       当我们添加新的子类时调用基类的方法时,它却能够正确执行不同的代码。例如,可以把三角形(导出类)看作是几何图形(基类),当我们在导出类中调用基类画图的方法的时候,却能正确的画出三角形。(补充:在编译器中是无法精确地了解哪一段代码将会被执行,所在这里涉及了一个后期绑定的概念,通过后期绑定则当向一个对象发送消息时,该对象知道对这条消息应该做些什么)。
例如一段程序代码:
void doSomething(Shape shape){
        //擦除
        shape.erase();  
       //绘制  
        shape.draw();
}
       这个方法可以与任何Shape对话,因此它是独立于任何它要绘制和擦除的对象具体类型的,如果程序中其他部分用到了doSomething()方法:
      Circle circle = new Circle();
      Line line = new Line();
      doSomething(circle);
      doSomething(line);
     对于doSomething()的调用会自动地正确处理,而不管对象的确切类型。
     以这行代码为例:
     doSomething(circle);
     当Circle被传入到预期接收Shape的方法中,由于Circle可以被doSomething()看作是Shape,也就是说,doSomething()可以发送给Shape的任何消息,Circle都可以接收,这么做是完全安全且符合逻辑的。
     把导出类看作是它的基类的过程是向上转型。通常基类在顶部,而导出类在其下部散开,因此,转型为一个基类就是在继承图中向上移动,即“向上转型”。
1.8、单根继承结构
       在OOP(面向对象)中,Java里边所有的类都继承自单一的基类,这个类的名字就是Object。
       在单根继承结构中的所以对象都具有一个公共接口,所以他们归根到底都是相同基本类型。所以这种结构保证所有对象都具备某些功能。因此在系统中你可以在每个对象上执行某些基本操作。所以对象都可以很容易的在堆上创建,而参数传递也得到极大的简化。
    单根继承结构使垃圾回收器的实现变得容易很多,因为所有对象都保证具有其类型信息,因此不会因无法确定对象的类型而陷入僵局。这对于系统级操作(如异常处理)显得尤其重要,并且给编程带来了更大的灵活性。
1.9、容器(也称之为集合)
        通常说来,如果不知道在解决某个特定问题时需要多少个对象,或者他们将存活多久,那么就不可能知道如何存储这些对象。我们不知道需要多少空间来创建这些对象,因为这类信息只有在运行时才能获得。
        在OOP中都有一组容器,例如Java中就有List(用于存储序列),Map(也被称为关联数组,用来建立对象之间的关联),Set(每种对象类型只有一个,不能重复),以及诸如队列、树、堆栈等更多的构件。
  1.9.1、参数化类型
        容器存储的对象都只具有Java中的通用类型:Object。单根继承结构意味着所有的东西都是Object类型,所以可以存储Object的容器可以存储任何东西。这使得容器很容易被复用。
        因为容器只存储Object,所以当将对象引用置入容器时,它必须向上转型为Object,因此它会丢失其身份,当将它取回的时候却是对Object对象的引用,而不是最初置入的对象,因此这里就用到了转型,但这是向下转型为更具体的类型。我们知道向上转型是安全的,例如Circle是一种Shape类型,但不知道某个Object是Circle还是Shape,所以除非确切知道所要处理的对象类型,否则向下转型几乎是不安全的。
      向下转型并非彻底是危险的,因为如果向下转型为错误类型,就会得到被称为异常的运行时错误。
      因为这些原因,在Java中就出现泛型这个类型。一对尖括号,中间包含类型信息,通过这些特征就可以识别对泛型的使用。例如:
      ArrayList<Shape> shapes = new ArrayList<Shape>();
   
      从上面我们就可以了解到,其实在编程的过程中会大量的使用泛型。因为它给我们带来极大的便利。
1.10、对象的创建和生命期
       在使用对象的时候,最关键的问题之一便是它们的生成和销毁方式。每个对象为了生存都需要资源,尤其是内存,当我们不再需要一个对象的时候就必须清理掉,使其占有的资源可以被释放和重用。但是在实际开发过程中有的对象很难确定什么时候销毁,怎样控制对象生命周期是由程序员决定。
       第一种:为了追求最大的执行速度,对象的存储空间和生命周期可以在编写程序时确定,这可以通过将对象置于堆栈或静态存储区域内来实现。这种方式将存储空间分配和释放置于优先考虑的位置,某些情况下这样控制非常有价值。但是也牺牲了灵活性,因为必须在编写程序时知道对象的确切数量、生命周期和类型。
      第二种:在被称为堆的内存池中动态的创建对象。在这种方式中,直到运行时才知道需要多少对象,它们的生命周期如何,以及具体类型只能在程序运行时相关代码被执行到那一刻才能确定。如果需要一个新对象,可以在需要的时候直接在堆中创建。因为存储空间是在运行时被动态管理的,所以需要大量的时间在堆中分配空间,这可能要远远大于在堆栈中创建存储空间的时间。
      Java采用了动态内存分配方式。每当创建新对象时,就要使用new关键字来构建此对象的动态实例。对于对象生命周期,在堆栈上创建对象的语言,编译器可以确定对象存活的时间,并可以自动销毁它。如果是在堆上创建对象,编译器对它的生命周期一无所知。但是Java提供了“垃圾回收器”的机制,它可以自动发现对象何时不再被使用,并继而销毁它。
1.11、异常处理:处理错误
      自从编程语言问世以来,错误处理就始终是最困难的问题之一。Java的异常处理在众多编程语言中格外引人注目,因为Java一开始就内置了异常处理,而且强制你使用它。它是唯一可接受的错误报告方式。如果没有编写正确的处理异常的代码,那么就会得到一条编译时的出错消息。这种有保障的一致性有时会使错误处理非常容易。
1.12、并发编程
      在计算机编程中有一个基本概念,就是在同一时刻处理多个任务的思想。
      在程序中彼此独立运行的部分被称之为线程。通常线程只是一种为单一处理器分配执行时间的手段。但是如果操作系统支持多处理器,那么每个任务都可以被指派给不同的处理器,并且它们是在真正地并发执行。在语言级别上,多线程所带来的便利之一是程序员不用再操心机器上是有多个处理器还是只有一个处理器。由于程序在逻辑上分为线程,所以如果齐全拥有多个处理器,那么程序不需要特殊调整也能执行得更快。
      所以这些都使得并发看起来相当简单,但是有一个隐患:共享资源。如果有多个并行任务都要访问同一项资源,那么就会出问题。例如,两个进程不能同时想一台打印机发送信息。为了解决这个问题,可以共享的资源,必须在使用期间被锁定。因此,整个过程是:某个任务锁定某项资源,完成其任务,然后释放资源锁,使其他任务可以使用这项资源。
总结:
       这一章其实已经提到了很多的知识,但是并没有详细的解释,它主要是让我们在脑海里有一个印象。在编程中其实都是对现实中某些实物的抽象过程。每个事物我们可以看成一个对象,而这个对象都有自己的存在意义,也就是它能够干什么,具有什么功能,同时在生活中会有很多相似的东西,我们没必要去一一的重复,因此就有了复用这个概念。当我们在某个对象上有更多要求的时候可以新建一个去继承该对象的对象,然后扩展对应的接口。在Java中有一个特性:多态,例如,人类是一个基类,然后有一些接口,走、跳、跑等,然后在这个接口中传入一个“男人”的对象,那么这个男人就会具有上述的功能。然后进一步深入就是当我们创建对象之后要学会怎样去管理,和存储。所以也就有了容器、泛型、生命周期这些概念。在编程的时候难免会出现错误,在Java有异常处理的机制可以帮助我们更好的解决这个难题,另外为了提高效率所以会有并发这个概念。
      其实我对这章总结得比较简单,只是为了方便自己记忆罢了,里边提到的知识点,后面应该会详细阐述的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

《Java编程思想》(第4版)学习总结 的相关文章

随机推荐