1. exception
异常
代表程序运行中遇到了意料之外的事情,为了描述这种异常情况,java标准库中建立了一些通用的异常。Throwable为父类,派生了Error类和Exception类量大子类。
-
Error
代表了JVM自身的异常。这一类异常发生时无法通过程序来修正,需要尽快停止JVM的运行。 -
Exception
代表程序运行中发生了意料之外的事。这些以外的情况可以被Java异常处理机制处理。Exception有派生出两个子类
- RuntimeException
这类异常其实是程序设计的错误,通过修正程序是可以避免的,如角标越界、除零异常等。 - 非RuntimeException
这类异常通常由外部因素导致,是不可避免的。如:IO异常,网络异常
在遇到复杂的类关系时,绘制它们的UML类图是理清它们之前关系的非常好的手段
上图所示异常中Error和RuntimeException称为免检异常,既不需要对这两类异常进行强制检查。而除了这两类一异常外的其他异常称为必检异常。在编写代码时需要对这类异常处理。
Throwable对象,其主要的成员变量有 detailMessage和cause。
- detailMessage为一个字符串,用来存储异常的详细信息。
- cause 为另一个 Throwable 对象,用来存储引发异常的原因。这是因为一个异常发生时,通常引发异常的上级程序也发生异常,从而导致一连串的异常产生,叫作
异常链
。一个异常的 cause属性可以指向引发它的下级异常,从而将整个异常链保存下来。
2. 序列化
序列化:是把对象转换为字节序列的过程。
反序列化:是把字节序列恢复为对象的过程。
对象的序列化通常由两个目的:
- 将对象序列化成字节后保存在存储介质中。是为了持久化对象
- 将对象序列化成字节后再网络上传输。是为了传输对象
在使用dubbo等RPC框架时实体对象必须要实现Serializable接口就是因为要将对象序列化用于在网络间传输。
在Java中要表名一个类是可序列化的则必须基础Serializable接口或器子接口Externalizable接口。
Serializable接口的使用非常简单,只需要将需要序列化的类实现Serializable接口就可以了。不需要增加任何方法。
2.1 序列化过程中的版本问题
在序列化和反序列化过程中,要面临版本的问题。
例如:将User类的对象user持久化到了硬盘中。然后在User类中新增了属性,那么此时还能将持久化在硬盘中的user对象反序列化为新的User类对象吗?
该问题的回答需要涉及 Serializable接口的 serialVersionUID字段。serialVersionUID字段叫作序列化版本控制字段,我们经常会在实现了Serializable 接口的类中见到它。
public class User implements Serializable {
private static final long serialVersionUID = 1L;
}
在反序列化过程中,如果对象字节序列中的serialVersionUID与当前类的该值不通,则反序列化失败。否则成功。
如果没有显示的为类定义serialVersionUID属性,那么会自动生成一个。自动生成的序列化版本控制字段与类名、类及属性修饰符、接口及接口顺序、属性、构造函数等相关。其中任何一项的改变都会导致serialVersionUID发生变化。
一次对于上面的问题。user对象能否还原成一个新的User类对象需要分情况讨论:
- 如果旧User类和新User类中均有serialVersionUID字段,并且值一样。则可以还原成一个新的User类的对象。如果新User类中新增了属性,其值为null
- 如果旧类和新类中serialVersionUID不一致,或者没有显示的设置serialVersionUID属性值。因为系统会自动生成serialVersionUID。则反序列化失败。会报异常
InvalidClassException
在使用时,一般都会为实现Serializable接口的类显式声明一个serialVersionUID。这样便可以:
- 在希望类的版本间实现序列化和反序列化的兼容时,保持serialVersionUID值不变。
- 在希望类的版本间序列化和反序列化不兼容时,确保serialVersionUID值发生变化。
3.反射
Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。-- 来自百度词条
- 反射机制极大的提高了程序的灵活性和扩展性,降低模块的耦合性,提高自身的适应能力。
- 通过反射机制可以让程序创建和控制任何类的对象,无需提前硬编码目标类。
- 使用反射机制能够在运行时构造一个类的对象、判断一个类所具有的成员变量和方法、调用一个对象的方法。
- 反射机制是构建框架技术的基础所在,使用反射可以避免将代码写死在框架中。
正是反射有以上的特征,所以它能动态编译和创建对象,极大的激发了编程语言的灵活性,强化了多态的特性,进一步提升了面向对象编程的抽象能力,因而受到编程界的青睐。
尽管反射机制带来了极大的灵活性及方便性,但反射也有缺点。反射机制的功能非常强大,但不能滥用。在能不使用反射完成时,尽量不要使用,原因有以下几点:
- 性能问题。Java反射机制中包含了一些动态类型,所以Java虚拟机不能够对这些动态代码进行优化。因此,反射操作的效率要比正常操作效率低很多。我们应该避免在对性能要求很高的程序或经常被执行的代码中使用反射。而且,如何使用反射决定了性能的高低。如果它作为程序中较少运行的部分,性能将不会成为一个问题。
- 安全限制。使用反射通常需要程序的运行没有安全方面的限制。如果一个程序对安全性提出要求,则最好不要使用反射。
- 程序健壮性。反射允许代码执行一些通常不被允许的操作,所以使用反射有可能会导致意想不到的后果。反射代码破坏了Java程序结构的抽象性,所以当程序运行的平台发生变化的时候,由于抽象的逻辑结构不能被识别,代码产生的效果与之前会产生差异。
反射是每个框架必用的功能。所以了解反射对读框架至关重要。
3.1反射示例
需求:要比较两个类的属性是否一致。
- 实体类
public class Student {
private Integer id;
private String name;
}
public class Car {
private String name;
private String type;
}
- 工具类比较两个Student对象属性是否一致
public class StudentUtils {
/**
* 比较两个student对象的不同属性
*/
public static Map<String,String> diffStudent(Student oldStu,Student newStu){
Map<String,String> diffMap=new HashMap<>();
if(oldStu.getId()!=null && !oldStu.getId().equals(oldStu.getId())){
diffMap.put("id","from "+oldStu.getId()+" to " + newStu.getId());
}
if(oldStu.getName()!=null && !oldStu.getName().equals(oldStu.getName())){
diffMap.put("id","from "+oldStu.getName()+" to " + newStu.getName());
}
return diffMap;
}
}
我们在编码时就知道Student对象有哪些属性,所以可以这么写。那么如果我们要比较其它对象的不同改怎么做呢?
这里要面临两个问题:
- 不知道需要比较对象的具体类型
- 不知道比较对象的具体属性
要解决上述两个问题,需要在参数传入后,直接判断传入对象的类型及其包含的属性和方法。反射就能帮我们解决。
Java反射机制主要提供了以下功能。
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时修改任意一个对象的成员变量;
- 在运行时调用任意一个对象的方法。
public static Map<String,String> diffObject(Object oldObj,Object newObj) throws IllegalAccessException {
Map<String,String> diffMap=new HashMap<>();
Class oldStuClass = oldObj.getClass();
Class newStuClass = newObj.getClass();
//判断是否是同一类
if(!oldStuClass.equals(newStuClass)){
return diffMap;
}
//获取对象的所有属性
Field[] declaredFields = oldStuClass.getDeclaredFields();
//对属性逐一比对
for (Field field : declaredFields) {
field.setAccessible(true);
Object oldValue = field.get(oldObj);
Object newValue = field.get(newObj);
if(oldValue!=null && !oldValue.equals(newValue)){
diffMap.put(field.getName(),"from "+oldValue+" to " + newValue);
}
}
return diffMap;
}
public static void main(String[] args) throws IllegalAccessException {
Student oldStu = new Student(1, "huazige");
Student newStu = new Student(2, "mybatis");
Map<String, String> stringStringMap = diffObject(oldStu, newStu);
System.out.println(JSON.toJSONString(stringStringMap));
Car oldCar = new Car("比亚迪", "电车");
Car newCar = new Car("红旗", "油车");
Map<String, String> carMap = diffObject(oldCar, newCar);
System.out.println(JSON.toJSONString(carMap));
}
运行结果:
{
"name":"from huazige to mybatis","id":"from 1 to 2"}
{
"name":"from 比亚迪 to 红旗","type":"from 电车 to 油车"}
diffObject方法完成了student和Car两个不同类的对象属性的对比,降低了参数的耦合性使得功能更为通用。
3.2type接口及其子类
Type接口只定义了一个方法。
default String getTypeName() {
return toString();
}
Type接口及子类类图:
- Class类:它代表运行的 Java程序中的类和接口,枚举类型(属于类)、注解(属于接口)也都是 Class类的子类。
- WildcardType 接口:它代表通配符表达式。例如,“?”“?extends Number”“?super Integer”都是通配符表达式。
- TypeVariable 接口:它是类型变量的父接口。例如,“Map<K,V>”中的“K”“V”就是类型变量。
- ParameterizedType 接口:它代表参数化的类型。例如,“Collection <String>”就是参数化的类型。
- GenericArrayType接口:它代表包含 ParameterizedType或者 TypeVariable元素的列表。
4.注解
Java注解又称Java标注,是在 JDK5 时引入的新特性,注解(也被称为元数据)。
Java注解它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。
Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。
4.1 Java注解应用
- 生成文档这是最常见的,也是java 最早提供的注解;
- 在编译时进行格式检查,如@Override放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出;
- 跟踪代码依赖性,实现替代配置文件功能,比较常见的是spring 2.5 开始的基于注解配置,作用就是减少配置;
- 在反射的 Class, Method, Field 等函数中,有许多于 Annotation 相关的接口,可以在反射中解析并使用 Annotation。
4.2 java注解分类
- Java自带的标准注解
包括@Override、@Deprecated、@SuppressWarnings、@FunctionalInterface,使用这些注解后编译器就会进行检查。
- @Override :它的作用是对覆盖超类中方法的方法进行标记,如果被标记的方法并没有实际覆盖超类中的方法,则编译器会发出错误警告。
- @Deprecated:它的作用是对不应该再使用的方法添加注解,当编程人员使用这些方法时,将会在编译时显示提示信息
- @SuppressWarnings:其参数有:
deprecation:使用了过时的类或方法时的警告
unchecked:执行了未检查的转换时的警告
fallthrough:当 switch 程序块直接通往下一种情况而没有 break 时的警告
path:在类路径、源文件路径等中有不存在的路径时的警告
serial:当在可序列化的类上缺少serialVersionUID 定义时的警告
finally :任何 finally 子句不能正常完成时的警告
all:关于以上所有情况的警告 - @FunctionalInterface:Java 8为函数式接口引入了一个新注解@FunctionalInterface,主要用于编译级错误检查,加上该注解,当你写的接口不符合函数式接口定义的时候,编译器会报错
- 元注解
元注解是用于定义注解的注解,包括@Retention、@Target、@Inherited、@Documented、@Repeatable 。
元注解也是Java自带的标准注解,只不过用于修饰注解,比较特殊。
- @ Retention用来定义该注解在哪一个级别可用,在源代码中(SOURCE)、类文件中(CLASS)或者运行时(RUNTIME)。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
public enum RetentionPolicy {
//此注解类型的信息只会记录在源文件中,编译时将被编译器丢弃,也就是说不会保存在编译好的类信息中
SOURCE,
//编译器将注解记录在类文件中,但不会加载到JVM中。如果一个注解声明没指定范围,则系统默认就是CLASS
CLASS,
//注解信息会保留在源文件、类文件中,在执行的时也加载到Java的JVM中,因此可以反射性的读取。
RUNTIME
}
- @Documented:生成文档信息的时候保留注解,对类作辅助说明
- @Target:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
public enum ElementType {
/** Class, interface (including annotation interface), enum, or record
* declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation interface declaration (Formerly known as an annotation type.) */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
* @since 1.8
*/
TYPE_USE,
/**
* Module declaration.
*
* @since 9
*/
MODULE,
/**
* Record component
*
* @jls 8.10.3 Record Members
* @jls 9.7.4 Where Annotations May Appear
*
* @since 16
*/
RECORD_COMPONENT;
}
- @Inherited:说明子类可以继承父类中的该注解
- @Repeatable 表示注解可以重复使用。
- 自定义注解
用户可以根据自己的需求定义注解。