尚硅谷Java——宋紅康筆記【day25-day29】


day25

Map接口

一、Map的實現類的結構:
|----Map:雙列數據,存儲key-value對的數據 ---類似於高中的函數:y = f(x)
   |----HashMap:作為Map的主要實現類;線程不安全的,效率高;存儲null的key和value
      |----LinkedHashMap:保證在遍歷map元素時,可以按照添加的順序實現遍歷。
         原因:在原有的HashMap底層結構基礎上,添加了一對指針,指向前一個和后一個元素。對於頻繁的遍歷操作,此類執行效率高於HashMap。
   |----TreeMap:保證按照添加的key-value對進行排序,實現排序遍歷。此時考慮key的自然排序或定制排序,底層使用紅黑樹
   |----Hashtable:作為古老的實現類;線程安全的,效率低;不能存儲null的key和value
      |----Properties:常用來處理配置文件。key和value都是String類型

HashMap的底層:數組+鏈表 (jdk7及之前)
        數組+鏈表+紅黑樹 (jdk 8)

面試題:

  1. HashMap的底層實現原理?
  2. HashMap 和 Hashtable的異同?
  3. CurrentHashMap 與 Hashtable的異同?(暫時不講)

二、Map結構的理解:

Map中的key:無序的、不可重復的,使用Set存儲所有的key ---> key所在的類要重寫equals()和hashCode() (以HashMap為例)

Map中的value:無序的、可重復的,使用Collection存儲所有的value --->value所在的類要重寫equals()

一個鍵值對:key-value構成了一個Entry對象。

Map中的entry:無序的、不可重復的,使用Set存儲所有的entry

三、HashMap的底層實現原理?以jdk7為例說明:

HashMap map = new HashMap():

在實例化以后,底層創建了長度是16的一維數組Entry[] table。

...可能已經執行過多次put... ---> map.put(key1,value1):

首先,調用key1所在類的hashCode()計算key1哈希值,此哈希值經過某種算法計算以后,得到在Entry數組中的存放位置。

如果此位置上的數據為空,此時的key1-value1添加成功。 ----情況1

如果此位置上的數據不為空,(意味着此位置上存在一個或多個數據(以鏈表形式存在)),比較key1和已經存在的一個或多個數據的哈希值:

   如果key1的哈希值與已經存在的數據的哈希值都不相同,此時key1-value1添加成功。----情況2

   如果key1的哈希值和已經存在的某一個數據(key2-value2)的哈希值相同,繼續比較:調用key1所在類的equals(key2)方法,比較:

      如果equals()返回false:此時key1-value1添加成功。----情況3

      如果equals()返回true:使用value1替換value2。

補充:關於情況2和情況3:此時key1-value1和原來的數據以鏈表的方式存儲。

在不斷的添加過程中,會涉及到擴容問題,當超出臨界值(且要存放的位置非空)時,擴容。默認的擴容方式:擴容為原來容量的2倍,並將原有的數據復制過來。

jdk8 相較於jdk7在底層實現方面的不同:

  1. new HashMap():底層沒有創建一個長度為16的數組
  2. jdk 8底層的數組是:Node[],而非Entry[]
  3. 首次調用put()方法時,底層創建長度為16的數組
  4. jdk7底層結構只有:數組+鏈表。jdk8中底層結構:數組+鏈表+紅黑樹。
       4.1 形成鏈表時,七上八下(jdk7:新的元素指向舊的元素。jdk8:舊的元素指向新的元素)
       4.2 當數組的某一個索引位置上的元素以鏈表形式存在的數據個數 > 8 且當前數組的長度 > 64時,此時此索引位置上的所數據改為使用紅黑樹存儲。

DEFAULT_INITIAL_CAPACITY : HashMap的默認容量,16
DEFAULT_LOAD_FACTOR:HashMap的默認加載因子:0.75
threshold:擴容的臨界值,=容量 * 填充因子:16 * 0.75 => 12
TREEIFY_THRESHOLD:Bucket中鏈表長度大於該默認值,轉化為紅黑樹:8
MIN_TREEIFY_CAPACITY:桶中的Node被樹化時最小的hash表容量:64

四、LinkedHashMap的底層實現原理(了解)

源碼中:

static class Entry<K,V> extends HashMap.Node<K,V> {
     Entry<K,V> before, after;//能夠記錄添加的元素的先后順序
     Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
     }
 }

五、Map中定義的方法:

添加、刪除、修改操作:

Object put(Object key,Object value):將指定key-value添加到(或修改)當前map對象中
void putAll(Map m):將m中的所有key-value對存放到當前map中
Object remove(Object key):移除指定key的key-value對,並返回value
void clear():清空當前map中的所有數據

元素查詢的操作:

Object get(Object key):獲取指定key對應的value
boolean containsKey(Object key):是否包含指定的key
boolean containsValue(Object value):是否包含指定的value
int size():返回map中key-value對的個數
boolean isEmpty():判斷當前map是否為空
boolean equals(Object obj):判斷當前map和參數對象obj是否相等

元視圖操作的方法:

Set keySet():返回所有key構成的Set集合
Collection values():返回所有value構成的Collection集合
Set entrySet():返回所有key-value對構成的Set集合

①遍歷所有的key集

//遍歷所有的key集:keySet()
Set set = map.keySet();
Iterator iterator = set.iterator();
while(iterator.hasNext()){
    System.out.println(iterator.next());
}

②遍歷所有的value集

//遍歷所有的value集: values()
Collection values = map.values();
for(Object obj : values){
    System.out.println(obj);
}

③遍歷所有的key-value

//遍歷所有的key-value
//方式一:entrySet()
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while(iterator1.hasNext()){
    Object obj = iterator1.next();
    //entrySet集合中的元素都是entry
    Map.Entry entry = (Map.Entry) obj;
    System.out.println(entry.getKey() + "------>" + entry.getValue());
}
System.out.println();

//方式二:
Set keySet = map.keySet();
Iterator iterator2 = keySet.iterator();
while(iterator2.hasNext()){
Object key = iterator2.next();
Object value = map.get(key);
System.out.println(key + "======" + value);
}

總結:常用方法:

添加:put(Object key,Object value)

刪除:remove(Object key)

修改:put(Object key,Object value)

查詢:get(Object key)

長度:size()

遍歷 :keySet() / values() / entrySet()

TreeSet

向TreeMap中添加key-value,要求key必須是由同一個類創建的對象

因為要按照key進行排序:自然排序 、定制排序

TreeSet排序例子

import org.junit.Test;

import java.util.*;

