1. 程式人生 > >Java進階學習第十四天(泛型、反射泛型、註解、Log4J日誌元件)

Java進階學習第十四天(泛型、反射泛型、註解、Log4J日誌元件)

一、泛型

1、概述
泛型是JDK1.5以後才有的,可以在編譯時期進行型別檢查,且可以避免頻繁型別轉化!

	// 執行時期異常 
	@Test
	public void testGeneric() throws Exception {
		// 集合的宣告
		List list = new ArrayList();
		list.add("China");
		list.add(1);
		// 集合的使用
		String str = (String) list.get(1);
	}
	// 使用泛型
	@Test
	public void testGeneric2() throws Exception {
		// 宣告泛型集合的時候指定元素的型別
		List<String> list = new ArrayList<String>();
		list.add("China");
		//	list.add(1);// 編譯時期報錯
		String str = list.get(1); 
	}

2、泛型擦除
泛型只在編譯時期有效,編譯後的位元組碼檔案中不存在有泛型資訊!

	//泛型擦除例項  
	public void save(List<Person> p){
	}
	public void save(List<Dept> d){    // 報錯:與上面方法編譯後一樣
	}

3、泛型寫法

	@Test
	public void testGeneric3() throws Exception {
		// 宣告泛型集合,集合兩端型別必須一致
		List<Object> list = new ArrayList<Object>();
		List<String> list1 = new ArrayList<String>();
		List list2 = new ArrayList<String>();
		List<Integer> list3 = new ArrayList();
		// 錯誤
		//List<Object> list4 = new ArrayList<String>();
		// 錯誤: 泛型型別必須是引用型別,不能為基本型別
		//List<int> list5 = new ArrayList<int>();
	}

4、泛型方法、泛型類、泛型介面
作用:設計公用的類、方法,對公用的業務實現進行抽取, 使程式更靈活!
① 泛型方法

public class GenericDemo {
	// 定義泛型方法
	public <K,T> T save(T t,K k) {
		return null;
	}
	// 測試方法
	@Test
	public void testMethod() throws Exception {
		// 使用泛型方法:在使用泛型方法的時候,確定泛型型別
		save(1.0f,1);
	}
}

② 泛型類

public class GenericDemo<T> {
	// 定義泛型方法
	public <K> T save(T t,K k) {
		return null;
	}
	public void update(T t) {
	}
	// 測試方法
	@Test
	public void testMethod() throws Exception {
		// 泛型類: 在建立愛泛型類物件的時候,確定型別
		GenericDemo<String> demo = new GenericDemo<String>();
		demo.save("test", 1);
	}
}

③ 泛型介面

public interface IBaseDao<T> {
	void save(T t );
	void update(T t );
}

泛型介面型別確定: 實現泛型介面的類也是抽象,那麼型別在具體的實現中確定或建立泛型類的時候確定

public class BaseDaoImpl<T> implements BaseDao<T> {
}

泛型介面型別確定: 在業務實現類中直接確定介面的型別

public class PersonDao implements BaseDao<Person>{
}

5、泛型關鍵字
① ? :指定只是接收值

/**
 * Ctrl + shift + R   檢視當前專案中類
 * Ctrl + shift + T   檢視原始碼jar包中的類
 * Ctrl + shift + L   檢視方法返回值
 */
public class App_extends_super {
	//只帶泛型特徵的方法
	public void save(List<?> list) {
		// 只能獲取、迭代list,不能編輯list
	}

	@Test
	public void testGeneric() throws Exception {	
		// ?  可以接收任何泛型集合,但是不能編輯集合值,所以一般在方法引數中用
		List<?> list = new ArrayList<String>();
		//list.add("");// 報錯
	}
}

② extends【上限】 :元素的型別必須繼承自指定的類

public class App_extends_super {
	/**
	 * list集合只能處理 Double/Float/Integer等型別
	 * 限定元素範圍:元素的型別要繼承自Number類(上限)
	 * @param list
	 */
	public void save(List<? extends Number> list) {
	}

	@Test
	public void testGeneric() throws Exception {
		List<Double> list_1 = new ArrayList<Double>();
		List<Float> list_2 = new ArrayList<Float>();
		List<Integer> list_3 = new ArrayList<Integer>();
		List<String> list_4 = new ArrayList<String>();
		
		// 呼叫
		save(list_1);
		save(list_2);
		save(list_3);
		//save(list_4);
	}
}

