• 不要用return、break、continue、throw来退出finally语句块。这样将直接跳出finally语句块,从而跳过try语句块,这并不是我们想要的效果。
  • 关于try-catch
    1. 如果一个catch子句要捕获一个类型为E的受检查异常,而其相对应的try子句却不能抛出E或其子类异常,这样编译时无法通过的。
      try {} catch(IOException e){}// 非法,因为IOException是检查异常
    2. 捕获Exception或Throwable的catch子句是合法的,不管预期相对应的try子句的内容是什么,哪怕为空
      try {} carch(Exception e){}// 合法
    3. 一个方法可以抛出的检查异常集合是它所适用的所有类型声明要抛出的检查异常集合的交集
      即:假设接口1声明方法f() throws A,接口2声明方法f() throws B,实现类同时实现这两个接口,必须实现方法f(),但是这里写成f()才对,
      因为异常要取交集
  • 对于一个未赋值的final域或变量,在try和catch块中同时为其赋值时是会报错的,哪怕逻辑上OK。
    因此在使用final域时,最好的方式是声明同时赋值
  • System.exit()
    1. 将停止所有的程序线程。
    2. 该方法被调用时,会执行所有关闭挂钩操作,释放VM之外的资源
      因此我们如果需要在System.exit()执行后释放资源,可以调用Runtime.getRuntime().addShutdownHook(Thread t)来添加关闭钩子
    3. 与之类似的方法
      System.halt() 该方法会直接关闭所有线程,不执行关闭钩子的动作,建议少用。
  • 在复习一遍实例化过程
    • 分配内存,并将所有域初始化为零值
    • 进入构造方法,首先按照域声明顺序进行初始化赋值
    • 再递归执行构造方法程序体
  • 构造器必须声明其实例化操作会抛出的所有检查异常
    • 哪怕这些异常不是在构造器程序体中抛出
    • 典型是的域声明同时初始化时抛出的异常,需要在构造器抛出
  • finally中的关流问题
    我们经常遇到输入输出流需要在finally中,但流的close()方法也会抛出异常。
    • 解决方法是在finally中也抛出异常,但这样就不美观了。
    • 好的解决方法是写一个方法,传入这些待关闭的流,使用时统一调用即可。一般来说,这些流都继承了Closeable方法。
  • 对于任何在finally语句块中抛出的异常,我们都应该当场处理,而不是继续抛出去
  • 在执行循环时,不能依赖于异常跳出循环,这样速度会非常慢
  • & | 操作符
    1. 正常情况是位与/或运算符
    2. 两边位boolean时,变成了逻辑与/或,只不过没有短路功能。
  • Class.newInstance()会传播从无参构造器抛出的所有异常。
    • 比较坑爹的是我们在调用该方法时只能显式地看到其声明的两个异常。对应的无参构造器会抛出什么异常我们并不知道。
  • Java的异常检查机制不是虚拟机强制执行的,只是编译器提供的工具。因此编译器给出的异常检查警告一定要认真对待。
  • 探测类丢失
    1. 要想编写一个能够探测类丢失的程序,使用反射来引用类,而不要使用常规方法,具体原因可以参见 解惑44
      虽然Java语言规范非常仔细地描述了类初始化是何时发生的,但是类被加载的时机却是远远不可预测的,就像解惑44,居然是验证时加载了类。
    2. 不要对捕获NoClassDefFoundError形成依赖,一般来说,捕获Error及其子类是非常不可取的。
  • Java的重载解析过程
    1. 选取所有可获得并可应用的方法或构造器
    2. 在第一阶段选取的方法或构造器选取最精确的一个
      精确:如果一个方法可以接受传递给另一个方法的任何参数,我们就是该方法不如另一个方法精确。
    3. 要想强制要求编译器选择一个确定的重载版本,需要将实参转型为形参所声明的类型
  • 每一个静态域在声明他的类及其所有子类中共享一份单一的拷贝(这点要尤其注意,之前理解错误)
    即:静态域由声明它的类及其所有子类所共享
  • 在选择继承还是组合的方式时,一定要充分考虑他们的关系是 is-a 还是 has-a,不能自己随便用
  • 静态方法
    1. 属于类,不存在任何动态的分派机制(不会重载),调用时按引用类型决定调用的是哪个方法(编译器绑定)。
    2. 一定不要通过对象来调用静态方法,因为一不小心就会引起问题
  • 小记一笔
    1. 类的静态域是在类加载后就进行初始化的,初始化顺序和声明顺序一致
    2. 实例变量是在创建对象时进行初始化的,顺序也和声明的顺序一致
  • instanceof操作符
    1. 当左操作符是null时,运算结果被定义为false
    2. 右操作符是不允许为null的
    3. 如果左右操作符都是类,则要求一个类必须是另一个类的子类
      如 new Type() instanceof String, 要求Type必须是String的子类或父类
  • 一个final类型的实例域被赋值前,是有可能被取用的,语法上没有问题
  • 应注意不要使用循环的类初始化和循环的实例初始化。
    1. 循环的类初始化:初始化静态域时调用尚未初始化的静态域
    2. 循环的实例初始化:初始化实例时调用尚未初始化的实例变量
  • 禁止在构造器中、伪构造器中调用重写方法,因为这样可能调用到子类的重写方法,而此时重写方法调用的值很可能尚未初始化好。
  • 静态方法调用的限定表达式是可以计算的,但是它的值将被忽略。如(现实中不要这样写)
    1. ((Null)null).greet(); 是可以正常运行的,并不会报错,其中greet是类Null的静态方法
    2. null.greet(); 就是不可行的了,因为null属于原始类型,不能这样做,编译无法通过。
  • 奇怪的问题
    1. 语言规范不允许一个本地变量声明语句作为一条语句在for、while循环中重复执行,但是在语句块中就可以
    2. 举例
      while(true) int i=0;// 编译无法通过。
      while(true) {int i=0;}// 编译通过
  • 计数
    1. 线程安全的计数器,不要忘记使用AutomicLong哦
    2. int计数到溢出最短只需要21秒,long最短需要9x108算),因此计数最好使用long
  • 实例不可变
    1. String、BigDecimal、BigInteger、所有包装类的实例都是不可变的。
    2. 即,我们不能修改现有实例的值,对这些实例的操作将产生一个新的实例
    3. 举例:
      • String str = “e”;
        str.replace(‘e’,‘d’);//执行后str的值并不会改变,要想得到改变后的值:str = str.replace(‘e’,‘d’);
      • BigInteger i = new BigInteger(10);
        i.add(new BigInteger2.);//同样,执行后不会有变化,这一项是比较容易忽略的,要记住。
  • 无论什么时,只要我们重写了equals()方法,就一定要同时重写hashCode()方法
    原因:HashSet会先根据hashCode()值找元素位置,再调用equals()判断是否相等。
  • 重写方法时,最好(必须)加上注解Override,这样可以避免错误。
  • 八进制以0(零)开头,这一点记住了,012代表八进制,12才是十进制
  • 新API
    1. Arrays.deepToString() 能够将多维数组的每一项都打印
    2. 整形的包装类支持通用的位处理操作:如bitCount(),统计被置位的位数
  • Java平台的每一个主版本都在其类库中隐藏了一些宝藏,因此研究Java的新特性页面有很大好处。
    此外,持续研究Java API也是有好处的。
  • 恶心的日期
    1. Calendar的月是从0开始的。
    2. 使用Calendar时,最好随时翻看API,因为很多反人类的API会误导我们。
  • IdentityHashMap:与其他类不同的是,该Map在比较key时,使用的是引用等价性比较,而不是常用的值比较
  • 取余
    -2%3 = 1-2 = -13 + 1-1为商,1为余
    2%3 = 21 = 0
    3 + 20为商,2为余
  • 负数的绝对值为负数的情况
    Math.abs():当参数为MIN_VALUE时,由于整数的二进制数不具有对称性,因此最小值的绝对值还是为该值
  • 比较器的缺陷
    1. 使用Comparator时,我们需要重写int compare(o1, o2)方法,一般直接返回o1-o2这样的值,但是如果o1-o2发生了溢出, 可能得到相反的效果
    2. 解决方案是不要在重写的compare中直接相减,更好的方法是比较,然后转化为-1、0、1这样的数
  • 要尽量避免重用平台类的名字,千万不要使用java.lang包中的类名,因为很多地方自然地用到这些类,当我们自己定义了同名类时,一不小心就让这些地方用到了我们定义的名字,到时候哭死都找不出问题所在。
  • 隐藏
    1. 当子类与父类存在同名实例变量、静态方法、成员类型时,子类会将可访问到的父类的变量隐藏起来,但其依然存在,可以通过将子类对象转为父类引用进行调用。
    2. 要避免隐藏,因为这可能导致混乱
      遮掩
    3. 当一个变量和一个类型具有相同的名字,并且它们位于相同作用域时,变量名具有优先权
    4. 按照标准命名规范时,不会遇到这种问题。
      遮蔽
    5. 指的是本地变量、内部类、方法等,因为与导入的变量、类、方法等同名,而采用本地的变量、内部类、方法的情况,即本地遮蔽了导入
    6. 更普遍地说:一个变量、方法或类型可以分别遮蔽在一个闭合的文本范围内的具有相同名字的变量、方法、类型。
    7. 更形象地说:一个闭合范围内,范围1包含变量A和范围2,范围2包含同名变量A,则在范围2中的A遮蔽了范围1中范围2外的A变量
  • 静态内部类和非静态内部类
    1. 静态内部类能够拥有自己的静态成员变量和静态成员方法
      非静态内部类不具有上述特征
    2. 静态内部类不能访问外部类的非静态成员和方法
      非静态内部类可以访问外部类的非静态成员和方法
    3. 静态内部类在创建时不需要依赖外部类的实例
      Outer.Inner inner = new Outer.Inner();
      非静态内部类创建实例时需要依赖外部类实例
      Outer.Inner inner = outer.new Outer.Inner();
  • 一个包内私有的方法(即方法的访问权限是default)不能被位于另一个包中的某个方法直接重写
    这点要注意,内容在解惑70
  • 静态导入
    1. 当静态导入的方法和本类中已有方法重名时,本类中方法具有优先权。识别到本地方法后就不会再使用导入的方法了。
    2. 由此看来,静态导入也会引起一定的问题,要谨慎使用。
  • final
    1. 修饰成员方法,不能重写
    2. 修饰静态方法,不能被隐藏
    3. 修饰域,不管是静态还是成员的,都只限制其值不能被赋值两次,没有其它限制
  • 如果两个重载的方法能够接收相同的参数,应该使得它们具有相同的行为。
  • 三目运算符
    1. 当第二个和第三个操作数为引用类型时,运算结果的类型是它们的最小公共超类
  • join()方法原理:在表示正在被连接的Thread实例上调用wait()方法,wait()方法会释放线程锁
  • 跨包访问:
    1. 访问位于其它包中的非公共类型的成员是不合法的。这些成员包括包、方法、域等。
      举例在公共类A中定义default的静态内部类B,在包外的类C中执行A.B.hashCode()是非法的,因为B是defalut的,包外不可访问。
    2. 这种非法问题,在正常时候编译就能检查出来,但是在反射时却只有运行时才能看出效果。
      因此,在使用反射访问某个类型时,请保证使用的Class对象对应的类型是可被包外访问的。
  • 一个非静态的嵌套类的构造器,在编译时会将一个隐藏的参数作为它的第一个参数买这个参数表示了它的直接外围实例。
    利用反射创建嵌套类对象时,就需要我们显式地传递这个参数了。因此不能使用newInstance()来创建对象。取而代之的是用Constructor。
  • 静态和非静态内部类的选择
    内部类中需要使用直接外围类时,创建非静态内部类
    其它情况优先创建静态内部类
  • 应尽量避免使用反射来实例化内部类。
  • PrintStream
    1. System.out、System.err都是PrintStream类型的。
    2. PrintStream可被创建为自动刷新的,触发刷新的条件:
      • 一个字节数组被写入
      • println()方法被调用
      • 换行字符或者字节被写入
    3. 注意的是,write(int)方法是唯一一个在自动刷新功能开启的情况下不刷新PrintStream的输出方法
    4. 对应3.,System.out.write(int)方法就不会自动刷新
  • 子进程
    1. 由于某些本地平台只提供有限大小的缓冲,所以如果不能迅速地读取子进程的输出流,就有可能会导致子进程的阻塞,甚至死锁。
    2. 解决方案:人为地排空子进程的输出流,即获取子process的输入流,将流中的内容全部读出。
  • 创建一个类的实例的方法
    1. 正常new
    2. 反射创建
    3. 序列化后反序列化
    4. 克隆
  • 对于一个实现了Serializable接口的单件类,必须有一个readResolve方法返回其唯一实例。否则,利用序列化反序列化可以创建出新的实例,从而失去单例
    private Object readResolve() {
    return t;
    }
  • Thread.interrupt():
    Thread.interrupted():测试当前线程是否中断,同时清除当前线程的中断状态。
    Thread.isInterrupted():测试当前线程是否终端,不会清除中断状态。
  • Thread的中断机制
    1. Java的中断是一种协作机制。也就是说调用线程对象的interrupt方法并不一定就中断了正在运行的线程,它只是要求线程自己在合适的
      时机中断自己。每个线程都有一个boolean的中断状态(这个状态不在Thread的属性上),interrupt方法仅仅只是将该状态置为true。
    2. 对正常运行的线程调用interrupt()并不能终止他,只是改变了interrupt标示符
    3. 如果一个方法声明抛出InterruptedException,表示该方法是可中断的,比如wait,sleep,join,也就是说可中断方法会对interrupt调用
      做出响应
    4. Object.wait,Thread.sleep方法,会不断的轮询监听interrupted标志位,发现其设置为true后,会停止阻塞并抛出 InterruptedException
      异常。
  • Java在使用一个类时,总是会先检查一下其是否已经进行了初始化。若没有,则进行初始化,若发现正在初始化,则等待其初始化是否已经完成。
  • 将int或long转换为float;long转换为double时,都会导致精度丢失。要注意:使用
  • List<?> 通配符类型
    1. 与List相比,其实一个参数化类型,需要更强的类型检查。
    2. 编译器一般不会允许添加除了null以外的任何元素到List<?>中,而List可添加任意元素
    3. 应该避免直接使用原生类型
  • 原生类型、通配符类型、参数化类型是不同的,不能混用。
10-05 16:37