一、Integer缓存问题
Integer会缓存 -128到127之间的数值,这个区间内的对象会被复用。
//EG public class Tset{ public static void main(String[] args){ Integer a = 100; Integer b = 100; Integer c = 1000; Integer d = 1000; System.out.println(a == b); System.out.println(c == d); } } /* 结果为: true false 这是因为100属于 -128到127这个区间,所以创建这个区间内的Integer对象时, 会首先查缓存(一个Integer数组), 如果该数组中已经有一个对应数值的对象了,则直接返回这个对象。如果没有,才会new。 Long、Double、Char等封装类都有类似的设定 所以,对于封装类值的比较要使用equals进行判断 如果Integer对象直接使用new来生成,则==来判断则为不相等,因为地址肯定不同,是两个不同的对象 */二、序列化问题
序列化:将内存中的对象信息转换为可以存储或者传输的数据的过程 反序列化:将序列化后的数据转换为原对象的过程
JAVA自带了序列化工具,只需要将要序列化的对象继承 Serializable 接口即可,每一个序列化类都有一个serialVersionUID的版本号,反序列化时会校验本地对应的类的序列化版本号(通过反射来获取)和加载的序列化字节流中的版本是否一致,如果不一致,则反序列化失败。
transient关键字:被该关键字标记的成员将不会参与序列化。
三、Java值传递
java中只存在值传递,在传递参数时,会将实际参数复制一份到方法中。 当向函数传递一个对象的引用时,也是将该对象的引用复制一份传递到函数中,此时相当于有两个引用指向该对象。
public class Test{ public static void main(String[] args) { Person a = new Person(); a.setName("sun"); a.setAge("21"); a.pass(a); System.out.println(a.getName()); } } Class Person{ private String name; private String age; //get and set public void pass(Person person){ person = new Person(); person.setName("xxx"); System.out.println(person.getName()); } } /* out: xxx sun 这说明传递的参数并不是Person的引用a,而是a的拷贝。如果传递的是实际引用a,那么当运行完pass函数后,再输出a.getName(),结果应该是xxx。 */四、深拷贝和浅拷贝
Object类自带的clone()方法,是浅拷贝方式。而且如果要使用该方法,必须实现Cloneable接口,否则会抛异常。
如果一个类中持有另一个类的实例(成员变量是一个类),那么使用该方法拷贝时,不会拷贝类成员变量,而只是拷贝一份引用。
class AA{ private int a; public AA(){ a = 1; } } class BB implements Cloneable{ private int b; public AA aa = new AA(); public BB(){ b = 1; } @Override protected BB clone() throws CloneNotSupportedException { return (BB)super.clone(); } } public class Test { public static void main(String[] args) throws CloneNotSupportedException { BB b = new BB(); BB b2 = b.clone(); System.out.println(b.aa==b2.aa); } } //输出结果为true,说明b2和b共享同一个AA对象实例。所以,clone()方法除非重写拷贝逻辑,否则只能进行浅拷贝 //深拷贝和浅拷贝的区别在于引用类型是否共享 //浅拷贝引用类型会共享 //深拷贝则引用类型各自独立五、枚举
枚举类除了声明的枚举常量外没有其他实例,而且枚举类如果被abstract或者final关键字修饰会报编译错误。
而且枚举类不能实例化,通过反射来访问枚举类会出现IllegalArgumentException异常。
六、subList()和asList()方法
使用subList产生的字串,对其操作,会同时映射到母串中。
public void testSubList() { List<String> stringList = new ArrayList<>(); stringList.add("赵"); stringList.add("钱"); stringList.add("孙"); stringList.add("李"); stringList.add("周"); stringList.add("吴"); stringList.add("郑"); stringList.add("王"); List<String> subList = stringList.subList(2, 4); System.out.println("子列表:" + subList.toString()); System.out.println("子列表长度:" + subList.size()); subList.set(1, "慕容"); System.out.println("子列表:" + subList.toString()); System.out.println("原始列表:" + stringList.toString()); } /*Output: 子列表:[孙,李] 子列表长度:2 子列表:[孙,慕容] 原始列表:[赵,钱,孙,慕容,周,吴,郑,王] 同时注意,在subList生成子串时,母串的modCount(修改计数器)也会复制到字串 字串进行增删改的时候同时会检查母串的modCount是否发生过修改,没有修改才会成功。如果不一致的话,会出现快速失败。 */而asList()方法是数组到集合的桥梁,会生成一个基于某个数组的定长列表,由于是定长列表,所以不支持add()和remove()。
七、代码块
public class Test{ public void testMethod(){ //普通代码块(在方法内): { int a = 1; } int b = 2; //同步代码块用来限定变量的生命周期,在代码块内部声明的变量只在代码块内部有效,通常局部变量的声明周期是包含整个方法域的,使用普通代码块可以提前结束其生命,提高内存利用率。 } //静态代码块:静态代码块只在类加载时执行一次,是类的初始化类构造器的一部分,在类的初始化阶段执行。 static{ System.out.println("static block") } //构造代码块(在方法外):用来完成公共部分的初始化工作,构造代码块在类实例化时一定会执行一次,且先于构造函数。所以,当构造函数有多个时,可以使用构造代码块将公共部分抽离出来。 { System.out.println("construct block") } //同步代码块:可以具体到某一位置再加锁,而不用将整个方法都锁定,粒度更小一些。 public void testMethod2(){ synchronize(this){ System.out.println("同步代码块") } } } /* 代码块的执行顺序: 静态变量初始化-->静态代码块-->构造代码块-->构造方法-->普通代码块 继承中的执行顺序: 父类静态块-->子类静态块-->父类构造代码块-->父类构造器-->子类构造代码块-->子类构造器 其中静态代码块和静态变量的赋值都是在类的初始化阶段完成的,而构造代码块的执行是在类实例化的时候完成的。 */八、String
String是常量类型,底层实现为一个被final修饰的数组,所以每次更改String变量的值都是重新申请一块空间存储一个新的string字符串。
字符串创建有两种方式,直接赋值和通过构造方法。
直接赋值方式创建的字符串存放在方法区的常量池,如果有相同的字符串,会复用已有的。
构造方法方式创建的字符串存放在堆内存。
public class TestString { public static void main(String[] args) { String str1 = "Lance"; String str2 = new String("Lance"); String str3 = str2; //引用传递,str3直接指向st2的堆内存地址 String str4 = "Lance"; /** * ==: * 基本数据类型:比较的是基本数据类型的值是否相同 * 引用数据类型:比较的是引用数据类型的地址值是否相同 * 所以在这里的话:String类对象==比较,比较的是地址,而不是内容 */ System.out.println(str1==str2);//false System.out.println(str1==str3);//false System.out.println(str3==str2);//true System.out.println(str1==str4);//true } }九、Comparator的升降序
为什么comparator接口的参数(o1,o2),如果返回o1-o2就是升序,o2-o1则为降序呢?
源码实现中,Comparator接口的compare方法只要返回值小于0,则不交换,如果返回值大于0,则交换。 所以o1-o2,如果返回值大于0,发生交换,o1到了o2后面,所以为升序。
