黑菜菌的JAVA学习笔记

简介

本文是笔者对《JAVA编程思想》的学习笔记。以自己的思维理解来写下这篇文章,尽可能地简练,易懂。本文将随本人学习进度实时更新

对象导论

抽象过程

汇编语言是对底层机器码的抽象,而面向过程的语言是对汇编语言的抽象。这些语言在解决问题时都是基于计算机的结构,而不是需要解决的问题的结构来考虑的。而面向对象的编程中,可以把每个对象看作是一台微型计算机——它具有自己的属性和方法(类似现实世界的特征与行为)。

  1. 万物皆为对象
  2. 程序是对象的集合,它们互相发送消息(或者说发送调用请求)来告知彼此需要做的。
  3. 每个对象都有自己的由其他对象所构成的存储。
    • 也即当前对象是由其他对象的组成的,它的属性可以是其他类的对象,以此组合成一个更为复杂的对象。EX.房子对象中含有椅子对象、电视对象等。
  4. 每个对象都有它的类型
    • 也即对象是某个类(class)的实例(instance)
  5. 某一特定类型的所有对象都可以接受同样的信息。
    • 同一类的对象能接受相同的方法调用。

综上,每一个对象都有其独立内部数据(也即属性或状态)和方法,在内存里都分配着唯一的地址,彼此区分。

每个对象都有一个接口

所有的对象都是唯一的,但这些具有相同特性和行为的对象可以被归属于一个类(class)[1]。这样做的好处就是你可以通过自定义类来解决对应问题,而不用拘泥于编译器中的基本类型(比如数据类型)。编译器对你所自定义的类会像处理内置类型一样对待。而且一旦这个类被创建,你可以随心所欲地创建任意多个对象。接口(interface)确定了对某一特定对象所能发出的请求(或者说方法调用)。这个过程可以描述为:向某个对象"发送消息"(产生请求),该对象执行对应的程序代码(方法)

每个对象都提供服务

将对象想象成一种"Service Provider"服务提供者,程序本身通过调用其他对象提供的服务来向用户提供服务。这样有助于提高对象的内聚性,高内聚是软件设计的基本质量要求之一。不要试图将太多的功能方法塞在一个对象中,而要分解性地看待问题。

被隐藏的具体实现

把程序员按照角色分成类的创建者(那些创建新的数据类型的程序员)和客户端程序员(那些在其应用中使用数据类型的类消费者)。类创建者的目标是构建类,这些类只对客户端程序员暴露必须的部分而隐藏其他部分,这样做约束客户端程序员,防止其改动对象内部脆弱的部分以减少程序BUG,而客户端程序员的目的就是调用各种用于实现特定目的类的对象来组合成新的类。

这里涉及了一个概念——访问控制。访问控制优势如下:

  1. 让客户端程序员无法触及他们不该触及的部分——这些部分对数据类型的操作是必需的但不是客户端程序员解决特定问题所需的接口。
  2. 让客户端程序员轻松看出来哪些东西对他们是重要的,哪些可以忽略。
  3. 允许库的设计者修改类内部的工作方式而不用担心影响到客户端程序员。也就是说可以优化一些算法使程序运行更流畅而不影响到实际使用。

复用具体实现

复用可以表现为:一个类创建多个对象;也有在一个类中使用另一个类的对象。

组合:新的类可以由任意数量、任何类型的其他对象以任意形式组合起来以实现新类想要实现的功能。组合常常被视作拥有(has-a)关系,在创建一个新的类的时候,我们更应该考虑使用组合而不是继承

继承

当你要创建的新类与原来的类比较,只是功能上的扩充或者修改的时候,建议使用继承。继承的特点是

  1. 当父类修改后,子类也会反映这些变动。
  2. 子类与父类具有相同的类型。
  3. 子类继承父类的所有属性与方法。
  4. 可以通过在子类中新增方法或者覆盖原父类的方法来达成差异性。
  5. 在java中使用单根继承,且所有的类都继承自终极基类Object,这使得垃圾回收变得容易

