Java核心技术——卷I——异常、断言和日志

当程序在运行期间由于出现错误而使得某些操作没有完成,程序应该做到:返回到一种安全状态,并能够让用户执行其他的命令;或者允许用户保存所有工作的结果,并以妥善的方式终止程序。异常处理的任务就是将控制权从产生错误的地方转移到能够处理这种情况的错误处理器。一般在程序中可能出现的问题有以下几个:

1)用户输入错误。例如输入一个URL,而这个URL语法却不对。如果代码没有对此检查,网路层就会报错。

2)硬件设备错误。例如,打印机在打印过程中可能没纸了。

3)物理内存限制。例如,磁盘已满,内存空间不够。

4)代码错误。例如方法出错,而对于方法中的错误,传统的做法是返回一个特殊的错误码,由调用分析。除此之外的还有一种表示错误状况的常用返回值是null引用。

Java的做法与传统的做法不同,在这种情况下,方法并不会返回值,而是抛出(throw)一个封装了错误信息的对象。需要注意的是,这个方法会立即退出,并不会返回正常值(或任何值)。此外,也不会从调用这个方法的代码继续执行,取而代之的是,异常处理机制开始搜索能够处理这种异常状况的异常处理器(exception handle)。

Java程序设计语言中,异常对象都是派生于Throwable类的一个类实例。而且如果Java中内置的异常类不能满足需求,还可以创建自己的异常类。如下是Java异常层次结构的一个简化示意图:

one

需要注意的是,所有的异常都是由Throwable继承而来,但在下一层立即分解为两个分支:Error和Exception。Error类层次结构描述了Java运行时系统的内部错误和资源耗尽错误。而Exception又分解为两个分支:一个分支派生于RuntimeException;另一个分支包含其他异常。一般规则是:由编程错误导致的异常属于RuntimeException;如果程序本身没有问题,但由于像I/O错误这类问题导致的异常属于其他异常。

派生于RuntimeException的异常包括以下问题:

1)错误的强制类型转换。

2)数组访问越界。

3)访问null指针。

不是派生于RuntimeException异常包括:

1)试图超越文件末尾继续读取数据。

2)试图打开一个不存在的文件。

3)试图根据给定的字符串查找Class对象,而这个字符串表示的类并不存在。

Java语言规范将派生于Error类或RuntimeException类的所有异常称为非检查型(unchecked)异常,所有其他的异常称为检查型(checked)异常。C++有两个基本的异常类,一个是runtime_error;另一个是logic_error。logic_error类相当于Java中的RuntimeException,也表示程序中的逻辑错误;runtime_error类是所有由于不可预测的原因所引发的异常的基类。它相当于Java中的非RuntimeException类型的异常。

如果遇到了无法处理的情况,Java方法可以抛出一个异常。这个道理很简单:方法不仅需要告诉编译器将要返回什么值,还要告诉编译器有可能发生什么错误。当自己编写方法时需要记住在遇到下面4种情况时会抛出异常:

1)调用了一个抛出检查型异常的方法。

2)检测到一个错误,并且利用throw语句抛出一个检查型异常。

3)程序出现错误。

4)Java虚拟机或运行时库出现内部错误。

应该通过方法首部的异常规范(exception specification)声明这个方法可能抛出异常。如果一个方法有可能抛出多个检查型异常,那么就必须在方法的首部列出所有的异常类。每个异常类之间用逗号隔开。但是,不需要声明Java内部的错误,即从Error继承的异常。同样,也不应该声明从RuntimeException继承的那些非检查型异常。

总之,一个方法必须声明所有可能抛出的检查型异常,而非检查型异常要么在控制之外(Error),要么是由从一开始就应该避免的情况所导致的(RuntimeException)。如果方法没有声明所有可能发生的检查型异常,编译器就会发出一个错误信息。

如果在子类中覆盖了超类的一个方法,子类方法中声明的检查型异常不能比超类方法中声明的异常更通用(子类方法可以抛出特定的异常,或者根本不抛出任何异常)。特别说明的是,如果超类方法没有抛出任何检查型异常,子类也不能抛出任何检查型异常。

如果类中的一个方法声明它会抛出一个异常类,而这个异常是某个特定类的实例,那么这个方法抛出的异常可能属于这个类,也可能属于这个类的任意一个子类。Java中的throws说明符与C++中的throw说明符基本类似,但有一个重要区别。在C++中,throw说明符在运行时执行,而不是在编译时执行。也就是说,C++编译器将不处理任何异常规范。但是,如果函数抛出的异常没有出现在throw列表中,就会调用unexpected函数,默认情况下,程序会终止。另外,在C++中,如果没有给出throw说明,函数可能会抛出任何异常。而在Java中,没有throws说明符的方法将根本不能抛出任何检查型异常。

如果一个已有的异常类能够满足要求,抛出这个异常就非常容易。在这种情况下:

1)找到一个合适的异常类。

2)创建这个类的一个对象。

3)将对象抛出。

在C++与Java中,抛出异常的过程基本相同,只有一点微小的差别。Java中,只能抛出Throwable子类的对象,而在C++中,却可以抛出任何类型的值。

与抛出异常的相关的API 如下:

two

如果发生了某个异常,但没有在任何地方捕获这个异常,程序就会终止,并在控制台上打印一个消息,其中包括这个异常的类型和一个堆栈轨迹。要想捕获一个异常,需要设置try/catch语句。最简单的try语句块如下所示:

three

如果try语句块中的任何代码抛出了catch子句中指定的一个异常类,那么:

1)程序将跳过try语句块的其余代码。

2)程序将执行catch子句中的处理器代码。

如果try语句块中的代码没有抛出任何异常,那么程序将跳过catch子句。如果方法中的任何代码抛出了catch子句中没有声明的一个异常类型,那么这个方法就会立即退出(希望它的调用者为这种类型的异常提供了catch子句)。

如果想传播一个异常,就必须在方法的首部添加一个throws说明符,提醒调用者这个方法可能会抛出异常。编译器会严格地执行throws说明符。如果调用了一个抛出检查型异常的方法,就必须处理这个异常,或者继续传递这个异常。而一般经验是,要捕获那些知道如何处理的异常,而继续传播那些不知道怎么样处理的异常。

如果编写一个方法覆盖超类的方法,而这个超类方法没有抛出异常,你就必须捕获你的方法代码中出现的每一个检查型异常。不允许在子类的throws说明符中出现超类方法未列出的异常类。

在一个try语句块中可以捕获多个异常类型,并对不同的类型做出不同的处理。要为每个异常类型使用一个单独的catch子句。异常对象可能包含有关异常性质的信息。要想获得这个对象的更多信息,可以使用getMessage()方法得到详细的错误信息(如果有的话),或者使用e.getClass().getName()得到异常对象的实际类型。

