java基礎知識點


title: java基礎知識點
date: 2017-12-08 15:35:31
updated: 2020-03-15 17:33:58
categories: java
tags:
- java


10天惡補java的學習筆記

java學習路線

用法:一共27章筆記,每章都很少,按順序看完即可。有問題想深入的可以百度+b站視頻,以下是看的過程中我另外加餐深入瞭解的:

JAVA 工程師技術路線參考:

  • 基礎:Java 教程,JSP 教程
  • 進階:SQL 教程,MySQL 教程
  • 高級:正則表達式教程,Mongodb教程, AJAX 教程,JSON 教程,XML 教程,
  • 開發工具:Eclipse、IDEA

總結:以陽哥筆記爲主線往下看,附加string相關+三章的b站視頻即可。(截止寫完這篇博客時,陽哥筆記還剩網絡編程、反射兩章,估計還要配合b站視頻看)

瘋狂看的話,一週半應該能看完(符合學習一門語言10天的節奏,時間太長收益不大)。把陽哥看完,java基礎應該差不多了

java語法

java教程

java菜鳥教程(含jdk、eclipse下載、環境變量設置)

JDK 1.6 在線中文手冊

int與String轉換

int a = 20;
System.out.println(new Integer(a).toString()); //int to String


String ss = "25";
System.out.println(Integer.parseInt(ss)); //String to int

static

靜態方法中只能訪問靜態成員變量,靜態方法中也只能調用靜態方法

靜態方法:----不能操作對象(雖然使用對象也可以調用類的靜態方法,但容易混淆並且與個別對象毫無關係,因此建議使用類名調用靜態方法)

靜態方法是一種不能向對象實施操作的方法(eg. Math.pow()),在運算時,不使用任何對象。可以認爲靜態方法是沒有this參數的方法(在一個非靜態方法中,this參數表示這個 方法的隱式參數)

靜態方法不能訪問實例域(因爲不能操作對象),但可以訪問靜態域,如下:

public static int getID()
{
	return nextId;   //public static int nextId;
}

可以通過類名調用靜態方法:

Employee.getID();

在下面兩種情況下使用靜態方法:

一個方法不需要訪問對象狀態,其所需參數都是通過顯式參數提供(Math.pow(2,3))

一個方法只需要訪問類的靜態域,而不訪問實例域( Employee.getID(); )

問:是否可以從一個static方法內部發出對非static方法的調用? 不可以
不可以。因爲非static方法是要與對象關聯在一起的,必須創建一個對象後,纔可以在該對象上進行方法調用,而static方法調用時不需要創建對象,可以直接調用。也就是說,當一個static方法被調用時,可能還沒有創建任何實例對象,如果從一個static方法中發出對非static方法的調用,那個非static方法是關聯到哪個對象上的呢?這個邏輯無法成立,故不可以

舉例:

public class xix {
    // 靜態成員 
    public static String string="static成員";
    // 普通成員
    public String string2="非static成員";
    // 靜態方法
    public static void method(){
        string="sss";
        //string2="sss";編譯報錯,因爲靜態方法裏面只能調用靜態方法或靜態成員
        //method2();
        System.out.println("這是static方法,static方法與對象無關");
    }

    // 普通方法 
    public void method2(){
        string ="string1";
        string2="string2";
        method(); //非靜態方法裏面可以發出對static方法的調用
        System.out.println("這是非static方法,此方法必須和指定的對象關聯起來才起作用");
    }
    public static void main(String[] args) {
        xix x=new xix();
        x.method2();// 引用調用普通方法 
        x.method();// 引用調用靜態方法
    }
}

final

final 修飾類中的屬性或者變量

無論屬性是基本類型還是引用類型,final 所起的作用都是變量裏面存放的"值"不能變。

這個值,對於基本類型來說,變量裏面放的就是實實在在的值,如 1,“abc” 等。

而引用類型變量裏面放的是個地址,所以用 final 修飾引用類型變量指的是它裏面的地址不能變,並不是說這個地址所指向的對象或數組的內容不可以變,這個一定要注意。

例如:類中有一個屬性是 final Person p=new Person(“name”); 那麼你不能對 p 進行重新賦值,但是可以改變 p 裏面屬性的值 p.setName(‘newName’);

final 修飾屬性,聲明變量時可以不賦值,而且一旦賦值就不能被修改了。對 final 屬性可以在三個地方賦值:聲明時、初始化塊中、構造方法中,總之一定要賦值。

2、final修飾類中的方法

作用:可以被繼承,但繼承後不能被重寫。

3、final修飾類

作用:類不可以被繼承。

final 變量:

final 變量能被顯式地初始化並且只能初始化一次。被聲明爲 final 的對象的引用不能指向不同的對象。但是 final 對象裏的數據可以被改變。

也就是說 final 對象的引用不能改變,但是裏面的值可以改變。
final 修飾符通常和 static 修飾符一起使用來創建類常量。

final 方法

類中的 final 方法可以被子類繼承,但是不能被子類修改。
聲明 final 方法的主要目的是防止該方法的內容被修改。

public class Test{
    public final void changeName(){
       // 方法體
    }
}

