1.JAVA运行

android keep注解 混淆 keep java_java


JDK(Java SE Development Kit),Java标准开发包,它提供了编译、运行Java程序所需的各种工具和资源,包括Java编译器、Java运行时环境,以及常用的Java类库等。

JRE( Java Runtime Environment) 、Java运行环境,用于解释执行Java的字节码文件。普通用户而只需要安装 JRE(Java Runtime Environment)来运行 Java 程序。而程序开发者必须安装JDK来编译、调试程序。

JVM(Java Virtual Mechinal),Java虚拟机,是JRE的一部分。它是整个java实现跨平台的最核心的部分,负责解释执行字节码文件,是可运行java字节码文件的虚拟计算机。所有平台的上的JVM向编译器提供相同的接口,而编译器只需要面向虚拟机,生成虚拟机能识别的代码,然后由虚拟机来解释执行。

2.JVM内存

android keep注解 混淆 keep java_java_02

2.1程序计数器

当前线程所执行字节码的行号指示器,一个线程对应一个程序计数器,不会发生OOM。

2.2栈

限定仅在表头进行插入和删除操作的线性表,即先进后出。也是线程私有的,每个线程分配一个栈空间

android keep注解 混淆 keep java_android keep注解 混淆_03


每个方法运行时都会创建一个栈帧。栈帧中存储了局部变量表、操作数栈、动态连接和方法出口等信息。每个方法从调用到运行结束的过程,就对应着一个栈帧在栈中压栈到出栈的过程

android keep注解 混淆 keep java_Java_04

2.2.1局部变量表

存储了基本数据类型(boolean、byte、char、short、int、float、long、double)的局部变量及参数,和对象的引用(String,数组,对象等)。
局部变量表所需的内存空间在编译期间完成分配,在方法运行期间不会改变局部变量表的大小。

2.2.2操作数栈

先进后出栈,通常用于进行计算操作

可能出现的异常:
StackOverflowError:栈溢出错误
如果一个线程在计算时所需要用到栈大小 > 配置允许最大的栈大小,那么Java虚拟机将抛出 StackOverflowError

OutOfMemoryError:内存不足
栈进行动态扩展时如果无法申请到足够内存,会抛出 OutOfMemoryError 异常。

2.3堆

堆是Java虚拟机所管理的内存中最大的一块存储区域。堆内存被所有线程共享。主要存放使用new关键字创建的对象。
垃圾收集器就是根据GC算法,收集堆上对象所占用的内存空间。

2.4方法区

方法区同 Java 堆一样是被所有线程共享的区间,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码。更具体的说,静态变量+常量+类信息(版本、方法、字段等)+运行时常量池存在方法区中。常量池是方法区的一部分。

2.4.1常量池

常量池避免了频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。

3.数据类型

3.1 基本数据类型

Byte
Short
Int 32位,占用空间为4字节,最大数据存储量为231-1,数据范围为-231~231-1。
Long
Float
Double
Char
Boolean

3.2 包装数据类型

byte Byte
Short Short
Int Integer
Long Long
Char Character
float Float
double Double
boolean Boolean

包装类型可以为NULL,所以可以用来修饰POJO
不过基本类型更高效,因为基本类型在栈中直接存储的具体数值,而包装类型则存储的是堆中的引用。

Integer中,数据范围在-128到127之间,直接引用常量池中的数据,超过这个范围则会new一个Integer对象,由自动拆箱的代码规定

3.3 引用数据类型

数组,类,接口
String也是引用数据类型

4.运算符

4.1算术运算符

加(+)、减(-)、乘(*)、除(/)、取模(%)

自增和自减

android keep注解 混淆 keep java_java_05

4.2关系运算符

关系运算符用来比较两个值,包括大于(>)、小于(<)、大于等于(>=)、小于等于(<=)、等于(==)和不等于(!=)6种

== 对于基本类型来说是值比较,对于引用类型来说是比较的是引用;而 equals 默认情况下是引用比较,只是很多类重写了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。

4.3逻辑运算符