③ super【下限】:元素的型別必須是指定的類的父類

public class App_super {
	/**
	 * super限定元素範圍:必須是String父類(下限)
	 * @param list
	 */
	public void save(List<? super String> list) {
	}

	@Test
	public void testGeneric() throws Exception {
		// 呼叫上面方法,必須傳入String的父類
		List<Object> list1 = new ArrayList<Object>();
		List<String> list2 = new ArrayList<String>();
		List<Integer> list3 = new ArrayList<Integer>();
		
		//save(list3);
	}
}

二、、泛型的反射(反射泛型)

1、反射泛型涉及API
① Type:介面,任何型別預設的介面!包括: 引用型別、原始型別、引數化型別
② 引數化型別: ParameterizedType
List<String> list = new ArrayList<String>();
即:ArrayList<String> 為引數化型別
③ 反射泛型案例

public class AdminDao extends BaseDao<Admin> {}
public class AccountDao extends BaseDao<Account> {}
/**
 * 所有dao的公用的方法,都在這裡實現
 */
public class BaseDao<T>{
	private Class clazz;	// 儲存當前執行類的引數化型別中的實際的型別
	private String tableName;	// 表名
	
	// 建構函式: 通過獲取當前執行類的引數化型別,來獲取引數化型別中實際型別的定義(class)
	public BaseDao(){
		//  this  表示當前執行類:(AccountDao/AdminDao)
		//  this.getClass()  當前執行類的位元組碼(AccountDao.class/AdminDao.class)
		//  this.getClass().getGenericSuperclass();  當前執行類的父類,即為BaseDao<Account>
		//  其實就是“引數化型別”:ParameterizedType   
		Type type = this.getClass().getGenericSuperclass();
		// 強制轉換為“引數化型別”:BaseDao<Account>
		ParameterizedType pt = (ParameterizedType) type;
		// 獲取引數化型別中,實際型別的定義:new Type[]{Account.class}
		Type types[] =  pt.getActualTypeArguments();
		// 獲取資料的第一個元素:Accout.class
		clazz = (Class) types[0];
		// 表名:與類名一樣,只要獲取類名就可以
		tableName = clazz.getSimpleName();
	}

