1.Java為什么跨平臺
java語言編寫的程序,一次編譯后,可以在多個系統(tǒng)平臺上運行,因為只要裝了Java虛擬機就可以運行,而C語言需要編譯成匯編程序,而不同CPU的匯編指令是不同的。
2.什么是注解,注解如何被處理
//作用范圍,可以用于類,方法,構(gòu)造器,參數(shù)等
@Target
//在生成javadoc的時候就會把@Documented注解給顯示出來
@Documented
//作用時機 source(源碼中保留) 、class(class中保留)、runtime(運行時保留)
@Retention
//如果類A標(biāo)注了該注解,那么其子類也會有該注解
@Inherited
注解簡單使用,已測試,請安心食用
@Retention(RetentionPolicy.RUNTIME)
@Target(value = ElementType.TYPE)
@interface Anno{
String value();
}
@Anno(value = "hello")
public class AnnotationTest {
public static void main(String[] args) {
Annotation[] annotations = AnnotationTest.class.getAnnotations();
System.out.println(annotations.length);
for (Annotation annotation : annotations) {
if(annotation instanceof Anno){
System.out.println(((Anno) annotation).value());
}
}
}
}
//輸出 1 hello
3.Java的異常
Error
Error 類是指 java 運行時系統(tǒng)的內(nèi)部錯誤和資源耗盡錯誤。應(yīng)用程序不會拋出該類對象。如果出現(xiàn)了這樣的錯誤,除了告知用戶,剩下的就是盡力使程序安全的終止。
RuntimeException
算數(shù)溢出、除0等
CheckedException
I/O錯誤導(dǎo)致
4.Java的泛型
泛型
泛型擦除
JVM并不知道泛型的存在,因為泛型在編譯階段就已經(jīng)被處理成普通的類和方法。比如:下面兩個list的類型是相同的。
5.Object類的常用方法
- hashCode()
String的hashCode實現(xiàn)方式:
(char[0] *31 + char[1]) * 31 + char[2] + ......
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
- equals()
- watit()
- notify()
- notifyAll()
- finalize()
在被垃圾回收器清除對象內(nèi)存空間前調(diào)用一次,但方法里面的內(nèi)容不一定能執(zhí)行,可能還沒有執(zhí)行對象就掛掉了。如果進行了自救,下次垃圾回收時不會再調(diào)用該方法。
用于釋放非java資源和給對象最后一次存活的機會(修改指針指向) - toString() 返回文本形式表示對象的字符串
- clone()
6.String字符串常量池的用法
JDK8字符串常量池位于堆,運行時常量池位于方法區(qū)
String str1 = "abcd";//先檢查字符串常量池中有沒有"abcd",如果字符串常量池中沒有,則創(chuàng)建一個,然后 str1 指向字符串常量池中的對象,如果有,則直接將 str1 指向"abcd"";
String str2 = new String("abcd");//堆中創(chuàng)建一個新的對象
String str3 = new String("abcd");//堆中創(chuàng)建一個新的對象
System.out.println(str1==str2);//false
System.out.println(str2==str3);//false
String s1 = "abc",s2="a";
String s3=s2+"bc";
String s6 = "a"+"bc";
System.out.println(s3 == "abc"); //false,說明s3引用指向的對象不在字符串常量池
System.out.println(s6 == "abc"); //true
System.out.println(s1 == "abc"); //true
new這個關(guān)鍵字,毫無疑問會在堆中分配內(nèi)存,創(chuàng)建一個String類的對象。因此,a這個在棧中的引用指向的是堆中的這個String對象的。
然后,因為"abc"是個常量,所以會去常量池中找,有沒有這個常量存在,沒的話分配一個空間,放這個"abc"常量,并將這個常量對象的空間地址給到堆中String對象里面;如果常量池中已經(jīng)有了這個常量,就直接用那個常量池中的常量對象的引用唄,就只需要創(chuàng)建一個堆中的String對象。
+ 的原理是StringBuilder
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = new String("b");
String s4 = s1 + s3;
String s5="ab";
String s6 = s1 + s2;
String s66= s1 + s2;
String s7 = "a" + s2;
String s8 = s1 + "b";
String s9 = "a" + "b";
System.out.println(s2 == s3); //false
System.out.println(s4 == s5); //false s4 是使用了StringBulider來相加了
System.out.println(s4 == s6); //false s4和s6 兩個都是使用了StringBulider來相加了
System.out.println(s6 == s66); //false 兩個都是使用了StringBulider來相加了
System.out.println(s5 == s7); //false s7是使用了StringBulider來相加了
System.out.println(s5 == s8); //false s8是使用了StringBulider來相加了
System.out.println(s7 == s8); //false 兩個都是使用了StringBulider來相加了
System.out.println(s9 == s8); //false 兩個都是使用了StringBulider來相加了
}
String s1 = new String("abc");這句話創(chuàng)建了幾個字符串對象?
將創(chuàng)建 1 或 2 個字符串。如果池中已存在字符串常量“abc”,則只會在堆空間創(chuàng)建一個字符串常量“abc”。如果池中沒有字符串常量“abc”,那么它將首先在池中創(chuàng)建,然后在堆空間中創(chuàng)建,因此將創(chuàng)建總共 2 個字符串對象
7.自動裝箱和自動拆箱
//自動裝箱
int a = 1;
Integer b = a;
調(diào)用了Integer b = Integer.valueOf(a);
//自動拆箱
Integer x =3;
int y = x; 其實執(zhí)行了int y = x.intValue();
例子:
Integer i1 = 40;
Integer i2 = 40;
Integer i3 = 0;
Integer i4 = new Integer(40);
Integer i5 = new Integer(40);
Integer i6 = new Integer(0);
System.out.println("i1=i2 " + (i1 == i2));
System.out.println("i1=i2+i3 " + (i1 == i2 + i3));
System.out.println("i1=i4 " + (i1 == i4));
System.out.println("i4=i5 " + (i4 == i5));
System.out.println("i4=i5+i6 " + (i4 == i5 + i6));
System.out.println("40=i5+i6 " + (40 == i5 + i6)); Copy to clipboardErrorCopied</pre>
結(jié)果:
i1=i2 true //Integer的緩存機制
i1=i2+i3 true //+只作用于int,而Integer和int無法比較,自動拆箱
i1=i4 false //一個位于堆中,一個位于緩存中
i4=i5 false //兩個對象引用地址不一樣
i4=i5+i6 true //+只作用于int,而Integer和int無法比較,自動拆箱
40=i5+i6 true //+只作用于int,自動拆箱
8.內(nèi)存泄漏
對象不會再被程序用到了,但是gc又不能回收
9.try catch finally 返回值
如果finnally中有return,那么先執(zhí)行try和catch中的語句和return后的賦值操作,再執(zhí)行finnally,在finally里面返回。
如果finnally中沒有return,么先執(zhí)行try和catch中的語句和return后的賦值操作,再執(zhí)行finnally,再返回到try或catch中 返回。
10.==和equals的區(qū)別
略
11.hashCode和equals
1、如果兩個對象equals,Java運行時環(huán)境會認(rèn)為他們的hashcode一定相等。
2、如果兩個對象不equals,他們的hashcode有可能相等,但會讓hashCode盡量不相等。
3、如果兩個對象hashcode相等,他們不一定equals。
4、如果兩個對象hashcode不相等,他們一定不equals。
//HashMap的hash方法
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
一般而言:
equals等,hashCode一定等。hashCode等,equals不一定等,因為可能哈希沖突。
什么時候要重寫equals和hashCode?
有時我們僅僅希望一個類中的一些比較重要的字段一樣就認(rèn)為兩個對象就是相等的,因此就要重寫兩個方法.
為什么是重寫equals一定要重寫hashCode
為了效率,當(dāng)該類對象作為HashMap的Key的時候,由于HashMap先比較hashCode,再比較equals,如果該類對象hashCode不相等但equals等,那么HashMap的算法就沒意義了,因此如果equals相等,那么hashCode一定要相等.
如何重寫equals和hashCode
如:
public class User {
private String id;
private String name;
private String age;
public User(){
}
public User(String id, String name, String age){
this.id = id;
this.name = name;
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
@Override
public String toString() {
return this.id + " " + this.name + " " + this.age;
}
@Override
public boolean equals(Object obj) {
if(this == obj){
return true;//地址相等
}
if(obj == null){
return false;//非空性:對于任意非空引用x,x.equals(null)應(yīng)該返回false。
}
if(obj instanceof User){
User other = (User) obj;
//需要比較的字段相等,則這兩個對象相等
if(equalsStr(this.name, other.name)
&& equalsStr(this.age, other.age)){
return true;
}
}
return false;
}
private boolean equalsStr(String str1, String str2){
if(StringUtils.isEmpty(str1) && StringUtils.isEmpty(str2)){
return true;
}
if(!StringUtils.isEmpty(str1) && str1.equals(str2)){
return true;
}
return false;
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + (name == null ? 0 : name.hashCode());
result = 31 * result + (age == null ? 0 : age.hashCode());
return result;
}
}
hashCode使用31的原因
- 31是一個不大不小的質(zhì)數(shù),太小會造成乘積會在一個很小的范圍,造成哈希沖突.太大會超出int的范圍
- 而且使用31*i = (1<<5 - 1) * i = i<<5 - i 位運算容易計算
12.String StringBuilder StringBuffer
效率 StringBuilder > StringBuffer > String
由于String是final類型,每次更改都會重寫拷貝,而StringBuilder基于數(shù)據(jù),以擴容方式實現(xiàn)更改。
為什么StringBuilder是線程不安全的?
如果同時有10個線程,每個線程循環(huán)1000次進行append,最后輸出長度,會發(fā)現(xiàn)長度小于10000,并且會拋出數(shù)組越界。
public class StringBuilderDemo {
public static void main(String[] args) throws InterruptedException {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 10; i++){
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 1000; j++){
stringBuilder.append("a");
}
}
}).start();
}
Thread.sleep(100);
System.out.println(stringBuilder.length());
}
}
為什么長度小于1000?
可以看到,append方法是線程不安全的,如果當(dāng)前數(shù)組長度為10,同時有兩個線程執(zhí)行到count+=len那么count=11,而不是12
//存儲字符串的具體內(nèi)容
char[] value;
//已經(jīng)使用的字符數(shù)組的數(shù)量
int count;
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
為什么會拋出數(shù)組越界?
當(dāng)線程A,B同時進入append邏輯,線程A加入兩個字符,線程B也加入兩個字符,兩個線程同時執(zhí)行完了ensureCapacityInternal(count + len),然后線程A擴容完成,并將count值修改,這是B執(zhí)行拷貝添加的字符到原字符末尾,但起始位置可能是沒有分配的下標(biāo)因此數(shù)組越界.
此外還可能發(fā)生數(shù)據(jù)添加覆蓋的問題
考慮兩個線程同時執(zhí)行了ensureCapacityInternal(count + len),然后線程A執(zhí)行了拷貝數(shù)組,之后線程B執(zhí)行拷貝數(shù)據(jù)將A添加的值覆蓋,這就導(dǎo)致數(shù)據(jù)覆蓋.
而StringBuffer使用synchronized確保線程安全