前期绑定:在非面向对象的编程中,编译器产生的函数调用会引起所谓的前期绑定。即程序执行前进行绑定,在出现函数调用的时候,会产生对具体函数名字的引用,就会把执行逻辑直接解析到这个方法的绝对地址上。在JAVA中final,static,private,构造方法,成员变量(包括非静态和静态)也都属于前期绑定。

后期绑定:程序在运行前,虚拟机并不知道类的类型,运行时根据对象的类型进行绑定。后期绑定中虚拟机通过给对象中安置某种"配置信息",然后利用该段信息,虚拟机计算出该对象的方法地址,然后调用,实现类型的识别和方法的调用。在C++中需要通过关键字virtual来使用这一特性,但在JAVA中,动态绑定是默认行为

转型(cast):把导出类(子类)看作是基类(父类)的过程叫向上转型(upcasting)。反之则叫向下转型

[例] Circle有area()方法,Rectangle也有area()方法,当我们想要使用打印面积方法printArea(Shape shape)时可以将Circle和Rectangle都向上转型为Shape类,这样我们不需要关心具体实现也无需在函数内判断这些形状,编译器会处理好相关细节

参数化类型(泛型)

当使用向下转型时,虚拟机在运行时会进行检查,占用额外的运行时间。而且向下转型是及其危险的,如果转型出错,会报运行时异常。当我们创建一个容器,能让虚拟机知道自己所要保存的对象的类型,不需要向下转型也能达成目的。我们称这一解决方案为参数化类型机制,在java里也叫泛型。泛型会在编译时进行检查。

对象的生命周期

C++为了追求效率与执行速度,对象的存储空间和生命周期可以在编写程序的时候确认。可以通过把对象放在堆栈或者静态区域来实现。这样虽然有价值,但也失去了灵活性。所以在java中采用了第二种方式——动态内存分配方式。不同于在堆上创建对象,编译器对生命周期一无所知。JAVA允许在堆栈上创建对象,使得编译器可以确认对象的存活时间,并依此自动销毁对象。JAVA提供了被称为“垃圾回收机制”的机制,它可以自动得知对象的生命周期,并在不用的时候销毁它。这减少了代码量也避免了隐性的内存泄漏问题。

异常处理:处理错误

异常处理虽然不是面向对象的特征,但在JAVA里内置了异常处理,而且强制你必须使用,如果你没有编写正确处理异常的代码,在编译时会报错。异常表现为一种对象,它可以从错误发生地抛出,并被专门处理的异常处理器“捕获”,不再是只能退出程序,你可以经常进行校正,并恢复程序的执行。

并发编程

把问题拆分成多个可独立运行的部分,从而提高程序的响应能力。这样的概念称之为“并发”,彼此独立运行的部分叫做“线程”。处理的时候有一个隐患“共享资源”,当多个线程访问同一资源的时候会出问题,所以JAVA线程在使用某一共享资源时锁定,防止其他任务访问使用。

Java与Internet

  1. 客户/服务器计算技术

它的核心思想就是,在中心位置有个中央信息存储池(central repository of information)用于存储数据,你可以根据需要把它发布出去提供给多个信息消费者。信息存储池、用于发布信息的软件以及信息与软件所存在的硬件服务器或机群被总称为服务器。而安装在用户机器上的软件与服务器进行通信,获取并处理信息,然后将之显示在客户机的用户机器上。

为了减轻服务器负载,我们通常会将尽可能多的任务分配给客户机或者中间件(服务端的其他机器)处理。

  1. WEB是一台巨型服务器

Web实际上是一个巨型的Client/Server系统,最初Web是单向地找服务器要文件,发展到后面,人们需要更完整的C/S服务器功能,比如需要向服务器端发送一些表单数据,或者查询数据库等等。

Web浏览器有这样一个概念:一段信息可以在不经过处理的情况下在任何计算机上显示出来。但是早期,浏览器只是一个观察器,无法做任何计算处理,这使得服务器端性能很吃紧。所以为了解决这一问题,通过引入在客户端中运行程序的能力,这被称之为“客户端编程”。

  1. 客户端编程

