《Java编程思想》总结

2022-09-08 14:05:20 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

一、从机器语言到高级语言

程序员对计算机的任何操作都是通过机器语言完成的。 机器语言是二进制代码,由操作码和操作数组成。在物理层面上,每一个操作码都由相应的电路来实现。 由于机器语言的可读性和可移植性很差,先后出现了低级语言和高级语言。(相较于“低级”和“高级”,机器语言是“底层”语言)

最常见的低级语言是汇编语言,汇编语言程序经过汇编过程可以转换为机器码。 汇编语言用一些容易理解和记忆的字母或单词来代替指令,因此程序的可读性得到了提升。但是,汇编语言的可移植性仍然特别差:在不同的设备中,汇编语言对应着不同的机器语言指令集,不同平台之间不可直接移植。

高级语言基本脱离了机器的硬件系统,用人类更易理解的方式编写程序。 高级语言的执行方式有两种,一种是解释,一种是编译。解释是源程序翻译一句就执行一句的过程,而编译是把源程序翻译成可执行的目标代码,再由用户决定何时执行。

C语言是比较传统的高级语言。C语言程序经过编译转换为汇编码,再经过汇编转换为机器码。 与汇编语言相比,C语言简洁、紧凑,书写形式自由,具有良好的可读性。 C语言具有一定的可移植性:在不同的设备上,C语言程序可以“入乡随俗地”根据设备的规则编译成可执行的汇编码,再经过汇编执行。 但是这样的可移植性是不够的。

Java也是一门高级语言,它的设计目标之一便是“一次编译,到处运行。” Java解决可移植性问题的方式是统一使用JVM(Java Virtual Machine,Java虚拟机)来运行Java程序(即平台适应代码)。在不同的设备上,遵循《Java虚拟机规范》实现JVM——虽然在不同设备上JVM的实现是有区别的,但它们遵循相同的规范。在运行Java程序时,Java程序先经过编译转换为字节码,再由JVM解释执行。 Java凭借着JVM获得了优秀的可移植性。

语言实际上是帮助程序员更容易地操作计算机的工具,选择何种语言来编程,是Java还是C ,本质上相当于“选择腾讯视频还是优酷视频来观看电视节目(那么选择汇编语言就是选择了电视机)”。 正如腾讯视频是腾讯公司的产品,Java是美国公司Sun的产品。 希望读者能明白:语言只是工具。

二、JDK

JDK(Java Development Kit,Java开发工具包)为程序员开发Java程序提供了支持。 举个例子,假如程序员想要创建字符串”Hello World”,Java代码写作: String str = “Hello World”; Java语言规范认为上面的语句是合法的,而程序员之所以可以这样写代码,是因为在JDK中提供了String工具——如果没有JDK,编译器就不认识String。 我们研究Java,实际上也是在研究JDK源码。

三、Hello World

如何判断一个人是不是程序员? 看到“Hello World”,DNA动了的是程序员,否则就不是。

下面是经典的Hello World程序: public class HelloWorld { public static void main(String[] args) { System.out.println(“Hello World”); } } 运行此程序,打印台输出字符串“Hello World”。 main方法是程序的入口,执行main方法的实际过程如下: 1)运行%JAVA_HOME%/bin/javac.exe,对HelloWorld.java文件进行编译,生成HelloWorld.class文件。 2)运行%JAVA_HOME%/bin/java.exe,解释执行HelloWorld.class。

.java文件是Java的编译单元,里面是我们写的Java代码,.class文件则对应着字节码。

四、Java是一门程序设计语言

在讨论Java的任何特性之前,首先Java是一门程序设计语言。 既然是程序设计语言,就离不开语句和程序控制结构。 语句包括变量和操作符。 程序控制结构包括顺序结构、分支结构和循环结构。 这部分内容比较好理解(而且很琐碎),就不细说了。

