《Java编程思想》《Think in Java》笔记

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/starjuly/article/details/61427743

前言

这本书不适合初学者,这本书适合已经学过Java框架并做过一两个项目的同学来看,这本书对基础知识的理解非常透彻。我在看的时候常常有一种醍醐灌顶的感觉,常常为“原来是这样子的!”而激动,确实是一本非常好的书。我在看书时将书本我觉得重点的内容原封不动的摘录下来,给想看重点的同学看看,也为一些对某些基础概念不是很懂的同学给个了解渠道。

第1章     对象入门

1.1   抽象的进步

       所有编程语言的最终目的都是提供一种“抽象”方法。一种较有争议的说法是:解决问题的复杂程度直接取决于抽象的种类及质量。

       OOP允许我们根据问题来描述问题,而不是根据方案。

       “纯粹”的面向对象程序设计方法:

       (1)所有东西都是对象。可将对象想象成一种新型变量;它保存着数据,但可要求它对自身进行操作。理论上讲,可从要解决的问题身上提出所有概念性的组件,然后在程序中将其表达为一个对象。

       (2)程序是一大堆对象的组合;通过消息传递,各对象知道自己该做些什么。为了向对象发出请求,需向那个对象“发送一条消息”。更具体地讲,可将消息想象为一个调用请求,它调用的是从属于目标对象的一个子例程或函数。

       (3)每个对象都有自己的存储空间,可容纳其他对象。或者说,通过封装现有对象,可制作出新型对象。所以,尽管对象的概念非常简单,但在程序中却可达到任意高的复杂程度。

扫描二维码关注公众号,回复: 5476169 查看本文章

       (4)每个对象都有一种类型。根据语法,每个对象都是某个“类”的一个“实例”。其中,“类”(Class)是“类型”(Type)的同义词。一个类最重要的特征就是“能将什么消息发给它?”。

       (5)同一类所有对象都能接收相同的消息。这实际是别有含义的一种说法,大家不久便能理解。由于类型为“圆”(Circle)的一个对象也属于类型为“形状”(Shape)的一个对象,所以一个圆完全能接收形状消息。这意味着可让程序代码统一指挥“形状”,令其自动控制所有符合“形状”描述的对象,其中自然包括“圆”。这一特性称为对象的“可替换性”,OOP[莫星灿1] 最重要的概念之一

1.2 对象的接口

       当我们进行面向对象的程序设计时,面临的最大一项挑战性就是:如何在“问题空间”(问题实际存在的地方)的元素与“方案空间”(对实际问题进行建模的地方,如计算机)的元素之间建立理想的“一对一”对应或映射关系。

       如何利用对象完成真正有用的工作呢?必须有一种办法能向对象发出请求,令其做一些实际的事情,比如完成一次交易、在屏幕上画一些东西或者打开一个开关等等。每个对象仅能接受特定的请求。我们向对象发出的请求是通过它的“接口”(Interface)定义的,对象的“类型”或“类”则规定了它的接口形式。“类型”与“接口”的等价或对应关系是面向对象程序设计的基础。

       以电灯泡为例:

       在这个例子中,类型/类的名称是 Light,可向 Light 对象发出的请求包括包括打开(on)、关闭(off)、变得更明亮(brighten)或者变得更暗淡(dim)。通过简单地声明一个名字(lt),我们为 Light 对象创建了一个句柄[莫星灿2] 。然后用new关键字新建类型为 Light 的一个对象。再用等号将其赋给句柄。为了向对象发送一条消息,我们列出句柄名(lt),再用一个句点符号(.)把它同消息名称(on)连接起来。[莫星灿3] 从中可以看出,使用一些预先定义好的类时,我们在程序里采用的代码是非常简单和直观的。

