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