finalfinal 類不能被繼承,沒有類能夠繼承 final 類的任何特性。
public final class Test {
   // 類體
}

abstract修飾符

抽象類:

抽象類不能用來實例化對象,聲明抽象類的唯一目的是爲了將來對該類進行擴充。

一個類不能同時被 abstract 和 final 修飾。如果一個類包含抽象方法,那麼該類一定要聲明爲抽象類,否則將出現編譯錯誤。
抽象類可以包含抽象方法和非抽象方法。

abstract class Caravan{
   private double price;
   private String model;
   private String year;
   public abstract void goFast(); //抽象方法
   public abstract void changeColor();
}

抽象方法

抽象方法是一種沒有任何實現的方法,該方法的的具體實現由子類提供。
抽象方法不能被聲明成 final 和 static。

任何繼承抽象類的子類必須實現父類的所有抽象方法,除非該子類也是抽象類。

如果一個類包含若干個抽象方法,那麼該類必須聲明爲抽象類。抽象類可以不包含抽象方法。

抽象方法的聲明以分號結尾,例如:public abstract void sample();

public abstract class SuperClass{
    abstract void m(); //抽象方法
}
 
class SubClass extends SuperClass{
     //實現抽象方法
      void m(){
          .........
      }
}

抽象類除了不能實例化對象之外,抽象類必須被繼承,才能被使用

  1. 抽象類不能被實例化(初學者很容易犯的錯),如果被實例化,就會報錯,編譯無法通過。只有抽象類的非抽象子類可以創建對象。

  2. 抽象類中不一定包含抽象方法,但是有抽象方法的類必定是抽象類。

  3. 抽象類中的抽象方法只是聲明,不包含方法體,就是不給出方法的具體實現也就是方法的具體功能。

  4. 構造方法,類方法(用static修飾的方法)不能聲明爲抽象方法。

  5. 抽象類的子類必須給出抽象類中的抽象方法的具體實現,除非該子類也是抽象類。

synchronized 修飾符

synchronized 關鍵字聲明的方法同一時間只能被一個線程訪問。
synchronized 修飾符可以應用於四個訪問修飾符。

public synchronized void showDetails(){
.......
}

transient 修飾符

序列化的對象包含被 transient 修飾的實例變量時,java 虛擬機(JVM)跳過該特定的變量。

該修飾符包含在定義變量的語句中,用來預處理類和變量的數據類型。

public transient int limit = 55;   // 不會持久化
public int b; // 持久化

volatile 修飾符

volatile 修飾的成員變量在每次被線程訪問時,都強制從共享內存中重新讀取該成員變量的值。而且,當成員變量發生變化時,會強制線程將變化值回寫到共享內存。這樣在任何時刻,兩個不同的線程總是看到某個成員變量的同一個值。

一個 volatile 對象引用可能是 null。

public class MyRunnable implements Runnable
{
    private volatile boolean active;
    public void run()
    {
        active = true;
        while (active) // 第一行
        {
            // 代碼
        }
    }
    public void stop()
    {
        active = false; // 第二行
    }
}

通常情況下,在一個線程調用 run() 方法(在 Runnable 開啓的線程),在另一個線程調用 stop() 方法。 如果 第一行 中緩衝區的 active 值被使用,那麼在 第二行 的 active 值爲 false 時循環不會停止。

但是以上代碼中我們使用了 volatile 修飾 active,所以該循環會停止。

子類與父類

子類是父類的類型,但父類不是子類的類型。

子類的實例可以聲明爲父類型,但父類的實例不能聲明爲子類型。

class Vehicle {}

public class Car extends Vehicle {
    public static void main(String args[]){
        Vehicle v1 = new Vehicle(); //父類型
        Vehicle v2 = new Car(); //子類的實例可以聲明爲父類型
        Car c1 = new Car();    // 子類型
        Car c2 = new Vehicle(); //這句會報錯,父類型的實例不能聲明爲子類型

        //Car(子類)是Vehicle(父類)類型, Vehicle(父類)不是Car(子類)類型
        boolean result1 =  c1 instanceof Vehicle;    // true
        boolean result2 =  c1 instanceof Car;        // true
        boolean result3 =  v1 instanceof Vehicle;    // true
        boolean result4 =  v1 instanceof Car;          // false
        boolean result5 =  v2 instanceof Vehicle;    // true
        boolean result6 =  v2 instanceof Car;          // true

        System.out.println(result1);
        System.out.println(result2);
        System.out.println(result3);
        System.out.println(result4);
        System.out.println(result5);
        System.out.println(result6);
   }
}

switch語句

當變量的值與 case 語句的值相等時,那麼 case 語句之後的語句開始執行,直到 break 語句出現纔會跳出 switch 語句。

output:
b
c
haha

在控制檯輸入由*號組成的菱形

public class Test{
    public static void main(String[] args)
    {
        int num = 11;  //打印一個一行最多num個*的菱形
        int k = num; //通過數學計算,我們可知菱形的行數總數也爲num
        for(int i=0; i<(k+1)/2; ++i)    //升序排序打印輸出
        {
            int mid = (k-1)/2; //mid是每行的居中點下標
            for(int j=0; j<k; ++j)
            {
                int temp = j-mid;
                if(java.lang.Math.abs(temp) <= i)
                {
                    System.out.print("*");
                }
                else
                {
                    System.out.print(" ");
                }
            }
            System.out.println();
        }

        for(int i=(k-1)/2-1; i>=0; i--)    //降序排序打印輸出
        {
            int mid = (k-1)/2; //mid是每行的居中點下標
            for(int j=0; j<k; ++j)
            {
                int temp = j-mid;
                if(java.lang.Math.abs(temp) <= i)
                {
                    System.out.print("*");
                }
                else
                {
                    System.out.print(" ");
                }
            }
            System.out.println();
        }
    }
}

==與equals

String類

注意:String 類是不可改變的,所以你一旦創建了 String 對象,那它的值就無法改變了。

如果需要對字符串做很多修改,那麼應該選擇使用 StringBuffer & StringBuilder 類。

StringBuffer 和 StringBuilder 類

當對字符串進行修改的時候,需要使用 StringBuffer 和 StringBuilder 類。

和 String 類不同的是,StringBuffer 和 StringBuilder 類的對象能夠被多次的修改,並且不產生新的未使用對象。

StringBuilder 類在 Java 5 中被提出,它和 StringBuffer 之間的最大不同在於 StringBuilder 的方法不是線程安全的(不能同步訪問)。

由於 StringBuilder 相較於 StringBuffer 有速度優勢,所以多數情況下建議使用 StringBuilder 類。然而在應用程序要求線程安全的情況下,則必須使用 StringBuffer 類。

Java 中 StringBuffer 和 String 是有一定的區別的,首先,String 是被 final 修飾的,他的長度是不可變的,就算調用 String 的

concat 方法,那也是把字符串拼接起來並重新創建一個對象,把拼接後的 String 的值賦給新創建的對象,而 StringBuffer 的長度是可變的,

調用StringBuffer 的 append 方法,來改變 StringBuffer 的長度,並且,相比較於 StringBuffer,String 一旦發生長度變化,是非常耗費內存的!

String 長度大小不可變
StringBuffer 和 StringBuilder 長度可變
StringBuffer 線程安全 StringBuilder 線程不安全
StringBuilder 速度快

length與size

length()方法,length屬性和size()的方法的區別:

1.length()方法是針對字符串來說的,要求一個字符串的長度就要用到它的length()方法;

2.length屬性是針對Java中的數組來說的,要求數組的長度可以用其length屬性;

3.java中的size()方法是針對泛型集合說的,如果想看這個泛型有多少個元素,就調用此方法來查看!

數組與Arrays類

java.util.Arrays 類能方便地操作數組,它提供的所有方法都是靜態的。

具有以下功能:

  • 給數組賦值:通過 fill 方法。
  • 對數組排序:通過 sort 方法,按升序。
  • 比較數組:通過 equals 方法比較數組中元素值是否相等。
  • 查找數組元素:通過 binarySearch 方法能對排序好的數組進行二分查找法操作。

String str = “helloworld”;

char[] data = str.toCharArray();// 將字符串轉爲數組

格式化

  1. 對整數進行格式化:%[index][][]4][標識][最小寬度]轉換方式 格式化字符串由4部分組成,特殊的格式常以%index開頭,index從1開始取值,表示將第index個參數拿進來進行格式化,[最小寬度]的含義也很好理解,就是最終該整數轉化的字符串最少包含多少位數字。剩下2個部分的含義:

標識:

 '-' 在最小寬度內左對齊,不可以與"用0填充"同時使用
 '#' 只適用於8進制和16進制,8進制時在結果前面增加一個0,16進制時在結果前面增加0x
 '+' 結果總是包括一個符號(一般情況下只適用於10進制,若對象爲BigInteger纔可以用於8進制和16進制)
 ' ' 正值前加空格,負值前加負號(一般情況下只適用於10進制,若對象爲BigInteger纔可以用於8進制和16進制)
 '0' 結果將用零來填充
 ',' 只適用於10進制,每3位數字之間用","分隔
 '(' 若參數是負數,則結果中不添加負號而是用圓括號把數字括起來(同'+'具有同樣的限制)
轉換方式:
d-十進制 o-八進制 x或X-十六進制
上面的說明過於枯燥,我們來看幾個具體的例子。需要特別注意的一點是:大部分標識字符可以同時使用。

  1. 對浮點數進行格式化:%[index$][標識][最少寬度][.精度]轉換方式
    我們可以看到,浮點數的轉換多了一個"精度"選項,可以控制小數點後面的位數。
    標識:
'-' 在最小寬度內左對齊,不可以與"用0填充"同時使用
'+' 結果總是包括一個符號
' ' 正值前加空格,負值前加負號
'0' 結果將用零來填充
',' 每3位數字之間用","分隔(只適用於fgG的轉換)
'(' 若參數是負數,則結果中不添加負號而是用圓括號把數字括起來(只適用於eEfgG的轉換)
轉換方式:
'e', 'E' -- 結果被格式化爲用計算機科學記數法表示的十進制數
'f' -- 結果被格式化爲十進制普通表示方式
'g', 'G' -- 根據具體情況,自動選擇用普通表示方式還是科學計數法方式
'a', 'A' -- 結果被格式化爲帶有效位數和指數的十六進制浮點數

