JAVA编程思想读书笔记(8-14章)

来源:互联网 发布:软件测试逻辑思维题 编辑:程序博客网 时间:2024/06/07 00:17
1.多态:多态的实现依赖于动态绑定,即方法只有在运行的时候才去根据引用和方法名寻找正确的方法体并执行。并不是所有的方法都采用动态绑定,被final和static修饰的方法采用前期绑定,也就是说父类引用对象static执行的是基类的方法。
多态的好处很多,他更便于我们理解,是我们只和基类打交道,可扩展性高。最重要的是,它体现了一个重要的思想:将需要改变的和不需要改变的分离开。

2.清理:清理顺序应该是先导出类,然后基类。因为导出类中的一些方法可能依赖于基类中的一些成员,这也是初始化的时候先初始化基类的原因。

3.终极初始化顺序:这里有一个比较难以注意的点,就是子类中变量的初始化时间。
首先,最开始的时候,对象的存储空间会被全部清零。也就是说,这个时候所以变量的值都是0或者null。
然后调用父类构造器。在父类的构造器中可能会调用方法,如果这个方法被覆盖了就调用覆盖后的新方法。在新方法中可能会调用到子类的成员变量,这时的变量还没有初始化,因此也就没有值。(这里容易发生错误,所以提倡在父类构造器中使用不能被覆盖的方法)
父类构造器完成工作后子类的各个变量才进行初始化,然后进入构造器的方法体执行。

4.向下转型:向下转型的对象只有在运行时才能被检查是否正确,如果不是转型类型的话会抛出异常,以保证安全性。

5.纯继承:纯继承指的是子类继承父类,仅仅重写父类的方法而不做扩展,也就是严格的“is a”的关系。一般见到的在父类基础上增加方法的是一种类似于“is like a”的关系,这种扩展是由风险的,因为父类引用看不到这些特化的方法,不利于父类引用的使用。

6.接口:对于接口可以理解为一种协议。实现了接口的类必须实现其全部方法,所以相应的对象可以放心的发送接口中定义的消息。当然,和抽象类一样,接口还有一个作用就是保证不能实例化,仅仅作为抽象层的协议。
接口的可见性和类相同,实际上接口就是一种特殊的类。默认包可见。接口中的所有域强制public  static final,除了抽象方法外,接口中的变量必须在定义时初始化,这些静态常量存在接口的内存区域。
接口是对于多继承的一种代替方案,如果直接继承多个类的话就需要在子类对象中维护多个父类对象,造成混乱。而实现多个接口则只有一个对象实例。
接口是可以扩展的,方式是新接口继承旧接口,然后增加新方法。而且这里可以使用多继承,继承多个接口。不过这里不能严格理解为继承。
接口可以嵌套,实现的时候只需要实现最外层的方法。也可以impl内层接口,实现内层接口的方法。

7.内部类:内部类的一个特点是可以直接看见外部类的变量,就好像自己拥有这些变量一样。实际上每一个内部类对象都必须连接一个外部类对象,可以在内部类中使用 外部类名.this  获取这个外部类对象。
就像上面说的那样,内部类对象不能脱离于外部类对象而存在,所以内部类对象必须由外部类对象来创建,不能直接new。一般来说,采取的方式是在外部类中添加一个返回内部类对象的方法。也可以用外部类对象.new来实例化:
Outer outer= new Outer();
Inner inner= outer.new Inner();

至于内部类对象如何在创建时链接到外部类对象,这是编译器自己解决的。
(10.5以后的内部类探究比较复杂,日后需要深入了解的时候再看吧)

8.容器类:和数组不同,JAVA类库提供了一些容器类来保存对象引用和基本数据。容器不是固定长度的,所以不需要关心长度。
容器分为两种,一种是collection,表示元素按一定规则排列,分为list和set,list必须按插入顺序保存元素,而set不允许集合中有重复(已存在的元素无法加入)。另外一种是map,将一种对象和另一种对象关联起来,通过键值对存储。
HashSet提供了最快的查找技术

