zoukankan      html  css  js  c++  java
  • (27)回复泛型,注解、日志组件、枚举在实际项目中的使用

    ---恢复内容开始---

    1、泛型

      掌握的知识:基本用法、泛型擦除、泛型类/泛型方法/泛型接口、泛型关键字、反射泛型(案例)

      a、概述:泛型是JDK1.5以后才有的, 可以在编译时期进行类型检查,且可以避免频繁类型转化!

     1 // 运行时期异常 
     2     @Test
     3     public void testGeneric() throws Exception {
     4         // 集合的声明
     5         List list = new ArrayList();
     6         list.add("China");
     7         list.add(1);
     8         
     9         // 集合的使用
    10         String str = (String) list.get(1);
    11         
    12     }
    13     
    14     // 使用泛型
    15     @Test
    16     public void testGeneric2() throws Exception {
    17         // 声明泛型集合的时候指定元素的类型
    18         List<String> list = new ArrayList<String>();
    19         list.add("China");
    20 //        list.add(1);// 编译时期报错
    21         
    22         String str = list.get(1); 
    23     }

      泛型擦除,泛型只在编译时期有效,编译后的字节码文件中不存在有泛型信息!

    1 /*
    2      * 泛型擦除实例 
    3      
    4     public void save(List<Person> p){
    5     }
    6     public void save(List<Dept> d){    // 报错: 与上面方法编译后一样
    7     }
    8     */

      泛型的写法

     1 // 泛型写法
     2     @Test
     3     public void testGeneric3() throws Exception {
     4         // 声明泛型集合,集合两端类型必须一致
     5         List<Object> list = new ArrayList<Object>();
     6         List<String> list1 = new ArrayList<String>();
     7         List list2 = new ArrayList<String>();
     8         List<Integer> list3 = new ArrayList();
     9         
    10         // 错误
    11         //List<Object> list4 = new ArrayList<String>();
    12         // 错误: 泛型类型必须是引用类型,不能为基本类型
    13         List<int> list5 = new ArrayList<int>();
    14     }

     b. 泛型方法/泛型类/泛型接口

      作用:

      设计公用的类、方法,对公用的业务实现进行抽取!

      使程序更灵活!

      1. 泛型方法:

     1 public class GenericDemo {
     2 
     3     // 定义泛型方法
     4     public <K,T> T save(T t,K k) {
     5         return null;
     6     }
     7     
     8     // 测试方法
     9     @Test
    10     public void testMethod() throws Exception {
    11         // 使用泛型方法:  在使用泛型方法的时候,确定泛型类型
    12         save(1.0f, 1);
    13     }
    14 }

      2. 泛型类:

     1 public class GenericDemo<T> {
     2 
     3     // 定义泛型方法
     4     public <K> T save(T t,K k) {
     5         return null;
     6     }
     7     
     8     public void update(T t) {
     9 
    10     }
    11     
    12     // 测试方法
    13     @Test
    14     public void testMethod() throws Exception {
    15         
    16         // 泛型类:  在创建爱泛型类对象的时候,确定类型
    17         GenericDemo<String> demo = new GenericDemo<String>();
    18         demo.save("test", 1);
    19     }
    20 }

      3. 泛型接口:

     1 /**
     2  * 泛型接口
     3  * @author Jie.Yuan
     4  *
     5  * @param <T>
     6  */
     7 public interface IBaseDao<T> {
     8     void save(T t );
     9     void update(T t );
    10 }
    11 
    12 =======================================
    13 泛型接口类型确定: 实现泛型接口的类也是抽象,那么类型在具体的实现中确定或创建泛型类的时候确定
    14 public class BaseDao<T> implements IBaseDao<T> {
    15 ======================================
    16 泛型=接口类型确定: 在业务实现类中直接确定接口的类型
    17 public class PersonDao implements IBaseDao<Person>{
    18 }

     c. 泛型关键字 

      泛型中:

        ?    指定只是接收值

        extends      元素的类型必须继承自指定的类

        super        元素的类型必须是指定的类的父类

      关键字  ?

     1 /**
     2  * 泛型, 涉及到一些关键字
     3  * 
     4  * Ctrl + shift + R   查看当前项目中类
     5  * Ctrl + shift + T   查看源码jar包中的类
     6  * @author Jie.Yuan
     7  *
     8  */
     9 public class App_extends_super {
    10     
    11     //只带泛型特征的方法
    12     public void save(List<?> list) {
    13         // 只能获取、迭代list;  不能编辑list
    14     }
    15 
    16     @Test
    17     public void testGeneric() throws Exception {
    18         
    19         // ?  可以接收任何泛型集合, 但是不能编辑集合值; 所以一般在方法参数中用
    20         List<?> list = new ArrayList<String>();
    21         //list.add("");// 报错
    22     }
    23 }

      关键字  extends    【上限】

     1 public class App_extends_super {
     2     
     3     
     4     /**
     5      * list集合只能处理 Double/Float/Integer等类型
     6      * 限定元素范围:元素的类型要继承自Number类  (上限)
     7      * @param list
     8      */
     9     public void save(List<? extends Number> list) {
    10     }
    11 
    12     @Test
    13     public void testGeneric() throws Exception {
    14         List<Double> list_1 = new ArrayList<Double>();
    15         List<Float> list_2 = new ArrayList<Float>();
    16         List<Integer> list_3 = new ArrayList<Integer>();
    17         
    18         List<String> list_4 = new ArrayList<String>();
    19         
    20         // 调用
    21         save(list_1);
    22         save(list_2);
    23         save(list_3);
    24         //save(list_4);
    25     }
    26 }

      关键字  super     【下限】

     1 /**
     2  * 泛型, 涉及到一些关键字
     3  * 
     4  * Ctrl + shift + R   查看当前项目中类
     5  * Ctrl + shift + T   查看源码jar包中的类
     6  * @author Jie.Yuan
     7  *
     8  */
     9 public class App_super {
    10     
    11     
    12     /**
    13      * super限定元素范围:必须是String父类   【下限】
    14      * @param list
    15      */
    16     public void save(List<? super String> list) {
    17     }
    18 
    19     @Test
    20     public void testGeneric() throws Exception {
    21         // 调用上面方法,必须传入String的父类
    22         List<Object> list1 = new ArrayList<Object>();
    23         List<String> list2 = new ArrayList<String>();
    24         
    25         List<Integer> list3 = new ArrayList<Integer>();
    26         //save(list3);
    27     }
    28 }

      d. 泛型的反射

        案例,设置通用方法,会用到反射泛型!下面有简单代码

      步骤:

      1. 案例分析 /  实现

      2. 涉及知识点(jdk api)

      3. 优化 / 反射泛型

      反射泛型涉及API

      Student    类型的表示

      Id   name

      ParameterizedType   参数化类型的表示

      ArrayList<String>();

      Type    接口,任何类型默认的接口!

              包括: 引用类型、原始类型、参数化类型

     

      List<String>  list   =  new   ArrayList<String>();

      泛型集合:    list

      集合元素定义:new   ArrayList<String>();  中的String

      参数化类型  ParameterizedType 

      即:ArrayList<String> ” 为参数化类型

    public class AdminDao extends BaseDao<Admin> {}
    public class AccountDao extends BaseDao<Account> {}
    
    
    /**
     * 所有dao的公用的方法,都在这里实现
     * @author Jie.Yuan
     *
     */
    public class BaseDao<T>{
        
        // 保存当前运行类的参数化类型中的实际的类型
        private Class clazz;
        // 表名
        private String tableName;
        
        
        
        // 构造函数: 1. 获取当前运行类的参数化类型; 2. 获取参数化类型中实际类型的定义(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      返回封装后的对象
         */
        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);
            }
        }
        
        
        /**
         * 查询全部
         * @return
         */
        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);
            }
        }
    }

    这里额外回顾反射,直接看代码:

     1 public class Admin {
     2 
     3     // Field
     4     private int id = 1000;
     5     private String name = "匿名";
     6     
     7     // Constructor
     8     public Admin(){
     9         System.out.println("Admin.Admin()");
    10     }
    11     public Admin(String name){
    12         System.out.println("Admin.Admin()" + name);
    13     }
    14     
    15     // Method
    16     public int getId() {
    17         return id;
    18     }
    19     public void setId(int id) {
    20         this.id = id;
    21     }
    22     public String getName() {
    23         return name;
    24     }
    25     public void setName(String name) {
    26         this.name = name;
    27     }
    28     
    29 }
    30 
    31 
    32 // 反射技术
    33 public class App {
    34 
    35     // 1. 创建对象
    36     @Test
    37     public void testInfo() throws Exception {
    38         // 类全名
    39         String className = "cn.itcast.c_reflect.Admin";
    40         // 得到类字节码
    41         Class<?> clazz = Class.forName(className);
    42         
    43         // 创建对象1: 默认构造函数简写
    44         //Admin admin = (Admin) clazz.newInstance();
    45         
    46         // 创建对象2: 通过带参数构造器创建对象
    47         Constructor<?> constructor = clazz.getDeclaredConstructor(String.class);
    48         Admin admin = (Admin) constructor.newInstance("Jack");
    49         
    50     }
    51     @Test
    52     //2. 获取属性名称、值
    53     public void testField() throws Exception {
    54         
    55         // 类全名
    56         String className = "cn.itcast.c_reflect.Admin";
    57         // 得到类字节码
    58         Class<?> clazz = Class.forName(className);
    59         // 对象
    60         Admin admin =  (Admin) clazz.newInstance();
    61         
    62         // 获取所有的属性名称
    63         Field[]  fs =  clazz.getDeclaredFields();
    64         // 遍历:输出每一个属性名称、值
    65         for (Field f : fs) {
    66             // 设置强制访问
    67             f.setAccessible(true);
    68             // 名称
    69             String name = f.getName();
    70             //
    71             Object value = f.get(admin);
    72             
    73             System.out.println(name + value);
    74         }
    75     }
    76     
    77     @Test
    78     //3. 反射获取方法
    79     public void testMethod() throws Exception {
    80         
    81         // 类全名
    82         String className = "cn.itcast.c_reflect.Admin";
    83         // 得到类字节码
    84         Class<?> clazz = Class.forName(className);
    85         // 对象
    86         Admin admin =  (Admin) clazz.newInstance();
    87         
    88         // 获取方法对象    public int getId() {
    89         Method m = clazz.getDeclaredMethod("getId");
    90         // 调用方法
    91         Object r_value = m.invoke(admin);
    92         
    93         System.out.println(r_value);
    94     }
    95     
    96 }

    3. 注解

      概述 

      注解与注释,

        注解,告诉编译器如何运行程序!

        注释, 给程序员阅读,对编译、运行没有影响;

      注解作用,

        1. 告诉编译器如何运行程序;

        2. 简化(取代)配置文件   【案例后再看】

      常用的注解,

      

    // 重写父类的方法
        @Override
        public String toString() {
            return super.toString();
        }
        
        // 抑制编译器警告
        @SuppressWarnings({"unused","unchecked"})
        private void save() {
            List list = null;
        }
        
        // 标记方法以及过时
        @Deprecated
        private void save1() {
        }

      自定义注解

        通过自定义注解,可以给类、字段、方法上添加描述信息!

         a、注解基本写法

     

    /**
     * 自定义注解  (描述一个作者)
     * @author Jie.Yuan
     *
     */
    public @interface Author {
    
        /**
         * 注解属性
         *       1. 修饰为默认或public
         *    2. 不能有主体
         */
        String name();
        int age();
    }
    
    //使用自定义注解
    @Author(name = "Jet", age = 30)
        public void save() {
    
        }

      b.带默认值的注解

     1 public @interface Author {
     2 
     3     /**
     4      * 注解属性
     5      *       1. 修饰为默认或public
     6      *    2. 不能有主体
     7      */
     8     String name();
     9     int age() default 30;   // 带默认值的注解;  使用的时候就可以不写此属性值
    10 }

     c、默认名臣的注解

      注解属性的默认默认名称是value

    1 public @interface Author {
    2     // 如果注解名称为value,使用时候可以省略名称,直接给值
    3     // (且注解只有一个属性时候才可以省略名称)
    4     String value();
    5 }
    6 
    7 使用
    8 @Author("Jet")
    9 @Author(value = "Jet")

      注解属性类型为数组:

    public @interface Author {
        
        String[] value() default {"test1","test2"};
    }
    使用:
    @Author({“”,“”})
        public void save() {
    
        }

    元注解

    元注解,表示注解的注解!

    指定注解的可用范围:

    @Target({

    TYPE,     

    FIELD,     字段

    METHOD,  方法

    PARAMETER,   参数

    CONSTRUCTOR, 构造器

     LOCAL_VARIABLE  局部变量

    })

    // 元注解 - 2. 指定注解的声明周期

    @Retention(RetentionPolicy.SOURCE)    注解只在源码级别有效

    @Retention(RetentionPolicy.CLASS)      注解在字节码即别有效  默认值

    @Retention(RetentionPolicy.RUNTIME)   注解在运行时期有效

    注解的反射

     1 @Id
     2     @Author(remark = "保存信息!!!", age = 19)
     3     public void save() throws Exception {
     4         // 获取注解信息: name/age/remark
     5         
     6         
     7         // 1. 先获取代表方法的Method类型;
     8         Class clazz = App_2.class;
     9         Method m = clazz.getMethod("save");
    10         
    11         // 2. 再获取方法上的注解
    12         Author author = m.getAnnotation(Author.class);
    13         // 获取输出注解信息
    14         System.out.println(author.authorName());
    15         System.out.println(author.age());
    16         System.out.println(author.remark());
    17     }

    4. 注解,优化BaseDao的代码

    当表名与数据库名称不一致、 字段与属性不一样、主键不叫id, 上面的BaseDao不能用!

    这是,

    可以通过配置文件(XML) 解决!

    注解:

    简化XML配置, 程序处理非常方便!

    (不便于维护: 例如修改字段名,要重新编译!)

    XML

    便于维护!  需要些读取代码!

     1 当表名与数据库名称不一致、 字段与属性不一样、主键不叫id, 上面的BaseDao不能用!
     2 这是,
     3     可以通过配置文件(XML) 解决!
     4 
     5 
     6 注解:
     7     简化XML配置, 程序处理非常方便!
     8     (不便于维护: 例如修改字段名,要重新编译!)
     9 
    10 XML
    11     便于维护!  需要些读取代通过反射注解=ViewCode】

    5. Log4J日志组件

    程序中为什么用日志组件?

    简单来说,为了项目后期部署上线后的维护、错误排查!

    Log4j,  log for java, 开源的日志组件!

    使用步骤:

    1. 下载组件,引入jar文件;

    log4j-1.2.11.jar

    2. 配置 :  src/log4j.properties

    3. 使用

    # 通过根元素指定日志输出的级别、目的地(目的地可以同时指定多个~): 
    #  日志输出优先级: 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

    下面是使用log4j的示例代码

     1 public class App {
     2     
     3     Log log = LogFactory.getLog(App.class);
     4     
     5     @Test
     6     public void save() {
     7         try {
     8             log.info("保存: 开始进入保存方法");
     9 
    10             int i = 1/0;
    11             
    12             log.info("保存: 执行保存结束,成功");
    13         } catch (Exception e) {
    14             
    15             log.error("执行App类Save()方法出现异常!");  // 异常
    16             
    17             e.printStackTrace();
    18         }
    19     }
    20     
    21     /*
    22      * 思考: 日志的输出级别作用?
    23      *      ----> 控制日志输出的内容。
    24      */
    25     @Test
    26     public void testLog() throws Exception {
    27         // 输出不同级别的提示
    28         log.debug("调试信息");
    29         log.info("信息提示");
    30         log.warn("警告");
    31         log.error("异常");
    32         
    33     }
    34 }
    35 public class Index extends HttpServlet {
    36     
    37     
    38     Log log =  LogFactory.getLog(Index.class);
    39 
    40     public void doGet(HttpServletRequest request, HttpServletResponse response)
    41             throws ServletException, IOException {
    42         try {
    43             log.info("进入servlet");
    44             int i = 1/0;
    45             log.info("进入servlet结束");
    46         } catch (Exception e) {
    47             log.error("计算异常!",e);
    48         }
    49     }
    50 }

    6、枚举完全讲解

    6.1基本概念

    为什么使用枚举类

    一些方法在运行的过程中所需要的值不是任意的,而是在一个范围中。在jdk1.5出现之前解决的方案,就是自己实现一个带有枚举功能的类。

    l Jdk1.5新增的enum关键字用于定义一个枚举类,一旦定义一个枚举类之后,这个枚举类及自动继承了,java类库中的Enum

    自定义枚举类
    /**
     * 自己手动的创建一个 枚举类
     * 1、私有化,private,隐藏构造方法不让别人使用
     * 2、蒂尼几个public static final的实例,给外界使用
     * 
     * 手动实现的一个枚举类就是 Grade
     * 为Student类中定义一个,Grade,当为这个grade复制的时候,只能是Grade中定义好的几个public static final型的值,否则就会出现变异问题
     * 一旦定义其他值就会出错,于是就达到了,枚举的作用,但是这样做还是比较麻烦的,于是,java中退出了一个枚举类
     * @author YUCHEN
     *
     */
    class Grade{
        
        private Grade(){
            
        }
        
        public static final Grade A = new Grade();
        public static final Grade B = new Grade();
        public static final Grade C = new Grade();
        public static final Grade D = new Grade();
        public static final Grade E = new Grade();
    }
    
    class Student{
        private Grade grade;    //考试等级
        
        public Grade getGrade()
        {
            return this.grade;
        }
        
        public void setGrade(Grade grade){
            this.grade = grade;
        }
    }

    6.2默认构造函数枚举

    按下面步骤,读下面案例:

     1 // 1. 枚举类定义
     2 enum Grade{
     3     A,B,C,D,E;
     4 }
     5 class Student{
     6     private String name;
     7     
     8     // 2. 使用枚举类型
     9     private Grade grade; //ABCDE
    10     public Grade getGrade() {
    11         return grade;
    12     }
    13     public void setGrade(Grade grade) {
    14         this.grade = grade;
    15     }
    16 }
    17 public class Demo1 {
    18     public static void main(String[] args) {
    19         Student stu = new Student();
    20         // 3. 给枚举类型赋值,只能是枚举类定义的值(第1步中所定义)
    21         stu.setGrade(Grade.A);
    22         
    23         System.out.println(stu.getGrade());
    24     }
    25 }
    View Code

    上述定义的枚举为默认构造函数枚举, 也可以这样

    1 // 1. 枚举类定义
    2 enum Grade{
    3     A(),B(),C(),D(),E();
    4     // 必须为私有
    5     private Grade(){
    6     }
    7 }

    此时,Grade类中有一个默认无参数构造函数

    6.3有参构造函数

     1 // 1. 带参数构造函数的枚举定义
     2 enum Grade{
     3     A("100-90"),B("90-80"),C("80-70"),D("70-60"),E("60-0");
     4     
     5     private String value;
     6     // 定义get方法返回数据
     7     public String getValue() {
     8         return value;
     9     }
    10     
    11     private Grade(String value) {
    12         this.value = value;
    13     }
    14 }
    15 class Student{
    16     // 2. 使用枚举类型
    17     private Grade grade; //ABCDE
    18     public Grade getGrade() {
    19         return grade;
    20     }
    21     public void setGrade(Grade grade) {
    22         this.grade = grade;
    23     }
    24 }
    25 public class Demo1 {
    26     public static void main(String[] args) {
    27         Student stu = new Student();
    28         // 3. 给枚举类型赋值
    29         stu.setGrade(Grade.A);
    30         // 输出对应的“分数”
    31         System.out.println(stu.getGrade().getValue());
    32     }
    33 }

    枚举类中抽象方法的定义

     1 // 1. 带参数构造函数的枚举定义
     2 // 并且需要返回更多的信息, 优秀,良好,好,一般,差
     3 enum Grade{
     4     A("100-90"){
     5         public String getLocalStr() {
     6             return "优秀";
     7         }
     8     }
     9     
    10     ,B("90-80"){
    11         public String getLocalStr() {
    12             return "良好";
    13         }
    14     }
    15     
    16     ,C("80-70"){
    17         public String getLocalStr() {
    18             return "好";
    19         }
    20     }
    21     
    22     ,D("70-60"){
    23         public String getLocalStr() {
    24             return "一般";
    25         }
    26     }
    27     
    28     ,E("60-0"){
    29         public String getLocalStr() {
    30             return "差";
    31         }
    32     };
    33     
    34     private String value;
    35     // 定义get方法返回数据
    36     public String getValue() {
    37         return value;
    38     }
    39     
    40     private Grade(String value) {
    41         this.value = value;
    42     }
    43     
    44     // 返回成绩段对应的“描述”, 需要每个对象重新实现次方法
    45     public abstract String getLocalStr();
    46 }
    47 class Student{
    48     // 2. 使用枚举类型
    49     private Grade grade; //ABCDE
    50     public Grade getGrade() {
    51         return grade;
    52     }
    53     public void setGrade(Grade grade) {
    54         this.grade = grade;
    55     }
    56 }
    57 public class Demo1 {
    58     public static void main(String[] args) {
    59         Student stu = new Student();
    60         // 3. 给枚举类型赋值
    61         stu.setGrade(Grade.A);
    62         // 输出对应的“分数”
    63         System.out.println(stu.getGrade().getValue());
    64         
    65         // 输出描述
    66         System.out.println(stu.getGrade().getLocalStr());
    67     }
    68 }
    View Code

    给我的体验忒儿的

    ---恢复内容结束---

    1、泛型

      掌握的知识:基本用法、泛型擦除、泛型类/泛型方法/泛型接口、泛型关键字、反射泛型(案例)

      a、概述:泛型是JDK1.5以后才有的, 可以在编译时期进行类型检查,且可以避免频繁类型转化!

     1 // 运行时期异常 
     2     @Test
     3     public void testGeneric() throws Exception {
     4         // 集合的声明
     5         List list = new ArrayList();
     6         list.add("China");
     7         list.add(1);
     8         
     9         // 集合的使用
    10         String str = (String) list.get(1);
    11         
    12     }
    13     
    14     // 使用泛型
    15     @Test
    16     public void testGeneric2() throws Exception {
    17         // 声明泛型集合的时候指定元素的类型
    18         List<String> list = new ArrayList<String>();
    19         list.add("China");
    20 //        list.add(1);// 编译时期报错
    21         
    22         String str = list.get(1); 
    23     }

      泛型擦除,泛型只在编译时期有效,编译后的字节码文件中不存在有泛型信息!

    1 /*
    2      * 泛型擦除实例 
    3      
    4     public void save(List<Person> p){
    5     }
    6     public void save(List<Dept> d){    // 报错: 与上面方法编译后一样
    7     }
    8     */

      泛型的写法:

     1 // 泛型写法
     2     @Test
     3     public void testGeneric3() throws Exception {
     4         // 声明泛型集合,集合两端类型必须一致
     5         List<Object> list = new ArrayList<Object>();
     6         List<String> list1 = new ArrayList<String>();
     7         List list2 = new ArrayList<String>();
     8         List<Integer> list3 = new ArrayList();
     9         
    10         // 错误
    11         //List<Object> list4 = new ArrayList<String>();
    12         // 错误: 泛型类型必须是引用类型,不能为基本类型
    13         List<int> list5 = new ArrayList<int>();
    14     }

     b. 泛型方法/泛型类/泛型接口

      作用:

      设计公用的类、方法,对公用的业务实现进行抽取!

      使程序更灵活!

      1. 泛型方法:

     1 public class GenericDemo {
     2 
     3     // 定义泛型方法
     4     public <K,T> T save(T t,K k) {
     5         return null;
     6     }
     7     
     8     // 测试方法
     9     @Test
    10     public void testMethod() throws Exception {
    11         // 使用泛型方法:  在使用泛型方法的时候,确定泛型类型
    12         save(1.0f, 1);
    13     }
    14 }

      2. 泛型类:

     1 public class GenericDemo<T> {
     2 
     3     // 定义泛型方法
     4     public <K> T save(T t,K k) {
     5         return null;
     6     }
     7     
     8     public void update(T t) {
     9 
    10     }
    11     
    12     // 测试方法
    13     @Test
    14     public void testMethod() throws Exception {
    15         
    16         // 泛型类:  在创建爱泛型类对象的时候,确定类型
    17         GenericDemo<String> demo = new GenericDemo<String>();
    18         demo.save("test", 1);
    19     }
    20 }

      3. 泛型接口:

     1 /**
     2  * 泛型接口
     3  * @author Jie.Yuan
     4  *
     5  * @param <T>
     6  */
     7 public interface IBaseDao<T> {
     8     void save(T t );
     9     void update(T t );
    10 }
    11 
    12 =======================================
    13 泛型接口类型确定: 实现泛型接口的类也是抽象,那么类型在具体的实现中确定或创建泛型类的时候确定
    14 public class BaseDao<T> implements IBaseDao<T> {
    15 ======================================
    16 泛型=接口类型确定: 在业务实现类中直接确定接口的类型
    17 public class PersonDao implements IBaseDao<Person>{
    18 }

     c. 泛型关键字 

      泛型中:

        ?    指定只是接收值

        extends      元素的类型必须继承自指定的类

        super        元素的类型必须是指定的类的父类

      关键字  ?

     1 /**
     2  * 泛型, 涉及到一些关键字
     3  * 
     4  * Ctrl + shift + R   查看当前项目中类
     5  * Ctrl + shift + T   查看源码jar包中的类
     6  * @author Jie.Yuan
     7  *
     8  */
     9 public class App_extends_super {
    10     
    11     //只带泛型特征的方法
    12     public void save(List<?> list) {
    13         // 只能获取、迭代list;  不能编辑list
    14     }
    15 
    16     @Test
    17     public void testGeneric() throws Exception {
    18         
    19         // ?  可以接收任何泛型集合, 但是不能编辑集合值; 所以一般在方法参数中用
    20         List<?> list = new ArrayList<String>();
    21         //list.add("");// 报错
    22     }
    23 }

      关键字  extends    【上限】

     1 public class App_extends_super {
     2     
     3     
     4     /**
     5      * list集合只能处理 Double/Float/Integer等类型
     6      * 限定元素范围:元素的类型要继承自Number类  (上限)
     7      * @param list
     8      */
     9     public void save(List<? extends Number> list) {
    10     }
    11 
    12     @Test
    13     public void testGeneric() throws Exception {
    14         List<Double> list_1 = new ArrayList<Double>();
    15         List<Float> list_2 = new ArrayList<Float>();
    16         List<Integer> list_3 = new ArrayList<Integer>();
    17         
    18         List<String> list_4 = new ArrayList<String>();
    19         
    20         // 调用
    21         save(list_1);
    22         save(list_2);
    23         save(list_3);
    24         //save(list_4);
    25     }
    26 }

      关键字  super     【下限】

     1 /**
     2  * 泛型, 涉及到一些关键字
     3  * 
     4  * Ctrl + shift + R   查看当前项目中类
     5  * Ctrl + shift + T   查看源码jar包中的类
     6  * @author Jie.Yuan
     7  *
     8  */
     9 public class App_super {
    10     
    11     
    12     /**
    13      * super限定元素范围:必须是String父类   【下限】
    14      * @param list
    15      */
    16     public void save(List<? super String> list) {
    17     }
    18 
    19     @Test
    20     public void testGeneric() throws Exception {
    21         // 调用上面方法,必须传入String的父类
    22         List<Object> list1 = new ArrayList<Object>();
    23         List<String> list2 = new ArrayList<String>();
    24         
    25         List<Integer> list3 = new ArrayList<Integer>();
    26         //save(list3);
    27     }
    28 }

      d. 泛型的反射

        案例,设置通用方法,会用到反射泛型!下面有简单代码

      步骤:

      1. 案例分析 /  实现

      2. 涉及知识点(jdk api)

      3. 优化 / 反射泛型

      反射泛型涉及API

      Student    类型的表示

      Id   name

      ParameterizedType   参数化类型的表示

      ArrayList<String>();

      Type    接口,任何类型默认的接口!

              包括: 引用类型、原始类型、参数化类型

     

      List<String>  list   =  new   ArrayList<String>();

      泛型集合:    list

      集合元素定义:new   ArrayList<String>();  中的String

      参数化类型  ParameterizedType 

      即:ArrayList<String> ” 为参数化类型

    public class AdminDao extends BaseDao<Admin> {}
    public class AccountDao extends BaseDao<Account> {}
    
    
    /**
     * 所有dao的公用的方法,都在这里实现
     * @author Jie.Yuan
     *
     */
    public class BaseDao<T>{
        
        // 保存当前运行类的参数化类型中的实际的类型
        private Class clazz;
        // 表名
        private String tableName;
        
        
        
        // 构造函数: 1. 获取当前运行类的参数化类型; 2. 获取参数化类型中实际类型的定义(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      返回封装后的对象
         */
        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);
            }
        }
        
        
        /**
         * 查询全部
         * @return
         */
        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);
            }
        }
    }

    这里额外回顾反射,直接看代码:

     1 public class Admin {
     2 
     3     // Field
     4     private int id = 1000;
     5     private String name = "匿名";
     6     
     7     // Constructor
     8     public Admin(){
     9         System.out.println("Admin.Admin()");
    10     }
    11     public Admin(String name){
    12         System.out.println("Admin.Admin()" + name);
    13     }
    14     
    15     // Method
    16     public int getId() {
    17         return id;
    18     }
    19     public void setId(int id) {
    20         this.id = id;
    21     }
    22     public String getName() {
    23         return name;
    24     }
    25     public void setName(String name) {
    26         this.name = name;
    27     }
    28     
    29 }
    30 
    31 
    32 // 反射技术
    33 public class App {
    34 
    35     // 1. 创建对象
    36     @Test
    37     public void testInfo() throws Exception {
    38         // 类全名
    39         String className = "cn.itcast.c_reflect.Admin";
    40         // 得到类字节码
    41         Class<?> clazz = Class.forName(className);
    42         
    43         // 创建对象1: 默认构造函数简写
    44         //Admin admin = (Admin) clazz.newInstance();
    45         
    46         // 创建对象2: 通过带参数构造器创建对象
    47         Constructor<?> constructor = clazz.getDeclaredConstructor(String.class);
    48         Admin admin = (Admin) constructor.newInstance("Jack");
    49         
    50     }
    51     @Test
    52     //2. 获取属性名称、值
    53     public void testField() throws Exception {
    54         
    55         // 类全名
    56         String className = "cn.itcast.c_reflect.Admin";
    57         // 得到类字节码
    58         Class<?> clazz = Class.forName(className);
    59         // 对象
    60         Admin admin =  (Admin) clazz.newInstance();
    61         
    62         // 获取所有的属性名称
    63         Field[]  fs =  clazz.getDeclaredFields();
    64         // 遍历:输出每一个属性名称、值
    65         for (Field f : fs) {
    66             // 设置强制访问
    67             f.setAccessible(true);
    68             // 名称
    69             String name = f.getName();
    70             //
    71             Object value = f.get(admin);
    72             
    73             System.out.println(name + value);
    74         }
    75     }
    76     
    77     @Test
    78     //3. 反射获取方法
    79     public void testMethod() throws Exception {
    80         
    81         // 类全名
    82         String className = "cn.itcast.c_reflect.Admin";
    83         // 得到类字节码
    84         Class<?> clazz = Class.forName(className);
    85         // 对象
    86         Admin admin =  (Admin) clazz.newInstance();
    87         
    88         // 获取方法对象    public int getId() {
    89         Method m = clazz.getDeclaredMethod("getId");
    90         // 调用方法
    91         Object r_value = m.invoke(admin);
    92         
    93         System.out.println(r_value);
    94     }
    95     
    96 }

    3. 注解

      概述 

      注解与注释,

        注解,告诉编译器如何运行程序!

        注释, 给程序员阅读,对编译、运行没有影响;

      注解作用,

        1. 告诉编译器如何运行程序;

        2. 简化(取代)配置文件   【案例后再看】

      常用的注解,

      

    // 重写父类的方法
        @Override
        public String toString() {
            return super.toString();
        }
        
        // 抑制编译器警告
        @SuppressWarnings({"unused","unchecked"})
        private void save() {
            List list = null;
        }
        
        // 标记方法以及过时
        @Deprecated
        private void save1() {
        }

      自定义注解

        通过自定义注解,可以给类、字段、方法上添加描述信息!

         a、注解基本写法

     

    /**
     * 自定义注解  (描述一个作者)
     * @author Jie.Yuan
     *
     */
    public @interface Author {
    
        /**
         * 注解属性
         *       1. 修饰为默认或public
         *    2. 不能有主体
         */
        String name();
        int age();
    }
    
    //使用自定义注解
    @Author(name = "Jet", age = 30)
        public void save() {
    
        }

      b.带默认值的注解

     1 public @interface Author {
     2 
     3     /**
     4      * 注解属性
     5      *       1. 修饰为默认或public
     6      *    2. 不能有主体
     7      */
     8     String name();
     9     int age() default 30;   // 带默认值的注解;  使用的时候就可以不写此属性值
    10 }

     c、默认名臣的注解

      注解属性的默认默认名称是value

    1 public @interface Author {
    2     // 如果注解名称为value,使用时候可以省略名称,直接给值
    3     // (且注解只有一个属性时候才可以省略名称)
    4     String value();
    5 }
    6 
    7 使用
    8 @Author("Jet")
    9 @Author(value = "Jet")

      注解属性类型为数组:

    public @interface Author {
        
        String[] value() default {"test1","test2"};
    }
    使用:
    @Author({“”,“”})
        public void save() {
    
        }

    元注解

    元注解,表示注解的注解!

    指定注解的可用范围:

    @Target({

    TYPE,     

    FIELD,     字段

    METHOD,  方法

    PARAMETER,   参数

    CONSTRUCTOR, 构造器

     LOCAL_VARIABLE  局部变量

    })

    // 元注解 - 2. 指定注解的声明周期

    @Retention(RetentionPolicy.SOURCE)    注解只在源码级别有效

    @Retention(RetentionPolicy.CLASS)      注解在字节码即别有效  默认值

    @Retention(RetentionPolicy.RUNTIME)   注解在运行时期有效

    注解的反射

     1 @Id
     2     @Author(remark = "保存信息!!!", age = 19)
     3     public void save() throws Exception {
     4         // 获取注解信息: name/age/remark
     5         
     6         
     7         // 1. 先获取代表方法的Method类型;
     8         Class clazz = App_2.class;
     9         Method m = clazz.getMethod("save");
    10         
    11         // 2. 再获取方法上的注解
    12         Author author = m.getAnnotation(Author.class);
    13         // 获取输出注解信息
    14         System.out.println(author.authorName());
    15         System.out.println(author.age());
    16         System.out.println(author.remark());
    17     }

    4. 注解,优化BaseDao的代码

    当表名与数据库名称不一致、 字段与属性不一样、主键不叫id, 上面的BaseDao不能用!

    这是,

    可以通过配置文件(XML) 解决!

    注解:

    简化XML配置, 程序处理非常方便!

    (不便于维护: 例如修改字段名,要重新编译!)

    XML

    便于维护!  需要些读取代码!

     1 当表名与数据库名称不一致、 字段与属性不一样、主键不叫id, 上面的BaseDao不能用!
     2 这是,
     3     可以通过配置文件(XML) 解决!
     4 
     5 
     6 注解:
     7     简化XML配置, 程序处理非常方便!
     8     (不便于维护: 例如修改字段名,要重新编译!)
     9 
    10 XML
    11     便于维护!  需要些读取代通过反射注解=ViewCode】

    5. Log4J日志组件

    程序中为什么用日志组件?

    简单来说,为了项目后期部署上线后的维护、错误排查!

    Log4j,  log for java, 开源的日志组件!

    使用步骤:

    1. 下载组件,引入jar文件;

    log4j-1.2.11.jar

    2. 配置 :  src/log4j.properties

    3. 使用

    # 通过根元素指定日志输出的级别、目的地(目的地可以同时指定多个~): 
    #  日志输出优先级: 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

    下面是使用log4j的示例代码

     1 public class App {
     2     
     3     Log log = LogFactory.getLog(App.class);
     4     
     5     @Test
     6     public void save() {
     7         try {
     8             log.info("保存: 开始进入保存方法");
     9 
    10             int i = 1/0;
    11             
    12             log.info("保存: 执行保存结束,成功");
    13         } catch (Exception e) {
    14             
    15             log.error("执行App类Save()方法出现异常!");  // 异常
    16             
    17             e.printStackTrace();
    18         }
    19     }
    20     
    21     /*
    22      * 思考: 日志的输出级别作用?
    23      *      ----> 控制日志输出的内容。
    24      */
    25     @Test
    26     public void testLog() throws Exception {
    27         // 输出不同级别的提示
    28         log.debug("调试信息");
    29         log.info("信息提示");
    30         log.warn("警告");
    31         log.error("异常");
    32         
    33     }
    34 }
    35 public class Index extends HttpServlet {
    36     
    37     
    38     Log log =  LogFactory.getLog(Index.class);
    39 
    40     public void doGet(HttpServletRequest request, HttpServletResponse response)
    41             throws ServletException, IOException {
    42         try {
    43             log.info("进入servlet");
    44             int i = 1/0;
    45             log.info("进入servlet结束");
    46         } catch (Exception e) {
    47             log.error("计算异常!",e);
    48         }
    49     }
    50 }

    6、枚举完全讲解

    6.1基本概念

    为什么使用枚举类

    一些方法在运行的过程中所需要的值不是任意的,而是在一个范围中。在jdk1.5出现之前解决的方案,就是自己实现一个带有枚举功能的类。

    l Jdk1.5新增的enum关键字用于定义一个枚举类,一旦定义一个枚举类之后,这个枚举类及自动继承了,java类库中的Enum

    自定义枚举类
    /**
     * 自己手动的创建一个 枚举类
     * 1、私有化,private,隐藏构造方法不让别人使用
     * 2、蒂尼几个public static final的实例,给外界使用
     * 
     * 手动实现的一个枚举类就是 Grade
     * 为Student类中定义一个,Grade,当为这个grade复制的时候,只能是Grade中定义好的几个public static final型的值,否则就会出现变异问题
     * 一旦定义其他值就会出错,于是就达到了,枚举的作用,但是这样做还是比较麻烦的,于是,java中退出了一个枚举类
     * @author YUCHEN
     *
     */
    class Grade{
        
        private Grade(){
            
        }
        
        public static final Grade A = new Grade();
        public static final Grade B = new Grade();
        public static final Grade C = new Grade();
        public static final Grade D = new Grade();
        public static final Grade E = new Grade();
    }
    
    class Student{
        private Grade grade;    //考试等级
        
        public Grade getGrade()
        {
            return this.grade;
        }
        
        public void setGrade(Grade grade){
            this.grade = grade;
        }
    }

    6.2默认构造函数枚举

    按下面步骤,读下面案例:

     1 // 1. 枚举类定义
     2 enum Grade{
     3     A,B,C,D,E;
     4 }
     5 class Student{
     6     private String name;
     7     
     8     // 2. 使用枚举类型
     9     private Grade grade; //ABCDE
    10     public Grade getGrade() {
    11         return grade;
    12     }
    13     public void setGrade(Grade grade) {
    14         this.grade = grade;
    15     }
    16 }
    17 public class Demo1 {
    18     public static void main(String[] args) {
    19         Student stu = new Student();
    20         // 3. 给枚举类型赋值,只能是枚举类定义的值(第1步中所定义)
    21         stu.setGrade(Grade.A);
    22         
    23         System.out.println(stu.getGrade());
    24     }
    25 }
    View Code

    上述定义的枚举为默认构造函数枚举, 也可以这样

    1 // 1. 枚举类定义
    2 enum Grade{
    3     A(),B(),C(),D(),E();
    4     // 必须为私有
    5     private Grade(){
    6     }
    7 }

    此时,Grade类中有一个默认无参数构造函数

    6.3有参构造函数

     1 // 1. 带参数构造函数的枚举定义
     2 enum Grade{
     3     A("100-90"),B("90-80"),C("80-70"),D("70-60"),E("60-0");
     4     
     5     private String value;
     6     // 定义get方法返回数据
     7     public String getValue() {
     8         return value;
     9     }
    10     
    11     private Grade(String value) {
    12         this.value = value;
    13     }
    14 }
    15 class Student{
    16     // 2. 使用枚举类型
    17     private Grade grade; //ABCDE
    18     public Grade getGrade() {
    19         return grade;
    20     }
    21     public void setGrade(Grade grade) {
    22         this.grade = grade;
    23     }
    24 }
    25 public class Demo1 {
    26     public static void main(String[] args) {
    27         Student stu = new Student();
    28         // 3. 给枚举类型赋值
    29         stu.setGrade(Grade.A);
    30         // 输出对应的“分数”
    31         System.out.println(stu.getGrade().getValue());
    32     }
    33 }

    6.4枚举类中抽象方法的定义

     1 // 1. 带参数构造函数的枚举定义
     2 // 并且需要返回更多的信息, 优秀,良好,好,一般,差
     3 enum Grade{
     4     A("100-90"){
     5         public String getLocalStr() {
     6             return "优秀";
     7         }
     8     }
     9     
    10     ,B("90-80"){
    11         public String getLocalStr() {
    12             return "良好";
    13         }
    14     }
    15     
    16     ,C("80-70"){
    17         public String getLocalStr() {
    18             return "好";
    19         }
    20     }
    21     
    22     ,D("70-60"){
    23         public String getLocalStr() {
    24             return "一般";
    25         }
    26     }
    27     
    28     ,E("60-0"){
    29         public String getLocalStr() {
    30             return "差";
    31         }
    32     };
    33     
    34     private String value;
    35     // 定义get方法返回数据
    36     public String getValue() {
    37         return value;
    38     }
    39     
    40     private Grade(String value) {
    41         this.value = value;
    42     }
    43     
    44     // 返回成绩段对应的“描述”, 需要每个对象重新实现次方法
    45     public abstract String getLocalStr();
    46 }
    47 class Student{
    48     // 2. 使用枚举类型
    49     private Grade grade; //ABCDE
    50     public Grade getGrade() {
    51         return grade;
    52     }
    53     public void setGrade(Grade grade) {
    54         this.grade = grade;
    55     }
    56 }
    57 public class Demo1 {
    58     public static void main(String[] args) {
    59         Student stu = new Student();
    60         // 3. 给枚举类型赋值
    61         stu.setGrade(Grade.A);
    62         // 输出对应的“分数”
    63         System.out.println(stu.getGrade().getValue());
    64         
    65         // 输出描述
    66         System.out.println(stu.getGrade().getLocalStr());
    67     }
    68 }
    View Code

     6.5枚举API

    l Java中声明的枚举类,均是java.lang.Enum类的孩子,它继承了Enum类的所有方法。常用方法:

    • Stirng name()
    • Int ordinal()
    • Enum valueof(Class enumClass, String name)

    自定义的枚举类

    • Enum valueof(String name)
    • Enum[] values() 此方法虽然在JDK文档中查找不到,但每个枚举类都具有该方法,它遍历枚举类的所有枚举值非常方便

    掌握枚举对象、枚举对象下标、枚举字符串:

     1         // 1. 输出枚举名称
     2         System.out.println(Grade.B.name());// -->B
     3 
     4         // 2. 获取枚举的位置(下标)
     5         System.out.println(Grade.B.ordinal());
     6 
     7         // 3. 字符串转换为枚举类型
     8         Grade g = Grade.valueOf("B");
     9         // Grade g = Grade.valueOf(Grade.class, "B");// 用另外一个重载方法,也可以
    10         System.out.println(g.getLocalStr());
    11         
    12         // 字符串转换为枚举类型
    13         Grade grade = Enum.valueOf(Grade.class, "B");
    14         System.out.println(grade.getLocalStr());
    15         
    16         // 4. 遍历所有的枚举值
    17         Grade[] gs = Grade.values();
    18         for (Grade myGrade : gs) {
    19             System.out.println("myGrade-->" + myGrade);
    20         }
  • 相关阅读:
    《算法导论》读书笔记(五)
    《算法导论》读书笔记(四)
    《算法导论》读书笔记(三)
    《算法导论》读书笔记(二)
    《算法导论》读书笔记(一)
    Posix消息队列
    管道和FIFO
    linux内核数据结构之kfifo
    linux内核数据结构之链表
    Mybatis XML 映射配置文件 -- 熟悉配置
  • 原文地址:https://www.cnblogs.com/OliverZhang/p/6003178.html
Copyright © 2011-2022 走看看