之前主要用的C++的比较多,之前花了快2个月的实际认真系统全面的学习了以下java的基础语法,《java编程思想》这本书翻译水平确实不是很好,很多话读着会比较拗口。推荐读之前,先去网上搜索 “java编程思想勘误”,当然,有时间,最好还是直接读英文版。
网上书评价这个本书不太适合初学者,确实,站在计算机零基础的人的角度上,坚持读完确实是一个很大的问题,然后如果你有C++的基础,或者计算机学习的经历,相对来说确实会容易些。然而,不可否认,这确实是一本经典,在讲述java语法的同时,把面向对象的思想解释的淋漓尽致,它教给你的更是一种编写程序的思维方式。因此,过段时间我打算再通读这本书几次。
接下去,放上我的读书笔记,都是一些书中重点的归纳,看这个读书笔记,同时回想知识,相当于又温习了一遍这本书。
Thinking in java
ch2 一切都是对象
1. java对象引用存储于栈中,java对象都存储于堆。(C++中对象的位置取决于上下文)。基本类型存栈中,都有正负号:boolean大小不确定,char 2bytes,long 8bytes。高精度数字类BigInteger和BigDecimal。
2. 类中new一个对象:bool初始化为false,基本类型0,0.0,对象为NULL,方法的局部变量不初始化会出错。
ch3ch4 操作符
1. 几乎所有操作符只可操作基本类型,除了=,==,!=,能操作所有对象,String支持+,+=,用于连接字符串,若表达式以字符串开头,后续所有操作数必须是字符串型,若不是就强制转换。注意表达式的提升现象。
2. 注意避免别名问题(操作的实际是引用)。并且==比较的也是引用,比较实际内容用equals(),基本类型的包装类已被重载,其默认行为是比较引用。
3. 一元加号可以将较小数据类型操作数提升为int。
4. >>>无符号右移位操作符,>>默认有符号右移。若右操作数超过范围就取余。若对byte或short值移位,可能得到不正确的结果:先被转换为int,再右移,截断,赋值给原来类型。
5. java中的条件只能是boolean值,不同于C/C++。不可对boolean取~,且!只能对布尔型操作,与或非(||)两边必须为布尔值。
6. float、double转为整型总是执行截尾,可调用round()方法舍入。
7. 对数组和容器可用foreach语法,range()方法指定范围,效率稍微降低。
8. continue和break可接标签。
ch5 初始化与清理
1. 每个重载的方法必须有一个独一无二的参数类型列表。
2. 除了char被提升为int(若有byte/short),其他若没有响应的类型,则提升到最接近的。
3. 如果自己定义了一个构造器编译器就不会创建默认的构造器。
4. 在构造器中调用构造器可用this(class name)调用:必须位于开头,且只可调用一次。
5. 一旦垃圾回收动作发生,首先调用finalize()方法,并在下一次垃圾回收动作发生才真正回收对象所占的内存。要知道调用了finalize()函数对象仍可能不被垃圾回收。
6. System.gc()提醒希望虚拟机进行一次垃圾回收,而垃圾回收动作与否是不确定的。
System.runFinalization()强制调用已失去引用对象的finalize()。
7. 垃圾回收的一种方法,自适应的垃圾回收技术:停止-复制和标记-清扫之中切换。从栈和静态区出发,遍历所有引用找到存活的对象。
8. java可以直接在类中变量声名处初始化变量。构造器初始化之前会执行自动初始化基本类型和对象引用。
9. 对静态类型的初始化:只有在所对应的类被加载才启动,先初始化当前类静态变量(包括块),然后是非静态,然后是基本类型。
有个名为Dog的类,单个类创建对象的过程如下:
a) 找到Dog.class文件并载入。
b) 静态对象初始化,存储区域清0,然后是new出来的对象并被置NULL,之后是基本类型置0。
c) 执行字段定义处的初始化操作。
d) 执行构造器。
10. 变量定义的先后顺序决定初始化顺序。静态方法中不能调用非静态变量。静态变量还可以在静态块static{}中初始化。非静态示例也可以只是少了static关键字。
11. 可变参数可以由Object类+foreach语句实现,并且任何一个类都可以实现可变参数,当有多个重载方法时选择最佳匹配。还要注意的是:带可变参数的重载方法,所带不可变参数形式也要一致。
12. enum中可用ordinal()得到声明的顺序,并且会产生一个static values()方法按照声明顺序产生一个数组。
ch6 访问权限控制
1. 一个编译单元(.java)的类名称需与文件名称相同,且在一个编译单元内最多一个public。java可运行程序由一组.class文件构成,java解释器负责查找,装载,解释,查找方式:
a) 找出环境变量CLASSPATH(根目录可含多个路径,;分隔)
b) 将句点都换成/,将路径与环境变量连接
2. importstatic语句导入使用类的静态方法,简化了操作。
3. 包访问权限对同个包中非private成员大家都可访问,但对其他包的来说该成员是private。若当前2个文件无包名,位于同个目录下,就当做是默认包权限。
4. 单例设计模式:private static Soup2 ps1 = new Soup2();
public static Soup2access(){return ps1}
同时说明了不能因为在类中某个对象的引用是private,就认为其他对象无法拥有该对象的public引用。
5. 类的访问权限仅有包访问权限和public。
ch7 复用类
1. 组合:在新类中产生现有类的对象。通常用于想在新类中使用现有类功能而非接口。
继承:用现有类的形式并在其中添加新代码。(是否需要向上转型?)
代理:新类使用基类对象,不用继承,在新类中重新封装。
2. print(类名)会调用类的toString()方法。
3. 每个类都有一个根基类Object。
4. 在继承类后,若导出类是无参构造器,java会在导出类的构造器中自动调用基类的无参构造器。若导出类构造器有参,需要在其构造器显示调用基类构造器,如super(i)。
5. 若要自己手动定义清理函数,清理的顺序要与构造的顺序相反。比如清理动作放在finally块(无论try任何执行结果,finally总会执行)。
6. 若基类含一个已被重载多次的方法名称,那么在导出类中重新定义该方法并不会屏蔽基类中的任何方法,至少需要返回值,参数不同区分一个重载方法。
7. @override注解防止你在不想重载时意外的重载。
8. final关键字:
a) final数据:必须在定义处或构造器中初始化。
i. 编译时常量,必须为基本数据类型。
ii. 运行时初始化的值,而你不希望它被改变。对对象引用,final使引用恒定不变。如private final int[] a,其中a[i]还是可以改变的。
iii. finalstatic只占据一段不能改变的存储空间。即无论生成多少个对象,该成员值仅在第一次创建被初始化。编译时常量和运行时常量都有可能。
b) final方法:禁止覆盖该方法。private方法不是基类接口的一部分,属于隐藏在类中的代码,对其他类是不可见的。
c) final类:不能被继承。
ch8 多态
1. 多态:也称作动态绑定。回想动态绑定的使用方法。增加了程序的可扩展性,并且将改变的事物与未变的事物分离开来。
向上转型:把对某个对象的引用视为其基类型的引用的做法被称作向上转型。
2. java中除了static和final方法(private方法属于final方法)之外,其他所有的方法都是后期绑定。
3. 只有非private方法才可以被覆盖,在导出类中对基类中的private方法,最好采用不同的名字。
4. 只有非静态方法调用才可以是多态的,不包括基本数据类型成员。静态方法与类相关联,而不是与某个对象。
5. 包含继承的类初始化过程:
a) 父类静态成员,静态块按照声明顺序初始化。
b) 子类静态成员,静态块按照声明顺序初始化。
c) 父类基本类型,普通成员初始化,构造器执行。
d) 子类基本类型,普通成员初始化,构造器执行
6. 注意一种情况:如果在父类中方法中调用子类未初始化的成员会招致灾难,(相当于上面第三步调用子类的成员)此时调用的成员为0还未被子类初始化。在构造器唯一能安全调用的是那些基类本身的final方法。构造器中应该尽量避免调用其他方法。
7. 协变返回类型:在导出类的覆盖方法处,可以返回基类被覆盖方法返回类型的某种导出类型。协变返回类型允许返回了更具体的类型而不一定是基类。
8. 更好的方式是首先选择“组合”,尤其是不能十分确定应该使用哪一种方式时。继承在编译时就需要知道确切类型。我们可以在类中定义一个基类对象,定义一个函数改变引用的指向从而实现动态灵活性。
9. 继承:is-a is-like-a关系在导出类包含了扩展接口
10. 子类对象赋给基类对象后,我们还可以进行向下转型,在运行期间对类型进行检查的行为称作“运行时类型识别”RTTI,向下转型失败时,抛出一个异常。
ch9 接口
1. abstract关键字定义了抽象类和抽象方法,抽象类中可以包括已经定义的方法。抽象类的另一个作用是:阻止产生这个类的任何对象。抽象基类中声明一个抽象方法接受基类引用,子类中定义该方法后,传入子类引用不需要向下转型。P172
2. interface产生一个完全抽象的类,没有提供任何具体实现,且接口中的域隐式的为final和static。与implements关键字配套使用。且接口中被定义的方法必须是public的。当接口类被当做形参时,无法区分它是一个普通类,抽象类还是接口。
3. 一般来说,只可以将extends用于单一类,但是可以引用多个基类接口。
例如:interface A extends B,C{}其中ABC均为接口
4. 策略设计模式:创建一个能根据所传递的参数对象的不同而具有不同行为的方法。P175最简单就是利用多态,多个子类覆盖一个方法。基类参数接受子类引用即可。
5. 对2个不同无关的类(比如一个接口和类),成员有相同的形式,且当无法修改你想要使用的类,可使用适配器设计模式:接受你所拥有的接口,并产生你所需要的接口。P177
6. 没有任何与接口相关的存储,因此一个类可以实现多个接口,但是只能extends一个。
7. 当想要创建对象时,所有的定义都首先必须存在。
8. 接口的优点:
a) 为能够向上转型为多个基类型。
b) 防止创建该类的对象。
接口常与向上转型一起使用P180
9. 重载方法仅仅通过返回类型是无法区分的。如果在打算组合的不同接口中使用相同的方法名通常会造成代码可读性的混乱,应该尽量避免。
10. 适配接口的含义:“可以用任何对象来调用我的方法,只要你的对象遵循我的接口”P182。因此得出一个重要结论:让方法形参接受接口类型,是一种让任何类都可以对该方法进行适配的方式。
11. 因为接口中的域是static的,初始化发生在第一次被加载时(被首次访问时)。
12. 嵌套接口P185,注意类中的私有接口,无法得到他的实例,即使通过方法返回值也无法保存,只能当做同个类其他方法的形参传入。
13. 工厂方法:不是直接调用构造器,而是在工厂对象上调用创建方法,而该工厂对象将生产接口的某个实现的对象。实现了我们的代码与接口的分离。P188。
14. 当必需时,更应该重构接口而不是导出添加额外级别的间接性(创建接口和工厂)。恰当的原则应该是优先选择类而不是接口。
ch10 内部类
1. 类中的静态方法可以有自己的局部变量,调用一次静态函数就销毁,不可以直接使用类中的非静态成员,必须实例化后才可用。比如main可以直接调用同个类中static成员。
2. 若想从外部类的非静态方法之外的任意位置创建某内部类的对象,需明确指明内部类的类型:OuterClassName.InnerClassName,通常定义一个函数来获得对内部类的引用。而且必须要先创建一个外部类对象。同个类的main中则不用。
3. 内部类拥有对其外围类的所有元素的访问权,包括私有变量。非静态内部类是通过秘密地捕获一个指向那个外围类对象的引用做到的。
4. 通过在内部类的函数return 外类名.this显式获得对外部类的引用。
5. .new用法:比如在同个类的main中,必须先创建一个外围类,再创建一个内部类:(除了为创建嵌套类即静态类)。注意在同个类的非静态函数中,可以直接new Inner();
a) Outerou = new Outer();
b) Outer.Innerin = ou.new Inner();
6. 内部类可以return子类型或接口的实现,但是返回值为基类或接口类型,从而实现了向上转型,隐藏了实现细节。
7. 外部类可以访问内部类的private元素,但是需要通过外围类的函数成员。
8. 在方法的作用域内创造一个完整的类称为局部内部类,此时的类在函数外就不可用了,并且不能有访问说明符。当我们加上if时,在if外就不可用了,但此时不论if的条件真假,类都已经编译过,只是为假时,不执行if内部的class之外的语句。
9. 创建一个继承自Contents的匿名类的对象,并通过new表达式返回的引用被自动向上转型为对Contents的引用:
a) publicContents contents(final Stringdest){ //引用一个外部定义的要在匿名类中使用的对象需要final
return new Contents(x){ //可传入带参数的构造器,基类要有对应构造器
类成员;
}; //分号表示表达式的结束
}
10. 匿名类中产生构造器的效果就是通过实例初始化,即通过传入的参数。
11. 匿名类结合工厂方法P200:
public static ServiceFactory factory =
new ServiceFactory(){
public Service getService(return new Impletation1() );
};
12. 普通的内部类不能有static数据、方法、嵌套类,因为内部类要持有外部类的指针,静态方法和成员独立于实例存在与之矛盾。但是嵌套类可以包含这些东西。
a) 当你想要创建某些公共代码,使得他们可以被某个接口的不同实现所共有,可以在内部类中实现其外围接口。
b) 可以使用嵌套类存放包含main的代码,在发布时可以删除该嵌套类。外围类并未携带任何嵌套类的代码。
13. 内部类最吸引人的原因:每个内部类能独立的继承自一个接口的实现,相当于实现了“多重继承”,即内部类允许继承多个非接口类型。同时获得的特性:
a) 内部类可由多个实例,每个实例都有自己的状态信息。内部类就是一个独立的实体。
b) 单个外围类中,可让多个内部类以不同方式实现同个接口或继承同个类。
14. 内部类可以提供闭包的功能,通过定义一个函数return 内部类;返回接口类型实现。并且在外部类中通过传入引用的不同,可以实现回调,即运行时动态决定调用方法。
15. 内部类的另一个作用就是将不变的事物与变化的事物相互分离。比如内部类继承一个抽象类,实现其中的抽象方法。类似于“多重继承”。
16. 如果只是继承一个内部类,构造器必须接受外围类对象enclosingClassReference,并且调用enclosingClassReference.super();
17. 如果要覆盖一个内部类的方法必须明确继承这个内部类而不是继承他的外围类。且当调用这个方法时会先调用基类的方法,再调用被覆盖方法。
18. 局部内部类和匿名内部类的比较:
a) 我们需要一个已命名的构造器,或需要重载构造器使用前者。
b) 或者需要不止一个该内部类的对象,要要使用局部内部类。
19. 内部类也包含一个.class文件存储他们的信息,命名方式:外围类名$内部类名,若是匿名类编译器会简单地产生一个数字作为标识符。
ch11 持有对象
1. 使用泛型可以在编译期防止将错误类型的对象放置到容器中。向上转型也可作用于泛型。
2. Arrays.asList()方法接受一个数组或是一个用逗号分隔的元素列表并转化为一个List。当多层继承时,必须要Arrays.<Snow>asList();来显式类型参数说明,下面一种方法则不用。
Collection.addAll(collection,moreInts);为首选方式。
3. List:一种可修改大小的序列
a) ArraysList:擅长随机访问元素,插入和移除元素较慢。
b) LinkedList:擅长插入和删除元素,随机访问较上者慢。但是特性集较上者大。可用于实现栈、队列、双端队列。包括的方法见JDK文档。
i. 其中poll()移除头并返回,列表为空时返回NULL,其他则抛出异常。
ii. offer()表示添加到末尾。
iii. 在实现栈中,当名称与JDK中的stack冲突时,通过显示的导入来控制对首先Stack实现的选择。
c) 几个常用方法:都受equals()函数影响。
i. contains(),containsAll()
ii. remove(),removeAll()
iii. subList()得到一个子集
iv. set(),在第一个参数位置处,用第二个参数替换整个位置的元素
v. 包含一个重载的addAll()方法,可以用于插入中间
vi. toArray()方法,将任意的Collection转换为一个数组
d) LinkedList派生于Queue,Collection包含了一个可用的Queue。
i. peek和element不移除的情况下返回队头。队列为空时peek返回NULL,后者抛出异常。
e) PriorityQueue声明下一个弹出元素是最需要的元素,默认允许重复,升序打印,可以在调用其构造器时第二个参数,使用Collection.reverseOrder()产生反序的Comparator。
4. Set:主要用于查找。插入操作只可插入不存在的元素。Set与Collection具有完全一样的接口,没有添加任何额外的功能。
a) HashSet:基于散列函数,获取元素最快。
b) TreeSet:基于红黑树,按升序排列。
c) LinkedHashSet:使用了散列加链表,按照被添加的顺序排列。
d) 常用的操作:
i. set1.contains(“H”);是否包含H元素。同样有containsAll()方法。还有removeAll()方法。
ii. 可以使用TextFile打开一个文件并读入一个Set中。默认字典序,若要改为字典序向TreeSet构造器传入String.CASE_INSENTIVE_ORDER
5. Map:
a) HashMap:速度最快。
i. 典型用法:Integer freq=m.get(r);
m.put(r, freq == null ? 1 :freq+1);
ii. 常用方法:containsKey(xx),containsValue(xx)。keySet(),values() 返回此映射中包含的键的 Set 视图与Collection视图。
b) TreeMap:升序保存键。
c) LinkedHashMap:按照插入顺序保存键。
6. 迭代器的作用:不重写代码就可以应用于不同类型的容器,将遍历序列的操作与序列底层的结构分离,即统一了对容器的访问方式。
a) next()获得序列中的下一个元素
b) hasNext()检查序列中是否还有元素
c) remove()将迭代器新近返回的元素删除,即remove()前必须先调用next().
d) ListIterator属于Itetator的子类型,但可以双向移动。可以看做初始
it.nextindex = 0,it.preindex = -1,执行一次next()两者都加1,previous则两者都减1。我们得到的next()元素就是序号nextindex处的元素。我们还可以ListIterator<Pet> it = pets.listIterator(3)直接操作指定序列处的元素。
7. Collection和Iterator P240
a) 接受参数为Collection:当要实现一个不是Collection的外部类时,可通过继承AbstractCollection并实现其中的iterator()和size()方法。
b) 接受参数为迭代器:继承并且提供创建迭代器的能力实现起来容易的多。只需要在新类定义一个Iterator<xx>()内部类,并在其中实现next,hasNext,remove方法。
8. 所有Collection对象都能与foreach语句一同工作。其中原理是因为实现了Iterable的接口包括next,hasNext和remove三个方法。
9. 迭代器形参不接受数组类型,但可以接受Arrays.asList()的返回值。
10. 假如想要添加多种在foreach语句中使用这个类的方法,可采用适配器模式。P243
11. Arrays.asList()产生的List对象会使用底层数组作为其物理实现,如果你执行的操作会修改这个list,而你并不想其被修改,你就应该在另一个容器中创建一个副本:
List<String> shuffled = new ArrayList<String>( Arrays.asList(words));
12. 容器不能持有基本类型,但是自动包装机制会仔细地执行基本类型到容器中所有的包装类型之间的双向转换。
13. 除了TreeSet之外的所有Set都拥有与Collection完全一样的接口。Map和Collection之间的唯一重叠就是Map可以使用entrySet和values方法来产生Collection。Queue接口都是独立的,因此在创建具有Queue功能的实现时,不需要Collection方法。
ch12 通过异常处理错误
1. 异常发生时:
a) new一个异常对象
b) 弹出对异常对象的引用
c) 进入异常处理程序
2. 终止模型与恢复模型。
3. 创建自定义异常:class My extends Exception(){ super(msg) }本例还调用了基类的带参数构造器。
4. throws声明一个异常,表明此方法将产生异常,假如方法的代码里产生了异常却没有处理,编译器将提醒你声明或者处理。不声明表示不会产生异常。
throw表明抛出一个异常。
5. e.printStackTrace();输出信息到标准错误:从方法调用处到异常抛出处的方法调用序列。
6. java.util.logging提供了记录日志的功能:我们通常需要捕获和记录其他人编写的异常,因此通常在catch中调用生成日志方法。P254
7. 可以通过覆盖Throwable.getMessage()方法得到更详细的信息。
8. 通过catch(Exception e)可以捕获所有异常,最好把它放在处理程序列表的末尾。Exception是所有的基类,它的基类是Throwable类。Throwable类分为Error和Exception两种类型。
9. e.getStackTrace()得到一个栈轨迹的数组,元素0是栈顶,就是异常被抛出处。
10. 重抛异常会把异常抛给上一级环境中的异常处理程序,同一个try块的后续catch子句将被忽略。P258
a) throw(Exception)e.fillInStackTrace( );打印新的栈轨迹,否则打印原来的栈轨迹(包括重抛异常经过的方法)。捕获一种异常后抛出一种异常的做法与此情形类似。
b) 构成一个异常链:ExA a = new ExA( ); //创建一个异常对象
a.initCause(newNullPointerException( ));
throw a;
如果在下面的catch块捕捉ExA异常并打印栈轨迹,会有NullPointer异常的信息一同打印出来。
11. 只能在代码中忽略RuntimeException类型的异常(未处理的话,main结束自动打印栈轨迹),其他类型异常处理都是由编译器强制实施的。
12. 当要把除内存之外的资源恢复到他们的初始状态时,就要用到finally子句,比如文件描述符。
a) 可以保证在方法有多个点返回的情况下清理工作仍然执行。
b) 但有可能会因为覆盖而导致异常的丢失。
13. 异常限制:覆盖方法时,只能抛出在基类方法的异常说明里列出的那些异常。
a) 基类抛出异常,子类覆盖后方法后要么不抛,要么抛出与基类一样的基础,要么抛出基类异常的派生类。
b) 接口f()抛出异常A,其中的f()定义来自基类并抛出异常C,子类继承基类并实现该接口后覆盖f()必须抛出与基类相同的异常C。
c) 条件同上,基类中没有同名方法,可以实现该接口抛出异常A。
14. 如果用法恰当的话,直接向上层抛出异常能简化编程。对于在构造阶段可能抛出异常,并且要求清理的类,最安全的使用方法就是使用嵌套的try子句。通常要求应该创建不能失败的构造器。
15. 对一个基类异常来说,其异常的派生类也可以匹配基类异常。
16. C属于弱类型语言,C++和java为强类型静态语言,编译时就做类型检查。反射和泛型就是用来补偿静态类型检查所带来的过多限制。
17. 可以把被检查的异常包装进RuntimeException中,相当于“屏蔽”。
ch13 字符串
1. String类的方法实际传递的只是原对象引用的一个拷贝。只有当方法运行,其局部引用才存在,返回的引用也指向了新的对象,原本引用还在原地。
2. 反汇编java代码:javap -c xxxx 其中-c表生成JVM字节码
对String的+的重载,编译器使用了效率更高的StringBuilder类的append()方法。当你为一个类编写toString()方法时,如果你要在其中编写循环,最好明确的创建一个StringBuilder对象,这样可以避免产生过多的StringBuilder。
3. 在toString中打印对象的内存地址:super.toString()。直接调用this.toString()会造成递归调用。
4. 注意String的length()和数组上的length
5. System.out.format用法与C中printf()用法一致。
6. 格式化功能可以由java.util.Formatter类处理,构造其对象时传入一个参数System.out或者System.error。之后直接调用它的format函数即可。
a) f.format(“%-15s%5s %10.2f\n”, “Tax“, “”, total*0.06);其中-表示左对齐,默认右对齐。
b) 对于format中的转换,char不可转整数,浮点。对于boolean基本对象或其包装类,其转换结果永远是true或false。但对其他类型,只要不为null,永远都是true。
7. String.format与前一个用法一样,但返回一个String对象。
8. 打开、读入二进制文件:net.mindview.util.BinaryFile。
System.out.println(format(BinaryFile.read(“Hex.class”)))在format中新建一个StringBuilder对象result保存结果,逐字节遍历,存入其中,最后returnresult.toString();
9. java正则表达式中表示一个普通的反斜杠:。第一个斜杠转义了第二个,第三个斜杠转义了第四个。
10. java对反斜线/的处理不同于其他语言,使用时候需要转义,但是* .之类的特殊字符不用转义就保持特殊含义。
11. -?\\d+ 表示可能有一个或零个负号,后面跟着一位或多位数字
12. (-|\\+)? 表示字符串其实字符可能是一个-或者+,或2个都没有。后一个\转义+,前一个\转义\
13. Java 匹配点无特殊含义的(.) 或 { [ ( ? $ ^ 和 * 前面加双斜框。
java层------正则表达式------实际含义 常采用从实际含义逆推的方法。
14. 最简单应用正则的方法就是用String类的:
a) mathes(“正则”)。前一个\对后一个\转义。
b) split()方法,接受一个正则参数,在原始字符串中与正则匹配的部分在最终结果中都不见了。
c) replaceFirst()和replaceAll()
15. 量词:P299
a) 贪婪型:只要字符串中存在匹配项就一直匹配下去。可能得到多个匹配。
i. X?一个或0个 X*零个或多个 X+一个或多个
b) 勉强型:最小匹配的。
c) 占有型:会产生很多状态以便在失败时回溯。
16. Patternp = Pattern.compile(正则表达式);
Mathcher m = p.mathcher(目标字符串);
17. "(?i)((^[aeiou])|(\\s+[aeiou]))\\w+[aeiou]\\b“ \s空格 \w词字符
18. Matcher类中lookingAt()有正则表达式与输入最开始的第一部分匹配就成功
matches()在正则表达式与输入整个部分匹配就成功
19. Pattern.compile()可加第二个参数 | 分隔,或者直接在正在表达式开头括号内指出()
a) Pattern.CASE_INSENSITIVE(?i)忽略大小写
b) MULTILINE(?m) 开启多行模式
20. print(Arrays.toString(Pattern.compile(“!!“).split(input)));
21. Matcher对象的reset()方法可将现有的Matcher应用于下一个新的字符序列。
22. \\b[Ssct]\\w+ 搜索以Ssct开头的单词
23. \\w+\\.{1}\\w+搜索词字符开头类似于: xx.xx 形式的字符串(查找.java文件)
^$匹配空行
24. publicstatic BufferedReader input = new BufferedReader(
new StringReader(“This is a test”) ) //假设属于类SimpleRead
a) 把String类转换为流并作为BufferedReader构造器的参数。
b) Scannerstdin = new Scanner(SimpleRead.input);接下去可以直接使用stdin.nextInt()等方法,next()方法得到下一个String。
c) Scanner类有一个假设,输入结束会跑出IOException,所以Scanner类把IOException吞掉。我们仍可以通过ioException找到最近发生的异常。
d) 默认情况Scanner类使用空格作为定界符,使用useDelimiter()可以用正则表达式指定自己所需的定界符。delimiter()方法返回正作为定界符使用的Pattern对象。
e) 可以配合用正则表达式扫描字符串P311重要
ch14 类型信息
1. 运行时识别类型信息实现包括两种方式
a) RTTI:编译时打开检查.class文件。主要包括三种形式:
i. 在.class对象上
ii. instanceof
iii. 强制类型转换(父子类的转换,有可能造成编译时错误)
b) 反射:运行时打开检查.class文件
2. 类加载的过程:首先类加载器检查这个类的Class对象是否已经加载,未加载则默认加载器根据类名查找.class文件,在这个类的字节码被加载时,会先验证保证完整和安全。
3. Class.forName(“Gum”);运行时返回Class对象的一个引用并指向Gum。
Fancytoy.class使用类字面常量,编译时检查,更加高效。步骤:加载,链接(验证字节码),初始化。
4. Class的几个常用方法:
a) getSimpleName,getName,isInterface,getSuperclass,getInterfaces
b) getClass根据对象获取Class引用
c) 使用newInstance创建的类必须带默认构造器
d) getDeclaredFields得到声明的数据,可保存在Object数组中。getDeclaredMethod得到声明的方法。
e) getMethods得到可公共访问的方法和接口,包括继承过来的接口和方法
5. 我们可以通过getSuperclass和instansof来区别基本类型和对象。
6. 对static final的访问可以在初始化类之前,但是也可能作为运行时常量,此时访问它就必须先初始化类。而对static非final的变量访问则是在链接和初始化之后。
7. 使用泛化的Class在编译期就可以检查出错误而不是运行时才发现。
a) 放松限制:Class<? extendsNumber> a = int.class;
b) Class<?super Interger> b = a.getsuperclass(); b的类型不能Number,并且当调用newInstance()的时候返回类型也不能是具体类型,而得是Object。
8. privatestatic List<Class<? extends Pet>> types =
newArrayList<Class<? extends Pet>>();
a) 第一种添加方法,必须使用转型:types.add(
(Class<?extends Pet>) Class.forname(name) );
要计数的话就继承一个HashMap<String, Integer>定义count方法,instanceof满足就调对应count。此方法会产生很多的判断语句。
b) 使用类字面常量:public static final List<Class<? extends Pet>> alltypes =
Collections.unmodifiableList(Array.asList(Cat.class, …) );之后再用sublist取子集。
c) 改进(count参数pet):for(Map.Entry<Class<?extends Pet>, Integer> pair
: entrySet())
if(pair.getKey().isInstance(pet)) //动态的instanceof
put(pair.getKey(), pair.getValue() + 1);
d) basetype.isAssignableFrom(type) //type为basetype本身或其子类才返回真。
9. 工厂方法设计模式:将对象的创建工作交给类自己去完成。相当于创建一个内部类,然后其中定义一个方法作为创建类的方式。
10. 涉及比较时,instanceof保持了类的概念包括本身和子类,而==则比较实际的Class对象,没考虑继承。
11. 代理的目的是为了进行额外的操作,并将额外的操作从实际对象中分离到不同的地方。 使用的场合是调试和远程方法调用。
使用代理的步骤如下:
a) 创建一个实现接口InvocationHandler的类,它必须实现invoke()方法。
b) 创建被代理的类以及接口。
c) 通过Proxy的静态方法newProxyInstance(ClassLoaderloader, Class<?>[] interfaces,InvocationHandler h)创建一个代理。
d) 通过代理调用方法。(在一个新类中)
12. 使用代理要注意的地方:
a) 通过invoke()方法的第一个参数就是被代理对象,然后通过该对象调用对应的方法。
b) 额外的处理都放在invoke中。
13. 空对象的意义是可以接受传递给它的所代表对象的消息,但是将返回表示未实际并不存在“真实”对象的值。通过这种方式可以假设所有对象都是有效的。
14. P344匿名类作函数参数时直接实现接口,逗号作为单个实现的结束。
15. 反射可以到达并调用所有的方法,包括private,没有办法可以阻止。P349
a) 通常的步骤:
i. TobeVistedpf = new TobeVisted();
ii. Fieldf = pf.getClass().getDeclaredField(“i”); //访问i变量
iii. f.setAccessible(true);
iv. 之后即可通过f.getInt(pf)等方法访问和修改pf的数据成员了
b) 对final变量在遭遇反射修改时是安全的,系统在不抛出异常的情况下接受任何修改,但是实际上不会发生修改。
16. 面向对象的目的是让我们在凡可以使用的地方都使用多态机制,只在必须的时候用RTTI。比如基类被别人控制,此时就可用RTTI,我们继承它,然后添加需要的方法。且RTTI有时能解决效率问题。应当尽量编写能够进行静态检查的代码。
ch15 泛型
1. 多态算一种泛化机制。泛型的意义就是使代码能应用于某种不具体的类型,而不是具体的接口或类。通常容器只用来存储一种类型的对象。泛型的主要目的之一就是指定容器持有的类型对象,而且由编译器保证类型的正确性。
2. 元组tuple是将一组对象(类型可不同)直接打包存储于其中的一个单一对象。public class TwoTuple<A,B>{…} 之后就可以当做返回值返回了。
3. 不用LinkedList实现自己的链式存储栈:内部定义一个节点类。push方法将设置当前节点和下一个结点为原来的top,并更新top。pop要使用一个末端哨兵判断栈何时为空P357。
4. 接口本身的权限同其他类,其中的变量都是public static final,方法都是public abstract。
5. 要想使某个其他类可以使用foreach语法,接让这个类先实现Iterable<Coffee>接口:之后:public Iterator<Coffee> iterator() 然后在return语句中实现匿名类,在其中实现hasNext,next,remove方法即可。
6. 基本类型无法作为泛型的类型参数,但是有自动打包和拆包机制,方便了转换。
7. 应当尽量使用泛型方法,具体参数类型推断由编译器负责。我们同样可编写一个工具类简化我们的类型推断工作,但是注意这里的类型推断只对赋值操作有效,做函数参数不行如New.map()。使用类型推断+static可以使使用成为更通用的工具类库。
8. 可变参数列表可以和泛型很好的共存。
9. 为创造一个通用的生成器,首先需要继承generator<T>,根据传入的.class对象newInstance一个新对象。要求这个类必须为public且包含默认的构造器。
10. 泛型同样可应用于内部类和匿名内部类。
11. 可以很容易的用容器的泛型类创建复杂模型。P372 shelf->Aisle->Store
12. 在泛型代码内部,无法获得任何与泛型类型有关的信息。因此比较两个不同泛型类型的.class结果会使true
13. java编译器中无法像C++一样保证能够在无法调用obj上的某个f()时出错。我们必须给定泛型类的边界,告知编译器只能遵循这个类的边界。历史原因:保证迁移兼容性,即保证现有的代码和类文件仍旧合法。
14. 泛型类型只在静态类型检查期间出现,此后程序中所有泛型类型都将被擦除,被替换为它们的非泛型上界。
15. 在泛型中的所有动作都发送在边界处:对传递进来的值就行额外的编译器检查,并插入对传递出去的值的转型。
16. 通常擦除的补偿采用类型标签:显式地传递Class对象,就可以使用isInstance了。
17. 建议使用显式的工厂创建类型实例:并将限制其类型,使得只能接受实现了这个工厂的类。编译时检查注意与前面运行时的newInstance对比。P382
18. 还可以用模板方法设计模式创建类型实例,定义抽象类,再定义一个实现类实现其中的create()方法得到类型新实例。
19. 利用newInstance()的参数版本创建某个类的对象:
Class.forName(typename).getConstructor(args[0].getClass(),args[1].getClass()).newInstance(args[0],args[1]);
20. 泛型数组:通常可用ArrayList代替,不仅可获得数组的行为,还有编译时检查。其他:
a) gia= (Generic<Integer>[])new Generic[SIZE];还可创建一个被擦除的数组然后转型。
b) 传入类型标签T,之后再转型:array = (T[])Array.newInstance(type,size)。注意若不传入类型标签,T会被擦除到Object
21. classSolid<T extends Dimension & HasColor & Weight>{} extends后类必须先写出来,然后才是接口。
a) 可以在继承的每个层次上添加边界限制,从而减少重复书写,比如:
class Solid2<T extends Dimension & Hascolor & Weight>
extends ColoredDimension2<T>{}
22. List<Fruit>flist = new ArrayList<Apple>();编译时错误,不能把一个涉及Apple的泛型赋值给一个涉及Fruit的泛型。
但是如果我们List<? entendsFruit> flist = new ArrayList<Apple>();就可以成功。但是一旦执行这种类型的向上转型,你将丢失掉向其中传递任何对象的能力,包括Obj
23. 数组的协变性:比如开始num[0] = new Integer(0);之后num[0] = new Double(3.4)就会运行时出错。此协变性对使用了通配符?的List不成立。
24. 原则:
a) 从数据类型中读: ? extends
假如有List<? extends Fruit> flist = newArrayList<Apple>();只可以调用contains和indexOf这些方法,而不能add()。因为?表示任何继承自Fruit的类(上界),你不能保证这个类一定是Apple。
b) 写入数据结构: ?super Apple
假如有List<? super Apple>apples = new ArrayList<Fruit>();apples.add(x),x可以是Apple(下界)的子类或本身。因为?表示任何以Apple作为基类的类,肯定包含Apple的子类或本身的基类Apple的公共部分。
c) 都需要:不要用通配符。
25. 无界通配符:P397例子
a) List表示持有任何Object类型的原声List,List<?>表示具有某种特定类型的非原声List,只是不知道那种类型是什么。
b) 还有一种用法:static Map<String, ?>map3 确定一个参数,另一个是不确定的某种类型。
c) 不能往List<?> list添加任意对象,除了null
26. 捕获转换只有在这样的情况下才能工作:在方法内部你需要使用确切的类型。此时需要特别使用<?>
27. 问题
a) 任何基本类型都不能做类型参数,且自动包装机制不能应用于数组。
b) 一个类不能实现同一个泛型接口的两种变体,因为擦除。加入类B<a>实现接口A,类C不能继承B,再实现接口A<b>。
c) <T>默认擦除到Object
d) 有时需要List<Widget> lw2= List.class.cast(in.readObject());通过泛型类转型,而不能转型到实际类型。
28. 自限定的类型:价值在于可以产生协变参数类型。
a) 循环泛型:class Subtype extendsBasicHolder<Subtype>{} BasicHolder中定义了接受T类型的set和返回T的get方法。现在,在Subtype中set和get的参数只能是Subtype了。
b) 自限定:class SelfBounded<Textends SelfBounded<T>>{}
要求继承关系中使用SelfBounded时必须按照:
class A extends SelfBounded<A>{}
c) 如果不使用自限定,将重载参数类型列表。使用了自限定,只能获得方法的一个版本,他接受确切的导出参数类型(不是基类)。
29. Collections类包含解决类型检查的静态方法checkedMap(),checkedList()…
30. catch语句不能捕获泛型类型的异常,但是类型参数可能会在一个方法的throws子句中用到。比如:
interface Processor<T, E extends Exception>{
void process(List<T> resultCollector) throws E;}
31. java中混型实现:
a) 使用接口:A和B是2个功能类实现接口a和b,在类C中要产生混型的效果:让C实现a,b,在类C中继承一个基类base并创建A和B的实例向上转型,然后定义ab接口中方法即可。
b) 装饰器模式:Basic类先包装一层Decorator装饰器类,之后将其他类包装在这个可装饰对象的四周,来将功能分层。主要在主类中采用组合方式即可实现混型效果。
c) 动态代理:代理可以被转型为被代理对象。P416
32. java对缺乏潜在类型机制的补偿:
a) 反射,类型检查在运行时:Class<?> spkr = speaker.getClass();
Method speak =sprk.getMethod(“speak“);
speak.invoke(speaker); //执行方法
i. 将一个方法应用于序列,可以如此声明这个方法:
public static <T, S extends Iterable<? extends T>>
void apply(S seq, Method f, Object…args){
for(T t : seq) f.invoke(t, args);} 这样该方法可以接受任何实现了iterable接口的事物。
b) 适配器仿真:定义一个接口包含同名方法,我们为每一个需要适配某方法的类创建一个适配器类实现该接口。P424
33. 策略设计模式:将“变化的事物”完全隔离到一个函数对象中。P427体会思想
ch16 数组
1. 数组和容器的主要区别:效率,类型和保存基本类型的能力。但是缺点是大小固定。
2. 对象数组保存的是引用,基本类型数组保存基本类型的值。数组中length表示数组长度而不是数组实际保存的元素个数。
3. 初始化
a) 聚集初始化:
Bery[] d = { new Bery(), new Bery(), new Bery() };
b) 动态初始化
a = new Bery[]{ new Bery(), new Bery(), new Bery() };可以直接做函数形参。
4. 创建三维数组:int[][][] a = new int[7][][];
第一层for:a[i] = new int[5][]; 确定了一维,还要创建二维(共三维)
第二层for:a[i][j] = new int[5]; 确定了二维,还要创建一维
5. 创建二维数组:int[][] a = new int[7][];
第一层for:a[i] = new int[5];
第二层for:a[i][j] = i*j;
6. 当我们用基本类型去初始化对应的包装类型时,自动包装机制就会起作用。
7. 不能实例化具有参数化类型的数组,通常声明同类型非参数化类型再转型为参数化。
8. Object数组能存放除了基本类型之外的任何对象。
9. Arrays.fill()只能用同一个值或引用填充某些位置。
10. 策略设计模式的一个实例:Generator生成器。我们可以通过选择Generator的类型来创建任何类型的数据。P443,P445展示了一组RandomGenerator。
11. boolean[]result ,Boolean[] in。 自动拆箱机制:result[0] = in[0];
12. 注意:自动包装机制不能应用于数组://ia = Ia
13. 复制数组:System.arraycopy(),如果复制对象数组,只复制了对象的引用,不是复制了对象的拷贝。这被称为浅复制。
14. 数组相等的条件:个数相同,对应位置的元素也相等。equeals,deepequals。
15. 想将某个类的数组应用于sort:
a) 实现Comparable<CompType>接口,并实现compareTo()函数,相等返回0
b) 实现comparator<CompType>接口,并实现compare函数,这样可以在sort的第二个参数使用这个类了。
16. java标准库针对基本类型采用快速排序,针对对象采用稳定归并排序。
17. 若对未排序的数组使用Arrays.binarySearch()将产生不可预料的结果。找到目标,返回值大于0,负值a表示应该插入的位置-(a+1)
18. 对对象进行二分查找,要在该对象实现Comparator并作为binarySearch第三个参数。
19. 泛型可以产生类型安全的容器,应当优先使用容器,除非性能称为问题。
ch17 容器深入研究
1. Collection.fill同Arrays中功能一样,但只对List对象有效。还有Collection.nCopies()
2. P461的CollectionData是适配器设计模式的一个实例,它将Generator适配到CollecitonData的构造器。
3. P462介绍了一种Map生成器。类似于CollectionData接受Genarator类型或迭代器类型参数,用以产生map
4. 享元设计模式:在普通的解决方案需要过多对象,或产生普通对象太占用空间时使用。
5. print一个map的原理就是,先通过entrySet()获取Set视图,再通过返回对象的迭代器取出访问。P469简单的使用享元示例。
6. Collection的常用方法:addAll,toArray等P470
7. 如果一个操作是可选的,未获支持的,实现后运行时会出现UnsupportedOperation-Exception异常。常见例子是由固定尺寸的数据结构支持的容器。任何改变其尺寸的方法都会产生异常,比如retailAll,add,addAll等等
8. HashSet,首选,存入其中的元素必须定义hashCode()
TreeSet表示保持大小次序的Set,红黑树实现,必须实现Comparable接口
LinkedHshSet保持插入次序的Set,必须定义hashCode()
9. 所有存储在Set、map中的类都必须具有equals方法。默认的hashCode是合法的(来自Object),即便他是不正确的。
10. SortedSet是一个接口,可以保证元素处于排序状态。通过向上转型可以使用包括first,last,subSet等方法。
11. 填充到优先级队列的对象必须要实现Comparable接口并定义compareTo方法
12. 关联数组及其简单的一个时间就是利用二维Object类型数组
13. 要在Map中使用对象,要定义hashCode和equals方法(接受object类型)。instanceof会检测对象是否为null
14. map中查询过程:计算散列码,用散列码查询数组。
15. java的散列函数以前会使用质数,目前都使用2的整数次方。主要是消除除法和求余的开销,用掩码代替。
16. 不应该使hashCode依赖于具有唯一性的对象信息,尤其是this的值。加入多个String都包含相同的字符串序列,则这些String对象都映射到同一块区域。
17. 产生hashCode的一种方法:int result = 17; result = 37*result+s.hashCode()
result = 37*result + id; returnresult; 对每个有意义的域计算一个散列码。
18. P502:tests.add(newTest<List<Integer>>)(“add”){
int test(){}
};
19. HashSet的性能基本比TreeSet好,但是用后者迭代较快。
20. HashMap的性能因子:负载因子=尺寸(当前存储的项数)/容量(表中桶位数)
负载轻的表插入和查找比较理想,但是会减慢迭代器遍历的过程。HashMap和HashSet允许你指定负载因子,当负载因子达到这个数时,自动增加容量。默认0.75。
21. Collection.unmodifiableCollecton表示只读,不可修改的。
Collection.synchronizedCollection同步控制
a) 快速报错,比如在声明迭代器之后add会产生异常。正确做法是添加完后再声明。
22. WeakHashMap用来保存WeakReference:当程序需要那个“值”的时候,便在映射中查询现有对象,然后再使用,而不是重新创建。并且他允许垃圾回收期自动清理键和值,只要当键值没有被其他地方使用时。
ch19 Java I/O系统
1. newFlile(“.”).list(); 返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。
a) 还有一个接受参数类型为接口FilenameFilter的版本,我们必须在其实现中实现accept函数以便实现过滤器功能(通过正则),此后返回的就是过滤后的目录了。
b) 最简单的实现,就是定义一个作为list()参数的匿名内部类。体现了匿名内部类怎样通过创建特定的一次性的类来解决问题。
2. P528提供了local产生有本地目录中的文件构成的File对象数组,walk产生给定目录下整个目录树包含的目录和文件构成的List。
3. File对象还可以创建新的目录或不存在的整个目录路径,查看文件特性,删除文件等。
4. 面向字节的I/O类:处理字节和二进制对象:InputStream和OutputStream。
面向字符:处理字符和字符串:Reader和Writer
5. I/O流的典型使用方式:
a) 缓冲输入文件:BufferedReader in = new BufferedReader(new FileReader(name)); String s = in.readline(); finally也要close
b) 从内存输入:StringReader in = new StringReader(String s);
c) 格式化的内存输入:DataInputStream in = newDataInputStream(
newBufferdInputStream(
new FileInputStream(“xx.java”)));
in.readByte()一次一字节地读取字符。available()查看还有多少可供读取字字符。
d) 基本的文件输出:PrintWriter out = new PrintWriter(
newBufferedWriter(new FileWriter(“xx.out”)));
简化书写可以直接PrintWriter out = new PrintWriter(“xx.out”);写结束要close(),不然缓冲区内容不会被刷新清空,造成不完整的情况。
e) 格式化的写入:DataOutputStream类,用法类似输入out.WriteDouble等
f) 读写随机访问文件:RandomAccessFile相当于组合了c和e
6. 读取二进制文件可以用BufferedInputStream类的read方法读入byte数组。
7. 对System.in必须包装才可使用:比如BufferedReader stdin =new BufferedReader(new InputStreamReader(System.in));之后可以stdin.readline();
8. 标准I/O重定向:setIn(InputStream) setOut(PrintStream) setErr(PrintStream)
9. Processprocess = new ProcessBuilder(command.split(“ ”).start();以指定程序和命令行参数启动一个进程
10. 新I/O使用了nio修改了FileInputStream,FileOutputStream,RandomAccessFile类,通过对每一种上述类的对象getChannel()即可得到通道FileChannel类对象。可通过此对象wrtie或read,唯一与之交互的缓冲器是ByteBuffer,以原始的字节形式或基本类型输出和读取数据(无法对对象作用)。
a) 注意:在read完之后,要flip()反转此缓冲区。通常用于读取已写入数据。
b) transferTo()和transeferFrom()允许两个通道向连接。
11. 缓冲器容纳的是普通字节,为把他们转换成字符,要么在输入时编码,或输出时解码(CharSet类),才能调用asCharBuffer()打印输出。P554
12. 获取基本类型ByteBuffer bb = ByteBuffer.allocate(BSize); bb.asCharBuffer().put(“Hi”);(用wrap()也行) 可以通过getChar()逐个读取,其他基本类型类似。其中的asCharBuffer()称为视图缓冲器,让我们通过某个特定的基本数据类型的视窗查看其底层的ByteBuffer
13. bb.order(ByteOrder.BIG_ENDIAN)改变bb为大端存储顺序。
14. 内存映射文件: MappedByteBuffer out =
newRandomAccessFile("test.dat", "rw").getChannel()
.map(FileChannel.MapMode.READ_WRITE, 0,length);之后可通过put写get读。
注意:映射文件访问获得的性能比nio更好。
15. 压缩类的使用:直接将输出流封装成GZIPOutputStream或ZipOutputStream,输入流同理。其他操作全部都是I/O读写。
16. jar程序可根据我们的选择自动压缩文件。
17. java的对象序列化将那些实现了Serializable接口的对象转换成一个字节序列,并能在以后将这个字节序列完全恢复为原来的对象。持久性意味着一个对象的生存周期不取决于程序是否运行,它可以生存于程序的调用之间。
a) 序列化和反序列化过程:创建某些OutputStream对象,封装在ObjectOutputStream对象内,只需writeObject()即可将对象序列化。反序列化为ObjectInputStream和readObject()
b) 打开和读取一个被序列化的对象,在两个不同的类中必须包含这个被序列化的.class文件
c) 在调用ObjectOutputStream.writeObject()时,会检查传递的Serializable对象,若有自己的writeObject就调用这个自己实现的。
18. 某些情况用Externalizable接口代替Serializable来对序列化过程进行控制,同时需要增添两个接口writeExternal(ObjectOutput out)和readExternal(…),这两个方法会在序列化和反序列化过程中被调用。
a) 对Externalizable对象,恢复时所用普通的默认构造器会被调用,而Serializable对象,对象完全以它存储的二进制位为基础来构造,不调用构造器。
b) 我们需要在writeExternal()和readExternal()将重要信息写入或者恢复:out.writeInt(i); //etc… 否则就是恢复的i就是默认值0而不是我们想要的值。
c) transient关键字可以逐个字段的关闭序列化。除非明确writeObject()才可反序列化出来。因为defaultWriteObject()方法仅保存非transient字段。
19. 我们可以通过一个字节数组来使用对象序列化,从而实现任何可Serializable对象的深度复制,即复制整个对象网,而不仅仅是基本对象及其引用。P582
20. 要想序列化static对象,必须:void serializeStaticState(ObjectOutputStream os){os.writeInt(color);}
21. PreferencesAPI用于存储和读取用户的偏好以及程序配置项的设置。P588
ch19 枚举类型
1. 创建enum时编译器会自动生成一个继承自java.lang.Enum的类,这个类实现了Comparable接口,因此包括compareTo()方法,它还实现了Serializable接口。
2. 静态导入可以用于enum,此时就不用类名修饰。但是在定义enum的通个文件,默认包定义enum这种技巧都无法使用。
3. 可以在enum中添加方法,必须在enum实例序列的最后添加一个分号,且方法必须在枚举实例之后。比如WEST(“This is a test”); EnumTest(String s){this.s = s};我们只能在enum内部使用构造器创建实例。
4. switch中可以使用enum,其中的case语句如果有return,若不含default,编译器就会报错,否则不会。
5. values()方法是编译器添加的static方法,可以得到某个枚举包含的所有枚举实例。其效果等同于e.getClass().getEnumConstants();
6. 可以在枚举中实现一个或多个接口。
7. 枚举的枚举:APPETIZER(Food.Appetizer.class)(属于Food接口),构造器调用getEnumConstants()产生一个枚举的枚举。P598
8. EnumSet内部实现原理是将一个或多个long作为比特向量,优点在于判断一个二进制位是否存在具有更好的表达能力,无须担心性能。向EnumSet添加eum实例的顺序并不重要,因为其输出的次序决定于enum实例定义时的顺序。
9. EnumMap要求其中的键key必须来自一个enum,其内部可以由数组实现。
10. 我们还可以为enum中的每一个实例编写方法,从而为每个实例赋予各自不同的行为。并且这个方法可以覆盖enum中实例定义后面存在的方法。
11. 编译器不允许我们将一个enum实例当做class类型(不能当形参的参数类型),因为他是static final的。
12. enum的一个应用场景:职责链。比如邮箱经过大概扫描、机器搜索、人工检索,每次都经过这条链。enum还特别适合做状态机。
13. 介绍了一个石头剪刀布游戏,最简单的办法就是利用二维数组。
ch20 注解
1. 三种定义在java.lang的注解:
a) @override当前方法将覆盖基类中的方法
b) @Deprecated如果程序员使用了注解为它的元素,那么编译器会发出警告信息
c) @SuppressWarnings关闭不当的编译器警告
2. 可以自定义注解P621,并且通过注解处理器生成外部文件,比如能够自动生成数据库表,用以存储javaBean对象。
3. apt是一个注解处理工具。
4. 单元测试是对类中的每个方法提供一个或多个测试的一种实践,其目的是为了有规律地测试一个类的各个部分是否具备正确的行为。基于注解的测试框架包括@unit,junit也支持。
ch21 并发
1. 并发通常是提高运行在单处理器上的程序的性能。(即使有上下文切换损耗,但是因为阻塞使得并发变得有意义起来)。
2. java的线程机制是抢占式的,表示调度机制会周期性地中断线程, 将上下文切换到另一个线程,从而为每个线程都提供时间片,使得每个线程都分配到数量合理的时间去驱动它的任务。
3. Thread.yield()建议线程调度器此时具有相同优先级的其他线程可以运行。
4. 创建线程:
a) newThread(new LiftOff()).start()启动一个线程,运行实现了Runnanle接口的LiftOff对象的run方法(无返回值)。
b) ExecutorServiceexec = Executors.newCachedThreadPool();
exec.execute(new LiftOff);首选方法,a中可能发生:另一个任务可能会在构造器结束之前开始执行,这意味着该任务能够访问处于不稳定状态的对象。
c) b方法是首选,但你可以使用newFixedThreadPool(5)创建一个线程池。
d) SingleThreadExecutor会序列化所有提交给它的任务。
5. 从任务中产生返回值:
a) ExecutorServiceexec = Executors.newCachedThreadPool();
exec.submit(new LiftOff); 它的类型参数表示实现了Callable<T>接口的对象调用它的方法call()返回的值。返回类型为Future<T>。
6. 休眠:sleep()使当前线程阻塞一定时间。
7. 优先级:
a) getPriority()读取现有线程优先级
b) setPriority()设置当前线程优先级。可移植的就三种:MAX_PRIORITY,NORM_PRIORITY,MIN_PRIORITY
8. 打印到终端是不可被中断的。
9. daemon.setDaemon(true);设置为后台线程:
a) 当所有的非后台线程结束时,程序也就终止了,同时杀死所有的后台线程。
b) 通过编写定制的TreadFactory(接口)可以定制有Executor创建的线程的属性(后台、优先级、名称)。
c) isDaemn()确定是否是后台线程,若是后台线程,其创建的任何线程默认都为后台。
10. Thread类自身不执行任何操作,它只是驱动赋予它的任务。
11. t.join()表示当前线程将被挂起,直到t线程结束才恢复。
12. 阻塞(sleep()/等待输入输出/等待获得锁)和挂起(wait()):
a) 挂起是主动行为,恢复也要主动完成,阻塞是被动行为,正等待事件或资源。
b) 挂起不释放CPU,若任务优先级高其他任务永远轮不到,阻塞释放CPU,其他任务可以运行。
c) 操作系统的任务调度直接忽略挂起的任务。
13. 捕获线程中的异常:实现Thread.UncaughtExceptionHandler接口,并实现其中的
uncaughtException(Thread t, Throwable e) 方法。最后还要:t.setUncaughtExceptionHandler(Myexceptionclassname); 还可以设置默认的未捕获异常处理器:setDefaultUncaughtExceptionHandler(xxx);
14. 同一个互斥可以被同一个任务多次获得。P699
15. synchronized static方法是针对每个类的锁。在类的范围内放置对static数据的并发访问。
16. Brian同步规则:如果你正在写一个变量,它接下来将被另一个线程读取,或者正在读取一个上一次以及被另一个线程写过的变量,那么你必须使用同步,并且读写线程要用相同的监视器锁同步。
17. Lock类可以提供显式的加锁。ReentrantLock是非阻塞的锁。
18. 原子性可以应用于除long和double之外的所有基本类型之上的“简单操作“(读取和写入)。但对long和double使用volatile关键字,就会获得原子性。
19. volatile关键字还保证了可视性:如果你将一个域声明为volatile的,只要对这个域产生了写操作,那么所有的读操作都可以看到这个修改,用线程的局部变量维护对这个域的精确同步。即volatile域会立刻被写到主存中,非volatile关键字就没必要立刻写到主存。
volatile无法工作的2个特殊情况:
a) 当一个域依赖于它之前的值时,如java自增(涉及一个读和写)。
b) 某个域的值受其他域的值限制。
20. 通常选择synchronized关键字或显式的Lock对象,涉及性能调优时可能会使用AtomicInteger等特殊的原子性变量类。
21. 共享资源一般是以对象形式存在的内存片段。
22. 防止多个线程同时访问方法内部的部分代码而不是整个代码,通过这种方式分离出来的代码段称为临界区,也叫同步控制块。synchronized(syncObject){ … },其中的参数为Object类型。
a) 可以把一个非保护类型的类,在其他类的保护和控制下,应用于多线程环境。
b) 临界区通常效率更高,对象不加锁的时间更短。
23. 线程本地存储ThreadLocal:为使用相同变量的每个不同的线程都创建不同的存储。只能通过get和set方法访问该对象的内容。
24. 一个线程可能的四种状态:
a) 新建:只有在线程被创建时短暂的处于此状态。
b) 就绪:只要调度器把CPU时间片分配给线程就可以运行。
c) 阻塞:线程能运行,但某个条件阻止他运行,不分配CPU时间片。
d) 死亡:run方法返回,不可被调度运行。但是要知道,此时该线程还可以被中断。
25. I/O和synchronized上的阻塞是不可中断的,sleep()可以中断。对前者,我们可以直接关闭任务在其上发生阻塞的底层资源(比如描述符)。注意被阻塞的nio通道会自动地响应中断。
26. Thread.interrupted()测试线程是否被中断,同时清除中断标志,防止此消息通知2次。
exec.shutdownNow();通常在多线程程序main最后调用,意思是中断所有线程。
27. wait()将任务挂起,直到notify()或notifyAll()发送,任务才被唤醒继续执行。
a) 调用sleep()和yield()的时候锁并没有被释放。而wait()的时候锁是释放的。因此只能在同步控制块或同步控制方法中调用前面这3个方法。
b) 通常:while(conditionIsNotMet) wait();原因P706
c) notify()只唤醒一个任务,因此在使用它时必须确保唤醒的是恰当的任务。
d) notifyAll()因某个特定锁而被调用时,只有等待这个锁的任务才会被唤醒。
28. condition类包括await()、signal()、signalAll()类似于上面的三个函数,但是这种方式相对来说更安全。
29. BlockingQueue接口提供了同步队列,包括LinkedBlokingQueue、ArrayBlokingQueue(固定大小)、synchronousQueue(每次take必须等待一个put)
take()方法在元素为空时一直等待。
30. 任务间可使用PipeWriter和PipeReader通信,与普通I/O最大区别是可被中断的。
31. 死锁的条件:
a) 互斥。
b) 占有且等待。
c) 不可剥夺,资源不可被抢占。
d) 循环等待。
32. 新类库中的构件
a) CountDownLatch:同步一个任务或多个任务,强制它们等待由其他任务执行的一组操作完成。latch.await(); latch.countDown(); random.NextInt线程安全
b) CyclicBarrier:一组任务它们并行地执行,然后在下一个步骤之前等待,知道所有任务完成。barrier.await()
c) DelayQueue:这是一个无界的BlockingQueue,放置实现了Delayed接口的对象,对象只能在其到期才能从队列取走。其中元素有序,且队头到期的时间最长。若没有任何延迟到期,则不会有头元素,pool()返回null。Deylayed接口要实现getDelay()和compareTo()
d) PriorityBlockingQueue:优先级队列中的对象是按照优先级顺序从队列中出现的任务,对象要实现Runnable和Comparable<T>接口
e) ScheduledThreadPoolExecutor:
i. schedule()给定延迟后执行一次Runnbale对象的run()
ii. scheduleAtFixedRate()每隔规则的时间重复执行任务。
f) Semaphore:计数信号量允许n个任务同时访问一项资源。
i. se.acquire()从se信号量获取一个许可,在提供许可前线程阻塞。
ii. se.release()释放一个许可。
g) Exchanger:比如一个producer和consumer都共同持有一个List<T>、Exchanger,当其中一个调用exchange()时,等待另一个调用exchange(),此时互换要交换的对象。
应用场景:一个任务在创建对象,创建代价很高,而另一个任务在消费这些对象,通过这个方式使更多对象在被创建的时候被消费。(填充和消费同时发生了)
33. 三个仿真:柜台出纳,饭店仿真,机器人组装汽车。
34. 使用Lock通常会比使用synchronized要高效许多,synchronized开销的变化范围太大,前者则相对一致。使用synchronized的情况:
a) 互斥方法的方法体非常小
b) 代码被阅读的次数远多于被编写的次数
35. 免锁容器的通用策略:对容器的修改可以与读取操作同时发送,只要读取这只能看到完成修改的结果即可。原理:修改是在容器数据结构的某个部分的一个单独的副本上执行的,修改过程不可视,修改完成,副本才会与主数据结构以原子操作交换。
a) CopyOnWriteArrayList的好处之一:多个迭代器遍历修改此列表,不会抛出ConcurrentModificationException。在没有写入者时,速度快很多。
b) Collections.synchronizedList无论读入写入数量,都具大致的性能
c) connurrentHashMap添加写入者影响比CopyOnWriteArrayList更小。
36. 乐观锁:某些Atomic对象可以使用一个compareAndSet()的方法,你将旧值和新值一起提交给这个方法,若旧值与当前值不一样,这个操作就失败,否则把新值赋给当前值。
37. ReadWriteLock对向相对不频繁写入,但多个任务经常读取的情况进行了优化。真正提高性能还取决于:读和写频率比,二者时间,线程竞争个数,多处理等因素。首先应选用更直观的同步,之后调优时进行性能测试。
38. 活动对象:每个对象维护自己的工作器线程和消息队列,所有对对象的请求都将进入队列排队,我们串行化了消息而不是方法。举例:newSingleThreadExecutor()和exec.submit()方法结合使用。存到List<Future<?>>中后通过get得到。
39. 线程的上下文切换大约需要100条指令,进程的上下文切换要上千条指令。