9.容器的泛型:在容器声明的时候使用泛型对容器进行预定义,这样在装入容器的时候编译器会进行检查,防止不正确的类型进入。在取出的时候编译器也会自动对对象进行转型。

10.添加一组元素:
collection可以将一个list对象中的元素直接添加进自身,有两种方法,一种是对象调用:collection.addAll(list),另一种是静态方法:Collection.addAll(collection,list)。第一个参数是目标容器,第二个可以是list,也可以是变长参数的用逗号隔开的数据元素。
Arrays.asList()接受可变参数,可以用一组元素创建list。但是创建的时候不会做类型检查,只会在装入元素后根据元素类型来确定创建的list的类型。更好的习惯是显式指出创建的目标容器类型:Arrays.<Person>asList()


11.List :list中的很多操作是依赖于eauqls的,凡是涉及到元素比较(是否含有,取交集等)都需要用到equals

12.迭代器 使用迭代器来对容器进行访问是一种良好的编程好处,这样做的好处是方便的代码复用。通过使用迭代器用户不必知道容器类型,因为访问方式是统一的。
通常迭代器对象可以通过iterator方法获取,是轻量级的。使用hasNext进行末尾判断,next获得下一个元素,还可以通过remove删除最近访问的元素。
for each遍历就是通过迭代器实现的,实际上遍历就是在调用迭代器的方法,for的第二个参数就是迭代器。

13.Set .为了增强确定元素是否存在和访问速度,Set维护了一个HashSet,使用了散列技术。

14.栈可以借由LinkedList,主要有pop,push,peek方法,peek返回栈顶元素。

15.异常机制的必要性:JAVA中的异常处理机制强制程序员在可能出现错误的地方进行处理,显著的增强程序的健壮性。异常机制使得只需要在一个地方对错误进行集中处理,而不是每次调用方法的时候检查。更加清晰,简洁。
异常的含义是当前无法解决的问题,当前程序无法得到足够的信息处理这个问题,无法继续进行,于是只能把这个问题交给上一级。
异常的存在形式是对象,根类是Throwable,和编程相关的基类是Exception。每个异常类都有两个构造器,一个默认,一个接收错误信息字符串。一般来说,异常类除了类型信息外不再具有有意义的信息。

16.异常处理机制:如果一个异常没有被捕捉和处理,那么程序中断,无法继续运行。异常层层上抛,直到交给main方法,这时终止程序执行并产生异常信息。如果异常在try catch中被捕捉处理,(子异常处理放在父类前)那么处理后程序在try catch后面继续运行。
需要注意finally的问题,finally是必须执行的,在catch处理后执行。如果处理中有返回操作,那么finally在返回前执行。就算异常要上抛,finally也会在跳出本层前执行。常用于保证一些资源的回收。finally的这种霸道也会带来风险,比如抛出一个新异常覆盖了原异常,这就GG

17.异常声明:异常分为可检测异常和不可检测异常(RunTimeException),可检测异常在编译时必须捕捉处理或者在方法名后使用throws声明。声明后相当于不处理,功能是告知可能抛出的异常,方便客户端程序员进行处理。
不可检测异常又称为不受声明异常,由虚拟机抛出,如果不处理的话会直达main方法。

18.异常使用:应该在知道如何处理的情况下才进行异常的捕获,这会与可检测异常的强制捕捉相冲突。

19.String:字符串的值是不可改变的,任何在其上面的修改操作都是新建了一个字符串对象。

20.StringBuilder在拼接生成字符串的时候有性能优势,如果操作的字符串比较复杂,例如使用循环多次拼接字符串的时候,应该使用一个builder,而不是生成多个String对象。使用builder.append进行拼接,最后用toString生成结果字符串。他还有insert  replace  substring  reverse等方法

21.格式化输出:C语言中常用的printf格式化输出,在JAVA中可以使用printf,也可以使用format,效果和用法一样
构造格式化字符串:可以使用String.format

22.toString打印:在重写toString的时候,如果想要在字符串中拼接内存地址,应该用super.toString,而不是this。如果用this的话可能会发生递归调用,因为this转字符串的时候会再次调用toString。