逻辑与(&&)、逻辑或(||)和逻辑非(!)。
短路与,左侧为false,右侧不计算,直接返回false
短路或,左侧为true,右侧不计算,直接返回true

4.4位运算符

位运算符用来对二进制位进行操作,包括按位取反(~)、按位与(&)、按位或(|)、异或(^)、右移(>>)、左移(<<)和无符号右移(>>>)。位运算符只能对整数型和字符型数据进行操作。

4.5赋值运算符

android keep注解 混淆 keep java_Java_06

4.6条件运算符

条件运算符( ? : )也称为 “三元运算符”或“三目运算符”。

语法形式:布尔表达式 ? 表达式1 :表达式2。

运算过程:如果布尔表达式的值为 true ,则返回 表达式1的值,否则返回 表达式2 的值。

5.字符串

String和StringBuffer/StringBuilder,它们可以储存和操作字符串。其中String是只读字符串,也就意味着String引用的字符串内容是不能被改变的。而StringBuffer/StringBuilder类表示的字符串对象可以直接进行修改。StringBuilder是Java 5中引入的,它和StringBuffer的方法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方面都没有被synchronized修饰,因此它的效率也比StringBuffer要高。

String常量池:
String 是由 final 修饰的类,是不可以被继承的。

//1、

String str = new String("abcd");

//2、

String str = "abcd";

第一种使用 new 创建的对象,存放在堆中。每次调用都会创建一个新的对象。

第二种先在栈上创建一个 String 类的对象引用变量 str,然后通过符号引用去字符串常量池中找有没有 “abcd”,如果没有,则将“abcd”存放到字符串常量池中,并将栈上的 str 变量引用指向常量池中的“abcd”。如果常量池中已经有“abcd”了,则不会再常量池中创建“abcd”,而是直接将 str 引用指向常量池中的“abcd”。

6.类加载

JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。

6.1类加载的时机

java虚拟机规范虽然没有强制性约束在什么时候开始类加载过程,但是对于类的初始化,虚拟机规范则严格规定了几种情况必须立即对类进行初始化,如果类没有进行过初始化,则需要先触发其初始化。
1.使用new关键字实例化对象
2.访问类的静态变量,包括读取一个类的静态字段 和 设置一个类的静态字段
除常量,即final修饰的静态变量。编译器会将常量当做值value而不是域field来对待。
编译器会直接将常量插入到字节码中,而不是从对象中载入域的值。编译器优化,将能够计算出结果的值在编译阶段替换为常量
3.访问类的静态方法
4.反射
5.当初始化一个类时,发现其父类还未初始化,则先出发父类的初始化
6.虚拟机启动时,定义了main()方法的那个类先初始化

6.2类加载的过程

类加载的过程主要分为三个部分:

6.2.1加载

把class字节码文件从各个来源通过类加载器装载入内存中

6.2.2链接

6.2.2.1验证

主要是为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误。

包括对于文件格式的验证,比如常量中是否有不被支持的常量?文件中是否有不规范的或者附加的其他信息?

对于元数据的验证,比如该类是否继承了被final修饰的类?类中的字段,方法是否与父类冲突?是否出现了不合理的重载?

对于字节码的验证,保证程序语义的合理性,比如要保证类型转换的合理性。

对于符号引用的验证,比如校验符号引用中通过全限定名是否能够找到对应的类?校验符号引用中的访问性(private,public等)是否可被当前类访问?

6.2.2.2准备

主要是为类变量(注意,不是实例变量)分配内存,并且赋予初值。

特别需要注意,初值,不是代码中具体写的初始化的值,而是Java虚拟机根据不同变量类型的默认初始值。

比如8种基本类型的初值,默认为0;引用类型的初值则为null;常量的初值即为代码中设置的值,final static tmp = 456, 那么该阶段tmp的初值就是456

6.2.2.3解析

将常量池内的符号引用替换为直接引用的过程。

6.2.3初始化

这个阶段主要是对类变量初始化,是执行类构造器的过程。

换句话说,只对static修饰的变量或语句进行初始化。

如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。

如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。

7.面向对象

讲事物抽象成对象,通过setget设置属性
三大基本特征:封装、继承、多态

8.封装

8.1类和对象

类是一个抽象的概念 ,而对象是一个具体的东西

类中有默认的无参构造,通过调用无参构造可以new一个对象。当代码中有有参构造,默认的无参构造消失,new对象是需要输入对应参数。但也可以再重写一个无参构造,这样既可以使用有参也可以使用无参来创建对象

使用无参构造方法创建的对象状态默认值为 null(color 字符串为引用类型),如果是基本类型的话,默认值为对应基本类型的默认值,比如说 int 为 0,更详细的见下图。

android keep注解 混淆 keep java_java_07

8.2封装实现

私有化属性,使用private修饰。
通过方法来控制成员变量的操作,提高了代码的安全性
把代码用方法进行封装,提高了代码的复用性

通过get/set方法进行属性的操作属性,用public修饰

8.3权限修饰符

android keep注解 混淆 keep java_android keep注解 混淆_08

8.3this关键字

this:代表所在类的对象引用
方法被哪个对象调用,this就代表那个对象
使用this的时机
局部变量和成员变量重名

9.继承

子类可以继承父类的非 private 成员变量

通用的方法和成员变量放在父类中,以达到代码复用的目的。
特殊的方法和成员变量放在子类中。
除此之外,子类还可以重写父类的方法。这样,子类也就焕发出了新的生命力。

如果一个类在定义的时候没有使用 extends 关键字,那么它隐式地继承了 java.lang.Object 类

9.1extends关键字

class 子类 extends 父类 {}

Java中只能单继承,不可以多继承,但可以多层继承

9.2重写和重载

重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。
重载对返回类型没有特殊的要求,不能以返回类型区分重载。因为调用时不能指定类型信息,编译器不知道你要调用哪个函数

9.3super关键字

子类无法继承父类的构造方法。如果父类的构造方法是带有参数的,则必须在子类的构造器中显式地通过 super 关键字进行调用。

10.多态

引用变量究竟指向哪一个实例对象,在编译期间是不确定的,只有运行期才能确定,这样不用修改源码就可以把变量绑定到不同的类实例上,让程序拥有了多个运行状态,这就是多态

Parent p = new Child();

10.1运行特点

成员变量
编译看左边(父类),运行看左边(父类);无论如何都是访问父类的成员变量。

成员方法
编译看左边(父类),运行看右边(子类),动态绑定。

Static方法
编译看左边(父类),运行看左边(父类)。

只有非静态的成员方法,编译看左边,运行看右边。

10.2必要条件

1.要有继承
2.有方法的重写
3.父类引用指向子类对象

11.抽象

11.1Abstract关键字

用于修饰方法和类

抽象方法:不同类的方法相似,但是具体内容又不太一样,所以我们只能抽取他的声明,没有具体的方法体,没有具体方法体的方法就是抽象方法
抽象类:有抽象方法的类必须是抽象类
抽象方法只能在抽象类里面

抽象类和抽象方法必须被abstract修饰
抽象类不能创建对象(不能实例化)
抽象类中可以有非抽象的方法
抽象类和类的关系也是继承
一个类继承了抽象类要么重写所有的抽象方法,要么他自己是抽象类

Abstract不可以和final、private、static共存

12.接口

接口比抽象类更抽象

12.1implement关键字

可以实现多个接口

接口中定义的变量会在编译的时候自动加上 public static final 修饰符,即常量

12.2final关键字

修饰符,可以用于修饰类、成员方法和成员变量
final所修饰的类:不能被继承,不能有子类
final所修饰的方法:不能被重写
final所修饰的变量:是不可以修改的,是常量

12.2.1不可变对象

一个类的对象在通过构造方法创建后如果状态不会再被改变,那么它就是一个不可变(immutable)类。它的所有成员变量的赋值仅在构造方法中完成,不会提供任何 setter 方法供外部类去修改。