在Java 7中,同一个catch子句可以捕获多个异常类型。只有当捕获的异常类型彼此之间不存在子类关系时才需要这个特性。捕获多个异常时,异常变量隐含为final变量。

可以在catch子句中抛出一个异常。

不管是否有异常被捕获,finally子句中的任何代码都会执行。try语句可以没有finally子句,而没有catch子句。当finally子句包含return语句时,有可能产生意想不到的结果。假设利用return语句从try语句块中间退出。在方法返回前,会执行finally字句快。如果finally快也有一个return语句,这个返回值将会遮蔽原来的返回值。finally子句的体主要用于清理资源。不要把改变控制流的语句(return,throw,break,continu)放在finally子句中

try-with-resources语句(带资源的try语句)的最简形式为:

four

try快退出时,会自动调用res.close()。在Java 9中,可以在try首部中提供之前声明的事实最终变量。try-with-resources语句自身也可以有catc子句,甚至还可以有一个finally子句。这些子句会在关闭资源之后执行。

堆栈轨迹(stack trace)是程序执行过程中某个特定点上所有挂起的方法调用的一个列表。当Java程序因为一个未捕获的异常而终止时,就会显示堆栈轨迹。可以调用Throwable类的printStackTrace方法访问堆栈轨迹的文本描述信息。一个更灵活的方法是使用StackWalker类,它会生成一个StackWalker.StakFrame实例流,其中每个实例分别描述一个栈帧。

与访问堆栈轨迹的相关API如下:

five

six

使用异常的一些技巧如下:

1)异常处理不能代替简单的测试。因此使用异常的基本规则是:只在异常情况下使用异常

2)不要过分地细化异常。将正常处理与错误处理分开。

3)充分利用异常层次结构。

4)不要压制异常。

5)在检测错误时,“苛刻”要比放任更好。

6)不要羞于传递异常。

其中5,6可以归纳为“早抛出,晚捕获”。

断言机制允许在测试期间向代码中插入一些检查,而在生产代码中会自动删除这些检查。Java语言引入了关键字assert。改关键字有两种形式:

seven

这两个语句都会计算条件,如果结果为false,则抛出一个AssertionError异常。在第二个语句中,表达式将传入AssertionError对象的构造器,并转换成一个消息字符串。“表达式”(expression)部分的唯一目的是产生一个消息字符串。

在默认情况下,断言是禁用的。可以在运行程序时用 -enableassertions 或 -ea选项启用断言。要注意的是,不必重新编译程序来启用或禁用断言。启用或禁用断言是类加载器的功能。禁用断言时,类加载器会去除断言代码,因此,不会降低程序运行的速度。也可以在某个类或整个包中启用断言。也可以用选项 -disableassertions 或 -da 在某个特定类和包中禁用断言。启用和禁用所有断言的 -ea 和 -da 开关不能应用到那些没有类加载器的“系统类”上,对于这些系统类,需要使用 -enablesystemassertions/ -eas开关启用断言。

在Java语言中,给出了3种处理系统错误的机制:

1)抛出一个异常。

2)日志。

3)使用断言。

而什么时候应该选择使用断言,要清楚以下几点:

1)断言失败是致命的、不可恢复的错误。

2)断言检查只是在开发和测试阶段打开。

因此,不应该使用断言向程序的其他部分通知发生了可恢复性的错误,或者,不应该利用断言与程序用户沟通问题。断言只应该用于在测试阶段确定程序内部错误的位置。

与断言内容相关的API如下:

eight

nine

断言是一种测试和调试阶段使用的战术性工具;与之不同的是,日志是一种在程序整个声明周期都可以使用的战略性工具。

日志API的主要优点如下:

1)可以很容易地取消全部日志记录。或者仅仅取消某个级别以下的日志,而且可以很容易地再次打开日志。

2)可以很简单地禁止日志记录,因此,将这些日志代码留在程序中的开销很小。

3)日志记录可以被定向到不同的处理器。如在控制台显示、写至文件,等等。

4)日志记录器和处理器都可以对记录进行过滤。过滤器可以根据过滤器实现器指定的标准丢弃那些无用的记录。

5)日志记录可以采用不同的方式格式化。如,纯文本或XML。

6)应用程序可以使用多个日志记录器,它们使用与包名类似的有层次结构的名字。

7)日志系统的配置由配置文件控制。

在Java 9种,Java平台有一个单独的轻量级日志系统,它不依赖于java.logging模块(这个模块包含标准Java日志框架)。这个系统只用于Java API。如果有java.logging模块,日志消息会自动地转发给它。

要生成简单的日志记录,可以使用全局日志记录器(global logger)并调用其info方法。可以调用getLogger方法创建或获取日志记录器。未被任何变量引用的日志记录器可以能会被垃圾回收。为了防止这种情况发生,用静态变量存储日志记录器的引用。

与包名类似,日志记录器也具有层次结构,而且还更强。日志记录器的父与子之间将共享某些属性。通常,日志有以下7个级别:

1)SEVERE。

2)WARNING。

3)INFO。

4)CONFIG。

5)FINE。

6)FINER。

7)FINEST。

默认情况下,实际上只记录前3个级别,也可以设置一个不同的级别。另外,还可以使用Level.ALL开启所有级别的日志记录,或者使用Level.OFF关闭所有级别的日志记录。默认的系统配置会记录INFO或更高级别的所有日志,因此,应该使用CONFIG、FIFE、FINER和FINEST级别来记录那些有助于诊断但对用户意义不大的调试信息。默认的日志记录将显示根据调用堆栈得出的包含日志调用的类名和方法名。记录日志的常见用途是记录那些预料之外的异常。

可以通过编辑配置文件来修改日志系统的各个属性。默认情况下,配置文件位于:conf/logging.properties(或者在Java 9之前,位于 jre/lib/logging.properties)。

本地化的应用程序包含资源包中的本地特定信息。资源包包含一组映射,分别对应各个本地环境。一个程序可以包含多个资源包,每个资源包都有一个名字。要想为资源包增加映射,需要对应每个本地环境提供一个文件。请求一个日志记录器时,可以指定一个资源包,然后为日志消息指定资源包的键,而不是实际的日志消息字符串。通常需要在本地化的消息中增加一些参数,因此,消息可能包括占位符{0}、{1}等。或者在Java 9中,可也在logrb方法中指定资源包对象(而不是名字)。

默认情况下,日志记录器将记录发送到ConsoleHandler,并由它输出到System.err流。与日志记录器一样,处理器也有日志级别。对于一个要记录的日志记录,它的日志级别必须高于日志记录器和处理器二者的阈值。默认情况下,日志记录器将记录发送到自己的处理器和父日志记录器的处理器。日志管理器配置文件中的参数如下:

ten

日志记录文件的模式变量描述如下:

eleven

如果多个应用程序(或者同一个应用程序的多个副本)使用同一个日志文件,就应该开启append标志。另外,应该在文件模式中使用%u,以便每个应用程序创建日志的唯一副本。

