今日分享开始啦,请大家多多指教~
今天给大家分享一下装箱和拆箱以及 Java 反射机制。Java 为每种基本数据类型都提供了对应的包装型,而且还提供了包装类和基本数据类型之间的相互转化机制,也就是所谓的“装箱”和“拆箱”。Java 反射机制其实是动态地获取信息以及动态调用对象的功能。今天将从多方面进行总结,以便大家更好地认识及应用。
一、什么是装箱?什么是拆箱?
在 Java SE5 之前,如果要生成一个数值为 10 的 Integer 对象,必须这样进行:
Integer i = new Integer(10);
而在从 Java SE5 开始就提供了自动装箱的特性,如果要生成一个数值为 10 的 Integer 对象,只需要这样就可以了:
Integer i = 10;
这个过程中会自动根据数值创建对应的 Integer 对象,这就是装箱。
那什么是拆箱呢?顾名思义,跟装箱对应,就是自动将引用类型转换为基本数据类型:
Integer i = 10; //装箱
int n = i; //拆箱
复制代码
简单一点说,装箱就是 自动将基本数据类型转换为引用类型;拆箱就是自动将引用类型转换为基本数据类型。
下表是基本数据类型对应的引用类型:
二、装箱和拆箱是如何实现的
上一小节了解装箱的基本概念之后,这一小节来了解一下装箱和拆箱是如何实现的。
我们就以 Interger 类为例,下面看一段代码:
public class Main {
public static void main(String[] args) {
Integer i = 10;
int n = i;
}
}
复制代码
反编译 class 文件之后得到如下内容:
从反编译的字节码中可以看出,在装箱的时候自动调用的是 interger 的 valueOf(int)方法。而在拆箱的时候自动调用的是 interger 的 intValue 方法
因此用一句话总结装箱和拆箱的实现过程:
装箱过程是通过调用包装器的 valueOf 方法实现的,而拆箱过程是通过引用类型调用 xxxValue 实现的。
三、面试中的相关问题
虽然大多数人对装箱和拆箱的概念都清楚,但是在面试和笔试中遇到了与装箱和拆箱的问题却不一定会答得上来。下面列举一些常见的与装箱/拆箱有关的面试题。
1、下面这段代码的输出结果是什么?
public class Main {
public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1==i2);
System.out.println(i3==i4);
}
}
复制代码
也许有些朋友会说都会输出 false,或者也有朋友会说都会输出 true。但是事实上输出结果是:
true
false
为什么会出现这样的结果?输出结果表明 i1 和 i2 指向的是同一个对象,而 i3 和 i4 指向的是不同的对象。此时只需一看源码便知究竟,下面这段代码是 Integer 的 valueOf 方法的具体实现:
public static Interger valueOf(int i){
if(i>=-128&&i<=IntergerCache.high){
return IntergerCache.cache[i+128];
}else{
return new Interger(i);
}
}
复制代码
通过 valueOf 方法创建 Integer 对象的时候,如果数值在[-128,127]之间,便返回指向 IntegerCache.cache 中已经存在的对象的引用;否则创建一个新的 Integer 对象。
上面的代码中 i1 和 i2 的数值为 100,因此会直接从 cache 中取已经存在的对象,所以 i1 和 i2 指向的是同一个对象,而 i3 和 i4 则是分别指向不同的对象。
其它的引用类型,可以去查看 valueOf 的实现。
弦外音:八种数据类型取值范围
整型:
byte:-2^7 ~ 2^7-1,即-128 ~ 127。1 字节。Byte。末尾加 B
short:-2^15 ~ 2^15-1,即-32768 ~ 32767。2 字节。Short。末尾加 S
int:-2^31 ~ 2^31-1,即-2147483648 ~ 2147483647。4 字节。Integer。
long:-2^63 ~ 2^63-1,即-9223372036854774808 ~ 9223372036854774807。8 字节。Long。末尾加 L。(也可以不加 L)
浮点型:
float:4 字节。Float。
double:8 字节。Double。
字符型:
char:2 字节。Character。
布尔型:
boolean:Boolean。
类型转换:
boolean 类型与其他基本类型不能进行类型的转换(既不能进行自动类型的提升,也不能强制类型转换), 否则,将编译出错。
byte 型不能自动类型提升到 char,char 和 short 直接也不会发生自动类型提升(因为负数的问题),同时,byte 当然可以直接提升到 short 型。
当对小于 int 的数据类型(byte, char, short)进行运算时,首先会把这些类型的变量值强制转为 int 类型进行计算,最后会得到 int 类型的值。因此,如果把 2 个 short 类型的值相加,最后得到的结果是 int 类型,如果需要得到 short 类型的结果,就必须显示地运算结果转为 short 类型。
下面程序的输出结果是什么?
当 "=="运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)。另外,对于包装器类型,equals 方法并不会进行类型转换。
第一个和第二个输出结果没有什么疑问。第三句由于 a+b 包含了算术运算,因此会触发自动拆箱过程(会调用 intValue 方法),因此它们比较的是数值是否相等。而对于 c.equals(a+b)会先触发自动拆箱过程,再触发自动装箱过程,也就是说 a+b,会先各自调用 intValue 方法,得到了加法运算后的数值之后,便调用 Integer.valueOf 方法,再进行 equals 比较。同理对于后面的也是这样,不过要注意倒数第二个和最后一个输出的结果(如果数值是 int 类型的,装箱过程调用的是 Integer.valueOf;如果是 long 类型的,装箱调用的 Long.valueOf 方法)。
一、类的加载与 ClassLoader 的理解
1、加载
将 class 文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的 java.lang.class 对象。
2、链接
将 Java 类的二进制代码合并到 JVM 的运行状态之中的过程。
验证:确保加载的类信息符合 JVM 规范,没有安全方面的问题;
准备:正式为类变量分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区内进行分配;
解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
3、初始化
执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步。
二、什么时候会发生类初始化
1、类的主动引用(一定会发生类的初始化)
当虚拟机启动,先初始化 main 方法所在的类;
new 一个类的对象;
调用类的静态成员(除了 final 常量)和静态方法;
使用 java.lang.reflect 包的方法对类进行反射调用;
当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类;
2、类的被动调用(不会发生类的初始化)
三、类加载器的作用
将 class 文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的 java.lang.Class 对象,作为方法区中类数据的访问入口。
四、动态创建对象执行方法
package com.reflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test03 {
public static void main(String[] args) throws Exception {
//获得class对象
Class c1 = Class.forName("com.reflection.User");
//1、构造一个对象,本质是无参构造器
User user1 = (User) c1.newInstance();
System.out.println(user1);
//2、通过构造器创建对象
Constructor constructor = c1.getDeclaredConstructor(int.class, String.class, int.class);
User user2 = (User) constructor.newInstance(1,"郭一诺",1);
System.out.println(user2);
//3、通过反射调用普通方法
User user3 = (User) c1.newInstance();
Method setName = c1.getDeclaredMethod("setName", String.class);
//invoke激活
setName.invoke(user3,"素小暖");
System.out.println(user3.getName());
//4、通过反射操作属性
User user4 = (User) c1.newInstance();
Field name = c1.getDeclaredField("name");
//true:取消Java语言访问检查
name.setAccessible(true);
name.set(user4,"素小暖2");
System.out.println(user4.getName());
}
}
复制代码
五、通过反射获取泛型信息
1、代码实例
package com.reflection;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
public class Test04 {
public void test01(Map<String,User> map, List<User> list){
System.out.println("test01");
}
public Map<String,User> test02(){
System.out.println("test02");
return null;
}
//通过反射获取泛型信息
public static void main(String[] args) throws Exception {
Method method = Test04.class.getMethod("test01", Map.class, List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println("***"+genericParameterType);
if(genericParameterType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
method = Test04.class.getMethod("test02", null);
Type genericReturnType = method.getGenericReturnType();
if(genericReturnType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("test02,"+actualTypeArgument);
}
}
}
}
复制代码
2、控制台输出
3、反射解决泛型问题
六、通过反射获取注解信息
1、代码实例
package com.reflection;
import java.lang.annotation.*;
import java.lang.reflect.Field;
public class Test05 {
public static void main(String[] args) throws Exception {
Class c1 = Class.forName("com.reflection.Student");
//通过反射获取注解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//获得注解value的值
TableSu tableSu = (TableSu) c1.getAnnotation(TableSu.class);
String value = tableSu.value();
System.out.println(value);
//获得类指定的注解
Field field = c1.getDeclaredField("name");
FieldSu annotation = field.getAnnotation(FieldSu.class);
System.out.println(annotation.columnName());
System.out.println(annotation.type());
System.out.println(annotation.length());
}
}
@TableSu("db_student")
class Student{
@FieldSu(columnName = "db_id",type = "int",length = 10)
private int id;
@FieldSu(columnName = "db_name",type = "varchar2",length = 10)
private String name;
@FieldSu(columnName = "db_age",type = "int",length = 10)
private int age;
public Student() {
}
public Student(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
//类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableSu{
String value();
}
//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldSu{
String columnName();
String type();
int length();
}
复制代码
2、控制台输出
七、通过配置文件动态调用方法
1、在项目根目录建 Class.txt 文件,内容如下
classname=com.guor.reflect.Person
methodname=getPerson
2、建一个 Person 类
3、通过反射获取配置文件中内容调用类中方法
4、控制台输出
小结:
Java 反射机制其实是动态地获取信息以及动态调用对象的功能;而所谓动态是指,对于任意一个运行状态的类,都能够知道这个类的所有属性和方法;并且对于任意一个对象,都能调用他的任意一个方法。
今日份分享已结束,请大家多多包涵和指点!
评论