常见的不可变类是String类
当创建一个 String 对象时,假如此字符串在常量池中不存在,那么就创建一个;假如已经存,就不会再创建了,而是直接引用已经存在的对象。这样做能够减少 JVM 的内存开销,提高效率。
且非常适合作为哈希值(比如说作为 HashMap 的键),多次调用只返回同一个值,来提高效率。
如果对象的状态是可变的,那么在多线程环境下,就很容易造成不可预期的结果。而 String 是不可变的,就可以在多个线程之间共享,不需要同步处理。

不可变类有很多优点,就像之前提到的 String 类那样,尤其是在多线程环境下,它非常的安全。尽管每次修改都会创建一个新的对象,增加了内存的消耗

12.3抽象类(abstract class)和接口(interface)有什么异同?

相同点:
抽象类和接口都不能够实例化,但可以定义抽象类和接口类型的引用。
一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类。
不同点:
接口比抽象类更加抽象,因为抽象类中可以定义构造器,可以有抽象方法和具体方法,而接口中不能定义构造器而且其中的方法全部都是抽象方法。
抽象类中的成员可以是private、默认、protected、public的,而接口中的成员全都是public的。
抽象类中可以定义成员变量,而接口中定义的成员变量实际上都是常量。

13.构造方法

13.1构造方法的定义

构造函数名必须与其类名称相同
构造函数必须没有显式返回类型

13.2构造方法类型

默认构造函数(无参数构造函数)
参数化构造函数

14.参数传递

当一个参数按照值的方式在两个方法之间传递时,调用者和被调用者其实是用的两个不同的变量——被调用者中的变量(原始值)是调用者中变量的一份拷贝,对它们当中的任何一个变量修改都不会影响到另外一个变量。
而当一个参数按照引用传递的方式在两个方法之间传递时,调用者和被调用者其实用的是同一个变量,当该变量被修改时,双方都是可见的。

区别在于参数类型为基本数据类型还是引用数据类型

基本类型的变量存储的都是实际的值,而引用类型的变量存储的是对象的引用——指向了对象在内存中的地址。值和引用存储在 stack(栈)中,而对象存储在 heap(堆)中。

android keep注解 混淆 keep java_抽象类_09


方法的参数是基本类型的时候:形式参数的改变不影响实际参数。

在栈中,形参改后只在方法中生效,方法执行结束,方法消失,改变的形参也消失

方法的参数是引用类型的时候:形式参数的改变影响实际参数。

在栈中,形参改变的是堆中的值,方法消失后,main方法中的同一个参数指向堆中参数改变的地址

15.equals和hashcode

如果两个对象需要相等(equals),那么它们必须有着相同的哈希码(hashCode);
但如果两个对象有着相同的哈希码,它们却不一定相等。

16.集合

android keep注解 混淆 keep java_java_10

16.1ArrayList

android keep注解 混淆 keep java_android keep注解 混淆_11


ArrayList 实现了 List 接口,是基于数组实现的。数组定义后大小确定无法改变,ArrayList 是数组很好的替代方案,它提供了比数组更丰富的预定义方法(增删改查),并且大小是可以根据元素的多少进行自动调整的,非常灵活。

16.1.1创建ArrayList

List<String> alist = new ArrayList<>();

因为ArrayList实现了list接口,所以可以用多态

16.1.2增删改查

add()
set()
remove(int index) 方法用于删除指定下标位置上的元素,remove(Object o) 方法用于删除指定值的元素。
如果要正序查找一个元素,可以使用 indexOf() 方法;如果要倒序查找一个元素,可以使用 lastIndexOf() 方法。

16.2 linkedlist

android keep注解 混淆 keep java_java_12


第一个节点由于没有前一个节点,所以 prev 为 null;

最后一个节点由于没有后一个节点,所以 next 为 null;

这是一个双向链表,每一个节点都由三部分组成,前后节点和值。

16.2.1创建linkedlist

LinkedList<String> list = new LinkedList<>();

16.2.2增删改查