默认情况下,会根据日志记录的级别进行过滤。每个日志记录器和处理器都有一个可选的过滤器来完成附加的过滤。要定义一个过滤器,需要实现Filter接口并定义以下方法:

boolean isLoggable(LogRecord record)。

要想将一个过滤器安装到一个日志记录器或处理器中,只需要调用setFilter方法即可。注意,同一时刻最多只能有一个过滤器。

日志的一些使用技巧如下:

1)对一个简单的应用,选择一个日志记录器。可以把日志记录器命名为与主应用包一样的名字。

2)默认的日志配置会把级别等于或高于INFO的所有消息记录到控制台。

与日志相关的API如下;

twelve

thirteen

fourteen

fifteen

sixteen

调试的一些技巧如下:

1)打印或记录任意变量的值。

2)可以在每一个类中放置一个单独的main方法。这也就可以提供一个单元测试桩(stub),能够独立地测试类。

3)善使用JUint。

4)日志代理是一个子类的对象,它可以截获方法调用,记录日志,然后调用超类中的方法。

5)利用Throwable类的printStackTrace方法,可以从任意的异常对象获得堆栈轨迹。

6)将堆栈轨迹捕获到一个字符串中。

7)将程序错误记入一个文件当中。

8)将未捕获的异常的堆栈轨迹记录到一个文件中。

9)观察类的加载过程。

10)-Xlint选项告诉编译器找出常见的代码问题。

11)Java虚拟机增加了对Java应用程序的监控和管理支持,允许在虚拟机中安装代理来跟踪内存消耗、线程使用、类加载等情况。

12)Java任务控制器是一个专业级性能分析和诊断工具,包含在Oracle JDK中,可以免费用。

MySQL必知必会——全文本搜索

并非所有的引擎都支持全文本搜索,MySQL支持几种基本的数据库引擎。两个最常使用的引擎为MyISAM和InnoDB。前者支持全文本搜索,而后者不支持。

使用正则表达式的搜索机制的几个重要的限制:

1)性能:通配符和正则表达式匹配通常要求MySQL尝试匹配表中所有行(而且这些搜索极少使用表索引)。因此,由于被搜索的行不断递增,这些搜索可能非常耗时。

2)明确控制:使用通配符和正则表达式匹配很难(而且并不总是能)明确地控制匹配什么和不匹配什么。

3)智能化的结果:虽然基于通配符和正则表达式的搜索提供了非常灵活的搜索,但它们都不能提供一种智能化的选择结果的方法。例如,一个特殊词的搜索将会返回包含该词的所有行,而不区分包含单个匹配的行和包含多个匹配的行(按照可能是更好的匹配来排列它们)。类似,一个特殊词的搜索将不会找出不包含该词但包含其他相关词的行。

更新索引是要花时间,虽然不很多,但毕竟要花时间。如果正在导入数据到一个新表,此时不应该启用fulltext索引。应该首先导入所有数据,然后再修改表,定义fulltext。这样有助于更快地导入数据(而且使索引数据的总时间小于在导入每行时分别进行索引所需的总时间)。

在索引之后,使用两个函数Match()和Against()执行全文本搜索,其中的Match()指定被搜索的列,Against()指定要使用的搜索表达式。传递给Match()的值必须与fulltext()定义中的相同,如果指定多个列,则必须列出它们(而且次序要正确)。除非不适用binary方式,否则全文本搜索不区分大小写。全文本搜索的一个重要部分就是对结果排序。具有较高等级的行先返回(因为这些行很可能是你真正想要的行)。如果指定多个搜索项,则包含多数匹配词的那些行具有比包含较少词(或仅有一个匹配)的那些行高的等级值。

查询扩展用来设法放宽所返回的全文本搜索结果的范围。在使用查询扩展时,MySQL对数据和索引进行两遍扫描来完成搜索。首先,进行一个基本的全文本搜索,找出与搜索条件匹配的所有行。其次,MySQL检查这些匹配行并选择所有有用的词。再其次,MySQL再次进行全文本搜索,这次不仅适用原来的条件,而且还使用所有用的词。注意,查询扩展功能是在MySQL 4.1.1中引入的,因此不能用于之前的版本。表中的行越多(这些行中的文本越多),使用查询扩展返回的结果越好。

MySQL支持全文本搜索的另外一种形式称为布尔方式。以布尔方式可以提供关于如下内容的细节:

1)要匹配的词。

2)要排斥的词(如果某行包含这个词,则不返回改行,即使它包含其他指定的词也是如此)。

3)排列提示(指定某些词比其他词更重要,更重要的词等级跟高)。

4)表达式分组。

5)另外一些内容。

布尔方式不同于迄今使用的全文本搜索语法的地方在于,即使没有定义fulltext索引,也可以使用它。但这是一种非常缓慢的操作(其性能将随着数据的增加而降低)。

全文本布尔操作符如下:

one

在布尔排序中,不按等级值降序排序返回的行。

关于全文本搜索的某些重要的说明如下:

1)在索隐全文本数据时,短词被忽略且从索引中排除。短词定义为那些具有3个或3个以下字符的词(如果需要,这个数目可以更改)。

2)MySQL带有一个内建的非用词列表。这些词在索引全文数据时总是被忽略。如果需要,可以覆盖这个列表。

3)许多词出现的频率很高,搜索它们没有用处(返回太多的结果)。因此,MySQL规定了一条50%规则。如果一个词出现在50%以上的行中,则将它作为一个非用词忽略。50%规则不用于in Boolean mode。

4)如果表中的行数少于3行,则全文本搜索不返回结果(因为每个词要么不出现,要么至少出现在50%的行中)。

5)忽略词中的单引号。

6)不具有词分隔符(如日语和汉语)的语言不能恰当地返回全文本搜索结果。

7)仅在MyISAM数据引擎中支持全文本搜索。

邻近搜索是许多全文本搜索支持的一个特性,它能搜索相邻的词(在相同的句子中、相同的段落中或者在特定数目的词的部分中,等等)。MySQL全文本搜索现在还不支持邻近操作符。

MySQL必知必会——组合查询

MySQL允许执行多个查询(多条select语句),并将结果作为单个查询结果集返回。这些组合查询通常称为并(union)或复合查询(compound query)。有两种基本情况,其中需要使用组合查询:

1)在单个查询中从不同的表返回类似结构的数据。

2)对单个表执行多个查询,按单个查询返回数据。

组合查询和多个where条件:在多数情况下,组合相同表的两个查询完成的工作与具有多个where子句条件的单条查询完成的工作相同。换句话说,任何具有多个where子句的select语句,都可以作为一个组合查询给出。

创建组合查询:可以用union操作符来组合数条SQL查询。利用union可以给出多条select语句,将它们的结果组合成单个结果集。union的使用很简单,所需要做的只是给出每条select语句,在各条语句之间放上关键字union。