public class TreeMapTest {

//自然排序
@Test
public void test1(){
    TreeMap map = new TreeMap();
    User u1 = new User(&quot;Tom&quot;,23);
    User u2 = new User(&quot;Jerry&quot;,32);
    User u3 = new User(&quot;Jack&quot;,20);
    User u4 = new User(&quot;Rose&quot;,18);

    map.put(u1,98);
    map.put(u2,89);
    map.put(u3,76);
    map.put(u4,100);

    Set entrySet = map.entrySet();
    Iterator iterator1 = entrySet.iterator();
    while (iterator1.hasNext()){
        Object obj = iterator1.next();
        Map.Entry entry = (Map.Entry) obj;
        System.out.println(entry.getKey() + &quot;----&gt;&quot; + entry.getValue());

    }
}

//定制排序
@Test
public void test2(){
    TreeMap map = new TreeMap(new Comparator() {
        @Override
        public int compare(Object o1, Object o2) {
            if(o1 instanceof User &amp;&amp; o2 instanceof User){
                User u1 = (User)o1;
                User u2 = (User)o2;
                return Integer.compare(u1.getAge(),u2.getAge());
            }
            throw new RuntimeException(&quot;輸入的類型不匹配!&quot;);
        }
    });
    User u1 = new User(&quot;Tom&quot;,23);
    User u2 = new User(&quot;Jerry&quot;,32);
    User u3 = new User(&quot;Jack&quot;,20);
    User u4 = new User(&quot;Rose&quot;,18);

    map.put(u1,98);
    map.put(u2,89);
    map.put(u3,76);
    map.put(u4,100);

    Set entrySet = map.entrySet();
    Iterator iterator1 = entrySet.iterator();
    while (iterator1.hasNext()){
        Object obj = iterator1.next();
        Map.Entry entry = (Map.Entry) obj;
        System.out.println(entry.getKey() + &quot;----&gt;&quot; + entry.getValue());

    }
}

}

Collections:操作Collection、Map的工具類

reverse(List):反轉 List 中元素的順序
shuffle(List):對 List 集合元素進行隨機排序
sort(List):根據元素的自然順序對指定 List 集合元素按升序排序
sort(List,Comparator):根據指定的 Comparator 產生的順序對 List 集合元素進行排序
swap(List,int, int):將指定 list 集合中的 i 處元素和 j 處元素進行交換

Object max(Collection):根據元素的自然順序,返回給定集合中的最大元素
Object max(Collection,Comparator):根據 Comparator 指定的順序,返回給定集合中的最大元素
Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object):返回指定集合中指定元素的出現次數
void copy(List dest,List src):將src中的內容復制到dest中
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替換 List 對象的所有舊值

其中要注意copy函數的使用:

//報異常:IndexOutOfBoundsException("Source does not fit in dest")
List dest = new ArrayList();
Collections.copy(dest,list);

正確的用法:

List dest = Arrays.asList(new Object[list.size()]);
System.out.println(dest.size());//list.size();
Collections.copy(dest,list);

System.out.println(dest);

Collections 類中提供了多個 synchronizedXxx() 方法,該方法可使將指定集合包裝成線程同步的集合,從而可以解決多線程並發訪問集合時的線程安全問題

//返回的list1即為線程安全的List
List list1 = Collections.synchronizedList(list);

Properties

Properties:常用來處理配置文件。key和value都是String類型

例子

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