1.3 实现方案的隐藏

       从根本上说,大致有两方面的人员涉足面向对象的编程:“类创建者”(创建新数据类型的人)以及客户程序员[莫星灿4] (在自己的应用程序中采用现成数据类型的人)。

       “接口”Interface)规定了可对一个特定的对象发出哪些请求。然而,必须在某个地方存在着一些代码,以便满足这些请求。这些代码与那些隐藏起来的数据便叫作“隐藏的实现”。站在程式化程序编写(Procedural Programming)的角度,整个问题并不显得复杂。一种类型含有与每种可能的请求关联起来的函数。一旦向对象发出一个特定的请求,就会调用那个函数。我们通常将这个过程总结为向对象“发送一条消息”(提出一个请求)。对象的职责就是决定如何对这条消息作出反应(执行相应的代码)。

       Java采用三个显式(明确)关键字以及一个隐式(暗示)关键字来设置类边界public,private,protected 以及暗示性的friendly[莫星灿5] 。若未明确指定其他关键字,则默认为后者。这些关键字的使用和含义都是相当直观的,它们决定了谁能使用后续的定义内容。“public”(公共)意味着后续的定义任何人均可使用。而在另一方面,“private”(私有)意味着除您自己、类型的创建者以及那个类型的内部函数成员,其他任何人都不能访问后续的定义信息。private在您与客户程序员之间竖起了一堵墙。若有人试图访问私有成员,就会得到一个编译期错误。“friendly”(友好的)涉及“包装”或“封装”(Package)的概念——即Java 用来构建库的方法。若某样东西是“友好的”,意味着它只能在这个包装的范围内使用(所以这一访问级别有时也叫作“包装访问”)。“protected”(受保护的)与“private”相似,只是一个继承的类可访问受保护的成员,但不能访问私有成员。继承的问题不久就要谈到。

1.4 方案的重复使用

       许多人认为代码或设计方案的重复使用是面向对象的程序设计提供的最伟大的一种杠杆。

       为重复使用一个类,最简单的办法是仅直接使用那个类的对象。但同时也能将那个类的一个对象置入一个新类。我们把这叫作“创建一个成员对象”。新类可由任意数量和类型的其他对象构成。无论如何,只要新类达到了设计要求即可。这个概念叫作“组织”——在现有类的基础上组织一个新类。有时,我们也将组织称作“包含”关系,比如“一辆车包含了一个变速箱”。

[莫星灿6] 1.5 继承:重新使用接口

       使用继承时,相当于创建了一个新类。这个新类不仅包含了现有类型的所有成员(尽管private 成员被隐藏起来,且不能访问),但更重要的是,它复制了基础类的接口。也就是说,可向基础类的对象发送的所有消息亦可原样发给衍生类的对象。根据可以发送的消息,我们能知道类的类型。这意味着衍生类具有与基础类相同的类型!为真正理解面向对象程序设计的含义,首先必须认识到这种类型的等价关系。

1.5.1 改善基础类

       为改善一个函数,只需为衍生类的函数建立一个新定义即可。我们的目标是:“尽管使用的函数接口未变,但它的新版本具有不同的表现”。

1.5.2 等价与类似关系

       但在许多时候,我们必须为衍生类型加入新的接口元素。所以不仅扩展了接口,也创建了一种新类型。这种新类型仍可替换成基础类型,但这种替换并不是完美的,因为不可在基础类里访问新函数。我们将其称作“类似”关系;新类型拥有旧类型的接口,但也包含了其他函数,所以不能说它们是完全等价的。

1.6 多形对象的互换使用

       通常,继承最终会以创建一系列类收场,所有类都建立在统一的接口基础上。我们用一幅颠倒的树形图来阐明这一点(注释⑤):

⑤:这儿采用了“统一记号法”,本书将主要采用这种方法。

       对这样的一系列类,我们要进行的一项重要处理就是将衍生类的对象当作基础类的一个对象对待。这一点是非常重要的,因为它意味着我们只需编写单一的代码,令其忽略类型的特定细节,只与基础类打交道。这样一来,那些代码就可与类型信息分开。所以更易编写,也更易理解。此外,若通过继承增添了一种新类型,如“三角形”,那么我们为“几何形状”新类型编写的代码会象在旧类型里一样良好地工作。所以说程序具备了“扩展能力”,具有“扩展性”。

       以上面的例子为基础,假设我们用 Java 写了这样一个函数:

Void doStuff(Shape s){

  s.erase();

  //…

  s.draw();

}

       这个函数可与任何“几何形状”(Shape)通信,所以完全独立于它要描绘(draw)和删除(erase)的任何特定类型的对象。如果我们在其他一些程序里使用 doStuff()函数:

       那么对doStuff()的调用会自动良好地工作,无论对象的具体类型是什么。

       这实际是一个非常有用的编程技巧。请考虑下面这行代码:

       doStuff(c);

       此时,一个 Circle(圆)句柄传递给一个本来期待 Shape(形状)句柄的函数。由于圆是一种几何形状,所以doStuff()能正确地进行处理。也就是说,凡是 doStuff()能发给一个 Shape的消息,Circle也能接收。所以这样做是安全的,不会造成错误。

       我们将这种把衍生类型当作它的基本类型处理的过程叫作“Upcasting”(上溯造型[莫星灿7] )。其中,“cast”(造型)是指根据一个现成的模型创建;而“Up”(向上)表明继承的方向是从“上面”来的——即基础类位于顶部,而衍生类在下方展开。所以,根据基础类进行造型就是一个从上面继承的过程,即“Upcasting”。在面向对象的程序里,通常都要用到上溯造型技术。这是避免去调查准确类型的一个好办法。请看看doStuff()里的代码:

