Java30天养成计划 || 15 快速上手Java枚举和反射

写在前面,大家好!我是【跨考菌】,一枚跨界的程序猿,专注于后台技术的输出,目标成为全栈攻城狮!这博客是对我跨界过程的总结和思考。如果你也对Java后端技术感兴趣,抑或是正在纠结于跨界,都可以关注我的动态,让我们一起学习,一起进步~
我的博客地址为:【跨考菌】的博客

上一篇 Java30天养成计划 || 14 快速上手Java网络编程 讲解了Java网络编程的相关知识点,带你了解QQ通讯的机制。本文介绍在Spring框架中非常重要的反射核心知识点和枚举相关内容。和【跨考菌】一起加油吧~

在这里插入图片描述

1、枚举

1.1 什么是枚举类型

我们学习过单例模式,即一个类只有一个实例。而枚举其实就是多例,一个类有多个实例,但实例的个数不是无穷的,是有限个数的。例如word文档的对齐方式有几种:左对齐、居中对齐、右对齐。开车的方向有几种:前、后、左、右!
  我们称呼枚举类中实例为枚举项!一般一个枚举类的枚举项的个数不应该太多,如果一个枚举类有30个枚举项就太多了!

1.2 定义枚举类型

定义枚举类型需要使用enum关键字,例如:

public enum Direction {
    FRONT, BEHIND, LEFT, RIGHT;
}
Direction d = Direction.FRONT;

注意,定义枚举类的关键字是enum,而不是Enum,所有关键字都是小写的!
其中FRONT、BEHIND、LEFT、RIGHT都是枚举项,它们都是本类的实例,本类一共就只有四个实例对象。

在定义枚举项时,多个枚举项之间使用逗号分隔,最后一个枚举项后需要给出分号!但如果枚举类中只有枚举项(没有构造器、方法、实例变量),那么可以省略分号!建议不要省略分号!

不能使用new来创建枚举类的对象,因为枚举类中的实例就是类中的枚举项,所以在类外只能使用类名.枚举项。

1.3 枚举与switch

枚举类型可以在switch中使用

		Direction d = Direction.FRONT;
		switch(d) {
		case FRONT: System.out.println("前面");break;
		case BEHIND:System.out.println("后面");break;
		case LEFT:  System.out.println("左面");break;
		case RIGHT: System.out.println("右面");break;
			default:System.out.println("错误的方向");
		}
		Direction d1 = d;
		System.out.println(d1);

注意,在switch中,不能使用枚举类名称,例如:“case Direction.FRONT:”这是错误的,因为编译器会根据switch中d的类型来判定每个枚举类型,在case中必须直接给出与d相同类型的枚举选项,而不能再有类型。

1.4 所有枚举类都是Enum的子类

所有枚举类都默认是Enum类的子类,无需我们使用extends来继承。这说明Enum中的方法所有枚举类都拥有。

  • int compareTo(E e):比较两个枚举常量谁大谁小,其实比较的就是枚举常量在枚举类中声明的顺序,例如FRONT的下标为0,BEHIND下标为1,那么FRONT小于BEHIND;
  • boolean equals(Object o):比较两个枚举常量是否相等;
  • int hashCode():返回枚举常量的hashCode;
  • String name():返回枚举常量的名字;
  • int ordinal():返回枚举常量在枚举类中声明的序号,第一个枚举常量序号为0;
  • String toString():把枚举常量转换成字符串;
  • static T valueOf(Class enumType, String name):把字符串转换成枚举常量。

1.5 枚举类的构造器

枚举类也可以有构造器,构造器默认都是private修饰,而且只能是private。因为枚举类的实例不能让外界来创建!

enum Direction {
	FRONT, BEHIND, LEFT, RIGHT; 
	
	Direction()  {
		System.out.println("hello");
	}
}

