Fork me on GitHub
文章目录
  1. 1. Java核心技术读书笔记
    1. 1.0.1. volatile变量不能提供原子性!
    2. 1.0.2. 字节码校验
    3. 1.0.3. 调用构造器的具体处理步骤
    4. 1.0.4. 泛型的约束和局限性
    5. 1.0.5. 对象拷贝与对象克隆的区别
    6. 1.0.6. 编写equals方法的建议
  • 2. 关于动态绑定
  • 3. 感谢
  • Java核心技术读书笔记

    1. 在Java中,共有8种基本类型:4种整型(int4,short2,long8,byte1)、2种浮点类型(float4,double8)、字符类型(char2表示Unicode编码的字符单元)、boolean类型(1bit)
    2. 强烈建议不要在程序中使用char类型,除非确实需要对UTF-16代码单元进行操作(char类型用UTF-16编码描述一个代码单元):( Why?
    3. 整型和布尔型不能相互转换
    4. &&和||是按照短路方式求值的。&和|运算符不按照短路方式计算!
    5. 对移位运算符右侧的参数需要进行模32的运算(除非左边的操作数是long类型,此时需模64)。例如,1 << 35和1 << 3是相等的
    6. String类对象是不可变字符串。编译器可以让字符串共享
    7. 字符串比较使用equals方法,千万不要使用==!这个运算符只能确定两个字符串是否放置在同一个虚拟机中位置上(虚拟机完全可能将内容相同的多个字符串的拷贝位置放在不同的地方!)
    8. 如果需要用许多小段的字符串构建一个字符串,那么应该使用StringBuilder。在JDK5.0中引入StringBuilder类。这个类的前身是StringBuffer,其效率稍微有些低,但是允许采用多线程的方式执行添加或删除字符的操作。如果所有的字符串都在一个单线程中编辑(通常情况下),应该用StringBuilder替代它。
    9. 在C++中可以在嵌套的块中重定义一个变量(内层定义的变量会覆盖外层定义的变量),但是这在Java中是不允许的!
    10. 在循环中,检测两个浮点数是否相等需要格外小心!注意舍入误差,即可能浮点数无法用二进制完全表示。
    11. switch语句的case标签可以是:1. 类型为char, byte, short, int(或者其包装类Character, Byte, Short, Integer);2. 枚举常量;3. 字符串字面量(Since JDK7.0)
    12. 字符串长度string.length()。数组的长度array.length
    13. 数组长度可以为0,和null不同
    14. Java中的[]运算符被预定义为检查数组边界,并且没有指针运算,即不能通过a+1得到下一个数组元素
    15. 如果将一个方法应用于null对象上,就会产生空指针异常,这是RuntimeException运行时异常
    16. 全局变量会自动初始化,局部变量不会自动初始化。
    17. 所有Java对象都存储在堆中。
    18. 当且仅当没有给类显式提供任何构造器的情况下,系统才会提供一个默认的构造器
    19. 关于初始化块:首先运行初始化块,然后才运行构造器的主体部分。不过,通常把初始化代码放在构造器中就行了。
    20. 如果在一个类的构造器A中调用另一个构造器B,该调用代码必须写在构造器A的第一行!
    21. Java程序可以没有main方法(但是JDK1.8不可以了)。代码如下:

      1
      2
      3
      4
      5
      6
      public class Hello {
      static {
      System.out.println("Hello, World");
      System.exit(0);
      }
      }
    22. 静态方法与类绑定–静态绑定。实例化变量之后,变量是什么类型的,就会调用那个类的静态方法(其实是废话)

    23. 方法的名字和参数列表称为方法的签名,返回类型不是签名的一部分!
    24. 动态绑定。在运行的时候,调用parent.method的解析过程
      1. 首先,虚拟机提取object的实际类型的方法表。这既可能是Parent类型,Child1类型,Child2类型…类的其他子类的方法表
      2. 接下来,虚拟机搜索定义method签名的类。此时,虚拟机可以知道应该调用哪个方法。
      3. 最后,虚拟机调用方法。
    25. 类强制转换:1. 只能在继承层次内进行类型转换。2. 在将父类转换成子类之前,应该使用instanceof进行检查。

      1
      2
      3
      if (parent instanceof child) {
      result = (child)parent;
      }
    26. 通过类型转换调整对象的类型并不是一种好的做法。

    27. Java中的受保护部分对于所有子类以及同一个包中的其他所有类都是可见的。
    28. 4个访问修饰符:
      1. private: 仅仅对本类可见
      2. public: 对所有类可见
      3. protected: 对本包和所有子类可见
      4. default: 默认,对本包可见,不需要修饰符
    29. 在Java中,只有基本类型不是对象,其他一切皆对象。
    30. 如果两个对象持有相同的引用,那么它们一定是相等的。
    31. 对于数组类型的域,可以使用静态的Arrays.equals方法检测相应的数组元素是否相等。
    32. 字符串的散列码hashCode是由内容导出的。在StringBuffer内部没有定义hashCode方法,它的散列码是由Object类的默认hashCode到处的对象存储地址。
    33. 建议:Equals和hashCode的定义一致。两个相等的对象拥有相等的散列码。空对象的散列码为0
    34. ArrayList从JDK7.0开始,可以省略右边的类型参数
    35. JDK5.0之前没有提供泛型类,而是有一个ArrayList类,其中保存的类型是Object,它是自适应大小的集合
    36. ArrayList的CRUD都不能越界!
    37. 9个包装器类(Integer, Long, Float, Double, Short, Byte, Character, Void, Boolean)。对象包装器类是不可变的,一旦构造了包装器,就不允许改变其中的值。同时,对象包装器类还是final,因此不能定义它们的子类
    38. 自动装箱、自动拆箱
    39. 自动装箱规范要求boolean, byte, char <= 127 ,介于-128~127之间的short和int被包装到固定的对象中。装箱和拆箱是编译器认可的,而不是虚拟机。
    40. Java中方法都是值传递的
    41. JDK5.0之前,每个Java方法都有固定数量的参数,但是之后支持变参方法Vararg
    42. 只在必要的时候使用反射。
    43. 接口中的所有方法自动地属于public,常量都自动是public static final。不能有实例域和静态方法!
    44. 默认的克隆操作是浅拷贝,如果要深拷贝,必须重载clone
    45. 内部类可以访问外围类的私有变量。内部类引用局部变量,必须是final
    46. final变量可以为空(全局变量,自动初始化)
    47. 当内部类不需要访问外围类对象的时候,应该使用静态内部类(其它都一样)。声明在接口中的内部类自动成为static, public类
    48. 代理(since JDK1.3):利用代理可以在运行时创建一个实现了一组给定接口的新类。只有在编译时无法确定需要实现哪个接口时才有必要使用。
    49. Throwable => Error + Exception.由程序错误导致的异常属于RuntimeException;而程序本身没有问题,但由于像I/O错误这类问题导致的异常属于其他异常
    50. 常见的RuntimeException:1. 错误的类型转换ClassCastException。2. 数组访问越界IndexOutOfBoundException。3. 访问空指针NullPointerException
    51. 其他异常:1. 试图在文件尾部后面读取数据。2. 试图打开一个不存在的文件。3. 试图根据给定的字符串查找Class对象,而这个字符串表示的类并不存在。
    52. Java语言规范将派生于Error类或RuntimeException类的所有异常成为未检查unchecked异常,所有其他的异常称为已检查异常checked
    53. 实际上,所有的错误都发生在运行时。
    54. 假设在try和finally块中均含有return语句,利用return语句从try语句块中退出。在方法返回前,finally子句的内容将被执行。如果finally子句中也有一个return语句,这个返回值会覆盖原来的返回值。
    55. 泛型程序设计意味着编写的代码可以被很多不同类型的对象所重用
    56. 数组在连续的存储位置上存放对象引用,链表将每个对象存放在独立的结点中。在Java中,所有链表实际上都是双向链表
    57. 为了避免发生并发修改的异常,请遵循下述规则:可以根据需要给容器添加许多迭代器,但是这些迭代器只能读取列表。另外,再单独附加一个既能读又能写的迭代器
    58. list的get方法(最好不要使用):每次查找一个元素都要从列表的头部重新开始搜索。LinkedList对象根本不做任何缓存位置信息的操作。
    59. TreeSet树集(有序集合):在对集合进行遍历时,每个值将自动地按照排序后的顺序呈现。
    60. TreeSet添加元素快于数组|链表,慢于散列表。如果树中包含n个元素,查找新元素的正确位置平均需要log2 n次比较。
    61. 优先级队列(Priority Queue)中的元素可以按照任意的顺序插入,却总是按照排序的顺序进行检索(所以,总是获得最小的那个元素)。优先级队列使用了堆(heap)-一个可以自我调整的二叉树。
    62. Hashtable和HashMap几乎是一样的。唯一的区别是:Hashtable的方法是同步的,而HashMap不是。
    63. 如果向一个变量写入值,而这个变量接下来可能会被另一个线程读取,或者,从一个变量读值,而这个变量可能是之前被另一个线程写入的,此时必须使用同步
    64. volatile关键字为实例域的同步访问提供了一种免锁机制。如果声明一个域为volatile,那么编译器和虚拟机就知道该域是可能被另一个线程并发更新的。
    65. 对象序列化是以特殊的文件格式存储对象数据的。(每个文件都是以AC ED开头的)
    66. Path和Files类since JDK7.0
    67. 每个Java程序至少拥有三个类加载器:1. 引导类加载器。2. 扩展类加载器。3. 系统类(应用类)加载器。引导类加载器负责加载系统类(通常从JAR文件rt.jar中进行加载),引导类加载器没有对应的ClassLoader对象。扩展类加载器用于从jre/lib/ext目录加载标准扩展。系统类加载器用于加载应用类。引导类加载器通常使用C语言实现的,扩展类加载器和系统类加载器通常是用Java来实现的。

    volatile变量不能提供原子性!


    volatile只提供了保证访问该变量时,每次都是从内存中读取最新值,并不会使用寄存器缓存该值——每次都会从内存中读取。而对该变量的修改,volatile并不提供原子性的保证。那么编译器究竟是直接修改内存的值,还是使用寄存器修改都符合volatile的定义。所以,一句话,volatile并不提供原子性的保证。所以,下面的代码并不能确保number的递增。还是要使用synchronized

    1
    2
    3
    4
    private volatile int number = 0;
    public void add_number() {
    ++number;
    }

    字节码校验


    在类加载器将新加载的字节码传递给虚拟机时,这些字节码首先要接受校验器的校验。下面是校验器执行的一些检查:

    1. 变量要在使用之前初始化
    2. 方法调用与对象引用类型之间要匹配
    3. 访问私有变量和方法的规则没有被违反
    4. 对本地变量的访问都落在运行时堆栈内
    5. 运行时堆栈没有溢出

    调用构造器的具体处理步骤

    1. 所有数据域被初始化为默认值(0 => 数值型, false => 布尔型, null => 对象类型)
    2. 按照在类声明中出现的次序,依次执行所有域初始化语句和初始化块
    3. 如果在构造器第一行调用了第二个构造器,则执行第二个构造器的主体
    4. 执行这个构造器的主体

    泛型的约束和局限性

    1. 不能用基本类型实例化类型参数(用包装类)
    2. 运行时类型查询只适用于原始类型
    3. 不要创建参数化类型的数组
    4. 不能实例化类型变量
    5. 泛型类的静态(变量、方法)上下文中类型无效

    对象拷贝与对象克隆的区别

    1. 当拷贝一个对象时,原始变量和拷贝变量引用同一个对象
    2. 当克隆一个对象时,引用新的对象

    编写equals方法的建议

    1. 显式参数命名为otherObject,稍后需要将它转换成另一个叫做other的变量。
    2. 检测this与otherObject是否引用同一个对象,即如果this == otherObject,返回true
    3. 检测otherObject是否为null,如果是null,返回false
    4. 比较this与otherObject是否属于同一个类。如果equals的语义在每个子类都有变化,就用getClass判断。如果所有的子类都拥有统一的语义,就用instanceof检测
    1
    2
    3
    4
    5
    if (!(getClass() == otherObject.getClass()) 
    return false;

    if (!(otherObject instanceof ClassName))
    return false;
    1. 将otherObject转换为相应的类类型变量
    2. 对所有需要比较的域进行比较。使用==比较基本类型于,使用equals比较对象与。如果所有的域都匹配,就返回true;否则返回false。
    3. 如果在子类中重新定义equals,就要在其中包含调用super.equals(other)。

      关于动态绑定


      必须明确的一点是,动态绑定针对的是对象的方法,这一点尤为重要!因此,在向上转型的情况下,对象的方法可以找到子类,而对象的属性还是父类的属性。

    感谢

    感谢访问我的个人博客的朋友,如果您感觉本站对您搜索的问题有所帮助,并感觉对本站还满意的话,顶一下吧,希望您把本站分享给您的朋友!在此对您表示由衷的谢意! :)