23.正则表达式的使用:
首先可以使用字符串类的方法,matches方法接受一个正则表达式字符串参数,返回匹配真假。
split参数一样,返回根据正则表达式匹配部分分割的字符串数组,可以使用Arrays.toString(s)输出。

使用Pattern和Matcher类进行复杂处理:首先使用正则表达式构造Pattern,然后使用其matcher方法传入字符串构造Mather,对这个Matcher进行多种操作:
              Pattern  p=Pattern.compile("\\d+");
        Matcher matcher=p.matcher("1234 2354");
最简单的是matches方法,直接返回是否完全匹配。然后是lookingAt方法,判断字符串开头是否匹配正则,无需全部匹配。
find()用于找出字符串中全部匹配部分,调用一次后下次调用就会寻找下一个匹配的部分,类似于游标的next。方法返回boolean表示是否找到,每次查找后可以使用group()得到相应的匹配部分:
            while(matcher.find()){
            System.out.println(matcher.group());
        }
还可以使用find(int i),参数表示从哪个位置开始查找,相当于指定起始查找位置,而不是顺序查找了。
在每次匹配成功后,start和end可以获得这部分的起始和结束位置。

Pattern的compile还有个多一个int flag参数的版本,通过各种flag指定匹配的多种条件。
通过matcher的reset(s)可以重置他的操作字符串。

替换:
可以直接使用字符串的方法replaceFirst(String ref,String b)和replaceAll(String ref,String b)。两个参数,表达式和替换后结果;两个方法,替换最开始一个和全部。
使用matcher方法:
appendReplacement(StringBuffer sbuf, String replace)。第一个参数用于保存结果,第二个是查找成功后对m.group替换后的结果。在全部替换完成后,使用appendTail(sbuf)将剩余字符串装入结果stringbuffer。

24.RTTI:运行时类型确定,包括向下转型前类型检查,通过查询class获取类型信息,关键字instanceof

Class:每个类都持有一个Class对象,这个对象并不是在编译时就加载,而是在第一次使用类的时候才加载进来。Class有一个静态成员方法forName(参数完整类名),返回这个类的class对象的引用,此外还可以用如Teacher.class(类字面常量)来获得这个引用。可以使用这个class对象通过newInstance产生相应类的对象实例。如果想对Class类型进行一定的约束,要求在编译时进行检查,可以使用通配符继承和泛型结合,如一个宠物class引用数组:
List<Class<? extends pet> list>

class对象的类型检查:class.isInstance(...  )。需要注意的是,使用class对象与目标类的class对象进行比较(equals)是不合理的,因为这表示确切的类型,忽略了继承关系。

对于RTTI来说,在编译时打开和检查.class文件,获取对象的所有信息。

25.反射:
反射机制就是支持在运行的时候才加载.class文件,获取对象的相关信息。支持在编译时有完全不了解的类,在运行时通过远程或者本地确定下来。是由Class和reflect类库支持的。通过forName创建一个class对象,然后可以调用getConsructer,getMethod等方法使用这个类的成员。

26.动态代理:
传统的代理需要创建一个代理类,这个类和被代理类实现一样的接口,作为代理将消息转交给被代理者。
动态代理可以动态的创建代理对象,而且代理类不需要实现被代理者的接口,封装了代理过程:
创建类A,实现InvocationalHandler接口,在构造器中传入被代理者对象,然后在invoke方法中再次调用invoke(被代理对象,args)。使用:
MyInterface proxy=(Interface) Proxy.newProxyInstance(
    MyInterface.class.getClassLoader,  new Class[]{ Interface.class} ,new  A(被代理者))。
三个参数,类加载器,要实现的接口,和A实例。这样就产生了一个可使用的针对特定被代理者的代理者对象。
可以说是非常灵活了。

27.空对象:
可以在类中维护一个静态对象,称作空对象。在初始化的时候用这个对象取代null,这样只需要在一些地方用equals判断是否是空对象,来判断对象是否存在,而不需要担心合法性,省去很多判空的代码。