java基础类型
- 整形:int,long,short,byte
- 浮点型:double,float
- 布尔类型:boolean
- 字符类型:char
int 4个字节
long 8个字节
short 2个字节
byte 1个字节
double 8个字节
float 4个字节
boolean 1个bit, 八分之一个字节
char 2个字节
引用类型
- 强引用
- 当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
- 软引用
- 如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。
- 可以用来内存敏感的高速缓存
- 可以配合referenceQueue一起使用,当引用被回收的时候,JAVA 虚拟机就会把这个软引用加入到与之关联的引用队列中。
- 弱引用
- 与软引用类似,弱引用与软引用的区别在于:更短的生命周期,在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
- 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中
- 虚引用
- 如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。 虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
包装类型:
- 由于java有基础类型,基础类型本身并不是对象,所以其实本身违反了java的面向对象思想,所以将基础数据类型转换对应的包装类型,那么就可以操作基础类型像操作对象一样了。
基础类型 | 包装类型 |
---|---|
boolean | Boolean |
char | Character |
long | Long |
byte | Byte |
int | Integer |
short | Short |
float | Float |
double | Double |
- 自动拆箱和装箱
- 拆箱:
- 自动将包装类型转换成基础数据类型
- 自动拆箱如果遇到包装给null,则会抛出NEP
- 拆箱:
-
- 装箱:把基础数据类型转换成包装数据类型
- 装修的数据如果是-128-127之间的数据。调用integer.valueof()会被缓存起来,具体源码如下,
那么此时访问integer对象使用== , 还是equals的结果是一致的,都是常量池的数据比较。访问在此区间之外的数据则是内存比较
- 装修的数据如果是-128-127之间的数据。调用integer.valueof()会被缓存起来,具体源码如下,
- 装箱:把基础数据类型转换成包装数据类型
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
Object
-
object具体方法:
- hashcode()
- equals()
- wait()
- notify()
- notifyall()
- clone()
- toString()
- getClass()
- finalize() //实例被垃圾回收器回收的时候触发的操作
- registerNatives()
-
equals 和 ==
- 如果是两个引用类型比较,那么就是内存地址比较
- == 不能重写值比较
- 所有对象集成object都有equals方法,默认实现是==
-
equals 和 hashcode
- 当equals方法被重写时,通常有必要重写hashCode方法,以维护hashCode方法的常规约定:值相同的对象必须有相同的hashCode。
- hashCode不同时,object1.equals(object2)为false;
- hashCode相同时,object1.equals(object2)不一定为true;
- 当我们向一个Hash结构的集合中添加某个元素,集合会首先调用hashCode方法,这样就可以直接定位它所存储的位置,若该处没有其他元素,则直接保存。若该处已经有元素存在,就调用equals方法来匹配这两个元素是否相同,相同则不存,不同则链到后面(如果是链地址法)。
-
string sringbuffer stringbuilder
- 三个都是final 类,不可以被集成
- string不可变,sringbuffer stringbuilder长度可变
- sringbuffer是线程安全的
- stringbuilder线程不安全
- StringBuffer在StringBuilder的方法之上添加了synchronized,保证线程安全。
-
string是final类,不可以继承,也不可以重写java.lang.String(类加载机制)
- 双亲委派模型
- 因为加载某个类的时候,优先使用父类加载器来加载需要使用的类。如果我们自定义了
java.lang.String这个类 - 加载该类的类加载器为appclassloader
- appclassloader的父加载器为extclaassloader,所以这是加载string的类为extclassloader 但是在jre/lib/ext 下并没有发现string.class信息
- 所以接着上传给顶级加载器bootstrap。
- bootstrap在jre/lib目录下的rt.jar找到了string.class, 将其加载到内存中。
- 这就会类加载器的委托模型
- 所以用户自定义的类加载器不会生效
- 因为加载某个类的时候,优先使用父类加载器来加载需要使用的类。如果我们自定义了
- 双亲委派模型
-
substring
- 会创建一个新的string
- 编译时会吧+转换成stringbuilder 的append方法
- String = “abc”, 可能创建一个或者不创建对象,如果“ABC”在常量池中不存在,那么久会在常量池中创建一个String 对象,然后将对象的指向这个内存地址,以后无论创建多少次“abc”,都是相同的内存地址,
- 注意: substring和new String 都是在堆中重新创建一个对象。
-
常量池
-
String str = new String(“ABC”); 至少创建一个对象,也可能两个。因为用到new关键字,肯定会在heap中创建一个str2的String对象,它的value是“ABC”。同时如果这个字符串在字符串常量池里不存在,会在池里创建这个String对象“ABC”。
-
- String s1= “a”;
- String s2 = “a”;
- 此时s1 == s2 返回true
- “” 引号创建的对象在常量池塚
-
- String s1= new String(“a”);
- String s2 = new String(“a”);
- 此时s1 == s2 返回false
- 如果“a”在常量池中,那么仅在堆中拷贝一份引用(new String)
- 如果“a”不在常量池塚,那么会现在常量池中创建一份数据,然后在进行堆拷贝,
- 会创建两个对象
-
-
编译优化
final 都会在编译器优化,并且会被直接算好 -
String intern
- jdk7以后吧常量池从方法区移到了堆上
- 当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2);// false
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);// true
String s = newString("1"),生成了常量池中的“1” 和堆空间中的字符串对象。
s.intern(),这一行的作用是s对象去常量池中寻找后发现"1"已经存在于常量池中了。
String s2 = "1",这行代码是生成一个s2的引用指向常量池中的“1”对象。
结果就是 s 和 s2 的引用地址明显不同。因此返回了false。
String s3 = new String("1") + newString("1"),这行代码在字符串常量池中生成“1” ,并在堆空间中生成s3引用指向的对象(内容为"11")。注意此时常量池中是没有 “11”对象的。
s3.intern(),这一行代码,是将 s3中的“11”字符串放入 String 常量池中,此时常量池中不存在“11”字符串,JDK1.6的做法是直接在常量池中生成一个 "11" 的对象。
但是在JDK1.7中,常量池中不需要再存储一份对象了,可以直接存储堆中的引用。这份引用直接指向 s3 引用的对象,也就是说s3.intern() ==s3会返回true。
String s4 = "11", 这一行代码会直接去常量池中创建,但是发现已经有这个对象了,此时也就是指向 s3 引用对象的一个引用。因此s3 == s4返回了true。
String s3 = new String("1") + new String("1");
String s4 = "11";
s3.intern();
System.out.println(s3 == s4);// false
String s3 = new String("1") + newString("1"),这行代码在字符串常量池中生成“1” ,并在堆空间中生成s3引用指向的对象(内容为"11")。注意此时常量池中是没有 “11”对象的。
String s4 = "11", 这一行代码会直接去生成常量池中的"11"。
s3.intern(),这一行在这里就没什么实际作用了。因为"11"已经存在了。
结果就是 s3 和 s4 的引用地址明显不同。因此返回了false。
String str1 = new String("SEU") + new String("Calvin");
System.out.println(str1.intern() == str1);// true
System.out.println(str1 == "SEUCalvin");// true
str1.intern() == str1就是上面例子中的情况,str1.intern()发现常量池中不存在“SEUCalvin”,因此指向了str1。 "SEUCalvin"在常量池中创建时,也就直接指向了str1了。两个都返回true就理所当然啦。
String str2 = "SEUCalvin";//新加的一行代码,其余不变
String str1 = new String("SEU") + new String("Calvin");
System.out.println(str1.intern() == str1);// false
System.out.println(str1 == "SEUCalvin");// false
str2先在常量池中创建了“SEUCalvin”,那么str1.intern()当然就直接指向了str2,你可以去验证它们两个是返回的true。后面的"SEUCalvin"也一样指向str2。所以谁都不搭理在堆空间中的str1了,所以都返回了false。
面向对象
-
抽象类和接口的区别
- 抽象类只能被继承并且只能继承一个,接口需要被实现但是可以实现多个
- 抽象类中可以有成员变量;接口中的变量必须是static final的,必须是被初始化的,接口中只能有常量,不能有变量
- 抽象类的中方法可以不是抽象的;但是接口中必须是抽象的
- java 8 接口有default方法,可以被实现了
-
使用场景:
- 如果要创建不带变量和实现的基类,那么应该选择接口而不是抽象类
- 如果知道某个类应该是基类,那么第一个选择的应该是让它成为一个接口,只有在必须要有方法定义和成员变量的时候,才应该选择抽象类。因为抽象类中允许存在一个或多个被具体实现的方法,只要方法没有被全部实现该类就仍是抽象类。
-
重写和重载:
- 编译期的静态多分派:overloading重载 根据调用引用类型和方法参数决定调用哪个方法(编译器)
- 运行期的动态单分派:overriding 重写 根据指向对象的类型决定调用哪个方法(JVM)