大二就买了这本书,现在再看这本书,看到了一些之前没看的细节,也有了不一样的体会。本文使用第4版,整理每章的笔记心得。老外的书有个特色,他会花费大量的文字去阐述一个概念,这比堆代码强多了。html
1.1 抽象java
抽象是计算机最重要的概念之一。抽象就是从杂乱的事物表象中,提取出对待解决问题来讲最关键的部份内容。C 在解决问题时,主要是基于计算机的结构进行抽象,而不是基于所要解决的问题的结构。而 Java 则是针对问题进行建模,根据问题来描述问题,程序能够经过添加一个新类型的对象使自身适用于某个特定问题,程序员不会受限于任何特定类型的问题。程序员
对象具备状态、行为和标识。纯粹的面向对象程序设计的基本特性:正则表达式
1.2 类和对象算法
每一个对象都是某个类的实例,一个类实际上就是一个抽象数据类型,它描述了具备相同特性(成员)和行为(功能)的对象集合。程序员能够经过定义类来适应问题,而不是被迫使用现有的用来表示机器中的存储单元的数据类型。根据需求添加新类型扩展编程语言,系统就像对待内置类型同样验证并管理它们。数据库
把对象看作是服务提供者好处:将问题分解为一系列对象的集合;提升对象的内聚性,每一个对象都专一本职工做,就是高内聚性,还有个低耦合,解耦通常就用队列实现;方便理解代码和重用。编程
面向对象的特色:封装、继承和多态。何为封装?即隐藏对象的属性和细节,仅对外公开接口,控制属性的读取和修改。访问控制存在的缘由:隔离与提供的服务无关的部分;分离并保护接口和实现。Java 中有四种访问权限,分别是:设计模式
1.3 代码复用数组
两种方式,组合和继承,组合灵活性比较高,相比于继承耦合度低一些。若是要使用某个类提供的服务功能时,通常用组合,当要是使用类提供的接口时使用继承。安全
继承,使用现有的类建立新类型。子类拥有父类的成员(public, protected)而且复制了父类的接口,也就是说,子类与父类具备相同的类型。子类有两种方式改变本身的行为:添加新方法和覆盖父类的方法。当添加新方法时,若是全部子类都须要,就能够把它抽取到父类中。
Java 中,Object是全部类的直接或间接父类。单根继承的好处有:全部对象具备共用接口,利于向后兼容;全部对象都具有某些功能,易于建立和参数传递;易于垃圾回收。
1.6 多态
既然父类和子类是相同类型,那么在运行时子类就能替换父类(向上转型)来完成不一样的功能,这就是多态。多态的体现是:方法的重载和覆盖。编译器(静态分发,重载)和运行系统(JVM动态分发,覆盖)会处理相关细节,保证程序正确的行为。
1.7 容器和泛型
容器,就是Java中的数据结构了,不一样的容器提供不一样的接口和行为,对于某些操做具备不一样的效率。在JDK 5 以前容器存储的对象是Obejct,存储对象必须向上转型,会丢失其身份,当取出时须要向下转型,可能会出错,由于不知道以前放进去的是什么类型的对象,所以,JDK5增长了泛型,明确指出容器可接收的对象类型。
1.8 对象的建立和生命周期、异常和并发
Java 使用动态内存分配技术,使用关键词 new 在堆上建立对象。使用垃圾回收器释放对象占用的内存。
Java 内置了异常的处理,并且强制使用。异常提供了一种从错误进行可靠恢复的途径。
并发控制好共享资源的访问便可,Java提供了并发编程库,有现成可用的并发数据结构。
1.9 Java与Internet
网络编程会涉及到不少知识,TCP/IP,多线程,IO模型等,要写出高性能的Java程序,仍是要下大工夫的,虽然大问题被JVM搞定了。
2.1 对象
Java 经过引用来操做对象,使用 new 建立对象。那么对象被安置在哪一个地方呢?计算中有5个地方能够存储数据,分别是:
2.2 基本类型
基本类型存储值,并置于堆栈中,高效。Java 中基本类型的大小是固定的,不随硬件架构的变化而变化。基本类型以下:
全部数值均有正负号,JDK5的自动包装功能,自动地将基本类型转为包装类型。
高精度数字:BigInteger:支持任意精度的整数;BigDecimal:支持任意精度的定点数。
数组也是对象,能存储基本类型和引用类型,Java会确保数组被初始化。
2.3 做用域 scope
Java 使用花括号定义做用域,局部变量在花括号结束时,生命周期就结束了,而对象不是如此,它能一直保存下去,Java经过垃圾回收器管理对象的内存。通常不会出现内存泄漏,但也不是绝对的。
2.4 类,字段,方法
使用 class 关键字定义一个类,类拥有字段(成员变量)和方法,对于成员变量,即便没进行初始化,Java也会保证它有一个默认值,引用类型默认为null,数字都默认为0,布尔默认false,char默认’\u0000’(null)。对于局部变量编译器会强制进行初始化。
方法,方法名和参数合起来称为方法签名,关于参数,在Java中只有值传递。Java消除了向前引用的问题,也就是同一个类中,成员变量和方法的前后顺序能够随意。
static关键字可用来修饰字段,方法和类。修饰字段方法:表示属于类,不用新建对象就可以使用。通常是修饰内部类,此类与通常的类没有差异。
2.5 注释
经常使用标签和html以下:
3.1 优先级&赋值运算符
从左到右先乘除后加减,当不肯定时,使用括号明确标识便可。
赋值运算符(=),对基本类型赋值就是把一个地方的内容复制到另外一个地方,好比int a=b,就是把b的内容复制给a;对对象赋值只是使这个变量也指向该对象,好比String s = a,s和a指向同一个对象。将对象传给方法时,也只是传递一个引用的值,或者说传递一个对象的别名。
3.2 算术、关系、逻辑运算符,直接常量
加减乘除,取模,一元加、减操做符,自增,自减。
== 做用于基本类型,比较值是否相等;做用于对象比较是不是同一个引用,比较对象使用equals,默认的equals比较引用,须要重写。
与(&&)、或(||)、非(!)生成一个布尔值,具备短路功能,即若是第一个表达式能肯定整个表达式的结果,那么就不会运算后面的表达式。
直接常量,必须明确告诉编译器常量类型,好比10F,10D,10L,0xFF。对于char、byte、short超过其最大范围自动转为int。
指数计数法:float a = 1.39E-43F;表示1.39×e^-43,若是不加F编译器默认会当作double处理,会提示类型转换。
3.3 位操做符和移位操做符
位操做符:
移位操做符,只能用来处理整数,char、byte、short移位时自动转为int:
在进行移位时,好比int只有数值右端的低5位才有用,好比 16>>2 和 16>>34 相等,由于2^5=32,至关于对32取模。long类型就是低6位数字有效。
这里多说两句,源码中或进行移位时会常常看到(&0xFF),缘由是什么呢?
通常咱们会对字节byte进行操做,首先 0xFF 表示低8位(1字节),当对byte移位操做时,会自动转成int,而Java中 int类型有32位,而且在计算机中数字使用有符号的二进制补码表示。因此byte转为int时会进行符号扩展,高位以符号位填充。若是byte为正数那么其补码与原码相同,此时进不进行位与操做都无所谓,可是为负数时不一样,好比byte a = -4;其转为int在内部的表示就是11111111111111111111111111111100,这进行运算显然是不正确的,有效位只有低八位,因此与0xFF位与运算,把高24位置0,运算结果就正确了。
不仅是移位操做,只要byte或short转为int时都会出现此问题,此时只需进行位于运算取得有效数字便可。
3.4 三元操做符 boolean-exp ? true : false
3.5 字符串操做符 + 和 +=
字符串链接操做符,编译器会把双引号内的字符序列自动转换成字符串,若是表达式中有字符串,表达式最终结果为字符串。
3.6 类型转换操做符 (cast)
转换对象既能够是数值,也能够是变量。转换分为两种:窄化转换和扩辗转换。窄化转换:编译器会强制进行类型转换,如把float赋值给int,此时数字会被截尾而不是四舍五入(使用Math.round()进行舍入)。扩辗转换:没必要显示的进行类型转换,编译器会自动处理,如把int赋值给float。
Java 中,除了布尔类型,其余基本类型能够相互转换。类数据类型不能相互转换。
while和do while的区别,即便表达式为false,do-while也会执行一次,而while不执行。
for循环,如 for(int i = 0; i < n; i++)。Foreach用于遍历数组和容器,如 for(String st :args) { }
死循环while(true) 和 for(;;),对编译器来讲是一回事。goto通常不用。
switch语句,case因子支持能产生整数值得表达式和枚举,好比,byte、short、int、char,JDK7开始支持 String
5.1 用构造器确保初始化
建立对象时,JVM会自动调用该对象的构造方法,确保在使用前被正确初始化。Java 构造方法名称与类名称相同,没有返回值。无参构造方法,又称默认构造器,编译器会自动建立一个默认构造方法,可是若是写了一个构造方法,编译器就再也不自动建立了,若是你添加的是有参数的,那么无参的构造方法就没了。
5.2 方法重载
为了让方法名相同的方法存在,必须用到方法重载。区分方法重载的依据:根据类名和方法的形参列表。不能以返回值来区分。
若是实参(char)小于形参(int),实参会被提高;若是大于,编译器会强制类型转换。
5.3 this 和 static
this,表示当前对象的引用。this关键字只能在方法内部使用,编译器会“偷偷”把这个引用传到方法内部,在方法内部调用同类的另外一方法或者使用同类的字段,不须要明确使用this,编译器会自动添加。
在构造方法中调用其余构造方法,使用this能够实现,必须把this置于最起始处,而且只能调用一个(一次)。
static方法就是没有this的方法,内部不能调用非静态方法,反过来能够。可经过类自己来调用static方法。
5.4 清理:终结处理和垃圾回收
Java 中的对象:
垃圾回收和finalize()都不保证会必定发生,若是JVM并未面临内存耗尽的情形,它是不会浪费时间执行垃圾回收释放内存。
5.5 垃圾回收如何工做
HotSpot VM 采用了分代回收的方法,其工做流程是,首先在年轻代中的Eden区移动”堆指针”为对象分配空间,当Eden满了或没法分配,进行一次垃圾回收,把还存活的对象复制到Survivor区或者提高到老年代,清空Eden,如此循环,若是老年代满了,对整个堆进行回收,清空年轻代。
垃圾回收器就是经过一面回收空间,一面使堆中的对象紧凑排列,实现了一种高速,无限空间的堆模型。如何判断”活”对象?对任何”活”的对象,必定能最终追溯到其存活在堆栈或静态存储区之中的引用,这个引用链条可能穿过数个对象层次。因此从堆栈或静态存储区开始,就能找到全部”活”的对象。
Just-In-Time,JIT,即便编译器,在必要时把热点代码翻译成本地代码。代码每执行一次就会作一些优化,执行次数越多,速度越快,因此,理论上Java 程序运行是愈来愈快的。
5.6 成员初始化、构造方法初始化、静态和非静态数据初始化
对应方法的局部变量,使用前没有初始化,编译器以错误的形式保证初始化。对于数据成员(即字段)则不同,JVM会保证每一个字段有一个默认值,固然也能够指定字段的初始值。
构造方法被JVM 自动调用,在类内部,变量初始化顺序由其定义的前后决定,而且在构造方法调用以前初始化。
静态初始化只在必要时刻进行,其实这是由于JVM是按需加载类。静态数据先于非静态数据初始化。
5.7 初始化小结
静态字段、块先于非静态数据初始化,顺序由其定义的前后决定;非静态字段、块先于构造方法初始化,顺序由其定义前后决定;构造方法最后被调用。若是考虑继承,和这差不太多。
5.8 数组初始化
以int类型数组为例。
int[] a[]; 数组定义
int a[] = {1, 2, 3}; 定义并初始化,编译器会分配存储空间,至关于使用new
int a[] = new int[] {1, 2, 3}; 定义并初始化,与上面的区别是,能够在其余地方手动初始化
可变参数,如 print(int… args) 编译器会自动填充数组,因此得到的仍旧是一个数组参数。
5.9 枚举
JDK5添加了枚举类型,enum,枚举可当作类来处理,而且具备本身的方法。枚举常常配合switch使用。
public enum Database { MYSQL, ORACLE, MONGODB}
switch(Database db)
case MYSQL:
// TODO
break;
6.1 建立独一无二的包名
一个包(如jar文件)有许多.class 文件构成,为了更好的管理这里class文件,按照惯例,包名通常使用反转域名,利用操做系统的文件系统,把包名分解为机器上的一个目录。Java 解释器工做工程是:首先得到CLASSPATH环境变量,获取一个根路径,把包名转成路径名,而后在目录中查找class文件,固然也会去加载标准类库。CLASSPATH中有个点(.)表示当前目录。
包的名称对应路径名称。Java 使用 package 组织类在单一的名称空间,经过导入不一样的包,来解决冲突。
6.2 Java 访问权限修饰词
Java中有4中访问权限,从大到小是:public、protected、包访问权限(默认权限)、private。
访问权限的控制常被称为具体实现的隐藏,即封装,把数据和方法包含在类中,隐藏具体实现。其结果就是一个具备特征和行为的数据类型。访问权限控制将权限的边界划在了数据类型的内部,主要出于如下两种缘由:
为了清楚可见,能够采用一种将public成员置于开头,后面是protected、包访问权限和private成员,站在类使用者的角度能够从头读起,关注对本身最重要的部分。可使用eclipse的 source->Sort Members 功能自动排列。
复用代码的两种方式:组合和继承。
7.1 组合语法
组合即将类引用置于新类中,编译器自动将此引用置为null,能够在如下位置初始化:
7.2 继承语法、初始化
Java 中类隐式地从java.lang.Object继承。可使用 extends 关键字明确继承某类,会自动获得父类中全部的域和方法。Java 使用super 关键字表示父类,能够明确调用父类方法。
从外部来看,子类就像是一个与父类具备相同接口的新类,或许还有一些额外的方法和域,但继承并不仅是复制父类的接口。当建立一个子类对象时,该对象包含一个父类的子对象,这个子对象与用父类直接建立的对象是同样的。两者区别在于,后者来在于外部,而父类子对象被包含在子类对象的内部。
为了保证父类子对象的正确初始化,Java 会自动在子类的构造方法插入对基类构造方法的调用,初始化过程是从父类”向外”扩散。
若是父类没有无参的构造方法,那么就必须显示的使用 super 关键字调用父类的构造方法,并配以适当的参数列表,如 super(true)
7.3 代理
若是一个类 A,想复用另外一类 B 的接口方法,但不想使用继承怎么办?首先,先定义与 B 相同的方法名,而后在内部维持一个 B 的引用,方法的处理使用 B 的同名方法便可,这就称为代理。
7.4 名称屏蔽、protected、向上转型
若是Java 的父类拥有某个已被屡次重载的方法,那么在子类中从新定义该方法并不会屏蔽其在父类中的任何版本,也就是说重载机制能够正常工做。
子类可以访问父类pulbic和protected的字段或方法,能覆盖重写,对于private类型的只有父类子对象可以访问。”覆盖”只有在某方法是父类接口的一部分时才会出现,若是某方法是private,它就不是父类接口的一部分,它仅是一些隐藏于类中的代码,若是子类有同名的方法,也只不过是具备相同名称而已。
JDK5 增长了一个 @Override 注解,用以代表我要覆盖父类的方法,能够防止在不想重载时而意外进行了重载。
子类是父类的一种类型,子类复制了父类的接口,子类能够向上转型为父类,以完成某些功能。
7.5 final 关键字
final一般表示不可改变的,final使用的状况有三种:数据、方法和类。
7.6 初始化和加载
类是按需加载的。初始化顺序为(其中内部按定义前后进行):
在面向对象的程序设计语言中,多态是继数据抽象和继承以后的第三种基本特征。“封装”经过合并特征和行为建立新的数据类型。”实现隐藏”经过将细节私有化(private)分离接口和实现。而多态则是消除类型之间的耦合关系,经过分离作什么和怎么作,从另外一角度分离接口和实现,可以改善代码的组织机构和可读性,可以建立可扩展的程序。
多态也称为动态绑定、后期绑定或运行时绑定。
8.1 向上转型和方法绑定
对象既能够做为它本身自己的类型使用,也能够做为基类型使用,而把对象的引用视为其父类型引用的作法就叫向上转型。那么已经转为父类型,程序又是怎么正确调用子类的方法呢?这和方法的绑定有关。
首先,编译器是不知道对象的类型的,不能前期绑定。若是你查看字节码的话会发现,多态方法的调用是经过invokevirtual 指令调用父类的方法,在运行时怎么才能找到正确的对象呢?经过对象内部的vtable和itable信息就能够找到对象继承的类和实现的接口的方法。
Java 中除了static 方法和final方法(private方法属于final方法)以外,其余全部方法都是后期绑定。
8.2 缺陷:”覆盖”私有方法
私有方法默认是final方法,对子类来讲是屏蔽的,因此若是子类有一个同名的方法话,只是至关于添加一个新方法,当向上转型时,会调用父类中的方法。子类中对于父类的private方法,最好采用不一样的名字。
8.3 缺陷:域与静态方法
静态方法不具备多态性,由于它是与类相关而非某个对象。
不要在子类中定义与父类同名的字段(特别是那些私有方法和私有字段),容易使人混淆。
可是也能解释的通,在分析运行过程时能够这样分析:
8.4 构造器和多态
构造方法不具备多态性,由于它默认是static的,隐式声明。
涉及到继承的初始化顺序,首先父类确定在子类以前初始化,而后再是子类。对于静态数据有些特殊,具体过程以下:
构造方法内部的多态行为,就是在内部调用可能被重写的方法,通常不会这么作,在构造方法被调用以前,字段会被赋予默认初始值。
编写构造方法的准则:用尽量简单的方法使对象进入正常状态,最好避免调用其余方法。构造方法惟一能安全调用的就是final方法,它不会被子类覆盖。
协变返回类型:在子类覆盖的方法中能够返回父类方法的返回类型的某种子类型。好比 A extends B,C extends D,在D中有个方法 B fun() 返回B,在JDK5以前,子类C也只能返回B类型,而不能返回具体类型,有了协变就能够返回具体类型A。P(164)
8.5 用继承进行设计
继承在编译期间就肯定了对象之间的关系,咱们不能再运行期间决定继承不一样的对象,而组合能够在运行时改变对象的引用,动态选择类型(也就选择了行为)。一条通用的准则是:用继承表达行为之间的差别,并用字段表达状态上的变化。
纯继承就是子类不添加新方法,与父类彻底相同。
向下转型与运行时类型识别:Java 中,全部的转型都会检查。
接口和内部类为咱们提供了一种将接口与实现分离的更加结构化的方法。
9.1 抽象类和抽象方法
抽象类是普通类与接口之间的一种中庸之道,尽管在构建有有些未实现方法的类时,第一反应是新建接口,但抽象类还是用于此目的的一种重要而必须的工具,由于你不可能老是使用接口。
抽象类不能实例化,没有对象。没有抽象方法的抽象类,在阻止类实例化时有用。抽象类和抽象方法可使类的抽象性更明确,并告诉用户编译器打算怎样使用它们,此外它们仍是重构工具,把公共方法往上提。
9.2 接口
接口是彻底抽象的类,任何方法都没有具体实现,用来创建类与类之间的协议。Java 容许实现多个接口,相似于多重继承。
接口中的域默认是public static final 的,方法默认是public的。
使用接口和向上转型,能够实现解耦。策略模式-利用参数对象的不一样,而表现不一样的行为。适配器模式-使用组合来适配接口,或使用继承来适配。
能够经过继承接口,来扩展接口,接口能够继承多个接口。
9.3 接口中的域
默认是static final的,经常使用来作常量工具,JDK5以后又枚举类型。不建议使用接口纯粹的表示常量,可使用final class 来表示,再来个private 的构造方法,更安全。
接口中不容许空白final,但能够被常量表达式初始化。
9.4 嵌套接口
接口能够嵌套在类或其余接口中,拥有public和包访问权限。P(186)
使用工厂方法生成实现某接口的对象,为何要添加额外级别的间接性呢?一个缘由就是建立框架,对于复杂的代码可从中受益,也可以使用匿名内部类来实现。
9.5 小结
优先选择类而不是接口,当接口的必需性比较明显时,再进行重构。
10.1 内部类
能够将一个类的定义放在另外一个类的内部,这就是内部类。
内部类不只是一种代码隐藏机制,它了解外部类,拥有外部类全部成员的访问权限。内部类被建立时会秘密的捕获一个指向外部类对象的引用,注意,内部类对象只能与外部类对象有关联。
使用 .this 能够获取外部类对象的引用。不能直接建立内部类对象,只能经过外部类对象使用 .new 建立。
私有内部类,彻底阻止任何依赖于类型的编码,而且彻底隐藏了实现的细节。好比 迭代器模式,Iterator。
10.2 在方法和做用域内的内部类
能够在方法里面或在任意的做用域内定义内部类,有两个理由
在方法的做用域内建立一个完整的类,称做局部内部类。好比在 a 方法中定义一个内部类 inner,此时标识符inner在方法结束仍可用,也就是说能够在同一子目录下的任意类中对某个内部类使用标识符inner,并不会有命名冲突。
在语句块内,好比if语句中,此时不是说该内部类的建立是有条件的,它其实与别的类一块儿编译过了,只是在定义此内部类的做用域以外,它是不可用的,除此以外,它与普通的类同样。
10.3 匿名内部类
返回一个没有名字的类,就叫作匿名内部类。匿名内部类末尾的分号,不是标记此内部类结束的,而是表示表达式的结束。若是内部类要使用一个在其外部定义的对象,而且在内部直接使用,那么编译器会要求其参数引用是final的,为何呢?
这和局部变量的生命周期有关,好比一个方法返回一个匿名内部类的引用,并传递了一个参数,方法结束此参数的生命周期结束,匿名内部类通常都包含回调,此时执行回调,就会找一个不存在的参数,而把参数使用final修饰,基本类型值不变,引用类型引用不变,保证了参数的一致性,此外编译器还会作一些手脚,会把此参数拷贝为匿名内部类对象的成员变量,这样就算局部变量声明周期结束,匿名内部类仍是可以访问其副本。
其实,不只是匿名内部类,好比在静态块中的内部类,在方法中的内部类,等局部内部类,只要涉及到变量声明周期问题的,都须要使用final修改。final修改的变量是拷贝来用的,不是直接使用,它的不可变性也保证了一致性。
对于匿名内部类而言,其字段初始化实际效果就是构造器,只不过没有名字,不能重载而已。匿名内部类既能够扩展类,也能够实现接口,但不能二者兼具,并且若是实现接口,只能实现一个。
10.4 嵌套类
被声明为static类型的内部类,叫作嵌套类。嵌套类与普通内部类对象的区别:
接口内部的类,接口中的全部成员(字段 or 方法)默认都是 public static 的,那么内部的类就是嵌套类,其实嵌套类和普通类没有区别,只是存在的地方不一样而已。
一个内部类被嵌套多少层并不重要,它能透明的访问全部的外部类。
10.5 为何须要内部类
最主要的缘由是,每一个内部类都能独立地继承自一个(接口的)实现,不管外部类是否继承了此(接口的)实现,完善了多重继承。
内部类的其余特性:
10.6 闭包与回调
谈到闭包,想到最多就是js中的闭包,在js中,在一个函数内定义另一个函数就会产生闭包,就是指有权访问另一个函数做用域变量的函数。而在Java中内部类就是面向对象的闭包。
回调,对象可以携带一些信息,这些信息容许它在稍后的某个时刻调用初始的对象。回调的价值在于它的灵活性,能够在运行时动态的决定须要调用什么方法。
10.7 内部类的继承、是否可被覆盖
在内部类构造器中使用使用enclosingClassReference.super()。
不能覆盖,若是继承它的外部类,而且定义一个同名的内部类,不会影响另外一个,也就是说,两个内部类是彻底独立的两个实体,各自在本身的命名空间内。
内部类标识,若是是匿名内部类编译器简单的产生一个数字做标识符;若是是嵌套在其余类中,则使用外部类名字加$加内部类名字。
数组是编译器支持的类型,可以保存一组对象,不足之处是容量固定,通常写程序时并不知道须要多少对象,Java类库提供了一套至关完整的容器类来解决这个问题,基本类型是List、Set、Queue、Map,这些对象又称为集合类。容器有各自的特色,而且大小均可自扩展。
11.1 泛型和类型安全的容器
以ArrayList为例,在没有泛型以前,ArrayList存储的是Object对象,Object是全部类的直接或间接父类,也就是说能够将任何对象放进ArrayList中,在编译期或运行时都不会有问题,可是,当使用get方法取出对象时,获得的只是一个Object引用,必须强制转型为实际类型,不然会获得语法错误。
好比有两个类,Apple和Orange,除了都是Object子类,两个类绝不相关。分别将Apple对象和Orange对象放进Arraylist中,此时经过get获得一个引用,我认为是Apple对象,而后强转为Apple类型,使用Apple对象提供的服务,可是当我拿到的引用其实是Orange对象呢?在运行时会抛出一个类型转换异常,Apple cannot cast to Orange。
使用泛型就能避免此问题,ArrayList<Apple>表示此容器存储的是Apple对象,这样在编译期防止将错误类型放到容器中。当使用get方法时会自动进行类型转换。向上转型也可做用于泛型,即父类及其子类能放在同一个容器中。(泛型远不止保证容器类型安全这么简单.)
11.2 基本概念
容器的用途是“保存对象”,其中有两种不一样的概念:
11.3 添加一组元素
java.util包中的Arrays和Collections类中有不少实用的方法,简单整理以下:
(1)Arrays类
(2)Collections类
容器的toString方法都提供了可读性良好的输出结果。
11.4 容器
(1)List接口在Collection接口之上,添加了大量方法,可在List中间插入和删除元素。有两种类型的List:
(2)迭代器
迭代器是一种设计模式,它是一个轻量级对象,用来遍历容器中的对象,没必要关心容器底层的结构。Java的Iterator只能单向移动,只能用来:
为何须要迭代器呢?没必要关系容器中元素的个数;不关心容器类型,代码更通用。迭代器统一了对容器的访问方式,将遍历容器的操做与容器底层的结构分离。
ListIterator 是一个更增强大的Iterator的子类型,只能用于各类List类型的访问,能够双向移动。能够产生相对于迭代器在列表中的前一个和后一个元素的索引,而且可以使用set方法访问过的最后一个元素。经过调用listIterator得到一个指向List开始处的ListIterator对象,此外可以使用listIterator(n)指定指向的初始位置。
(3)LinkedList
实现了List接口,添加了可使其做为栈、队列或双端队列的方法。
getFirst(),element()返回列表的头,为空时抛出异常,peek方法与这俩方法稍有差别,列表为空时返回null。
removeFirst()与remove()方法同样,为空时抛出异常,poll为空时返回null。
addFirst(),add(),addlast()三个方法相同,都将某个元素插入到表尾。removeLast()移除并返回列表的最后一个元素。
(4)其余
Java 的基本理念是“结构不佳的代码不能运行”。
12.1 基本概念
Java异常机制的好处:
抛出异常时,首先,使用new在堆上建立异常对象(GC会回收);而后,当前的执行路径终止,并从从当前环境中弹出异常对象的引用;此时异常处理程序(catch块)处理异常。
全部异常都有两个构造函数,一个默认的和一个以字符串为参数的,放异常相关信息。
使用 try 捕获异常,catch 处理异常,catch会依次向下搜索与异常匹配的第一个处理程序,一旦执行完毕,则异常处理完毕,只有匹配的catch块才会被执行。
继承Exception,自定义异常,让编译器自动产生默认的构造函数便可,异常的名称应该能望文生义。
throws 关键字能够在方法名称说明潜在的异常列表;throw 关键字在代码中抛出具体的异常。
RuntimeException 运行时异常,也称为不受检查的异常,这类异常不须要在程序中显式的捕获,由于它表明的是编程错误,如NullPointException、ClassCastException、ArrayIndexOfBoundException…
受检查的异常,编译时被强制检查的异常,如IOException。可经过throw new RuntimeException(cause),转为不检查异常。
12.2 异常
catch(Exception e) { e.printStackTrace(); } 捕获全部异常,并打印异常调用堆栈信息,默认输出到System.error,可以使用其余构造方法进行重定向。getStackTrace()方法返回栈轨迹元素数组,每个元素表示一个栈帧,元素0-栈顶元素是最后一个方法调用,最后一个元素是第一个方法调用。
从新抛出异常,即把刚捕获的异常从新抛出,使用throw 关键字。若是只是抛出当前异常对象,那么printStackTrace()方法显示的是原来异常抛出点的调用栈信息,而非从新抛出点的信息。可以使用fillInStackTrace()方法把当前调用栈信息填入原来异常对象,并返回一个Throwable对象,调用fillInStackTrace()方法的地方就成了异常的新发地。若是抛出另外一个异常对象,效果相似于调用fillInStackTrace,可是原来异常的信息会丢失。
捕获一个异常后抛出另一个异常,而且保存原始异常信息,此为异常链。有两种方式:一是使用Throwable(Throwable cause)构造方法传递原始异常;二是使用initCause(Throwable cause)连接异常。其中只有Error、Exception、RuntimeException有带cause参数的构造方法,其余异常使用initCause方法连接。
12.3 使用 finally 清理
finally子句用来作一些清理工做,好比打开的文件或网络链接。finally块老是被执行:
为了防止异常丢失,要把全部可能抛出异常的方法放到try-catch子句中。
12.4 异常限制
当覆盖方法时,只能抛出在基类方法的异常说明里列出的那些异常。异常限制对构造方法不起做用,子类构造方法不能捕获父类构造方法抛出的异常。
编写构造方法时不要作太多处理,一个原则就是尽快让对象进入一个稳定状态。
字符串操做时程序设计中最多见的行为。
13.1 String
String 是不可变对象,对其进行的操做均不会影响原有的字符,而是返回一个全新的String对象。使用”+”链接字符串,大量的链接会产生一堆垃圾中间对象,虽然编译器会自动优化,但最好的方式是使用StringBuilder.append,不要使用append(“a”+”b”),编译器会建立另外一个StringBuilder处理括号内的字符串操做。
无心识的递归,当打印类信息时,在toString()方法中使用this关键字,其实this默认调用toString()方法。
String上的经常使用操做(下标从0开始):
为了节省内存和提升运行速度,对于基本类型和String类型,Java提供了常量池的概念,对于String,直接使用引号会直接存在常量池中;不使用引号的String对象,可以使用intern方法,尝试将当前字符放入常量池。好比在JDK7中:
13.2 格式化输出
和C的printf()风格同样的格式化输出,Java中格式化功能都由java.util.Formatter类提供。构造Formatter实例时能够指定结果输出目的地,如Appendable(StringBuilder)、OutputStream、File。
格式化说明符,用来控制空格与对齐。%[argument_index$][flags][width][.precision]conversion,说明以下:
String.format()方法,内部会新建一个Formatter对象,屡次调用不要使用此方法。
13.3 正则表达式
强大而灵活的文本处理工具。解决字符串匹配、选择、编辑以及验证问题。Java 中对象反斜杠\ 的处理,\\ 表示插入一个正则表达式的反斜杠,也就是其后的字符有特殊意义或者用来转义,如 \\d 表示一位数字,\\\\ 表示普通的反斜杠,\\+ 表示字符+。
String对象提供了丰富的字符串操做,若是正则表达式不止使用一次的话,非String对象的正则表达式具有更加的性能。虽是这么说,但写出高性能的正则表达式有难度,不必定比API快。
特殊字符:\t制表符,\n换行符,\r回车,\f换页,\e转义(Escape)
预约义类:.(点)任意字符;[abc] 包含a,b,c任一字符和a|b|c相同;[^abc] 除了abc以外的字符;[a-zA-Z] a到z或A到Z的字符;[abc[hij]] 任意abchij字符;[a-z&&[hij]] 任意hij字符;\s 空白符(空格、tab、换行、换页、回车);\S 非空白字符[^\s];\d 数字[0-9];\D 非数字 [^0-9];\w 词字符[a-zA-Z0-9];\W 非词字符[^\w]
逻辑操做符:XY Y跟在X后面;X|Y X或Y;(X) 捕获组,可在表达式中使用\i引用第i个捕获组
边界匹配符:^ 一行的起始;$ 一行的结束;\B 非词的边界;\b 词的边界;\G 前一个匹配的结束
量词:X? 一个或零个;X* 零个或多个;X+ 一个或多个;X{n} 刚好n个;X{n,} 至少n个;X{n,m} 至少n个但不超过m个
量词表达式分为:贪婪型,为全部可能得模式进行匹配,默认;勉强型,知足模式的最少字符,使用?;占用型,防止匹配失败回溯,使用+。表达式中的X一般使用圆括号括起来(X)。
组(Groups),用括号划分的正则表达式,能够根据组号引用某个组,组号0为整个表达式,组号1表示第一对括号括起的组,以此类推。如 A(B(C))D ,其中有三个组,组0为ABCD,组1为BC,组2为C。
13.4 Pattern 和 Matcher
java.util.regex包提供了更增强大的正则表达式对象。
Pattern.complie(String regex) 和 Pattern.complie(String regex, int flag)方法编译正则表达式,使用matcher()方法生成一个Matcher对象。其中 flag 能够调整匹配的行为,经常使用的常量有:
多个 flag 使用 | 分割,如Pattern.complie(“^java”, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE),匹配以 Java 开头的行,不区分大小写,对每一行(从字符序列的第一个字符开始,到每一行终结符)都进行匹配。
Matcher对象,有如下几种方法:
替换操做,replaceFirst()、replaceAll()替换时只能使用一个固定的字符串,appendReplacement(buf)执行渐进式的替换,能够对匹配到的字符串作一些额外的操做,如转成大写,appendTail()将剩余的部分追加到buf中。
13.5 扫描输入
Scanner 类,能够接受任何的输入对象,包括File、InputStream、String或Readable对象,使用hasNext()和next() 遍历。Scanner会把IOException吞掉,可以使用ioException()方法,找到最近发生的异常。
Scanner 默认使用空格做为定界符,可以使用useDelimiter()设置定界符。其余用法可查看API文档。
StringTokenizer,有了正则和Scanner这个类能够不用了。
14.1 RTTI
运行时类型信息(RTTI,Runtime Type Information)使得能够在程序运行时发现和使用类型信息。
Java 有两种方式让咱们在运行时识别对象和类的信息:一是假定在编译时已经知道了全部的类型;二是使用“反射”机制。
运用多态时,子类会向上转型为父类,丢失了具体类型,客户端拿到了一个泛化的引用,而使用RTTI能够查询某个泛化引用的确切类型。
14.2 Class 对象
Class 对象就是一个类在运行时在 JVM 中的表示,JVM 按需加载类,使用Class.forName(“className”) - 全限定名,手动触发加载,得到对象的引用。
CLass.getInterfaces() 获取对象接口信息;Class.getSuperclass() 获取父类信息;Class.newInstance() 建立一个对象,尽管不知道它的确切类型,此类必须有无参构造方法。
得到CLass对象的引用的另外一种方法是使用类字面量,如String.class,简单,安全,高效,编译时就会检查,没必要使用try-catch块。
类字面量可应用于普通的类、接口、数组、基本数据类型及其包装类型。包装类型还可以使用.TYPE,但最好使用.class。使用”.class”建立Class对象引用时,不会自动初始化该对象,即不会触发如下动做:加载,查找字节码建立Class对象;连接,验证字节码,为静态域分配空间,解析符号引用;初始化,如有父类,则对其初始化,执行静态初始化块。
使用static final 修饰的字段,若是在编译阶段肯定了具体值(编译期常量),那么直接引用不会触发类的初始化。一个static若是不是final的,那么在使用前先进行连接和初始化。
泛化的Class引用。Class引用指向的就是对象的确切类型,可经过泛型语法,让编译器强制执行额外的类型检查。经常使用的是使用”?”和“?extends Class”,如Class<?> clazz = Number.class,Class<? exnteds Number> clazz = int.class。
14.3 类型转换前先作检查
强制类型转换,(Shape) 运行时若是转换错误,会抛出ClassCastException。
instanceof ,Class.isInstance(Object obj) 判断对象是不是某个类的实例,指的就是你是这个类吗,或者你是这个类的派生类吗?
14.4 反射:运行时的类信息
反射能够用来检查可用方法和方法名,能够跨网络远程平台建立和运行对象,即RMI - 远程方法调用。Class类和java.lang.reflect包提供了对反射的支持,经过API匿名对象的信息能在运行时彻底肯定下来,而在编译时不须要知道任何事情。
RTTI 与反射的区别在于:RTTI 在编译时打开和检查.class文件;反射在运行时打开和检查.class文件。
动态代理,Java能够经过Proxy.newProxyInstance()和InvocationHandler来实现动态代理。
空对象,使用内置的null,每次使用时都要判断是否为空,而null除了在你试图用它执行任何操做时产生NullPointerException,没有其余任何行为。能够引入空对象的思想,它接受它所表明的对象的消息,返回一个实际上不存在任何真实对象的值,可避免没必要要的精力去检查null。
接口与类型信息,经过反射可以获得任何你想要的信息,关于一个类。
通常的类和方法只能使用具体的类型:基本类型和自定义的类。若是编写可应用在多种类型的代码,此限制对代码的束缚比较大。
多态算是一种泛化机制,但依赖继承和接口;泛型实现了参数化类型,使代码应用于多种类型,Java泛型的核心概念就是告诉编译器想使用的类型,编码器会自动检查类型的正确性和自动转型。
15.1 泛型
泛型类和接口,只需在类名或接口名后添加泛型类型便可,如Stack<T>,Generator<T>,基本类型没法做为类型参数,会自动打包拆包。
泛型方法,可以独立于类而产生变化,static方法须要使用泛型能力,必须使其成为泛型方法。定义泛型方法只需将泛型参数,如public <T> void fun(T x){ };
当使用泛型类时,须要在建立对象的时候指定类型参数的值,而调用泛型方法时,一般不须要指明参数类型,编译器会自动找出具体的类型,此为类型推断。类型推断只在赋值操做中有效,若是不是赋值操做,编译器不执行类型推断,此时编译器认为,泛型方法的返回值赋值给一个Object类型变量。
JDK7以后,能够这样声明一个容器,Map<String, String> maps = new HashMap<>();
在泛型方法中能够显式的指明类型,在点操做符和方法名之间插入尖括号,而后把类型置于尖括号内。如 New.<Person, List<Pet>>map(); P363
泛型方法和可变参数列表能很好的共存:如 public static <T> List<T> makeList(T… args){ };