在Java中,字符串操作十分常见。 字符串String最重要的特点是:String对象不可变。 这意味着String类中每一个看起来会修改String值的方法,实际上都是创建了一个全新的String对象,以包含修改后的字符串内容。 补充: 《Java编程思想》中提到:如果程序中有多个String对象,都包含相同的字符串序列,那么这些String对象都映射到同一块内存区域。

五、Java是面向对象的

什么是对象? 《Java编程思想》中提到:我们将问题空间中的元素及其在解空间中的表示称为“对象”。 我对这句话的理解是:一个对象就是一个问题和这个问题的解法的组合。 还是很难理解。 那么我们就还是通俗地理解为:万物皆为对象。 面向对象程序中的一切行为都由对象来完成。如果想要解决某个问题,就创建一个可以解决这个问题的对象,因此每个对象都是带着它的职责诞生的。

《Java编程思想》中提到:每个对象都有一个接口。 Java初学者很容易狭隘地把接口仅仅理解为抽象类型interface,事实上,接口是更加广泛的概念。 我对接口的理解是:接口实际上像是一种“刺激-反应”机制,它描述对象接收怎样的信息和对象收到信息后会返回怎样的信息,除此之外,对象的其它信息将会被隐藏——接口是建立在Java的封装机制之上才有意义的。 每个对象都支持这样的“刺激-反应”机制,在代码层面上,对象的接口就是这个对象可以调用的所有的方法,接口接收的信息就是方法名及方法的入参,接口返回的信息就是方法的返回值。再去理解“每个对象都有一个接口”这句话,大概就是,每个对象都有它可以调用的方法集。

注意在上面的代码层面的描述中,自始至终都没有提到过方法体,Java抽象类型interface中的抽象方法也确实是没有方法体的。 抽象过程的意图是站在一定的高度上去分析问题,因此对于程序设计具有重要的意义,在抽象与实现的关系上,原先是有了已实现的方法,再将这个方法抽象化(上升)。抽象类型interface将这个过程逆转了:先设计出接口,再将这个接口实现(落地)。这意味着由原先的对象选择接口,变成了接口选择对象,而面向对象的核心思想便是创建解决问题的对象,因此抽象类型interface体现了面向对象的思想。

每个对象可以调用的方法都是由这个对象的类(class)描述的。 类和对象的关系,就像是人类和张三的关系。人类是一套规范,张三是符合这套规范的个体,在面向对象的术语中,我们称张三是人类的一个实例。

《Java编程思想》中提到:因为类描述了具有相同特性(数据元素)和行为(功能)的对象集合,所以一个类实际上就是一个数据类型。 因此可以说,Java中的基本数据类型有8种,而数据类型有无数种。 程序员通过定义类来适应问题,而不再被迫只能使用基本的数据类型(适应计算机中的存储单元)来解决问题。

在整个编程过程中,程序员的工作就是定义类、创建对象、并引导对象解决实际问题。 (实际上还要销毁对象,不过JVM帮我们做了这件事)

六、用引用操纵对象

《Java编程思想》中提到:尽管一切都看作对象,但操纵的标识符实际上是对象的一个引用。 以下面的代码为例: Person zhangSan = new Person(); 其中的zhangSan就是引用,相当于对象的名称。在Java中,对象必须通过引用来操纵,就像一个人必须通过姓名或称号来指挥。

而对于基本数据类型: int i = 2; int型变量2不是对象,i也不应该被称作对象的引用。 这说明Java并不是纯粹的面向对象语言。

对于一般类型的对象,其引用标识的内存区域内存储的是对象所在的地址。 而对于8种基本数据类型的变量,其标识符标识的内存区域内存储的是真实的数值。 之所以会这样,是因为每种基本数据类型的变量占用存储空间的大小是确定的,而对象的大小是难以确定的。

七、封装

