文章目录
Java集合、泛型和枚举
Java 9新增的不可变集合
Java 9 版本以前,假如要创建一个包含 6 个元素的 Set 集合,程序需要先创建 Set 集合,然后调用 6 次 add() 方法向 Set 集合中添加元素。Java 9 对此进行了简化,程序直接调用 Set、List、Map 的 of() 方法即可创建包含 N 个元素的不可变集合,这样一行代码就可创建包含 N 个元素的集合
如下程序示范了如何创建不可变集合
import java.util.List;
import java.util.Set;
import java.util.Map;
public class Test16 {
public static void main(String[] args) {
// TODO Auto-generated method stub
// 创建包含4个元素的Set集合
Set set = Set.of("Java", "Kotlin", "Go", "Swift");
System.out.println(set);
// 不可变集合,下面代码导致运行时错误
// set.add("Ruby");
// 创建包含4个元素的List集合
List list = List.of(34, -25, 67, 231);
System.out.println(list);
// 不可变集合,下面代码导致运行时错误
// list.remove(1);
// 创建包含3个key-value对的Map集合
Map map = Map.of("语文", 89, "数学", 82, "英语", 92);
System.out.println(map);
// 不可变集合,下面代码导致运行时错误
// map.remove("语文");
// 使用Map.entry()方法显式构建key-value对
Map map2 = Map.ofEntries(Map.entry("语文", 89), Map.entry("数学", 82), Map.entry("英语", 92));
System.out.println(map2);
}
}
Java 9中增强的“菱形”语法
在 Java 7 版本以前,如果使用带泛型的接口、类定义变量,那么调用构造器创建对象时构造器的后面也必须带泛型,这显得有些多余了。例如如下两条语句:
List<String> strList = new ArrayList<String>();
Map<String, Integer> scores = new HashMap<String, Integer>();
上面两条语句中等号右边的尖括号部分完全是多余的,Java 7 版本以前是必需的,不能省略。从 Java 7 开始,Java 允许在构造器后不带完整的泛型信息,只要给出一对尖括号<>即可。Java 可以推断出尖括号里应该是什么泛型信息。即上面两条语句可以改写为如下形式:
List<String> strList = new ArrayList<>();
Map<String, Integer> scores = new HashMap<>();
下面程序示范了 Java 7 的菱形语法
public void test5() {
// Java自动推断出ArrayList的<>里应该是String
List<String> names = new ArrayList<>();
names.add("zengraoli1");
names.add("zengraoli2");
names.add("zengraoli3");
names.forEach(ele -> System.out.println(ele));
// Java 自动推断出 HashMap 的<>里应该是 String,List<String>
Map<String, List<String>> coursesInfo = new HashMap<>();
// Java自动推断出ArrayList的<>里应该是String
List<String> courses = new ArrayList<>();
courses.add("Java入门教程");
courses.add("Python基础教程");
coursesInfo.put("C语言中文网", courses);
// 遍历 Map 时,Map 的 key 是 String 类型,value List<String>类型
coursesInfo.forEach((key, value) -> System.out.println(key + "-->" + value));
}
Java 9 再次增强了“菱形”语法,它甚至允许在创建匿名内部类时使用菱形语法,Java 可根据上下文来推断匿名内部类中泛型的类型。下面程序示范了在匿名内部类中使用菱形语法
public void test6() {
// 指定Foo类中泛型为String
Foo<String> foo = new Foo<>() {
public void test(String t) {
System.out.println("test 方法的 t 参数为:" + t);
}
};
// 使用泛型通配符,此时相当于通配符的上限为Object
Foo<?> foo2 = new Foo<>() {
// test()方法的参数类型为Object
public void test(Object t) {
System.out.println("test 方法的 Object 参数为:" + t);
}
};
// 使用泛型通配符,通配符的上限为Number
Foo<? extends Number> fn = new Foo<>() {
// 此时test ()方法的参数类型为Number
public void test(Number t) {
System.out.println("test 方法的 Number 参数为:" + t);
}
};
}
Java反射机制
通过 Java 的反射机制,程序员可以更深入地控制程序的运行过程。例如,在程序运行时由用户输入一个类名,然后动态获取该类拥有的构造、属性和方法,甚至调用任意类的任意方法
Java反射机制是什么?
编译期是指把源码交给编译器编译成计算机可以执行的文件的过程。在 Java 中也就是把 Java 代码编成 class 文件的过程。编译期只是做了一些翻译功能,并没有把代码放在内存中运行起来,而只是把代码当成文本进行操作,比如检查错误。
运行期是把编译后的文件交给计算机执行,直到程序运行结束。所谓运行期就把在磁盘中的代码放到内存中执行起来。
Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。在 Java 中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。
Java 反射机制主要提供了以下功能,这些功能都位于java.lang.reflect包。
- 在运行时判断任意一个对象所属的类。
- 在运行时构造任意一个类的对象。
- 在运行时判断任意一个类所具有的成员变量和方法。
- 在运行时调用任意一个对象的方法。
- 生成动态代理。
要想知道一个类的属性和方法,必须先获取到该类的字节码文件对象。获取类的信息时,使用的就是 Class 类中的方法。所以先要获取到每一个字节码文件(.class)对应的 Class 类型的对象
众所周知,所有 Java 类均继承了 Object 类,在 Object 类中定义了一个 getClass() 方法,该方法返回同一个类型为 Class 的对象。例如,下面的示例代码:
Class labelCls = label1.getClass(); // label1为 JLabel 类的对象
利用 Class 类的对象 labelCls 可以访问 labelCls 对象的描述信息、JLabel 类的信息以及基类 Object 的信息。表 1 列出了通过反射可以访问的信息。
类型 | 访问方法 | 返回值类型 | 说明 |
---|---|---|---|
包路径 | getPackage() | Package 对象 | 获取该类的存放路径 |
类名称 | getName() | String 对象 | 获取该类的名称 |
继承类 | getSuperclass() | Class 对象 | 获取该类继承的类 |
实现接口 | getlnterfaces() | Class 型数组 | 获取该类实现的所有接口 |
构造方法 | getConstructors() | Constructor 型数组 | 获取所有权限为 public 的构造方法 |
getDeclaredContruectors() | Constructor 对象 | 获取当前对象的所有构造方法 | |
方法 | getMethods() | Methods 型数组 | 获取所有权限为 public 的方法 |
getDeclaredMethods() | Methods 对象 | 获取当前对象的所有方法 | |
成员变量 | getFields() | Field 型数组 | 获取所有权限为 public 的成员变量 |
getDeclareFileds() | Field 对象 | 获取当前对象的所有成员变量 | |
内部类 | getClasses() | Class 型数组 | 获取所有权限为 public 的内部类 |
getDeclaredClasses() | Class 型数组 | 获取所有内部类 | |
内部类的声明类 | getDeclaringClass() | Class 对象 | 如果该类为内部类,则返回它的成员类,否则返回 null |
如上表所示,在调用 getFields() 和 getMethods() 方法时将会依次获取权限为 public 的字段和变量,然后将包含从超类中继承到的成员变量和方法。而通过 getDeclareFields() 和 getDeclareMethod() 只是获取在本类中定义的成员变量和方法
Java 反射机制的优缺点
优点:
- 能够运行时动态获取类的实例,大大提高系统的灵活性和扩展性。
- 与 Java 动态编译相结合,可以实现无比强大的功能。
- 对于 Java 这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。
缺点:
- 反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;
- 反射调用方法时可以忽略权限检查,获取这个类的私有方法和属性,因此可能会破坏类的封装性而导致安全问题。
Java 反射机制在一般的 Java 应用开发中很少使用,即便是 Java EE 阶段也很少使用
Java反射机制API
java.lang.Class 类
java.lang.Class 类是实现反射的关键所在,Class 类的一个实例表示 Java 的一种数据类型,包括类、接口、枚举、注解(Annotation)、数组、基本数据类型和 void。Class 没有公有的构造方法,Class 实例是由 JVM 在类加载时自动创建的
在程序代码中获得 Class 实例可以通过如下代码实现:
// 1. 通过类型class静态变量
Class clz1 = String.class;
String str = "Hello";
// 2. 通过对象的getClass()方法
Class clz2 = str.getClass();
每一种类型包括类和接口等,都有一个 class 静态变量可以获得 Class 实例。另外,每一个对象都有 getClass() 方法可以获得 Class 实例,该方法是由 Object 类提供的实例方法
Class 类提供了很多方法可以获得运行时对象的相关信息,下面的程序代码展示了其中一些方法
public class Test19 {
public static void main(String[] args) {
// TODO Auto-generated method stub
// 获得Class实例
// 1.通过类型class静态变量
Class clz1 = String.class;
String str = "Hello";
// 2.通过对象的getClass()方法
Class clz2 = str.getClass();
// 获得int类型Class实例
Class clz3 = int.class;
// 获得Integer类型Class实例
Class clz4 = Integer.class;
System.out.println("clz2类名称:" + clz2.getName());
System.out.println("clz2是否为接口:" + clz2.isInterface());
System.out.println("clz2是否为数组对象:" + clz2.isArray());
System.out.println("clz2父类名称:" + clz2.getSuperclass().getName());
System.out.println("clz2是否为基本类型:" + clz2.isPrimitive());
System.out.println("clz3是否为基本类型:" + clz3.isPrimitive());
System.out.println("clz4是否为基本类型:" + clz4.isPrimitive());
}
}
运行结果如下:
clz2类名称:java.lang.String
clz2是否为接口:false
clz2是否为数组对象:false
clz2父类名称:java.lang.Object
clz2是否为基本类型:false
clz3是否为基本类型:true
clz4是否为基本类型:false
java.lang.reflect 包
java.lang.reflect 包提供了反射中用到类,主要的类说明如下:
- Constructor 类:提供类的构造方法信息。
- Field 类:提供类或接口中成员变量信息。
- Method 类:提供类或接口成员方法信息。
- Array 类:提供了动态创建和访问 Java 数组的方法。
- Modifier 类:提供类和成员访问修饰符信息。
示例代码如下:
public void test2() {
try {
// 动态加载xx类的运行时对象
Class c = Class.forName("java.lang.String");
// 获取成员方法集合
Method[] methods = c.getDeclaredMethods();
// 遍历成员方法集合
for(Method method: methods) {
// 打印权限修饰符,如public、protected、private
System.out.print(Modifier.toString(method.getModifiers()));
// 打印返回值类型名称
System.out.print(" " + method.getReturnType().getName() + " ");
// 打印方法名称
System.out.println(method.getName() + "();");
}
} catch (ClassNotFoundException e) {
System.out.println("找不到指定类");
}
}
Java通过反射访问构造方法
为了能够动态获取对象构造方法的信息,首先需要通过下列方法之一创建一个 Constructor 类型的对象或者数组
- getConstructors()
- getConstructor(Class<?>…parameterTypes)
- getDeclaredConstructors()
- getDeclaredConstructor(Class<?>…parameterTypes)
如果是访问指定的构造方法,需要根据该构造方法的入口参数的类型来访问。例如,访问一个入口参数类型依次为 int 和 String 类型的构造方法,下面的两种方式均可以实现。
objectClass.getDeclaredConstructor(int.class,String.class);
objectClass.getDeclaredConstructor(new Class[]{int.class,String.class});
创建的每个 Constructor 对象表示一个构造方法,然后利用 Constructor 对象的方法操作构造方法。Constructor 类的常用方法如下表所示
方法名称 | 说明 |
---|---|
isVarArgs() | 查看该构造方法是否允许带可变数量的参数,如果允许,返回 true,否则返回 |
false
getParameterTypes() | 按照声明顺序以 Class 数组的形式获取该构造方法各个参数的类型
getExceptionTypes() | 以 Class 数组的形式获取该构造方法可能抛出的异常类型
newInstance(Object … initargs) | 通过该构造方法利用指定参数创建一个该类型的对象,如果未设置参数则表示
采用默认无参的构造方法
setAccessiable(boolean flag) | 如果该构造方法的权限为 private,默认为不允许通过反射利用 netlnstance()
方法创建对象。如果先执行该方法,并将入口参数设置为 true,则允许创建对
象
getModifiers() | 获得可以解析出该构造方法所采用修饰符的整数
通过 java.lang.reflect.Modifier 类可以解析出 getMocMers() 方法的返回值所表示的修饰符信息。在该类中提供了一系列用来解析的静态方法,既可以查看是否被指定的修饰符修饰,还可以字符串的形式获得所有修饰符。下表列出了 Modifier 类的常用静态方法。
静态方法名称 | 说明 |
---|---|
isStatic(int mod) | 如果使用 static 修饰符修饰则返回 true,否则返回 false |
isPublic(int mod) | 如果使用 public 修饰符修饰则返回 true,否则返回 false |
isProtected(int mod) | 如果使用 protected 修饰符修饰则返回 true,否则返回 false |
isPrivate(int mod) | 如果使用 private 修饰符修饰则返回 true,否则返回 false |
isFinal(int mod) | 如果使用 final 修饰符修饰则返回 true,否则返回 false |
toString(int mod) | 以字符串形式返回所有修饰符 |
下列代码判断对象 con 所代表的构造方法是否被 public 修饰,以及以字符串形式获取该构造方法的所有修饰符
int modifiers = con.getModifiers(); // 获取构造方法的修饰符整数
boolean isPublic = Modifier.isPublic(modifiers); // 判断修饰符整数是否为public
string allModifiers = Modifier.toString(modifiers);
下面通过一个案例来演示如何调用 Constructor 类的方法获取构造方法的信息
1)首先创建一个 Book 类表示图书信息。在该类中声明一个 String 型变量表示图书名称,两个 int 型变量分别表示图书编号和价格,并提供 3 个构造方法
public class Book {
String name; // 图书名称
int id, price; // 图书编号和价格
// 空的构造方法
private Book() {
}
// 带两个参数的构造方法
protected Book(String _name, int _id) {
this.name = _name;
this.id = _id;
}
// 带可变参数的构造方法
public Book(String... strings) throws NumberFormatException {
if (0 < strings.length)
id = Integer.valueOf(strings[0]);
if (1 < strings.length)
price = Integer.valueOf(strings[1]);
}
// 输出图书信息
public void print() {
System.out.println("name=" + name);
System.out.println("id=" + id);
System.out.println("price=" + price);
}
}
2)编写测试类 Test01,在该类的 main() 方法中通过反射访问 Book 类中的所有构造方法,并将该构造方法是否带可变类型参数、入口参数类型和可能拋出的异常类型信息输出到控制台
import java.lang.reflect.Constructor;
public class Test20 {
public void test1() {
// 获取动态类Book
Class book = Book.class;
// 获取Book类的所有构造方法
Constructor[] declaredContructors = book.getDeclaredConstructors();
// 遍历所有构造方法
for(int i = 0; i < declaredContructors.length; i++) {
Constructor con = declaredContructors[i];
// 判断构造方法的参数是否可变
System.out.println("查看是否允许带可变数量的参数:" + con.isVarArgs());
System.out.println("该构造方法的入口参数类型依次为:");
// 获取所有参数类型
Class[] parameterTypes = con.getParameterTypes();
for (int j = 0; j < parameterTypes.length; j++) {
System.out.println(" " + parameterTypes[j]);
}
System.out.println("该构造方法可能拋出的异常类型为:");
// 获取所有可能拋出的异常类型
Class[] exceptionTypes = con.getExceptionTypes();
for (int j = 0; j < exceptionTypes.length; j++) {
System.out.println(" " + parameterTypes[j]);
}
// 创建一个未实例化的Book类实例
Book book1 = null;
while (book1 == null) {
try {
if (i == 1) {
// 通过执行带两个参数的构造方法实例化book1
book1 = (Book) con.newInstance("Java 教程", 10); // protected
}else if (i == 2) {
// 通过执行默认构造方法实例化book1
book1 = (Book) con.newInstance(); // private
}else {
// 通过执行可变数量参数的构造方法实例化book1
Object[] parameters = new Object[] { new String[] { "100", "200" } };
book1 = (Book) con.newInstance(parameters); // Book
}
}catch (Exception e) {
System.out.println("在创建对象时拋出异常,下面执行 setAccessible() 方法");
con.setAccessible(true); // 设置允许访问 private 成员
}
}
book1.print();
System.out.println("=============================\n");
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
new Test20().test1();
}
}
3)程序运行输出
运行测试类 Test20,当通过反射访问默认构造方法 Book() 时,将看到如下所示的输出。
查看是否允许带可变数量的参数:false
该构造方法的入口参数类型依次为:
该构造方法可能拋出的异常类型为:
在创建对象时拋出异常,下面执行 setAccessible() 方法
name=null
id=0
price=0
=============================
当通过反射访问两个参数的构造方法 Book(String_name,int_id) 时,将看到如下所示的输出。
查看是否允许带可变数量的参数:false
该构造方法的入口参数类型依次为:
class java.lang.String
int
该构造方法可能拋出的异常类型为:
name=Java 教程
id=10
price=0
=============================
当通过反射访问可变参数数量的构造方法 Book(String…strings) 时,将看到如下所示的输出。
查看是否允许带可变数量的参数:true
该构造方法的入口参数类型依次为:
class [Ljava.lang.String;
该构造方法可能拋出的异常类型为:
class [Ljava.lang.String;
name=null
id=100
price=200
=============================