zoukankan      html  css  js  c++  java
  • Java复习——反射和泛型的复习

    反射

    Class类

    一个类被类加载器加载到内存之中,占有一片区域,这个空间里的内容就是类的字节码,不同的类的字节码是不一样的,这一个个空间页可以使用类来表示,这就是Class类。

    根据这个概念可知:不同类对应的Class类是不一样的,同一类的不同对象对应的Class类则是一样的

    九大预定义对象

    这九大指的是基本的 Java 类型(booleanbytecharshortintlongfloatdouble)和关键字 void 也可以表示为 Class 对象,这里值得注意的一点是:Java基本类型和其包装类的Class类型是不同的。

    数组类型的Class对象

    我们知道数组也作为一种对象存在,具有相同个数和类型的数组属于同一类型,具有相同的Class对象,我们可以调用数组的Class类中的getSuperClass方法来获得数组的父类,可以看到是Object,所以基本类型的数组可以为Object使用,但是不可以作为Object[]使用。

    在框架的设计中很多都用到了反射,比如spring中我们在applicationContext.xml中配置类,通过反射+工厂模式得到类的实例,就可以操作类了——操作一个类可以分为操作属性,构造方法,普通方法。在最后我通过反射完成一个解析properties文件来创建集合的小例子,这也是框架中经常使用的通过配置文件创建类的功能。

    反射的原理

    我们编写的Java文件是xxx.java保存在硬盘上的,只是.java是无法运行的,需要编译成.class文件,JVM再将这个class文件通过类加载器加载到内存中,这个class文件在内存中是以Class类的形式表示。所以,使用反射的第一步是得到Class类(因为.java文件和没有加载到内存的.class文件计算机都是无法操作的),得到Class类就可以操作类的内容了(属性,构造方法,普通方法)

    属性类:Field    构造方法类:Constructor  普通方法类:Method

    获得Class类

    方法一:类名.class

    Class clazz1=Person.class;

    方法二:实例.getClass()

    Class clazz2=new Person().getClass();

    方法三:Class.forName("类的全路径")     最常用

    Class clazz3=Class.forName("cn.lynu.model.Person");

    实例化对象

    如果不使用new的方式,如何获得一个类的实例化?  那就是使用反射的newInstance()方法

    Class clazz3=Class.forName("cn.lynu.model.Person");
    Person person=(Person)clazz3.newInstance();

    使用反射操作无参构造

        /**
         * 反射操作无参构造函数
         * @throws Exception 
         */
        @Test
        public void fun1() throws Exception{
            Class clazz = Class.forName("cn.lynu.model.Person"); //得到class类
            Person p1 = (Person) clazz.newInstance();          //通过反射得到实例
            p1.setName("lz");
            System.out.println(p1.getName());
        }

    这里我通过反射得到Person类的实例p1,通过p1给Name属性赋值

    使用反射操作有参构造

    主要使用的是getConstructor(构造方法参数的Class类)获得Constructor类,再通过Constructor类实例化,实例化的过程就可以给值了

        /**
         * 反射操作有参构造函数
         * @throws Exception 
         */
        @Test
        public void fun2() throws Exception{
            Class clazz = Class.forName("cn.lynu.model.Person");
            //使用getConstructor(可变参数 参数类型的class类)得到构造函数类
            Constructor constructor = clazz.getConstructor(String.class,int.class);
            Person p2 = (Person) constructor.newInstance("lz",21);
            System.out.println(p2.getName()+" "+p2.getAge());
        }

    使用反射操作属性

    主要使用的是getDeclaredField("属性名")得到Field类,通过Field类设置属性的值(set()方法),对了,不要忘了使用 setAccessible(true); 就可以操作私有的属性(属性一般都是私有的)

        /**
         * 反射操作属性
         * @throws Exception 
         */
        @Test
        public void fun3() throws Exception{
            Class clazz = Class.forName("cn.lynu.model.Person");
            //通过getDeclaredField(属性名)得到属性类
            Field age = clazz.getDeclaredField("age"); 
            //私有属性需要设置setAccessible(true),必须在设置参数之前
            age.setAccessible(true);
            //反射实例化对象
            Person p3 = (Person) clazz.newInstance();
            age.set(p3, 21);  //设置参数
            System.out.println(p3.getAge());
        }

    问题:如何通过反射调用类中静态的属性?  答案就是在set方法的时候,第一个参数给个null即可

    使用反射操作普通方法

    主要使用getDeclaredMethod("方法名","参数Class类")获得Method类,再通过Method类的invoke()方法给方法赋值。如果操作的是私有的方法也需要设置setAccessible(true); 。当操作的是静态的方法时候,因为静态方法调用的方式是 类名.方法名,所以使用反射操作静态方法的时候不需要实例  m1.invoke(null,"lz");

        /**
         * 反射操作普通函数
         * @throws Exception 
         */
        @Test
        public void fun4() throws Exception{
            Class<?> clazz = Class.forName("cn.lynu.model.Person");
            //使用getDeclaredMethod(方法名,方法参数Class类)得到方法类
            Method name = clazz.getDeclaredMethod("setName", String.class);
            Person p4 = (Person) clazz.newInstance();   //反射实例化
            name.invoke(p4, "lz");     //给普通方法设置参数
            System.out.println(p4.getName());
        }

    问题:如何通过反射调用类中静态方法?  答案就是invoke方法第一个参数传null

    反射的小例子:

    properties文件:

    className=java.util.HashSet

    解析配置文件通过反射创建集合:

        @Test
        public void fun()throws Exception{
            InputStream inStream=new FileInputStream("src/config.properties");
            Properties properties = new Properties();
            properties.load(inStream);
            inStream.close();
            String className=properties.getProperty("className");
            Collection collection=(Collection)(Class.forName(className).newInstance());
            collection.add("a");
            collection.add("b");
            collection.add("a");
            System.out.println(collection.toString());
        }

    泛型

    泛型多用于集合之上,例如这样的场景:将一个字符串类型的值放入到集合中,这个值就会失去其类型,统一为Object类型,而后再将这个值取出进行类型转换,就容易出现类型转换异常,因为Object可以转换为任何类型,要解决这样的问题就需要使用泛型

    泛型语法

    一般情况下我们使用T表示泛型,但这不是强制要求。注意:给这个T赋具体类型的时候要使用基本类型的包装类

    1. 在集合上使用  集合<具体类型>
    2. 在返回值和形参上使用
    3. 在类上使用 class A<T>
    4. 在方法上使用 public <T> void fun1(T t1){}      public <T> T fun2(T t1,T t2){}
    5. 在局部变量(因为要使用T,就需要在已定义过的地方,所以可以在泛型方法和泛型类的成员属性使用)上使用   
    public <T> void fun1(){
            T abc;
    }
    
    class a<T>{
            T bcd;
    }

    泛型的擦除

    泛型是给编译器看的,用于限定参数的类型,让编译器挡住非法参数输入,但是通过编译之后,编译器在编译代码的时候就不存在泛型了,因为被擦除掉了,使程序效率不受影响

    在集合中使用泛型

    这应该是最常见的一个应用场景了,我们在定义一个集合的时候编译器提醒我们使用泛型,我们使用的泛型的具体类型一定要是引用类型,不能是基本数据类型,如int,float之类的,但是我们可以将基本数据类型的值添加到泛型是其包装类的集合中,这是因为JDK5自动拆装箱的存在:

    List<String> list1=new ArrayList<String>();     //正确
    
    List<Integer> list2=new ArrayList<Integer>();   //正确
    
    List<int> list3=new ArrayList<int>();      //错误

    为了证明泛型只存在编译阶段,我们可以使用反射来跳过编译,并向集合中添加不同于指定泛型类型的值:

    //指定集合的泛型为Integer , 说明这个集合只可以添加Integer类型的值(int也可以,自动拆装箱)
    List<Integer> list4=new ArrayList<Integer>();
    
    //使用反射向list4中添加一个String(破环了泛型的编译期类型限制)
    List.class.getMethod("add",Object.class).invoke(list4,"abc");

    这个结果完全没有问题,可见泛型只存在于编译阶段

    集合中泛型的注意点

    我们可以将有 “<类型>” 这样的类型称之为参数化类型,没有的称之为原始类型

    1.参数化类型和原始类型相互兼容,不论是将一个参数化类型给原始类型,还是反过来,都不会出错,只是会有一个警告

    // 参数化类型给原始类型不报错,会警告
    List list = new ArrayList<String>();
    
    // 原始类型给参数化类型不报错,会警告
    List<String> list2 = new ArrayList();

    在编译阶段,调用这个集合是以赋值号左边的变量为准,没加泛型就是Object类型,加了泛型就以实际的泛型类型为准

    2.参数化类型不考虑继承关系

    也就是说即使赋值号左右两边的泛型类型存在继承关系,也无法通过编译

    List<Object> list3=new ArrayList<Integer>();   //错误
    
    List<String> list3=new ArrayList<Object>();   //也错误

    基于上面两点,我们来看一个例子:

    List l1=new ArrayList<String>();
    
    List<Object> l2=l1;

    这两句会通过编译吗? 答案是会  原因自己想想吧

    最后再来看一个复杂点的集合中泛型的使用:

    我们使用map的entrySet()方法获得存在key-value映射关系的Set集合,遍历并输出,相当于另一种得到map中元素的方法

    //使用泛型演示对map的操作
            Map<String, Integer> map=new HashMap<String, Integer>();
            map.put("a", 1);
            map.put("b", 2);
            map.put("c", 3);
            
            //entrySet()返回此映射中包含的映射关系的set集合
            Set<Entry<String, Integer>> entrySet = map.entrySet();
            for(Entry<String, Integer> entry:entrySet){
                System.out.println(entry.getKey()+":"+entry.getValue());
            }

    Entry类型的全限定名是 java.util.Map.Entry 因为map中 获得key-value映射关系的情况经常发生,所以java提供了这个类型

    通配符 ?

    使用通配符可以比使用泛型更增强通用性,?表示通配符,它被限定在  <> 中  ,例如<?> 。通配符的使用场景只能在方法的形参中,虽然通配符增强了通用性,但是限制或者是说局限也多,例如变量不可以使用

    通配符可以分为:

    无限定通配符:<?>

    子类限定通配符:<? extends Number>  表示泛型类型只能是Number 或者是其子类

    父类限定通配符:<? super Integer>  表示泛型类型只能是Integer或者是其父类

    一个使用通配符的例子:

        public static void main(String[] args) throws Exception {
    
            List<Integer> list= new ArrayList<Integer>();
            list.add(1);        
    
            fun1(list);
            
        }
    
        /**
         * 使用通配符增强通用性
         * @param list
         */
        public static void fun1(List<? extends Number> list){
           list.get(0);
        }

    泛型类似于C++中模板

    Java中的泛型像C++中模板一样使用,但是其本质还是有区别的,Java中的泛型并没有C++的模板那么强大,我们之前说了,泛型只存在与编译阶段,等到运行期就不存在泛型了,但是我们还是可以利用泛型来增强一些方法和类的通用性。

    下面是一个使用泛型进行数组元素颠倒顺序的例子,使用泛型增加了通用性

    import java.util.Arrays;
    
    public class TestDemo3 {
    
        public static void main(String[] args) {
            Integer[] arr1={1,2,3,4,5,6};
            System.out.println(Arrays.toString(arr1));
            reverse(arr1);
            System.out.println(Arrays.toString(arr1));
            System.out.println("==================");
            String[] arr2={"aaa","bbb","ccc","eee","fff"};
            System.out.println(Arrays.toString(arr2));
            reverse(arr2);
            System.out.println(Arrays.toString(arr2));
        }
        
        /**
         * 使用泛型颠倒数组元素
         */
        public static <T> void reverse(T[] arr){
            for(int i=0;i<(arr.length/2);i++){
                T temp=arr[i];
                arr[i]=arr[arr.length-1-i];
                arr[arr.length-1-i]=temp;
            }
        }
    
    }

    reverse()方法使用泛型,所以我们不管数组到底是什么类型的,都可以完成元素的颠倒操作

    区分泛型类中的方法和泛型方法

    泛型类中的方法:

    class A<T>{
      public T fun1(T t1){}       //不是泛型方法,只是泛型类中的一个方法
    }

    泛型方法:

    public <T>  T fun1(T t1){}        //泛型方法

    这俩个fun1方法表达意思是不一样的,泛型方法与泛型类没什么关系,泛型方法不一定要在泛型类中,泛型类中也不一定都是泛型方法

    泛型的继承和实现

    泛型可以认为不可以继承,子类不是泛型类:

    class A<T>{}
    
    class AA1 extends A<String>  {}        //这个AA1不是泛型类,只是其父类A是泛型类(必须在声明的时候需要指定父类泛型的具体类型)

    这个例子中AA1不是泛型类,只是其父类泛型类,AA1在声明的时候需要指定父类泛型的具体类型

    子类是泛型类 :

    class A<T>{}
    
    class AA2<T> extends A<T> {}          //子类也是泛型类

    这里子类AA2是泛型类,继承的父类A也是泛型类,子类也是泛型类情况下,可以实例化AA2的时候再指明泛型的具体类型

    public class TestDemo5{
        public static void main(String[] args) {
            AA1 aa1=new AA1();
            aa1.fun2(t1);        //t1为Integer类型
            
            AA2<String> aa2=new AA2<String>(); //泛型类在实例化时指定类型
            aa2.fun2(t1);        //t1为String类型
        }
    }
    
    class A<T>{
        
        private T ab;
        
        public void fun1(){}
        
        public void fun2(T t1){}
        
        public void fun3(T t1,T t2){}
    }
    
    class AA1 extends A<Integer>{     //继承于泛型类A,AA1不是泛型类
        
    }
    
    class AA2<T> extends A<T>{        //继承于泛型类A,AA2是泛型类
        
    }
  • 相关阅读:
    MSBuild最佳实践
    Javascript:阻止浏览器默认右键事件,并显示定制内容
    zeptoJS:如何像jQuery一样,让滚动变得优雅?
    Javascript:DOM表格操作
    Javascript:getElementsByClassName
    Javascript:DOM动态创建元素实例应用
    Javascript:倒计时
    Javascript:sort()方法快速实现对数组排序
    探究css !important的应用之道
    Javascript:splice()方法实现对数组元素的插入、删除、替换及去重
  • 原文地址:https://www.cnblogs.com/lz2017/p/7141232.html
Copyright © 2011-2022 走看看