最初,交互性内容完全由服务器提供,客户端浏览器只能解释并显示服务器产生的静态页面。最基本的超文本标记语言HTML(HyperText Markup Language)包含最简单的数据收集机制——表单:文本输入框、复选框、单选框、列表和提交按钮等。这种提交动作通过Web服务器的通用网关接口(Common Gateway Interface,CGI)传递。传统的Web浏览器甚至不能流畅地看动图,因为图形交互格式(Graphic Interchange Format,GIF)的文件必须在服务器创建每个图形版本然后再逐帧发给客户端。这就很难受。于是就提出了客户端编程。

  • 插件:这是指将一段代码插入到浏览器的适当位置,以此来给浏览器添加一些新的功能。它允许专家级程序员不需要经过浏览器厂商的许可,开发某种语言的扩展,创建新的客户端编程语言。但是编写插件不是一件简单的事情。
  • 脚本语言:插件引起了脚本语言的开发。程序员只需要将需要在客户端运行的源代码嵌入到HTML页面中就行了,当然,这也使得源码会暴露给任何人浏览或窃取,所以通常我们不会使用脚本语言做复杂的事情。其中JavaScript语言是个典型(之所以这样命名是为了碰瓷当时如日中天的Java语言),起初各个Web浏览器厂商还是用彼此相异的方式来解释执行JS的,后面以ECMAScript的形式实现对JS的支持,各个厂商开始费劲地支持这一标准化。得益于JS的垃圾错误处理机制,JS难用的一批,直到近些年才有牛皮的大佬写出真正复杂的代码。JS通常用于创建丰富的交互性图形化用户界面(Graphic User Interface,GUI),但其实,脚本语言可以解决客户端编程中遇到的80%的问题。所以如果你的问题恰好在这个范围内,建议你使用脚本语言这种更容易、快捷的开发方式,而不是Java这种复杂的东西。
  • Java:脚本语言可以解决掉80%,剩下20%的硬骨头建议用Java来啃,因为Java是功能强大、安全、跨平台、国际化的编程语言。他可以很优雅地处理并发、数据库访问、网络编程和分布式计算。Java是通过applet与Java Web Start来进行客户端编程的。applet可以理解为一个附着在浏览器的小程序,当applet被激活时,他向服务器提出需要执行一个程序,然后服务器自动下发最新版程序,交给浏览器执行(前提是浏览器有内置的Java解释器),又由于Java是一种成熟的编程语言,所以它可以执行一些复杂的校验表单操作并迅速标出错误数据,不用再等服务器标记并传回图片了。这就提高了响应速度,降低网络拥塞。
  • 备选方案:由于需要安装Java运行时环境"JRE"对于当时的用户来说,占带宽、麻烦,IE并没有内置JRE,所以在当时并未得到广泛的运用。所以当时受Micro公司的Flex支持的Flash在98%的浏览器上都可以使用,安装更新都很便捷,ActionScript也是基于ECMAScript的,所以这是一个不错的备选方案。
  • .NET与C#.NET平台大致相当于java虚拟机(JVM,即执行Java程序的平台),除了只支持Widows外,C#充分吸收了Java的优点,又改进了Java的一些做的不够好的地方,这使得Java开始重视这一竞争对手,并在之后的java SES中做出了重大改进,.NET也提出开始支持在Linux上运行。
    4.服务端编程

大多数应用情形都是——客户端向服务端发送一个“请给我一个文件”的请求,诸如请求HTML页面、图片、JavaScript等。更复杂的请求通常涉及数据库事务,比如注册用户,搜索信息等,一般在服务端将检索到的信息生成一个HTML页面后返回给浏览器(如果客户端装有Java或者其他脚本程序,服务端就可以发送一些原始数据交给客户端编排格式,这样有助于减少服务端的负载)。这些数据库的请求都必须通过服务器端的某些代码来处理,这就是所谓的服务端编程。过去都是通过Perl、Python、C++或者其他语言编写CGI来实现的,单却造成了更复杂的系统。

总结

面向过程的语言,偏向于使用数据定义和函数的调用,他们倾向于解释计算机要做什么。而面向对象的编程则更倾向于解释我们需要解决什么样的问题。


  1. 类描述了具有相同特性(数据元素)和行为(功能)的对象集合。 ↩︎

posted @ 2020-06-09 15:35  黑菜菌  阅读(394)  评论(1编辑  收藏  举报