JAVA基础复习(四)

    本着重新学习(看到什么复习什么)的原则,这一篇讲的是JAVA的反射。看了诸位大神的解释后详细的查了一些东西,记录下来,也感谢各位在网络上的分享!!!

    根据之前学习的泛型引出了JAVA的另一个高级特性——反射。JAVA反射机制即是在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。也就是说可以通过对反射机制的使用读取和调用某个类中的相关方法或者属性变量,也可以通过对其中是否包含某种方法的判定等进行类方法的选择和调用。

    为了更加详细的了解和解释反射,需要明确以下几种对象:

    1.Class对象:

    class和Class是有区别的(JAVA大小写敏感),Class代表的是类的实体,在运行中每加载一个class,JVM就为其创建一个Class类型的实例,在Class实例中存储了对应的类或接口的类型信息或者方法信息,从而把信息和对相应的Class实例关联起来。比如JVM在加载String类的时候会先读取String.class,而后便会为其创建一个Class类型的实例(Class classString = new Class(String))。根据源码可知,Class类的构造方法是private的,也就是说不能通过new操作符创建一个传入空参的Class实例,所以只能由JVM进行创建。而Class类的原理就是因为所有类都是集成自Object类,而在Object类中定义了getClass()方法用于取得运行时的类型引用,引用指向的就是Class类的对象。并且通过Class实例来获取class信息的这种方式就是反射。

    /*
     * Private constructor. Only the Java Virtual Machine creates Class objects.
     * This constructor is not used and prevents the default constructor being
     * generated.
     */
    private Class(ClassLoader loader) {
        // Initialize final field for classLoader.  The initialization value of non-null
        // prevents future JIT optimizations from assuming this final field is null.
        classLoader = loader;
    }
    /**
     * Returns the runtime class of this {@code Object}. The returned
     * {@code Class} object is the object that is locked by {@code
     * static synchronized} methods of the represented class.
     *
     * <p><b>The actual result type is {@code Class<? extends |X|>}
     * where {@code |X|} is the erasure of the static type of the
     * expression on which {@code getClass} is called.</b> For
     * example, no cast is required in this code fragment:</p>
     *
     * <p>
     * {@code Number n = 0;                             }<br>
     * {@code Class<? extends Number> c = n.getClass(); }
     * </p>
     *
     * @return The {@code Class} object that represents the runtime
     *         class of this object.
     * @jls 15.8.2 Class Literals
     */
    public final native Class<?> getClass();

    Class类提供了很多方法用于获取类信息,其中最常用的就是获取Class对象。

    1.可以调用Class类的ForName方法

    2.可以调用对应对象的getClass方法

    3.可以使用对应对象的类名.class

    4.可以调用类加载器ClassLoader对象的loadClass方法

package com.day_4.excercise_1;

public class TryReflection {

    public static void main(String[] args) throws Exception{
    	// 获取Class对象
    	Class<?> aClass = Class.forName("java.lang.String");
    	String bString = "bClass";
    	Class<?> bClass = bString.getClass();
    	Class<?> cClass = String.class;
    	Class<?> dClass = ClassLoader.getSystemClassLoader().loadClass("java.lang.String");
    	
    	System.out.println("1."+aClass);
    	System.out.println("2."+bClass);
    	System.out.println("3."+cClass);
    	System.out.println("4."+dClass);

    	// Class对象对比和instanceof对比
    	Integer intnum = new Integer(1);
    	System.out.println(intnum instanceof Integer);
    	System.out.println(intnum instanceof Number);
    	System.out.println(intnum.getClass()==Integer.class);
//    	System.out.println(intnum.getClass()==Number.class);
    }
}

    此外,要注意instanceof不只匹配当前类型,还匹配当前类型的子类,并且instanceof判定类型是调用的无参构造器,所以两个instanceof的返回结果都是true。但是"=="只精确判断类型,不能做子类的比较。

    Class类中还定义了很多方法用于获取该类的信息,如获取包名的getPackage(),获取类名的getSimpleName(),获取该类继承的父类名的getSuperclass()和获得该类实现的类或接口名的getInterfaces()等等。由于JVM在加载class时都是动态加载的,所以可以实现在运行期间根据不同的条件加载不同的实现类。

    最近看到了ClassLoader和Class.ForName的区别,记录一下:

    Class.forName除了将类加载到虚拟机中以外还将类进了初始化,而ClassLoader的loadClass并没有对类进行初始化,只是把类加载到了虚拟机中。