union的规则:

1)union必须由两条或两条以上的select语句组成,语句之间使用关键字union分隔(因此,如果组合4条select语句,将要使用3个union关键字)。

2)union中的每个查询必须包含相同的列、表达式或聚集函数(不过每个列不需要以相同的次序列出)。

3)列数据类型必须兼容:类型不必完全相同,但必须是DBMS可以隐含地转换的类型

union从查询结果集中自动去除了重复的行(换句话说,它的行为与单条select语句中使用多个where子句条件一样)。这是union的默认行为,但是如果需要,可以改变它。使用union all,MySQL将不取消重复的行。

union与where:union几乎总是完成与多个where条件相同的工作。union all为union的一种形式,它完成where子句完成不了的工作。如果确实需要每个条件的匹配行全部出现(包括重复行),则必须使用union all 而不是where。

对组合查询结果排序:select语句的输出用order by子句排序。在用union组合查询时,只能使用一条order by子句,它必须出现在最后一条select之后。对于结果集,不存在用一种方式排序一部分,而又用另一种方式排序另一部分的情况,因此不允许使用多条order by子句。

MySQL必知必会——创建高级联结表

别名除了用于列名和计算字段外,SQL还允许给表名起别名。这样做有两个主要理由:

1)缩短SQL语句。

2)允许在单条select语句中多次使用相同的表。

表别名不仅能用于where子句,它还可以用于select的列表,order by子句以及语句的其他部分。注意:表别名只在查询执行中使用,与列别名不一样。表别名不返回到客户机。

自联结通常作为外部语句用来替代从相同表中检索数据时使用的子查询语句。虽然最终的结果是相同,但有时候处理联结远比处理子查询快得多。

标准的联结返回所有数据,甚至相同的列多次出现。自然联结排除多次出现,使每个列只返回一次。这一般是通过对表使用通配符(select*),对所有其他表的列使用明确的子集来完成的。

外部联结:联结包含了那些在相关表中没有关联的行,这种类型的联结称为外部联结。

select语句使用了关键字outer join来指定联结的类型,与内部联结关联两个表中的行不同的是,外部联结还包含没有关联的行。在使用outer join语法时,必须使用right或left关键字指定包括其所有行的表(right指出的是outer join右边的表,而left指出的是outer join左边的表)。

MySQL不支持简化字符*=和=*的使用,这两种操作符在其他DBMS中是很流行的。

外部联结的类型:存在两种基本的外部联结形式,左外部联结和右外部联结。他们之间唯一的差别是所关联的表的顺序不同。换句话说,左外部联结可通过颠倒from或where子句中表的顺序转换为右外部联结。因此,两种类型的外部联结可互换使用。

使用联结和联结的条件:

1)注意所使用的联结类型。一般使用内部联结,但是用外部联结也是有效的。

2)保证使用正确的联结条件,否则将返回不正确的数据。

3)应该总是提供联结条件,否则会得出笛卡儿积。

4)在一个联结中可以包含多个表,甚至对于每个联结可以采用不同的联结类型。不过要注意,虽然这样做是合法的,一般也很有用,但应该在一起测试它们之前,分别测试每个联结。这将使故障排除更为简单。

Java核心技术——卷I——接口、lambad表达式与内部类

接口(interface)是用来描述类应该做什么,而不指定它们具体如何做。一个类可以实现(implement)一个或多个接口。在Java中,接口不是类,而是对希望符合这个接口的类的一组需求。接口中的所有方法都自动是public。因此,在接口的声明中,不必提供关键字public。不过,在实现接口时,必须把方法声明为public;否则,编译器将认为这个方法的访问属性是包可见性,这是类的默认访问属性,之后编译器就会报错,指出程序员试图提供更严格的访问权限。

CUE:在Java 5中,Comparable接口已经提升为一个泛型类型。

接口还可以定义常量。不过接口绝不会有实例字段,在Java 8 之前,接口中绝对不会实现方法(现在已经可以在接口中提供简单方法了。当然,这些方法不能引用实例字段——接口没有实例)。提供实例字段和方法的实现的任务应该由实现接口的那个类来完成。为了让类实现一个接口,通常需要完成以下两个步骤:

1)将类声明为实现给定的接口。

2)对接口中的所有方法提供定义。

要将类声明为实现某个接口,需要使用关键字implements。

CUE:Java是一种强类型(strongly typed)语言。

部分API如下:

one

接口不是类。具体来说,不能使用new运算符实例化一个接口。不过,尽管不能构造接口的对象,却能声明接口的变量,接口变量必须引用实现了这个接口的类对象。像使用instanceof检查一个对象是否属于某个特定类一样,也可以使用instanceof检查一个对象是否实现了某个特定的接口。与建立类的继承层次一样,也可以扩展接口。允许有多条接口链,从通用性较高的接口扩展到专用性较高的接口。与接口中的方法都自动被设置为public一样,接口中的字段总是public static final。尽管每个类只能有一个超类,但却可以实现多个接口,使用逗号将想要实现的各个接口分隔开。

CUE:C++允许一个类有多个超类,这种特性称为多重继承。Java不支持多重继承。

在Java 8中,允许在接口中增加静态方法。在Java 9中,接口中的方法可以是private。private方法可以是静态方法或实例方法。可以为接口方法提供一个默认实现。必须使用default修饰符标记这样一个方法。接口中默认方法也可以调用其他方法。默认方法的一个重要用法是“接口演化”。为接口增加一个非默认方法不能保证“源代码兼容”(source compatible),只可以保证“二进制兼容”。如果将方法实现为一个默认方法就可以解决这两个问题。 当默认方法发生冲突时,Java规定如下:

1)超类优先。如果超类提供了一个具体方法,同名而且有相同参数类型的默认方法会被忽略。

2)接口冲突。如果一个接口提供了一个默认方法,另一个接口提供了一个同名而且参数类型(不论是否是默认参数)相同的方法,必须覆盖这个方法来解决冲突。

当一个类扩展了一个超类同时实现了一个接口,并从超类和接口继承了相同的方法。在这种情况下,只会考虑超类的方法,接口的所有默认方法都会被忽略。这正是“类优先”规则。“类优先”规则可以确保与Java 7的兼容性。

回调(callback)是一种常见的程序设计模式。在这种模式中,可以指定某个特定的事件发生时应该采取的动作。

部分AIP如下:

two Cloneable接口指示一个类提供了一个安全的clone方法。Cloneable接口是Java提供的少数标记接口(tagging interface)之一(又称之为记号接口(marker interface))。标记接口不包含任何方法;它唯一的作用就是允许在类型查询中使用instanceof。clone方法是Object的一个protected方法。在Java 1.4之间,clone方法的返回类型是Object,而现在是可以为clone方法指定正确的返回类型。如果在一个对象上调用clone,但这个对象的类并没有实现Cloneable接口,Object类的clone方法就会抛出一个CloneNotSupportedException。默认的克隆操作是“浅拷贝”,并没有克隆对象中引用的其他对象。与此对应的深拷贝,会同时克隆所有的子对象。所有数组类型都有一个公共的clone方法,而不是受保护的。可以调用这个方法建立一个新数组,包含原数组所有元素的副本。关于clone,对于每个类,需要确定:

