纯干货!JAVA细节标注+源码分析

作者: 龙猫小爷 | 来源:发表于2016-09-15 00:29 被阅读957次

    一、基本数据类型

    注释

    单行注释://

    区域注释:/*   */

    文档注释:/**  */

    数值

    对于byte类型而言,有28个可能的数值,一半是负数一半是正数。因为0被存储为正的二进制,因此整数的个数比负数少1。

    如果要表示八进制在数值前加0,要表示十六进制在数值前加0x。

    使用float类型时,必须添加后缀F或f。在JAVA SE5.0前,浮点型只能用十进制数表示,从JAVA SE5.0之后可以使用十六进制表示浮点数值。例如:0.125可以表示为0x1.0p-3。在十六进制表示法中,使用p表示指数,而不是e。

    JAVA采用UNICODE编码,因此每个字符占2个字节。

    boolean不能与任何类型的值进行转换替换。也就是说JAVA中没有false是0,true是非0这种说法。

    数值转换

    自动转换:byte->short(char)->int->long->float->double,低精度向高精度转换时是自动的

    手动强制转换:高精度向低精度转换,要进行手动强制转换。

    隐含强制转换:byte b=123;因为123是int类型的字面常量,他们之间转换需要使用强制类型转换。这种情况下由系统自动识别进行转换。但是对于变量则不行。如:int i=123;  byte b=i;


    二、表达式

  1. “/”运算符
  2. 整数除以0,编译通过,将会报运行时错误。
  3. 浮点数除以0则可以得到结果。                                                                                                                           
  4. 自增自减运算
  5. 自增自减运算符不进行类型提升。
  6. 前缀方式是先执行自增或自减运算,再运行表达式。
  7. 后缀方式是先运行表达式,再执行自增或自减运算。
  8. 关系运算
  9. NaN表示不知道结果,所有与NaN比较都是返回false。
  10. 短路与非短路逻辑运算:&&、&或||、|。&&运算的特点体现为其不在无意义的计算上浪费时间,如果第一个运算为false则接下来的表达式则将不再执行,而&则是所有的表达式都要执行。
  11. 三元运算符:<逻辑表达式>?<结果表达式1>:<结果表达式2>,三元运算符中结果表达式1和2如果为基本类型,则要求其类型是兼容的(即类型相同或者可以相互转换),如果不行,则编译报错。
  12. 位运算:&、|、^、~分别表示与、或、异或、按位取反。
  13. 移位运算:<<、>>、>>>分别表示左移、右移、无符号右移。左移时,在右边填充0;右移时填充的值与最高位相同;无符号右移填充0。
  14. Math.ceil():返回一个大于等于操作数的最近整数值。                                          
  15. Math.floor():返回一个小于等于操作数的最近整数值。
  16. Math.round():它表示“四舍五入”,算法为Math.floor(x+0.5),即将原来的数字加上0.5后再向下取整。                                                                                      

  17. 三、流程控制

  18. switch多分支语句
  19. 合法判断表达式:基本类型表达式(返回值必须是int兼容型的,byte\short\char\int);枚举类型表达式;字符串类型表达式。
  20. 合法的case表达式:与判断表达式相匹配(两种类型必须相同);自身是常量(字面常量或final变量);不能有两个相同的case。
  21. break:循环结束并推出,如果嵌套了多层循环,break跳出离其最近的一层循环。
  22. continue:中断当此循环。当其执行时,本次循环结束,进入条件判断,如果条件满足,进入下一次循环。

  23. 四、数组

  24. 声明数组
  25. int[] a[]这样的形式也是正确的,表示声明一个二维数组。
  26. 在JAVA中数组引用声明时不可以提供数组长度,否则编译报错。例如:int[5]   a;这种写法是错误的。
  27. 数组是默认初始化的,无论是在类中还是在方法中。String类型初始化为null,其他基本类型为对应的0值。
  28. 使用Arrays.sort()方法进行排序,排序结果还存放在原来的数组中。
  29. 比较两个数组的元素值是否相同使用:Arrays.equals(a,b)。
  30. String[] args,中的args表示执行程序时输入的命令行参数。

  31. 五、对象和类

  32. 面向对象的特点
  33. 面向对象是一种从组织结构上模拟客观世界的方法,它从组成客观世界的对象着眼,通过抽象,将对象映射到计算机系统中,又通过模拟对象之间的相互作用、相互联系来模拟现实客观世界,描述客观世界的运动规律。
  34. 面向对象技术以基本对象模型为单位,将对象内部处理细节封装在模型内部,并且重视对象模块间的接口联系和对象与外部环境间的联系,能层次清晰地表示对象模型。
  35. 某类对象是对现实世界具有共同特性的某类事物的抽象。
  36. 对象蕴含许多信息,可以用一组属性来表征。
  37. 对象内部含有数据和对数据的操作。
  38. 对象之间是相互关联和相互作用的。
  39. 成员变量
  40. 基本类型的成员变量初始化为0值,引用型成员变量初始化为null。
  41. 局部变量不会初始化,一旦使用未初始化的值则会报错,如果没有用到则不会报错。
  42. 变长参数
  43. 在实际开发时需要能够灵活地接受参数的方法,即方法参数的个数是不定的,往往我们会采用数组来实现,但是过于麻烦。
  44. 从Java SE 5.0开始提供了变长参数的新特性,是开发人员能够开发出真正接受任意个数参数的方法。
    1. public static void sortAndPrint(int... entrys){
    2. //此时的entrys可以当作数组来进行操作
    3. Arrays.sort(entrys);
    4. }
    5. //调用该方法
    6. sortAndPrint(1,2,3,4,5,6,7,8,9,10);
  45. 一个方法中最多只能有一个变长参数。
  46. 如果方法的参数不止一个,变长参数必须为最后一个。
  47. this预定义对象引用:this只能在属于对象的方法或语句块中使用,类似main这样用static修饰的方法则不可以,因为this是属于对象的,而static是属于类的,当对象还没有创建时我们依旧调用类方法,这时候this就为空造成错误。
  48. Date方法
  49. new Date()表示创建当前系统时间;new Date(116,7,3)表示1900+116=2016年,7+1=8月,3日。
  50. boolean after/before(Date when)表示判断时间的前后。                                                                                     

  51. 六、访问控制

  52. final变量
  53. final修饰变量的含义是,该变量一旦被显式初始化之后其值就不能再被修改。要特别注意,对原始类型和引用类型的变量值不能修改的含义不完全一样。引用类型不能修改表示不能再该对象上重新new一个新对象,但是该对象内部的成员变量却可以修改。
  54. final成员变量系统不会为其赋值默认初始值,而是要求在构造器完成之前必须显示初始化。在声明的同时初始化;在非静态语句块中进行初始化;在构造器中进行初始化。
  55. java中可能有多个修饰符同时修饰一个元素,这多个修饰符之间可以任意交换次序。int属于变量类型,不属于修饰符,其位置不能改变。
  56. final的局部变量可以只声明而不初始化,初始化的时间只要在使用其值之前就可以。
  57. final修饰的类为最终类,不能被继承。
  58. final修饰的方法为最终方法,无法重写,只能继承,可以重载。
  59. static关键字
  60. 方法中不能声明static变量。
  61. 如果要修饰常量则使用static final。
  62. 静态最终成员变量的初始化(static final系统同样不会为其自动初始化),只能在声明的同时初始化或者在静态语句块中进行初始化。

  63. 七、继承

  64. 成员变量的隐藏:对于成员变量来说,当子类本身具有与继承自父类的成员变量名称相同的成员变量时,便构成了成员变量的隐藏。即在子类中直接调用该成员变量时,将调用子类本身具有的成员变量,而不是从父类继承的成员变量。但是可以通过super关键字进行访问。
  65. 对象引用的赋值与比较
  66. 引用复制时可以直接将子类引用赋值给父类引用,若需将父类引用赋给子类引用则必须进行强制类型转换。
  67. 比较两个引用是否指向同一个对象有如下要求:相同类型的引用可以进行比较;不同类型的引用要进行比较,其中一个的类型必须派生自另一个,否则编译报错。
  68. 抽象方法
  69. 抽象方法不能用final和private修饰,因为其违背了抽象方法需要在子类中继承并重写的原则。
  70. 抽象方法不能用static、native、synchronized修饰,因为这三个修饰符都要求方法中必须要有实现。http://www.xuebuyuan.com/1668678.html 

  71. 八、接口

  72. 接口的特性
  73. 接口可以由abstract修饰,因为其本身就是抽象的,如果不写编译时系统也会自动加上。
  74. 用public修饰接口,其必须位于与其同名的java文件中。
  75. 接口可以同时继承任意多个接口,使用extends关键字。
  76. 接口内不能有静态语句块等执行代码部分。
  77. 接口中成员变量的作用是能够保证实现该接口的任何类都可以访问相同的常量,不能动态将其值进行修改,同时使用声明的常量也会增强代码可读性。
  78. 接口回调
    1. public class LeftFragment extends Fragment{
    2. public interface MyListener{
    3. public void showMessage(int index);
    4. }
    5. private MyListener mListener;
    6. @Override
    7. public void onAttach(Activity activity) {/*判断宿主activity是否实现了接口MyListener*/
    8. super.onAttach(activity);
    9. try {
    10. mListener = (MyListener) activity;
    11. }catch (ClassCastException e) {
    12. throw new ClassCastException(getActivity().getClass().getName()
    13. +" must implements interface MyListener");
    14. }
    15. }

    16. class MyButtonClickListener implements View.OnClickListener{
    17. @Override
    18. public void onClick(View v) {
    19. if(button == mButton1) {
    20. mListener.showMessage(1);
    21. }
    22. if(button == mButton2) {
    23. mListener.showMessage(2);
    24. }
    25. if(button == mButton3) {
    26. mListener.showMessage(3);
    27. }
    28. }
    29. }
    30. }
    1. public class MainActivity extends Activity implements LeftFragment.MyListener{
    2. @Override
    3. public void showMessage(int index) {
    4. if(1 == index) {
    5. showMessageView.setText(R.string.first_page);
    6. }else if(2 == index) {
    7. showMessageView.setText(R.string.second_page);
    8. }else {
    9. showMessageView.setText(R.string.then_page);
    10. }
    11. }
    12. }

  79. 九、构造器

  80. 级联调用构造器
  81. 先调用父类构造器之后才会调用子类构造器。
  82. 当子类构造器中没有显式调用父类构造器,则系统会自动调用父类的无参构造器,如果父类没有无参构造器则编译错误。
  83. 一旦自定义编写了一个构造器,则系统不会提供默认构造器。
  84. 在实际开发中如果没有特殊需要,给每一个类提供一个无参构造器是良好的习惯。
    • 调用兄弟构造器
    1. 重载构造器之间的相互调用称为调用兄弟构造器。
    2. 使用关键字this来调用兄弟构造器
      1. public H(String s){
      2. }
      3. public H(){
      4. this("lin");
      5. }

    十、内部类

  85. 非静态内部类
  86. 从非静态内部类外面看,完全可以将其看成是内部类的一个非静态成员,与普通的成员没有什么区别。
  87. 内部类和外部类中的其他成员是一个级别的,其也是外部类的一个成员。在内部类类体中,其又是单独的一个类,一样有其自己的成员变量或方法。
  88. 外部类创建内部类对象与普通对象的语法相同,都是使用new调用相应构造器即可。
  89. 外部类之外创建内部类对象,必须在所属对象存在的情况下才存在,因此非静态内部类在创建了外部类对象后才可以使用。
  90. 内部类会存有外部类的引用,有可能会造成内存泄漏。当内部类的工作没有完成,但是外部类已经结束运行,这时候内部类持有外部类的引用导致外部类无法被回收。
  91. 内部类不可以有静态成员。
  92. 非静态成员内部类被static修饰后就变成了静态成员内部类。
  93. 内部类与外部类之间的成员互访
  94. 内部类可以任意访问外部类的任何成员。
  95. 外部类要访问内部类的成员,首先要创建内部类的对象。外部类可以访问内部类的任何成员。
  96. 如果外部类和内部类有同名的成员变量时,在内部类中使用this是无法访问外部类的变量的,因为this指向的还是内部类自己。要使用外部类的变量:<外部类名>.this.<外部类被访问的成员变量名>。
  97. 局部内部类
  98. 局部内部类只在局部有小,因此只能在其有效的位置访问或创建其对象。
  99. 局部内部类可以访问外部类的成员,但却不可以访问同在一个局部的普通局部变量。因为普通局部变量随着所在语句块的执行结束而消亡,而创建的局部内部类对象并不会随着语句块的结束而消亡。如果在语句块结束后,调用了局部内部类对象中访问普通局部变量的方法就要出现问题,因此要访问的局部变量不存了。
  100. 局部内部类可以访问final的局部变量,因为final局部变量不会随着语句块的结束而小时,因此可以被局部内部类访问。
  101. 静态方法中的局部内部类只能访问外部类静态的成员。
  102. 静态内部类
  103. 静态内部类就相当于外部类的静态成员,因此不能直接访问外部类的非静态成员。
  104. 直接使用new <外部类名>.<静态内部类名>创建静态内部类。
  105. 非静态内部类应该与外部类的对象存在着对成员的共享关系,其是外部类对象组成的一部分,用来辅助外部类对象工作。静态内部类其实已经脱离了外部类的控制,实质上只是一个放置在别的类中普通类而已。
  106. 静态内部类不会保存外部类的引用。
  107. 匿名内部类
  108. 匿名内部类没有名称,因此匿名内部类在声明类的同时也创建了对象。匿名内部类的声明要么是基于继承的,要么是基于实现接口的。
  109. 基于继承的匿名内部类:new后面跟着匿名内部类要继承父类的某个构造器,在匿名内部类类体中可以重写父类的方法或提供自己新的方法与成员。因为匿名内部类没有名字,没有办法声明匿名内部类类型的引用,因此提供新的方法与成员只能自己内部使用,外面无法调用。 
    1. public class text {
    2. public static void main(String[] args) {
    3. int j = 0;
    4. H h = new H() {
    5. public void change(H h) {
    6. System.out.println(i);
    7. System.out.println(j);
    8. }
    9. public void la(){
    10. }
    11. };
    12. h.change(h);
    13. //无法调用h.la();
    14. }
    15. }
    16. class H {
    17. public int i = 0;
    18. public H() {
    19. }
    20. public void change(H h) {
    21. }
    22. }
  110. 基于接口的匿名内部类:将new之后的类名改为接口名,表面上感觉是创建了接口对象(接口无法实例化),实际上是创建了实现此接口的匿名内部类对象。
  111. 匿名内部类要么实现一个接口,要么继承一个类,不能同时既进行继承又实现接口。
  112. 匿名内部类可以减少代码冗余,简化开发。
    1. Button.setOnClickListener(new View.onCliclListener(){
    2. //匿名内部类,不用重新创建一个类
    3. }
    4. );

  113. 十一、异常处理

  114. finally语句块的使用
  115. finally语句块最多只能有一个,也可以没有,该语句块在try-catch语句中应该紧跟最后一个catch语句块。
  116. 一旦异常抛出,其后面的代码将不再执行。
  117. finally语句块中的代码无论在什么情况下都将保证执行。
  118. 虽说是保证执行,但是也有特殊情况可能会中断finally语句执行——finally语句块中本身产生异常;执行finally语句块的线程死亡;finally语句块中执行了System.exit(0)方法;计算机掉电。
  119. 即使try和catch语句中有return语句执行,在离开前也要执行finally语句块。
  120. try、catch、finally使用注意问题
  121. 无catch时finally必须紧跟try。
  122. catch与finally不能同时省略。
  123. try、catch、finally之间不能插入其他代码。
  124. 异常的层次结构

  125. Throwable类有两个直接子类,Error和Exception类。其中Exception类的直接或间接子类,除去RuntimeException类的直接或间接子类,称为捕获异常(检查异常,非运行时异常),其他的都为未捕获异常(非检查异常,运行时异常)。
  126. 对于捕获异常来说,若try语句块里不可能抛出某种类型的捕获异常,而在catch里又编写了该捕获异常的处理程序,将不能通过编译。但是对于未捕获异常来说没有此限制,即在catch中使用Error、RuntimeException等。
    1. //以下代码无法通过编译,如果将IOException改为Exception则可以
    2. try {
    3. int a=0;
    4. } catch (IOException e) {
    5. System.out.println("catch");
    6. }
  127. 再次抛出异常:实际开发中,不可能所有的异常在其产生的位置都能立即进行处理,有时需要上报,由上一级程序进行处理。这里的上报就是异常的再次抛出。
  128. 显性再抛出
  129. 一旦方法有可能抛出捕获异常,则在方法声明时需要特别指出,否则编译报错。而通过编写代码将catch语句捕获的异常再次抛出(throw e),就称为显性再抛出。
    1. public static void la(int i) throws Exception {
    2. try {
    3. throw new IOException() ;
    4. } catch (Exception e) {
    5. throw e;
    6. }
    7. }
  130. throws在方法声明时使用,用来指出该方法可能再次抛出异常;throw用来实际抛出异常。
  131. throw语句抛出的是捕获异常,其类型必须与所在方法声明中可能抛出的捕获异常类型相同或者为其子类,否则编译错误。
  132. 隐性再抛出
  133. 隐性与显性再抛出效果是相同的。若抛出的是捕获异常,隐性和显性再抛出都必须在方法中明确声明。隐性再抛出是在方法体中没有任何抛出异常的语句,若产生异常将自动抛出。
    1. public static void la(int i) throws Exception {
    2. }
  134. 如果只是想将收到的异常直接再抛出,不比使用显性再抛出,直接使用隐性再抛出即可。两种抛出与捕获处理掉一样,都是合法的异常处理方法。
  135. 异常的匹配
  136. try-catch语句捕获并处理异常时,可以为catch语句块指定处理的异常类型。指定处理的异常类型若没有任何子类,则只能捕获指定的异常类型;指定处理的异常类型若有子类,则指定类型及其子类的异常都可以捕获。(例如Exception可以捕获其子类的异常)。
  137. 若多个catch语句块中所指定的异常类型相互有派生关系,那么必须将子类的异常写在上面,父类型的异常写在下面(否则将编译错误)。若为级别相同或者没有任何派生关系的异常类型,其catch语句放置的先后顺序无所谓。
    1. public static void la(int i) {
    2. try {
    3. if(i==1){
    4. throw new IOException();
    5. }else {
    6. throw new ClassNotFoundException();
    7. }
    8. } catch (IOException e) {
    9. // TODO: handle exception
    10. }catch (ClassNotFoundException e) {
    11. // TODO: handle exception
    12. }catch (Exception e) {
    13. // TODO: handle exception
    14. }
    15. }

  138. 十二、字符串

  139. 字符串编码格式转换:getBytes(String charsetName)方法获取编码格式的字节数组,JAVA的字符串默认编码为UNICODE。
    1. //将默认编码格式转换为iso8856
    2. String s1="LinGengLiang";
    3. String s2=new String(s1.getBytes(),"iso8856");
    4. //再将iso8856转换为gd2312
    5. String s3=new String(s2.getBytes("iso8856"),"gd2312");
  140. String的一些基础知识
  141. public String intern():此方法将指定字符串对象在字符串常量池中对应对象的引用返回,若其本身就在字符串常量池中,则直接将自己的引用返回,若该字符串在堆中,则返回字符串常量池中其联系对象的引用。
  142. String类是无法被继承的,应为其为final类。
  143. 比较字符串内容是否相同,直接比较它们在常量池中的引用是否相同即可。
  144. 使用“+”进行字符串连接,其实是生成StirngBuffer对象后使用append进行连接。
  145. StringBuffer一些基础知识
  146. 该类对象允许对其内容进行修改,而不产生冗余的中间对象。
  147. append和insert都被重载了,不仅可以插入字符串也可以插入基本数据类型和其他类型。
  148. StringBuffer虽然也有equals方法,但是其功能相当于将两个引用进行“==”比较,并没有实现比较字符串内容的功能。如果要比较内容,先要调用toString方法再进行比较。
  149. 同步操作。
  150. StringBuiler一些基础知识:StringBuiler类的执行效率比StirngBuffer类稍高,但是StringBuiler类字符串编辑方法并没有进行同步,在多线程环境下可能会出现问题。
  151. String类中正则式的应用
  152. public boolean matches(String regex):该方法可以检查字符串是否匹配指定的模式,这个匹配针对整个字符串而言的,只能进行整体匹配,不能只匹配一部分。
  153. public String replaceAll/replaceFirst(String regex,String replacement):利用正则表达式对字符串中部分匹配的内容进行替换。
  154. public String[] split(String regex,int limit):用指定的分隔符将字符串拆分为多个子串。

  155. 十三、集合框架

  156. 重写equals方法
  157. 重写equals方法要满足一下规则:自反性(x.equals(x)==true)、对称性(a.equals(b)==b.equals(a))、传递性、一致性(多次对ab进行equals检查的结果应当始终相同)、a.equals(null)==False。
  158. 重写equals步骤:判断是否是同一个引用,判断传进的是否为null,使用instanceof测试传进的对象是否是相同的类,将传递进来的引用强制转换成自己的类型测试成员是否等价。
  159. 重写equals时,访问限制必须为public类型;方法的入口参数必须为object类型,不可以是别的类型(没有构成重写,也不能通过多态调用重写的方法)。
  160. 重写了equals方法就必须重写hashCode方法,否则两个等价的对象可能得到不同的哈希码(因为默认对象的哈希码是通过存储地址获得的,就算两个对象的内容相同,得到的哈希码有可能不同)。
  161. 重写hashCode方法
  162. 如果equals测试成立,则两个对象的哈希码必然相同;若两个对象中的哈希码不相同,则equals测试一定不成立。
  163. 对哈希码取值算法需要一个合适的区分度,区分度太大太小都不好。在同样的区分度下,若使得对象可以按照哈希码均匀分布式最好的。
  164. Ordered与Sorted接口
  165. Ordered含义是按照某种由具体情况决定的顺序进行排序,例如按先后顺序排序,这种顺序是由后天指定,是由具体情况的客观因素造成的。Ordered排序意味着可以按照某种后天规定的顺序遍历其中所有的元素。
  166. 按照Ordered排序的集合:ArrayList,Vector,LinkedList,List接口,LinkedHashSet。
  167. Sorted含义是按照天然的顺序进行排序,这种顺序是由先天指定的、自身条件影响的、其代表了参与排序元素的本质,不受外界因素的影响。
  168. 按照Sorted排序的集合:TreeSet,TreeMap,SortedSet,SortedMap。
  169. List
  170. 集合框架中的类并不真正存放对象,而只是存放对象的引用。
  171. 集合如果不指定泛型类型,不影响使用,并且可以存储任意类型的数据。
    1. HashMap map=new HashMap();
    2. map.put(1, 123);
    3. map.put("1", "456");
    4. System.out.println(map.get(1));
    5. System.out.println(map.get("1"));
  172. 在实际JAVA开发中尽量使用接口类型的引用,避免使用具体类型的引用。所以最好:List list=new ArrayList<>();
  173. Set
  174. Set不允许有重复的元素,Set中的元素没有顺序,JAVA有权以任意的顺序进行摆放。
  175. Set类不再具有索引,因此无法直接获取对象,只能通过迭代器进行查找。
    1. Collection treeSet=new TreeSet();
    2. Iterator i=treeSet.iterator();//获取迭代器
    3. //使用hasNext,next等方法进行访问

  176. Set中重复元素或者相同元素唯一的标准是equals方法返回true,而不是用眼睛看到具体内容形成的判断。
  177. 自定义满足Sorted集合的类:该类必须实现Comparable接口,重写compareTo方法的返回值来决定元素的顺序。该方法将返回一个int值,负整数、零或正整数代表了此对象是小于、等于还是大于指定对象。
    1. class H implements Comparable{
    2. public int i;
    3. public int k;
    4. public H(int i,int k) {
    5. this.i=i;
    6. this.k=k;
    7. }
    8. @Override
    9. public int compareTo(Object o) {
    10. H h=(H) o;
    11. return this.i-h.i;
    12. }
    13. }
  178. 定制SortedSet排序规则:在实际开发中可能会出现同一类型的元素在不同的SortedSet中按不同规则排序的需求,这时就需要实现Comparator接口,生成比较器,重写compare方法。该方法返回一个int值,与compareTo相同。(通常Comparable接口将指定一个元素类的默认排序方式,如果有特殊情况需要按别的规则进行排序则需要自定义比较器,例如H类中原本是根据i进行排序,在Compare类中则更改为k进行排序)。
    1. class Compare implements Comparator{
    2. @Override
    3. public int compare(Object o1, Object o2) {
    4. H h1=(H) o1;
    5. H h2=(H) o2;
    6. return h1.k-h2.k;
    7. }
    8. }
    9. Set treeSet=new TreeSet(new Compare()); //为集合添加比较器
  179. Map
  180. Map可以称为键/值集合,因为在实现了该接口的集合中,元素都是成对出现的。一个称之为键,另一个称之为值。Map中只是存放了键值对象的引用。
  181. HashMap类是Map接口的最常用实现之一,既不是Ordered的也不是Sorted,该类通过对键计算哈希码来决定值得存储,不保证键的存储顺序。HashMap可以使用null作为键值对。
  182. Hashtable对元素操作方法是同步方法,在运行时可能会有一些性能损失。该类不允许null作为键值对。
  183. LinkedHashMap类是通过双链表的方式实现的Map,键按照出入集合的顺序存储。
  184. TreeMap类正是该接口的一个实现,保证了键/值不管以什么顺序插入,在遍历的时候,都会以键的天然顺序进行遍历。
  185. 实际开发中有时需要对Map中的所有键值进行遍历,因为所有的Map实现都是按键进行存储的,一般是对键进行遍历再一一取出值即可。通过keySet()方法返回一个包含Map中所有键对象的Set集合,通过values()方法返回一个包含Map中所有值对象的Collection集合。
  186. 集合元素的常用操作:注意是Collections(工具类不能实例化)不是Collection
  187. 元素排序:Collections.sort(List list,Comparator c)——此方法默认将元素按照升序的方式进行排序,也可以使用比较器来指定排序规则。
  188. 搜索特定元素:Collections.binarySearch(List list,Object key)——搜索完成后返回指定对象的索引,如果不包含则返回负数。并且list必须是经过排序的,因为该方法是二分法。
  189. 任意打乱元素顺序:Collections.shuffle(List list)
  190. HashMap分析
  191. JDK1.6实现hashmap的方式是采用位桶+链表的方式,即散列链表方式。JDK1.8则是采用位桶+链表/红黑树的方式,即当某个位桶的链表长度达到某个阈值(8)的时候,这个链表就转化成红黑树,这样大大减少了查找时间。
  192. 定义链表结点结构Node,继承Map.Entry<k,v>。重写了hashCode的方法将key的hashCode与value的hashCode做或运算并返回。重写了equals方法,判断两个node是否相等,若key和value都相等,返回true,可以与自身比较为true。
    1. //Node是单向链表,它实现了Map.Entry接口
    2. static class Node<k,v> implements Map.Entry<k,v> {
    3. final int hash;
    4. final K key;
    5. V value;
    6. Node<k,v> next;
    7. //构造函数Hash值 键 值 下一个节点
    8. Node(int hash, K key, V value, Node<k,v> next) {
    9. this.hash = hash;
    10. this.key = key;
    11. this.value = value;
    12. this.next = next;
    13. }
    14. public final K getKey() { return key; }
    15. public final V getValue() { return value; }
    16. public final String toString() { return key + = + value; }
    17. public final int hashCode() {
    18. return Objects.hashCode(key) ^ Objects.hashCode(value);
    19. }
    20. public final V setValue(V newValue) {
    21. V oldValue = value;
    22. value = newValue;
    23. return oldValue;
    24. }
    25. //判断两个node是否相等,若key和value都相等,返回true。可以与自身比较为true
    26. public final boolean equals(Object o) {
    27. if (o == this)
    28. return true;
    29. if (o instanceof Map.Entry) {
    30. Map.Entry<!--?,?--> e = (Map.Entry<!--?,?-->)o;
    31. if (Objects.equals(key, e.getKey()) &&
    32. Objects.equals(value, e.getValue()))
    33. return true;
    34. }
    35. return false;
    36. }
    37. }
  193. 定义红黑树结点结构TreeNode,继承自LinkedHashMap.Entry(其是实质上就是继承自HashMap.Node)。并且在该结构内实现了一系列操作红黑树的方法。
    1. static final class TreeNode<k,v> extends LinkedHashMap.Entry<k,v> {
    2. TreeNode<k,v> parent; // 父节点
    3. TreeNode<k,v> left; //左子树
    4. TreeNode<k,v> right;//右子树
    5. TreeNode<k,v> prev; // needed to unlink next upon deletion
    6. boolean red; //颜色属性
    7. TreeNode(int hash, K key, V val, Node<k,v> next) {
    8. super(hash, key, val, next);
    9. }
    10. //返回当前节点的根节点
    11. final TreeNode<k,v> root() {
    12. for (TreeNode<k,v> r = this, p;;) {
    13. if ((p = r.parent) == null)
    14. return r;
    15. r = p;
    16. }
    17. }
    18. //............接下来还有许多方法
    19. }
  194. 首先生成一个Node数组,当添加一个元素(key-value)时,就首先计算元素key的hash值,以此确定插入数组中的位置,但是可能存在同一hash值的元素已经被放在数组同一位置了,这时就添加到同一hash值的元素的后面,他们在数组的同一位置,但是形成了链表,所以说数组存放的是链表。而当链表长度太长时,链表就转换为红黑树,这样大大提高了查找的效率。
  195. 填充比,默认值为0.75,如果实际元素所占容量占分配容量的75%时就要扩容了。如果填充比很大,说明利用的空间很多,但是查找的效率很低,因为链表的长度很大(当然最新版本使用了红黑树后会改进很多),HashMap本来是以空间换时间,所以填充比没必要太大。但是填充比太小又会导致空间浪费。如果关注内存,填充比可以稍大,如果主要关注查找性能,填充比可以稍小。
    1. private static final long serialVersionUID = 362498820763181265L;
    2. static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默认容量为16
    3. static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量
    4. static final float DEFAULT_LOAD_FACTOR = 0.75f;//填充比
    5. //当add一个元素到某个位桶,其链表长度达到8时将链表转换为红黑树
    6. static final int TREEIFY_THRESHOLD = 8;
    7. static final int UNTREEIFY_THRESHOLD = 6;
    8. static final int MIN_TREEIFY_CAPACITY = 64;
    9. transient Node<k,v>[] table;//存储元素的数组
    10. transient Set<map.entry<k,v>> entrySet;
    11. transient int size;//存放元素的个数
    12. transient int modCount;//被修改的次数fast-fail机制
    13. int threshold;//临界值 当实际大小(容量*填充比)超过临界值时,会进行扩容
    14. final float loadFactor;//填充比
  196. 扩容机制:构造Hash表时,如果不指明初始大小,默认大小为16,如果Node数组中的元素达到Node.length*填充比,则会开始扩容。旧数组的元素赋值到新数组中将非常耗时。
    1. final Node<K,V>[] resize() {
    2. //将旧数组赋值给oldTab
    3. Node<K,V>[] oldTab = table;
    4. //获取到目前的容量
    5. int oldCap = (oldTab == null) ? 0 : oldTab.length;
    6. //临界值
    7. int oldThr = threshold;
    8. int newCap, newThr = 0;
    9. if (oldCap > 0) {
    10. //当目前的容量超过最大容量时直接返回久数组
    11. if (oldCap >= MAXIMUM_CAPACITY) {
    12. threshold = Integer.MAX_VALUE;
    13. return oldTab;
    14. }
    15. //否则将容量扩大两倍,也将临界值扩大两倍
    16. else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
    17. oldCap >= DEFAULT_INITIAL_CAPACITY)
    18. newThr = oldThr << 1; // double threshold
    19. }
    20. else if (oldThr > 0) // initial capacity was placed in threshold
    21. newCap = oldThr;
    22. else { // zero initial threshold signifies using defaults
    23. newCap = DEFAULT_INITIAL_CAPACITY;
    24. newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    25. }
    26. if (newThr == 0) {
    27. float ft = (float)newCap * loadFactor;
    28. newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
    29. (int)ft : Integer.MAX_VALUE);
    30. }
    31. threshold = newThr;
    32. @SuppressWarnings({"rawtypes","unchecked"})
    33. Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    34. table = newTab;
    35. //数组辅助到新的数组中,分红黑树和链表讨论
    36. if (oldTab != null) {
    37. for (int j = 0; j < oldCap; ++j) {
    38. Node<K,V> e;
    39. if ((e = oldTab[j]) != null) {
    40. oldTab[j] = null;
    41. if (e.next == null)
    42. newTab[e.hash & (newCap - 1)] = e;
    43. else if (e instanceof TreeNode)
    44. ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
    45. else { // preserve order
    46. Node<K,V> loHead = null, loTail = null;
    47. Node<K,V> hiHead = null, hiTail = null;
    48. Node<K,V> next;
    49. do {
    50. next = e.next;
    51. if ((e.hash & oldCap) == 0) {
    52. if (loTail == null)
    53. loHead = e;
    54. else
    55. loTail.next = e;
    56. loTail = e;
    57. }
    58. else {
    59. if (hiTail == null)
    60. hiHead = e;
    61. else
    62. hiTail.next = e;
    63. hiTail = e;
    64. }
    65. } while ((e = next) != null);
    66. if (loTail != null) {
    67. loTail.next = null;
    68. newTab[j] = loHead;
    69. }
    70. if (hiTail != null) {
    71. hiTail.next = null;
    72. newTab[j + oldCap] = hiHead;
    73. }
    74. }
    75. }
    76. }
    77. }
    78. return newTab;
    79. }
  197. Hash方法,使Key值分布更加均匀。
    1. static final int hash(Object key) {
    2. int h;
    3. return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    4. }
    首先由key值通过hash(key)获取hash值h,再通过 h&(length-1)得到所在数组位置。一般对于哈希表的散列常用的方法有直接定址法,除留余数法等,既要便于计算,又能减少冲突。h&(length-1)相当于对数组长度取余,这样效率比较高。
    1. //这段代码保证HashMap的容量总是2的n次方
    2. static final int tableSizeFor(int cap) {
    3. int n = cap - 1;
    4. n |= n >>> 1;
    5. n |= n >>> 2;
    6. n |= n >>> 4;
    7. n |= n >>> 8;
    8. n |= n >>> 16;
    9. return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    10. }
    这段代码通过移位操作保证了哈希表的容量一直是2的整数倍。可以从源码看出,在HashMap的构造函数中,都直接或间接的调用了tableSizeFor函数。下面分析原因:length为2的整数幂保证了length-1最后一位(当然是二进制表示)为1,从而保证了取索引操作 h&(length-1)的最后一位同时有为0和为1的可能性,保证了散列的均匀性。反过来讲,当length为奇数时,length-1最后一位为0,这样与h按位与的最后一位肯定为0,即索引位置肯定是偶数,这样数组的奇数位置全部没有放置元素,浪费了大量空间。
  198. Put操作:先判断存储数组是否为空,如果为空则调用resize方法扩容。接着通过hash值找到数组中的位置如果为空则直接存入。如果不为空先判断第一个结点与插入结点是否相同,如果相同则表示找到了插入位置,如果不同则先判断该结点是红黑树结点还是链表结点再分开进行处理冲突,最终找到应该插入的结点E。最后更新hash值和key值均相同的结点E的value值。最后判断实际容量是都超过了临界值,如果是则扩容。
    1. public V put(K key, V value) {
    2. return putVal(hash(key), key, value, false, true);
    3. }
    4. final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
    5. boolean evict) {
    6. Node<k,v>[] tab; Node<k,v> p; int n, i;
    7. //如果tab为空或长度为0,则分配内存resize()
    8. if ((tab = table) == null || (n = tab.length) == 0)
    9. n = (tab = resize()).length;
    10. //(n - 1) & hash找到put位置,如果为空,则直接put
    11. if ((p = tab[i = (n - 1) & hash]) == null)
    12. tab[i] = newNode(hash, key, value, null);
    13. else {
    14. Node<k,v> e; K k;
    15. //第一节节点hash值同,且key值与插入key相同
    16. if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))
    17. e = p;
    18. else if (p instanceof TreeNode)//属于红黑树处理冲突
    19. e = ((TreeNode<k,v>)p).putTreeVal(this, tab, hash, key, value);
    20. else {
    21. //链表处理冲突
    22. for (int binCount = 0; ; ++binCount) {
    23. //p第一次指向表头,以后依次后移
    24. if ((e = p.next) == null) {
    25. //e为空,表示已到表尾也没有找到key值相同节点,则新建节点
    26. p.next = newNode(hash, key, value, null);
    27. //新增节点后如果节点个数到达阈值,则将链表转换为红黑树
    28. if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
    29. treeifyBin(tab, hash);
    30. break;
    31. }
    32. //容许null==null
    33. if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))
    34. break;
    35. p = e;//更新p指向下一个节点
    36. }
    37. }
    38. //更新hash值和key值均相同的节点Value值
    39. if (e != null) { // existing mapping for key
    40. V oldValue = e.value;
    41. if (!onlyIfAbsent || oldValue == null)
    42. e.value = value;
    43. afterNodeAccess(e); //该方法在LinkedHashMap被调用
    44. return oldValue;
    45. }
    46. }
    47. ++modCount;
    48. if (++size > threshold)
    49. resize();
    50. afterNodeInsertion(evict);
    51. return null;
    52. }
  199. Get操作:通过hash值找到数组中的位置,判断第一个结点是否就是要找的结点,如果不是则判断该结点是红黑树结点还是链表结点,分开进行查找。找到则返回该结点,如果没找到则返回null。
    1. public V get(Object key) {
    2. Node<k,v> e;
    3. return (e = getNode(hash(key), key)) == null ? null : e.value;
    4. }
    5. final Node<k,v> getNode(int hash, Object key) {
    6. Node<k,v>[] tab; Node<k,v> first, e; int n; K k;
    7. //hash & (length-1)得到对象的保存位
    8. if ((tab = table) != null && (n = tab.length) > 0 &&
    9. (first = tab[(n - 1) & hash]) != null) {
    10. if (first.hash == hash && // always check first node
    11. ((k = first.key) == key || (key != null && key.equals(k))))
    12. return first;
    13. if ((e = first.next) != null) {
    14. //如果第一个节点是TreeNode,说明采用的是数组+红黑树结构处理冲突
    15. //遍历红黑树,得到节点值
    16. if (first instanceof TreeNode)
    17. return ((TreeNode<k,v>)first).getTreeNode(hash, key);
    18. //链表结构处理
    19. do {
    20. if (e.hash == hash &&
    21. ((k = e.key) == key || (key != null && key.equals(k))))
    22. return e;
    23. } while ((e = e.next) != null);
    24. }
    25. }
    26. return null;
    27. }
  200. ArrayList分析
  201. ArrayList底层的数据结构就是数组,数组元素类型为Object类型,即可以存放所有类型数据。我们对ArrayList类的实例的所有操作底层都是基于数组的。
  202. ArrayList继承AbstractList抽象父类,实现了List接口(规定了List的操作规范)、RandomAccess(可随机访问)、Cloneable(可拷贝)、Serializable(可序列化)。
    1. public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  203. 类的属性中核心的属性为elementData,类型为Object[],用于存放实际元素,并且被标记为transient,也就意味着在序列化的时候,此字段是不会被序列化的。
    1. public class ArrayList<E> extends AbstractList<E>
    2. implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    3. {
    4. // 版本号
    5. private static final long serialVersionUID = 8683452581122892189L;
    6. // 缺省容量为10
    7. private static final int DEFAULT_CAPACITY = 10;
    8. // 空对象数组,当传入大小为0时,运用此数组
    9. private static final Object[] EMPTY_ELEMENTDATA = {};
    10. // 缺省空对象数组,当没有传入大小时,运用此数组
    11. private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    12. // 元素数组,表示该数组不能被序列化,该数组是实际用来存储的
    13. transient Object[] elementData;
    14. // 实际元素大小,默认为0
    15. private int size;
    16. // 最大数组容量
    17. private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    18. }
    1. public ArrayList() {
    2. // 无参构造函数,设置元素数组为空
    3. this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    4. }
    5. public ArrayList(int initialCapacity) {
    6. if (initialCapacity > 0) {
    7. this.elementData = new Object[initialCapacity];
    8. } else if (initialCapacity == 0) {
    9. this.elementData = EMPTY_ELEMENTDATA;
    10. } else {
    11. throw new IllegalArgumentException("Illegal Capacity: "+
    12. initialCapacity);
    13. }
    14. }
  204. add函数:先回判断数组的大小是否合适,如果发现需要的下标值超过了数组的范围则会进行扩容(如果数组为空,则会初始化为默认大小10),一般来说新容量是旧容量的1.5倍(oldCapacity >> 1)),特殊情况下(新扩展数组大小达到了最大值)则只取最大值。
    1. public boolean add(E e) { // 添加元素
    2. ensureCapacityInternal(size + 1); // 判断数组是否需要扩容
    3. elementData[size++] = e;
    4. return true;
    5. }
    6. private void ensureCapacityInternal(int minCapacity) {
    7. if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 判断元素数组是否为空数组
    8. minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // 取较大值
    9. }
    10. ensureExplicitCapacity(minCapacity);
    11. }
    12. private void ensureExplicitCapacity(int minCapacity) {
    13. // 结构性修改加1
    14. modCount++;
    15. if (minCapacity - elementData.length > 0)
    16. grow(minCapacity);
    17. }
    18. private void grow(int minCapacity) {
    19. int oldCapacity = elementData.length; // 旧容量
    20. int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量为旧容量的1.5倍
    21. if (newCapacity - minCapacity < 0) // 新容量小于参数指定容量,修改新容量
    22. newCapacity = minCapacity;
    23. if (newCapacity - MAX_ARRAY_SIZE > 0) // 新容量大于最大容量
    24. newCapacity = hugeCapacity(minCapacity); // 指定新容量
    25. // 拷贝扩容,扩容的方式就是直接复制到一个新数组中
    26. elementData = Arrays.copyOf(elementData, newCapacity);
    27. }

    28. private static int hugeCapacity(int minCapacity) {         if (minCapacity < 0) // overflow             throw new OutOfMemoryError();         return (minCapacity > MAX_ARRAY_SIZE) ?             Integer.MAX_VALUE :             MAX_ARRAY_SIZE; }
  205. 如果要指定位置进行插入,先检查索引是否合法再将index之后的值依次往后赋值,将index位置空出。
    1. public void add(int index, E element) {
    2. //检查索引是否合法,如果不合法抛出异常
    3. rangeCheckForAdd(index);
    4. ensureCapacityInternal(size + 1); // Increments modCount!!
    5. System.arraycopy(elementData, index, elementData, index + 1,
    6. size - index);
    7. elementData[index] = element;
    8. size++;
    9. }
  206. set函数
    1. public E set(int index, E element) {
    2. // 检验索引是否合法,如果不合法抛出异常
    3. rangeCheck(index);
    4. // 旧值
    5. E oldValue = elementData(index);
    6. // 赋新值
    7. elementData[index] = element;
    8. // 返回旧值
    9. return oldValue;
    10. }
  207. get函数:先判断索引是否合法,如果合法则通过数组取出。
    1. public E get(int index) {
    2. // 检验索引是否合法
    3. rangeCheck(index);
    4. return elementData(index);
    5. }
  208. remove函数:将index之后的数组依次往前复制,再返回旧值。
    1. public E remove(int index) {
    2. // 检查索引是否合法
    3. rangeCheck(index);
    4. modCount++;
    5. E oldValue = elementData(index);
    6. // 需要移动的元素的个数
    7. int numMoved = size - index - 1;
    8. if (numMoved > 0)
    9. System.arraycopy(elementData, index+1, elementData, index,
    10. numMoved);
    11. // 赋值为空,有利于进行GC
    12. elementData[--size] = null;
    13. // 返回旧值
    14. return oldValue;
    15. }
    • LinkedList分析
    1. LinkedList底层使用的双向链表结构,有一个头结点和一个尾结点,双向链表意味着我们可以从头开始正向遍历,或者是从尾开始逆向遍历,并且可以针对头部和尾部进行相应的操作。
    2. LinkedList的类继承结构很有意思,我们着重要看是Deque接口,Deque接口表示是一个双端队列,那么也意味着LinkedList是双端队列的一种实现(在尾结点插入,在头结点删除,和队列一样),所以,基于双端队列的操作在LinkedList中全部有效。
      1. public class LinkedList<E>
      2. extends AbstractSequentialList<E>
      3. implements List<E>, Deque<E>, Cloneable, java.io.Serializable
    3. 静态内部类Node就是实际的结点,用于存放实际元素的地方。
      1. private static class Node<E> {
      2. E item; // 数据域
      3. Node<E> next; // 后继
      4. Node<E> prev; // 前驱
      5. // 构造函数,赋值前驱后继
      6. Node(Node<E> prev, E element, Node<E> next) {
      7. this.item = element;
      8. this.next = next;
      9. this.prev = prev;
      10. }
      11. }
    4. LinkedList的属性非常简单,一个头结点、一个尾结点、一个表示链表中实际元素个数的变量。注意,头结点、尾结点都有transient关键字修饰,这也意味着在序列化时该域是不会序列化的。
    5. add函数:将元素添加到链表尾部。
      1. public boolean add(E e) {
      2. // 添加到末尾
      3. linkLast(e);
      4. return true;
      5. }
      6. void linkLast(E e) {
      7. // 保存尾结点,l为final类型,不可更改
      8. final Node<E> l = last;
      9. // 新生成结点的前驱为l,后继为null
      10. final Node<E> newNode = new Node<>(l, e, null);
      11. // 重新赋值尾结点
      12. last = newNode;
      13. if (l == null) // 尾结点为空
      14. first = newNode; // 赋值头结点
      15. else // 尾结点不为空
      16. l.next = newNode; // 尾结点的后继为新生成的结点
      17. // 大小加1
      18. size++;
      19. // 结构性修改加1
      20. modCount++;
      21. }

    6. addAll(int index,Collection c)函数:参数中的index表示在索引下标为index的结点(实际上是第index + 1个结点)的前面插入。在addAll函数中,addAll函数中还会调用到node函数,get函数也会调用到node函数,此函数是根据索引下标找到该结点并返回。
      1. public boolean addAll(int index, Collection<? extends E> c) {
      2. // 检查插入的的位置是否合法
      3. checkPositionIndex(index);
      4. // 将集合转化为数组
      5. Object[] a = c.toArray();
      6. // 保存集合大小
      7. int numNew = a.length;
      8. if (numNew == 0) // 集合为空,直接返回
      9. return false;
      10. Node<E> pred, succ; // 前驱,后继
      11. if (index == size) { // 如果插入位置为链表末尾,则后继为null,前驱为尾结点
      12. succ = null;
      13. pred = last;
      14. } else { // 插入位置为其他某个位置
      15. succ = node(index); // 寻找到该结点
      16. pred = succ.prev; // 保存该结点的前驱
      17. }
      18. for (Object o : a) { // 遍历数组
      19. @SuppressWarnings("unchecked") E e = (E) o; // 向下转型
      20. // 生成新结点
      21. Node<E> newNode = new Node<>(pred, e, null);
      22. if (pred == null) //表示在第一个元素之前插入(索引为0的结点)
      23. first = newNode;
      24. else
      25. pred.next = newNode;
      26. pred = newNode;
      27. }
      28. if (succ == null) { // 表示在最后一个元素之后插入
      29. last = pred;
      30. } else {
      31. pred.next = succ;
      32. succ.prev = pred;
      33. }
      34. // 修改实际元素个数
      35. size += numNew;
      36. // 结构性修改加1
      37. modCount++;
      38. return true;
      39. }
      1. Node<E> node(int index) {
      2. // 判断插入的位置在链表前半段或者是后半段
      3. if (index < (size >> 1)) { // 插入位置在前半段
      4. Node<E> x = first;
      5. for (int i = 0; i < index; i++) // 从头结点开始正向遍历
      6. x = x.next;
      7. return x; // 返回该结点
      8. } else { // 插入位置在后半段
      9. Node<E> x = last;
      10. for (int i = size - 1; i > index; i--) // 从尾结点开始反向遍历
      11. x = x.prev;
      12. return x; // 返回该结点
      13. }
      14. }
      在根据索引查找结点时,会有一个小优化,结点在前半段则从头开始遍历,在后半段则从尾开始遍历,这样就保证了只需要遍历最多一半结点就可以找到指定索引的结点。
    7. unlink函数:在调用remove移除结点时,会调用该方法。
      1. E unlink(Node<E> x) {
      2. // 保存结点的元素
      3. final E element = x.item;
      4. // 保存x的后继
      5. final Node<E> next = x.next;
      6. // 保存x的前驱
      7. final Node<E> prev = x.prev;
      8. if (prev == null) { // 前驱为空,表示删除的结点为头结点
      9. first = next; // 重新赋值头结点
      10. } else { // 删除的结点不为头结点
      11. prev.next = next; // 赋值前驱结点的后继
      12. x.prev = null; // 结点的前驱为空,切断结点的前驱指针
      13. }
      14. if (next == null) { // 后继为空,表示删除的结点为尾结点
      15. last = prev; // 重新赋值尾结点
      16. } else { // 删除的结点不为尾结点
      17. next.prev = prev; // 赋值后继结点的前驱
      18. x.next = null; // 结点的后继为空,切断结点的后继指针
      19. }
      20. x.item = null; // 结点元素赋值为空
      21. // 减少元素实际个数
      22. size--;
      23. // 结构性修改加1
      24. modCount++;
      25. // 返回结点的旧元素
      26. return element;
      27. }
    • TreeMap
    1. HashMap和LinkedHashMap都是用哈希值去寻找我们想要的键值对,优点是有O(1)的查找速度。但是如果对查找性能要求不高,反而对有序性要求比较高(key值有序)的应用场景就可以使用TreeMap。
    2. TreeMap继承了NavigableMap,而NavigableMap继承自SortedMap,NavigableMap有几种方法,分别是不同的比较要求:floorKey是小于等于,ceilingKey是大于等于,lowerKey是小于,higherKey是大于。
      1. public class TreeMap<K,V>
      2. extends AbstractMap<K,V>
      3. implements NavigableMap<K,V>, Cloneable, java.io.Serializable
      4. {
      5. private final Comparator<? super K> comparator;
      6. //红黑树的结点,root为根节点,接下来所有对红黑树的操作都通过该root结点
      7. private transient Entry<K,V> root = null;
      8. private transient int size = 0;
      9. private transient int modCount = 0;
      10. public TreeMap() {
      11. comparator = null;
      12. }
      13. public TreeMap(Comparator<? super K> comparator) {
      14. this.comparator = comparator;
      15. }
      16. //后面省略
      17. }
      1. static final class Entry<K,V> implements Map.Entry<K,V> {
      2. K key;
      3. V value;
      4. Entry<K,V> left = null;
      5. Entry<K,V> right = null;
      6. Entry<K,V> parent;
      7. boolean color = BLACK; //红黑树默认颜色为黑色
      8. //后续省略
      9. }
    3. 红黑树的第4条性质保证了这些路径中的任意一条都不存在连续的红节点,而红黑树的第5条性质又保证了所有的这些路径上的黑色节点的数目相同。因而最短路径必定是只包含黑色节点的路径,而最长路径为红黑节点互相交叉的路径,由于所有的路径的起点必须是黑色的,而红色节点又不能连续存在,因而最长路径的长度为全为黑色节点路径长度的二倍。
    4. put方法:                                                                                            
      1. public V put(K key, V value) {
      2. Entry<K,V> t = root;
      3. if (t == null) {
      4. compare(key, key); // type (and possibly null) check
      5. root = new Entry<>(key, value, null);
      6. size = 1;
      7. modCount++;
      8. return null;
      9. }
      10. int cmp;
      11. Entry<K,V> parent;
      12. // split comparator and comparable paths
      13. //如果有比较器则使用该比较器进行比较,找到插入位置的父节点
      14. //如果没有比较器则使用key值默认的比较器
      15. Comparator<? super K> cpr = comparator;
      16. if (cpr != null) {
      17. do {
      18. parent = t;
      19. cmp = cpr.compare(key, t.key);
      20. if (cmp < 0)
      21. t = t.left;
      22. else if (cmp > 0)
      23. t = t.right;
      24. else
      25. return t.setValue(value);
      26. } while (t != null);
      27. }
      28. else {
      29. if (key == null)
      30. throw new NullPointerException();
      31. Comparable<? super K> k = (Comparable<? super K>) key;
      32. do {
      33. parent = t;
      34. cmp = k.compareTo(t.key);
      35. if (cmp < 0)
      36. t = t.left;
      37. else if (cmp > 0)
      38. t = t.right;
      39. else
      40. return t.setValue(value);
      41. } while (t != null);
      42. }
      43. Entry<K,V> e = new Entry<>(key, value, parent);
      44. if (cmp < 0)
      45. parent.left = e;
      46. else
      47. parent.right = e;
      48. //修复红黑树
      49. fixAfterInsertion(e);
      50. size++;
      51. modCount++;
      52. return null;
      53. }
    5. get方法:就是二叉查找树的查找方法,通过比较器和每个结点进行比较,判断是向左移动还是向右移动。
      1. final Entry<K,V> getEntry(Object key) {
      2. // Offload comparator-based version for sake of performance
      3. if (comparator != null)
      4. return getEntryUsingComparator(key);
      5. if (key == null)
      6. throw new NullPointerException();
      7. Comparable<? super K> k = (Comparable<? super K>) key;
      8. Entry<K,V> p = root;
      9. while (p != null) {
      10. int cmp = k.compareTo(p.key);
      11. if (cmp < 0)
      12. p = p.left;
      13. else if (cmp > 0)
      14. p = p.right;
      15. else
      16. return p;
      17. }
      18. return null;
      19. }
    • LinkedHashMap
    1. 当希望按照插入的顺序输出,又可以使用Hash来快速查找对象,可以使用LinkedHashMap,它是一个按顺序存放的双向链表保证key的顺序。
    2. LinkedHashMap继承了HashMap,同时继承HashMap的Node对象,生成自己独特的存储数据结构Entry<K,V>。
      1. public class LinkedHashMap<K,V> extends HashMap<K,V>
      2. implements Map<K,V>
      1. static class Entry<K,V> extends HashMap.Node<K,V> {
      2. Entry<K,V> before, after;
      3. Entry(int hash, K key, V value, Node<K,V> next) {
      4. super(hash, key, value, next);
      5. }
      6. }
      然后又三个自己的独特成员:
      1. //链表头结点
      2. transient LinkedHashMap.Entry<K,V> head;
      3. //链表尾结点
      4. transient LinkedHashMap.Entry<K,V> tail;
      5. // 简单说就是这个用来控制元素的顺序,
      6. // true: 是访问的顺序,也就是谁最先访问,就排在第一位
      7. // false:存放顺序,就是你put 元素的时候的顺序
      8. final boolean accessOrder;
    3. 初始化:LinkedHashMap初始化时,可以选择是否初始化accessOrder。如果不初始化,则默认是false,此时保持放入元素先后的顺序不变化,即按照你插入的顺序排列和输出。如果选择初始化为true,则按照访问顺序排列。
      1. public LinkedHashMap(int initialCapacity, float loadFactor) {
      2. super(initialCapacity, loadFactor);
      3. accessOrder = false;
      4. }
      5. public LinkedHashMap(int initialCapacity) {
      6. super(initialCapacity);
      7. accessOrder = false;
      8. }
      9. public LinkedHashMap() {
      10. super();
      11. accessOrder = false;
      12. }
      13. public LinkedHashMap(Map<? extends K, ? extends V> m) {
      14. super(m);
      15. accessOrder = false;
      16. }
      17. public LinkedHashMap(int initialCapacity,
      18. float loadFactor,
      19. boolean accessOrder) {
      20. super(initialCapacity, loadFactor);
      21. this.accessOrder = accessOrder;
      22. }
    4. LinkedHashMap没有重写put函数,也就是说它直接使用的是HashMap的put函数。在HashMap中当找到了存放结点并且这个结点已经存在时,有这样一段代码。
      1. if (e != null) { // existing mapping for key
      2. V oldValue = e.value;
      3. if (!onlyIfAbsent || oldValue == null)
      4.     e.value = value;
      5. afterNodeAccess(e); //该方法在LinkedHashMap被调用
      6. return oldValue;
      7. }
      afterNodeAccess这个方法却在LinkedHashMap中实现:
      1. void afterNodeAccess(Node<K,V> e) { // move node to last
      2. LinkedHashMap.Entry<K,V> last;
      3. if (accessOrder && (last = tail) != e) {
      4. LinkedHashMap.Entry<K,V> p =
      5. (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
      6. p.after = null;
      7. if (b == null)
      8. head = a;
      9. else
      10. b.after = a;
      11. if (a != null)
      12. a.before = b;
      13. else
      14. last = b;
      15. if (last == null)
      16. head = p;
      17. else {
      18. p.before = last;
      19. last.after = p;
      20. }
      21. tail = p;
      22. ++modCount;
      23. }
      24. }

    5. get函数,直接取出结点,如果accessOrder为真,则调用afterNodeAccess方法重新进行排序:
      1. public V get(Object key) {
      2. Node<K,V> e;
      3. if ((e = getNode(hash(key), key)) == null)
      4. return null;
      5. if (accessOrder)
      6. afterNodeAccess(e);
      7. return e.value;
      8. }
    • HashSet
    1. HashSet底层是基于HashMap 或者 LinkedHashMap实现的,但是HashSet中由于只包含键,不包含值,由于在底层具体实现时,使用的HashMap或者是LinkedHashMap(可以指定构造函数来确定使用哪种结构),我们知道HashMap是键值对存储,所以为了适应HashMap存储,HashSet增加了一个PRESENT类域(类所有),所有的键都有同一个值(PRESENT)。
      1. public class HashSet<E>
      2. extends AbstractSet<E>
      3. implements Set<E>, Cloneable, java.io.Serializable
      1. private transient HashMap<E,Object> map;
      2. private static final Object PRESENT = new Object();
    2. 初始化,通过构造函数来判断到底使用HashMap还是LinkedHashMap。
      1.     public HashSet() {
      2. map = new HashMap<>();
      3. }
      4. public HashSet(Collection<? extends E> c) {
      5. map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
      6. addAll(c);
      7. }
      8. public HashSet(int initialCapacity, float loadFactor) {
      9. map = new HashMap<>(initialCapacity, loadFactor);
      10. }
      11. public HashSet(int initialCapacity) {
      12. map = new HashMap<>(initialCapacity);
      13. }
      14. HashSet(int initialCapacity, float loadFactor, boolean dummy) {
      15. map = new LinkedHashMap<>(initialCapacity, loadFactor);
      16. }
    3. add函数:
      1. public boolean add(E e) {
      2. return map.put(e, PRESENT)==null;
      3. }
    4. HashSet没有get函数,只能通过迭代器获取存入的值。
      1. public Iterator<E> iterator() {
      2. return map.keySet().iterator();
      3. }
    1. ConcurrentHashMap是conccurrent家族中的一个类,由于它可以高效地支持并发操作,以及被广泛使用。与同是线程安全的老大哥HashTable相比,它已经更胜一筹,因此它的锁更加细化,而不是像HashTable一样为几乎每个方法都添加了synchronized锁,这样的锁无疑会影响到性能。
    2. 在源码JDK8版本中,它摒弃了Segment(锁段)的概念,而是启用了一种全新的方式实现,利用CAS算法。它沿用了与它同时期的HashMap版本的思想,底层依然由“数组”+链表+红黑树的方式思想,但是为了做到并发,又增加了很多辅助的类,例如TreeBin,Traverser等对象内部类。
    3. sizeCtl是一个控制标识符,因为它是一个控制标识符,在不同的地方有不同的用途,而且它的取值不同,也代表不同的含义。实际容量>=sizeCtl,则扩容。                                                                                               
    4. Node与HashMap的定义很相似,但是它的value和next属性设置了volatile同步锁,它不允许调用setValue方法直接改变Node的value域,它增加了find方法辅助map.get()方法。
      1. static class Node<K,V> implements Map.Entry<K,V> {
      2. final int hash;
      3. final K key;
      4. volatile V val;
      5. volatile Node<K,V> next;
      6. Node(int hash, K key, V val, Node<K,V> next) {
      7. this.hash = hash;
      8. this.key = key;
      9. this.val = val;
      10. this.next = next;
      11. }
      12. public final K getKey() { return key; }
      13. public final V getValue() { return val; }
      14. public final int hashCode() { return key.hashCode() ^ val.hashCode(); }
      15. public final String toString(){ return key + "=" + val; }
      16. public final V setValue(V value) {
      17. throw new UnsupportedOperationException();
      18. }
      19. public final boolean equals(Object o) {
      20. Object k, v, u; Map.Entry<?,?> e;
      21. return ((o instanceof Map.Entry) &&
      22. (k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
      23. (v = e.getValue()) != null &&
      24. (k == key || k.equals(key)) &&
      25. (v == (u = val) || v.equals(u)));
      26. }
      27. /**
      28. * Virtualized support for map.get(); overridden in subclasses.
      29. */
      30. Node<K,V> find(int h, Object k) {
      31. Node<K,V> e = this;
      32. if (k != null) {
      33. do {
      34. K ek;
      35. if (e.hash == h &&
      36. ((ek = e.key) == k || (ek != null && k.equals(ek))))
      37. return e;
      38. } while ((e = e.next) != null);
      39. }
      40. return null;
      41. }
      42. }
    5. 当链表长度过长的时候,会转换为TreeNode。但是与HashMap不相同的是,它并不是直接转换为红黑树,而是把这些结点包装成TreeNode放在TreeBin对象中,由TreeBin完成对红黑树的包装。而且TreeNode在ConcurrentHashMap集成自Node类,而并非HashMap中的集成自LinkedHashMap.Entry<K,V>类,也就是说TreeNode带有next指针,这样做的目的是方便基于TreeBin的访问。
      1. static final class TreeNode<K,V> extends Node<K,V> {
      2. TreeNode<K,V> parent; // red-black tree links
      3. TreeNode<K,V> left;
      4. TreeNode<K,V> right;
      5. TreeNode<K,V> prev; // needed to unlink next upon deletion
      6. boolean red;
      7. TreeNode(int hash, K key, V val, Node<K,V> next,
      8. TreeNode<K,V> parent) {
      9. super(hash, key, val, next);
      10. this.parent = parent;
      11. }
      12. Node<K,V> find(int h, Object k) {
      13. return findTreeNode(h, k, null);
      14. }
      15. /**
      16. * Returns the TreeNode (or null if not found) for the given key
      17. * starting at given root.
      18. */
      19. final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) {
      20. if (k != null) {
      21. TreeNode<K,V> p = this;
      22. do {
      23. int ph, dir; K pk; TreeNode<K,V> q;
      24. TreeNode<K,V> pl = p.left, pr = p.right;
      25. if ((ph = p.hash) > h)
      26. p = pl;
      27. else if (ph < h)
      28. p = pr;
      29. else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
      30. return p;
      31. else if (pl == null)
      32. p = pr;
      33. else if (pr == null)
      34. p = pl;
      35. else if ((kc != null ||
      36. (kc = comparableClassFor(k)) != null) &&
      37. (dir = compareComparables(kc, k, pk)) != 0)
      38. p = (dir < 0) ? pl : pr;
      39. else if ((q = pr.findTreeNode(h, k, kc)) != null)
      40. return q;
      41. else
      42. p = pl;
      43. } while (p != null);
      44. }
      45. return null;
      46. }
      47. }
    6. TreeBin:这个类并不负责包装用户的key、value信息,而是包装的很多TreeNode节点。它代替了TreeNode的根节点,也就是说在实际的ConcurrentHashMap“数组”中,存放的是TreeBin对象,而不是TreeNode对象,这是与HashMap的区别。另外这个类还带有了读写锁。可以看到在构造TreeBin节点时,仅仅指定了它的hash值为TREEBIN常量,这也就是个标识位。
      1. static final class TreeBin<K,V> extends Node<K,V> {
      2. TreeNode<K,V> root;
      3. volatile TreeNode<K,V> first;
      4. volatile Thread waiter;
      5. volatile int lockState;
      6. // values for lockState
      7. static final int WRITER = 1; // set while holding write lock
      8. static final int WAITER = 2; // set when waiting for write lock
      9. static final int READER = 4; // increment value for setting read lock

    7. ForwardingNode:一个用于连接两个table的节点类。它包含一个nextTable指针,用于指向下一张表。而且这个节点的key value next指针全部为null,它的hash值为-1。这里面定义的find的方法是从nextTable里进行查询节点,而不是以自身为头节点进行查找。
    8. CAS:在ConcurrentHashMap中,随处可以看到U, 大量使用了U.compareAndSwapXXX的方法,这个方法是利用一个CAS算法实现无锁化的修改值的操作,他可以大大降低锁代理的性能消耗。这个算法的基本思想就是不断地去比较当前内存中的变量值与你指定的一个变量值是否相等,如果相等,则接受你指定的修改的值,否则拒绝你的操作。因为当前线程中的值已经不是最新的值,你的修改很可能会覆盖掉其他线程修改的结果。
    9. Unsafe静态块:unsafe代码块控制了一些属性的修改工作,比如最常用的SIZECTL 。在这一版本的concurrentHashMap中,大量应用来的CAS方法进行变量、属性的修改工作。利用CAS进行无锁操作,可以大大提高性能。                                                                                                                                ConcurrentHashMap定义了三个原子操作,用于对指定位置的节点进行操作。正是这些原子操作保证了ConcurrentHashMap的线程安全。

    十四、泛型

    • 泛型类型
    1. 一个泛型类的定义格式如下:这里引入了类型变量T,一个类型变量可以是用户指定的任何非基本数据类型。类型变量T不能再静态方法中使用。
      1. class 类名<T1,T2.......Tn>{...........}
    2. 类型形式参数的名字用单个、大写字母表示。一下是常用的类型形式参数名:
      1. E-元素(常用语集合框架)
      2. K-键值
      3. N-数字
      4. T-类型
      5. V-值
      6. S\U\V-第二三四类型
    3. 钻石运算符:JAVA SE7之后的版本中,调用泛型类的构造函数时,只要编译器能够通过上下文确定或推断出类型实际参数,就可以用类型实际参数的空集<>代替类型实际参数。
      1. ClassName<Integer> cn=new ClassName<>();
    4. 泛型类可以有多个类型形式参数:
      1. class className<K,V> implements Pair<K,V> //一旦类继承或实现了泛型类或接口,则该类必须也为泛型类,否则编译错误
    5. 原生类型:原生类型是不带有任何类型参数的泛型类或接口的名字。
      1. ClassName<Integer> cn=new ClassName<>();
      2. ClassName cn=new ClassName();
      允许把一个参数化类型传递给一个对应的原生类型,但是把原生类型赋值给参数化类型则会警告。
    • 泛型方法
    1. 泛型方法是一类引入自己类型形式参数的方法。这类似于声明一个泛型类型,只是类型形式参数的辖域限制在声明该参数的方法中。该方法允许静态和非静态的泛型方法。
      1. public class text<T> {
      2. public static void main(String[] args) {
      3. NumberOf1(2, 3);
      4. }
      5. public static <K,V> void NumberOf1(K k,V v) {
      6. System.out.println(k);
      7. System.out.println(v);
      8. }
      9. }
      1. public class text<T> {
      2. T t;
      3. public static void main(String[] args) throws InterruptedException {
      4. text<Integer> tt=new text<Integer>();
      5. tt.t=1;
      6. System.out.println(tt.t);
      7. tt.NumberOf1("123456"); //正确
      8. tt.NumberOf2("123456") //错误,因为类中的T为Integer类型
      9. }
      10. //以下这两个函数是完全不同的,NumberOf1中的T是方法内部自己的类型参数和类中的T无关
      11. //NumberOf2中的T是类中的T,必须被类中T的类型约束
      12. public <T> void NumberOf1(T t) {
      13. System.out.println(t);
      14. }
      15.     public void NumberOf2(T t) {
      16. System.out.println(t);
      17. }
      18. }

    • 受限类型形式参数
    1. 有时需要约束可以作为“类型实际参数”使用的类型,这就是受限类型形式参数所要发挥的作用。
      1. class ClassName<T extends Number> //T必须是Number的直接或间接子类
    2. 多重限制:有多重限制的类型变量,是所有列在限制中的类型的子类型。如果一个类是限制之一,则必须一开始就要指定这个类,并且有且只有一个类。且T必须全部继承或实现ABC。
      1. <T extends A & B & C>
    • 泛型、继承和子类型
    1. Integer是Number的子类型,所以Integer类型可以赋值给Number类型。但是List<Integer>并不是List<Number>的子类型,所以List<Integer>是不能赋值给List<Number>类型的。也就是说声明中的<>与初始化中的<>必须完全一致,否则编译错误。
    2. ArrayList<E> implements List<E>,List<E> extends Collection<E>,所以ArrayList是List的子类型,List是Collection的子类型。
      1. //这样是正确的,并且是提倡的写法
      2. Collection<Number> c=new ArrayList<Number>();
      3. //这样是错误的,<>中的类型不同,所以两者并不是父子类关系
      4. Collection<Number> c=new ArrayList<Integer>();
    3. 泛型类和非泛型类中构造函数都可以是泛型的:
      1. class Method<X>{
      2. public <T> H(T t) {
      3. }
      4. }
      5. //创建该类型
      6. Method<Integer> m=new Method<>("123");
      7. //由此可见,X的类型是Integer,T的类型是String
    • 通配符
    1. ?表示未知类型,被称为通配符。通配符可以在各种情况下使用,可以作为形式参数、域或局部变量的类型,也可以作为返回值类型。但是,通配符不能用于泛型方法调用、泛型类实例创建或者超类的类型实际参数。当使用通配符时,对泛型类的操作不能依赖于类型形式参数。例如:List<?>,不能使用add方法,因为该方法依赖了类型形式参数。
    2. 上界通配符:可以放宽对变量的限制。
      1. //可以匹配Number类或其子类的列表ArrayList,但是该列表不能做插入或取出操作,只能遍历
      2. public void NumberOf1(ArrayList<? extends Number> a) {
      3. }
    3. 无界通配符:当一个方法可以用Object类提供的功能来实现时,无界通配符是适用的;若代码使用了泛型类中的方法,而这些方法又是不依赖于类型形式参数(例如list.size()等),那么无界通配符也是适用的。List<Object>和List<?>是不一样的,我们可以向List<Object>中插入一个Object或者Object的任意子类型,但是只能向List<?>中插入null。
    4. 下界通配符:下界通配符将未知类型限制为某个指定类型或者该类型的超类型。
      1. //这对Integer的父类,例如Number、Object都有效
      2. public static void NumberOf1(Collection<? super Integer> a) {
      3. a.add(1);
      4. }
    5. 通配符和子类型:                                                                                                                                                                  
    1. Java泛型实现原理——类型擦除
    2. 类型擦除后保留原始类型,字节码中保留的就是原始类型。无论何时定义一个泛型类型,相应的原始类型都会被自动地提供。类型变量被擦除,并使用其限定类型(如果无限定类型用Object)替换。限定类型:<T extends Number>,Number就是其限定类型,如果有则用Number替换。
      1. class Pair<T> {
      2. private T value;
      3. public T getValue() {
      4. return value;
      5. }
      6. public void setValue(T value) {
      7. this.value = value;
      8. }
      9. }
      1. class Pair {
      2. private Object value;
      3. public Object getValue() {
      4. return value;
      5. }
      6. public void setValue(Object value) {
      7. this.value = value;
      8. }
      9. }


    十五、反射与注解

    • 反射为Java程序在运行时提供了动态的能力,而注解允许通过一定的方式编写描述类的元数据,这些元数据可以为编译器提供信息,也可以进入字节码文件在运行时使用。
    • 反射——Class的使用
    1. Class类属于java.lang包,不需要使用import语句引入特殊的包就可以使用,其对象代表一个类,携带类的相应信息,主要包括构造器、方法、成员变量等。
    2. 不可以使用Class对象的构造器来创建Class类对象,只能使用提供的静态工厂方法来获取对象。
    3. 想要获取一个Class对象不一定要使用forName方法进行加载,直接调用对象的getClass方法来获取对象所在类对应的Class对象。也可以使用类名.Class来获取类对象。
    4. 精确判断对象类型:instanceof无法精确判断对象类型,因为子类对象可以看作父类类型。而利用反射则可以判断是否是精确的类型。
      1. class Father{}
      2. class Son extends Father()
      3. Son son=new Son();
      4. son instanceof Father //返回true
      5. son.getClass()==Class.for(com.test.Father) //返回false
      6. son.getClass()==Father.class //返回false
    • 反射——Field
    1. Field类的对象代表成员变量,携带成员变量的信息。与Class类类似,一样不可以通过构造器创建Field类的类对象,对象都是通过Class类对象提供的get系列方法获得的。

    • 反射——Method
    1. Method类的对象代表一个方法,携带方法的相关信息。
    2. 不管实际对应方法的返回值为什么类型,都作为Object类型返回。若返回值为基本数据类型,则返回对应封装类的对象。
    3. obj参数之处要被调用方法所属的对象,若调用静态的方法用null值。
    4. args之处要被调用方法的参数序列,若方法没有参数则传递空数组——new Object[0],若方法有基本数据类型的参数则使用基本数据类型的封装类对象。
    • 反射——Constructor
    1. Constructor类的对象代表一个构造器,携带构造器的相关信息。

    2. Class类提供的newInstance方法只能调用对应类的无参构造器动态创建对象,若希望调用有参构造器则需要使用Constructor类的newInstance方法。
    • 反射与修饰符
    1. Class、Field、Method、Constructor类都提供了用于获取各自表示类、成员变量、方法和构造器对应修饰符的方法.
    2. public int getModifiers(); 该方法返回的是整数类型,可以用Modifier的静态常量来表示。
    • 利用反射动态创建数组对象
    1. java.lang.reflect.Array类中提供了动态创建数组的newInstance方法。

    • 注解
    1. 声明自己的注解:
      1. @interface <注解名称>{
      2. <注解属性类型> <注解属性名称> [default <默认值>];
      3. java.lang.String last();
      4. }
      5. //使用注解是可以直接使用@<注解名称>(last=“...”)
      6. //此后获取到该注解,可直接调用last方法获取用户输入的属性值
    2. 确定注解的使用目标:
      1. @Target(ElementType.<使用目标点>)

    3. 通过反射提取注解信息:
      1. <注解名称> 引用=xxx.getAnnotation(<注解名称>.class);
      2. xxx表示不同的使用目标对应反射类对象的引用。


    十六、枚举

    相关文章

      网友评论

        本文标题:纯干货!JAVA细节标注+源码分析

        本文链接:https://www.haomeiwen.com/subject/yxfbettx.html