尚硅谷Java——宋紅康筆記【day19-day24】


day19

測試Thread中的常用方法:

  1. start():啟動當前線程;調用當前線程的run()
  2. run(): 通常需要重寫Thread類中的此方法,將創建的線程要執行的操作聲明在此方法中
  3. currentThread():靜態方法,返回執行當前代碼的線程
  4. getName():獲取當前線程的名字
  5. setName():設置當前線程的名字
  6. yield():釋放當前cpu的執行權
  7. join():在線程a中調用線程b的join(),此時線程a就進入阻塞狀態,直到線程b完全執行完以后,線程a才結束阻塞狀態。
  8. stop():已過時。當執行此方法時,強制結束當前線程。
  9. sleep(long millitime):讓當前線程“睡眠”指定的millitime毫秒。在指定的millitime毫秒時間內,當前線程是阻塞狀態。
  10. isAlive():判斷當前線程是否存活

線程的優先級:

  • MAX_PRIORITY:10
  • MIN _PRIORITY:1
  • NORM_PRIORITY:5 -->默認優先級

2.如何獲取和設置當前線程的優先級:

  • getPriority():獲取線程的優先級
  • setPriority(int p):設置線程的優先級

說明:高優先級的線程要搶占低優先級線程cpu的執行權。但是只是從概率上講,高優先級的線程高概率的情況下被執行。並不意味着只有當高優先級的線程執行完以后,低優先級的線程才執行。

示例:h1.setPriority(Thread.MAX_PRIORITY)

Thread.currentThread().getPriority()

多線程的創建,方式一:繼承於Thread類

  1. 創建一個繼承於Thread類的子類
  2. 重寫Thread類的run() --> 將此線程執行的操作聲明在run()中
  3. 創建Thread類的子類的對象
  4. 通過此對象調用start()

創建多線程的方式二:實現Runnable接口

  1. 創建一個實現了Runnable接口的類
  2. 實現類去實現Runnable中的抽象方法:run()
  3. 創建實現類的對象
  4. 將此對象作為參數傳遞到Thread類的構造器中,創建Thread類的對象
  5. 通過Thread類的對象調用start()

比較創建線程的兩種方式。

開發中:優先選擇:實現Runnable接口的方式

原因:

  • 1.實現的方式沒有類的單繼承性的局限性
  • 2.實現的方式更適合來處理多個線程有共享數據的情況。

聯系:public class Thread implements Runnable

相同點:兩種方式都需要重寫run(),將線程要執行的邏輯聲明在run()中。

day20

關於售票例子的思考

  1. 問題:賣票過程中,出現了重票、錯票 -->出現了線程的安全問題
  2. 問題出現的原因:當某個線程操作車票的過程中,尚未操作完成時,其他線程參與進來,也操作車票。
  3. 如何解決:當一個線程a在操作ticket的時候,其他線程不能參與進來。直到線程a操作完ticket時,其他線程才可以開始操作ticket。這種情況即使線程a出現了阻塞,也不能被改變。

同步機制,解決線程安全問題

4.在Java中,我們通過同步機制,來解決線程的安全問題。

方式一:同步代碼塊

synchronized(同步監視器){ //需要被同步的代碼 }

說明:

  • 1.操作共享數據的代碼,即為需要被同步的代碼。 -->不能包含代碼多了,也不能包含代碼少了。
  • 2.共享數據:多個線程共同操作的變量。比如:ticket就是共享數據。
  •   3.同步監視器,俗稱:鎖。任何一個類的對象,都可以充當鎖。
    
  •      要求:多個線程必須要共用同一把鎖。
    
  •   補充:在實現Runnable接口創建多線程的方式中,我們可以考慮使用this充當同步監視器。
    
  •   說明:在繼承Thread類創建多線程的方式中,慎用this充當同步監視器,考慮使用當前類充當同步監視器。
    

方式二:同步方法。

如果操作共享數據的代碼完整的聲明在一個方法中,我們不妨將此方法聲明同步的。

5.同步的方式,解決了線程的安全問題。---好處

操作同步代碼時,只能有一個線程參與,其他線程等待。相當於是一個單線程的過程,效率低。 ---局限性

關於同步方法的總結:

  1. 同步方法仍然涉及到同步監視器,只是不需要我們顯式的聲明。
  2. 非靜態的同步方法,同步監視器是:this;
    靜態的同步方法,同步監視器是:當前類本身

使用同步機制將單例模式中的懶漢式改寫為線程安全的

復習:

懶漢式:

public class SingletonTest2 {
	public static void main(String[] args) {
		
		Order order1 = Order.getInstance();
		Order order2 = Order.getInstance();
		
		System.out.println(order1 == order2);
		
	}
}


class Order{
	
	//1.私有化類的構造器
	private Order(){
		
	}
	
	//2.聲明當前類對象,沒有初始化
	//4.此對象也必須聲明為static的
	private static Order instance = null;
	
	//3.聲明public、static的返回當前類對象的方法
	public static Order getInstance(){
		
		if(instance == null){
			
			instance = new Order();
			
		}
		return instance;
	}
	
}

線程安全的懶漢式:

public class BankTest {

}

class Bank{

    private Bank(){}

    private static Bank instance = null;

    public static Bank getInstance(){
        //方式一:效率稍差
//        synchronized (Bank.class) {
//            if(instance == null){
//
//                instance = new Bank();
//            }
//            return instance;
//        }
        //方式二:效率更高
        if(instance == null){

            synchronized (Bank.class) {
                if(instance == null){

                    instance = new Bank();
                }

            }
        }
        return instance;
    }

}

死鎖

1.死鎖的理解:不同的線程分別占用對方需要的同步資源不放棄,都在等待對方放棄自己需要的同步資源,就形成了線程的死鎖

2.說明:

1)出現死鎖后,不會出現異常,不會出現提示,只是所有的線程都處於阻塞狀態,無法繼續