s.erase();

// ...

s.draw();

       注意它并未这样表达:“如果你是一个Circle,就这样做;如果你是一个Square,就那样做;等等”。若那样编写代码,就需检查一个Shape 所有可能的类型,如圆、矩形等等。这显然是非常麻烦的,而且每次添加了一种新的 Shape类型后,都要相应地进行修改。在这儿,我们只需说:“你是一种几何形状,我知道你能将自己删掉,即erase();请自己采取那个行动,并自己去控制所有的细节吧。”

1.6.1 动态绑定

       将一条消息发给对象时,如果并不知道对方的具体类型是什么,但采取的行动同样是正确的,这种情况就叫作“多形性”(Polymorphism)。对面向对象的程序设计语言来说,它们用以实现多形性的方法叫作“动态绑定[莫星灿8] 。编译器和运行期系统会负责对所有细节的控制;我们只需知道会发生什么事情,而且更重要的是,如何利用它帮助自己设计程序。

1.6.2 抽象的基础类和接口

       设计程序时,我们经常都希望基础类只为自己的衍生类提供一个接口。也就是说,我们不想其他任何人实际创建基础类的一个对象,只对上溯造型成它,以便使用它们的接口。[莫星灿9] 为达到这个目的,需要把那个类变成“抽象”的——使用abstract 关键字。若有人试图创建抽象类的一个对象,编译器就会阻止他们。这种工具可有效强制实行一种特殊的设计。

       亦可用abstract 关键字描述一个尚未实现的方法——作为一个“”使用,指出:“这是适用于从这个类继承的所有类型的一个接口函数,但目前尚没有对它进行任何形式的实现。”抽象方法也许只能在一个抽象类里创建。继承了一个类后,那个方法就必须实现,否则继承的类也会变成“抽象”类。通过创建一个抽象方法,我们可以将一个方法置入接口中,不必再为那个方法提供可能毫无意义的主体代码。

       interface(接口)关键字将抽象类的概念更延伸了一步,它完全禁止了所有的函数定义。“接口”是一种相当有效和常用的工具。另外如果自己愿意,亦可将多个接口都合并到一起(不能从多个普通class 或abstract class 中继承)。

1.7 对象的创建和存在时间

从技术角度说,OOP(面向对象程序设计)只是涉及抽象的数据类型、继承以及多形性,但另一些问题也可能显得非常重要。本节将就这些问题进行探讨。

最重要的问题之一是对象的创建及破坏方式。对象需要的数据位于哪儿,如何控制对象的“存在时间”呢?针对这个问题,解决的方案是各异其趣的。

第二个方法是在一个内存池中动态创建对象,该内存池亦叫“”或者“内存堆”。若采用这种方式,除非进入运行期,否则根本不知道到底需要多少个对象,也不知道它们的存在时间有多长,以及准确的类型是什么。这些参数都在程序正式运行时才决定的。若需一个新对象,只需在需要它的时候在内存堆里简单地创建它即可。由于存储空间的管理是运行期间动态进行的,所以在内存堆里分配存储空间的时间比在堆栈里创建的时间长得多(在堆栈里创建存储空间一般只需要一个简单的指令,将堆栈指针向下或向下移动即可)。由于动态创建方法使对象本来就倾向于复杂,所以查找存储空间以及释放它所需的额外开销不会为对象的创建造成明显的影响。除此以外,更大的灵活性对于常规编程问题的解决是至关重要的。

1.7.1 集合与继承器

针对一个特定问题的解决,如果事先不知道需要多少个对象,或者它们的持续时间有多长,那么也不知道如何保存那些对象。既然如此,怎样才能知道那些对象要求多少空间呢?事先上根本无法提前知道,除非进入运行期。

继续器”(Iterator),它属于一种对象,负责选择集合内的元素,并把它们提供给继

承器的用户。作为一个类,它也提供了一级抽象。利用这一级抽象,可将集合细节与用于访问那个集合的代码隔离开。通过继承器的作用,集合被抽象成一个简单的序列。继承器允许我们遍历那个序列,同时毋需关心基础结构是什么——换言之,不管它是一个矢量、一个链接列表、一个堆栈,还是其他什么东西。这样一来,我们就可以灵活地改变基础数据,不会对程序里的代码造成干扰。

1.7.2 单根结构

在面向对象的程序设计中,由于C++的引入而显得尤为突出的一个问题是:所有类最终是否都应从单独一个基础类继承。在Java 中(与其他几乎所有OOP语言一样),对这个问题的答案都是肯定的,而且这个终级基础类的名字很简单,就是一个“Object[莫星灿10] ”。

       单根结构中的所有对象都有一个通用接口,所以它们最终都属于相同的类型。

       单根结构中的所有对象(比如所有 Java 对象)都可以保证拥有一些特定的功能。

       利用单根结构,我们可以更方便地实现一个垃圾收集器。与此有关的必要支持可安装于基础类中,而垃圾收集器可将适当的消息发给系统内的任何对象。如果没有这种单根结构,而且系统通过一个句柄来操纵对象,那么实现垃圾收集器的途径会有很大的不同,而且会面临许多障碍。

1.7.3 集合库与方便使用集合

由于集合是我们经常都要用到的一种工具,所以一个集合库是十分必要的,它应该可以方便地重复使用。这样一来,我们就可以方便地取用各种集合,将其插入自己的程序。Java 提供了这样的一个库,尽管它在Java1.0和 1.1中都显得非常有限。

1. 下溯造型与模板/通用性

为了使这些集合能够重复使用,或者“再生”,Java 提供了一种通用类型,以前曾把它叫作“Object”。单根结构意味着、所有东西归根结底都是一个对象”!所以容纳了Object 的一个集合实际可以容纳任何东西。这使我们对它的重复使用变得非常简便。

为使用这样的一个集合,只需添加指向它的对象句柄即可,以后可以通过句柄重新使用对象。但由于集合只能容纳Object,所以在我们向集合里添加对象句柄时,它会上溯造型成 Object,这样便丢失了它的身份或者标识信息。再次使用它的时候,会得到一个Object 句柄,而非指向我们早先置入的那个类型的句柄。所以怎样才能归还它的本来面貌,调用早先置入集合的那个对象的有用接口呢?

在这里,我们再次用到了造型(Cast)。但这一次不是在分级结构中上溯造型成一种更“通用”的类型。而是下溯造型成一种更“特殊”的类型。这种造型方法叫作“下溯造型[莫星灿11] ”(Downcasting)。举个例子来说,我们知道在上溯造型的时候,Circle(圆)属于Shape(几何形状)的一种类型,所以上溯造型是安全的。但我们不知道一个Object到底是 Circle 还是Shape,所以很难保证下溯造型的安全进行,除非确切地知道自己要操作的是什么。

1.7.4 清除时的困境:由谁负责清除?

在Java 中,垃圾收集器在设计时已考虑到了内存的释放问题(尽管这并不包括清除一个对象涉及到的其他方面)。垃圾收集器“知道”一个对象在什么时候不再使用,然后会自动释放那个对象占据的内存空间。采用这种方式,另外加上所有对象都从单个根类Object 继承的事实,而且由于我们只能在内存堆中以一种方式创建对象,所以Java 的编程要比 C++的编程简单得多。我们只需要作出少量的抉择,即可克服原先存在的大量障碍。

1.    垃圾收集器对效率及灵活性的影响

既然这是如此好的一种手段,为什么在C++里没有得到充分的发挥呢?我们当然要为这种编程的方便性付出

一定的代价,代价就是运行期的开销。正如早先提到的那样,在C++中,我们可在堆栈中创建对象。在这种情况下,对象会得以自动清除(但不具有在运行期间随心所欲创建对象的灵活性)。在堆栈中创建对象是为对象分配存储空间最有效的一种方式,也是释放那些空间最有效的一种方式。在内存堆(Heap)中创建对象可能要付出昂贵得多的代价。如果总是从同一个基础类继承,并使所有函数调用都具有“同质多形”特征,那么也不可避免地需要付出一定的代价。但垃圾收集器是一种特殊的问题,因为我们永远不能确定它什么时候启动或者要花多长的时间。这意味着在Java 程序执行期间,存在着一种不连贯的因素。所以在某些特殊的场合,我们必须避免用它——比如在一个程序的执行必须保持稳定、连贯的时候(通常把它们叫作“实时程序”,尽管并不是所有实时编程问题都要这方面的要求)。

1.8 违例控制:解决错误

“违例控制”将错误控制方案内置到程序设计语言中,有时甚至内建到操作系统内。这里的“违例”(Exception)属于一个特殊的对象,它会从产生错误的地方“扔”或“掷”出来。随后,这个违例会被设计用于控制特定类型错误的“违例控制器”捕获。在情况变得不对劲的时候,可能有几个违例控制器并行捕获对应的违例对象。由于采用的是独立的执行路径,所以不会干扰我们的常规执行代码。这样便使代码的编写变得更加简单,因为不必经常性强制检查代码。除此以外,“掷”出的一个违例不同于从函数返回的错误值,也不同于由函数设置的一个标志。那些错误值或标志的作用是指示一个错误状态,是可以忽略的。但违例不能被忽略,所以肯定能在某个地方得到处置。最后,利用违例能够可靠地从一个糟糕的环境中恢复。此时一般不需要退出,我们可以采取某些处理,恢复程序的正常执行。显然,这样编制出来的程序显得更加可靠。[莫星灿12] 

1.9 多线程

有些时候,中断对那些实时性很强的任务来说是很有必要的。但还存在其他许多问题,它们只要求将问题划分进入独立运行的程序片断中,使整个程序能更迅速地响应用户的请求。在一个程序中,这些独立运行的片断叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理”。多线程处理一个常见的例子就是用户界面。利用线程,用户可按下一个按钮,然后程序会立即作出响应,而不是让用户等待程序完成了当前任务以后才开始响应。

最开始,线程只是用于分配单个处理器的处理时间的一种工具。但假如操作系统本身支持多个处理器,那么每个线程都可分配给一个不同的处理器,真正进入“并行运算”状态。从程序设计语言的角度看,多线程操作最有价值的特性之一就是程序员不必关心到底使用了多少个处理器。程序在逻辑意义上被分割为数个线程;假如机器本身安装了多个处理器,那么程序会运行得更快,毋需作出任何特殊的调校。根据前面的论述,大家可能感觉线程处理非常简单。但必须注意一个问题:共享资源!如果有多个线程同时运行,而且它们试图访问相同的资源,就会遇到一个问题。举个例子来说,两个进程不能将信息同时发送给一台打印机。为解决这个问题,对那些可共享的资源来说(比如打印机),它们在使用期间必须进入锁定状态。所以一个线程可将资源锁定,在完成了它的任务后,再解开(释放)这个,使其他线程可以接着使用同样的资源。

Java 的多线程机制已内建到语言中,这使一个可能较复杂的问题变得简单起来。对多线程处理的支持是在对象这一级支持的,所以一个执行线程可表达为一个对象。Java 也提供了有限的资源锁定方案。它能锁定任何对象占用的内存(内存实际是多种共享资源的一种),所以同一时间只能有一个线程使用特定的内存空间。为达到这个目的,需要使用synchronized关键字。其他类型的资源必须由程序员明确锁定,这通常要求程序员创建一个对象,用它代表一把锁,所有线程在访问那个资源时都必须检查这把锁

1.10 永久性

Java 1.1 提供了对“有限永久性”的支持,这意味着我们可将对象简单地保存到磁盘上,以后任何时间都可取回。之所以称它为“有限”的,是由于我们仍然需要明确发出调用,进行对象的保存和取回工作。这些工作不能自动进行。在Java 未来的版本中,对“永久性”的支持有望更加全面。

1.11 Java 和因特网

Java 除了可解决传统的程序设计问题以外,还能解决World Wide Web(万维网)上的编程问题。

1.11.1 什么是 Web ?

1. 客户机/服务器计算

客户机/服务器系统的基本思想是我们能在一个统一的地方集中存放信息资源。一般将数据集中保存在某个数据库中,根据其他人或者机器的请求将信息投递给对方。客户机/服务器概述的一个关键在于信息是“集中存放”的。所以我们能方便地更改信息,然后将修改过的信息发放给信息的消费者。将各种元素集中到一起,信息仓库、用于投递信息的软件以及信息及软件所在的那台机器,它们联合起来便叫作“服务器”(Server)。而对那些驻留在远程机器上的软件,它们需要与服务器通信,取回信息,进行适当的处理,然后在远程机器上显示出来,这些就叫作“客户”(Client)。[莫星灿13] 