3.對字符進行格式化:

對字符進行格式化是非常簡單的,c表示字符,標識中’-'表示左對齊,其他就沒什麼了。

這個格式化沒有太掌握,用的時候還需要進一步學習

sleep休眠、測量時間

sleep()使當前線程進入停滯狀態(阻塞當前線程),讓出CPU的使用、目的是不讓當前線程獨自霸佔該進程所獲的CPU資源,以留一定時間給其他線程執行的機會。

你可以讓程序休眠一毫秒的時間或者到您的計算機的壽命長的任意段時間。

例如,下面的程序會休眠3秒:

Thread.sleep(1000*3);   // 休眠3秒
output:3001

Calendar類與Date類

我們現在已經能夠格式化並創建一個日期對象了,但是我們如何才能設置和獲取日期數據的特定部分呢,比如說小時,日,或者分鐘? 我們又如何在日期的這些部分加上或者減去值呢? 答案是使用Calendar 類。

Calendar類的功能要比Date類強大很多,而且在實現方式上也比Date類要複雜一些。

Calendar類是一個抽象類,在實際使用時實現特定的子類的對象,創建對象的過程對程序員來說是透明的,只需要使用getInstance方法創建即可。

1.Calendar c = Calendar.getInstance();//默認是當前日期

正則表達式

java.util.regex包

java.util.regex 包主要包括以下三個類:

Pattern 類:pattern 對象是一個正則表達式的編譯表示。Pattern 類沒有公共構造方法。要創建一個 Pattern 對象,你必須首先調用其公共靜態編譯方法,它返回一個 Pattern 對象。該方法接受一個正則表達式作爲它的第一個參數。

Matcher 類:Matcher 對象是對輸入字符串進行解釋和匹配操作的引擎。與Pattern 類一樣,Matcher 也沒有公共構造方法。你需要調用 Pattern 對象的 matcher 方法來獲得一個 Matcher 對象。

PatternSyntaxException:PatternSyntaxException 是一個非強制異常類,它表示一個正則表達式模式中的語法錯誤。

1.匹配一組

String content = "I am noob " + "from runoob.com.";
String pattern = ".*runoob.*";
boolean isMatch = Pattern.matches(pattern, content);
System.out.println("字符串中是否包含了 'runoob' 子字符串? " + isMatch); //true

  1. 匹配多組
// 按指定模式在字符串查找
String line = "This order was placed for QT3000! OK?";
String pattern = "(\\D*)(\\d+)(.*)";

// 創建 Pattern 對象
Pattern r = Pattern.compile(pattern);

// 現在創建 matcher 對象
Matcher m = r.matcher(line);
if (m.find( )) {
 System.out.println("Found value: " + m.group(0) );
 System.out.println("Found value: " + m.group(1) );
 System.out.println("Found value: " + m.group(2) );
 System.out.println("Found value: " + m.group(3) ); 
} else {
 System.out.println("NO MATCH");
}

可變參數

JDK 1.5 開始,Java支持傳遞同類型的可變參數給一個方法。
在方法聲明中,在指定參數類型後加一個省略號(…) 。

一個方法中只能指定一個可變參數,它必須是方法的最後一個參數。任何普通的參數必須在它之前聲明。

finalize() 方法

Java 允許定義這樣的方法,它在對象被垃圾收集器析構(回收)之前調用,這個方法叫做 finalize( ),它用來清除回收對象。

例如,你可以使用 finalize() 來確保一個對象打開的文件被關閉了。
在 finalize() 方法裏,你必須指定在對象銷燬時候要執行的操作。

finalize() 一般格式是:

protected void finalize()
{
   // 在這裏終結代碼
}

關鍵字 protected 是一個限定符,它確保 finalize() 方法不會被該類以外的代碼調用。

當然,Java 的內存回收可以由 JVM 來自動完成。如果你手動使用,則可以使用上面的方法。

java異常處理

所有的異常類是從 java.lang.Exception 類繼承的子類。

Exception 類是 Throwable 類的子類。除了Exception類外,Throwable還有一個子類Error 。

Java 程序通常不捕獲錯誤。錯誤一般發生在嚴重故障時,它們在Java程序處理的範疇之外。

Error 用來指示運行時環境發生的錯誤。

例如,JVM 內存溢出。一般地,程序不會從錯誤中恢復。

異常類有兩個主要的子類:IOException 類和 RuntimeException 類。

從大體來分異常爲兩塊:

1、error—錯誤 : 是指程序無法處理的錯誤,表示應用程序運行時出現的重大錯誤。例如jvm運行時出現的OutOfMemoryError以及Socket編程時出現的端口占用等程序無法處理的錯誤。

2、Exception — 異常 :異常可分爲運行時異常跟編譯異常

  • 運行時異常:即RuntimeException及其之類的異常。這類異常在代碼編寫的時候不會被編譯器所檢測出來,是可以不需要被捕獲,但是程序員也可以根據需要進行捕獲拋出。常見的RUNtimeException有:NullpointException(空指針異常),ClassCastException(類型轉換異常),IndexOutOfBoundsException(數組越界異常)等。
  • 編譯異常:RuntimeException以外的異常。這類異常在編譯時編譯器會提示需要捕獲,如果不進行捕獲則編譯錯誤。常見編譯異常有:IOException(流傳輸異常),SQLException(數據庫操作異常)等。