其实创建枚举项就等同于调用本类的无参构造器,所以FRONT、BEHIND、LEFT、RIGHT四个枚举项等同于调用了四次无参构造器,所以你会看到四个hello输出。

1.6 枚举类可以有成员

其实枚举类和正常的类一样,可以有实例变量,实例方法,静态方法等等,只不过它的实例个数是有限的,不能再创建实例而已。

enum Direction { // 没有了无参构造,所以不能省略。
	FRONT("front"), BEHIND("behind"), LEFT("left"), RIGHT("right");
	
	private String name;
	
	Direction(String name) {
		this.name = name;
	}
	
	public String getName() {
		return name;
	}
}
		Direction d = Direction.FRONT;
		System.out.println(d.getName());

因为Direction类只有唯一的构造器,并且是有参的构造器,所以在创建枚举项时,必须为构造器赋值:FRONT(“front”),其中”front”就是传递给构造器的参数。你不要鄙视这种语法,你应该做的是接受这种语法!
Direction类中还有一个实例域:String name,我们在构造器中为其赋值,而且本类还提供了getName()这个实例方法,它会返回name的值。

1.7 枚举类中还可以有抽象方法(了解)

还可以在枚举类中给出抽象方法,然后在创建每个枚举项时使用“特殊”的语法来重复抽象方法。所谓“特殊”语法就是匿名内部类!也就是说每个枚举项都是一个匿名类的子类对象!

通常fun()方法应该定义为抽象的方法,因为每个枚举常量都会去重写它
你无法把Direction声明为抽象类,但需要声明fun()方法为抽象方法。

enum Direction {
	FRONT() {
		public void fun() {
			System.out.println("FROND:重写了fun()方法");
		}
	}, 
	BEHIND() {
		public void fun() {
			System.out.println("BEHIND:重写了fun()方法");
		}
	}, 
	LEFT() {
		public void fun() {
			System.out.println("LEFT:重写了fun()方法");
		}
	},
	RIGHT() {
		public void fun() {
			System.out.println("RIGHT:重写了fun()方法");
		}
	};
	
	public abstract void fun() ;
}

1.8 每个枚举类都有两个特殊方法

每个枚举类都有两个不用声明就可以调用的static方法,而且这两个方法不是父类中的方法。这又是枚举类特殊的地方,下面是Direction类的特殊方法。

  • static Direction[] values():返回本类所有枚举常量;
  • static Direction valueOf(String name):通过枚举常量的名字返回Direction常量,注意,这个方法与Enum类中的valueOf()方法的参数个数不同。

2、反射

2.1 反射概述

2.1.1 什么是反射

每个.class文件被加载到内存后都是一个Class类的对象!例如Demo.class加载到内存后它是Class类型的一个对象。

在这里插入图片描述

反射就是通过Class对象获取类型相关的信息。一个Class对象就表示一个.class文件,可以通过Class对象获取这个类的构造器、方法,以及成员变量等。
反射是Java的高级特性,在框架中大量被使用!我们必须要了解反射,不然无法学好JavaWeb相关的知识!

2.1.2 反射相关类

与反射相关的类:

  • Class:表示类;
  • Field:表示成员变量;
  • Method:表示方法;
  • Constructor:表示构造器。

2.2 Class类

2.2.1 获取Class类

获取Class类的三种 基本方式:
通过类名称.class,对基本类型也支持;

  • Class c = int.class;
  • Class c = int[].class;
  • Class c = String.class
    通过对象.getClass()方法
  • Class c = obj.getClass();
    Class.forName()通过类名称加载类,这种方法只要有类名称就可以得到Class;
  • Class c = Class.forName(“cn.itcast.Demo”);