1)默认的clone方法是否满足需求;

2)是否可以在可变的子对象上调用clone来修补默认的clone方法。

3)是否不该使用clone。

实际上第3个选项是默认选项。如果选择第1或第2项,类必须:

1)实现Cloneable接口

2)重新定义clone方法,并指定public修饰符。

lambad表达式是一个可传递的代码块,以及必须传入代码的变量规范。它可以在以后执行一次或多次(也可以说带参数变量的表达式就被称为lambda表达式)。Java中的一种lambad表达式形式为:参数,箭头(->)以及一个表达式。如果代码要完成的计算无法放在一个表达式中,就可以像写方法一样,把这些代码放在**{}**中,并包含显示的return语句。即使lambad表达式没有参数,仍然要提供空括号,就像无参方法一样。如果可以推导出一个lambad表达式的参数类型,则可以忽略其类型。如果方法只有一个参数,而且这个参数的类型可以推导出,那么甚至还可以省略小括号。无需指定lambad表达式的返回类型。lambad表达式的返回类型总是会由上下文推导得出。如果一个lambad表达式只在某些分支返回一个值,而另外一些分支不返回值,这是不合法的。

对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambad表达式。这种接口称为函数式接口(funtional interface)。最好把lambad表达式看作是一个函数,而不是一个对象,另外要接受lambad表达式可以传递到函数式接口。lambad表达式是可以转换为接口的。而实际上,在Java中,对lambad表达式所能做的也只是转换为函数式接口。注意,不能把lambad表达式赋给类型为Object的变量,Object不是函数式接口。

有时,lambad表达式会涉及一个方法,当遇到这种情况时,可以使用方法引用(method reference)。方法引用,它指示编译器生成一个函数式接口,覆盖这个接口的抽象方法来调用给定的方法。类似于lambad表达式,方法引用不是一个对象。不过,为一个类型为函数式接口的变量赋值时会生成一个对象。要用 :: 运算符分隔方法名与对象或类名。主要分为以下3种情况:

1)Object::instanceMthod

2)Class::instanceMthod

3)Class::staticMthod

在第1种情况下,方法引用等价于向方法传递参数的lambad表达式。对于第2种情况,第1个参数会成为方法的隐式参数。在3种情况下,所有参数都传递到静态方法。部分方法引用示例如下:

three

注意,只有当lambad表达式的体只调用一个方法而不做其他操作时,才能把lambad表达式重写为方法引用。类似于lambad表达式,方法引用不能独立存在,总是会转换为函数式接口的实例。可以在方法引用中使用this参数,使用super也是合法的。

构造器引用与方法引用很类似,只不过方法名为new。可以用数组类型建立构造器引用。Java有一个限制,无法构造泛型类型T的数组。

lambad表达式有3个部分:

1)一个代码块;

2)参数;

3)自由变量的值,这是指非参数而且不在代码中定义的变量。

关于代码块以及自由变量值有一个术语:闭包(closure)。因此,在Java中,lambad表达式就是闭包。lambad表达式可以捕获外围作用域中变量的值。在lambad表达式中,只能引用不会改变的变量。也就是说,lambad表达式中捕获的变量必须实际上是事实最终变量(effectively final)。事实最终变量是指,这个变量初始化之后就不会再为它赋新值。lambad表达式的体与嵌套块有相同的作用域。这里同样适用于命名冲突和遮蔽的有关规则。在lambad表达式中声明与一个局部变量同名的参数或局部变量是不合法的。在一个方法中,不能有两个同名的局部变量,因此,lambad表达式中同样不能有同名的局部变量。在一个lambad表达式中使用this关键字时,是指创建这个lambad表达式方法的this参数。

使用lambad表达式的重点是延迟执行。Java API中最重要的函数式接口如下:

four

基本类型的函数式接口如下:

five

CUE:大多数标准函数式接口都提供了非抽象方法来生成或合并函数。

内部类(inner class)是定义在另一个类中的类。使用内部类主要有如下两个原因:

1)内部类可以对同一个包中的其它类隐藏

2)内部类方法可以访问定义这个类的作用域中的数据,包含原本私有数据。

就像C++里的嵌套类一样。被嵌套的类包含在外围类的作用域内。嵌套类就与Java中的内部类很类似。内部类的对象会有一个隐式引用,指向实例化这个对象的外部类对象。通过这个指针,它可以访问外部对象的全部状态。在Java中,静态内部类没有这个附加的指针,所有Java的静态内部类就相当于C++中的嵌套类。外围类的引用在构造器中设置。编译器会修改所有的内部类构造器,添加一个对应外围类引用的参数。

内部类中声明的所有静态字段都必须是final,并初始化为一个编译时常量。如果这个字段不是一个常量,就可能不唯一。内部类不能有static方法。Java语言规范对这个现在没有做任何解释。也可以允许有静态方法,但只能访问外围类的静态字段和方法。内部类是一个编译器现象,与虚拟机无关。编译器将会把内部类转化为常规的类文件,用$(美元符号)分隔外部类名与内部类吗,而虚拟机则对此一无所知。

声明局部类时不能有访问说明符(即public或private)。局部类的作用域被限定在声明这个局部类的块中。与其他内部类相比,局部类的优点有,它们不仅能够访问外部类的字段,还可以访问局部变量!当然,那些局部变量必须是事实最终变量(effectively final)。这说明,它们一旦赋值就绝不会改变。

如果只想创建这个类的一个对象,甚至不需要为类指定名字。这样一个类被称为匿名内部类(anonymous inner class)。一般的,语法如下:

new SuperType(construction parameters)

{

​ inner class methods and data

}

其中,SuperType可以是接口,如果是这样,内部类就要实现这个接口。SuperType可以是一个类,如果是这样,内部类就要扩展这个类。由于构造器的名字必须于类名相同,而匿名内部类没有类名,所以,匿名内部类不能有构造器。构造一个类型的新对象与构造一个扩展了那个类的匿名内部类的对象之间的差别是,如果构造参数列表的结束小括号后面跟一个开始大括号,就是在定义匿名内部类。尽管匿名内部类不能有构造器,但可以提供一个对象初始化块。

当内部类被声明为static时,该内部类就是个静态内部类。静态内部类就类似于其他内部类,只不过静态内部类的对象没有生成它的外围类对象的引用。只要内部类不需要访问外围类对象,就应该使用静态内部类。有些人会用嵌套类表示静态内部类。与常规内部类不同,静态内部类可以有静态字段和方法。在接口中声明的内部类自动是static和public。