package com.day_4.excercise_1;

public class TestClassLoaderForName {
	static {
		System.out.println("testStatic");
	}	
	public static String getStaticString = get();
	public static String get() {
		System.out.println("testStaticFunction");
		return "testString";
	}
}
package com.day_4.excercise_1;

public class TryClass2 {
	public static void main(String args[]) {
		// 1 
		try {
			Class.forName("com.day_4.excercise_1.TestClassLoaderForName");
			System.out.println("testClassForName");
		} catch (Exception e) {
			e.printStackTrace();
		}
		// 2
		try {
			ClassLoader.getSystemClassLoader().loadClass("com.day_4.excercise_1.TestClassLoaderForName");
			System.out.println("testClassLoader");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

    在使用场景1时执行了静态方法,而在使用场景2时没有执行静态方法。

    2.Field对象:

    Field对象提供类或者接口内定义的单个字段的信息,包括名称,类型,修饰符,访问权限,字段值等。

    可以通过Class类的以下几种方法获取字段Field对象。

    1.可以调用getField(name)方法:方法返回名称为name的public类型的Field对象,包括从父类中继承的Field对象

    2.可以调用getDeclaredField(name)方法:方法返回名称为name的当前类的某个Field对象,不关注访问权限类型,不包括从父类中继承的Field对象

    3.可以使用getFields()方法:方法返回所有的public类型的Field对象,包括从父类中继承的Field对象

    4.可以调用getDeclaredFields()方法:方法返回所有的当前类的Field对象,不关注访问权限类型,不包括从父类中继承的Field对象

    看一个实例,我定义了一个Pet类,其中age字段是public类型,price字段是private类型,为了区分方法返回值。

package com.day_4.excercise_1;

public class Pet {
	public Integer age;
	private Integer price;
	public Pet() {
	}
	private Pet(Integer age,Integer price) {
		this.age = age;
		this.price = price;
	}
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	public Integer getPrice() {
		return price;
	}
	public void setPrice(Integer price) {
		this.price = price;
	}
	public String toString() {
		return "Pet(" + age + "," + price + ")";
	}
}
package com.day_4.excercise_1;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class TryField {
	public static void showInfo(Field field) {
		// 字段名称
		System.out.println(field.getName());
		// 字段类型
		System.out.println(field.getType());
		// 字段修饰符
		System.out.println(field.getModifiers());
		// 字段修饰符是否包含private修饰符,其他同理
		System.out.println(Modifier.isPrivate(field.getModifiers()));
		System.out.println(Modifier.isPublic(field.getModifiers()));
		System.out.println(Modifier.isStatic(field.getModifiers()));
		System.out.println(Modifier.isFinal(field.getModifiers()));
		System.out.println(Modifier.isAbstract(field.getModifiers()));
	}
	public static void main(String args[]) throws Exception {
		Pet pet = new Pet();
		Class<?> PetClass = pet.getClass();
		Field petField = PetClass.getField("age");
		Field petPrivateField = PetClass.getDeclaredField("price");
		// 1.
		showInfo(petField);
		// 2.
		showInfo(petPrivateField);
		// 3.
		pet.setAge(5);
		pet.setPrice(100);
		// B.
		petPrivateField.setAccessible(true);
		// A.
		System.out.println(petField.get(pet));
		System.out.println(petPrivateField.get(pet));
		System.out.println(Modifier.isPrivate(petPrivateField.getModifiers()));
		System.out.println(Modifier.isPublic(petPrivateField.getModifiers()));
		petPrivateField.set(pet, 200);
		System.out.println(petPrivateField.get(pet));
	}
}

    在showInfo方法中接收Field类型对象,从而打印出对应的字段信息。还是一样,数字是场景,大写字母是关键。首先创建一个Pet类的对象,而后通过getClass方法获取pet的类对象,而后获取使用public修饰的age字段,即petField对象;使用private修饰的price字段,即petPrivateField对象。这里就使用了可分别只能获取public类型的字段的getField方法和能获取所有类型字段的getDeclaredField方法。这是因为如果依然使用getField方法获取private变量price,会抛出相应异常,即无法找到public类型的对应字段。当使用场景1场景2时,通过打印信息可以清晰知道每一个字段的对应信息,也可以通过Modifier类的对应判断方法返回boolean类型结果。当使用(3,A)时,会抛出异常,这是因为无法直接访问private修饰的字段,使用public修饰的字段age是可以打印出相应的信息的。当使用(3,A,B)时,可以让我们访问private修饰字段,即通过对字段的访问权限进行暂时的放权,使得我们可以对其进行操作,但是去检查字段的修饰符依然是不会发生改变,还是一个private类型的变量。当然,可以使用get方法获取变量值,也可以使用set方法设定字段值,这里就不赘述了。关于获取父类定义的变量的例子我就没贴出来,比较简单,可以自行尝试。

    public void setAccessible(boolean flag) throws SecurityException {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) sm.checkPermission(ACCESS_PERMISSION);
        setAccessible0(this, flag);
    }

    在这里要注意,在setAccessible()方法内部可以看到是接收了一个SecurityManager对象来管理是否可以安全访问的,即是否可以直接访问而不需要进行所谓的访问权限检查。而通过setAccessible()来允许了对私有变量的访问或者赋值,这在实际操作中是最好要避免的

    3.Method对象:

    Method对象的获取方式同Field字段的获取方式相同,所存储的信息也相应变为方法的信息。

    可以通过Class类的以下几种方法获取字段Method对象,参数中第一个参数为方法名称,第二个参数为该方法对应的参数列表。

    1.可以调用getMethod(name,Class<?>... parameterTypes)方法:方法返回名称为name的public类型的Method对象,包括从父类中继承的Method对象

    2.可以调用getDeclaredMethod(name,Class<?>... parameterTypes)方法:方法返回名称为name的当前类的某个Method对象,不关注访问权限类型,不包括从父类中继承的Method对象

    3.可以使用getMethods()方法:方法返回所有的public类型的Method对象,包括从父类中继承的Method对象

    4.可以调用getDeclaredMethods()方法:方法返回所有的当前类的Method对象,不关注访问权限类型,不包括从父类中继承的Method对象

    还是看一个实例。

package com.day_4.excercise_1;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.Arrays;

public class TryMethod {
	public static void showInfo(Method method) {
		System.out.println(method);
		// 方法名
		System.out.println(method.getName());
		// 方法返回类型
		System.out.println(method.getReturnType());
		// 方法参数列表
		System.out.println(Arrays.toString(method.getParameters()));
		// 方法修饰符
		System.out.println(method.getModifiers());
	}
	public static void main(String args[]) throws Exception {
		Pet pet = new Pet();
		Class<?> PetClass = pet.getClass();
		// 1.
		Method ageMethod = PetClass.getMethod("setAge",Integer.class);
		Method priceMethod = PetClass.getMethod("setPrice",Integer.class);
		showInfo(ageMethod);
		ageMethod.invoke(pet, 1);
		priceMethod.invoke(pet, 200);
		System.out.println(pet.toString());
		// 2.
		BeanInfo beanInfo = Introspector.getBeanInfo(PetClass);
		for(PropertyDescriptor pDescriptor : beanInfo.getPropertyDescriptors()) {
			System.out.println(pDescriptor.getName());
			showInfo(pDescriptor.getReadMethod());//pDescriptor.getWriteMethod()
		}
	}
}

    在实例中,showInfo方法返回方法的对应信息。在使用场景1时,可以通过Class对象的getMethod方法获取到该类中对应getMethod方法第一个参数名称的方法,如果想调用该方法,在反射的场景下使用invoke方法进行调用,第一个参数是该方法所处的类,第二个之后的参数为参数列表。在使用场景2时,即如示例中所示,实例化的类是一个javaBean,便可以使用java.beans.BeanInfo及其相关类进行get/set方法的获取等操作,使用的就是getReadMethod和getWriteMethod。同访问私有变量相同,访问私有方法同样可以使用setAccessible方法来使用或者访问,但是依然不建议使用。另外需要注意的是,JAVA的多态特性还是依然保持的,即若我们从Pet.class获取的Method对象,作用于其子类,如Cat类,实际调用的是Cat类中的覆写方法。

    4.Constructor对象:

    Constructor对象包含了一个构造方法的所有信息,可以通过该对象创建一个实例。

    可以通过Class类的以下几种方法获取字段Constructor对象。(要注意的是构造方法与上述方法或变量不同,不可能获取到父类的构造方法,因为他们之间没有必然关系)

    1.可以调用getConstructor(Class<?>... parameterTypes)方法:方法返回某个public类型的Constructor对象

    2.可以调用getDeclaredConstructor(Class<?>... parameterTypes)方法:方法返回某个Constructor对象,不关注访问权限类型

    3.可以使用getConstructors()方法:方法返回所有的public类型的Constructor对象

    4.可以调用getDeclaredConstructors()方法:方法返回所有的Constructor对象,不关注访问权限类型

    还是看一个实例。

package com.day_4.excercise_1;

import java.lang.reflect.Constructor;
import java.util.Arrays;

public class TryConstructor 
	public static void showInfo(Constructor constructor) {
		System.out.println(constructor);
		// 构造方法参数列表
		System.out.println(Arrays.toString(constructor.getParameters()));
		// 构造方法修饰符
		System.out.println(constructor.getModifiers());
	}
	public static void main(String args[]) throws Exception {
		Class<?> PetClass = Pet.class;
		Constructor<?> noParameterConstructor = PetClass.getDeclaredConstructor();
		Constructor<?> intParameterConstructor = PetClass.getDeclaredConstructor(Integer.class,Integer.class);
		intParameterConstructor.setAccessible(true);
		showInfo(noParameterConstructor);
		showInfo(intParameterConstructor);
		// 1.
		Pet noclassPet = (Pet) PetClass.newInstance();
//		Pet intclassPet = (Pet) PetClass.newInstance(5,100);
		
		Pet noconstructorPet = (Pet) intParameterConstructor.newInstance();
		Pet intconstructorPet = (Pet) intParameterConstructor.newInstance(5,100);
		System.out.println(intconstructorPet.toString());
	}
}

