疯狂Java讲义(第2版)
如何学习Java
- 有多少程序员真正掌握了Java的面向对象?真正掌握了Java的多线程、网络通信、反射等内容?有多少Java程序员真正理解了类初始化时内存运行过程?又有多少程序员理解Java对象从创建到消失的全部细节?
- 绝不要从IDE(如JBuilder、Eclipse和NetBeans)工具开始学习!IDE工具的功能很强大,初学者学起来也很容易上手,但也非常危险:因为IDE工具已经为我们做了许多事情,而软件开发者要全部了解软件开发的全部步骤。
前言
- 疯狂的本质是一种“享受编程”的状态。
第1章 Java语言概述
- Java EE规范是目前最成熟的,也是应用最广的企业级应用开发规范。
1.1 Java语言的发展简史
- C++缺少垃圾回收系统、可移植性、分布式和多线程等功能。
- 运行环境(即JRE)和开发环境(即JDK)。运行环境包括核心API、集成API、用户界面API、发布技术、Java虚拟机(JVM)五个部分;开发环境包括编译Java程序的编译器(即javac命令)。
1.2 Java的竞争对手及各自优势
- Java语言是一种特殊的高级语言,它既具有解释型语言的特征,也具有编译型语言的特征,因为Java程序要经过先编译,后解释两个步骤。
- Java语言目前是最流行的面向对象编程语言,与Java类似的程序设计语言还有C#、Ruby和Python等,它们在某些方面有自己的独特优势,因此都是Java语言有力的竞争者。
1.3 Java程序运行机制
- 编译型语言是指使用专门的编译器,针对特定平台(操作系统)将某种高级语言源代码一次性“翻译”成可被该平台硬件执行的机器码(包括机器指令和操作数),并包装成该平台所能识别的可执行性程序的格式,这个转换过程称为编译(Compile)。编译生成的可执行性程序可以脱离开发环境,在特定的平台上独立运行。
1.4 开发Java的准备
- JDK的全称是Java SE Development Kit,即Java标准版开发包,是Sun提供的一套用于开发Java应用程序的开发包,它提供了编译、运行Java程序所需的各种工具和资源,包括Java编译器、Java运行时环境,以及常用的Java类库等。
1.5 第一个Java程序
- Java程序是一种纯粹的面向对象的程序设计语言,因此Java程序必须以类(class)的形式存在,类(class)是Java程序的最小程序单位。Java程序不允许可执行性语句、方法等成分独立存在,所有的程序部分都必须放在类定义里。
1.6 Java程序的基本规则
- 一个Java源文件可以包含多个类定义,但最多只能包含一个public类定义;如果Java源文件里包含public类定义,则该源文件的文件名必须与这个public类的类名相同。
- 如果指定了CLASSPATH环境变量,一定不要忘记在CLASSPATH环境变量中增加一点(.),一点代表当前路径,用以强制Java解释器在当前路径下搜索Java类。
- Java语言是严格区分大小写的语言
第2章 理解面向对象
- 面向对象的方式实际上由OOA(面向对象分析)、OOD (面向对象设计)和OOP(面向对象编程)三个部分有机组成,其中,OOA和OOD的结构需要使用一种方式来描述并记录,目前业界统一采用UML(统一建模语言)来描述并记录OOA和OOD的结果。
2.1 面向对象
- 结构化程序设计里最小的程序单元是函数,每个函数都负责完成一个功能,用以接收一些输入数据,函数对这些输入数据进行处理,处理结束后输出一些数据。整个软件系统由一个个函数组成,其中作为程序入口的函数被称为主函数,主函数依次调用其他普通函数,普通函数之间依次调用,从而完成整个软件系统的功能
- 任何简单或复杂的算法都可以由顺序结构、选择结构和循环结构这三种基本结构组合而成。
- 虽然Java是面向对象的编程语言,但Java的方法类似于结构化程序设计的函数,因此方法中代码的执行也是顺序结构。
- 循环结构的基本形式有两种:当型循环和直到型循环
- Field(状态数据)+方法(行为)=类定义
2.2 UML(统一建模语言)介绍
- 面向对象软件开发需要经过OOA(面向对象分析)、OOD(面向对象设计)和OOP(面向对象编程)三个阶段,OOA对目标系统进行分析,建立分析模型,并将之文档化;OOD用面向对象的思想对OOA的结果进行细化,得出设计模型。OOA和OOD的分析、设计结果需要统一的符号来描述、交流并记录,UML就是这种用于描述、记录OOA和OOD结果的符号表示法。
- 泛化与继承是同一个概念,都是指子类是一种特殊的父类,类与类之间的继承关系是非常普遍的,继承关系使用带空心三角形的实线表示。
- 组件图通常包含组件、接口和Port等图元,UML使用带符号的矩形来表示组件,使用圆圈代表接口,使用位于组件边界上的小矩形代表Port。
2.3 Java的面向对象特征
- 在Java语言中,除了8个基本数据类型值之外,一切都是对象
- Java语言不允许直接访问对象,而是通过对对象的引用来操作对象。
第3章 数据类型和运算符
- 引用类型包括类、接口和数组类型,还有一种特殊的null类型。所谓引用数据类型就是对一个对象的引用,对象包括实例和数组两种。实际上,引用类型变量就是一个指针,只是Java语言里不再使用指针这个说法。
- 强类型包含两方面的含义:① 所有的变量必须先声明、后使用;② 指定类型的变量只能接受类型与之匹配的值。
3.1 注释
- 字符串不是基本数据类型,字符串类型是一个类,也就是一个引用数据类型。
- 文档注释以斜线后紧跟两个星号(/**)开始,以星号后紧跟一个斜线(*/)作为结尾,中间部分全部都是文档注释,会被提取到API文档中。
- 只有浮点数除以0才可以得到正无穷大或负无穷大,因为Java语言会自动把和浮点数运算的0(整数)当成0.0(浮点数)处理。如果一个整数值除以0,则会抛出一个异常:ArithmeticException: / by zero(除以0异常)。
3.3 数据类型分类
- Java语言是强类型(strongly typed)语言,意思是每个变量和每个表达式都有一个在编译时就确定的类型,所以,所有的变量必须显式声明类型,也就是说,所有的变量必须先声明,后使用。
3.5 基本类型的类型转换
- 有两种类型转换方式:自动类型转换和强制类型转换。
- 不仅如此,当把任何基本类型的值和字符串值进行连接运算时,基本类型的值将自动类型转换为字符串类型,虽然字符串类型不是基本类型,而是引用类型
- 表达式的类型将严格保持和表达式中最高等级操作数相同的类型
3.6 直接量
- 直接量是指在程序中通过源代码直接指定的值
- 关于字符串直接量有一点需要指出,当程序第一次使用某个字符串直接量时,Java会使用常量池(constant pool)来缓存该字符串直接量,如果程序后面的部分需要用到该字符串直接量时,Java会直接使用常量池(constant pool)中的字符串直接量。
- 常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括关于类、方法、接口中的常量,也包括字符串直接量。
3.7 运算符
- +还可以作为字符串的连接运算符
- 但如果除法运算符的两个操作数有1个是浮点数,或者2个都是浮点数,则计算结果也是浮点数,这个结果就是自然除法的结果。而且此时允许除数是0,或者0.0,得到结果是正无穷大或负无穷大。
- 如果把++放在左边,则先把操作数加1,然后才把操作数放入表达式中运算;如果把++放在右边,则先把操作数放入表达式中运算,然后才把操作数加1。
- 自加和自减只能用于操作变量,不能用于操作数值直接量或常量。
- Java并没有提供其他更复杂的运算符,如果需要完成乘方、开方等运算,则可借助于java.lang.Math类的工具方法完成复杂的数学运算
- 虽然Java支持这种一次为多个变量赋值的写法,但这种写法导致程序的可读性降低,因此不推荐这样写。
- 如果两个操作数都是引用类型,那么只有当两个引用变量引用相同类的实例时才可以比较,而且这两个引用必须指向同一个对象才会返回true。
4.1 顺序结构
- 不要同时使用静态初始化和动态初始化,也就是说,不要在进行数组初始化时,既指定数组的长度,也为每个数组元素分配初始值。
4.2 分支结构
- 使用if...else语句时,一定要先处理包含范围更小的情况。
4.3 循环结构
- 与while循环不同的是,do while循环的循环条件后必须有一个分号,这个分号表明循环结束。
4.5 数组类型
- 如果访问数组元素时指定的索引值小于0,或者大于等于数组的长度,编译程序不会出现任何错误,但运行时出现异常:java.lang.ArrayIndexOutOfBoundsException: N(数组索引越界异常),异常信息后的N就是程序员试图访问的数组索引。
- 使用foreach循环迭代数组元素时,并不能改变数组元素的值,因此不要对foreach的循环变量进行赋值。
4.6 深入数组
- 数组是一种引用数据类型,数组引用变量只是一个引用,数组元素和数组变量在内存里是分开存放的
第5章 面向对象(上)
- 构造器用于对类实例进行初始化操作,构造器支持重载,如果多个重载的构造器里包含了相同的初始化代码,则可以把这些初始化代码放置在普通初始化块里完成,初始化块总在构造器执行之前被调用。
5.1 类和对象
- Java的继承具有单继承的特点,每个子类只有一个直接父类。
- 如果父类方法具有private访问权限,则该方法对其子类是隐藏的,因此其子类无法访问该方法,也就是无法重写该方法。如果子类中定义了一个与父类private方法具有相同的方法名、相同的形参列表、相同的返回值类型的方法,依然不是重写,只是在子类中重新定义了一个新方法
- static修饰的方法和Field,既可通过类来调用,也可通过实例来调用;没有使用static修饰的普通方法和Field,只可通过实例来调用。
- 静态成员不能直接访问非静态成员。
- 所以请读者牢记一点:Java编程时不要使用对象去调用static修饰的Field、方法,而是应该使用类去调用static修饰的Field、方法!
- 普通方法访问其他方法、Field时无须使用this前缀,但如果方法里有个局部变量和Field同名,但程序又需要在该方法里访问这个被覆盖的Field,则必须使用this前缀。
- 当this作为对象的默认引用使用时,程序可以像访问普通引用变量一样来访问这个this引用,甚至可以把this当成普通方法的返回值。
- 使用this作为方法的返回值可以让代码更加简洁,但可能造成实际意义的模糊。
5.2 方法详解
- Java里的方法不能独立存在,所有的方法都必须定义在类里。方法在逻辑上要么属于类,要么属于对象。
- 如果在定义方法时,在最后一个形参的类型后增加三点(...),则表明该形参可以接受多个参数值,多个参数值被当成数组传入
- 同一个类中包含了两个或两个以上方法的方法名相同,但形参列表不同,则被称为方法重载
5.3 成员变量和局部变量
- 在Java语言中,根据定义变量位置的不同,可以将变量分成两大类:成员变量和局部变量
5.4 隐藏和封装
- 如果一个Java源文件里定义的所有类都没有使用public修饰,则这个Java源文件的文件名可以是一切合法的文件名;但如果一个Java源文件里定义了一个public修饰的类,则这个源文件的文件名必须与public修饰的类的类名相同。
- Java的包机制需要两个方面保证:① 源文件里使用package语句指定包名;② class文件必须放在对应的路径下。
5.5 深入构造器
- Java类必须包含一个或一个以上的构造器。
5.6 类的继承
- Java的子类不能获得父类的构造器。
- 方法的重写要遵循“两同两小一大”规则,“两同”即方法名相同、形参列表相同;“两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;“一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。尤其需要指出的是,覆盖方法和被覆盖方法要么都是类方法,要么都是实例方法,不能一个是类方法,一个是实例方法。
- 如果需要在子类方法中调用父类被覆盖的实例方法,则可使用super限定来调用父类被覆盖的实例方法
- 在一个构造器中调用另一个重载的构造器使用this调用来完成,在子类构造器中调用父类构造器使用super调用来完成。
5.7 多态
- Java引用变量有两个类型:一个是编译时类型,一个是运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就可能出现所谓的多态(Polymorphism)。
- 当把一个子类对象直接赋给父类引用变量时,例如上面的BaseClass ploymophicBc=new SubClass();,这个ploymophicBc引用变量的编译时类型是BaseClass,而运行时类型是SubClass,当运行时调用该引用变量的方法时,其方法行为总是表现出子类方法的行为特征,而不是父类方法的行为特征,这就可能出现:相同类型的变量、调用同一个方法时呈现出多种不同的行为特征,这就是多态。
- 通过引用变量来访问其包含的实例Field时,系统总是试图访问它编译时类型所定义的Field,而不是它运行时类型所定义的Field。
- 考虑到进行强制类型转换时可能出现异常,因此进行类型转换之前应先通过instanceof运算符来判断是否可以成功转换。
5.8 继承与组合
- 继承是实现类重用的重要手段,但继承带来了一个最大的坏处:破坏封装。
- 继承要表达的是一种“是(is-a)”的关系,而组合表达的是“有(has-a)”的关系。
5.9 初始化块
- Java使用构造器来对单个对象进行初始化操作,使用构造器先完成整个Java对象的状态初始化,然后将Java对象返回给程序,从而让该Java对象的信息更加完整。与构造器作用非常类似的是初始化块,它也可以对Java对象进行初始化操作。
- 当Java创建一个对象时,系统先为该对象的所有实例Field分配内存(前提是该类已经被加载过了),接着程序开始对这些实例变量执行初始化,其初始化顺序是:先执行初始化块或声明Field时指定的初始值,再执行构造器里指定的初始值。
- 当JVM第一次主动使用某个类时,系统会在类准备阶段为该类的所有静态Field分配内存;在初始化阶段则负责初始化这些静态Field,初始化静态Field就是执行类初始化代码或者声明类Field时指定的初始值,它们的执行顺序与源代码中的排列顺序相同。
第6章 面向对象(下)
- Java提供了final关键字来修饰变量、方法和类,系统不允许为final变量重新赋值,子类不允许覆盖父类的final方法,final类不能派生子类。通过使用final关键字,允许Java实现不可变类,不可变类会让系统更加安全。
6.3 类成员
- static关键字不能修饰构造器。static修饰的类成员属于整个类,不属于单个实例。
6.4 final修饰符
- final修饰的变量不可被改变,一旦获得了初始值,该final变量的值就不能被重新赋值。
- 与普通成员变量不同的是,final成员变量(包括实例Field和类Field)必须由程序员显式初始化,系统不会对final成员进行隐式初始化。
- 在上面程序中还示范了final修饰形参的情形。因为形参在调用该方法时,由系统根据传入的参数来完成初始化,因此使用final修饰的形参不能被赋值。
- 但对于引用类型变量而言,它保存的仅仅是一个引用,final只保证这个引用类型变量所引用的地址不会改变,即一直引用同一个对象,但这个对象完全可以发生改变
- 使用final修饰的引用类型变量不能被重新赋值,但可以改变引用类型变量所引用对象的内容。
- final修饰符的一个重要用途就是定义“宏变量”。当定义final变量时就为该变量指定了初始值,而且该初始值可以在编译时就确定下来,那么这个final变量本质上就是一个“宏变量”,编译器会把程序中所有用到该变量的地方直接替换成该变量的值。
- final修饰的方法不可被重写,如果出于某些原因,不希望子类重写父类的某个方法,则可以使用final修饰该方法。
- final修饰的方法仅仅是不能被重写,并不是不能被重载
- final修饰的类不可以有子类
- 如果需要设计一个不可变类,尤其要注意其引用类型Field,如果引用类型Field的类是可变的,就必须采取必要的措施来保护该Field所引用的对象不会被修改,这样才能创建真正的不可变类。
6.5 抽象类
- 但在某些情况下,某个父类只是知道其子类应该包含怎样的方法,但无法准确地知道这些子类如何实现这些方法。
- 抽象方法是只有方法签名,没有方法实现的方法。
- 抽象类不能用于创建实例,只能当作父类被其他子类继承。
- abstract不能用于修饰Field,不能用于修饰局部变量,即没有抽象变量、没有抽象Field等说法;abstract也不能用于修饰构造器,没有抽象构造器,抽象类里定义的构造器只能是普通构造器。
6.6 更彻底的抽象:接口
- 接口里不能包含普通方法,接口里的所有方法都是抽象方法。
- 一个类实现了一个或多个接口之后,这个类必须完全实现这些接口里所定义的全部抽象方法(也就是重写这些抽象方法);否则,该类将保留从父接口那里继承到的抽象方法,该类也必须定义成抽象类。
- 实现接口方法时,必须使用public访问控制修饰符,因为接口里的方法都是public的,而子类(相当于实现类)重写父类方法时访问权限只能更大或者相等,所以实现类实现接口里的方法时只能使用public访问权限。
6.7 内部类
- 成员内部类(包括静态内部类、非静态内部类)的class文件总是这种形式:OuterClass$InnerClass.class。
- 当创建匿名内部类时,必须实现接口或抽象父类里的所有抽象方法。如果有需要,也可以重写父类中的普通方法
- 如果匿名内部类需要访问外部类的局部变量,则必须使用final修饰符来修饰外部类的局部变量,否则系统将报错。
- 所谓回调,就是允许客户类通过内部类引用来调用其外部类的方法,这是一种非常灵活的功能。
6.8 枚举类
- 这种实例有限而且固定的类,在Java里被称为枚举类。
- 枚举类里定义抽象方法时不能使用abstract关键字将枚举类定义成抽象类(因为系统自动会为它添加abstract关键字),但因为枚举类需要显式创建枚举值,而不是作为父类,所以定义每个枚举值时必须为抽象方法提供实现,否则将出现编译错误。
6.11 使用JAR文件
- JAR文件与ZIP文件的区别就是在JAR文件中默认包含了一个名为META-INF/MANIFEST.MF的清单文件,这个清单文件是在生成JAR文件时由系统自动创建的。
7.2 系统相关
- Java提供了System类和Runtime类来与程序的运行平台进行交互。
7.3 常用类
- ThreadLocalRandom类是Java 7新增的一个类,它是Random的增强版。在并发访问的环境下,使用ThreadLocalRandom来代替Random可以减少多线程资源竞争,最终保证系统具有较好的性能。
- 当试图复制一个Collection集合里的元素来创建EnumSet集合时,必须保证Collection集合里的所有元素都是同一个枚举类的枚举值。
7.4 处理日期的类
- ArrayList和Vector类都是基于数组实现的List类,所以ArrayList和Vector类封装了一个动态的、允许再分配的Object[]数组。ArrayList或Vector对象使用initialCapacity参数来设置该数组的长度,当向ArrayList或Vector中添加元素超出了该数组的长度时,它们的initialCapacity会自动增加。
7.5 正则表达式
- 正则表达式是一个强大的字符串处理工具,可以对字符串进行查找、提取、分割、替换等操作。
7.6 国际化与格式化
- 国际化的英文单词是Internationalization,因为这个单词太长了,有时也简称I18N,其中I是这个单词的第一个字母,18表示中间省略的字母个数,而N代表这个单词的最后一个字母。
第8章 Java集合
- Java集合大致可分为Set、List和Map三种体系,其中Set代表无序、不可重复的集合;List代表有序、重复的集合;而Map则代表具有映射关系的集合。
8.1 Java集合概述
- 集合类和数组不一样,数组元素既可以是基本类型的值,也可以是对象(实际上保存的是对象的引用变量);而集合里只能保存对象(实际上只是保存对象的引用变量,但通常习惯上认为集合里保存的是对象)。
8.2 Collection和Iterator接口
- Collection接口是List、Set和Queue接口的父接口,该接口里定义的方法既可用于操作Set集合,也可用于操作List和Queue集合。
- 当使用Iterator对集合元素进行迭代时,Iterator并不是把集合元素本身传给了迭代变量,而是把集合元素的值传给了迭代变量,所以修改迭代变量的值对集合元素本身没有任何影响。
8.3 Set集合
- Set判断两个对象相同不是使用==运算符,而是根据equals方法。
- HashSet集合判断两个元素相等的标准是两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法返回值也相等。
- 对于TreeSet集合而言,它判断两个对象是否相等的唯一标准是:两个对象通过compareTo(Object obj)方法比较是否返回0——如果通过compareTo(Object obj)方法比较返回0,TreeSet则会认为它们相等;否则就认为它们不相等。
- 如果两个对象通过equals()方法比较返回true时,这两个对象通过compareTo(Object obj)方法比较应返回0。
- 为了让程序更加健壮,推荐HashSet和TreeSet集合中只放入不可变对象。
- 如果需要实现定制排序,则需要在创建TreeSet集合对象时,提供一个Comparator对象与该TreeSet集合关联,由该Comparator对象负责集合元素的排序逻辑。
- HashSet的性能总是比TreeSet好(特别是最常用的添加、查询元素等操作),因为TreeSet需要额外的红黑树算法来维护集合元素的次序。只有当需要一个保持排序的Set时,才应该使用TreeSet,否则都应该使用HashSet。
8.4 List集合
- List集合代表一个元素有序、可重复的集合,集合中每个元素都有其对应的顺序索引。
- List判断两个对象相等只要通过equals()方法比较返回true即可。
- Arrays.ArrayList是一个固定长度的List集合,程序只能遍历访问该集合里的元素,不可增加、删除该集合里的元素
8.5 Queue集合
- 因此现在的程序中需要使用“栈”这种数据结构时,推荐使用ArrayDeque或LinkedList,而不是Stack。
8.6 Map
- 再次强调:Set和Map的关系十分密切,Java源码就是先实现了HashMap、TreeMap等集合,然后通过包装一个所有的value都为null的Map集合实现了Set集合类。
- 使用TreeMap有一个好处:TreeMap中的key-value对总是处于有序状态,无须专门进行排序操作。当TreeMap被填充之后,就可以调用keySet(),取得由key组成的Set,然后使用toArray()方法生成key的数组,接下来使用Arrays的binarySearch()方法在已排序的数组中快速地查询对象。
8.8 操作集合的工具类:Collections
- Java提供了一个操作Set、List和Map等集合的工具类:Collections,该工具类里提供了大量方法对集合元素进行排序、查询和修改等操作,还提供了将集合对象设置为不可变、对集合对象实现同步控制等方法。
9.1 泛型入门
- Java集合有个缺点——当我们把一个对象“丢进”集合里后,集合就会“忘记”这个对象的数据类型,当再次取出该对象时,该对象的编译类型就变成了Object类型(其运行时类型没变)。
9.2 深入泛型
- 所谓泛型,就是允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方法时动态地指定(即传入实际的类型参数,也可称为类型实参)
9.3 类型通配符
- 如果Foo是Bar的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口, G
并不是G 的子类型!这一点非常值得注意,因为它与我们的习惯看法不同。 - 数组和泛型有所不同,假设Foo是Bar的一个子类型(子类或者子接口),那么Foo[]依然是Bar[]的子类型;但G
不是G 的子类型。
第10章 异常处理
- Java将异常分为两种,Checked异常和Runtime异常, Java认为Checked异常都是可以在编译阶段被处理的异常,所以它强制程序处理所有的Checked异常;而Runtime异常则无须处理。
10.2 异常处理机制
- 不管程序代码块是否处于try块中,甚至包括catch块中的代码,只要执行该代码块时出现了异常,系统总会自动生成一个异常对象。如果程序没有为这段代码定义任何的catch块,则Java运行时环境无法找到处理该异常的catch块,程序就在此退出,这就是前面看到的例子程序在遇到异常时退出的情形。
- 进行异常捕获时,一定要记住先捕获小异常,再捕获大异常。
10.3 Checked异常和Runtime异常体系
- Java的异常被分为两大类:Checked异常和Runtime异常(运行时异常)。所有的RuntimeException类及其子类的实例被称为Runtime异常;不是RuntimeException类及其子类的异常实例则被称为Checked异常。
10.6 异常处理规则
- 必须指出:异常处理机制的初衷是将不可预期异常的处理代码和正常的业务逻辑处理代码分离,因此绝不要使用异常处理来代替正常的业务逻辑判断。
11.5 事件处理
- 因为在AWT编程中,所有事件必须由特定对象(事件监听器)来处理,而Frame和组件本身并没有事件处理能力。
13.1 JDBC基础
- JDBC的全称是JavaDatabase Connectivity,即Java数据库连接,它是一种可以执行SQL语句的JavaAPI。
13.6 Java 7的RowSet 1.1
- 在使用处理流包装了底层节点流之后,关闭输入/输出流资源时,只要关闭最上层的处理流即可。关闭最上层的处理流时,系统会自动关闭被该处理流包装的节点流。
13.9 使用连接池管理连接
- 数据库连接池的解决方案是:当应用程序启动时,系统主动建立足够的数据库连接,并将这些连接组成一个连接池。每次应用程序请求数据库连接时,无须重新打开连接,而是从连接池中取出已有的连接使用,使用完后不再关闭数据库连接,而是直接将连接归还给连接池。通过使用连接池,将大大提高程序的运行效率。
14.1 基本Annotation
- @Override的作用是告诉编译器检查这个方法,保证父类要包含一个被该方法重写的方法,否则就会编译出错。
第15章 输入/输出
- Java的IO流使用了一种装饰器设计模式,它将IO流分成底层节点流和上层处理流,其中节点流用于和底层的物理存储节点直接关联——不同的物理节点获取节点流的方式可能存在一定的差异,但程序可以把不同的物理节点流包装成统一的处理流,从而允许程序使用统一的输入、输出代码来读取不同的物理存储节点的资源。
15.2 理解Java的IO流
- stream是从起源(source)到接收(sink)的有序数据。
- 字节流主要由InputStream和OutputStream作为基类,而字符流则主要由Reader和Writer作为基类。
15.4 输入/输出流体系
- 如果进行输入/输出的内容是文本内容,则应该考虑使用字符流;如果进行输入/输出的内容是二进制内容,则应该考虑使用字节流。
15.7 RandomAccessFile
- RandomAccessFile是Java输入/输出流体系中功能最丰富的文件内容访问类,它提供了众多的方法来访问文件内容,它既可以读取文件内容,也可以向文件输出数据。
- 如果程序需要向已存在的文件后追加内容,则应该使用RandomAccessFile。
15.8 对象序列化
- 对象序列化的目标是将对象保存到磁盘中,或允许在网络中直接传输对象。
15.9 NIO
- 面向流的输入/输出系统一次只能处理一个字节,因此面向流的输入/输出系统通常效率不高。
第16章 多线程
- 单线程的程序(前面介绍的绝大部分程序)只有一个顺序执行流,多线程的程序则可以包括多个顺序执行流,多个顺序流之间互不干扰。
16.2 线程的创建和启动
- 所有的线程对象都必须是Thread类或其子类的实例。
- 使用继承Thread类的方法来创建线程类时,多个线程之间无法共享线程类的实例变量。
16.3 线程的生命周期
- 在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead) 5种状态。
- 如果希望调用子线程的start()方法后子线程立即开始执行,程序可以使用Thread.sleep (1)来让当前运行的线程(主线程)睡眠1毫秒——1毫秒就够了,因为在这1毫秒内CPU不会空闲,它会去执行另一个处于就绪状态的线程,这样就可以让子线程立即开始执行。
16.5 线程同步
- 当使用多个线程来访问同一个数据时,很容易“偶然”出现线程安全问题。
16.7 线程组和未处理的异常
- 一旦某个线程加入了指定线程组之后,该线程将一直属于该线程组,直到该线程死亡,线程运行中途不能改变它所属的线程组。
16.8 线程池
- 当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池。
- 除此之外,使用线程池可以有效地控制系统中并发线程的数量,当系统中包含大量并发线程时,会导致系统性能剧烈下降,甚至导致JVM崩溃,而线程池的最大线程数参数可以控制系统中并发线程数不超过此数。
第17章 网络编程
- 实际上Java的网络通信非常简单,服务器端通过ServerSocket建立监听,客户端通过Socket连接到指定服务器后,通信双方就可以通过IO流进行通信。
18.1 类的加载、连接和初始化
- 系统可能在第一次使用某个类时加载该类,也可能采用预加载机制来加载某个类。
- 类加载指的是将类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。
18.2 类加载器
- 类加载器负责将.class文件(可能在磁盘上,也可能在网络上)加载到内存中,并为之生成对应的java.lang.Class对象
18.3 通过反射查看类信息
- 程序需要在运行时发现对象和类的真实信息