add(),还可以通过 addFirst() 方法将元素添加到第一位;addLast() 方法将元素添加到末尾;add(int index, E element) 方法将元素添加到指定的位置。
set()
remove()
如果要正序查找一个元素,可以使用 indexOf() 方法;如果要倒序查找一个元素,可以使用 lastIndexOf() 方法。

16.3数组和链表的区别

1.数组的大小是固定的,即便是 ArrayList 可以自动扩容,但依然会有一定的限制:如果声明的大小不足,则需要扩容;如果声明的大小远远超出了实际的元素个数,又会造成内存的浪费。尽管扩容的算法已经非常优雅,尽管内存已经绰绰有余。

2.数组的元素需要连续的内存位置来存储其值。这就是 ArrayList 进行删除或者插入元素的时候成本很高的真正原因,因为我们必须移动某些元素为新的元素留出空间
删除是同样的道理,删除之后的所有元素都必须往前移动一次。

即查找快,增删慢

LinkedList 就摆脱了这种限制:
1.LinkedList 允许内存进行动态分配,这就意味着内存分配是由编译器在运行时完成的,我们无需在 LinkedList 声明的时候指定大小。

2.LinkedList 不需要在连续的位置上存储元素,因为节点可以通过引用指定下一个节点或者前一个节点。也就是说,LinkedList 在插入和删除元素的时候代价很低,因为不需要移动其他元素,只需要更新前一个节点和后一个节点的引用地址即可。

即增删快,查找慢

16.4 线程安全的list

ArrayList是线程不安全的

16.4.1 Vector

Vector的线程安全是建立在每个方法上都加了 synchronized 关键字的基础上,锁的粒度很高,意味着性能不高

16.4.2 SynchronizedList

SynchronizedList没有在方法级别上使用 synchronized 关键字,而是在方法体内使用了 synchronized(this) 块

16.4.3 CopyOnWriteArrayList

CopyOnWriteArrayList 在进行写操作(add、set、remove)的时候会先进行拷贝,底层是通过数组复制来实现的。
CopyOnWriteArrayList 的写加锁,读不加锁。所以如果写的操作比较多,而读的操作比较少,内存就会被占用得比较多

16.5 List总结

单线程环境下最重要的就是 ArrayList 和 LinkedList,多线程环境下最重要的就是 CopyOnWriteArrayList

16.6 HashMap

android keep注解 混淆 keep java_android keep注解 混淆_13


HashMap 是一个 Map,用来存储 key-value 的键值对,每个键都可以精确地映射到一个值,然后我们可以通过这个键快速地找到对应的值。

16.6.1HashMap的特点

HashMap 的键必须是唯一的,不能重复。
HashMap 的键允许为 null,但只能有一个这样的键;值可以有多个 null。
HashMap 是无序的,它不保证元素的任何特定顺序。
HashMap 不是线程安全的;多线程环境下,建议使用 ConcurrentHashMap,或者使用 Collections.synchronizedMap(hashMap) 将 HashMap 转成线程同步的。
只能使用关联的键来获取值。
HashMap 只能存储对象,所以基本数据类型应该使用其包装器类型,比如说 int 应该为 Integer。
HashMap 实现了 Cloneable 和 Serializable 接口,因此可以拷贝和序列化。

16.6.2HashMap的节点

HashMap是一个集合,键值对的集合,源码中每个节点用Node<K,V>表示

static class Node<K,V> implements Map.Entry<K,V> {
   final int hash;
   final K key;
   V value;
   Node<K,V> next;

Node是一个内部类,这里的key为键,value为值,next指向下一个元素,可以看出HashMap中的元素不是一个单纯的键值对,还包含下一个元素的引用。

16.6.3HashMap的数据结构

HashMap的数据结构为 数组+(链表或红黑树),如图:

android keep注解 混淆 keep java_android keep注解 混淆_14


数组的特点:查询效率高,插入,删除效率低。

链表的特点:查询效率低,插入删除效率高。

在HashMap底层使用数组加(链表或红黑树)的结构完美的解决了数组和链表的问题,使得查询和插入,删除的效率都很高。

链表长度大于8时使用红黑树,长度降到6时使用链表

16.6.4HashMap存储元素

16.6.4.1HashCode

Map.put(“Key”,”Value”);
在put键值对时,先计算键的HashCode,该值用来定位要将这个元素存放到数组中的什么位置

16.6.4.1.1HashCode特点

对于同一个对象如果没有被修改(使用equals比较返回true)那么无论何时它的hashcode值都是相同的

对于两个对象如果他们的equals返回false,那么他们的hashcode值也有可能相等

16.6.4.2存储过程

通过hashcode值和数组长度取模我们可以得到元素存储的下标
可以分两种情况:

  1. 数组索引为下标的地方是空的,这种情况很简单,直接将元素放进去就好了。
  2. 已经有元素占据了索引为下标的位置,这种情况下我们需要判断一下该位置的元素和当前元素是否相等,使用equals来比较。
    如果使用默认的规则是比较两个对象的地址。也就是两者需要是同一个对象才相等,当然我们也可以重写equals方法来实现我们自己的比较规则最常见的是通过比较属性值来判断是否相等。
    如果两者相等则直接覆盖,如果不等则在原元素下面使用链表的结构存储该元素

16.6.5HashMap参数

初始容量大小和加载因子,初始容量大小是创建时给数组分配的容量大小,默认值为16,用数组容量大小乘以加载因子得到一个值,一旦数组中存储的元素个数超过该值就会调用rehash方法将数组容量增加到原来的两倍,专业术语叫做扩容.在做扩容的时候会生成一个新的数组,原来的所有数据需要重新计算哈希码值重新分配到新的数组,所以扩容的操作非常消耗性能.创建HashMap时我们可以通过合理的设置初始容量大小来达到尽量少的扩容的目的。加载因子也可以设置,但是除非特殊情况不建议设置.

android keep注解 混淆 keep java_父类_15

16.7LinkedHashMap

当需要一个按照插入顺序排列的键值对集合,HashMap做不到,因为HashMap 在插入的时候对键做了一次哈希算法,这就导致插入的元素是无序的。
LinkedHashMap可以维持插入顺序

16.7.1LinkedHashMap插入源码

LinkedHashMap 在添加第一个元素的时候,会把 head 赋值为第一个元素,等到第二个元素添加进来的时候,会把第二个元素的 before 赋值为第一个元素,第一个元素的 afer 赋值为第二个元素。这就保证了键值对是按照插入顺序排列的

16.8TreeMap

16.8.1红黑树

一种自平衡的二叉查找树(Binary Search Tree),结构复杂,但却有着良好的性能,完成查找、插入和删除的时间复杂度均为 log(n)。

16.8.1.1二叉查找树

android keep注解 混淆 keep java_java_16


一颗典型的二叉查找树:

1)左子树上所有节点的值均小于或等于它的根结点的值。

2)右子树上所有节点的值均大于或等于它的根结点的值。

3)左、右子树也分别为二叉排序树。

二叉查找树有一个不足,就是容易变成瘸子,就是一侧多,一侧少,就像下图这样:

android keep注解 混淆 keep java_Java_17

16.8.1.2平衡二叉树

左右两个子树的高度差的绝对值不超过 1

android keep注解 混淆 keep java_抽象类_18

16.8.1.3红黑树

节点是红色或者黑色的平衡二叉树,它通过颜色的约束来维持着二叉树的平衡

android keep注解 混淆 keep java_Java_19


1)每个节点都只能是红色或者黑色

2)根节点是黑色

3)每个叶节点(NIL 节点,空节点)是黑色的。

4)如果一个节点是红色的,则它两个子节点都是黑色的。也就是说在一条路径上不能出现相邻的两个红色节点。

5)从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

16.9ConcurrentHashMap

线程安全的Map:

16.9.1Hashtable

哈希表,使用较少。所有方法都用synchronized修饰,性能低

16.9.2Collections.synchronizedMap(new HashMap<String, String>())

将一个HashMap包装成一个SynchronizedMap

16.9.3ConcurrentHashMap

android keep注解 混淆 keep java_android keep注解 混淆_20