    在实例中,showInfo方法返回构造方法的对应信息。noParameterConstructor对应Pet类中的无参构造函数,intParameterConstructor对应Pet类中的有参构造函数。同样由于有参构造函数我定义为了private类型,故需要setAccessible方法给定访问权限。另外,通过对场景1的使用,可以得知当使用Class对象的newInstance方法时只能使用无参构造函数。而使用构造器Constructor的newInstance方法则可以全部调用。

    除上述之外,Class对象中还提供了很多其他的方法,如获取继承关系等。

    getSuperClass():获取父类的Class对象。(Object的父类是null,interface的父类是null)

    getInterfaces():获取当前类直接实现的接口,重点是当前类和直接实现,即不包括间接实现的接口。(如果本来就是interface,那么返回继承的interfaces)

    isAssignableFrom(Class):判断向上转型是否成立。(判定Pet pet = new Cat()能否成立,使用方法语句为:Pet.class.isAssignableFrom(Cat.class),当然该结果为true,反之则为false)

    了解到了反射的一些优点和使用,我也了解了一下反射的缺点。其一就是由于是动态类型,所以JVM只能选择牺牲效率来对其进行编译。所以最好避免在对性能要求很高的程序中使用反射。其二就是如setAccessible等方法的出现会导致原有的结构被打破,从而使得很多私有变量或者方法可以被使用,这是很不安全的。

    最后就是最重要的,那就是什么时候会应用反射机制。目前我看到的是,大部分个人代码编写中都不会使用反射,而在很多框架中都有反射的使用,如配置文件。

    有了前车之鉴,看了点视频,再查询果然有了针对性,再去查一些边边角角的知识点也有了侧重,学习方式还是应该保持。同之前所有文章相同,感谢大家的观点和讲解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无语梦醒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值