public class PropertiesTest {

//Properties:常用來處理配置文件。key和value都是String類型
public static void main(String[] args)  {
    FileInputStream fis = null;
    try {
        Properties pros = new Properties();

        fis = new FileInputStream(&quot;jdbc.properties&quot;);
        pros.load(fis);//加載流對應的文件

        String name = pros.getProperty(&quot;name&quot;);
        String password = pros.getProperty(&quot;password&quot;);

        System.out.println(&quot;name = &quot; + name + &quot;, password = &quot; + password);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(fis != null){
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

}

}

day26

泛型的使用

1.jdk 5.0新增的特性

2.在集合中使用泛型:

以ArrayList為例:

@Test
public void test1(){
    ArrayList<Integer> list = new ArrayList<>();
list.add(78);
list.add(87);
list.add(99);
list.add(65);

//list.add(&quot;Tom&quot;);

Iterator&lt;Integer&gt; iterator = list.iterator();
while (iterator.hasNext()){
    System.out.println(iterator.next());
}

}

以HashSet為例

@Test
public void test2(){
Map&lt;String, Integer&gt; map = new HashMap&lt;&gt;();

map.put(&quot;Tom&quot;,87);
map.put(&quot;Jerry&quot;,87);
map.put(&quot;Jack&quot;,67);

//map.put(123, &quot;kkk&quot;);

//泛型的嵌套
Set&lt;Map.Entry&lt;String, Integer&gt;&gt; entry = map.entrySet();
Iterator&lt;Map.Entry&lt;String, Integer&gt;&gt; iterator = entry.iterator();
while(iterator.hasNext()){
    Map.Entry&lt;String, Integer&gt; e = iterator.next();
    String key = e.getKey();
    Integer value = e.getValue();
    System.out.println(key + &quot;-----&gt;&quot; + value);
}

}

總結:
① 集合接口或集合類在jdk5.0時都修改為帶泛型的結構。
② 在實例化集合類時,可以指明具體的泛型類型
③ 指明完以后,在集合類或接口中凡是定義類或接口時,內部結構(比如:方法、構造器、屬性等)使用到類的泛型的位置,都指定為實例化的泛型類型。
比如:add(E e) --->實例化以后:add(Integer e)
④ 注意點:泛型的類型必須是類,不能是基本數據類型。需要用到基本數據類型的位置,拿包裝類替換
⑤ 如果實例化時,沒有指明泛型的類型。默認類型為java.lang.Object類型。

3.如何自定義泛型結構:泛型類、泛型接口;泛型方法

如果定義了泛型類,實例化沒有指明類的泛型,則認為此泛型類型為Object類型,如果大家定義了類是帶泛型的,建議在實例化時要指明類的泛型。

定義一個繼承了Order的子類,它的類型為

public class SubOrder extends Order<Integer> {
public static &lt;E&gt; List&lt;E&gt; copyFromArrayToList(E[] arr){
    ArrayList&lt;E&gt; list = new ArrayList&lt;&gt;();

    for (E e: arr){
        list.add(e);
    }

    return list;
}

}

那么,由於子類在繼承帶泛型的父類時,指明了泛型類型。則實例化子類對象時,不再需要指明泛型。

SubOrder sub1 = new SubOrder();
sub1.setOrderT(1122);

如果一個子類在繼承時未聲明類型:

public class SubOrder1<T> extends Order<T> {
}

聲明時:

SubOrder1<String> sub2 = new SubOrder1<>();
sub2.setOrderT("order2......");

泛型不同的引用不能相互賦值。

ArrayList<String> list1 = null;
ArrayList<Integer> list2 = new ArrayList<>();
//泛型不同的引用不能相互賦值。
//list1 = list2;

說明:
1. 泛型類可能有多個參數,此時應將多個參數一起放在尖括號內。比如:< E1,E2,E3>
2. 泛型類的構造器如下 public GenericClass(){} 。而下面是錯誤的public GenericClass<E>(){} 3. 實例化后,操作原來泛型位置的結構必須與指定的泛型類型一致。
4. 泛型不同的引用不能相互賦值。盡管在編譯時 ArrayList 和 ArrayList 是兩種類型,但是,在運行時只有一個 ArrayList 被加載到 JVM 中。
5. 泛型如果不指定,將被擦除,泛型對應的類型均按照 Object 處理,但不等價於Object。 經驗: 泛型要使用一路都用。要不用,一路都不要用。
6. 如果泛型結構是一個接口或抽象類,則不可創建泛型類的對象 。
7. jdk1.7,泛型的簡化 操作 ArrayList<Fruit> flist = new ArrayList<>();
8. 泛型的指定中不能使用基本數據類型,可以使用包裝類替換。
9. 在類接口上聲明的泛型,在本類或本接口中即代表某種類型,可以作為非靜態屬性的類型、非靜態方法的參數類型、非靜態方法的返回值類型。 但在靜態方法中不能使用類的泛型。
10. 異常類不能是泛型的
11. 不能使用 new E[] 。但是可以 E[] elements = (E[])new Object[capacity];
參考:ArrayList 源碼中聲明: Object[] elementData 而非泛型參數類型數組。
12. 父類有泛型,子類可以選擇保留泛型也可以選擇指定泛型類型:

子類不保留父類的泛型:按需實現

  沒有類型 擦除

  具體類型

子類保留父類的泛型:泛型子類

  全部保留

  部分保留

結論:子類必須是“富二代”,子類除了指定或保留父類的泛型,還可以增加自 己的泛型

泛型方法

泛型方法的格式: [訪問權限] <泛型> 返回類型 方法名 ([泛型標識 參數名稱]) 拋出的異常

泛型在繼承方面的體現

雖然類A是類B的父類,但是G<A>和G<B>二者不具備子父類關系,二者是並列關系。

補充:類A是類B的父類,A<G> 是 B<G> 的父類

通配符的使用

通配符:?

類A是類B的父類,G<A>和G<B>是沒有關系的,二者共同的父類是:G<?>

@Test
public void test1(){
    List<Object> list1 = null;
    List<String> list2 = null;
List&lt;?&gt; list = null;

list = list1;
list = list2;

System.out.println(list1);
System.out.println(list2);


List&lt;String&gt; list3 = new ArrayList&lt;&gt;();
list3.add(&quot;ASA&quot;);
list3.add(&quot;DGA&quot;);
list3.add(&quot;Gesag&quot;);

//list.add(&quot;AA&quot;);
//list.add('?');

list.add(null);

//獲取(讀取):允許讀取數據,讀取的數據類型為Object。
Object o = list.get(0);
System.out.println(o);

分析:

因為?是兩者的共同父類,所以可以將list1和2傳遞給list

添加(寫入):對於List<?>就不能向其內部添加數據。除了null

讀取:允許讀取數據,讀取的數據類型為Object。

有限制條件的通配符的使用。

? extends A:
    G<? extends A> 可以作為G<A>和G<B>的父類,其中B是A的子類

? super A:
    G<? super A> 可以作為G<A>和G<B>的父類,其中B是A的父類

@Test
public void test4(){
List&lt;? extends Person&gt; list1 = null;
List&lt;? super Person&gt; list2 = null;

List&lt;Student&gt; list3 = new ArrayList&lt;Student&gt;();
List&lt;Person&gt; list4 = new ArrayList&lt;Person&gt;();
List&lt;Object&gt; list5 = new ArrayList&lt;Object&gt;();

list1 = list3;
list1 = list4;
//list1 = list5;

//list2 = list3;
list2 = list4;
list2 = list5;

//讀取數據:
list1 = list3;
Person p = list1.get(0);
//編譯不通過
//Student s = list1.get(0);

list2 = list4;
Object obj = list2.get(0);
////編譯不通過
//Person obj = list2.get(0);

//寫入數據:
//編譯不通過
//list1.add(new Student());

//編譯通過
list2.add(new Person());
list2.add(new Student());

}

File類的使用

  1. File類的一個對象,代表一個文件或一個文件目錄(俗稱:文件夾)
  2. File類聲明在java.io包下
  3. File類中涉及到關於文件或文件目錄的創建、刪除、重命名、修改時間、文件大小等方法,並未涉及到寫入或讀取文件內容的操作。如果需要讀取或寫入文件內容,必須使用IO流來完成。
  4. 后續File類的對象常會作為參數傳遞到流的構造器中,指明讀取或寫入的"終點".

如何創建File類的實例

File(String filePath)
File(String parentPath,String childPath)
File(File parentFile,String childPath)

相對路徑:相較於某個路徑下,指明的路徑。
絕對路徑:包含盤符在內的文件或文件目錄的路徑

路徑分隔符

 windows:\\
 unix:/
 */

public String getAbsolutePath():獲取絕對路徑
public String getPath() :獲取路徑
public String getName() :獲取名稱
public String getParent():獲取上層文件目錄路徑。若無,返回null
public long length() :獲取文件長度(即:字節數)。不能獲取目錄的長度。
public long lastModified() :獲取最后一次的修改時間,毫秒值

如下的兩個方法適用於文件目錄:
public String[] list() :獲取指定目錄下的所有文件或者文件目錄的名稱數組
public File[] listFiles() :獲取指定目錄下的所有文件或者文件目錄的File數組


public boolean renameTo(File dest):把文件重命名為指定的文件路徑
比如:file1.renameTo(file2)為例:
要想保證返回true,需要file1在硬盤中是存在的,且file2不能在硬盤中存在。


public boolean isDirectory():判斷是否是文件目錄
public boolean isFile() :判斷是否是文件
public boolean exists() :判斷是否存在
public boolean canRead() :判斷是否可讀
public boolean canWrite() :判斷是否可寫
public boolean isHidden() :判斷是否隱藏


創建硬盤中對應的文件或文件目錄

public boolean createNewFile() :創建文件。若文件存在,則不創建,返回false
public boolean mkdir() :創建文件目錄。如果此文件目錄存在,就不創建了。如果此文件目錄的上層目錄不存在,也不創建。
public boolean mkdirs() :創建文件目錄。如果此文件目錄存在,就不創建了。如果上層文件目錄不存在,一並創建

刪除磁盤中的文件或文件目錄

public boolean delete():刪除文件或者文件夾
刪除注意事項:Java中的刪除不走回收站。

要想刪除成功,io4文件目錄下不能有子目錄或文件

day27

一、流的分類:
1.操作數據單位:字節流、字符流
2.數據的流向:輸入流、輸出流
3.流的角色:節點流、處理流

抽象基類 字節流 字符流
輸入流 InputStream Reader
輸出流 OutputStream Writer

二、流的體系結構

抽象基類 節點流(或文件流) 緩沖流(處理流的一種)
InputStream FileInputStream(read(byte[] buffer)) BufferedInputStream (read(byte[] buffer))
OutputStream FileOutputStream(write(byte[] buffer,0,len) BufferedOutputStream (write(byte[] buffer,0,len) / flush()
Reader FileReader (read(char[] cbuf)) BufferedReader (read(char[] cbuf) / readLine())
Writer FileWriter (write(char[] cbuf,0,len) BufferedWriter (write(char[] cbuf,0,len) / flush()

說明點:
1. read()的理解:返回讀入的一個字符。如果達到文件末尾,返回-1
2. 異常的處理:為了保證流資源一定可以執行關閉操作。需要使用try-catch-finally處理
3. 讀入的文件一定要存在,否則就會報FileNotFoundException。

學會對read()操作升級:使用read的重載方法

示例代碼:

@Test
public void testFileReader1(){
    FileReader fr = null;
    try{
        //1.File類的實例化
        File file = new File("hello.txt");
        //2.FileReader流的實例化
        fr = new FileReader(file);
    //3. 讀入的操作
    //read(char[] cbuf):返回每次讀入cbuf數組中的字符的個數。如果大奧文件末尾,返回-1
    char[] cbuf = new char[5];
    int len;
    while((len = fr.read(cbuf)) != -1){
        String str = new String(cbuf, 0 ,len);
        System.out.println(str);
    }

}catch (IOException e){
    e.printStackTrace();
}finally {
    if(fr != null){
        try {
            fr.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

}

讀取文件的推薦方式:

while((len = fr.read(cbuf)) != -1){
    String str = new String(cbuf, 0 ,len);
    System.out.println(str);
}

從內存中寫出數據到硬盤的文件里。

說明:
1. 輸出操作,對應的File可以不存在的。並不會報異常
2. File對應的硬盤中的文件如果不存在,在輸出的過程中,會自動創建此文件。
File對應的硬盤中的文件如果存在:
如果流使用的構造器是:FileWriter(file,false) / FileWriter(file):對原有文件的覆蓋
如果流使用的構造器是:FileWriter(file,true):不會對原有文件覆蓋,而是在原有文件基礎上追加內容

IO流基本操作:
1.File類的實例化
2.FileReader流的實例化
3.讀入的操作
4.資源的關閉


read():返回讀入的一個字符。如果達到文件末尾,返回-1

不能使用字符流來處理圖片等字節數據

測試FileInputStream和FileOutputStream的使用

  1. 對於文本文件(.txt,.java,.c,.cpp),使用字符流處理
  2. 對於非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt,...),使用字節流處理

使用字節流FileInputStream處理文本文件,可能出現亂碼。

圖片復制操作示例代碼

/*
實現對圖片的復制操作
 */
@Test
public void testFileInputOutputStream()  {
    FileInputStream fis = null;
    FileOutputStream fos = null;
    try {
        //
        File srcFile = new File("愛情與友情.jpg");
        File destFile = new File("愛情與友情2.jpg");
    //
    fis = new FileInputStream(srcFile);
    fos = new FileOutputStream(destFile);

    //復制的過程
    byte[] buffer = new byte[5];
    int len;
    while((len = fis.read(buffer)) != -1){
        fos.write(buffer,0,len);
    }

} catch (IOException e) {
    e.printStackTrace();
} finally {
    if(fos != null){
        //
        try {
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    if(fis != null){
        try {
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

}

處理流之一:緩沖流的使用

1.緩沖流:

  • BufferedInputStream
  • BufferedOutputStream
  • BufferedReader
  • BufferedWriter

2.作用:提供流的讀取、寫入的速度

提高讀寫速度的原因:內部提供了一個緩沖區

3.處理流,就是“套接”在已有的流的基礎上。

說明:

① 關閉外層流的同時,內層流也會自動的進行關閉。關於內層流的關閉,我們可以省略.

② 使用String讀取數據:

//方式二:使用String
String data;
while((data = br.readLine()) != null){
    //方法一:
    //bw.write(data + "\n");//data中不包含換行符
    //方法二:
    bw.write(data);//data中不包含換行符
    bw.newLine();//提供換行的操作
}

復制文件的示例代碼:

//實現文件復制的方法
public void copyFileWithBuffered(String srcPath,String destPath){
    BufferedInputStream bis = null;
    BufferedOutputStream bos = null;
try {
    //1.造文件
    File srcFile = new File(srcPath);
    File destFile = new File(destPath);
    //2.造流
    //2.1 造節點流
    FileInputStream fis = new FileInputStream((srcFile));
    FileOutputStream fos = new FileOutputStream(destFile);
    //2.2 造緩沖流
    bis = new BufferedInputStream(fis);
    bos = new BufferedOutputStream(fos);

    //3.復制的細節:讀取、寫入
    byte[] buffer = new byte[1024];
    int len;
    while((len = bis.read(buffer)) != -1){
        bos.write(buffer,0,len);
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    //4.資源關閉
    //要求:先關閉外層的流,再關閉內層的流
    if(bos != null){
        try {
            bos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    if(bis != null){
        try {
            bis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    //說明:關閉外層流的同時,內層流也會自動的進行關閉。關於內層流的關閉,我們可以省略.
//fos.close();
//fis.close();
}

}

處理流之二:轉換流的使用

1.轉換流:屬於字符流

  • InputStreamReader:將一個字節的輸入流轉換為字符的輸入流
  • OutputStreamWriter:將一個字符的輸出流轉換為字節的輸出流

2.作用:提供字節流與字符流之間的轉換

//參數2指明了字符集,具體使用哪個字符集,取決於文件dbcp.txt保存時使用的字符集  
InputStreamReader isr = new InputStreamReader(fis,"UTF-8");

3.解碼:字節、字節數組 --->字符數組、字符串
編碼:字符數組、字符串 ---> 字節、字節數組

4.字符集

  • ASCII:美國標准信息交換碼。用一個字節的7位可以表示。
  • ISO8859-1:拉丁碼表。歐洲碼表。用一個字節的8位表示。
  • GB2312:中國的中文編碼表。最多兩個字節編碼所有字符
  • GBK:中國的中文編碼表升級,融合了更多的中文文字符號。最多兩個字節編碼
  • Unicode:國際標准碼,融合了目前人類使用的所有字符。為每個字符分配唯一的字符碼。所有的文字都用兩個字節來表示。
  • UTF-8:變長的編碼方式,可用1-4個字節來表示一個字符。

示例代碼:

/*
此時處理異常的話,仍然應該使用try-catch-finally

綜合使用InputStreamReader和OutputStreamWriter
*/
@Test
public void test2() throws Exception {
//1.造文件、造流
File file1 = new File("dbcp.txt");
File file2 = new File("dbcp_gbk.txt");

FileInputStream fis = new FileInputStream(file1);
FileOutputStream fos = new FileOutputStream(file2);

InputStreamReader isr = new InputStreamReader(fis,&quot;utf-8&quot;);
OutputStreamWriter osw = new OutputStreamWriter(fos,&quot;gbk&quot;);

//2.讀寫過程
char[] cbuf = new char[20];
int len;
while((len = isr.read(cbuf)) != -1){
    osw.write(cbuf,0,len);
}

//3.關閉資源
isr.close();
osw.close();

}

day28

Path

  1. jdk 7.0 時,引入了 Path、Paths、Files三個類。
  2. 此三個類聲明在:java.nio.file包下。
  3. Path可以看做是java.io.File類的升級版本。也可以表示文件或文件目錄,與平台無關
  4. 如何實例化Path:使用Paths.

    static Path get(String first, String … more) : 用於將多個字符串串連成路徑
    static Path get(URI uri): 返回指定uri對應的Path路徑

如何使用Paths實例化Path

示例代碼:

@Test
public void test1() {
    Path path1 = Paths.get("d:\\nio\\hello.txt");//new File(String filepath)
Path path2 = Paths.get(&quot;d:\\&quot;, &quot;nio\\hello.txt&quot;);//new File(String parent,String filename);

System.out.println(path1);
System.out.println(path2);

Path path3 = Paths.get(&quot;d:\\&quot;, &quot;nio&quot;);
System.out.println(path3);

}

Path中的常用方法

String toString() : 返回調用 Path 對象的字符串表示形式
boolean startsWith(String path) : 判斷是否以 path 路徑開始
boolean endsWith(String path) : 判斷是否以 path 路徑結束
boolean isAbsolute() : 判斷是否是絕對路徑
Path getParent():返回Path對象包含整個路徑,不包含 Path 對象指定的文件路徑
Path getRoot() :返回調用 Path 對象的根路徑
Path getFileName() : 返回與調用 Path 對象關聯的文件名
int getNameCount(): 返回Path 根目錄后面元素的數量
Path getName(int idx): 返回指定索引位置 idx 的路徑名稱
Path toAbsolutePath() : 作為絕對路徑返回調用 Path 對象
Path resolve(Path p) :合並兩個路徑,返回合並后的路徑對應的Path對象
File toFile(): 將Path轉化為File類的對象

File file = path1.toFile();//Path--->File的轉換
Path newPath = file.toPath();//File--->Path的轉換

Files工具類的使用:操作文件或目錄的工具類

以這兩條代碼為例,導入文件路徑:

Path path1 = Paths.get("d:\\nio", "hello.txt");
Path path2 = Paths.get("atguigu.txt");

Path copy(Path src, Path dest, CopyOption … how): 文件的復制
說明:要想復制成功,要求path1對應的物理上的文件存在。path1對應的文件沒有要求。

Files.copy(path1, path2, StandardCopyOption.REPLACE_EXISTING);

Path createDirectory(Path path, FileAttribute<?> … attr) : 創建一個目錄
說明:要想執行成功,要求path對應的物理上的文件目錄不存在。一旦存在,拋出異常。

Path path3 = Paths.get("d:\\nio\\nio1");
Files.createDirectory(path3);

Path createFile(Path path, FileAttribute<?> … arr) : 創建一個文件
說明:要想執行成功,要求path對應的物理上的文件不存在。一旦存在,拋出異常。

Path path4 = Paths.get("d:\\nio\\hi.txt");
Files.createFile(path4);

void delete(Path path) : 刪除一個文件/目錄,如果不存在,執行報錯

Files.delete(path4);

void deleteIfExists(Path path): Path對應的文件/目錄如果存在,執行刪除.如果不存在,正常執行結束

Files.deleteIfExists(path3);

Path move(Path src, Path dest, CopyOption…how): 將 src 移動到 dest 位置
說明:要想執行成功,src對應的物理上的文件需要存在,dest對應的文件沒有要求。

Files.move(path1, path2, StandardCopyOption.ATOMIC_MOVE);

long size(Path path): 返回 path 指定文件的大小

long size = Files.size(path2);
System.out.println(size);

boolean exists(Path path, LinkOption … opts): 判斷文件是否存在

System.out.println(Files.exists(path2, LinkOption.NOFOLLOW_LINKS));

boolean isDirectory(Path path, LinkOption … opts) : 判斷是否是目錄
說明:不要求此path對應的物理文件存在。

System.out.println(Files.isDirectory(path1, LinkOption.NOFOLLOW_LINKS));

boolean isRegularFile(Path path, LinkOption … opts) : 判斷是否是文件

boolean isHidden(Path path) : 判斷是否是隱藏文件
說明:要求此path對應的物理上的文件需要存在。才可判斷是否隱藏。否則,拋異常。

System.out.println(Files.isHidden(path1));

boolean isReadable(Path path) : 判斷文件是否可讀

System.out.println(Files.isReadable(path1));

boolean isWritable(Path path) : 判斷文件是否可寫

System.out.println(Files.isWritable(path1));

boolean notExists(Path path, LinkOption … opts): 判斷文件是否不存在

System.out.println(Files.notExists(path1, LinkOption.NOFOLLOW_LINKS));
  • StandardOpenOption.READ:表示對應的Channel是可讀的。
  • StandardOpenOption.WRITE:表示對應的Channel是可寫的。
  • StandardOpenOption.CREATE:如果要寫出的文件不存在,則創建。如果存在,忽略
  • StandardOpenOption.CREATE_NEW:如果要寫出的文件不存在,則創建。如果存在,拋異常

InputStream newInputStream(Path path, OpenOption…how):獲取 InputStream 對象

InputStream inputStream = Files.newInputStream(path1, StandardOpenOption.READ);

OutputStream newOutputStream(Path path, OpenOption…how): 獲取 OutputStream 對象

OutputStream outputStream = Files.newOutputStream(path1, StandardOpenOption.WRITE,StandardOpenOption.CREATE);

SeekableByteChannel newByteChannel(Path path, OpenOption…how) : 獲取與指定文件的連接,how 指定打開方式。

SeekableByteChannel channel = Files.newByteChannel(path1, StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);

DirectoryStream<Path> newDirectoryStream(Path path) : 打開 path 指定的目錄

Path path2 = Paths.get("e:\\teach");
DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path2);
Iterator<Path> iterator = directoryStream.iterator();
while(iterator.hasNext()){
    System.out.println(iterator.next());
}

對象流的使用

1.ObjectInputStream 和 ObjectOutputStream
2.作用:用於存儲和讀取基本數據類型數據或對象的處理流。它的強大之處就是可以把Java中的對象寫入到數據源中,也能把對象從數據源中還原回來。
3.要想一個java對象是可序列化的,需要滿足相應的要求。見Person.java

Person.java

import java.io.Serializable;

/**

  • Person需要滿足如下的要求,方可序列化

  • 1.需要實現接口:Serializable

  • 2.當前類提供一個全局常量:serialVersionUID

  • 3.除了當前Person類需要實現Serializable接口之外,還必須保證其內部所有屬性

  • 也必須是可序列化的。(默認情況下,基本數據類型可序列化)

  • 補充:ObjectOutputStream和ObjectInputStream不能序列化static和transient修飾的成員變量

  • @author shkstart

  • @create 2019 上午 10:38
    */
    public class Person implements Serializable{

    public static final long serialVersionUID = 475463534532L;

    private String name;
    private int age;
    private int id;
    private Account acct;

    public Person(String name, int age, int id) {
    this.name = name;
    this.age = age;
    this.id = id;
    }

    public Person(String name, int age, int id, Account acct) {
    this.name = name;
    this.age = age;
    this.id = id;
    this.acct = acct;
    }

    @Override
    public String toString() {
    return "Person{" +
    "name='" + name + ''' +
    ", age=" + age +
    ", id=" + id +
    ", acct=" + acct +
    '}';
    }

    public int getId() {
    return id;
    }

    public void setId(int id) {
    this.id = id;
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public int getAge() {
    return age;
    }

    public void setAge(int age) {
    this.age = age;
    }

    public Person(String name, int age) {

     this.name = name;
     this.age = age;
    

    }

    public Person() {

    }
    }

class Account implements Serializable{
public static final long serialVersionUID = 4754534532L;
private double balance;

@Override
public String toString() {
    return &quot;Account{&quot; +
            &quot;balance=&quot; + balance +
            '}';
}

public double getBalance() {
    return balance;
}

public void setBalance(double balance) {
    this.balance = balance;
}

public Account(double balance) {

    this.balance = balance;
}

}

4.序列化機制:
對象序列化機制允許把內存中的Java對象轉換成平台無關的二進制流,從而允許把這種二進制流持久地保存在磁盤上,或通過網絡將這種二進制流傳輸到另一個網絡節點。當其它程序獲取了這種二進制流,就可以恢復成原來的Java對象。

RandomAccessFile的使用

  1. RandomAccessFile直接繼承於java.lang.Object類,實現了DataInput和DataOutput接口
  2. RandomAccessFile既可以作為一個輸入流,又可以作為一個輸出流
  3. 如果RandomAccessFile作為輸出流時,寫出到的文件如果不存在,則在執行過程中自動創建。如果寫出到的文件存在,則會對原有文件內容進行覆蓋。(默認情況下,從頭覆蓋)
  4. 可以通過相關的操作,實現RandomAccessFile“插入”數據的效果

示例代碼:

 /*
使用RandomAccessFile實現數據的插入效果
 */
@Test
public void test3() throws IOException {
RandomAccessFile raf1 = new RandomAccessFile(&quot;hello.txt&quot;,&quot;rw&quot;);

raf1.seek(3);//將指針調到角標為3的位置
//保存指針3后面的所有數據到StringBuilder中
StringBuilder builder = new StringBuilder((int) new File(&quot;hello.txt&quot;).length());
byte[] buffer = new byte[20];
int len;
while((len = raf1.read(buffer)) != -1){
    builder.append(new String(buffer,0,len)) ;
}
//調回指針,寫入“xyz”
raf1.seek(3);
raf1.write(&quot;xyz&quot;.getBytes());

//將StringBuilder中的數據寫入到文件中
raf1.write(builder.toString().getBytes());

raf1.close();

}

網絡編程

一、網絡編程中有兩個主要的問題:

  • 1.如何准確地定位網絡上一台或多台主機;定位主機上的特定的應用
  • 2.找到主機后如何可靠高效地進行數據傳輸

二、網絡編程中的兩個要素:

  • 1.對應問題一:IP和端口號
  • 2.對應問題二:提供網絡通信協議:TCP/IP參考模型(應用層、傳輸層、網絡層、物理+數據鏈路層)

三、通信要素一:IP和端口號

1.IP:唯一的標識 Internet 上的計算機(通信實體)
2.在Java中使用InetAddress類代表IP
3.IP分類:IPv4 和 IPv6 ; 萬維網 和 局域網
4.域名:  www.baidu.com  www.mi.com  www.sina.com  www.jd.com  www.vip.com
5.本地回路地址:127.0.0.1 對應着:localhost
6.如何實例化InetAddress:兩個方法:getByName(String host) 、 getLocalHost()

  兩個常用方法:getHostName() / getHostAddress()

7.端口號:正在計算機上運行的進程。

  • 要求:不同的進程有不同的端口號
  • 范圍:被規定為一個 16 位的整數 0~65535。

8.端口號與IP地址的組合得出一個網絡套接字:Socket

TCP的3個例子

通過三個例子學習客戶端與服務端的交互

例子1: 客戶端發送信息給服務端,服務端將數據顯示在控制台上

public class TCPTest1 {
//客戶端
@Test
public void client()  {
    Socket socket = null;
    OutputStream os = null;
    try {
        //1.創建Socket對象,指明服務器端的ip和端口號
        InetAddress inet = InetAddress.getByName(&quot;192.168.14.100&quot;);
        socket = new Socket(inet,8899);
        //2.獲取一個輸出流,用於輸出數據
        os = socket.getOutputStream();
        //3.寫出數據的操作
        os.write(&quot;你好,我是客戶端mm&quot;.getBytes());
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //4.資源的關閉
        if(os != null){
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        if(socket != null){
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

}
//服務端
@Test
public void server()  {

    ServerSocket ss = null;
    Socket socket = null;
    InputStream is = null;
    ByteArrayOutputStream baos = null;
    try {
        //1.創建服務器端的ServerSocket,指明自己的端口號
        ss = new ServerSocket(8899);
        //2.調用accept()表示接收來自於客戶端的socket
        socket = ss.accept();
        //3.獲取輸入流
        is = socket.getInputStream();

        //不建議這樣寫,可能會有亂碼

// byte[] buffer = new byte[1024];
// int len;
// while((len = is.read(buffer)) != -1){
// String str = new String(buffer,0,len);
// System.out.print(str);
// }
//4.讀取輸入流中的數據
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[5];
int len;
while((len = is.read(buffer)) != -1){
baos.write(buffer,0,len);
}

        System.out.println(baos.toString());

        System.out.println(&quot;收到了來自於:&quot; + socket.getInetAddress().getHostAddress() + &quot;的數據&quot;);

    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(baos != null){
            //5.關閉資源
            try {
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(is != null){
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(socket != null){
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(ss != null){
            try {
                ss.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

}

}

總結:
客戶端步驟分為:

  1. 創建Socket對象,指明服務器端的ip和端口號(關鍵字: Socket、InetAddress、getByName)
  2. 獲取一個輸出流,用於輸出數據(關鍵字: OutputStream、 socket.getOutputStream)
  3. 寫出數據的操作(關鍵字: getBytes)
  4. 關閉資源(后用先關,先關輸出流,再關socket)

服務端步驟分為:

  1. 創建服務器端的ServerSocket,指明自己的端口號(關鍵字: ServerSocket)
  2. 調用accept()表示接收來自於客戶端的socket(關鍵字: accept)
  3. 獲取輸入流(關鍵字: getInputStream)
  4. 讀取輸入流中的數據(關鍵字: ByteArrayOutputStream)
  5. 關閉資源

例子2:客戶端發送文件給服務端,服務端將文件保存在本地。

public class TCPTest2 {
/*
這里涉及到的異常,應該使用try-catch-finally處理
 */
@Test
public void client() throws IOException {
    //1.
    Socket socket = new Socket(InetAddress.getByName(&quot;127.0.0.1&quot;),9090);
    //2.
    OutputStream os = socket.getOutputStream();
    //3.
    FileInputStream fis = new FileInputStream(new File(&quot;beauty.jpg&quot;));
    //4.
    byte[] buffer = new byte[1024];
    int len;
    while((len = fis.read(buffer)) != -1){
        os.write(buffer,0,len);
    }
    //5.
    fis.close();
    os.close();
    socket.close();
}

/*
這里涉及到的異常,應該使用try-catch-finally處理
 */
@Test
public void server() throws IOException {
    //1.
    ServerSocket ss = new ServerSocket(9090);
    //2.
    Socket socket = ss.accept();
    //3.
    InputStream is = socket.getInputStream();
    //4.
    FileOutputStream fos = new FileOutputStream(new File(&quot;beauty1.jpg&quot;));
    //5.
    byte[] buffer = new byte[1024];
    int len;
    while((len = is.read(buffer)) != -1){
        fos.write(buffer,0,len);
    }
    //6.
    fos.close();
    is.close();
    socket.close();
    ss.close();

}

}

分析:步驟與例子1是一樣的,差別在於服務端保存文件的IO方法。

例子3:從客戶端發送文件給服務端,服務端保存到本地。並返回“發送成功”給客戶端。並關閉相應的連接。

public class TCPTest3 {
/*
這里涉及到的異常,應該使用try-catch-finally處理
*/
@Test
public void client() throws IOException {
    //1.
    Socket socket = new Socket(InetAddress.getByName(&quot;127.0.0.1&quot;),9090);
    //2.
    OutputStream os = socket.getOutputStream();
    //3.
    FileInputStream fis = new FileInputStream(new File(&quot;beauty.jpg&quot;));
    //4.
    byte[] buffer = new byte[1024];
    int len;
    while((len = fis.read(buffer)) != -1){
        os.write(buffer,0,len);
    }
    //關閉數據的輸出
    socket.shutdownOutput();

    //5.接收來自於服務器端的數據,並顯示到控制台上
    InputStream is = socket.getInputStream();
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    byte[] bufferr = new byte[20];
    int len1;
    while((len1 = is.read(buffer)) != -1){
        baos.write(buffer,0,len1);
    }

    System.out.println(baos.toString());

    //6.
    fis.close();
    os.close();
    socket.close();
    baos.close();
}

/*
這里涉及到的異常,應該使用try-catch-finally處理
 */
@Test
public void server() throws IOException {
    //1.
    ServerSocket ss = new ServerSocket(9090);
    //2.
    Socket socket = ss.accept();
    //3.
    InputStream is = socket.getInputStream();
    //4.
    FileOutputStream fos = new FileOutputStream(new File(&quot;beauty2.jpg&quot;));
    //5.
    byte[] buffer = new byte[1024];
    int len;
    while((len = is.read(buffer)) != -1){
        fos.write(buffer,0,len);
    }

    System.out.println(&quot;圖片傳輸完成&quot;);

    //6.服務器端給予客戶端反饋
    OutputStream os = socket.getOutputStream();
    os.write(&quot;你好,美女,照片我已收到,非常漂亮!&quot;.getBytes());

    //7.
    fos.close();
    is.close();
    socket.close();
    ss.close();
    os.close();

}

}

URL網絡編程

1.URL:統一資源定位符,對應着互聯網的某一資源地址

2.格式:

http://localhost:8080/examples/beauty.jpg?username=Tom

協議 主機名 端口號   資源地址    參數列表

  • url.getProtocol( ): 獲取該URL的協議名
  • url.getHost( ): 獲取該URL的主機名
  • url.getPort( ): 獲取該URL的端口號
  • url.getPath( ): 獲取該URL的文件路徑
  • url.getFile( ): 獲取該URL的文件名
  • url.getQuery( ): 獲取該URL的查詢名

    

public class URLTest1 {
public static void main(String[] args) {

    HttpURLConnection urlConnection = null;
    InputStream is = null;
    FileOutputStream fos = null;
    try {
        URL url = new URL(&quot;http://localhost:8080/examples/beauty.jpg&quot;);

        urlConnection = (HttpURLConnection) url.openConnection();

        urlConnection.connect();

        is = urlConnection.getInputStream();
        fos = new FileOutputStream(&quot;day10\\beauty3.jpg&quot;);

        byte[] buffer = new byte[1024];
        int len;
        while((len = is.read(buffer)) != -1){
            fos.write(buffer,0,len);
        }

        System.out.println(&quot;下載完成&quot;);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //關閉資源
        if(is != null){
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(fos != null){
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(urlConnection != null){
            urlConnection.disconnect();
        }
    }
}

}

UDP協議的網絡編程

public class UDPTest {
//發送端
@Test
public void sender() throws IOException {

    DatagramSocket socket = new DatagramSocket();



    String str = &quot;我是UDP方式發送的導彈&quot;;
    byte[] data = str.getBytes();
    InetAddress inet = InetAddress.getLocalHost();
    DatagramPacket packet = new DatagramPacket(data,0,data.length,inet,9090);

    socket.send(packet);

    socket.close();

}
//接收端
@Test
public void receiver() throws IOException {

    DatagramSocket socket = new DatagramSocket(9090);

    byte[] buffer = new byte[100];
    DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);

    socket.receive(packet);

    System.out.println(new String(packet.getData(),0,packet.getLength()));

    socket.close();
}

}

day29

反射初步了解

在Person類外部,不可以通過Person類的對象調用其內部私有結構。可以通過反射,創建Person類的對象,調用對象指定的屬性、方法,甚至可以通過反射,可以調用Person類的私有結構的。比如:私有的構造器、方法、屬性。

疑問1:通過直接new的方式或反射的方式都可以調用公共的結構,開發中到底用那個?

建議:直接new的方式。
什么時候會使用:反射的方式。 反射的特征:動態性

疑問2:反射機制與面向對象中的封裝性是不是矛盾的?如何看待兩個技術?

不矛盾。

關於java.lang.Class類的理解

  1. 類的加載過程:
    程序經過javac.exe命令以后,會生成一個或多個字節碼文件(.class結尾)。接着我們使用java.exe命令對某個字節碼文件進行解釋運行。相當於將某個字節碼文件加載到內存中。此過程就稱為類的加載。加載到內存中的類,我們就稱為運行時類,此運行時類,就作為Class的一個實例。
  2. 換句話說,Class的實例就對應着一個運行時類。
  3. 加載到內存中的運行時類,會緩存一定的時間。在此時間之內,我們可以通過不同的方式 來獲取此運行時類。

獲取Class的實例的方式

(前三種方式需要掌握)

  • 方式一:調用運行時類的屬性:.class
  • 方式二:通過運行時類的對象,調用getClass()
  • 方式三:調用Class的靜態方法:forName(String classPath) (常用)
  • 方式四:使用類的加載器:ClassLoader (了解)

    

public void test3() throws ClassNotFoundException {
    //方式一:調用運行時類的屬性:.class
    Class clazz1 = Person.class;
    System.out.println(clazz1);
    //方式二:通過運行時類的對象,調用getClass()
    Person p1 = new Person();
    Class clazz2 = p1.getClass();
    System.out.println(clazz2);
//方式三:調用Class的靜態方法:forName(String classPath)
Class clazz3 = Class.forName(&quot;com.atguigu.java.Person&quot;);
//clazz3 = Class.forName(&quot;java.lang.String&quot;);
System.out.println(clazz3);

System.out.println(clazz1 == clazz2);
System.out.println(clazz1 == clazz3);

//方式四:使用類的加載器:ClassLoader  (了解)
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass(&quot;com.atguigu.java.Person&quot;);
System.out.println(clazz4);

System.out.println(clazz1 == clazz4);

}

Class實例可以是哪些結構

萬事萬物皆對象

@Test
public void test4(){
    Class c1 = Object.class;
    Class c2 = Comparable.class;
    Class c3 = String[].class;
    Class c4 = int[][].class;
    Class c5 = ElementType.class;
    Class c6 = Override.class;
    Class c7 = int.class;
    Class c8 = void.class;
    Class c9 = Class.class;
int[] a = new int[10];
int[] b = new int[100];
Class c10 = a.getClass();
Class c11 = b.getClass();
// 只要數組的元素類型與維度一樣,就是同一個Class
System.out.println(c10 == c11);

}

newInstance

newInstance():調用此方法,創建對應的運行時類的對象。內部調用了運行時類的空參的構造器。

要想此方法正常的創建運行時類的對象,要求:
1.運行時類必須提供空參的構造器
2.空參的構造器的訪問權限得夠。通常,設置為public。

在javabean中要求提供一個public的空參構造器。原因:
1.便於通過反射,創建運行時類的對象
2.便於子類繼承此運行時類時,默認調用super()時,保證父類有此構造器

了解類的加載器

  • 對於自定義類,使用系統類加載器進行加載
  • 調用系統類加載器的getParent():獲取擴展類加載器
  • 調用擴展類加載器的getParent():無法獲取引導類加載器
  • 引導類加載器主要負責加載java的核心類庫,無法加載自定義類的。

類加載器作用是用來把類(class)裝載進內存的。JVM規范定義了如下類型的類的加載器。

@Test
public void test1(){
    //對於自定義類,使用系統類加載器進行加載
    ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
    System.out.println(classLoader);
    //調用系統類加載器的getParent():獲取擴展類加載器
    ClassLoader classLoader1 = classLoader.getParent();
    System.out.println(classLoader1);
    //調用擴展類加載器的getParent():無法獲取引導類加載器
    //引導類加載器主要負責加載java的核心類庫,無法加載自定義類的。
    ClassLoader classLoader2 = classLoader1.getParent();
    System.out.println(classLoader2);
ClassLoader classLoader3 = String.class.getClassLoader();
System.out.println(classLoader3);

}

Properties

Properties:用來讀取配置文件

@Test
public void test2() throws Exception {
Properties pros =  new Properties();
//此時的文件默認在當前的module下。
//讀取配置文件的方式一:
//FileInputStream fis = new FileInputStream(&quot;jdbc.properties&quot;);
//FileInputStream fis = new FileInputStream(&quot;src\\jdbc1.properties&quot;);
//pros.load(fis);

//讀取配置文件的方式二:使用ClassLoader
//配置文件默認識別為:當前module的src下
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream(&quot;jdbc1.properties&quot;);
pros.load(is);


String user = pros.getProperty(&quot;user&quot;);
String password = pros.getProperty(&quot;password&quot;);
System.out.println(&quot;user = &quot; + user + &quot;,password = &quot; + password);

}

總結:

  1. 注意兩種方式讀取文件的路徑不同,方式一在當前module下,方式二在當前module的src下
  2. 使用getResourceAsStream讀取文件
  3. 將要讀去的數據寫入一個.properties文件中,創建方式New --> Resource Bundle

獲取運行時類的屬性、方法、父類等

  • 獲取屬性結構
    getFields():獲取當前運行時類及其父類中聲明為public訪問權限的屬性
    getDeclaredFields():獲取當前運行時類中聲明的所有屬性。(不包含父類中聲明的屬性)
  • 獲取權限修飾符 數據類型 變量名
    getModifiers():權限修飾符
    getType():數據類型
    getName():變量名
  • 獲取方法結構
    getMethods():獲取當前運行時類及其所有父類中聲明為public權限的方法
    getDeclaredMethods():獲取當前運行時類中聲明的所有方法。(不包含父類中聲明的方法)
  • 獲取注解、權限修飾符、返回值類型、方法名、形參列表、拋出的異常

例如:

`/*
@Xxxx
權限修飾符  返回值類型  方法名(參數類型1 形參名1,...) throws XxxException{}
 */`

getAnnotations(): 獲取方法聲明的注解
Modifier.toString(m.getModifiers()): 權限修飾符
獲取的權限修飾符比較特殊, getModifiers()返回的是int類型(如1, 2, 3, 4),這些數字分別代表public, protected等
使用Modifier.toString將它轉化為字符串形式的權限修飾符 getReturnType().getName():返回值類型
getName()

形參列表

    Class[] parameterTypes = m.getParameterTypes();
    if(!(parameterTypes == null && parameterTypes.length == 0)){
        for(int i = 0;i < parameterTypes.length;i++){
        if(i == parameterTypes.length - 1){
            System.out.print(parameterTypes[i].getName() + &quot; args_&quot; + i);
            break;
        }

        System.out.print(parameterTypes[i].getName() + &quot; args_&quot; + i + &quot;,&quot;);
    }
}

拋出的異常

    Class[] exceptionTypes = m.getExceptionTypes();
    if(exceptionTypes.length > 0){
        System.out.print("throws ");
        for(int i = 0;i < exceptionTypes.length;i++){
            if(i == exceptionTypes.length - 1){
                System.out.print(exceptionTypes[i].getName());
                break;
            }
        System.out.print(exceptionTypes[i].getName() + &quot;,&quot;);
    }
}


免責聲明!

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



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