2)我們使用同步時,要避免出現死鎖。

解決線程安全問題的方式三:Lock鎖 --- JDK5.0新增

  1. synchronized 與 Lock的異同?
  • 相同:二者都可以解決線程安全問題
  • 不同:synchronized機制在執行完相應的同步代碼以后,自動的釋放同步監視器;Lock需要手動的啟動同步(lock()),同時結束同步也需要手動的實現(unlock())
  1. 優先使用順序:

Lock → 同步代碼塊(已經進入了方法體,分配了相應資源)→ 同步方法(在方法體之外)

例子:

import java.util.concurrent.locks.ReentrantLock;

class Window implements Runnable{

    private int ticket = 100;
    //1.實例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while(true){
            try{

                //2.調用鎖定方法lock()
                lock.lock();

                if(ticket > 0){

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + ":售票,票號為:" + ticket);
                    ticket--;
                }else{
                    break;
                }
            }finally {
                //3.調用解鎖方法:unlock()
                lock.unlock();
            }

        }
    }
}

public class LockTest {
    public static void main(String[] args) {
        Window w = new Window();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

總結

  1. 實例化ReentrantLock:private ReentrantLock lock = new ReentrantLock();
  2. 調用鎖定方法lock():lock.lock();
  3. 調用解鎖方法:unlock()lock.unlock();

創建線程的方式三:實現Callable接口。 --- JDK 5.0新增

如何理解實現Callable接口的方式創建多線程比實現Runnable接口創建多線程方式強大?

  1. call()可以有返回值的。
  2. call()可以拋出異常,被外面的操作捕獲,獲取異常的信息
  3. Callable是支持泛型的

例子:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

//1.創建一個實現Callable的實現類
class NumThread implements Callable{
    //2.實現call方法,將此線程需要執行的操作聲明在call()中
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            if(i % 2 == 0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}


public class ThreadNew {
    public static void main(String[] args) {
        //3.創建Callable接口實現類的對象
        NumThread numThread = new NumThread();
        //4.將此Callable接口實現類的對象作為傳遞到FutureTask構造器中,創建FutureTask的對象
        FutureTask futureTask = new FutureTask(numThread);
        //5.將FutureTask的對象作為參數傳遞到Thread類的構造器中,創建Thread對象,並調用start()
        new Thread(futureTask).start();

        try {
            //6.獲取Callable中call方法的返回值
            //get()返回值即為FutureTask構造器參數Callable實現類重寫的call()的返回值。
            Object sum = futureTask.get();
            System.out.println("總和為:" + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

}

總結

  1. 創建一個實現Callable的實現類class NumThread implements Callable{}
  2. 實現call方法,將此線程需要執行的操作聲明在call()中public Object call() throws Exception {}
  3. 創建Callable接口實現類的對象NumThread numThread = new NumThread()
  4. 將此Callable接口實現類的對象作為傳遞到FutureTask構造器中,創建FutureTask的對象FutureTask futureTask = new FutureTask(numThread);
  5. 將FutureTask的對象作為參數傳遞到Thread類的構造器中,創建Thread對象,並調用start()new Thread(futureTask).start()
  6. 獲取Callable中call方法的返回值,用get()返回值即為FutureTask構造器參數Callable實現類重寫的call()的返回值。Object sum = futureTask.get();

創建線程的方式四:使用線程池

好處:

  1. 提高響應速度(減少了創建新線程的時間)
  2. 降低資源消耗(重復利用線程池中線程,不需要每次都創建)
  3. 便於線程管理
    • corePoolSize:核心池的大小
    • maximumPoolSize:最大線程數
    • keepAliveTime:線程沒有任務時最多保持多長時間后會終止

代碼如下:

class NumberThread implements Runnable{

    @Override
    public void run() {
        for(int i = 0;i <= 100;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

class NumberThread1 implements Runnable{

    @Override
    public void run() {
        for(int i = 0;i <= 100;i++){
            if(i % 2 != 0){
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

public class ThreadPool {

    public static void main(String[] args) {
        //1. 提供指定線程數量的線程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
        //設置線程池的屬性
//        System.out.println(service.getClass());
//        service1.setCorePoolSize(15);
//        service1.setKeepAliveTime();


        //2.執行指定的線程的操作。需要提供實現Runnable接口或Callable接口實現類的對象
        service.execute(new NumberThread());//適合適用於Runnable
        service.execute(new NumberThread1());//適合適用於Runnable

//        service.submit(Callable callable);//適合使用於Callable
        //3.關閉連接池
        service.shutdown();
    }

}

總結:

  1. 提供指定線程數量的線程池ExecutorService service = Executors.newFixedThreadPool(10);
  2. 執行指定的線程的操作。需要提供實現Runnable接口或Callable接口實現類的對象service.execute(new NumberThread());對於Callable接口,它的語句是service.submit(Callable callable)
  3. 關閉連接池service.shutdown();

day21

String簡介

String:字符串,使用一對""引起來表示。

1.String聲明為final的,不可被繼承

2.String實現了Serializable接口:表示字符串是支持序列化的。實現了Comparable接口:表示String可以比較大小

3.String內部定義了final char[] value用於存儲字符串數據

4.String:代表不可變的字符序列。簡稱:不可變性。

體現:
1.當對字符串重新賦值時,需要重寫指定內存區域賦值,不能使用原有的value進行賦值。
2.當對現有的字符串進行連接操作時,也需要重新指定內存區域賦值,不能使用原有的value進行賦值。
3.當調用String的replace()方法修改指定字符或字符串時,也需要重新指定內存區域賦值,不能使用原有的value進行賦值。

5.通過字面量的方式(區別於new)給一個字符串賦值,此時的字符串值聲明在字符串常量池中。

6.字符串常量池中是不會存儲相同內容的字符串的。

String的實例化方式:

方式一:通過字面量定義的方式
方式二:通過new + 構造器的方式

面試題:String s = new String("abc");方式創建對象,在內存中創建了幾個對象?

兩個:一個是堆空間中new結構,另一個是char[]對應的常量池中的數據:"abc"

字符串拼接注意點

  1. 常量與常量的拼接結果在常量池。且常量池中不會存在相同內容的常量。
  2. 只要其中有一個是變量,結果就在堆中。
  3. 如果拼接的結果調用intern()方法,返回值就在常量池中

String 與基本數據類型、包裝類之間的轉換。

String --> 基本數據類型、包裝類:調用包裝類的靜態方法:parseXxx(str)
基本數據類型、包裝類 --> String:調用String重載的valueOf(xxx)

String 與 char[]之間的轉換

String --> char[]:調用String的toCharArray()
char[] --> String:調用String的構造器

String 與 byte[]之間的轉換

編碼:String --> byte[]:調用String的getBytes()
解碼:byte[] --> String:調用String的構造器

編碼:字符串 -->字節  (看得懂 --->看不懂的二進制數據)
解碼:編碼的逆過程,字節 --> 字符串 (看不懂的二進制數據 ---> 看得懂)

說明:解碼時,要求解碼使用的字符集必須與編碼時使用的字符集一致,否則會出現亂碼。

String內建函數

int length():返回字符串的長度: return value.length
char charAt(int index): 返回某索引處的字符return value[index]
boolean isEmpty():判斷是否是空字符串:return value.length == 0
String toLowerCase():使用默認語言環境,將 String 中的所有字符轉換為小寫
String toUpperCase():使用默認語言環境,將 String 中的所有字符轉換為大寫
String trim():返回字符串的副本,忽略前導空白和尾部空白
boolean equals(Object obj):比較字符串的內容是否相同
boolean equalsIgnoreCase(String anotherString):與equals方法類似,忽略大小寫
String concat(String str):將指定字符串連接到此字符串的結尾。 等價於用“+”
int compareTo(String anotherString):比較兩個字符串的大小
String substring(int beginIndex):返回一個新的字符串,它是此字符串的從beginIndex開始截取到最后的一個子字符串。
String substring(int beginIndex, int endIndex) :返回一個新字符串,它是此字符串從beginIndex開始截取到endIndex(不包含)的一個子字符串。

boolean endsWith(String suffix):測試此字符串是否以指定的后綴結束
boolean startsWith(String prefix):測試此字符串是否以指定的前綴開始
boolean startsWith(String prefix, int toffset):測試此字符串從指定索引開始的子字符串是否以指定前綴開始

boolean contains(CharSequence s):當且僅當此字符串包含指定的 char 值序列時,返回 true
int indexOf(String str):返回指定子字符串在此字符串中第一次出現處的索引
int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出現處的索引,從指定的索引開始
int lastIndexOf(String str):返回指定子字符串在此字符串中最右邊出現處的索引
int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出現處的索引,從指定的索引開始反向搜索

注:indexOf和lastIndexOf方法如果未找到都是返回-1

替換:
String replace(char oldChar, char newChar):返回一個新的字符串,它是通過用 newChar 替換此字符串中出現的所有 oldChar 得到的。
String replace(CharSequence target, CharSequence replacement):使用指定的字面值替換序列替換此字符串所有匹配字面值目標序列的子字符串。
String replaceAll(String regex, String replacement):使用給定的 replacement 替換此字符串所有匹配給定的正則表達式的子字符串。
String replaceFirst(String regex, String replacement):使用給定的 replacement 替換此字符串匹配給定的正則表達式的第一個子字符串。
匹配:
boolean matches(String regex):告知此字符串是否匹配給定的正則表達式。
切片:
String[] split(String regex):根據給定正則表達式的匹配拆分此字符串。
String[] split(String regex, int limit):根據匹配給定的正則表達式來拆分此字符串,最多不超過limit個,如果超過了,剩下的全部都放到最后一個元素中。

關於StringBuffer和StringBuilder的使用

String、StringBuffer、StringBuilder三者的異同?

String:不可變的字符序列;底層使用char[]存儲

StringBuffer:可變的字符序列;線程安全的,效率低;底層使用char[]存儲

StringBuilder:可變的字符序列;jdk5.0新增的,線程不安全的,效率高;底層使用char[]存儲

源碼分析:
String str = new String();//char[] value = new char[0];
String str1 = new String("abc");//char[] value = new char[]{'a','b','c'};

StringBuffer sb1 = new StringBuffer();//char[] value = new char[16];底層創建了一個長度是16的數組。
System.out.println(sb1.length());//
sb1.append('a');//value[0] = 'a';
sb1.append('b');//value[1] = 'b';

StringBuffer sb2 = new StringBuffer("abc");//char[] value = new char["abc".length() + 16];

//問題1. System.out.println(sb2.length());//3,里面只有3個元素
//問題2. 擴容問題:如果要添加的數據底層數組盛不下了,那就需要擴容底層的數組。
         默認情況下,擴容為原來容量的2倍 + 2,同時將原有數組中的元素復制到新的數組中。

        指導意義:開發中建議大家使用:StringBuffer(int capacity) 或 StringBuilder(int capacity)

StringBuffer的常用方法

StringBuffer append(xxx):提供了很多的append()方法,用於進行字符串拼接
StringBuffer delete(int start,int end):刪除指定位置的內容
StringBuffer replace(int start, int end, String str):把[start,end)位置替換為str
StringBuffer insert(int offset, xxx):在指定位置插入xxx
StringBuffer reverse() :把當前字符序列逆轉
public int indexOf(String str)
public String substring(int start,int end):返回一個從start開始到end索引結束的左閉右開區間的子字符串
public int length()
public char charAt(int n )
public void setCharAt(int n ,char ch)

總結:

增:append(xxx)

刪:delete(int start,int end)

改:setCharAt(int n ,char ch) / replace(int start, int end, String str)

查:charAt(int n )

插:insert(int offset, xxx)

長度:length();

遍歷:for() + charAt() / toString()

對比String、StringBuffer、StringBuilder三者的效率:

從高到低排列:StringBuilder > StringBuffer > String

JDK 8之前日期和時間的API(1)

System類中的currentTimeMillis():返回當前時間與1970年1月1日0時0分0秒之間以毫秒為單位的時間差。稱為時間戳。

java.util.Date類

|---java.sql.Date類

1.兩個構造器的使用

構造器一:Date():創建一個對應當前時間的Date對象

構造器二:創建指定毫秒數的Date對象

2.兩個方法的使用

toString():顯示當前的年、月、日、時、分、秒

getTime():獲取當前Date對象對應的毫秒數。(時間戳)

3.java.sql.Date對應着數據庫中的日期類型的變量

如何實例化

如何將java.util.Date對象轉換為java.sql.Date對象

//如何將java.util.Date對象轉換為java.sql.Date對象
        //情況一:
//        Date date4 = new java.sql.Date(2343243242323L);
//        java.sql.Date date5 = (java.sql.Date) date4;
        //情況二:
        Date date6 = new Date();
        java.sql.Date date7 = new java.sql.Date(date6.getTime());

day22

JDK 8之前日期和時間的API(2)

一、SimpleDateFormat的使用:SimpleDateFormat對日期Date類的格式化和解析

1.兩個操作:

1.1 格式化:日期 --->字符串(format)

1.2 解析:格式化的逆過程,字符串 ---> 日期(parse,注意拋出異常throws ParseException)

2.SimpleDateFormat的實例化(按照指定的方式格式化和解析:調用帶參的構造器)

二、Calendar日歷類(抽象類)的使用

方式一:創建其子類(GregorianCalendar)的對象

System.out.println(calendar.getClass());

方式二:調用其靜態方法getInstance()

Calendar calendar = Calendar.getInstance();

常用方法:

get()
set()
add()
getTime():日歷類---> Date
setTime():Date ---> 日歷類

jdk 8中日期時間API的測試

偏移量

年是從1900年開始的,月份是從0開始的

LocalDate、LocalTime、LocalDateTime 的使用

  1. LocalDateTime相較於LocalDate、LocalTime,使用頻率要高
  2. 類似於Calendar

now():獲取當前的日期(LocalDate)、時間(LocalTime)、日期+時間(LocalDateTime)

of():設置指定的年、月、日、時、分、秒。沒有偏移量

getXxx():獲取相關的屬性

getDayOfMonth()

getDayOfWeek()

getMinute()等

withXxx():體現不可變性,原來的數據不會改變。

withDayOfMonth 修改日期

withHour 修改小時

Instanct的使用

Instant:時間線上的一個瞬時點。這可能被用來記錄應用程序中的事件時間戳

now():獲取本初子午線對應的標准時間
atOffset():添加時間偏移量
toEpochMilli():獲取自1970年1月1日0時0分0秒(UTC)開始的毫秒數  ---> Date類的getTime()
ofEpochMilli():通過給定的毫秒數,獲取Instant實例  -->Date(long millis)

DateTimeFormatter:格式化或解析日期、時間

方式一:預定義的標准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME

DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;

方式二:本地化相關的格式。如:ofLocalizedDateTime()

FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT :適用於LocalDateTime

DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);

本地化相關的格式。如:ofLocalizedDate()

FormatStyle.FULL / FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT : 適用於LocalDate

DateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);

重點: 方式三:自定義的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)

DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");

CompareTest

一、說明:Java中的對象,正常情況下,只能進行比較:== 或 != 。不能使用 > 或 < 的

但是在開發場景中,我們需要對多個對象進行排序,言外之意,就需要比較對象的大小。

如何實現?使用兩個接口中的任何一個:Comparable 或 Comparator

二、Comparable接口與Comparator的使用的對比:

Comparable接口的方式一旦一定,保證Comparable接口實現類的對象在任何位置都可以比較大小。

Comparator接口屬於臨時性的比較。

Comparable接口的使用舉例: 自然排序

  1. 像String、包裝類等實現了Comparable接口,重寫了compareTo(obj)方法,給出了比較兩個對象大小的方式。

  2. 像String、包裝類重寫compareTo()方法以后,進行了從小到大的排列

  3. 重寫compareTo(obj)的規則:

    如果當前對象this大於形參對象obj,則返回正整數,

    如果當前對象this小於形參對象obj,則返回負整數,

    如果當前對象this等於形參對象obj,則返回零。

  4. 對於自定義類來說,如果需要排序,我們可以讓自定義類實現Comparable接口,重寫compareTo(obj)方法。在compareTo(obj)方法中指明如何排序

Comparator接口的使用:定制排序

  1. 背景:
    當元素的類型沒有實現java.lang.Comparable接口而又不方便修改代碼,或者實現了java.lang.Comparable接口的排序規則不適合當前的操作,那么可以考慮使用 Comparator 的對象來排序.
  2. 重寫compare(Object o1,Object o2)方法,比較o1和o2的大小:

如果方法返回正整數,則表示o1大於o2;

如果返回0,表示相等;

返回負整數,表示o1小於o2。

day23

枚舉類

一、枚舉類的使用

  1. 枚舉類的理解:類的對象只有有限個,確定的。我們稱此類為枚舉類
  2. 當需要定義一組常量時,強烈建議使用枚舉類
  3. 如果枚舉類中只有一個對象,則可以作為單例模式的實現方式。

二、如何定義枚舉類

方式一:jdk5.0之前,自定義枚舉類

方式二:jdk5.0,可以使用enum關鍵字定義枚舉類

三、Enum類中的常用方法:

values()方法:返回枚舉類型的對象數組。該方法可以很方便地遍歷所有的枚舉值。

valueOf(String str):可以把一個字符串轉為對應的枚舉類對象。要求字符串必須是枚舉類對象的“名字”。如不是,會有運行時異常:IllegalArgumentException。

toString():返回當前枚舉類對象常量的名稱

四、使用enum關鍵字定義的枚舉類實現接口的情況

情況一:實現接口,在enum類中實現抽象方法

情況二:讓枚舉類的對象分別實現接口中的抽象方法

自定義枚舉類:

  1. 聲明對象的屬性:private final修飾
  2. 私有化類的構造器,並給對象屬性賦值
  3. 提供當前枚舉類的多個對象:public static final的
  4. 其他訴求,如獲取枚舉類對象的屬性(getXxx方法)或者提供toString()

使用enum關鍵字定義枚舉類
說明:定義的枚舉類默認繼承於java.lang.Enum類

  1. 提供當前枚舉類的對象,多個對象之間用","隔開,末尾對象";"結束
  2. 聲明對象的屬性:private final修飾
  3. 私有化類的構造器,並給對象屬性賦值
  4. 其他訴求,如獲取枚舉類對象的屬性(getXxx方法)或者提供toString()

注解的使用

  1. 理解Annotation:
    ① jdk 5.0 新增的功能
    ② Annotation 其實就是代碼里的特殊標記, 這些標記可以在編譯, 類加載, 運行時被讀取, 並執行相應的處理。通過使用 Annotation,程序員可以在不改變原有邏輯的情況下, 在源文件中嵌入一些補充信息。
    ③在JavaSE中,注解的使用目的比較簡單,例如標記過時的功能,忽略警告等。在JavaEE/Android中注解占據了更重要的角色,例如用來配置應用程序的任何切面,代替JavaEE舊版中所遺留的繁冗代碼和XML配置等。

  2. Annocation的使用示例

  • 示例一:生成文檔相關的注解
  • 示例二:在編譯時進行格式檢查(JDK內置的三個基本注解)

@Override: 限定重寫父類方法, 該注解只能用於方法
@Deprecated: 用於表示所修飾的元素(類, 方法等)已過時。通常是因為所修飾的結構危險或存在更好的選擇
@SuppressWarnings: 抑制編譯器警告

  • 示例三:跟蹤代碼依賴性,實現替代配置文件功能
  1. 如何自定義注解:參照@SuppressWarnings定義
    ① 注解聲明為:@interface
    ② 內部定義成員,通常使用value表示
    ③ 可以指定成員的默認值,使用default定義
    ④ 如果自定義注解沒有成員,表明是一個標識作用。
    如果注解有成員,在使用注解時,需要指明成員的值。
    自定義注解必須配上注解的信息處理流程(使用反射)才有意義。
    自定義注解通過都會指明兩個元注解:Retention、Target

  2. jdk 提供的4種元注解

元注解:對現有的注解進行解釋說明的注解

Retention:指定所修飾的 Annotation 的生命周期:SOURCE\CLASS(默認行為)\RUNTIME,只有聲明為RUNTIME生命周期的注解,才能通過反射獲取。
Target:用於指定被修飾的 Annotation 能用於修飾哪些程序元素

----------出現的頻率較低---------

Documented:表示所修飾的注解在被javadoc解析時,保留下來。
Inherited:被它修飾的 Annotation 將具有繼承性。

  1. 通過反射獲取注解信息 ---到反射內容時系統講解

  2. jdk 8 中注解的新特性:可重復注解、類型注解

6.1 可重復注解:
① 在MyAnnotation上聲明@Repeatable,成員值為MyAnnotations.class
② MyAnnotation的Target和Retention等元注解與MyAnnotations相同。

6.2 類型注解:
ElementType.TYPE_PARAMETER 表示該注解能寫在類型變量的聲明語句中(如:泛型聲明)。
ElementType.TYPE_USE 表示該注解能寫在使用類型的任何語句中。

集合框架的概述

1.集合、數組都是對多個數據進行存儲操作的結構,簡稱Java容器。
說明:此時的存儲,主要指的是內存層面的存儲,不涉及到持久化的存儲(.txt,.jpg,.avi,數據庫中)

2.1 數組在存儲多個數據方面的特點:

一旦初始化以后,其長度就確定了。
數組一旦定義好,其元素的類型也就確定了。我們也就只能操作指定類型的數據了。 比如:String[] arr;int[] arr1;Object[] arr2;

2.2 數組在存儲多個數據方面的缺點:

一旦初始化以后,其長度就不可修改。
數組中提供的方法非常有限,對於添加、刪除、插入數據等操作,非常不便,同時效率不高。
獲取數組中實際元素的個數的需求,數組沒有現成的屬性或方法可用
數組存儲數據的特點:有序、可重復。對於無序、不可重復的需求,不能滿足。

二、集合框架
|----Collection接口:單列集合,用來存儲一個一個的對象
  |----List接口:存儲有序的、可重復的數據。 -->“動態”數組
    |----ArrayList、LinkedList、Vector

  |----Set接口:存儲無序的、不可重復的數據 -->高中講的“集合”
    |----HashSet、LinkedHashSet、TreeSet

|----Map接口:雙列集合,用來存儲一對(key - value)一對的數據 -->高中函數:y = f(x)
    |----HashMap、LinkedHashMap、TreeMap、Hashtable、Properties

三、Collection接口中的方法的使用

  • add(Object e):將元素e添加到集合coll中
  • size():獲取添加的元素的個數
  • addAll(Collection coll1):將coll1集合中的元素添加到當前的集合中
  • clear():清空集合元素
  • isEmpty():判斷當前集合是否為空

day24

Collection接口中聲明的方法的測試

向Collection接口的實現類的對象中添加數據obj時,要求obj所在類要重寫equals().

  1. contains(Object obj):判斷當前集合中是否包含obj
  2. containsAll(Collection coll1):判斷形參coll1中的所有元素是否都存在於當前集合中。
  3. remove(Object obj):從當前集合中移除obj元素。
  4. removeAll(Collection coll1):差集:從當前集合中移除coll1中所有的元素。
  5. retainAll(Collection coll1):交集:獲取當前集合和coll1集合的交集,並返回給當前集合
  6. equals(Object obj):要想返回true,需要當前集合和形參集合的元素都相同。
  7. hashCode():返回當前對象的哈希值
  8. 集合 --->數組:toArray()(拓展:數組 --->集合:調用Arrays類的靜態方法asList())
  9. iterator():返回Iterator接口的實例,用於遍歷集合元素。

注意:

List arr1 = Arrays.asList(new int[]{123, 456});
System.out.println(arr1.size());//1

List arr2 = Arrays.asList(new Integer[]{123, 456});
System.out.println(arr2.size());//2

集合元素的遍歷操作,使用迭代器Iterator接口

1.內部的方法:hasNext() 和 next()

推薦的方式:

//hasNext():判斷是否還有下一個元素
while(iterator.hasNext()){
    //next():①指針下移 ②將下移以后集合位置上的元素返回
    System.out.println(iterator.next());
}

2.集合對象每次調用iterator()方法都得到一個全新的迭代器對象,默認游標都在集合的第一個元素之前。

注意兩種錯誤方式:

//錯誤方式一:
Iterator iterator = coll.iterator();
while((iterator.next()) != null){
    System.out.println(iterator.next());
}

解析:while中的iterator.next()已經返回來集合中第一個數,接着輸出語句中的iterator.next()打印了集合中的第二個元素。然后又回到while中的iterator.next(),如此往復,知道遍歷完畢。打印的結果都是隔一個元素打印,並且最后會報錯,越界。

//錯誤方式二:
//集合對象每次調用iterator()方法都得到一個全新的迭代器對象,默認游標都在集合的第一個元素之前。
while (coll.iterator().hasNext()){
    System.out.println(coll.iterator().next());
}

解析:每次調用iterator()方法都得到一個全新的迭代器對象,每次都是打印集合第一個元素,並且是死循環。

3.內部定義了remove(),可以在遍歷的時候,刪除集合中的元素。此方法不同於集合直接調用remove()

jdk 5.0 新增了foreach循環,用於遍歷集合、數組

public void test1(){
    Collection coll = new ArrayList();
    coll.add(123);
    coll.add(456);
    coll.add(new Person("Jerry",20));
    coll.add(new String("Tom"));
    coll.add(false);

    //for(集合元素的類型 局部變量 : 集合對象)
    //內部仍然調用了迭代器。
    for(Object obj : coll){
        System.out.println(obj);
    }
}

List接口

1.List接口框架

|----Collection接口:單列集合,用來存儲一個一個的對象
  |----List接口:存儲有序的、可重復的數據。 -->“動態”數組,替換原有的數組
    |----ArrayList:作為List接口的主要實現類;線程不安全的,效率高;底層使用Object[] elementData存儲
    |----LinkedList:對於頻繁的插入、刪除操作,使用此類效率比ArrayList高;底層使用雙向鏈表存儲
    |----Vector:作為List接口的古老實現類;線程安全的,效率低;底層使用Object[] elementData存儲

2.ArrayList的源碼分析:

2.1 jdk 7情況下
ArrayList list = new ArrayList();//底層創建了長度是10的Object[]數組elementData
list.add(123);//elementData[0] = new Integer(123);
...
list.add(11);//如果此次的添加導致底層elementData數組容量不夠,則擴容。
默認情況下,擴容為原來的容量的1.5倍,同時需要將原有數組中的數據復制到新的數組中。

結論:建議開發中使用帶參的構造器:ArrayList list = new ArrayList(int capacity)

2.2 jdk 8中ArrayList的變化:
ArrayList list = new ArrayList();//底層Object[] elementData初始化為{}.並沒有創建長度為10的數組

list.add(123);//第一次調用add()時,底層才創建了長度10的數組,並將數據123添加到elementData[0],后續的添加和擴容操作與jdk 7 無異。

2.3 小結:jdk7中的ArrayList的對象的創建類似於單例的餓漢式,而jdk8中的ArrayList的對象的創建類似於單例的懶漢式,延遲了數組的創建,節省內存。

3.LinkedList的源碼分析:
LinkedList list = new LinkedList(); 內部聲明了Node類型的first和last屬性,默認值為null

list.add(123);//將123封裝到Node中,創建了Node對象。

其中,Node定義為:體現了LinkedList的雙向鏈表的說法

private static class Node<E> {
     E item;
     Node<E> next;
     Node<E> prev;

     Node(Node<E> prev, E element, Node<E> next) {
     this.item = element;
     this.next = next;
     this.prev = prev;
     }
 }

4.Vector的源碼分析:jdk7和jdk8中通過Vector()構造器創建對象時,底層都創建了長度為10的數組。在擴容方面,默認擴容為原來的數組長度的2倍。

面試題:ArrayList、LinkedList、Vector三者的異同?
同:三個類都是實現了List接口,存儲數據的特點相同:存儲有序的、可重復的數據
不同:見上

5.List接口中的常用方法

void add(int index, Object ele):在index位置插入ele元素
boolean addAll(int index, Collection eles):從index位置開始將eles中的所有元素添加進來
Object get(int index):獲取指定index位置的元素
int indexOf(Object obj):返回obj在集合中首次出現的位置。如果不存在,返回-1.
int lastIndexOf(Object obj):返回obj在當前集合中末次出現的位置。如果不存在,返回-1.
Object remove(int index):移除指定index位置的元素,並返回此元素
Object set(int index, Object ele):設置指定index位置的元素為ele
List subList(int fromIndex, int toIndex):返回從fromIndex到toIndex位置的子集合

總結:常用方法
增:add(Object obj)
刪:remove(int index) / remove(Object obj)
改:set(int index, Object ele)
查:get(int index)
插:add(int index, Object ele)
長度:size()
遍歷:
① Iterator迭代器方式
② 增強for循環
③ 普通的循環

注意:區分List中remove(int index)和remove(Object obj)

public void testListRemove() {
    List list = new ArrayList();
    list.add(1);
    list.add(2);
    list.add(3);
    updateList(list);
    System.out.println(list);//
}

private void updateList(List list) {
    //list.remove(2);
    list.remove(new Integer(2));
}

分析:第一條語句list.remove(2);中2是index,所以移除的是索引為2的元素,它是list的remove(int index);
第二條語句list.remove(new Integer(2));中的2是對象,所以移除的是內容為2的元素,它是list的remove(Object obj)。

set接口

  1. Set接口的框架:

|----Collection接口:單列集合,用來存儲一個一個的對象
  |----Set接口:存儲無序的、不可重復的數據 -->高中講的“集合”
    |----HashSet:作為Set接口的主要實現類;線程不安全的;可以存儲null值
    |----LinkedHashSet:作為HashSet的子類;遍歷其內部數據時,可以按照添加的順序遍歷,對於頻繁的遍歷操作,LinkedHashSet效率高於HashSet.
    |----TreeSet:可以按照添加對象的指定屬性,進行排序。

一、Set:存儲無序的、不可重復的數據
以HashSet為例說明:
1.無序性:不等於隨機性。存儲的數據在底層數組中並非按照數組索引的順序添加,而是根據數據的哈希值決定的。

2.不可重復性:保證添加的元素按照equals()判斷時,不能返回true.即:相同的元素只能添加一個。

二、添加元素的過程:以HashSet為例:
我們向HashSet中添加元素a,首先調用元素a所在類的hashCode()方法,計算元素a的哈希值,此哈希值接着通過某種算法計算出在HashSet底層數組中的存放位置(即為:索引位置),判斷數組此位置上是否已經有元素:
如果此位置上沒有其他元素,則元素a添加成功。 --->情況1
如果此位置上有其他元素b(或以鏈表形式存在的多個元素),則比較元素a與元素b的hash值:
  如果hash值不相同,則元素a添加成功。--->情況2
  如果hash值相同,進而需要調用元素a所在類的equals()方法:
    equals()返回true,元素a添加失敗
    equals()返回false,則元素a添加成功。--->情況2

對於添加成功的情況2和情況3而言:元素a 與已經存在指定索引位置上數據以鏈表的方式存儲。
jdk 7 :元素a放到數組中,指向原來的元素。
jdk 8 :原來的元素在數組中,指向元素a
總結:七上八下

HashSet底層:數組+鏈表的結構。


  1. Set接口中沒有額外定義新的方法,使用的都是Collection中聲明過的方法。

  2. 要求:向Set(主要指:HashSet、LinkedHashSet)中添加的數據,其所在的類一定要重寫hashCode()和equals()
    要求:重寫的hashCode()和equals()盡可能保持一致性:相等的對象必須具有相等的散列碼
    重寫兩個方法的小技巧:對象中用作 equals() 方法比較的 Field,都應該用來計算 hashCode 值。

TreeSet

  1. 向TreeSet中添加的數據,要求是相同類的對象。
  2. 兩種排序方式:自然排序(實現Comparable接口) 和 定制排序(Comparator)
  3. 自然排序中,比較兩個對象是否相同的標准為:compareTo()返回0.不再是equals().
  4. 定制排序中,比較兩個對象是否相同的標准為:compare()返回0.不再是equals().


免責聲明!

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



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