java基礎(9)
1.類載入器
類的載入:
概述:當程式要使用某個類時,如果該類還未被載入到記憶體中,則系統會通過載入,連線,初始化三步來實現對這個類的初始化
載入:
- 就是指將class檔案讀入記憶體,併為之建立一個Class物件
- 任何類被使用時系統都會建立一個Class物件
連線:
- 驗證:是否有正確的內部結構,並和其他類協調一致
- 準備:負責為類的靜態成員分配記憶體,並設定預設初始值
- 解析:將類的二進位制資料中的符號引用替換為直接引用
初始化:像預設初始化,顯示初始化,構造初始化等
類初始化的時機
1.建立類的例項
2.訪問類的靜態變數,或者為靜態變數賦值
3.呼叫類的靜態方法
4.使用反射方式來強調建立某個類或介面對應的java.lang.Class物件
5.初始化某個類的子類
6.直接使用java.exe命令來執行某個主類
類載入器
概述:類載入器負責將.class檔案載入到記憶體中,併為之生成對應的Class物件,雖然我們不需要關係類載入器機制,但是瞭解這個機制我們就能更好的理解程式的執行
類載入器的組成:
- Bootstrap ClassLoader:根類載入器
- 也被成為引導類載入器,負責java核心類的載入,比如System,String等,在JDK中JRE的lib目錄下的rt.jar檔案中
- Extension ClassLoader:擴充套件類載入器
- 負責JRE的擴充套件目錄中jar包的載入,在JDK中JRE的lib目錄下的ext目錄
- System ClassLoader:系統類載入器
- 負責在JVM啟動時載入來自java命令的class檔案,以及classpath環境變數所指定的jar包和類路徑
2.反射
概述:簡單的說就是通過class檔案物件(也就是Class類的物件),去使用該檔案中的成員變數,構造方法和成員方法
Class類
獲取class檔案物件的三種方式
1.類名.class
2.物件.getClass()
3.Class:forName("類名"):這裡的類名是包括包名的全稱類名(推薦使用)
程式碼示例:
import java.util.Date; public class Demo10 { public static void main(String[] args) throws ClassNotFoundException { String str = "123"; Class cls1 = str.getClass(); Class cls2 = String.class; Class cls3 = Class.forName("java.lang.String"); System.out.println(cls1 == cls2);//true System.out.println(cls1 == cls3);//true System.out.println(cls1.isPrimitive());//false,判斷是否為基本資料型別的Class例項物件的方法 System.out.println(int.class == Integer.class);//false System.out.println(int.class == Integer.TYPE);//true,TYPE是基本資料型別包裝類和void所獨有的靜態欄位,代表著基本資料型別的位元組碼 int[] array = {1, 3, 4}; System.out.println(array.getClass().isPrimitive());//false System.out.println(array.getClass().isArray());//true,判斷Class是否為陣列的方法 } }
通過反射獲取成員變數,構造方法,成員方法:
1.獲取成員變數: Field
- public Field[] getFields():獲取所有公共變數
- public Field[] getDeclaredFields():獲取所有變數
- public Field getField(String name):獲取單個指定的變數(不能為私有的變數)
- public Field getDeclaredField(String name):獲取單個變數(可以是私有變數)
- 欄位物件.set(物件, “該欄位的值”):設定某個欄位(成員變數)的值
2.獲取構造方法: Constructor
- public Constructor[] getConstructors():獲得所有的公共構造方法
- public Constructor[] getDeclaredConstructors():獲取所有的構造方法
- public Constructor
getConstructor(Class<?>... parameterTypes):獲取單個公共的構造方法,引數表示的是你要獲取的構造方法的構造引數個數及資料型別的class位元組碼檔案物件 - public Constructor
getDeclaredConstructor(Class<?>... parameterTypes):獲取單個構造方法(包括私有構造方法),引數表示的是你要獲取的構造方法的構造引數個數及資料型別的class位元組碼檔案物件 - public T newInstance(Object... instargs):使用此Constructor物件表示的構造方法來建立該構造方法的宣告類的新例項,並用指定的初始化引數初始化該例項
3.獲取成員方法: Method
- public Method[] getMethods():獲取自己的包括父親的成員方法
- public Method[] getDeclaredMethods():獲取自己的方法
- public Method getMethod(String name, Class<?>... parameterTypes):獲取單個方法(不可獲取私有的方法)
- public Method getDeclaredMethod(String name, Class<?>... parameterTypes):獲取單個方法(可獲得私有的方法)
- public Object invoke(Object obj, Object... args):返回值是object接受,第一個引數表示物件是誰,第二個引數表示呼叫該方法的實際引數
程式碼示例1(通過反射獲取和設定成員變數):
//cn.luyi.demo1.Person.java
package cn.luyi.demo1;
public class Person {
private String name;
private int age;
public String city;
public Person(){};
private Person(String name){
this.name = name;
};
public Person(String name, int age){
this.name = name;
this.age = age;
}
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;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", city=" + city + "]";
}
}
//Demo1
public class Demo1 {
public static void main(String[] args) throws Exception{
Class c = Class.forName("cn.luyi.demo1.Person");
Constructor con = c.getConstructor();
Object obj = con.newInstance();
//獲取單個公共變數
Field cityField = c.getField("city");
cityField.set(obj, "廣州");
System.out.println(obj);//Person [name=null, age=0, city=廣州]
//獲取單個私有變數
Field nameField = c.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(obj, "盧一");
System.out.println(obj);//Person [name=盧一, age=0, city=廣州]
}
}
程式碼示例2(通過反射獲取構造方法):
//cn.luyi.demo1.Person.java
package cn.luyi.demo1;
public class Person {
private String name;
private int age;
public String city;
public Person(){};
private Person(String name){
this.name = name;
};
public Person(String name, int age){
this.name = name;
this.age = age;
}
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;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", city=" + city + "]";
}
}
//Demo2.java
import java.lang.reflect.Constructor;
public class Demo2 {
public static void main(String[] args) throws Exception {
Class c = Class.forName("cn.luyi.demo1.Person");
//獲得無引數構造方法
Constructor con = c.getConstructor();
//通過這個無參構造建立一個新例項
Object obj = con.newInstance();
System.out.println(obj);//Person [name=null, age=0, city=null]
//獲得帶引數的構造方法
Constructor con2 = c.getConstructor(String.class, int.class);
//通過這個有參構造建立一個新例項
Object obj2 = con2.newInstance("luyi", 19);
System.out.println(obj2);//Person [name=luyi, age=19, city=null]
//獲得帶引數的私有構造方法
Constructor con3 = c.getDeclaredConstructor(String.class);
//設定反射的物件在使用的時候取消java語言訪問檢查,不設定會丟擲IllegalAccessExcetion異常
con3.setAccessible(true);
//通過這個私有帶參構造方法獲得一個新例項
Object obj3 = con3.newInstance("huangyi");
System.out.println(obj3);//Person [name=huangyi, age=0, city=null]
}
}
程式碼案例3(通過反射獲取成員方法):
//Student.java
public class Student {
public Student(){
};
public void show(){
System.out.println("I am a student");
}
private int sum(int n){
int plus = 0;
for(int i = 0; i <= n; i ++){
plus += i;
}
return plus;
}
}
//Demo3.java
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class Demo3 {
public static void main(String[] args) throws Exception {
Class c = Class.forName("cn.luyi.demo1.Student");
Constructor con = c.getConstructor();
Object obj = con.newInstance();
//獲取無引數的成員方法
Method method1 = c.getMethod("show");
method1.invoke(obj);
//獲取私有帶參的成員方法
Method method2 = c.getDeclaredMethod("sum", int.class);
method2.setAccessible(true);
//獲得返回值
Integer result = (Integer)method2.invoke(obj, 10);
System.out.println(result);
}
}
反射案例
案例1:通過反射越過泛型檢查
import java.lang.reflect.Method;
import java.util.ArrayList;
/*
* 怎麼給一個ArrayList<Integer>物件新增一個字串資料呢?
*/
public class Demo4 {
public static void main(String[] args) throws Exception {
ArrayList<Integer> array = new ArrayList<Integer>();
Class c = array.getClass();
//ArrayList的位元組碼檔案傳的這個引數的型別為Object型別,而不是Integer型別
Method m = c.getMethod("add", Object.class);
m.invoke(array, "Hello");
System.out.println(array);
}
}
3.動態代理
代理概述:本來應該自己做的事情,卻請了別人來做,被請的人就是代理物件
動態代理概述:在程式執行過程中產生的這個動態代理物件,而我們的動態代理其實就是通過反射來生產一個代理的
動態代理的實現:在java中的java.lang.reflect包下提供了一個Proxy類和一個InvocationHandle介面,通過使用這個類和介面就可以生成動態代理物件,JDK提供的代理只能針對介面來做代理,我們有更強大的代理cglib可以不僅僅針對介面來做
Proxy類中的方法建立動態代理物件:
- public static Object newProxyInstance(ClassLoader loader,Class<?>[] interface,InvocationHandler h),最終會呼叫InvocationHandler的方法
- InvocationHandler介面重寫的方法:Object invoke(Object proxy, Method method,Object[] args)
具體程式碼實現(通過動態代理給增刪改查方法新增許可權功能和日誌記錄功能):
//UserDao.java
public interface UserDao {
abstract public void add();
abstract public void delete();
abstract public void update();
abstract public void find();
}
//UserDaoImpl.java
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("新增功能");
}
@Override
public void delete() {
System.out.println("刪除功能");
}
@Override
public void update() {
System.out.println("修改功能");
}
@Override
public void find() {
System.out.println("查詢功能");
}
}
//MyInvocationHandle.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandle implements InvocationHandler {
private Object target;//目標物件
public MyInvocationHandle(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("許可權校驗");
Object result = method.invoke(target, args);
System.out.println("日誌記錄");
return result;
}
}
//Text.java
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
UserDao ud = new UserDaoImpl();
//對ud物件做一個代理
MyInvocationHandle handle = new MyInvocationHandle(ud);
UserDao proxy = (UserDao)Proxy.newProxyInstance(ud.getClass().getClassLoader(), ud.getClass().getInterfaces(),handle);
proxy.add();
proxy.delete();
proxy.update();
proxy.find();
}
}
4.列舉(Enum)
為什麼要有列舉
1.問題:要定義星期幾或性別的變數,該怎麼定義呢?假設用1-7分別表示星期一到星期天,但有人可能就會把星期天用0表示
2.列舉就是要讓某個型別的變數的取值只能為若干個固定值中的一個,否則,編譯器就會報錯,列舉可以讓編譯器在編譯時就控制源程式中填寫的非法值,普通變數的方式在開發階段無法實現這一目標
列舉類的注意事項
1.定義列舉類要用關鍵字enum
2.所有列舉類都是Enum的子類
3.列舉類的第一行必須是列舉項,最後一個列舉項後面的分號是可以省略的,但是如果列舉類有其他的東西,這個列舉類不能省略
4.列舉類可以有構造器,但必須是private修飾的,它預設也是private修飾的
5.列舉類可以有抽象方法,但是列舉項必須將重寫該方法
6.列舉可以用在switch語句中
列舉的基本應用
定義:public enum 列舉名稱{列舉元素列表}
使用:
public class Demo8 {
public static void main(String[] args) {
//使用列舉
WeekDay weekDay = WeekDay.Fri;
System.out.println(weekDay);//Fri
//列舉物件的成員方法
System.out.println(weekDay.name());//Fri
System.out.println(weekDay.ordinal());//5,在列舉的內容中是第幾個(從0開始)
//列舉類的靜態方法
System.out.println(WeekDay.valueOf("Sun"));//Sun,把字串Sun變成對應的列舉元素
System.out.println(WeekDay.values().length);//7,得到了有所有列舉元素的陣列,然後求這個陣列的長度
}
//定義了一個列舉類,列舉出星期天到星期六
public enum WeekDay{
Sun, Mon, Tue, Wed, Thi, Fri, Sat
}
}
實現帶有構造方法的列舉
public enum WeekDay{
//如果想要使用帶有引數的構造方法,則需要在各個列舉元素後面加個小括號然後傳參
Sun() , Mon(1), Tue("abc"), Wed, Thi, Fri, Sat;
//構造方法必須放在列舉元素列表之後,而且方法必須私有
private WeekDay(){
System.out.println("執行了無引數的構造方法");
};
private WeekDay(int day){
System.out.println("執行了帶有整數引數的構造方法");
};
private WeekDay(String day){
System.out.println("執行了帶有字串型別引數的構造方法");
}
}
實現帶有抽象方法的列舉
public class Demo9 {
public static void main(String[] args) {
TrafficeLamp t = TrafficeLamp.RED;
System.out.println(t);//RED
System.out.println(t.nextLamp());//GREEN
System.out.println(t.getTime());//30
System.out.println(t.nextLamp().nextLamp().getTime());//3
}
//實現了帶有抽象方法以及帶有引數的構造方法的列舉
public enum TrafficeLamp{
//這裡的每個列舉元素後面跟個引數,就相當於new一個物件後面跟個引數的構造方法
//後面加個大括號呼叫抽象方法,就相當於前面new出來的物件呼叫了這個抽象方法
RED(30){
@Override
public TrafficeLamp nextLamp(){
return GREEN;
}
},
GREEN(30){
@Override
public TrafficeLamp nextLamp(){
return YELLOW;
}
},
YELLOW(3){
@Override
public TrafficeLamp nextLamp(){
return RED;
}
};
public abstract TrafficeLamp nextLamp();
private int time;
private TrafficeLamp(int time){
this.time = time;
}
public int getTime() {
return time;
}
public void setTime(int time) {
this.time = time;
}
}
}
列舉實現單例模式
保證整個系統中的一個類只有一個物件的例項,實現這種功能的方式就是單例模式
當列舉只有一個成員時,就可以作為一種單例的實現方式了
5.方法引用的基本入門(簡化Lambda表示式,java8之後才有)
在某些場景之下,Lambda表示式要做的事情,其實在另外一個類裡已經寫過了,那麼此時如果通過Lambda表示式重複編寫相同的程式碼,就是浪費,那麼,如何才能複用已經存在的方法邏輯呢?
接下來我們來看一個案例:
//Cook.java
public class Cook {
//定義了一個做菜的靜態方法
public static void makeFood(String food){
System.out.println("將" + food + "作成可口的食物");
}
}
//Sitter.java
//定義一個保姆介面
public interface Sitter {
void work(String food);
}
//Demo3.java
public class Demo3 {
public static void main(String[] args) {
//直接使用Lambda表示式
hireSitter(food -> System.out.println("將" + food + "作成可口的食物"));
//使用方法引用,引用了在Cook類裡的makeFood方法
hireSitter(Cook::makeFood);
}
//僱傭一個保姆,並且讓她去做菜
public static void hireSitter(Sitter sitter){
sitter.work("白菜");
}
}
通過以上這個例子,我們也可以大致瞭解到方法引用的格式有一個為:類名::這個類裡的方法
接下來我們再具體看看方法引用的兩種常見格式
通過類名稱引用靜態方法的格式:類名稱::靜態方法
通過物件名引用成員方法的格式:物件名::成員方法
6.Stream流式思想(java8之後才有)
stream流式操作可以幫我們節省很多的程式碼,接下來我們就來認識一下stream流吧
java8當中的“流”其實就是Stream介面的物件
JDK提供了一個流介面:java.util.stream.Stream
如何獲取流?
1.根據集合獲取流:集合名稱.stream()
2.根據陣列獲取流:Stream.of(陣列名稱)
程式碼示例:
public class Demo1 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("luyi");
list.add("luer");
list.add("lusan");
Stream<String> streamA = list.stream();
String[] array = {"lusi", "luwu", "luliu"};
Stream<String> streamB = Stream.of(array);
}
}
Stream流的map對映方法
獲取流之後,可以使用對映方法:map(用於轉換的Lambda表示式)
對映:就是將一個物件轉換成為另一個物件,把老物件對映到新物件上
使用對映把字串型別的資料轉換為數字型別的資料的示例:
import java.util.ArrayList;
import java.util.stream.Stream;
public class Demo2 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("100");
list.add("200");
list.add("300");
//與list.stream().map(Integer::parseInt)等價
/*Stream<Integer> stream = list.stream().map((String str) -> {
return Integer.parseInt(str);
});*/
Stream<Integer> stream = list.stream().map(Integer::parseInt);
System.out.println(stream);
}
}
Stream流的filter過濾方法
如果希望對流當中的元素進行過濾,可以使用過濾方法
格式:filter(能產生boolean結構的Lambda)
過濾掉不是luyi的人的示例
public class Demo3 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("luyi");
list.add("luer");
list.add("lusan");
Stream<String> streamA = list.stream().filter((String str) ->{
boolean b = "luyi".equals(str);
return b;
});//filter那部分程式碼等價於filter(str -> "luyi".equlas(str));
}
}
Stream流的forEach遍歷方法
如果希望在流當中進行元素的遍歷操作,可以使用forEach方法
forEach(Lambda表示式):意思是,對流當中的每一個元素都要進行操作。引數Lambda表示式必須是一個能夠消費一個引數,而且不會產生資料結果的Lambda
例如:
Lambda s->System.out.println(s);等價於
方法引用:System.out::println
forEach的使用:
import java.util.stream.Stream;
public class Demo4 {
public static void main(String[] args) {
String[] array = {"趙露思" , "趙麗穎", "趙四"};
//遍歷Stream流列印資料
Stream.of(array).forEach((String str)->{
System.out.println(str);
});
//等價於forEach(s->System.out.println(s));
//也等價於forEach(System.out::println);
}
}
Stream流幾個方法的綜合使用:
import java.util.ArrayList;
import java.util.stream.Stream;
public class Demo4 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("趙露思,20");
list.add("趙麗穎,30");
list.add("趙四,40");
//過濾掉歲數小於20的人
list.stream().map(s -> s.split(",")[1]).map(s -> Integer.parseInt(s)).filter(n -> n > 20).forEach(System.out::println);
}
}
併發的Stream流:支援併發操作的流
流當中的元素如果特別多,那麼只有一個人在逐一、挨個處理,肯定比較慢,費勁
如果對流當中的元素,使用多個人同時處理,這就是“併發”
那麼如何才能獲取“併發流”呢?
格式:.parallelStream()
注意事項:
1.使用併發流操作的時候,到底有幾個人進行同時操作呢?不用管,JDK自己處理
2.只要正確使用,就不會出現多個人搶到同一個元素的情況
3.如果已經獲取了一個普通的流,那麼只要再呼叫一下parallel()方法也會變成併發流
示例程式碼:
import java.util.ArrayList;
public class Demo5 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
for(int i = 1; i <= 100; i ++){
list.add("hello----" + i);
}
//這是隻有一個人在做列印輸出的操作
list.stream().forEach(System.out::println);
//這是多個人在做列印輸出的操作
list.parallelStream().forEach(System.out::println);
//這是已經有了一個普通流,想轉換為併發流
list.stream().parallel().forEach(System.out::println);
}
}
7.模組化(java9及以上版本才有)
模組化思想可以給我們帶來的好處:
1.整體一整個,檔案體積過大,如果只是用到了其中的一部分內容,就會浪費
2.精確控制package包級別的訪問控制,只有匯出的包,模組之外才可以訪問,否則,只有模組內部自己訪問
不同的IDE有不同的建立module-info.java檔案的方法
這裡講一下Eclipse建立module-info.java的方法
如果你想在專案上建一個模組,那就在你的專案上進行一下操作:
右鍵你的專案名->找到Configure選項->點選它就會出現Create module-info.java的選項
模組的描述資訊module-info.java檔案的基本內容:
module 本模組的名稱{
exports 匯出的包名稱;
requires 需要依賴的其他模組名稱;
}