16.9.3.1ConcurrentHashMap 定义的关键字段
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
        implements ConcurrentMap<K,V>, Serializable {
    transient volatile Node<K,V>[] table;
    private transient volatile Node<K,V>[] nextTable;
    private transient volatile int sizeCtl;
}

table,默认为 null,第一次 put 的时候初始化,默认大小为 16,用来存储 Node 节点,扩容时大小总是 2 的幂次方。
Node中hash 和 key 是 final 的,和 HashMap 的 Node 一样,因为 key 是不会发生变化的。val 和 next 是 volatile 的,保证多线程环境下的可见性。

16.10Map总结

HashMap 完全不保证元素的顺序,添加了新的元素,之前的顺序可能完全逆转。

LinkedHashMap 默认会保持元素的插入顺序。

TreeMap 默认会保持 key 的自然顺序(根据 compareTo() 方法)。

17.反射

运行时才知道要操作的类,并且可以在运行时获取类的完整构造,并调用对应的方法

17.1获取反射中的Class对象

1.Class.forName

Class clazz = Class.forName(“类的全路径名”);

比如:JDBC加载数据库驱动

2.使用.class方法
适用于在编译前就知道要操作的类是哪个

Class clazz = String.class;

3.使用类对象的getClass()方法

String str = new String(“Hello”);
Class clazz = str.getClass();

17.2通过反射创建类的对象

1.通过Class对象的newInstance()方法

Class clazz = Apple.class;
Apple apple = (Apple)clazz.newInstance();

2.通过Constructor对象的newInstance()方法

Class clazz = Apple.class;
Constructor constructor = clazz.getConstructor();
Apple apple = (Apple)constructor .newInstance();

通过Constructor对象创建类对象可以选择特定的构造方法,通过Class对象创建类对象则只能通过无参构造

17.3反射的使用

1.JDBC加载数据库驱动

Class.forName("com.mysql.jdbc.Driver");

2.Spring的xml文件中配置bean,即IOC

<bean name="user" class="domain.User"></bean>

spring通过字符串解析,解析到我们想要new一个名为user的对象,他的实例是domain.User,所以spring通过反射创建domain.User对象,并将其放入bean容器中

17.4反射的优缺点

优点: 可以实现动态创建对象和编译,体现出很大的灵活性

缺点: 对性能有影响。使用反射基本上是一种解释操作,我们可以告诉 JVM,我们希望做什么并且它满足我们什么要求,这类操作总是慢于直接执行相同的操作

反射demo

@Test
void contextLoads() throws ClassNotFoundException {
    Class<HelloController> clazz1 = HelloController.class;
    System.out.println("clazz1:"+clazz1);
    Class<?> clazz2 = Class.forName("com.skye.controller.IndexController");
    System.out.println("clazz2:"+clazz2);
    DemoController dc = new DemoController();
    Class<? extends DemoController> clazz3 = dc.getClass();
    Class<DemoController> clazz4 = DemoController.class;
    System.out.println("clazz3:"+clazz3);
    System.out.println("clazz4:"+clazz4);
}

android keep注解 混淆 keep java_java_21

DemoController在编译前就声明了,所以编译时已经创建了Class对象,即Class@6106,当使用反射获取DemoControllerClass对象时,类加载器先去方法区找是否存在Class对象,找到后就不在创建新的Class对象,因为一个类只有一个类对象。
而另外两个Class对象均为反射执行后被创建

18.异常处理

18.1异常的结构层次

android keep注解 混淆 keep java_父类_22


Error 类异常描述了 Java 运行时系统的内部错误

Exception(例外)通常可分为两类,一类是写代码的人造成的,另外一类异常不是写代码的人造成的,要么需要抛出,要么需要捕获。

18.2异常处理

18.2.1异常抛出

Throw
throws是将异常声明但是不处理,而是将异常往上传,谁调用我就交给谁处理。而throw则是指抛出的一个具体的异常类型。

18.2.2异常捕获

Try
Catch
Finally
其中,finally中的代码无论如何都会执行