Java基础(9)

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
=============================

参考链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值