3、java處理異常的機制:拋出異常以及捕獲異常 ,一個方法所能捕捉的異常,一定是Java代碼在某處所拋出的異常。簡單地說,異常總是先被拋出,後被捕捉的。

4、throw跟throws的區別:

public void test() throws Exception {
    throw new Exception();
}

從上面這一段代碼可以明顯的看出兩者的區別。throws表示一個方法聲明可能拋出一個異常,throw表示此處拋出一個已定義的異常(可以是自定義需繼承Exception,也可以是java自己給出的異常類)。

5、接下來看一下如何捕獲異常:

1)首先java對於異常捕獲使用的是try—catch或try — catch — finally 代碼塊,程序會捕獲try代碼塊裏面的代碼,若捕獲到異常則進行catch代碼塊處理。若有finally則在catch處理後執行finally裏面的代碼。然而存在這樣兩個問題:

a.看如下代碼:

try{
    //待捕獲代碼
}catch(Exception e){
    System.out.println("catch is begin");
    return 1}finally{
     System.out.println("finally is begin");
}

在catch裏面有一個return,那麼finally會不會被執行呢?答案是肯定的,上面代碼的執行結果爲:

catch is begin
finally is begin 

也就是說會先執行catch裏面的代碼後執行finally裏面的代碼最後才return1 ;

b.看如下代碼:

try{
   //待捕獲代碼    
}catch(Exception e){
    System.out.println("catch is begin");
    return 1}finally{
     System.out.println("finally is begin");
     return 2 ;
}

在b代碼中輸出結果跟a是一樣的,然而返回的是return 2 ; 原因很明顯,就是執行了finally後已經return了,所以catch裏面的return不會被執行到。也就是說finally永遠都會在catch的return前被執行。(這個是面試經常問到的問題哦!)

6、對於異常的捕獲不應該覺得方便而將幾個異常合成一個Exception進行捕獲,比如有IO的異常跟SQL的異常,這樣完全不同的兩個異常應該分開處理!而且在catch裏處理異常的時候不要簡單的e.printStackTrace(),而是應該進行詳細的處理。比如進行console打印詳情或者進行日誌記錄。

注意:異常和錯誤的區別:異常能被程序本身可以處理,錯誤是無法處理。

java繼承

注意:super 語句必須是子類構造方法的第一條語句。不能在子類中使用父類構造方法名來調用父類構造方法。 父類的構造方法不被子類繼承。調用父類的構造方法的唯一途徑是使用 super 關鍵字,如果子類中沒顯式調用,則編譯器自動將 super(); 作爲子類構造方法的第一條語句。這會形成一個構造方法鏈。

靜態方法中不能使用 super 關鍵字。

  1. 這個例子很重要,第三個輸出注意看
class SuperClass {
  private int n;
  SuperClass(){
    System.out.println("SuperClass()");
  }
  SuperClass(int n) {
    System.out.println("SuperClass(int n)");
    this.n = n;
  }
}
class SubClass extends SuperClass{
  private int n;
  
  SubClass(){
    super(300);
    System.out.println("SubClass");
  }  
  
  public SubClass(int n){
    System.out.println("SubClass(int n):"+n);
    this.n = n;
  }
}
public class TestSuperSub{
  public static void main (String args[]){
    SubClass sc = new SubClass();
    SubClass sc2 = new SubClass(200); 
  }
}

output:
SuperClass(int n)
SubClass
SuperClass() //一定注意這一點,在子類構造方法之前,首先是調用父類的構造方法
SubClass(int n):200

2.又一個例子很重要

class Animal{
   public void move(){
      System.out.println("動物可以移動");
   }
}
 
class Dog extends Animal{
   public void move(){
      System.out.println("狗可以跑和走");
   }
   public void bark(){
      System.out.println("狗可以吠叫");
   }
}
 
public class TestDog{
   public static void main(String args[]){
      Animal a = new Animal(); // Animal 對象
      Animal b = new Dog(); // Dog 對象  //———如果改成Dog b = new Dog();就對了
 
      a.move();// 執行 Animal 類的方法
      b.move();//執行 Dog 類的方法
      b.bark();
   }
}

output:
TestDog.java:30: cannot find symbol
symbol  : method bark()
location: class Animal
                b.bark();
                

這是由於在編譯階段,只是檢查參數的引用類型。如果編譯通過,在運行時,Java虛擬機(JVM)指定對象的類型並且運行該對象的方法。

因此在上面的例子中,之所以能編譯失敗,是因爲Animal類中不存在move方法,改成Dog b = new Dog();就對了

java多態深入理解

強烈建議直接看菜鳥教程的多態和多態前一節(重載、重寫)

多態存在的三個必要條件

  • 繼承
  • 重寫
  • 父類引用指向子類對象

當使用多態方式調用方法時,首先檢查父類中是否有該方法,如果沒有,則編譯錯誤;如果有,再去調用子類的同名方法。