这样看来,客户机/服务器的基本概念并不复杂。这里要注意的一个主要问题是单个服务器需要同时向多个客户提供服务。在这一机制中,通常少不了一套数据库管理系统,使设计人员能将数据布局封装到表格中,以获得最优的使用。除此以外,系统经常允许客户将新信息插入一个服务器。这意味着必须确保客户的新数据不会与其他客户的新数据冲突,或者说需要保证那些数据在加入数据库的时候不会丢失(用数据库的术语来说,这叫作“事务处理”)。客户软件发生了改变之后,它们必须在客户机器上构建、调试以及安装。所有这些会使问题变得比我们一般想象的复杂得多。另外,对多种类型的计算机和操作系统的支持也是一个大问题。最后,性能的问题显得尤为重要:可能会有数百个客户同时向服务器发出请求。所以任何微小的延误都是不能忽视的。为尽可能缓解潜伏的问题,程序员需要谨慎地分散任务的处理负担。一般可以考虑让客户机负担部分处理任务,但有时亦可分派给服务器所在地的其他机器,那些机器亦叫作“中间件[莫星灿14] ”(中间件也用于改进对系统的维护)。

2. Web是一个巨大的服务器

Web实际就是一套规模巨大的客户机/服务器系统。但它的情况要复杂一些,因为所有服务器和客户都同时存在于单个网络上面。但我们没必要了解更进一步的细节,因为唯一要关心的就是一次建立同一个服务器的连接,并同它打交道(即使可能要在全世界的范围内搜索正确的服务器)。

最开始的时候,这是一个简单的单向操作过程。我们向一个服务器发出请求,它向我们回传一个文件,由于本机的浏览器软件(亦即“客户”或“客户程序”)负责解释和格式化,并在我们面前的屏幕上正确地显示出来。但人们不久就不满足于只从一个服务器传递网页。他们希望获得完全的客户机/服务器能力,使客户(程序)也能反馈一些信息到服务器。比如希望对服务器上的数据库进行检索,向服务器添加新信息,或者下一份订单等等(这也提供了比以前的系统更高的安全要求)。[莫星灿15] 在Web的发展过程中,我们可以很清晰地看出这些令人心喜的变化。

Web浏览器的发展终于迈出了重要的一步:某个信息可在任何类型的计算机上显示出来,毋需任何改动。然而,浏览器仍然显得很原始,在用户迅速增多的要求面前显得有些力不从心。它们的交互能力不够强,而且对服务器和因特网都造成了一定程度的干扰。这是由于每次采取一些要求编程的操作时,必须将信息反馈回服务器,在服务器那一端进行处理。所以完全可能需要等待数秒乃至数分钟的时间才会发现自己刚才拼错了一个单词。由于浏览器只是一个纯粹的查看程序,所以连最简单的计算任务都不能进行(当然在另一方面,它也显得非常安全,因为不能在本机上面执行任何程序,避开了程序错误或者病毒的骚扰)。

为解决这个问题,人们采取了许多不同的方法。最开始的时候,人们对图形标准进行了改进,使浏览器能显示更好的动画和视频。为解决剩下的问题,唯一的办法就是在客户端(浏览器)内运行程序。这就叫作“客户端编程”,它是对传统的“服务器端编程”的一个非常重要的拓展。[莫星灿16] 


 [莫星灿1]OOP: 面向对象程序设计

 [莫星灿2]终于知道句柄是什么鬼了!

 [莫星灿3]调用方法的原理

 [莫星灿4]不太理解

 [莫星灿5]较形象地理解这四个关键字

 [莫星灿6]同感

 [莫星灿7]也叫向上转型

 [莫星灿8]“什么人说什么话”道理类似

 [莫星灿9]使用抽象的原因

[莫星灿10]所有类都继承Object

 [莫星灿11]向下转型

 [莫星灿12]Exception Try{ }catch{ }的理解

 [莫星灿13]形象生动的诠释何为服务器和客户,BC端

 [莫星灿14]大公司如阿里这种应该对这个很有研究了

 [莫星灿15]早期浏览器形态

 [莫星灿16]B/S端


猜你喜欢

转载自blog.csdn.net/starjuly/article/details/61427743
今日推荐