与服务加载器相关的API如下:

six

利用代理(proxy)可以在运行时创建实现了一组给定接口的新类。具体地,代理类包含以下方法:

1)指定接口所需要的全部方法

2)Object类中的全部方法,如,toString等。

但不能在运行时为这些方法定义新代码。必须提供一个调用处理器(invocation handler)。调用处理器是实现了InvocationHandler接口的类的对象。这个接口只有一个invoke方法。方法签名如下:

Object invoke(Object proxy,Mehtod method,Object[] args)

无论何时调用代理对象的方法,调用处理器的invoke方法都会被调用,并向其传递Method对象和原调用的参数。之后调用处理器必须确定如何处理这个调用。创建一个代理对象,需要使用Proxy类的newProxyInstance方法。该方法有三个参数:

1)一个类加载器(class loader)

2)一个Class对象数组,每个元素对应需要实现的各个接口。

3)一个调用处理器。

需要记住,代理类是在程序运行过程中动态创建的。然而,一旦被创建,它们就变成常规类,与虚拟机中的任何其他类没有什么区别。所有的代理类都扩展Proxy类。一个代理类只有一个实例字段——即调用处理器,它在Proxy超类中定义。完成代理对象任务所需要的任何额外数据都必须存储在调用处理器中。所有的代理类的都要覆盖Object类的toString、equals和hasCode方法。没有定义代理类的名字,Oracle虚拟机就中的Proxy类将生成一个以字符串$Proxy开头的类名。对于一个特定的类加载器和预设的一组接口来说,只能有一个代理类。代理类总是public和final。如果代理类实现的所有接口都是public,这个代理了就不属于任何特定的包;否则,所有非公共的接口都必须属于一个包,同时,代理类也属于这个包。可以通过调用Proxy类的isProxyClass方法检测一个特定的Class对象是否表示一个代理类。

代理类相关的API如下:

seven

eight

MySQL必知必会——联结表

外键(foreign key):外键为某个表中的一列,它包含另一个表的主键值,定义了两个表之间的关系。

可伸缩性(scale):能够适应不断增加的工作量而不失败。设计良好的数据库或应用程序称之为可伸缩性好。

联结是一种机制,用来在一条select语句中关联表,因此称之为联结。联结不是物理实体。换句话说,它在实际的数据库表中不存在。联结由MySQL根据需要建立,它存在于查询的执行当中。

完全限定名:在引用的列可能出现二义性时,必须使用完全限定列名(用一个点分隔的表名和列名)。如果引用一个没有表名限制的具有二义性的列名,MySQL将返回错误。

笛卡儿积(cartesian product):由没有联结条件的表关系返回的结果称为笛卡尔积。检索出的行的数目将是第一个表中的行数乘以第二个表中的行数。

不要忘了where子句:应该保证所有的联结都由where子句,否则MySQL将返回比想要的数据多得多的数据。同理,应该保证where子句的正确型,不正确的过滤条件将导致MySQL返回不正确的数据。

CUE:有时又将笛卡尔积的联结类型称为叉联结(cross join)。

等值联结(equijoin):它基于两个表之间的相等测试,这种联结也成为内部联结。

ANSI SQL规范首先INNER JOIN语法。此外,尽管使用where子句定义的联结的确比较简单,但是使用明确的联结语法能够确保不会忘记联结条件。

SQL对一条select语句中可以联结的表的数目没有限制。创建联结的基本规则也相同。首先列出所有表,然后定义表之间的关系。

性能考虑:MySQL在运行时关联指定的每个表以处理联结。这种除了可能是非常耗费资源的,因此应该仔细,不要联结不必要的表。联结的表越多,性能下降越厉害。

MySQL必知必会——使用子查询

查询:任何SQL语句都是查询,但此术语一般指select语句。select语句是SQL的查询。SQL还允许创建子查询(subquery),即嵌套在其他查询中的查询。

在select语句中,子查询总是从内向外处理。

格式化SQL:包含子查询的select语句难以阅读,特别是它们较为复杂时更是如此。把子查询分解为多行并且适当进行缩进,能极大简化子查询的使用。

列必须匹配:在where子句中使用子查询,应该保证select语句具有与where子句中相同数目的列。通常,子查询将返回单个列并且与单个列匹配,但如果需要也可以使用多个列。

子查询中的where子句与前面使用的where子句稍有不同,因为它使用了完全限定列名。这种类型的子查询称为相关子查询。任何时候只要列名可能有多义性,就必须是使用这种语法(表命和列名由一个句点分隔)。

相关子查询(correlated subquery):涉及外部查询的子查询。

MySQL必知必会——分组数据

创建分组:分组是在select语句的group by子句中建立的。在具体使用group by子句前,需要知道的一些重要的规定:

1)group by子句可以包含任意数目的列,这使得能对分组进行嵌套,为数据分组提供更细致的控制。

2)如果group by子句中嵌套了分组,数据将在最后规定的分组上进行汇总。也就是说,在建立分组时,指定的所有列都一起计算(所以不能从个别的列取回数据)。

3)group by子句中列出的每个列都必须是检索列或有效的表达式(但不能是聚集函数)。如果在select中使用表达式,则必须在group by子句中指定相同的表达式,不能使用别名。

4)除聚集计算语句外,select语句中的每个列都必须在group by子句中给出。

5)如果分组列中具有null值,则null将作为一个分组返回。如果列中有多行null值,它们将分为一组。

6)group by子句必须出现在where子句之后,order by子句之前。

7)使用rollup,使用with rollup关键字,可以得到每个分组以及每个分组汇总级别(针对每个分组)的值。

where过滤行,而having过滤分组。where和having的差别,还有另一种理解方法。where在数据分组前进行过滤,having在数据分组后进行过滤。这是一个重要的区别,where排除的行不包括在分组中,这可能会改变计算值,从而影响having子句中基于这些值过滤掉的分组。

order by和group by之间的差别如下:

one

要注意,一般在使用group by子句时,应该给出order by子句。这是保证数据正确排序的唯一方法。千万不要依赖group by排序数据。

select语句中子句及其顺序如下:

two

Java核心技术——卷I——继承

继承的基本思想是:可以基于已有的类创建新的类。

反射是指在程序运行期间更多地了解类以及其属性的能力。

关键字extends表明正在构造的新类派生于一个已存在的类。这个已存在的类称为超类(super class)、基类(base class)或父类(parent class);新类称为子类(subclass)、派生类(derived class)或孩子类(child class)。子类其实比超类拥有的功能更多。Java与C++定义继承的方式十分相似。Java用关键字extends代替了C++中的冒号(:)。在Java中,所有的继承都是公共继承,而没有C++中的私有继承和保护继承。