多態的好處:可以使程序有良好的擴展,並可以對所有類的對象進行通用處理。

方法的重寫(Overriding)和重載(Overloading)是java多態性的不同表現,重寫是父類與子類之間多態性的一種表現,重載可以理解成多態的具體表現形式。

(1)方法重載是一個類中定義了多個方法名相同,而他們的參數的數量不同或數量相同而類型和次序不同,則稱爲方法的重載(Overloading)。

(2)方法重寫是在子類存在方法與父類的方法的名字相同,而且參數的個數與類型一樣,返回值也一樣的方法,就稱爲重寫(Overriding)。

(3)方法重載是一個類的多態性表現,而方法重寫是子類與父類的一種多態性表現。

重載與重寫的簡明理解:

  • 重載反映的是"隨機應變". 同樣一項功能, 根據數據類型的不同, 採用不同的處理方式.

  • 重寫反映的是"父子差異". 你"繼承"了父親吃火鍋的愛好, 但是吃同一份鴛鴦鍋(注意, 數據類型相同) , 你喜歡涮紅湯, 你父親喜歡涮清湯.

所謂多態就是指程序中定義的引用變量所指向的具體類型和通過該引用變量發出的方法調用在編程時並不確定,而是在程序運行期間才確定,即一個引用變量倒底會指向哪個類的實例對象,該引用變量發出的方法調用到底是哪個類中實現的方法,必須在由程序運行期間才能決定。因爲在程序運行時才確定具體的類,這樣,不用修改源程序代碼,就可以讓引用變量綁定到各種不同的類實現上,從而導致該引用調用的具體方法隨之改變,即不修改程序代碼就可以改變程序運行時所綁定的具體代碼,讓程序可以選擇多個運行狀態,這就是多態性。

java接口

類的多繼承是不合法,一個類只能繼承一個類,可以實現多個接口,但接口允許多繼承.

1.接口可以多繼承

2.接口的方法聲明必須是 public abstract 即便不寫默認也是

3.接口裏面不能包含方法具體實現

4.類實繼承接口必須實現接口裏申明的全部方法,除非該類是抽象類

5.類裏面可以聲明 public static final 修飾的變量

6.接口不能被實例化,但是可以被實現類創建

接口裏面定義的成員變量都是 public static final修飾,抽象類中的變量是普通變量

抽象類和接口的區別:

  1. 抽象類中的方法可以有方法體,就是能實現方法的具體功能,但是接口中的方法不行。

  2. 抽象類中的成員變量可以是各種類型的,而接口中的成員變量只能是 public static final 類型的。

  3. 接口中不能含有靜態代碼塊以及靜態方法(用 static 修飾的方法),而抽象類是可以有靜態代碼塊和靜態方法。

  4. 一個類只能繼承一個抽象類,而一個類卻可以實現多個接口。

什麼時候使用抽象類和接口:

  • 如果你擁有一些方法並且想讓它們中的一些有默認實現,那麼使用抽象類吧。

  • 如果你想實現多重繼承,那麼你必須使用接口。由於Java不支持多繼承,子類不能夠繼承多個類,但可以實現多個接口。因此你就可以使用接口來解決它。

  • 如果基本功能在不斷改變,那麼就需要使用抽象類。如果不斷改變基本功能並且使用接口,那麼就需要改變所有實現了該接口的類。

java多線程

Java 提供了三種創建線程的方法:

  • 通過實現 Runnable 接口;
  • 通過繼承 Thread 類本身;
  • 通過 Callable 和 Future 創建線程。

總結:Thread和Runnable的區別

如果一個類繼承Thread,則不適合資源共享。但是如果實現了Runable接口的話,則很容易的實現資源共享。

實現Runnable接口比繼承Thread類所具有的優勢:

1)適合多個相同的程序代碼的線程去處理同一個資源

2)可以避免java中的單繼承的限制

3)增加程序的健壯性,代碼可以被多個線程共享,代碼和數據獨立

4)線程池只能放入實現Runable或callable類線程,不能直接放入繼承Thread的類

線程調度

1、調整線程優先級:Java線程有優先級,優先級高的線程會獲得較多的運行機會。

Java線程的優先級用整數表示,取值範圍是1~10,Thread類有以下三個靜態常量:

static int MAX_PRIORITY
          線程可以具有的最高優先級,取值爲10static int MIN_PRIORITY
          線程可以具有的最低優先級,取值爲1static int NORM_PRIORITY
          分配給線程的默認優先級,取值爲5

Thread類的setPriority()和getPriority()方法分別用來設置和獲取線程的優先級。

每個線程都有默認的優先級。主線程的默認優先級爲Thread.NORM_PRIORITY。

線程的優先級有繼承關係,比如A線程中創建了B線程,那麼B將和A具有相同的優先級。

JVM提供了10個線程優先級,但與常見的操作系統都不能很好的映射。如果希望程序能移植到各個操作系統中,應該僅僅使用Thread類有以下三個靜態常量作爲優先級,這樣能保證同樣的優先級採用了同樣的調度方式。

