1.实参给形参赋值
- 基本数据类型:值传递
- 引用数据类型:地址值传递
- 特殊类型:String和包装类等的对象不可变性
2.Integer
当给integer赋值时,如果赋值区间在-128~127之间,从常量池中取值,否则自行在堆
中new一个
Integer i = 5;
Integer j = 5;
Integer i1 = 200;
Integer j1 = 200;
System.out.println(i==j);//true
System.out.println(i1==j1);//false
3.变量的就近原则和生命周期
4.ArrayList的源码分析
- JDK 7 时:new ArrayList() 时,底层就创建了一个长度为10的Object[]数组elementData,然后在进行add()后,当底层数组容量不够时,进行扩容,为原来的1.5倍(capacity+capacity>>1),然后将原来的数组数据复制到新的数组
- JDK 8 时:new ArrayList() 时,底层Object[] elementData 初始化为{},即创建一个空数组,等到进行add()操作时,再创建长度为10,其他基本与JDK 7 的无异
小结:JDK 7 类似于单例模式的饿汉式,JDK 8类似于懒汉式
5.Vector的源码分析
-
new Vector()时,底层就创建了一个长度为10的Object[]数组elementData,然后在进行add()后,当底层数组容量不够时,进行扩容,为原来的2倍,然后将原来的数组数据复制到新的数组
-
Vector是线程安全的,大多数操作数据的方法都有synchronized
6.ArrayList和LinkedList的区别
- ArrayList基于动态的数组,连续存储内存,适合下标访问,扩容机制(见4),适合进行频繁的查询操作,使用尾插法并指定初始容量可以极大提升性能,甚至于超过LinkedList(需要创建大量的Node对象)
- LinkedList基于双向链表,可以存储再分散的内存中,适合数据的插入及删除,不适合频繁的查询操作(需要逐一遍历)
7.==和equals的区别
-
== :如果是基本数据类型,则比较数值;如果是引用类型,则比较其地址值
-
Object类中定义了equals()方法,但其底层使用的逻辑仍然是==,而Object是所有类的父类,如果当前类没有重写equals()方法,那么此时使用的equal()便来自于Object类,也就是==;如果当前类或者其父类重写了equals() 方法,那么就按照其重写的方法进行比较
8.接口和抽象类
-
相同点:
- 都不能被实例化
- 接口的实现类和抽象类的子类只有实现了接口或抽象类中的方法后才能实例化
-
不同点
-
接口只有定义,不能有方法的实现,但在java 1.8中可以定义default方法体,而抽象类可以有定义与实现,方法可在抽象类中实现
-
一个类可以实现多个接口,但只能继承一个抽象类
-
接口强调特定功能的实现,而抽象类强调所属关系。即当你关注一个操作的时候,使用接口;当你关注一个事物的本质时,使用抽象类
-
接口成员变量默认为
public static final
,必须赋初始值,不能被修改;其所有的成员方法都是public、abstract的。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号 -
接口被用于常用的功能,便于日后维护和添加删除,而抽象类更倾向于充当公共类的角色,不适用于日后重新对里面的代码修改。功能需要累积时用抽象类,不需要累积时用接口
-
9.重载与重写的区别
- 重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同。方法的返回值和修饰符可以不同,但会编译错误,编译器认为是同一个方法
- 重写:发生在子父类中,方法名、参数列表必须相同,返回值范围小于或等于父类,抛出的异常小于或等于父类,访问修饰符范围大于或等于父类;如果父类的访问修饰符为private,则子类就不能重写该方法
所以说重载是编译期的多态,重写是运行时的多态
10.HashMap源码分析
HashSet的底层就是HashMap,HashSet的值就是HashMap的key
-
概念:
- DEFAULT_INITIAL_CAPACITY = 16 :默认数组长度
- DEFAULT_LOAD_FACTOR = 0.75:负载因子,负载因子太小时,数组的空间利用率不高,频繁扩容浪费内存;负载因子太大时,因为有的数组位置可能很难会有数据,就会造成链表过长,而数组永远无法扩容
- MIN_TREEIFY_CAPACITY = 64 :树化数组的最小长度,
- TREEIFY_THRESHOLD = 8 :树化时链表的阈值
- UNTREEIFY_THRESHOLD = 6:退化为链表时的阈值
-
JDK 7:HashMap底层是由数组加链表组成(先不考虑扩容问题)
- 数组刚创建时,创建一个长度为16的Entry[],用于存放key和value。且存放的key的对象必须重写hashCode()和equals()方法
- 在HashMap添加元素的过程中,首先会对key进行哈希值的计算,然后再进行特定的算法再对哈希值进行二次计算,而后存放到Entry的对应位置(如果此时该位置为空的话),
- 如果该位置不为空,那么对该位置的所有元素(此时该位置以链表形式存储数据),依次先进行哈希值的比较,如果哈希值都不一样,那么使用头插法将该元素插入到链表中;如果哈希值一样,那么再进行equals()比较,如果不一样,那么依然使头插法将该元素插入到链表中用
- 如果哈希值和equals()都一样,那么使用新的value代替该位置的旧的value
-
JDK 8 :HashMap底层是由数组加链表加红黑树组成(先不考虑扩容问题),与JDK 7的区别是:
- 使用的时数组名是:Node[]
- 先不指定数组的长度,等到先里面添加数据时才指定数组的长度为16
- 在链表中的插入方法使用的时尾插法
-
扩容问题:
- 数组扩容:如果再添加的过程中,数组中的使用的空间超过12(即默认长度×负载因子),数组长度扩容,为原来的两倍,会把原来的HashMap所有的数据重新进行哈希计算,然后再放到相应的位置
- 链表树化:当插入的元素后,链表长度大于8时,并且此时的数组长度大于64,链表会转化为红黑树,如果数组的长度小于64,则对数组进行扩容;当红黑数的元素小于6时,会再次转化为链表
11.List跟Set的区别
- List:存储有序,可以按对象进入的顺序保存对象,可以重复,允许多个null值,可以使用Iterator迭代器进行遍历,也可以用for循环遍历
- Set:存储无序,不可重复,最多允许一个Null对象,取元素时只能用Iterator迭代器进行遍历
12.HashMap和HashTable的区别
- HashMap方法没有用synchronized修饰,不是线程安全的,HashTable线程安全
- HashMap允许key和value时null,HashTable不允许的
13.什么是字节码,有什么作用
- java引入是虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个共同的接口
- 编译程序只需要面向虚拟机,生产虚拟机所能够理解的代码。java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成成特定机器上的机器码,然后再特定的及其上运。这就是解释了java是编译与解释共存的特点
- 执行流程:Java源代码—>>>编译器—>>>jvm可执行的Java字节码(即虚拟指令)—>>>jvm—>>>jvm中解释器—>>>机器可以执行的二进制机器码—>>>程序运行
采用字节码的好处
java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点,所以java程序执行时比较高效,而且由于字节码并不专对于一种特定的机器,因此,Java程序无序重新编译即可再多种不同的计算机上运行
14.类的加载器
JDK自带有三个类加载器:bootstrapClassLoader、ExtClassLoader、AppClassLoader
都是通过一个parent变量去维护的,并不是通过继承4
- bootstrapClassLoader是ExtClassLoader的父类加载器,默认负责加载%JAVA_HOME%lib下的jar包和class文件
- ExtClassLoader是AppClassLoader的父类加载器,负责加载%JAVA_HOME%lib/ext文件夹下的jar包和class类
- AppClassLoader是自定义加载器的父类,负责加载classpath下的类文件,
- 如果要自定义类加载器,则需要去继承ClassLoader来实现
15.双亲委派机制
当一个Hello.class这样的文件要被加载时:
- 不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达BootstrapClassLoader之前,都是在检查是否加载过,并不会选择自己去加载。
- 直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。
为什么要设计这种机制呢
这种设计有个好处是,如果有人想替换系统级别的类:String.java。篡改它的实现,在这种机制下这些系统的类已BootstrapClassLoader加载过了(为什么?因为当一个类需要加载的时候,最先去尝试加载的就是BootstrapClassLoader),所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。
16.java中的异常体系
Java中的所有异常都来自顶级父类Throwable
Throable下有两个子类Exception和Error
- Error是程序无法处理的错误,一旦出现这种错误,则程序将被迫停止运行,例如OOM内存溢出
- Exception不会导致程序停止,又分为两个RunTimeException运行时异常和CheckedException检查异常
- RunTimeException常常发生在程序运行过程中,会导致程序当前线程执行失败
- CheckedException常常发生在程序编译过程中,会导致编译不通过(在写代码的时候,编译器就会提示)
17.String
-
String是被final修饰的类,不能被继承
-
String实现了Serializable和Comparable接口,表示String支持序列化和可以比较大小
-
String底层是通过char类型的数据实现的,并且被final修饰,所以字符串的值创建之后就不可以被修改,具有不可变性
-
深入理解String的不可变性
- String字符串具有不可变性,当字符串重新赋值时,不会在原来的内存地址进行修改,而是重新分配新的内存地址进行赋值
- 当字符串进行拼接时,也不会在原来的内存地址进行修改,而是重新分配新的内存地址进行赋值
- 当调用String的replace方法修改指定的字符或字符串时,也不会在原来的内存地址进行修改,也是重新分配新的内存地址进行赋值
-
String实例化的两种方式
-
方式一:通过字面量方式实例化
String str = "abc";
-
方式二:通过new+构造器的方式实例化
String str=new String("abc");
-
两种实例化方式的对比
- 通过字面量方式为字符串赋值时,此时的字符串存储在方法区的字符串常量池中;
- 通过new+构造器方式实例化字符串时,字符串对象存储在堆中,但是字符串的值仍然存储在方法区的常量池中。如下图所示
-
18.StringBuffer和StringBulider和String
- 相同点:
- StringBuffer和StringBulider和String一样都是用final修饰且底层都是用char[]实现的
- StringBuffer和StringBulider初始容量都是16和扩容机制都是"旧容量*2+2"
- 不同点:
- String对象一旦创建,其值是不能修改的,如果要修改,会重新开辟内存空间来存储修改之后的对象;而StringBuffer和StringBuilder对象的值是可以被修改的
- StringBuffer几乎所有的方法都使用synchronized实现了同步,线程比较安全,在多线程系统中可以保证数据同步,但是效率比较低;而StringBuilder 没有实现同步,线程不安全,在多线程系统中不能使用 StringBuilder,但是效率比较高
- 如果我们在实际开发过程中需要对字符串进行频繁的修改,不要使用String,否则会造成内存空间的浪费;当需要考虑线程安全的场景下使用 StringBuffer,如果不需要考虑线程安全,追求效率的场景下可以使用 StringBuilder
- StringBuffer是JDK1.0就有的,而StringBulider是JDK5.0才出现的