1. 程式人生 > 實用技巧 >java基礎(9)

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 需要依賴的其他模組名稱;
}