	/**
	 * 主鍵查詢
	 * @param id	主鍵值
	 * @return T    返回封裝後的物件
	 */
	public T findById(int id){
		/*
		 * 1. 知道封裝的物件的型別
		 * 2. 表名【表名與物件名稱一樣,且主鍵都為id】
		 * 
		 * 	 首先 ---》得到當前執行類繼承的父類  BaseDao<Account>
		 *   然後 ---》得到Account.class
		 */
		String sql = "select * from " + tableName + " where id=? ";
		try {
			return JdbcUtils.getQuerrRunner().query(sql, new BeanHandler<T>(clazz), id);
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * 查詢全部
	 */
	public List<T> getAll(){
		String sql = "select * from " + tableName ;
		try {
			return JdbcUtils.getQuerrRunner().query(sql, new BeanListHandler<T>(clazz));
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}
}

2、反射覆習
① 反射:可以在執行時期動態建立物件以及獲取物件的屬性、方法

public class Admin {
	// Field
	private int id = 1000;
	private String name = "匿名";
	
	// Constructor
	public Admin(){
		System.out.println("Admin.Admin()");
	}
	public Admin(String name){
		System.out.println("Admin.Admin()" + name);
	}
	
	// Method
	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 class App {
	// 1. 建立物件
	@Test
	public void testInfo() throws Exception {
		// 類全名
		String className = "cn.itcast.c_reflect.Admin";
		// 得到類位元組碼
		Class<?> clazz = Class.forName(className);
		// 建立物件1: 預設建構函式簡寫
		//Admin admin = (Admin) clazz.newInstance();
		// 建立物件2: 通過帶引數構造器建立物件
		Constructor<?> constructor = clazz.getDeclaredConstructor(String.class);
		Admin admin = (Admin) constructor.newInstance("Jack");
	}
	
	@Test
	//2. 獲取屬性名稱、值
	public void testField() throws Exception {
		// 類全名
		String className = "cn.itcast.c_reflect.Admin";
		// 得到類位元組碼
		Class<?> clazz = Class.forName(className);
		// 物件
		Admin admin =  (Admin) clazz.newInstance();
		// 獲取所有的屬性名稱
		Field[]  fs =  clazz.getDeclaredFields();
		// 遍歷:輸出每一個屬性名稱、值
		for (Field f : fs) {
			// 設定強制訪問
			f.setAccessible(true);
			// 名稱
			String name = f.getName();
			// 值
			Object value = f.get(admin);
			System.out.println(name + value);
		}
	}
	
	@Test
	//3. 反射獲取方法
	public void testMethod() throws Exception {
		// 類全名
		String className = "cn.itcast.c_reflect.Admin";
		// 得到類位元組碼
		Class<?> clazz = Class.forName(className);
		// 物件
		Admin admin =  (Admin) clazz.newInstance();
		// 獲取方法物件    public int getId() {
		Method m = clazz.getDeclaredMethod("getId");
		// 呼叫方法
		Object r_value = m.invoke(admin);
		System.out.println(r_value);
	}
}

三、註解

1、註解與註釋
① 註解,告訴編譯器如何執行程式!
② 註釋, 給程式設計師閱讀,對編譯、執行沒有影響;

2、註解作用
① 告訴編譯器如何執行程式
② 簡化(取代)配置檔案

3、常用的註解:@Override、@SuppressWarnings、@Deprecated

	// 重寫父類的方法
	@Override
	public String toString() {
		return super.toString();
	}
	
	// 抑制編譯器警告
	@SuppressWarnings({"unused","unchecked"})
	private void save() {
		List list = null;
	}
	
	// 標記方法以及過時
	@Deprecated
	private void save1() {
	}

4、自定義註解
通過自定義註解,可以給類、欄位、方法上新增描述資訊!
① 註解基本寫法

/**
 * 自定義註解(描述一個作者)
 */
public @interface Author {
	/**
	 * 註解屬性
	 * 	  1. 修飾為預設或public
	 *    2. 不能有主體
	 */
	String name();
	int age();
}

使用:

@Author(name = "Jet", age = 30)
public void save() {}

② 帶預設值的註解

public @interface Author {
	/**
	 * 註解屬性
	 * 	  1. 修飾為預設或public
	 *    2. 不能有主體
	 */
	String name();
	int age() default 30;// 帶預設值的註解,使用的時候就可以不寫此屬性值
}

③ 預設名稱的註解
註解屬性名稱為value,這就是預設名稱

public @interface Author {
	// 如果註解名稱為value,使用時候可以省略名稱,直接給值
	// (且註解只有一個屬性時候才可以省略名稱)
	String value();
}

使用:

@Author("Jet")
@Author(value = "Jet")

④ 註解屬性型別為陣列

public @interface Author {
	String[] value() default {"test1","test2"};
}

使用:

@Author({"",""})
public void save() {}

⑤ 元註解:表示註解的註解
◆ 指定註解的可用範圍:

@Target({
TYPE,            類
FIELD,           欄位
METHOD,          方法
PARAMETER,       引數
CONSTRUCTOR,     構造器
LOCAL_VARIABLE   區域性變數
})

◆ 指定註解的宣告週期:

@Retention(RetentionPolicy.SOURCE)     註解只在原始碼級別有效
@Retention(RetentionPolicy.CLASS)      註解在位元組碼即別有效:預設值
@Retention(RetentionPolicy.RUNTIME)    註解在執行時期有效

◆ 註解反射

@Target({TYPE,FIELD , METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
//@Target({METHOD,FIELD,TYPE})   指定只能在方法、欄位、類上用;
@Retention(RetentionPolicy.RUNTIME)   // 位元組碼級別有效
public @interface Author {
	String authorName() default "Jet";
	int age() default 30;	
	String remark();
}
public class Demo {
	private String test;
	@Id
	@Author(remark = "儲存資訊!!!", age = 19)
	public void save() throws Exception {
		// 獲取註解資訊: name/age/remark
		// 1. 先獲取代表方法的Method型別;
		Class clazz = Demo.class;
		Method m = clazz.getMethod("save");
		// 2. 再獲取方法上的註解
		Author author = m.getAnnotation(Author.class);
		// 獲取輸出註解資訊
		System.out.println(author.authorName());
		System.out.println(author.age());
		System.out.println(author.remark());
	}
	
	@Test
	public void testMain() throws Exception {
		save();
	}
}

5、註解:優化BaseDao的程式碼
當表名與資料庫名稱不一致、 欄位與屬性不一樣、主鍵不叫id, 上面的BaseDao不能用!一方面,可以通過配置檔案(XML) 解決(便於維護,但是需要讀取程式碼)!

@Table(tableName="a_admin")
public class Admin {
	@Id
	@Column(columnName = "a_id")
	private int id;
	@Column(columnName = "a_userName")
	private String userName;
	@Column(columnName = "a_pwd")
	private String pwd;
	
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getUserName() {
		return userName;
	}
	public void setUserName(String userName) {
		this.userName = userName;
	}
	public String getPwd() {
		return pwd;
	}
	public void setPwd(String pwd) {
		this.pwd = pwd;
	}
	@Override
	public String toString() {
		return "Admin [id=" + id + ", pwd=" + pwd + ", userName=" + userName
				+ "]";
	}
}
/**
 * 解決優化的問題:
 * 	  1. 當資料庫表名與類名不一致、
 *    2. 欄位與屬性不一樣
 *    3. 主鍵不叫id   
 */
public class BaseDao<T> {
	
	// 當前執行類的型別
	private Class<T> clazz;
	// 表名
	private String tableName;
	// 主鍵
	private String id_primary;

	// 拿到當前執行類的引數化型別中實際的型別:BaseDao<Admin>,Admin.class
	public BaseDao(){
		Type type = this.getClass().getGenericSuperclass();
		ParameterizedType pt = (ParameterizedType) type;
		Type[] types = pt.getActualTypeArguments();
		clazz = (Class<T>) types[0];
		//已經拿到:  Admin.class	
		/*******1. 獲取表名*******/
		Table table = clazz.getAnnotation(Table.class);
		tableName = table.tableName();
		/*******2. 獲取主鍵欄位*******/
		//獲取當前執行類的所有欄位、遍歷、獲取每一個欄位上的id註解
		Field[] fs = clazz.getDeclaredFields();
		for (Field f : fs) {
			// 設定強制訪問
			f.setAccessible(true);
			// 獲取每一個欄位上的id註解
			Id anno_id = f.getAnnotation(Id.class);
			// 判斷
			if (anno_id != null) {
				// 如果欄位上有id註解,當前欄位(field)是主鍵,再獲取欄位名稱
				Column column = f.getAnnotation(Column.class);
				// 主鍵
				id_primary = column.columnName();
				// 跳出迴圈
				break;
			}
		}
		System.out.println("表:" + tableName);
		System.out.println("主鍵:" + id_primary);
	}
	
	public T findById(int id){
		try {
			String sql = "select * from " + tableName + " where " + id_primary +"=?";
			/*
			 * DbUtils的已經封裝好的工具類:BeanHandler不能用:因為要求屬性=欄位
			 */
			return JdbcUtils.getQuerrRunner().query(sql, new BeanHandler<T>(clazz), id);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	public List<T> getAll(){
		try {
			String sql = "select * from " + tableName;
			return JdbcUtils.getQuerrRunner().query(sql, new BeanListHandler<T>(clazz));
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}

/**
 * 自定義結果集:封裝單個Bean物件
 */
class BeanHandler<T> implements ResultSetHandler<T>{
	// 儲存傳入的要封裝的類的位元組碼
	private Class<T> clazz;
	public BeanHandler(Class<T> clazz) {
		this.clazz = clazz;
	}
	
	// 封裝結果集的方法
	@Override
	public T handle(ResultSet rs) throws SQLException {
		try {
			// 建立要封裝的物件  ‘1’
			T t = clazz.newInstance(); 
			// 向下讀一行
			if (rs.next()) {
				// a. 獲取類的所有的Field欄位陣列
				Field[] fs = clazz.getDeclaredFields();
				// b. 遍歷, 得到每一個欄位型別:Field
				for (Field f : fs) {
					// c. 獲取”屬性名稱“
					String fieldName = f.getName();
					// e. 獲取Field欄位上註解:@Column(columnName = "a_userName")
					Column column =  f.getAnnotation(Column.class);				
					// f. ”欄位名“
					String columnName = column.columnName();        // 資料庫中欄位 a_userName
					// g. 欄位值
					Object columnValue = rs.getObject(columnName);					
					// 設定(BeanUtils元件)
					BeanUtils.copyProperty(t, fieldName, columnValue);
				}
			}
			return t;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}

/**
 * 自定義結果集:封裝多個Bean物件到List集合
 */
class BeanListHandler<T> implements ResultSetHandler<List<T>>{
	// 要封裝的單個物件
	private Class<T> clazz;
	public BeanListHandler(Class<T> clazz){
		this.clazz = clazz;
	}

	// 把從資料庫查詢到的每一行記錄,封裝為一個物件,再提交到list集合,返回List<T>
	@Override
	public List<T> handle(ResultSet rs) throws SQLException {
		List<T> list = new ArrayList<T>();
		try {
			// 向下讀一行
			while (rs.next()) {
				// 建立要封裝的物件:‘1’
				T t = clazz.newInstance(); 
				// a. 獲取類的所有的Field欄位陣列
				Field[] fs = clazz.getDeclaredFields();
				// b. 遍歷, 得到每一個欄位型別:Field
				for (Field f : fs) {
					// c. 獲取”屬性名稱“
					String fieldName = f.getName();
					// e. 獲取Field欄位上註解:@Column(columnName = "a_userName")
					Column column =  f.getAnnotation(Column.class);
					// f. ”欄位名“
					String columnName = column.columnName();        // 資料庫中欄位 a_userName
					// g. 欄位值
					Object columnValue = rs.getObject(columnName);
					// 設定(BeanUtils元件)
					BeanUtils.copyProperty(t, fieldName, columnValue);
				}
				// 物件新增到集合
				list.add(t);
			}
			return list;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}

註解:簡化XML配置, 程式處理非常方便,但是不便於維護: 例如修改欄位名,要重新編譯!

四、Log4J日誌元件

1、程式中為什麼用日誌元件?
簡單來說,為了專案後期部署上線後的維護、錯誤排查!

2、Log4j:log for java,開源的日誌元件!

3、使用步驟
① 下載元件,引入jar檔案:log4j-1.2.11.jar
② 配置: src/log4j.properties
③ 使用

public class Demo {
	Log log = LogFactory.getLog(Demo.class);
	@Test
	public void save() {
		try {
			log.info("儲存: 開始進入儲存方法");
			int i = 1/0;	
			log.info("儲存: 執行儲存結束,成功");
		} catch (Exception e) {	
			log.error("執行App類Save()方法出現異常!");  // 異常
			e.printStackTrace();
		}
	}
	
	/*
	 * 思考: 日誌的輸出級別作用? ----> 控制日誌輸出的內容
	 */
	@Test
	public void testLog() throws Exception {
		// 輸出不同級別的提示
		log.debug("除錯資訊");
		log.info("資訊提示");
		log.warn("警告");
		log.error("異常");	
	}
}
# 通過根元素指定日誌輸出的級別、目的地: 
# 日誌輸出優先順序: debug < info < warn < error 
log4j.rootLogger=info,console,file

############# 日誌輸出到控制檯 #############
# 日誌輸出到控制檯使用的api類
log4j.appender.console=org.apache.log4j.ConsoleAppender
# 指定日誌輸出的格式:靈活的格式
log4j.appender.console.layout=org.apache.log4j.PatternLayout
# 具體格式內容
log4j.appender.console.layout.ConversionPattern=%d %p %c.%M()-%m%n

############# 日誌輸出到檔案 #############
log4j.appender.file=org.apache.log4j.RollingFileAppender
# 檔案引數: 指定日誌檔案路徑
log4j.appender.file.File=../logs/MyLog.log
# 檔案引數: 指定日誌檔案最大大小
log4j.appender.file.MaxFileSize=5kb
# 檔案引數: 指定產生日誌檔案的最大數目
log4j.appender.file.MaxBackupIndex=100
# 日誌格式
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d %c.%M()-%m%n