super是一个指示编译器调用超类方法的特殊关键字,它不是一个对象的引用。在Java中使用关键字super调用超类的方法,而在C++中则采用超类名加::操作符的形式。可以在子类中增加字段、增加方法或覆盖超类的方法,不过,继续绝不会删除任何字段或方法。

使用super调用构造器的语句必须是子类构造器的第一条语句。如果子类构造器没有显式地调用超类的构造器,将自动调用超类的无参构造器。如果超类没有无参构造器,并且在子类的构造器中又没有显式地调用超类的其他构造器,Java编译器就会报告一个错误。

与this一样,super关键字也有两个含义:一是调用超类的方法,二是调用超类的构造器。调用构造器的语句只能作为另一个构造器的第一条语句出现。构造器参数可以传递给当前类(this)的另一个构造器,也可以传递给超类(super)的构造器。

一个对象可以指示多种实际类型的现象称为多态(polymorphism)。在运行时能够自动地选择适当的方法,称为动态绑定(dynamic binding)。在C++中,如果希望实现动态绑定,需要将成员函数声明为virtual。在Java中,动态绑定是默认的行为。如果希望让一个方法是虚拟的,可以将它标记为final。

继承并仅限于一个层次。由一个公共超类派生出来的所有类的集合称为继承层次(inheritance hierarchy)。在继承层次中,从某个特定的类到其祖先的路径称为该类的继承链(inheritance chain)。在C++中,一个类可以有多个超类。Java不支持多重继续。

有一个简单规则可以用来判断是否应该将数据设计为继承关系,这就是”is-a”规则,它指出子类的每个对象也是超类的对象。“is-a”规则的另一种表述是原则替换。它指出程序中出现超类对象的任何地方都可以使用子类对象替换。。在Java中,对象变量是多态的。不过,不能将超类的引用赋给子类变量。在Java中,子类引用的数据可以转换成超类引用的数组,而不需要使用强制类型转换。

如果在子类中定义了一个与超类签名相同的方法,那么子类中的这个方法就会覆盖超类中这个相同前面的方法。允许子类将覆盖方法的返回类型改为原返回类型的子类型。

如果是private方法,static方法,final方法或构造器,那么编译器将可以准确地知道应该调用哪个方法。这称为静态绑定(static bindign)。与此对应的是,如果要调用的方法依赖于隐式参数的实际类型,那么必须在运行时使用动态绑定。动态绑定有一个非常重要的特性:无需对现有的代码进行修改就可以对程序进行扩展。

Warning:在覆盖一个方法的时候,子类方法不能低于超类方法的可见性

不允许扩展的类被称为final类。类中的某个特定的方法也可以被声明为final。如果这样做,子类就不能覆盖这个方法(final类中的所有方法自动地成为final方法。注意,不包括字段)。如果一个方法没有被覆盖并且很短,编译器就能够对它进行优化处理,这个过程称为内联。

对象引用的强制类型转换语法与数值表达式的强制类型转换类似,仅需要用一对圆括号将目标类名括起来,并放置在需要转换的对象引用之前就可以了。进行强制类型转换的唯一原因是:要在暂时忽视对象的实际类型之后使用对象的全部功能。总结:1)只能在继承层次内进行强制类型转换 2)在将超类转换成子类之前,应该使用instanceof进行检查。注意:如果对一个null的对象进行instanceof检查,不会产生异常,只是返回false。

使用abstract关键字修饰的方法称为抽象方法。包含一个或多个抽象方法的类本身必须被声明为抽象的,除了抽象方法,抽象类还可以包含字段和具体方法。即使不含抽象方法,也可以将类声明为抽象类。抽象类不能实例化。需要注意,可以定义一个抽象类的变量,但是这样的变量只能引用非抽象子类的对象。在C++中,有一种抽象方法称为纯虚函数(pure virtual function),要在末尾用=0标记。如果至少有一个纯虚函数,这个C++类就是抽象类。在C++中,没有提供用于表示抽象类的特殊关键字。

在Java中,保护字段(由protected修饰符修饰的字段)只能由同一个包中的类访问。事实上,Java中的受保护部分对所有子类及同一个包中的所有其他类都可见。Java中的4个访问控制修饰符小结:

1)仅对本类可见——private

2)对外部可见——public

3)对本包和所有子类可见——protected

4)对本包可见——默认,不需要修饰符。

Object类是Java中的所有类的始祖,在Java中每个类都扩展Object。如果没有明确地指出超类,Object就被认为是这个类的超类。可以使用Object类型的变量引用任何类型的对象。在Java中,只有基本类型不是对象。所有的数组类型,不管是对象数组还是基本类型的数组都扩展了Object类。

Object类中实现的equals方法将确定两个对象引用是否相等。在子类中定义equals方法时,首先调用超类的equals方法。如果检测失败,对象就不可能相等。如果超类中的字段都相等,就需要比较子类中的实例字段。Java语言规范要求equals方法具有以下的特性:

1)自反性:对于任何非空引用x,x.equals(x)应该返回true。

2)对称性:对于任何引用x和y,当且仅当y.equals(x)返回true时,x.equals(y)返回true。

3)传递性:对于任何引用x,y和z,如果x.equals(y)返回true,y.equals(z)返回true,x.equals(z)也应该返回true。

4)一致性:如果x和y引用的对象没有发生变化,反复调用x.equals(y)应该返回同样的结果。

5)对于任意非空引用x,x.equals(null)应该返回false。

equals相关API如下:

one

two

散列码(hash code)是由对象导出的一个整型值。散列码是没有规律的。如果是两个不同的对象,散列码基本上不会相同。由于hashCode方法定义在Object类中,因此每个对象都有一个默认的散列码,其值由对象的存储地址得出。注意,字符串的散列码是由内容导出的。hashCode方法应该返回一个整数(也可以是负数)。equals与hashCode的定义必须相容:即如果两个对象相等,那么它们的散列码也应该返回相同的值。如果存在数组类型的字段,那么可以使用静态的Arrays.hashCode方法计算一个散列码,这个散列码由数组元素的散列码组成。

hashCode方法的相关API如下:

three

four

toString方法,它会返回表示对象值的一个字符串。绝大多数(但不是全部)的toString方法都遵循这样的格式:类的名字,随后是一对方括号括起来的字段值。随处可见toString方法的主要原因是:只要对象与一个字符串通过操作符“+”连接起来,Java编译器就会自动地调用toString方法来获得这个对象的字符串描述。打印数组可以使用Arrays.toString。要想打印多维数组,则需要调用Arrays.deepToString方法。

Object类部分API如下:

five

ArrayList类类似于数组,但在添加和删除元素时,它能够自动地调整数组容量,而不需要为此编写任何代码。ArrayList是一个有类型参数(type parameter)的泛型类(generic class)。为了指定数组列表保存的元素对象的类型,需要用一对尖括号将类名括起来追加到ArrayList后面。在Java10中,最后使用var关键字以避免重复写类名。Java 5 以前的版本没有提供泛型类,而是有一个保存Object类型的元素的ArrayList类,它是一个”自适应大小”的集合。仍然可以使用没有后缀<……>的ArrayList,这将被认为是删去了类型参数的一个”原始”类型。