《Java编程思想》中提到:把数据和方法包装进类中,以及具体实现的隐藏,共同被称作是封装。 在Java中,最能体现封装思想的关键字是package。 Java中的四种访问权限从大到小依次是:public、protected、包访问权限(没有关键字)和private。 《Java编程思想》中提到:为了继承,一般的规则是将所有的数据成员都指定为private,将所有的方法指定为public。

控制对成员的访问权限有两个原因: 第一个原因是为了使用户不要碰触那些他们不该碰触的部分,用户实际上只应该使用为他们提供的接口。 第二个原因是为了让类库设计者可以更改类的内部工作方式,而不必担心这样会对客户端程序员产生重大的影响——访问权限控制确保不会有任何客户端程序员依赖于某个类的底层实现的任何部分。

八、代码复用

Java中主要有两种代码复用方式:组合和继承。

组合只需将对象引用置于新类中即可。 假如我们要创建计算机类Computer,而此时类库中已经有了主机类Host、显示器类Monitor、鼠标类Mouse、键盘类Keyboard,我们就可以让Computer类包含一个Host类型的成员属性、一个Monitor类型的成员属性、一个Mouse类型的成员属性和一个Keyboard类型的成员属性(一个类就是一种数据类型),而不用在Computer类中再写有关主机、显示器、鼠标、键盘的代码,达到代码复用的目的。

继承是使用关键字extends实现的。在一段继承关系中,导出类默认拥有它可以访问到的(public、protected修饰的、或者同包下包访问权限的)基类中的所有属性和方法。 在一段继承关系的基类和导出类之间,应当存在is-a或者is-like-a的语义关系,意思就是不要继承没有用的代码(何况Java只支持单继承)。在写代码时,程序员通常是自由的,你完全可以用可乐类Cola继承书籍类Book,而Cola类的实例永远也用不到Book类的接口,我们对此情形的理解是:可乐不是(is-not-a)书,并且可乐不像(is-not-like-a)书。不过,如果我们认为Cola类和Book类中都应该有makeHappy()方法,我们可以这么理解:可乐像书一样(is-like-a)使我们快乐。这样的代码复用才是有价值的。 is-a和is-like-a语义的区别在于,当导出类只覆盖了基类中的方法,而没有添加任何新方法的情况下,导出类和基类具有完全相同的接口,那么在任何场合下,导出类对象都可以完全替代一个基类对象,此时导出类和基类之间存在is-a语义。这是一种处理继承的理想方式,称为替代原则。

那么如何在组合与继承之间进行选择? 《Java编程思想》中提到:is-a(是一个)的关系是用继承来表达的,而has-a(有一个)的关系是用组合来表达的。

除了组合和继承,还有第三种代码复用方式是代理。Java并没有提供对它的直接支持。代理是继承与组合之间的中庸之道,它将一个成员对象置于所要构造的类中(就像组合),但与此同时在新类中暴露该成员对象的所有方法(就像继承)。 代理是常用的设计模式。

九、多态

我们举个例子说明什么是向上转型和向下转型: 我们认为“男孩儿是孩子,女孩儿也是孩子,孩子都会做游戏”,写成代码就是: Child child = new Child(); child.playGame(); Child boy = new Boy(); boy.playGame(); Child girl = new Girl(); girl.playGame(); 在第3行代码中,new Boy()创建的是一个Boy类型的对象,而“=”操作符左边是一个操纵Child类型对象的引用,将一个Boy对象交给一个操纵Child对象的引用来操纵时,发生向上转型,Boy对象转型成Child对象。 在第4行代码中,boy调用方法playGame()。boy操纵的是一个由Boy对象向上转型得到的Child对象,此时发生向下转型,这个对象重新转型成Boy对象,然后绑定Boy类中的playGame()方法而不是Child类中的playGame()方法。

将一个方法调用同一个方法主体关联起来被称作绑定。若在程序执行前进行绑定(由编译器和连接程序实现),叫作前期绑定。 多态技术的实现依赖于Java的后期绑定机制(也叫做动态绑定或运行时绑定),它的含义是在运行时根据对象的类型进行绑定。 在Java中,动态绑定是默认行为,通常情况下,动态绑定会自动发生。除非你加了static或final关键字。