2.2.2 Class类的常用方法

  • String getName():获取类名称,包含包名;

  • String getSimpleName():获取类名称,不包含包名;

  • Class getSupperClass():获取父类的Class,例如:new Integer(100).getClass().getSupperClass()返回的是Class!但new Object().getSupperClass()返回的是null,因为Object没有父类;

  • T newInstance():使用本类无参构造器来创建本类对象;

  • boolean isArray():是否为数组类型;

  • boolean isAnnotation():是否为注解类型;

  • boolean isAnnotationPresent(Class annotationClass):当前类是否被annotationClass注解了;

  • boolean isEnum():是否为枚举类型;

  • boolean isInterface():是否为接口类型;

  • boolean isPrimitive():是否为基本类型;

  • boolean isSynthetic():是否为引用类型;

2.2.3 通过反射创建对象

public class Demo1 {
	@Test
	public void fun1() throws Exception {
		String className = "cn.itcast.User";
		Class clazz = Class.forName(className); 
		User user = (User)clazz.newInstance();
		System.out.println(user);
	}
}

class User {
	private String username;
	private String password;

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	@Override
	public String toString() {
		return "User [username=" + username + ", password=" + password + "]";
	}
}
User [username=null, password=null]

2.3 Constructor

Constructor表示一个类的构造器。即构造器的反射对象!

2.3.1 获取Constructor对象

获取Construcator对象需要使用Class对象,下面API来自Class类:

  • Constructor getConstructor(Class… parameterTypes):通过指定的参数类型获取公有构造器反射对象;
  • Constructor[] getConstructors():获取所有公有构造器对象;
  • Constructor getDeclaredConstructor(Class… parameterTypes):通过指定参数类型获取构造器反射对象。可以是私有构造器对象;
  • Constructor[] getDeclaredConstructors():获取所有构造器对象。包含私有构造器;

2.3.2 Construcator类常用方法

  • String getName():获取构造器名;
  • Class getDeclaringClass():获取构造器所属的类型;
  • Class[] getParameterTypes():获取构造器的所有参数的类型;
  • Class[] getExceptionTypes():获取构造器上声明的所有异常类型;
  • T newInstance(Object… initargs):通过构造器反射对象调用构造器。

2.3.3 练习:通过Construcator创建对象

User.java
public class User {
	private String username;
	private String password;

	public User() {
	}

	public User(String username, String password) {
		this.username = username;
		this.password = password;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	@Override
	public String toString() {
		return "User [username=" + username + ", password=" + password + "]";
	}
}

Demo1.java
public class Demo1 {
	@Test
	public void fun1() throws Exception {
		String className = "cn.itcast.User";
		Class clazz = Class.forName(className);
		Constructor c = clazz.getConstructor(String.class, String.class); 
		User user = (User)c.newInstance("zhangSan", "123"); 
		System.out.println(user);
	}
}

2.4 Method

Method表示方法的反射对象

2.4.1 获取Method

获取Method需要通过Class对象,下面是Class类的API:

  • Method getMethod(String name, Class… parameterTypes):通过方法名和方法参数类型获取方法反射对象,包含父类中声明的公有方法,但不包含所有私有方法;
  • Method[] getMethods():获取所有公有方法,包含父类中的公有方法,但不包含任何私有方法;
  • Method getDeclaredMethod(String name, Class… parameterTypes):通过方法名和方法参数类型获取本类中声明的方法的反射对象,包含本类中的私有方法,但不包含父类中的任何方法;
  • Method[] getDeclaredMethods():获取本类中所有方法,包含本类中的私有方法,但不包含父类中的任何方法。

2.4.2 Method常用方法