可以使用add方法将元素添加到数组列表中。数组列表管理着一个内部的对象引用数组。数组列表的容量与数组的大小有一个非常重要的区别。如果分配一个有100个元素的数组,数组就有100个空位置(槽)可以使用。而容量为100个元素的数组列表只是可能保存100个元素(实际上也可以超过100,不过要以重新分配空间为代价),但是在最初,甚至完成初始化构造之后,数组列表不包含任何元素。size方法将返回数组列表中包含的实际元素个数。一旦数组列表的大小被确定,不再改变时可以调用trimToSize方法将存储块的大小调整为保存当前元素数量所需要的存储空间。垃圾回收器将回收多余的存储空间。

ArrayList相关API如下:

six

seven

eight

将一个原始ArrayList赋值给一个类型化ArrayList会得到一个警告。

所有的基本类型都有一个与之对应的类。通常,这些类称为包装器(wrapper)。这些包装器类有显而易见的名字:Integer,Long,Float,Double,Short,Byte,Character和Boolean(前6个类派生于公共的超类Number)。包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值。同时,包装器类还是final,因此不能派生它们的子类。将一个int类型的元素添加到Integer对象的过程称为自动装箱,相反的,当将一个Integer对象赋给一个int值时,将会自动拆箱。自动装箱和自动拆箱甚至也适用于算术表达式。基本类型与它们对应的对象包装器有一个很大不同:同一性。自动装箱的规范要求boolean,byte,char<=127,介于-128和127之间的short和int被包装到固定的对象中。包装器类引用可以是null,但会抛出一个NullPointerException异常。最后要注意,装箱和拆箱是编译器要做的工作,而不是虚拟机。编译器在生产类的字节码时会插入必要的方法调用。虚拟机只是执行这些字节码。

Integer相关API如下:

nine

可以提供参数数量可变的方法(有时这些方法被称为“变参”(varags)方法),如:public PrintStream printf(String fmt ,Object …… args){return format(fmt,args);} 这里的省略号……是Java代码的一部分,它表明这个方法可以接受任意数量的对象(除了fmt参数之外)。允许将数组作为最后一个参数传递给有可变参数的方法。

在比较两个枚举类型值时,并不需要调用equals,可以直接使用”==”即可。枚举的构造器总是私有的。所有的枚举类型都是Enum类的子类。每个枚举类型都有一个静态的values方法,它将返回一个包含全部枚举值的数组。ordinal方法返回enum声明中枚举常量的位置,位置从0开始计数。

与枚举类型相关的API 如下:

ten

反射库(reflection library)提供了一个丰富且精巧的工具集,可以用来编写能够动态操纵Java代码的程序。能够分析类能力的程序称为反射(reflective)。反射可以用来:

1)在运行时分析类的能力。

2)在运行时检查对象,例如,编写一个适用于所有类的toString方法。

3)实现范型数组操作代码。

4)利用Method对象,这个对象很像C++中的函数指针。

程序运行期间,Java运行时系统始终为所有对象维护一个运行时类型标识。这个信息会跟踪每个对象所属的类。保存这些信息的类名为Class。Object类中的getClass方法将会返回一个Class类型的实例。Class对象会描述一个特定类的属性。可能最常用的Class方法就是getName,该方法返回类的名字。需要注意,一个Class对象实际上表示的是一个类型,这可能是类,也可能不是类。Class类实际上是一个泛型类。虚拟机为每个类型管理一个唯一的Class对象,因此,可以利用==运算符实现两个对象的比较。

与反射相关的部分API 如下:

eleven

异常有两种类型:非检查型异常和检查型异常。对于检查型异常,编译器将会检测程序员是否知道这个异常并做好准备来处理后果。如果一个方法包含一条可能抛出检查型异常的语句,则在方法名上增加一个throws子句。调用这个方法的任何方法也都需要一个throws声明,包括main方法。

在Java中,类通常有一些关联的数据文件,如:图像和声音文件;包含消息字符串和按钮标签的文本文件。这些文件被称为资源(resource)。与之相关的API 如下:
twelve

利用反射分析类的能力,检查类的结构。与之相关的API如下:

thirteen

fourteen

使用反射在运行时分析对象,与之相关的API如下:

fifteen

sixteen

为反射编写泛型数组代码,与之相关的API如下:

seventeen

调用任意方法和构造器,与之相关的API如下:

eighteen

继承的设计技巧:

1)将公共的操作和字段放在超类中。

2)不要使用受保护的字段。

3)使用继承实现“is-a”关系。

4)除非所有继承的方法都有意义,否则不要使用继承。

5)在覆盖方法时,不要改变预期的行为。

6)使用多态,而不用使用类型信息。

7)不要滥用反射。

MySQL必知必会——汇总数据

聚集函数(aggregate function):运行在行组上,计算和返回单个值的函数。

one

只能用于单个列:AVG()只能用来确定特定数值的平均值,而且列名必须作为函数参数给出。为了获得多个列的平均值,必须使用多个AVG()函数。AVG()函数忽略值为NULL的行。

COUNT()函数进行计数,该函数确定表中行的数目或符合特定条件的行的数目。

1)使用count(*) 对表中的行的数目进行计数,不管表列中包含的是空值(NULL)还是非空值。

2)使用count(Column)对特定列中具有值的行进行计数,忽略NULL值。

3)NULL值:如果指定列名,则指定列的值为空的行被count()函数忽略,但如果count()函数中用的是星号(*),则不忽略。

MAX()返回指定列中的最大值。该函数要求指定列名。

1)对非数值数据该函数:虽然MAX()一般用来找出最大的数值或日期值,但MySQL允许将它用来返回任意列中的最大值,包括返回文本列中的最大值。在用于文本数据时,如果数据按相应的列排序,则函数返回最后一行。

2)NULL值:函数忽略值为NULL的行。

MIN()的功能与MAX()功能正好相反,它返回指定列的最小值。与MAX()一样,MIN函数要求指定列名。

SUM()用来返回指定列值的和(总计)。SUM也可以用来合计计算值。

1)NULL值:函数忽略值为NULL的行。

聚集不同值:

1)对所有的行执行计算,指定ALL参数或不给参数(因为ALL是默认行为)

2)只包含不同的值,指定distinct参数。注意,如果指定列名,则distinct只能用于count(),不能用于count(*)。因此不允许使用count(distinct),否则会产生错误。类似地,distinct必须使用列名,不能用于计算或表达式。

3)取别名:在指定别名以包含某个聚集函数的结果时,不应该使用表中实际的列名。虽然这样做并非不合法,但使用唯一的名字会使SQL更易于理解和使用(以及将来容易排除故障)。