2、線程睡眠:Thread.sleep(long millis)方法,使線程轉到阻塞狀態。millis參數設定睡眠的時間,以毫秒爲單位。當睡眠結束後,就轉爲就緒(Runnable)狀態。sleep()平臺移植性好。

3、線程等待:Object類中的wait()方法,導致當前的線程等待,直到其他線程調用此對象的 notify() 方法或 notifyAll() 喚醒方法。這個兩個喚醒方法也是Object類中的方法,行爲等價於調用 wait(0) 一樣。

4、線程讓步:Thread.yield() 方法,暫停當前正在執行的線程對象,把執行機會讓給相同或者更高優先級的線程。

5、線程加入:join()方法,等待其他線程終止。在當前線程中調用另一個線程的join()方法,則當前線程轉入阻塞狀態,直到另一個進程運行結束,當前線程再由阻塞轉爲就緒狀態。

6、線程喚醒:Object類中的notify()方法,喚醒在此對象監視器上等待的單個線程。如果所有線程都在此對象上等待,則會選擇喚醒其中一個線程。選擇是任意性的,並在對實現做出決定時發生。線程通過調用其中一個 wait 方法,在對象的監視器上等待。 直到當前的線程放棄此對象上的鎖定,才能繼續執行被喚醒的線程。被喚醒的線程將以常規方式與在該對象上主動同步的其他所有線程進行競爭;例如,喚醒的線程在作爲鎖定此對象的下一個線程方面沒有可靠的特權或劣勢。類似的方法還有一個notifyAll(),喚醒在此對象監視器上等待的所有線程。

注意:Thread中suspend()和resume()兩個方法在JDK1.5中已經廢除,不再介紹。因爲有死鎖傾向。

wait()相關詳解

Obj.wait(),與Obj.notify()必須要與synchronized(Obj)一起使用,也就是wait,與notify是針對已經獲取了Obj鎖進行操作,從語法角度來說就是Obj.wait(),Obj.notify必須在synchronized(Obj){…}語句塊內。從功能上來說wait就是說線程在獲取對象鎖後,主動釋放對象鎖,同時本線程休眠。直到有其它線程調用對象的notify()喚醒該線程,才能繼續獲取對象鎖,並繼續執行。相應的notify()就是對對象鎖的喚醒操作。但有一點需要注意的是notify()調用後,並不是馬上就釋放對象鎖的,而是在相應的synchronized(){}語句塊執行結束,自動釋放鎖後,JVM會在wait()對象鎖的線程中隨機選取一線程,賦予其對象鎖,喚醒線程,繼續執行。這樣就提供了在線程間同步、喚醒的操作。Thread.sleep()與Object.wait()二者都可以暫停當前線程,釋放CPU控制權,主要的區別在於Object.wait()在釋放CPU同時,釋放了對象鎖的控制。

線程示例1(這個可以作爲模板,請記住套路)

/**
 * 功能描述: 三個售票窗口同時出售20張票
 * 程序分析:1.票數要使用同一個靜態值
            2.爲保證不會出現賣出同一個票數,要java多線程同步鎖。
 *設計思路:
            1.創建一個站臺類Station,繼承Thread,重寫run方法,在run方法裏面執行售票操作!
                 售票要使用同步鎖:即有一個站臺賣這張票時,其他站臺要等這張票賣完!
            2.創建主方法調用類
 @Author:braincao
 @Date: 2018/1/3 10:43
 */
public class Test
{
    public static void main(String[] args)
    {
        Station a = new Station("A");
        Station b = new Station("B");
        Station c = new Station("C");
        a.start();
        b.start();
        c.start();
    }
}

class Station extends Thread{
    private String name;
    private static int tickets = 20; //爲了保持票數的一致,票數要靜態
    public Station(String name)
    {
        super(name);
    }