  • String getName():获取方法名;
  • Class getDeclaringClass():获取方法所属的类型;
  • Class[] getParameterTypes():获取方法的所有参数的类型;
  • Class[] getExceptionTypes():获取方法上声明的所有异常类型;
  • Class getReturnType():获取方法的返回值类型;
  • Object invode(Object obj, Object… args):通过方法反射对象调用方法,如果当前方法是实例方法,那么当前对象就是obj,如果当前方法是static方法,那么可以给obj传递null。args表示是方法的参数;

2.4.3 练习:通过Method调用方法

public class Demo1 {
	@Test
	public void fun1() throws Exception {
		String className = "cn.itcast.User";
		Class clazz = Class.forName(className);
		Constructor c = clazz.getConstructor(String.class, String.class);
		User user = (User)c.newInstance("zhangSan", "123");
		
		Method method = clazz.getMethod("toString"); 
		String result = (String)method.invoke(user); 
		System.out.println(result); 
	}
}

2.5 Field

Field表示类的成员变量,可以是实例变量,也可以是静态变量。

2.5.1 获取Field对象

获取Field对象需要使用Class对象,下面是Class类的API:

  • Field getField(String name):通过名字获取公有成员变量的反射对象,包含父类中声明的公有成员变量;
  • Field[] getFields():获取所有公有成员变量反射对象,包含父类中声明的公有成员变量;
  • Field getDeclaredField(String name):通过名字获取本类中某个成员变量,包含本类的private成员变量,但父类中声明的任何成员变量都不包含;
  • Field[] getDeclaredFields():获取本类中声明的所有成员变量,包含private成员变量,但不包含父类中声明的任何成员变量;

2.5.2 Field类的常用方法

  • String getName():获取成员变量名;
  • Class getDeclaringClass():获取成员变量的类型;
  • Class getType():获取当前成员变量的类型;
  • Object get(Object obj):获取obj对象的成员变量的值;
  • void set(Object obj, Object value):设置obj对象的成员变量值为value;

2.5.3 练习:通过Field读写成员

User.java
public class User {
	public String username;
	public String password;

	public User() {
	}

	public User(String username, String password) {
		this.username = username;
		this.password = password;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	@Override
	public String toString() {
		return "User [username=" + username + ", password=" + password + "]";
	}
}


Demo1.java
public class Demo1 {
	@Test
	public void fun1() throws Exception {
		String className = "cn.itcast.User";
		Class clazz = Class.forName(className);
		User user = new User("zhangSan", "123");
		
		Field field1 = clazz.getField("username"); 
		Field field2 = clazz.getField("password") ;
		
		String username = (String)field1.get(user); 
		String password = (String)field2.get(user); 
		
		System.out.println(username + ", " + password);
		
		field1.set(user, "liSi"); 
		field2.set(user, "456"); 
		
		System.out.println(user);
	}
}

2.6  AccessibleObject

AccessibleObject类是Constructor、Method、Field三个类的父类。AccessibleObject最为重要的方法如下:

  • boolean isAccessible():判断当前成员是否可访问;
  • void setAccessible(boolean flag):设置当前成员是否可访问。

当Constructor、Method、Field为私有时,如果我们想反射操作,那么就必须先调用反射对象的setAccessible(true)方法,然后才能操作。

User.java
public class User {
	private String username;
	private String password;

	public User() {
	}

	public User(String username, String password) {
		this.username = username;
		this.password = password;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	@Override
	public String toString() {
		return "User [username=" + username + ", password=" + password + "]";
	}
}

注意,User类的username和password成员变量为private的,这时再通过Field来反射操作这两个成员变量就必须先通过setAccessible(true)设置后才行。

public class Demo1 {
	@Test
	public void fun1() throws Exception {
		String className = "cn.itcast.User";
		Class clazz = Class.forName(className);
		User user = new User("zhangSan", "123");
		
		Field field1 = clazz.getDeclaredField("username");
		Field field2 = clazz.getDeclaredField("password");
 		
		field1.setAccessible(true);
		field2.setAccessible(true);
 		
		String username = (String)field1.get(user);
		String password = (String)field2.get(user);
		
		System.out.println(username + ", " + password);
		
		field1.set(user, "liSi");
		field2.set(user, "456");
		
		System.out.println(user);
	}
}


在这里插入图片描述

最后,如果您觉得对您有帮助的话,不要忘记帮助帮博主一键三连😊哦

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值