《Java编程思想》中提到:多态是一项让程序员“将改变的事物与未变的事物分离开来”的重要技术。 多态技术极大提升了程序的可扩展性。

十、内部类

为什么要在Java中增加内部类这项语言特性呢? 《Java编程思想》中提到:使用内部类最吸引人的原因是:每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。 内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效地实现了多重继承。也就是说,内部类允许继承多个非接口类型(类或抽象类)。

十一、异常

《Java编程思想》中提到:异常的基本的概念是用名称代表发生的问题,并且异常的名称应该可以望文知意。

异常也是对象,所有的异常类都继承自Throwable类。 JDK中定义了大量的异常类型,每当某种已定义的异常发生时,会创建一个异常对象并抛出(throw new xxxException(“xxx xxx xxx”))。

Java异常分为被检查的异常(CheckedException)和不被检查的异常(UncheckedException)。在开发Java程序时,有时候编译器会强制你为某次调用操作做异常处理,这是因为被调用的方法中抛出了被检查的异常(如IOException)。 《Java编程思想》中提到:被检查的异常强制你在可能还没准备好处理错误的时候被迫加上catch子句,会导致“吞食则有害”的问题:catch而不处理,异常将会丢失。异常确实发生了,但“吞食”后它却完全消失了。 不被检查的异常是编译器不强制要求处理的异常,换言之,编译时不会检查。不被检查的异常一般包括各种运行时异常(RuntimeException),如空指针异常、数组越界异常等。

十二、Java I/O系统

《Java编程思想》中提到:流代表任何有能力产出数据的数据源对象或者是有能力接收数据的接收端对象。 ——流不是数据本身。

所谓的输入流和输出流是相对于内存来说的,输入流从磁盘到内存,输出流从内存到磁盘。

十三、泛型

《Java编程思想》中提到:泛型实现了参数化类型的概念,“泛型”这个术语的意思是:“适用于许多许多的类型”。 《Java编程思想》中提到:Java泛型是使用擦除来实现的,这意味着当你在使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。 《Java编程思想》中提到:擦除的核心动机是它使得泛化的客户端可以用非泛化的类库来使用,反之亦然,这经常被称为“迁移兼容性”。 (直白地讲,兼容JDK 1.5之前没有泛型的版本的类库,也是因为这个原因,Java泛型的能力大打折扣) 正如你想的那样,即使你从不使用泛型,仍然可以完成大部分的Java开发工作。那么为什么要往Java中引入泛型呢? 《Java编程思想》中提到:我相信被称为泛型的通用语言特性(并非必须是其在Java中的特定实现)的目的在于可表达性,二不仅仅是为了创建类型安全的容器。类型安全的容器是能够创建更通用的代码这一能力所带来的副作用。 《Java编程思想》中提到:泛型正如其名称所暗示的:它是一种方法,通过它可以编写出更“泛化”的代码,这些代码对于它们能够作用的类型具有更少的限制,因此单个的代码段可以应用到更多的类型上。

十四、其他的一些内容

有很多我没有写到的内容,包括注解、反射、枚举、并发等。

我看过一些关于注解和反射的资料,总觉得对它们是理解的,但不能灵活运用到自己的代码中。在对它们有更加深入的了解之前,暂时写不出特别有价值的内容。 枚举是一个比较简单的功能,没什么想说的。 并发的内容比较多,而《Java编程思想》也没有讲特别深,(目前我看来)这部分内容需要知道的知识点比需要理解的知识点更多,我没有试着去整理这部分内容。

《Java编程思想》是一本800多页的书,大概读到600多页的时候,切身感受到食之无味,弃之可惜。要啃这本书需谨慎。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/156271.html原文链接:https://javaforall.cn

0 人点赞