    static Object key = "key"; //創建一個靜態鑰匙
    public void run()
    {
        while(tickets>0)
        {
            synchronized(key) //這個很重要,必須使用一個鎖,進去的人會把鑰匙拿在手上,出來後才把鑰匙讓出來
            {
                if(tickets>0)
                {
                    tickets--;
                    System.out.println(getName() + "窗口賣票一張 餘票:" + tickets);
                }
                else
                {
                    System.out.println("窗口無法買票,票買完了");
                }
            }

            try
            {
                sleep(1000); //休息一秒,這裏休息是必要的,否則很容易只讓一個線程一直賣票,休息了給其他線程讓出機會
            }
            catch(InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }
}

線程示例2:建立三個線程,A線程打印10次A,B線程打印10次B,C線程打印10次C,要求線程同時運行,交替打印10次ABC。這個問題用Object的wait(),notify()就可以很方便的解決

/**
 * 功能描述:建立三個線程,A線程打印10次A,B線程打印10次B,C線程打印10次C,
 * 要求線程同時運行,交替打印10次ABC。這個問題用Object的wait(),notify()就可以很方便的解決
 * @Author:braincao
 * @Date: 2018/1/3 14:44
 */
package com.multithread.wait;
public class Test implements Runnable {

    private String name;
    private Object prev; //前一個對象鎖
    private Object self; //自身對象鎖

    private Test(String name, Object prev, Object self) {
        this.name = name;
        this.prev = prev;
        this.self = self;
    }

    @Override
    public void run() {
        int count = 10;
        while (count > 0) {
            synchronized (prev) {
                synchronized (self) {
                    System.out.print(name);
                    count--;

                    self.notify(); //釋放自身對象鎖
                }
                try {
                    prev.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    public static void main(String[] args) throws Exception {
        Object a = new Object();
        Object b = new Object();
        Object c = new Object();
        Test pa = new Test("A", c, a);
        Test pb = new Test("B", a, b);
        Test pc = new Test("C", b, c);


        new Thread(pa).start();
        Thread.sleep(1000);  //確保按順序A、B、C執行
        new Thread(pb).start();
        Thread.sleep(1000);
        new Thread(pc).start();
        Thread.sleep(1000);
    }
}    

輸出結果:
ABCABCABCABCABCABCABCABCABCABC

先來解釋一下其整體思路,從大的方向上來講,該問題爲三線程間的同步喚醒操作,主要的目的就是ThreadA->ThreadB->ThreadC->ThreadA循環執行三個線程。爲了控制線程執行的順序,那麼就必須要確定喚醒、等待的順序,所以每一個線程必須同時持有兩個對象鎖,才能繼續執行。一個對象鎖是prev,就是前一個線程所持有的對象鎖。還有一個就是自身對象鎖。主要的思想就是,爲了控制執行的順序,必須要先持有prev鎖,也就前一個線程要釋放自身對象鎖,再去申請自身對象鎖,兩者兼備時打印,之後首先調用self.notify()釋放自身對象鎖,喚醒下一個等待線程,再調用prev.wait()釋放prev對象鎖,終止當前線程,等待循環結束後再次被喚醒。運行上述代碼,可以發現三個線程循環打印ABC,共10次。程序運行的主要過程就是A線程最先運行,持有C,A對象鎖,後釋放A,C鎖,喚醒B。線程B等待A鎖,再申請B鎖,後打印B,再釋放B,A鎖,喚醒C,線程C等待B鎖,再申請C鎖,後打印C,再釋放C,B鎖,喚醒A。看起來似乎沒什麼問題,但如果你仔細想一下,就會發現有問題,就是初始條件,三個線程按照A,B,C的順序來啓動,按照前面的思考,A喚醒B,B喚醒C,C再喚醒A。但是這種假設依賴於JVM中線程調度、執行的順序。

socket網絡通信

三次握手

在TCP/IP協議中,TCP協議通過三次握手建立一個可靠的連接

而下圖是java socket網絡通信的過程。定睛一看,服務器socket與客戶端socket建立連接的部分其實就是大名鼎鼎的三次握手。

第一次握手:客戶端嘗試連接服務器,向服務器發送syn包(同步序列編號Synchronize Sequence Numbers),syn=j,客戶端進入SYN_SEND狀態等待服務器確認
第二次握手:服務器接收客戶端syn包並確認(ack=j+1),同時向客戶端發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態
第三次握手:第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手

刷面經時別人總結的java知識點

1.抽象方法只能定義在抽象類中,抽象方法和抽象類必須由abstract修飾,abstract關鍵字只能描述類和方法,不能描述變量。抽象方法只定義方法聲明,不定義方法實現。抽象類不可以被實例化(創建對象),只有通過子類繼承抽象類並覆蓋抽象類中的所有抽象方法後,該子類纔可以被實例化,否則該子類還是一個抽象類。抽象類中有構造函數用於給子類對象進行初始化,同時抽象類中可以含有非抽象方法。abstract關鍵字不可以與final,private,static關鍵字共存,因爲被final修飾的方法不可以被重寫,意味着子類不可以重寫該方法,如果abstract和final共同修飾父類中的方法,子類要實現抽象方法(abstract的作用),而final又不讓該方法重寫,這相互矛盾。如果private和abstract共同修飾父類中的方法,private修飾則該方法不可以被子類訪問,但是abstract修飾需要子類去實現,兩者產生矛盾。如果static和abstract共同修飾父類中的方法,static表示是靜態的方法,隨着類的加載而加載,則該方法不需要在子類中去實現,這與abstract關鍵字矛盾。

2.static用於修飾成員變量和成員函數,想要實現對象中的共性數據的對象共享,可以將這個數據進行靜態修飾,被靜態修飾的成員可以直接被類名調用,靜態隨着類的加載而加載,而且優先於對象存在。靜態方法只能訪問靜態成員(靜態方法和靜態變量),不可以訪問非靜態成員,這是因爲靜態方法加載時,優先於對象存在,所以沒有辦法訪問對象中的成員。靜態方法中不能使用this和super關鍵字,因爲this代表本類對象,super代表父類對象,而靜態時,有可能沒有對象存在,所以this和super無法使用。

3.final關鍵字可以修飾類,方法,變量(成員變量內,局部變量,靜態變量),被final修飾的類是一個最終類,不可以被繼承,被final修飾的方法是一個最終方法,不可以被覆蓋,但是可以被繼承。被final修飾的變量只能是一個常量,只能賦值一次。內部類被定義在類中的局部位置上時,只能訪問局部被final修飾的局部變量。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章