java基礎不牢固容易踩的坑


java基礎不牢固容易踩的坑

  經過一年java后端代碼以及對jdk源碼閱讀之后的總結,對java中一些基礎中的容易忽略的東西寫下來,給偏愛技術熱愛開源的Coder們分享一下,避免在寫代碼中誤入雷區。

 (注:如無特殊說明,均以jdk8為基礎,本文所有例子均已通過編譯器通過,且對輸出進行了驗證)。

1.關於基本類型的包裝類的。

  基本類型boolean、char、byte、short、int、long、float、double。是java的特殊類型,特殊性在於區別於對象的存儲,對象存儲的是引用,引用指向在jvm堆中分配的值,基本類型直接存儲的就是值,能提高效率。

  同時java遵循面向對象思想為每個基本類型都提供了封裝類:Boolean、Character、Byte、Short、Integer、Float、Double。

坑1:變量賦值與類型轉換。

  變量賦值其實並不算是個坑,因為編譯器會自動檢查,例如long var = 2;編譯器會報錯。

  類型轉換分為自動轉換和強制轉換,這里不在贅述,具體轉換規則自行查詢。

  賦值的時候 = 和+=的區別,+=會自動轉換類型。

  short num;
  num = num + 1; //error
  num += 1; //ok

坑2:計算

  整數相除默認只保留整數,即使賦值給浮點類型也不行。

  double d = 5 / 2;
  System.out.println(d); //2

  byte相加超出長度后數值會變得很怪異。

  byte num = 127;
  num += 1;
  System.out.println(num);//-128

  兩個float相加結果會存在一定的誤差等等。

  float a1 = 1.001f;
  float a2= 1.819f;
  float a3 = a1 + a2;
  System.out.println(a3);//jdk8: 2.8200002
  System.out.println(12.0 - 11.9 == 0.1) //false

坑3:裝箱與拆箱 

  int a =100;
  Integer b = 100;
  Integer c= 100;
  System.out.println(a==b); //true
  System.out.println(b==a); //true
  System.out.println(b==c); //true
基本類型和包裝類型運算時會自動拆箱,所以ab相等;
當把100換成200 b==c會返回false。因為==比較的是引用;
b==c為true的原因是Integer采用了緩存,對-128到127之間的數據不再自動生成,而是直接引用(請看Integer中的IntegerCache內部類),類似於String;

2. null值

  1.null關鍵字,大小寫敏感

  2.null是引用類型的默認值

  4.null既不是對象也不是類型,可以強制轉換成任何引用類型。

    String s = (String) null; //ok

    int a = (int) null;  //error

  4.null值的引用類型變量,instance會返回false,如下:

  Integer iAmNull = null;
  System.out.println(iAmNull instanceof Integer); //false

  5.null值的引用變量調用非靜態方法,會拋npe,調用靜態方法是可以的。

3 void,Void

  void在邏輯上是一種數據類型,但不是基本類型,也不是引用類型。我們暫且不管它到底是什么類型,因為很多人都說不清。

  void提供了包裝類Void,看源碼我們會發現它被定義成final,而且構造方法是private,也就是說不能實例化。

  Void類型只能賦值為null,而void不能賦值,僅僅用來作為方法返回值輸出。

  Void能作為方法輸入參數當做占位符,只能傳值為null。

4.多態

  1.父類引用能指向子類對象,調用的方法具體取決於引用的對象,而不是取決於引用。

  public class A {
    public String show(D obj){
      return ("A and D");
    }  
    public String show(A obj){
      return ("A and A");
    }
  }
  class B extends A{
    public String show(B obj)...{
      return ("B and B");
    }
    public String show(A obj){
      return ("B and A");
    }
  }
  class C extends B{}
  class D extends B{}

 A a1 = new A();  
  A a2 = new B();
B b = new B(); C c = new C(); D d = new D(); System.out.println(a1.show(b)); ① System.out.println(a1.show(c)); ② System.out.println(a1.show(d)); ③ System.out.println(a2.show(b)); ④ System.out.println(a2.show(c)); ⑤ System.out.println(a2.show(d)); ⑥ System.out.println(b.show(b)); ⑦ System.out.println(b.show(c)); ⑧ System.out.println(b.show(d)); ⑨
  結果
  ①   A and A
  ②   A and A
  ③   A and D
  ④   B and A
  ⑤   B and A
  ⑥   A and D
  ⑦   B and B
  ⑧   B and B
  ⑨   A and D
  
  2.子類對父類方法不可見的情況下是不會覆蓋的,而是重新定義了一個方法。
  3.繼承關系只有方法會覆蓋,成員變量不會被覆蓋。  
  public class A {
   protected int i = 1;
  public void show(){
   System.out.println(i);
  }
  }
  public class B extends A{
      private int i = 10;
  public void show(){
   System.out.println(i);
  }
  }
  A a = new A();
  A a1 = new B();
  B b = new B();
  a.show(); //1
  a1.show(); //10
  b.show(); //10

5. super

  super並沒有代表超類的一個引用的能力,只是代表調用父類的方法而已。

  public class Test extends Number{
    public static void main(String[] args) {
      new Test().test();
    }
    private void test(){
      System.out.println(super.getClass().getName());  //獲取父類方法名getClass().getSuperClass().getName(); 
    }
  }

  這里應結合多態的override來理解上面的輸出。

6.字符串

  老生常談的問題了,字符串采用常量池緩存,不宜創建太多字符串,subString、new、+、等操作慎用,會創建很多字符串常量無法回收,當運行久了之后會占用越多越多的內存。

  字符串做參數,並不會改變改變實參的值。 

7.多線程

   線程安全的問題建議單獨去看。充分考慮到線程安全問題,不會出現死鎖問題。

  Object提供的wait、notify、notify不建議對多線程了解不深入的人去用。

  建議使用可重入鎖替代synchronized。

  多線程知識較多,這里不做詳細說明。

8.異常處理

   1.異常處理塊中可以繼續拋異常。

  2.try塊可以不需要catch或finally,但二者必須至少有一個

    3.finially塊中return 語句會覆蓋try塊中的return,finally塊在try塊代碼執行完后,return語句之前執行。

  public class MyClass {
  public static void main(String[] args) {
   System.out.print(new MyClass().getNum()); //4
      }
  int getNum(){
   try {
   System.out.println("try block");
   return 3;
   } finally {
   System.out.println("finally block");
   return 4;
  }
   }
  }

   輸出結果是:

    try block
    finally block
  4.碰到事務方法,異常處理要特別注意。

  5.finially不一定必執行,當在try塊中有system.exit(1);

    try {
    System.out.println("try block begin");
    System.exit(1);
    System.out.println("try block end");
    } catch (Exception e){
     System.out.println("catch block begin");
     System.exit(1);
     System.out.println("catch block end");
    } finally {
     System.out.println("finally block");
    }

    以上代碼輸出try block begin  

  6.異常不建議往上拋,特殊情況除外。

9.正則表達式

   replace、split等所有以正則表達式作為參數的方法

一定要注意正則表達式的含義,例如如下輸入
  String a = "acb..";
  System.out.println(a.replaceAll(".","b")); //bbbbb

  轉義字符串,尤其是路徑問題

10.靜態相關

  static可以修飾類(包含內部類)、成員方法、成員變量、類中代碼塊。

  static只能修飾類變量,不能修飾局部變量,編譯器會報錯。

  類啟動加載順序。靜態>非靜態,成員變量>代碼塊>構造方法,父類>子類。

  靜態方法和靜態變量會隨類的加載而加載,靜態內部類只有在使用時才會加載。

  static 不能和abstract同時使用,可以和final同時使用。

11 循環刪除

   在list中刪除a,看起來一切正常。如下所示。

  List<String> list = new LinkedList<>();
  list.add("a");
  list.add("b");
  list.add("c");
  for (String str : list){
   if ("b".equals(str)){
   list.remove(str);
   }
  }
  System.out.println(list); //[a, c]
 假如刪除a呢?ConcurrentModificationException,自己模擬下流程思考下原因。

12. 特殊關鍵字

  1.volatile:一旦一個共享變量(類的成員變量、類的靜態成員變量)被volatile修飾之后,那么就具備了兩層語義:

    1)保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。

    2)禁止進行指令重排序。

  2.transient:修飾成員變量。當對象被序列化時(寫入字節序列到目標文件)時,transient阻止實例中那些用此關鍵字聲明的變量持久化。

  3.strictfp:一旦使用了關鍵字strictfp來聲明某個類、接口或者方法時,那么在這個關鍵字所聲明的范圍內所有浮點運算都是精確的,符合IEEE-754規范的。

    例如一個類被聲明為strictfp,那么該類中所有的方法都是strictfp的。

  4.native:原生態方法,可以調用其他語言。這里不做詳細說明。

13 枚舉

  1.枚舉為每個枚舉對象創建一個實例,在首次使用時初始化。

  public static void main(String[] args) {
   weekday mon = weekday.mon;
  }
  public enum weekday {
   mon, tue, wes, thus, fri;
  private weekday() {
   System.out.println("hello"); //輸出hello五次
  }
  }
  2.構造方法可以傳值。
  
  public static void main(String[] args) {
   weekday mon = weekday.mon;
  }
  public enum weekday {
   mon, tue(1), wes(2), thus, fri;
  private weekday() {
   System.out.print("hello ");
  }
   private weekday(int a) {
   System.out.print("ok ");
  }
  }
  輸出:hello  ok  ok  hello  hello

14 泛型

 1. 泛型類、泛型方法,用<>表示,<>內的內容只要符合變量命名規范即可,不要求是T、K、E、V

 2. 泛型可以有多個變量,例如public Class MyClass<T1,T2,T3,T4>,一般1到2個。

 3. Set<Integer> 不是Set<Number>的子類,邏輯上不具備任何繼承關系,二者都屬於Set類。Set<Integer>賦值給Set<Number>會報錯。

 4. 上面一行的解決方式是泛型通配符。

 5. 泛型的類型參數只能是引用類型,如Set<int>編譯報錯。

 6. 不能對確切的泛型類型使用instance操作,

 7. <T extends Number>作用於方法或者類上,而 <? extends Number> 則不可以。

 8. 泛型運行期即被擦除,所以不能通過Type type = new TypeToken<TestGeneric<String>>(){}.getType(); 這種方式在運行期動態獲取泛型類型。

 9. 泛型類型在邏輯上看以看成是多個不同的類型,實際上都是相同的基本類型。

 

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM