Qida's Blog

纸上得来终觉浅,绝知此事要躬行。

泛型术语

泛型涉及的术语比较多,其与反射接口的对应关系如下:

术语 中文含义 举例 反射接口 备注
Generic type 泛型 List<E> ParameterizedType
Parameterized type 参数化类型 List<String> ParameterizedType
Raw type 原始类型 List ParameterizedType#getRawType 该方法虽然返回 Type 类型,但实际类型是 Class,可以强转使用:(Class<?>) type
Unbounded wildcard type 无限制通配符类型 List<?> ParameterizedType
Bounded wildcard type 有限制通配符类型(上限) List<? extends Number> ParameterizedType
Bounded wildcard type 有限制通配符类型(下限) List<? super Number> ParameterizedType
wildcard type 通配符类型 ? WildcardType
Formal type parameter 形式类型参数 E TypeVariable
Actual type parameter 实际类型参数 String ParameterizedType#getActualTypeArguments 该方法虽然返回 Type[] 类型,但各元素实际类型是 Class,可以强转使用:(Class<?>) type
Bounded type parameter 有限制类型参数 <E extends Number>
Recursive type bound 递归类型限制 <T extends Comparable<T>>
Generic method 泛型方法 static <E> List<E> asList(E[] a)
Type token 类型令牌 String.class

泛型 API

java.lang.reflect.Type

JDK 1.5 引入了泛型特性,一同引入的还有 Java Type 类型体系。其中 java.lang.reflect.Type 接口作为核心,是 Java 编程语言中所有类型的通用超级接口(common superinterface),这些类型包括:

  • 原始类型(raw types)
  • 参数化类型(parameterized types)
  • 数组类型(array types)
  • 八大原始类型(primitive types)
  • 类型变量(type variables)

调整后新引入的五个接口如下:

1
2
3
4
5
java.lang.reflect.Type
java.lang.reflect.ParameterizedType // 最最常用
java.lang.reflect.TypeVariable
java.lang.reflect.WildcardType
java.lang.reflect.GenericArrayType

Type

它们的核心方法如下:

Type_methods

类、字段、方法、构造方法也相应增加了一组方法,用于获取 Type

  • java.lang.Class

    1
    2
    3
    4
    5
    6
    7
    // 获取普通 Class
    Class<? super T> getSuperclass()
    Class<?>[] getInterfaces()

    // 获取 Type
    Type getGenericSuperclass()
    Type[] getGenericInterfaces()
  • java.lang.reflect.Field

    1
    2
    3
    4
    5
    // 获取普通 Class
    Class<?> getType()

    // 获取 Type
    Type getGenericType()
  • java.lang.reflect.Method

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 获取普通 Class
    Class<?> getReturnType()
    Class<?>[] getParameterTypes()
    Class<?>[] getExceptionTypes()

    // 获取 Type
    Type getGenericReturnType()
    Type[] getGenericParameterTypes()
    Type[] getGenericExceptionTypes()
  • java.lang.reflect.Constructor

    1
    2
    3
    4
    5
    6
    7
    // 获取普通 Class
    Class<?>[] getParameterTypes()
    Class<?>[] getExceptionTypes()

    // 获取 Type
    Type[] getGenericParameterTypes()
    Type[] getGenericExceptionTypes()

java.lang.reflect.GenericDeclaration

同时新增的接口还有 java.lang.reflect.GenericDeclaration,用于获取 TypeVariable

GenericDeclaration_method

从源码看,只有三个类实现了该接口(见下图):

  • java.lang.Class
  • java.lang.reflect.Method
  • java.lang.reflect.Constructor

GenericDeclaration

因此只有这三个地方可以声明为泛型,并获取其类型参数(type variables)集合:KV

  • 类型(例如 classinterface

    1
    2
    3
    public class Test<K, V> { ... }

    public interface Test<K, V> { ... }
  • 构造方法(Constructor

    1
    public <K, V> Test(K k, V v) { ... }
  • 方法(Method

    1
    public <K, V> K test(V v) { ... }

类、方法、构造方法也相应增加了一个方法,用于获取 TypeVariable

  • java.lang.Class
  • java.lang.reflect.Method
  • java.lang.reflect.Constructor
1
TypeVariable<?>[] getTypeParameters()

例子

下面是几个获取 Type 的例子,先创建三个类:BaseMapperPersonMapperPerson

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public interface BaseMapper<T extends Serializable & Comparable<T>, K extends Serializable> {

T getById(K id);

}

public class PersonMapper implements BaseMapper<Person, Long>, Serializable {
@Override
public Person getById(Long id) {
return null;
}

public void log(List<?> list) {

}

public <T extends Number> void test(List<T> list1, List<? extends Comparable<T>> list2, T[] array, T item) {
}
}

public class Person implements Serializable, Comparable<Person> {
private String personName;

@Override
public int compareTo(Person o) {
return this.personName.compareTo(o.getPersonName());
}

例子一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void test1() {
Method method = PersonMapper.class.getMethod("test", List.class, List.class, Number[].class, Number.class);

// [0] ParameterizedType
// [1] ParameterizedType
// [2] GenericArrayType
// [3] TypeVariable
Type[] genericParameterTypes = method.getGenericParameterTypes();

// Class
Type genericReturnType = method.getGenericReturnType();

// Class[0]
Type[] genericExceptionTypes = method.getGenericExceptionTypes();
}

三个 Type 变量的内容如下:

generic_type_examples

例子二

接下来看下 ParameterizedType 的使用,可以用于获取泛型的原始类型(Raw type)、实际类型参数(Actual type parameter)列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
public void test2() {
// com.github.reflection.BaseMapper<com.github.reflection.Person, java.lang.Long>
Type genericInterface = PersonMapper.class.getGenericInterfaces()[0];
assertTrue(genericInterface instanceof ParameterizedType);
ParameterizedType parameterizedType = (ParameterizedType) genericInterface;

// 获取原始类型:BaseMapper.class
assertEquals(BaseMapper.class, parameterizedType.getRawType());

// 获取实际类型参数列表:Person.class、Long.class
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
assertEquals("There are two actual type arguments", 2, actualTypeArguments.length);
// class com.github.reflection.Person
assertClass("One is Person", actualTypeArguments[0], Person.class);
// class java.lang.Long
assertClass("Another is Long", actualTypeArguments[1], Long.class);
}

private void assertClass(Type type, Class expectedClass) {
assertTrue(type instanceof Class);
Class clazz = (Class) type;
assertEquals(expectedClass, clazz);
}

genericInterface 变量的内容如下,接口返回类型虽然为 Type,实际类型为 ParameterizedType,因此可以强转。该泛型变量的原始类型、实际类型参数列表如下,实际都为 Class 类型,因此可以强转 (Class<?>) type

ParameterizedType_example

例子三

WildcardType 的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void test3() {
// java.util.List<?>
Method method = PersonMapper.class.getMethod("log", List.class);
Type genericParameterType = method.getGenericParameterTypes()[0];
assertTrue("The first parameter type of log method is instance of ParameterizedType",
genericParameterType instanceof ParameterizedType);
ParameterizedType parameterType = (ParameterizedType) genericParameterType;

// WildcardType
Type type = parameterType.getActualTypeArguments()[0];
assertTrue("The actual type argument of ParameterizedType is instance of WildcardType",
type instanceof WildcardType);
WildcardType wildcardType = (WildcardType) type;
assertEquals("No lower bounds exist", 0, wildcardType.getLowerBounds().length);
assertEquals("Only one upper bound exist", 1, wildcardType.getUpperBounds().length);
// 通配符默认上限类型为 Object
assertEquals("The upper bound is Object", Object.class, wildcardType.getUpperBounds()[0]);
}

Type 类型变量 genericParameterType 的内容如下,实际类型是 ParameterizedType,其 ? 通配符是 WildcardType

WildcardType_example

例子四

TypeVariable 的使用:

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void test4() {
Method method = BaseMapper.class.getMethod("getById", Serializable.class);

// TypeVariable
Type genericReturnType = method.getGenericReturnType();
assertTrue("Return type of method is instance of TypeVariable", genericReturnType instanceof TypeVariable);
TypeVariable typeVariable = (TypeVariable) genericReturnType;
assertEquals("First upper bound is Serializable", Serializable.class, typeVariable.getBounds()[0]);
ParameterizedType parameterizedType = (ParameterizedType) typeVariable.getBounds()[1];
assertEquals("Second upper bound is Comparable", Comparable.class, parameterizedType.getRawType());
}

Type 类型变量 genericReturnType 的内容如下,实际类型是 TypeVariable

TypeVariable_example

例子五

定义一个泛型工具类,用于获取 T.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public class GenericsUtils {
/**
* 通过反射获得定义 Class 时声明的父类的泛型参数的类型
* @param clazz
* @return 返回第一个类型
*/
public static Class getSuperClassGenricType(Class clazz) {
return getSuperClassGenricType(clazz, 0);
}

/**
* 通过反射获得定义 Class 时声明的父类的泛型参数的类型
* @param clazz
* @param 返回某个下标的类型
*/
public static Class getSuperClassGenricType(Class clazz, int index)
throws IndexOutOfBoundsException {
Type genType = clazz.getGenericSuperclass();
if (!(genType instanceof ParameterizedType)) {
return Object.class;
}
Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
if (index >= params.length || index < 0) {
return Object.class;
}
if (!(params[index] instanceof Class)) {
return Object.class;
}
return (Class) params[index];
}
}

参考

API:

https://www.baeldung.com/java-generics-vs-extends-object

Java Generic’s Wildcards

Java Reflection API 介绍

读懂 Java 类型(Type)系统

现代编程语言需要泛型 —— 排序例子

JDK 1.5 引入了注解,其主要用途如下:

  • 生成文档,通过代码里标识的元数据生成 javadoc 文档。
  • 编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。
  • 编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。
  • 运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例。

Annotation

首先,注解也是一种 class,所有注解都默认继承自通用接口:java.lang.annotation.Annotation

annotation

从上图可见,JDK 提供的元注解(meta annotation)如下:

1
2
3
4
5
6
@Native
@Documented
@Inherited
@Repeatable
@Target
@Retention

其中,@Native@Documented@Inherited 是一个“标记注解”,没有成员。

常用元注解的作用如下:

@Inherited

@Inherited 用于定义子类是否可继承父类定义的注解。仅针对 @Target(ElementType.TYPE) 类型的注解有效,并且仅针对 class 的继承,对 interface 的继承无效。注意:

  • 因此 interface 上标注的注解,无法被 JDK Proxy 动态代理生成的类所继承。
  • 也因此 Spring AOP 的切点表达式 @annotation(...) 无法切到基于 JDK Proxy 的动态代理类。

https://docs.oracle.com/javase/8/docs/api/java/lang/annotation/Inherited.html

Indicates that an annotation type is automatically inherited. If an Inherited meta-annotation is present on an annotation type declaration, and the user queries the annotation type on a class declaration, and the class declaration has no annotation for this type, then the class’s superclass will automatically be queried for the annotation type. This process will be repeated until an annotation for this type is found, or the top of the class hierarchy (Object) is reached. If no superclass has an annotation for this type, then the query will indicate that the class in question has no such annotation.

Note that this meta-annotation type has no effect if the annotated type is used to annotate anything other than a class. Note also that this meta-annotation only causes annotations to be inherited from superclasses; annotations on implemented interfaces have no effect.

@Target

@Target 用于定义注解能够被标注于源码的哪些位置。可用字段参考 ElementType 枚举。

JDK 8 扩展了注解的上下文,现在注解几乎可以加到任何地方:局部变量、泛型类、⽗类与接⼝的实现,就连⽅法的异常也能添加注解。

JDK 8 引入的这两个枚举如下:

  • ElementType.TYPE_PARAMETER 用于标注泛型的类型参数。
  • ElementType.TYPE_USE 用于标注各种类型。

extended_annotations_support

@Retention

@Retention 用于定义注解的生命周期。可用字段参考下面的 RetentionPolicy 枚举源码,常用的是 RUNTIME 类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* Annotation retention policy. The constants of this enumerated type
* describe the various policies for retaining annotations. They are used
* in conjunction with the {@link Retention} meta-annotation type to specify
* how long annotations are to be retained.
*
* @author Joshua Bloch
* @since 1.5
*/
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,

/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,

/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}

@Repeatable

JDK 8 引入了 @Repeatable,表示该注解是否可重复标注。配套引入的还有为 AnnotatedElement 接口新增了两个方法 getAnnotationsByTypegetDeclaredAnnotationsByType

AnnotatedElement

读取运行时(@Retention(RetentionPolicy.RUNTIME))的注解,需要用到反射 API。java.lang.reflect.AnnotatedElement 接口提供了一组方法,用于获取注解信息:

AnnotatedElement_methods

1
2
3
4
5
6
7
8
9
10
// 判断某个注解是否存在
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
// 获取注解(包括父类)
<T extends Annotation> T getAnnotation(Class<T>)
<T extends Annotation> T[] getAnnotationsByType(Class<T>)
Annotation[] getAnnotations()
// 获取注解(不包括父类)
<T extends Annotation> T getDeclaredAnnotation(Class<T>)
<T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T>)
Annotation[] getDeclaredAnnotations()

由于下列类都实现了该接口,因此都拥有这些方法获取注解信息:

1
2
3
4
5
6
java.lang.Class
java.lang.Package
java.lang.reflect.Field
java.lang.reflect.Method
java.lang.reflect.Constructor
java.lang.reflect.Parameter

main_api

举个例子,看下哪些情况通过反射可以拿到注解(或拿不到):

注解如下:

1
2
3
4
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Check {}

例子一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Check
public class Person {
@Check
public void say() {}

@Check
public void say(String msg) {}
}

@ToString
public class Child extends Person {
public String childName = "Hello";

@Override
public void say() {}
}

public class Test {
@Test
public void test() {
ToString annotation = Child.class.getAnnotation(ToString.class);
assertNull("结果为 null 因为 @ToString 标注为 @Retention(RetentionPolicy.SOURCE)", annotation);

Check annotation1 = Child.class.getAnnotation(Check.class);
assertNotNull("结果不为 null 因为 @Check 标注为 @Retention(RetentionPolicy.RUNTIME)、@Inherited 并且 @Check 被标注在父类上,可以被子类继承", annotation1);

Check annotation2 = Child.class.getDeclaredAnnotation(Check.class);
assertNull("结果为 null 因为 getDeclaredAnnotation 方法无法拿到父类注解", annotation2);

Method sayString = Child.class.getMethod("say", String.class);
Check annotation3 = sayString.getAnnotation(Check.class);
assertNotNull("结果不为 null 因为 say 方法未被子类重写,被完整继承下来", annotation3);

Method say = Child.class.getMethod("say");
Check annotation4 = say.getAnnotation(Check.class);
assertNull("结果为 null 因为 say 方法被子类重写了", annotation4);
}
}

例子二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Check
public interface Sayable {
@Check
void say();
}

public class Student implements Sayable {
@Override
public void say() {}
}

public class Test {
@Test
public void test() {
Check annotation5 = Student.class.getAnnotation(Check.class);
assertNull("结果为 null 因为接口上的注解无法被子类继承", annotation5);

Check annotation6 = Student.class.getMethod("say").getAnnotation(Check.class);
assertNull("结果为 null", annotation6);
}
}

AnnotatedType

JDK 8 引入了 java.lang.reflect.AnnotatedType

AnnotatedType

类、字段、方法、构造方法相应增加了一组方法用于获取 AnnotatedType

  • Class

    1
    2
    AnnotatedType getAnnotatedSuperclass()
    AnnotatedType[] getAnnotatedInterfaces()
  • Field

    1
    AnnotatedType getAnnotatedType()
  • Method

    1
    2
    3
    4
    AnnotatedType getAnnotatedReturnType()
    AnnotatedType[] getAnnotatedParameterTypes()
    AnnotatedType[] getAnnotatedExceptionTypes()
    AnnotatedType getAnnotatedReceiverType()
  • Constructor

    1
    2
    3
    4
    AnnotatedType getAnnotatedReturnType()
    AnnotatedType[] getAnnotatedParameterTypes()
    AnnotatedType[] getAnnotatedExceptionTypes()
    AnnotatedType getAnnotatedReceiverType()

参考

深入理解java注解的实现原理

API:

什么是反射?

反射机制是 Java 语言提供的一种基础功能,赋予程序在运行时自省(introspect,官方用语)的能力。通过反射我们可以直接操作类或者对象,比如获取某个对象的类定义,获取类声明的属性和方法,调用方法或者构造对象,甚至可以运行时修改类定义。

主要类:

1
2
3
4
5
6
7
8
9
java.lang.Class
java.lang.Package
java.lang.reflect.Field
java.lang.reflect.Method
java.lang.reflect.Constructor
// JDK 1.8 新引入,通过该类的 getName 方法能在运行时得到参数的名称(前提是通过 -parameters 指定编译器在编译的时候将参数名编译进去)
java.lang.reflect.Parameter

java.lang.reflect.Array

工具类:

1
java.lang.reflect.Modifier

继承关系:

main_api

Class 类

如何获取一个 classClass 实例?有几种方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 方法一:直接通过一个 class 的静态变量 class 获取
Class<String> aClass = String.class;

// 方法二:通过实例变量提供的 getClass() 方法获取
Class<? extends String> aClass2 = "Hello".getClass();

// 方法三:通过 class 的完整类名获取,底层调用的是应用类加载器 Application ClassLoader (sun.misc.Launcher$AppClassLoader)
Class<?> aClass3 = Class.forName("java.lang.String");

// 方法四:通过自定义 ClassLoader 获取
URLClassLoader classLoader = new URLClassLoader(new URL[] {new URL("file:/c:/")});
Class<?> aClass4 = classLoader.loadClass("java.lang.String");

// 获取父类
Class<? super String> superclass = String.class.getSuperclass(); // class java.lang.Object
// 获取外部类
Class<?> enclosingClass = Map.Entry.class.getEnclosingClass(); // interface java.util.Map
// 获取内部类
Class<?>[] classes = HashMap.class.getClasses(); // 2 个内部类
Class<?>[] declaredClasses = HashMap.class.getDeclaredClasses(); // 13 个内部类

由于 Class 实例在 JVM 中是唯一的,因此,上述方法获取的 Class 实例都是同一个实例。可以用 == 比较两个 Class 实例求证:

1
2
3
4
// true
assertTrue(aClass == aClass2);
assertTrue(aClass == aClass3);
assertTrue(aClass2 == aClass3);

拿到 Class 实例后,可以通过下列方法获取字段、方法、构造方法、注解,以进行后续操作。方法名以 getDeclared 开头的表示仅获取当前类的信息(不包括父类):

Member Class API Param type Return type Inherited members Private members
Class getDeclaredClasses() Array N Y
getClasses() Array Y N
Field getDeclaredField() String Single N Y
getField() String Single Y java.lang.NoSuchFieldException
getDeclaredFields() Array N Y
getFields() Array Y N
Method getDeclaredMethod() String, Class<?>... Single N Y
getMethod() String, Class<?>... Single Y java.lang.NoSuchMethodException
getDeclaredMethods() Array N Y
getMethods() Array Y N
Constructor getDeclaredConstructor() Class<?>... Single N/A Y
getConstructor() Class<?>... Single N/A java.lang.NoSuchMethodException
getDeclaredConstructors() Array N/A Y
getConstructors() Array N/A N
Annotation getDeclaredAnnotation() Class<T> Single N N/A
getAnnotation() Class<T> Single Y N/A
getDeclaredAnnotationsByType() Class<T> Array N N/A
getAnnotationsByType() Class<T> Array Y N/A
getDeclaredAnnotations() Array N N/A
getAnnotations() Array Y N/A

字段

一个 Field 对象包含了一个字段的所有信息,例如:

1
2
3
4
5
6
// 字段名称
String getName()
// 字段类型
Class<?> getType()
// 字段的修饰符,返回值是一个int,不同的 bit 表示不同的含义。使用 Modifier 工具类进行解析
int getModifiers()

String 类的 value 字段为例,它的定义是:

1
2
3
public final class String {
private final byte[] value;
}

用反射获取该字段的信息,代码如下:

1
2
3
4
5
6
7
8
9
Field f = String.class.getDeclaredField("value");
f.getName(); // "value"
f.getType(); // class [B 表示byte[]类型
int m = f.getModifiers();
Modifier.isFinal(m); // true
Modifier.isPublic(m); // false
Modifier.isProtected(m); // false
Modifier.isPrivate(m); // true
Modifier.isStatic(m); // false

获取或设置字段值,使用如下方法。注意如果试图获取非 public 字段,将抛出异常 java.lang.IllegalAccessException,解决办法是先设置 setAccessible(true)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 获取字段值
Object get(Object obj)
boolean getBoolean(Object obj)
byte getByte(Object obj)
char getChar(Object obj)
short getShort(Object obj)
int getInt(Object obj)
long getLong(Object obj)
float getFloat(Object obj)
double getDouble(Object obj)

// 设置字段值
void set(Object obj, Object value)
void setBoolean(Object obj, boolean z)
void setByte(Object obj, byte b)
void setChar(Object obj, char c)
void setShort(Object obj, short s)
void setInt(Object obj, int i)
void setLong(Object obj, long l)
void setFloat(Object obj, float f)
void setDouble(Object obj, double d)

方法

一个 Method 对象包含一个方法的所有信息,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 方法名称
String getName()
// 方法参数个数
int getParameterCount()
// 方法参数类型
Class<?>[] getParameterTypes()
// 方法参数注解
Annotation[][] getParameterAnnotations()
// 方法返回值类型
Class<?> getReturnType()
// 方法异常类型
Class<?>[] getExceptionTypes()
// 方法的修饰符,返回值是一个int,不同的 bit 表示不同的含义。使用 Modifier 工具类进行解析
int getModifiers()

方法调用:

1
2
3
4
// 第一个参数为指定的实例对象。
// 如果是静态方法,可传 null。
// 如果是非 public 方法,需先设置 setAccessible(true)
Object invoke(Object obj, Object... args)

构造方法

创建新实例的几种方式:

1
2
3
4
5
6
7
8
9
10
11
// 方式一:通过 new 操作符
Person p = new Person();

// 方式二:通过反射,调用 Class 提供的 newInstance() 方法。局限是只能调用其 public 无参构造方法
Person p = Person.class.newInstance();

// 方式三:通过反射,调用 Constructor 提供的 newInstance() 方法。如果是非 public 方法,需先设置 setAccessible(true)
// 这里通过 private Person(String name) 构造新实例
Constructor<Person> constructor = Person.class.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
Person peter = constructor.newInstance("Peter");

继承关系

通过以下方法获取父类或已实现接口:

1
2
3
4
5
// 获取父类(Object 的父类是 null,其他任何非 interface 的 Class 都必定存在一个父类类型)
Class<? super T> getSuperclass()

// 获取已实现接口(只返回当前类直接实现的接口类型,并不包括其父类实现的接口类型)
Class<?>[] getInterfaces()

isInstance

使用 instanceof 操作符或者 Class#isInstance 方法,可以判断一个实例的继承关系:

1
2
3
4
5
6
7
Object n = Integer.valueOf(123);
boolean isDouble = n instanceof Double; // false
boolean isInteger = n instanceof Integer; // true
boolean isNumber = n instanceof Number; // true
boolean isSerializable = n instanceof java.io.Serializable; // true

boolean isDouble = Double.class.isInstance(n); // false

cast

如果 instanceoftrue,可以使用以下方法对实例进行强制类型转换:

1
2
Number num1 = (Number) n;
Number num2 = Number.class.cast(n);

isAssignableFrom

如果是两个 Class 实例,要判断向上转型是否成立,可以调用 Class#isAssignableFrom 方法:

1
2
3
4
Integer.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Integer
Number.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Number
Object.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Object
Integer.class.isAssignableFrom(Number.class); // false,因为Number不能赋值给Integer

参考

https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/index.html

https://docs.oracle.com/javase/tutorial/reflect/index.html

JDK1.8 反射包新增了Parameter

https://www.liaoxuefeng.com/wiki/1252599548343744/1255945147512512

API:

Java SPI

SPI 全称 Service Provider Interface,Java 1.6 引入,是 Java 在语言层面为我们提供了一种方便地创建可扩展应用的途径。SPI 提供了一种 JVM 级别的服务发现机制,我们只需要按照 SPI 的要求,在 jar 包中进行适当的配置,JVM 就会在运行时通过懒加载,帮我们找到所需的服务并加载。如果我们一直不使用某个服务,那么它不会被加载,一定程度上避免了资源的浪费。

整体机制图如下:

Java SPI

使用例子

以 JDBC 为例,标准服务接口com.mysql.jdbc.Driver

MySQL 作为服务提供方,以 mysql-connector-java 5.1.44 为例,按规范要求其 META-INF/services/java.sql.Driver 配置文件中声明了两个实现类,如下:

1
2
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver

当类加载器载入 java.sql.DriverManager 类时,会执行其静态代码块,从而执行其中的 SPI 代码加载 JDBC Driver 实现,源码如下,详见:《Java 数据持久化系列(一)JDBC Driver 驱动程序总结》

1
2
3
4
5
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
while(driversIterator.hasNext()) {
Driver driver = driversIterator.next();
}

流程如下:

spi_flow_diagram

源码解析

ServiceLoader 的结构如下:

ServiceLoader

其成员变量如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public final class ServiceLoader<S>
implements Iterable<S>
{

// 类加载器加载配置文件时 所用的固定目录
private static final String PREFIX = "META-INF/services/";

// 代表被加载的类或者接口
// The class or interface representing the service being loaded
private final Class<S> service;

// 用于定位,加载和实例化 providers 的类加载器
// The class loader used to locate, load, and instantiate providers
private final ClassLoader loader;

// 创建 ServiceLoader 时采用的访问控制上下文
// The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;

// 缓存 providers,按实例化的顺序排列
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

// 懒查找迭代器
// The current lazy-lookup iterator
private LazyIterator lookupIterator;
}

SPI 的核心在于内部类 LazyIterator,承担了以下职责:

  1. 加载配置文件,解析、验证其内容
  2. 加载类
  3. 反射构造实例

核心源码及注释如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
    // Private inner class implementing fully-lazy provider lookup
//
private class LazyIterator
implements Iterator<S>
{

Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;

private boolean hasNextService() {
if (nextName != null) {
return true;
}
// 判断是否首次使用
if (configs == null) {
try {
// 本例中值为 META-INF/services/java.sql.Driver
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
// 使用类加载器从类路径中加载文件:META-INF/services/java.sql.Driver,如果多个 jar 包都存在该文件则结果为多个 URL 实例
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
// 依次解析 URL,获取 URL 内容的迭代器
pending = parse(service, configs.nextElement());
}
// 依次获取 URL 内容,例如第一条为 com.mysql.jdbc.Driver
nextName = pending.next();
return true;
}

private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// 使用指定的类加载器查找并加载类:com.mysql.jdbc.Driver
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
// 通过反射,调用 com.mysql.jdbc.Driver 的 public 无参构造方法创建 Object 实例对象,并强制转换为 interface java.sql.Driver 类型
S p = service.cast(c.newInstance());
// 塞入缓存,key 为 com.mysql.jdbc.Driver 字符串,value 是对应的实例对象
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}

...

}

使用场景

Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。这种动态加载机制的使用场景如下:

  • JDBC Driver 驱动程序管理类 java.sql.DriverManager。详见:JDBC Driver 驱动程序总结
  • JSR-303 Bean Validation 的 javax.validation.Validation
  • 日志门面接口实现类加载,SLF4J 加载不同提供商的日志实现类。
  • Spring
    • 对 servlet3.0 规范对 ServletContainerInitializer 的实现
    • 自动类型转换 Type Conversion SPI (Converter SPI、Formatter SPI) 等
    • Spring Factories 机制(SpringFactoriesLoader
  • Dubbo 通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。如果大家想要学习 Dubbo 的源码,SPI 机制务必弄懂。详见:http://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html

对比总结

下面总结下这几个加载类:

  • Java java.util.ServiceLoader
  • Spring org.springframework.core.io.support.SpringFactoriesLoader
  • Dubbo com.alibaba.dubbo.common.extension.ExtensionLoader
Java SPI Spring Factories Dubbo SPI
加载类 ServiceLoader SpringFactoriesLoader ExtensionLoader
加载文件 META-INF/services/接口全限定名 META-INF/spring.factories META-INF/dubbo
文件内容 接口实现类,多值以换行分隔 通过键值对方式(key=value)配置,多值以逗号分隔 通过键值对方式(key=value)配置,支持按需加载接口实现类
接口注解 / / @SPI

参考

https://www.jianshu.com/p/46b42f7f593c

JDK/Dubbo/Spring 三种 SPI 机制,谁更好?

各个类加载器的类路径(Classpath)

Java Launcher(即 java 命令)启动 Java 虚拟机时,各个类加载器按以下顺序类路径加载类:

Class Loader

三个类加载器使用的类路径,从以下系统属性中获取,可以通过 System.getProperty(...) 获取查看:

1
2
3
4
5
6
// Bootstrap ClassLoader
String property1 = System.getProperty("sun.boot.class.path");
// Extension ClassLoader
String property2 = System.getProperty("java.ext.dirs");
// Application ClassLoader
String property3 = System.getProperty("java.class.path");

下面详细介绍各个类加载器:

Bootstrap ClassLoader

负责加载构成 Java 平台的基础类(Bootstrap classes),位于 $JAVA_HOME/jre/lib 目录,包括 rt.jar 和其它几个重要 jar 文件中的类。这些基础类包括 Java 类库(Java Class Library (JCL))的公共类,以及此库可用的私有类。

几乎所有的 Java 类库(JCL) 都存储在一个名为“rt.jar”的 Java archive (jar) 归档文件中,该文件随 JREJDK 发行版一起提供。Java 类库(rt.jar)位于默认的 bootstrap classpath($JAVA_HOME/jre/lib)下,不必出现在为应用程序声明的 classpath 中。JRE 会使用引导类加载器(bootstrap class loader)找到 JCL。

Java 9 的模块系统目前已将这个单块的 rt.jar jar 包拆分并模块化。

Extension ClassLoader

负责加载扩展 Java 平台的扩展类(Extension classes)。位于 $JAVA_HOME/jre/lib/ext 扩展目录的每个 .jar 文件都被假定为扩展文件,并使用 Java Extension Framework 扩展机制加载。

sun.misc.Launcher$ExtClassLoader 执行过程中,URLClassPath 的值如下:

ext_classpath

例如,可以将 MySQL 厂商驱动程序 mysql-connector-java 放到该扩展目录中。

Application ClassLoader

负责加载由开发人员和第三方定义的未利用 Java 扩展机制的用户类(User classes)。

https://en.wikipedia.org/wiki/Classpath

Classpath is a parameter in the Java Virtual Machine or the Java compiler that specifies the location of user-defined classes and packages. The parameter may be set either on the command-line, or through an environment variable.

为了查找 User classes,Java Launcher(即 java 命令)将引用 User Classpath —— 一个包含了用户定义的类文件的目录、jar 包和 zip 包列表。Java Launcher(即 java 命令)将这个 User Classpath 字符串放到 java.class.path 系统属性中。该值的来源及优先级如下:

  • 默认值“ .”,表示当前工作目录下的所有类文件(如果在 jar 包中,则位于其下)。

  • CLASSPATH 环境变量,覆盖默认值。

    1
    2
    3
    4
    5
    # 查看 CLASSPATH 环境变量
    echo $CLASSPATH

    # 设置 CLASSPATH 环境变量
    set CLASSPATH=
  • -cp-classpath 命令行选项,覆盖默认值以及 CLASSPATH 环境变量。

    A semicolon (;) separated list of directories, JAR archives, and ZIP archives to search for class files.

    Specifying classpath overrides any setting of the CLASSPATH environment variable. If the classpath option isn’t used and CLASSPATH isn’t set, then the user class path consists of the current directory (.).

  • -jar 选项指定的 jar 包,它覆盖上述所有值。如果使用此选项,则所有用户类必须来自指定的 jar 包。

    Executes a program encapsulated in a JAR file. The jarfile argument is the name of a JAR file with a manifest that contains a line in the form Main-Class:classname that defines the class with the public static void main(String[] args) method that serves as your application’s starting point.

    When you use -jar, the specified JAR file is the source of all user classes, and other class path settings are ignored.

    If you’re using JAR files, then see jar.

参考:Setting the Class Path on Windows or Unix

⭐️ 注意点 1:User Classpath 使用字符串格式指定,路径不要含有空格,否则转义为 %20 之后会报错,例如:

1
java.lang.RuntimeException: Cannot resolve classpath entry: java.lang.RuntimeException: Cannot resolve classpath entry: D:\myprogram\mybatis%20tool\mybatis-generator-gui-0.8.4\target\classes\lib\mysql-connector-java-5.1.38.jar

⭐️ 注意点 2:每个路径使用以下方式进行分隔:

  • 在类 Unix 系统中,以冒号(:)分隔
  • 在 Windows 系统中,以分号(;)分隔

⭐️ 注意点 3:类文件具有反映“类的完全限定名称(class’s fully-qualified name)”的子路径名。例如 com.mypackage.MyClass

  • 如果类存储在 /myclasses 目录,则 /myclasses 必须在 User Classpath 中,并且类文件的完整路径必须为 /myclasses/com/mypackage/MyClass.class
  • 如果类存储在 myclasses.jar 中,则 myclasses.jar 必须在 User Classpath 中,并且类文件必须存储 myclasses.jar/com/mypackage/MyClass.class

下面来看几个例子,总结如下:

CLASSPATH 例子

Unpacked Classes

假设我们有一个名为主类:HelloWorld,存储在 D:\myprogram 目录下:

1
2
3
4
5
6
7
8
9
D:\myprogram\
|
---> org\
|
---> mypackage\
|
---> HelloWorld.class
---> SupportClass.class
---> UtilClass.class

查看 Windows 下 CLASSPATH 环境变量:

1
2
$ echo $CLASSPATH
.;E:\Developer\Java\jdk1.8.0_191\lib;

由于 CLASSPATH 环境变量默认包含当前目录(.),这意味着当我们的工作目录为 D:\myprogram\ 时,我们不需要显式指定 CLASSPATH

1
2
3
$ cd /D/myprogram
$ java org.mypackage.HelloWorld
hello world

否则,需要使用 -classpath 参数显式指定如下:

1
2
$ java -classpath D:\myprogram org.mypackage.HelloWorld
hello world

总结,设置 Classpath 的两种方式:

  • 使用 CLASSPATH 环境变量
  • 使用 -cp-classpath 命令行选项

JAR files

  • 单个 jar 包:使用绝对路径指定具体某个 jar 包
  • 多个 jar 包:使用绝对路径加上通配符 *

META-INF/MANIFEST.MF

如果程序已经打成 jar 包,需要使用清单文件指定入口类及 CLASSPATH,并使用 java -jar 命令启动。例如 Tomcat 的 bootstrap.jar 引导包:

1
2
3
......
Main-Class: org.apache.catalina.startup.Bootstrap
Class-Path: commons-daemon.jar

例子

Spring Boot 配置文件

Spring Boot will automatically find and load application.properties and application.yaml files from the following locations when your application starts:

  1. The classpath root
  2. The classpath /config package

The list is ordered by precedence (with values from lower items overriding earlier ones).

参考:External Application Properties

IDEA 如何查找类?

首先,为 IDEA 平台配置上你所拥有的 JDK 及路径:

Platform Settings SDKs

然后,为你的项目指定一个默认 SDK:

Project SDK

搞掂之后,IDEA 会为项目载入指定版本的 SDK,将基础目录 jre/lib/ 的 Bootstrap classes 和扩展目录 jre/lib/ext/ 的 Extension classes 加入 classpath:

External Libraries

参考

https://docs.oracle.com/en/java/javase/11/tools/java.html

https://docs.oracle.com/en/java/javase/11/tools/jar.html

1
2
3
4
5
# Lists the table of contents for the archive.
$ jar tf jar-file

# [Extracting the Contents of a JAR File](https://docs.oracle.com/javase/tutorial/deployment/jar/unpack.html)
$ jar xf jar-file [archived-file(s)]

Setting the Class Path on Windows or Unix

http://cr.openjdk.java.net/~mr/jigsaw/ea/module-summary.html

Wikipedia

类的加载过程

.java 源文件的从编译、加载、到对象创建的过程如下:

classloaoder_process

  • 首先,.java 源文件经过 javac 编译后生成 .class 类文件(内含字节码)。
  • 然后,通过 jar 命令或其它构建工具(如 Maven、Gradle)打包生成可运行的 jar 包。
  • 最终,通过 java -jar 命令运行 jar 包,执行其中清单文件(META-INF/MANIFEST.MF)中通过 Main-Class 指定的入口类的 main 方法以启动程序,并按照其 Class-Path 设置类路径。

从这里开始,就需要使用到类加载器将入口类(Main-Class)加载到 JVM。入口类在使用过程中如果使用到其它类,会根据类路径查找类文件并逐一加载。因此, jar 包中的类、及类路径中指定的类并不是一次性全部加载到 JVM 内存,而是使用到时才动态加载。可以指定启动参数 -verbose:class 输出类加载日志进行验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface Flyable {
void fly(String param);
}

public class Bird implements Flyable {
@Override
public void fly(String param) {}
}

public class Test {
public static void main(String[] args) {
System.out.println("===============");
Bird bird = new Bird();
}
}

类加载日志输出如下:

1
2
3
4
5
6
7
8
9
10
11
[Opened D:\tool\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded java.lang.Object from D:\tool\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded java.io.Serializable from D:\tool\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded java.lang.Comparable from D:\tool\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded java.lang.CharSequence from D:\tool\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded java.lang.String from D:\tool\jdk1.8.0_131\jre\lib\rt.jar]
...
[Loaded Test from file:/D:/workspaces/project-test/target/classes/]
===============
[Loaded com.github.proxy.Flyable from file:/D:/workspaces/project-test/target/classes/]
[Loaded com.github.proxy.Bird from file:/D:/workspaces/project-test/target/classes/]

类的生命周期

类从加载到 JVM 内存到被从内存中释放,经历的生命周期如下:

lifecycle_of_class

  • 加载阶段:包括根据类或接口的二进制名称(binary name)查找其字节码文件(可能是之前由 javac 编译器源代码编译出的字节码文件;或者是通过动态编译,例如 JDK 动态代理使用的 sun.misc.ProxyGenerator 工具类编译出的字节码文件 $Proxy0.class),并构造成一个表示该类或接口的 Class 类对象。加载阶段由类加载器 ClassLoader 及其子类负责实现:findClass 方法负责查找字节码文件,defineClass 方法负责构造成 Class 对象。
  • 验证阶段:确保类或接口的二进制代码在结构上是正确的。类文件校验器(Class File Verifier)会进行以下四类校验:
    • 文件完整性校验(File Integrity Check):第一步也是最简单的一步是检查类文件的结构。 它确保类文件具有适当的签名(前四个字节为魔数 0xCAFEBABE),并且类文件中的每个结构都具有适当的长度。它检查类文件本身不能内容过长或过短,并且常量池仅包含有效条目。当然,类文件的长度可能有所不同,但是每个结构(例如常量池)的长度作为文件规范的一部分都包含其中。
    • 类完整性校验(Class Integrity Check):
      • 该类具有父类(除非该类是 Object)。
      • 该父类不是一个 final 类,并且该子类不会尝试覆盖其父类中的 final 方法。
      • 常量池的条目格式正确,并且所有方法和字段引用均具有合法的名称和签名。
    • 字节码完整性校验(Bytecode Integrity Check):执行字节码校验器(Bytecode Verifier),检查每个字节码以确定代码在运行时的实际行为,包括对方法参数和字节码操作数的数据流分析,堆栈检查和静态类型检查。是整个验证阶段中最复杂的一步。
    • 运行时完整性校验(Runtime Integrity Check)
  • 准备阶段:包括为类或接口创建 static 静态字段(包括类变量和常量),并赋默认值。
  • 解析阶段:包括检查符号引用是否正确、将符号引用替换为直接引用。
  • 初始化阶段:
    • 类的初始化阶段包括执行 static 静态代码块、为 static 静态字段(变量)赋值。
    • 接口的初始化阶段包括为字段(接口字段默认为 public static final 常量)赋值。

各个步骤可以详见官方文档 “Execution” chapter of The Java™ Language Specification

execution

类加载器源码解析

Java 虚拟机中的类加载器(ClassLoader)负责加载来自文件系统、网络或其它来源的类文件。ClassLoader 是一个抽象类,其继承结构如下:

ClassLoader

类加载后,每个 Class 对象都包含一个定义它的类加载器的引用。可以通过以下方式查看:

1
2
3
4
5
6
7
8
9
10
11
public class Test {
@Test
public void test() {
// 结果为 null,因为启动类加载器为 C++ 编写
ClassLoader bootstrapClassLoader = java.lang.String.class.getClassLoader();
// sun.misc.Launcher$ExtClassLoader
ClassLoader extClassLoader = com.sun.crypto.provider.DESKeyFactory.class.getClassLoader();
// sun.misc.Launcher$AppClassLoader
ClassLoader appClassLoader = Test.class.getClassLoader();
}
}

ClassLoader 的核心方法如下:

类加载

loadClass (双亲委派)

loadClass 方法使用二进制名称(binary name)、通过“双亲委派模型(Delegation Model)”自顶向下尝试加载类,如下图所示:

classloader_hierarchy

⭐️ 这种设计的好处体现在:

  • 沙箱安全机制:例如自己写的 java.lang.String 类不会被加载,否则在 defineClass 方法这一步会报错,防止恶意代码污染,核心 API 库被随意篡改。核心 API 库只能由 Bootstrap ClassLoader$JAVA_HOME/jre/lib 目录进行加载。
  • 避免类的重复加载:当父加载器已经加载了该类时,就没有必要再加载一次,保证被加载类的唯一性。

类加载过程如下:

  1. 调用自身的 findLoadedClass(String) 方法以检查类是否已经被加载。
  2. 如未,则递归调用父加载器的 loadClass 方法(如果父加载器为 null,则使用虚拟机内置的 Bootstrap ClassLoader)。
  3. 如果父加载器都加载不到,则调用自身的 findClass(String) 方法查找类。
  4. 如果上述步骤找到了类,并且 resolve 标记为 true,则在目标 Class 对象上调用 resolveClass(Class) 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,检查类是否已经被加载
Class<?> c = findLoadedClass(name);
// 未被加载的情况
if (c == null) {
long t0 = System.nanoTime();
try {
// 如果父加载器不为 null,则委托父加载器去加载类
if (parent != null) {
// 调用父加载器的 loadClass 方法,委托其去加载类
c = parent.loadClass(name, false);
}
// 如果父加载器为 null,则委托 Bootstrap ClassLoader 去加载类
else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}

// 如果父加载器都加载不到,则调用自身的 findClass 方法查找类
if (c == null) {
long t1 = System.nanoTime();
c = findClass(name);

// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
// 如果上述步骤找到了类,并且 resolve 标记为 true,则在目标 Class 对象上调用 resolveClass(Class) 方法,进入“连接(Linking)”阶段(详见官方文档)
if (resolve) {
resolveClass(c);
}
return c;
}
}

加载到的 Class 可以通过反射的方式实例化对象:

1
2
Class<?> clazz = classLoader.loadClass("com.github.parent.HelloWorld");
Object instance = clazz.newInstance();

findClass

实现“加载阶段(Loading)”的查找功能。该方法应当被子类覆盖重写,用于使用指定的二进制名称(binary name)查找类或接口的字节码文件。ClassLoader 的默认实现是抛出一个 ClassNotFoundException

1
2
3
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}

ClassLoader

可以通过继承 ClassLoader 类,实现一个自定义的类加载器(参考 NetworkClassLoader 例子)。通过重写 findClass 方法可以从以下途径查找类文件:

  • 从 ZIP 包中读取,最常见,JAR,WAR,EAR 格式的基础。
  • 从网络中获取,典型场景是 Applet。
  • 运行时计算生成,典型情景是 JDK 动态代理技术。
  • 从其它文件中生成,典型场景是 JSP 应用,即由 JSP 文件生成对应的 Servlet Class 类。

也可以使用自带的 URLClassLoader 从本地路径(file:/)或网络路径(http://)加载类文件,示例如下:

1
2
3
4
5
6
7
8
// 从 E 盘中加载类文件
URLClassLoader classLoader = new URLClassLoader(new URL[] {new URL("file:/e:/")});
// 从 localhost 中加载类文件
// URLClassLoader classLoader = new URLClassLoader(new URL[] {new URL("http://localhost/testfile/")});
Class<?> clazz = classLoader.loadClass("com.github.parent.HelloWorld");
Object instance = clazz.newInstance();
// java.net.URLClassLoader
String name = instance.getClass().getClassLoader().getClass().getName();

下面是子类 URLClassLoader 的默认实现,源码及注释如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
// 将二进制名称替换为文件路径(类似全限定名),例如:com.github.HelloWorld > com/github/HelloWorld.class
String path = name.replace('.', '/').concat(".class");
// 从 URLClassPath 对象中查找文件
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
// 如果找到文件,则构造为 Class 类实例
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}

可见,如果找不到类(result == null),最底层的 ClassLoader 将抛出 ClassNotFoundException

类可以按需动态加载到内存,这是 Java 的一大特点,也称为运行时绑定,或动态绑定。

defineClass

实现“加载阶段(Loading)”的构造功能。

ClassLoader 提供了四个 defineClass 方法可供自定义类加载器时使用,如下图。其中,第二个方法最常使用:defineClass(String name, byte[] b, int off, int len)

defineClass

其调用的底层源码如下,会调用 preDefineClasspostDefineClass 进行预处理和后置处理:

1
2
3
4
5
6
7
8
9
10
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
protectionDomain = preDefineClass(name, protectionDomain);
String source = defineClassSourceLocation(protectionDomain);
Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
postDefineClass(c, protectionDomain);
return c;
}

如果自定义类加载器打破了双亲委派模型,然后还去加载核心 API 库,例如自己伪造一个 java.lang.String,会报错如下:

1
2
3
4
5
6
7
8
java.lang.SecurityException: Prohibited package name: java.lang
at java.lang.ClassLoader.preDefineClass(ClassLoader.java:659)
at java.lang.ClassLoader.defineClass(ClassLoader.java:758)
at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
at com.github.MyClassLoader.findClass(...)
at com.github.MyClassLoader.loadClass(...)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
...

这是由于 preDefineClass 预处理方法进行了二进制名称的前缀校验,源码如下,关键判断 name.startsWith("java.") 抛出异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/* Determine protection domain, and check that:
- not define java.* class,
- signer of this class matches signers for the rest of the classes in
package.
*/
private ProtectionDomain preDefineClass(String name,
ProtectionDomain pd)
{
if (!checkName(name))
throw new NoClassDefFoundError("IllegalName: " + name);

// Note: Checking logic in java.lang.invoke.MemberName.checkForTypeAlias
// relies on the fact that spoofing is impossible if a class has a name
// of the form "java.*"
// 关键判断
if ((name != null) && name.startsWith("java.")) {
throw new SecurityException
("Prohibited package name: " +
name.substring(0, name.lastIndexOf('.')));
}
if (pd == null) {
pd = defaultDomain;
}

if (name != null) checkCerts(name, pd.getCodeSource());

return pd;
}

resolveClass

进入类加载的“连接(Linking)”阶段(详见官方文档 “Execution” chapter of The Java™ Language Specification)。

资源加载

使用场景:例如 Spring Factories 机制中 SpringFactoriesLoader 加载类路径下的文件:

1
classLoader.getResources("META-INF/spring.factories")

文件加载后,通过 key-value 的方式读取指定 key,并以反射的方式实例化指定的类型。

getResource

查找指定名称的资源(图像、音频、文本等)。资源的名称是用“/”分隔的路径名,用于标识资源。
该方法首先递归调用父加载器查找资源;如果父加载器为 null,则使用虚拟机内置的启动类加载器(Bootstrap ClassLoader)。如果父加载器查找失败,则调用自身的 findResource(String) 查找资源。整个资源加载过程仍然为“双亲委派模型(Delegation Model)”:

1
2
3
4
5
6
7
8
9
10
11
12
public URL getResource(String name) {
URL url;
if (parent != null) {
url = parent.getResource(name);
} else {
url = getBootstrapResource(name);
}
if (url == null) {
url = findResource(name);
}
return url;
}

findResource

查找指定名称的资源。类加载器实现应当重写此方法以指定在何处查找资源。默认返回 null

1
2
3
protected URL findResource(String name) {
return null;
}

下面是子类 URLClassLoader 的默认实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
public URL findResource(final String name) {
/*
* The same restriction to finding classes applies to resources
*/
URL url = AccessController.doPrivileged(
new PrivilegedAction<URL>() {
public URL run() {
return ucp.findResource(name, true);
}
}, acc);

return url != null ? ucp.checkURL(url) : null;
}

参考

JavaDoc

其它:

证券投资基金是资产管理的主要方式之一,它是一种组合投资、专业管理、利益共享、风险共担的集合投资方式。它主要通过向投资者发行受益凭证(基金份额),将众多不特定投资者的资金汇集起来,形成独立财产,委托基金管理人进行投资管理,基金托管人进行财产托管,由基金投资人共享投资收益、共担投资风险的集合投资方式。

投资基金是一种间接投资工具,基金投资者、基金管理人、托管人是基金运作中的主要当事人。

基金管理机构和托管机构分别作为基金管理人和基金托管人,一般按照基金的资产规模获得一定比例的管理费收入和托管费收入(即为净值型产品)

基金概况

证券投资基金概况

基金分类

证券投资基金分类

按法律形式

契约型基金 公司型基金
法律主体资格不同 不具有法人资格 具有法人资格
投资者的地位不同 依据基金合同成立,基金投资者可通过持有人大会表达意见,但权利相对较小 通过股东大会,持有人权利较大
基金组织方式和营运依据不同 借用了信托法律制度,依据基金合同营运基金,基金投资人和基金管理人、托管人之间是信托委托人、受托人和受益人。基金投资人通过基金持有人大会行使权力。 借用了《公司法》规定的股份有限公司的组织方式,其依据投资公司章程营运基金,设有股东会、董事会等决策监督机构,基金投资人通过股东会行使权力,设立董事会进行相关事务的决策与监督,基金管理人的身份是公司董事会聘请的投资顾问。
优点 在设立上更为简单易行 法律关系明确清晰,监督约束机制较为完善
范围 中国的基金均是契约型基金 美国的投资公司为代表

按运作方式

开放式基金 封闭式基金
份额 不固定 固定
存续期限 不确定,理论上可以无限期存续 确定
交易方式 一般不上市,通过向基金管理公司和代销机构进行申购、赎回 上市流通
交易价格 按照每日基金单位资产净值(NAVPS) 根据市场行情变化,相对于单位资产净值可能折价或溢价,多为折价
估值频率 每个交易日估值,次日公告 每个交易日估值,每周披露
信息披露 每日公布基金单位资产净值(NAVPS),每季度公布资产组合,每六个月公布变更的招募说明书 每周公布基金单位资产净值(NAVPS),每季度公布资产组合
投资策略 强调流动性管理,基金资产中要保持一定现金及流动性资产 全部资金在封闭期内可进行长期投资
收益分配频率 开放式基金的基金合同应当约定每年基金收益分配的最多次数和基金收益分配的最低比例。实践中,许多基金合同规定每年至少一次。如果是货币基金,一般为每日结转收益或按月结转收益。 每年不得少于一次
收益分配方式 1、现金分红;2、分红再投资转换为基金份额 现金分红
总结 基金份额不固定,基金份额可以在基金合同约定的时间和场所进行申购、赎回的一种基金运作方式 基金份额在基金合同期限内固定不变,基金份额可以在证交所交易,但基金份额持有人不得申请赎回

按资金募集方式

公募基金 私募基金
制度 基金募集注册制。即证监会不进行实质性审核,而只是进行合规性审查。 基金管理人登记制,即私募基金的基金管理人只需向基金业协会登记即可,无须中国证监会审批。
申请期限 依据《证券投资基金法》的规定,公募基金应当经中国证监会注册。证监会在受理申请之日起 6 个月内依照法律法规进行审查,作出注册或者不予注册的决定。 基金业协会应当在私募基金管理人登记材料齐备后的 20 个工作日内,通过网站公告私募基金管理人名单及其基本情况的方式,为私募基金管理人办理登记手续。
监管主体 由证监会监管 由基金业协会制定相关指引和准则,实行自律管理。
提交文件 1、申请报告;2、基金合同草案;3、基金托管协议草案;4、招募说明书草案;5、律所出具的法律意见书;6、中国证监会规定提交的其它文件。 1、工商登记和营业执照正副本复印件;2、公司章程或者合伙协议;3、主要股东或者合伙人名单;4、高级管理人员的基本信息;5、基金业协会规定的其它信息。
募集群体 不特定对象、或特定对象累计超过 200 人。 合格投资者,且累计不得超过 200 人。

按投资对象

股票基金

债券基金

债券 债券基金
利息 有固定的利息收入 不同债券的组合,利息不固定
到期日 有确定的到期日 没有确定的到期日
收益率 单一债券的收益率可以根据购买价格、现金流以及到期收回的本金计算其投资收益率 较难计算和预测收益率
投资风险 单一债券的信用风险比较集中,随着到期日的临近,所承担的利率风险会下降 债券基金通过分散投资可以有效避免单一债券可能面临的较高的信用风险。由于没有固定到期日,所承担的利率风险将取决于所持有的债券的平均到期日

债券基金的风险如下:

  1. 利率风险
  2. 再投资风险
  3. 信用风险
  4. 流动性风险
  5. 提前赎回风险
  6. 可转债的特定风险
  7. 债券回购风险

货币基金

货币市场基金在投资组合中的作用

与其它类型基金相比,货币市场基金具有风险低、流动性好的特点,是厌恶风险、对资产流动性安全性要求较高的投资者进行短期投资和现金管理的理想工具,或是暂时存放现金的理想场所。

货币市场基金的投资对象与货币市场工具

货币市场基金的投资对象是货币市场工具,通常指到期日不足 1 年的短期金融工具,也成为现金投资工具。货币市场工具通常由政府、金融机构以及信誉卓著的大型工商企业发行,流动性好、安全性高,但其收益率与其它证券相比则非常低。货币市场与股票市场的一个主要区别是:货币市场进入门槛通常很高,在很大程度上限制了一般投资者的进入。此外,货币市场属于场外交易市场,交易主要由买卖双方通过电话或电子交易系统以协商价格完成。货币市场基金的投资门槛极低,因此,货币市场基金为普通投资者进入货币市场提供了重要通道。

按照中国证监会和中国人民银行 2015 年颁布的《货币市场基金监督管理办法》的规定,货币市场基金应当投资于以下金融工具:

  1. 现金;
  2. 期限在 1 年以内(含 1 年)的银行存款、债券回购、中央银行票据、同业存单;
  3. 剩余期限在 397 天以内(含 397 天)的债券、非金融企业债务融资工具、资产支持证券;
  4. 中国证监会、中国人民银行认可的其它具有良好流动性的货币市场工具。

货币市场基金不得投资于以下金融工具:

  1. 股票;
  2. 可转换债券、可交换债券;
  3. 以定期存款利率为基准利率的浮动利率债券,已进入最后一个利率调整期的除外;
  4. 信用等级在 AA+ 以下的债券与非金融企业债务融资工具;
  5. 中国证监会、中国人民银行禁止投资的其它金融工具。

货币市场基金的支付功能

由于货币市场基金风险低、流动性好,通过以下机制设计,基金管理公司将货币市场基金的功能从投资拓展为类似货币的支付功能:

  1. 每个交易日办理基金份额申购、赎回;
  2. 在基金合同中将收益分配的方式约定为红利再投资,并每日进行收益分配(每日结转收益);
  3. 每日按照面值(一般为 1 元)进行报价。

基金术语

基金分红

基金收益分配(即分红)是指将本基金的净收益根据持有基金单位的数量(即持有份额)按比例向基金持有人进行分配。若基金上一年度亏损,当年收益应先用于弥补上年亏损,在基金亏损完全弥补后尚有剩余的,方能进行当年收益分配。基金当年发生亏损无净收益的,不进行收益分配。

基金进行收益分配会导致基金份额净值的下降,但对投资者的利益没有实际影响。一只基金在收益分配前份额净值为1.2元,每份基金分配0.05元收益,在分配后基金份额净值将会下降到1.15元。但对投资者来说,分红前后的价值是不变的。

收益分配后基金份额净值不得低于面值

现有制度下,基金分红主要有两种形式:

  1. 现金分红。是指直接获得现金红利,落袋为安;
  2. 红利再投资转换为基金份额。红利再投资是指将所分得的现金红利再投资该基金或者购买的个股,从而达到增加原先持有基金或股票的份额,俗称“利滚利”,这样做既可免掉再投资的申购费,而且再投资所获得的基金份额还可以享受或增加下次分红的数额,可使基金份额随着分红的次数而增加。

假如选择红利再投资方式对开放式基金进行长期投资,则可享受基金投资增值的复利增长效果。例如,假如开放式基金每年分红5%,选择红利再投资,则10年后资金将增值为62.89%;而假如同样的收益情况,选择现金分红方式,则10年后资金只增值为50%,收益少了12.89%。假如投资时间更长,则差别更大。

中国法律规定默认的基金分红方式是现金分红,投资者可以通过销售机构将分红方式变更为红利再投资,以获得更大的回报。

现金分红涉;及三个日期:

  1. 权益登记日:是基金公司进行红利分配时,需要定出某一天,界定哪些基金持有人可以参加分红,定出的这一天就是权益登记日。也就说,在权益登记日当天仍持有或申购基金并得到确认的投资者均可享受此次分红。

  2. 除息日:分红方案中确定的将红利从基金资产中扣除的日期。除息日当天,基金净值将会降低,如果不考虑当日市场波动,下降的幅度就是单位基金的现金分红额。

  3. 派息日:也就是基金分红拨付给基金持有人的日子。

基金分拆

基金分拆是指保证投资者的投资总额不发生改变的前提下,将一份基金按照一定的比例分拆为若干份,每一基金份额的单位净值也按相同比例降低:

  • 分拆比例大于 1 的分拆为基金分拆;
  • 分拆比例小于 1 的分拆为基金合并。

基金份额净值

  • 基金资产估值:通过对基金所拥有的全部资产及全部负债按一定的原则和方法进行估算,进而确定基金资产公允价值的过程。
  • 基金资产总值(AV, Asset Value):基金全部资产的价值总和。
  • 基金资产净值(NAV, Net Asset Value):基金资产 - 基金负债(NAV = Assets - Liabilities)
  • 基金份额净值(NAVPS, Net Asset Value Per Share):基金资产净值 / 基金总份额(NAVPS = Net Asset Value (NAV) / Number of Shares Outstanding)。也叫“单位净值”,与“累计净值”的区别查看这里
  • 基金份额:是指基金发起人向投资者公开发行的,表示持有人按其所持份额对基金财产享有收益分配权、清算后剩余财产取得权和其他相关权利,并承担相应义务的凭证。

一图看清“基金份额净值”:基金份额净值

基金份额类型(费率模式)

常见的基金收费有申购费、赎回费、管理费、托管费、销售服务费。还有一个认购费,但那是只有在基金募集期才会用到的。这么多费率,怎么区分?

货币基金

一般来说,我们常见的货币基金主要分A类、B类,其实货基的A、B类相差并不大,在收益方面的差别也不是很大,但门槛方面相差还是比较多的(具体以基金合同为准):

基金份额类型(费率模式) 描述
A类份额 货币基金中的低门槛份额,适用于认购、申购金额低于500万的投资者,散户投资者买货基都是此类;
B类份额 货币基金中的高门槛份额,适用于认购、申购金额高于500万的投资者,专为高净值客户或机构客户设置。

股票型/混合型/债券型基金

基金份额类型(费率模式) 费用差别 适合人群
A类份额 前端收费,即申购时收取申购费(不同的销售渠道会有打折) 投资时间没有明确判断
B类份额 后端收费,即赎回时收取申购费(持有的时间越长,申购费越少,持有超过一定年限后,不再收取) 长期持有(一般3~5年)
C类份额 无申购费,但按日收取销售服务费 短期持有(一般1~2年)

值得注意的是:A类份额和B类份额,都要收取一定的基金管理费和托管费,但是不收取销售服务费。

A类基金:前端申购费+赎回费+管理费+托管费
B类基金:后端申购费+赎回费+管理费+托管费
C类基金:销售服务费+赎回费+管理费+托管费

持有区间收益率

  • 资产回报:是指股票、债券、房地产等资产价格的增加或减少。
  • 资产回报率 = (期末资产价格 - 期初资产价格) ÷ 期初资产价格 × 100%
  • 收入回报:包括分红、利息等。
  • 收入回报率 = 期间收入 ÷ 期初资产价格 × 100%
  • 持有区间收益 = 资产回报 + 收入回报
  • 持有区间收益率 = 资产回报率 + 收入回报率
  • 除权:因送股或配股而形成的剔除行为。
  • 除息:因派息而引起的剔除行为。
  • 除权(息)参考价 = (前收盘价 - 现金红利 + 配股价格 × 股份变动比例) ÷ (1+ 股份变动比例)

公式

申购计算公式

净申购金额=申购金额/(1+申购费率)

申购费用=申购金额-净申购金额

申购份额=净申购金额/T日基金份额净值

在金融市场上,资金的供给者通过投资金融工具获得各种类型的金融资产。金融资产是代表未来收益或资产合法要求权的凭证,标示了明确的价值,表明了交易双方的所有权关系和债权关系。它分为债券类金融资产和股权类金融资产两类。债券类金融资产以票据、债券等契约型投资工具为主,股权类金融资产以各类股票为主。资金的供给者产生了对金融资产进行管理的需求。

资产管理并没有一个得到广泛认可的明确定义,根据国内外行业共识,资产管理一般是指金融机构受投资者委托,为实现投资者的特定目标和利益,进行证券和其它金融产品的投资并提供金融资产管理服务、收取费用的行为。
从资产管理的外延来看,资产管理广泛涉及银行、证券、保险、基金、信托、期货等行业,但是具体范围并无明确界定。

资产管理提供的是代客理财服务,与储蓄产品有着本质区别:

  1. 对储蓄而言,存款人与银行是债权人与债务人关系,银行必须按照约定到期偿还本金,支付利息;而对资产管理而言,投资人与管理人是委托人和受托人关系,投资人自担风险、自享收益,管理人只作为管理顾问收取一定比例的管理费。
  2. 资产管理人对于投资人的根本效用价值在于通过集合资金、组合投资,有效管理风险,获取更合理的风险回报。所获取的收益与其承担的风险相匹配。

资产管理的简单总结如下图:

金融资产管理总结

金融市场和金融机构作为金融的微观运作载体,也是人们日常生活中接触最多的内容。下面总结一点《金融学》学习过程中总结的笔记。

什么是金融市场?

  • 广义的金融市场:是货币借贷、资金融通、买卖票据和有价证券等所有金融交易活动的总称,包括直接融资和间接融资在内的所有金融投融资活动。
  • 狭义的金融市场:专指以金融工具为载体的交易活动,即直接融资和金融投资活动。

金融的两大投资:

  • 产业投资:投资于实体经济的活动,如投资于工业、农业、服务业等,这些投资最后会形成各种各样的固定资产和流动资产,通过生产经营会产品利润,从而给投资者带来相应的回报。产业投资是最基本、最重要的投资,是经济发展的根基和利润的源泉。
  • 金融投资:以金融资产为标的物的投资活动,如买卖股票、债券、外汇等的投资活动。与产业投资相比,金融投资具有流动性强、交易成本低、风险和收益相对较高的特点。

金融市场的特点:

  • 以资金为交易对象,借助金融工具完成交易。
  • 交易之间不单纯是买卖关系,更主要的是信用关系,体现了货币所有权和使用权相分离的原则。
  • 可以是有形市场,也可以是无形市场。

金融市场的功能:

  • 资产配置与转化
  • 价格发现
  • 风险分散和规避
  • 宏观调控传导

总结:

  • 金融市场理论是现代金融学最前沿和最核心的内容。
  • 金融市场是资金供求双方借助金融工具以一定的价格进行各种投融资活动的场所。
  • 投融资需求是金融市场产生和发展的基础。
  • 金融市场的交易对象主要是金融工具金融资产价格是市场交易最敏感的因素。
  • 利率、汇率金融资产价格是金融市场交易最主要的价格机制。
  • 各经济主体都可以通过金融市场利用相应的信用形式进行投融资活动。

金融市场的分类

金融市场的构成要素

金融市场的构成要素

金融市场的构成三要素如下:

  • 市场参与者
  • 金融工具
  • 金融交易的组织形式

金融市场的构成要素

市场参与者

金融市场的参与者主要包括政府、中央银行、金融机构、个人和企业。

政府

中央银行

中央银行被称为银行的银行。如果把每家商业银行看作一家分行,央行相当于总行。

对于商业银行来说,中央银行扮演了三个角色:

  • 第一个角色,监管人。商业银行必须把吸纳的一部分存款,存到央行,称为存款准备金
  • 中央银行的第二个角色是,金融中心。商业银行之间转账,只需要在双方的央行准备金账户上增减数字,不需要实际转移资金。央行充当了交易清算的处理中心
  • 央行的第三个角色是,最终贷款人。如果一家商业银行没有足够的资金,可以进行银行间同业拆借(参考:shibor,上海银行间同业拆放利率,Shanghai Interbank Offered Rate);如果其它商业银行也没有足够的资金,他们有一个最终的求助对象,那就是央行。求助的途径有两个:
    • 再贷款,即商业银行向央行寻求贷款;
    • 再贴现,即商业银行把手中没有到期的票据卖给央行。

央行通过三大货币政策工具,最终决定全社会货币量:

  • 首先,央行通过再贷款和再贴现,可以扩大商业银行系统的贷款和投资规模,从而增加货币数量。

  • 其次,央行通过存款准备金率,设定了商业银行贷款和投资额度的上限,也就为货币的派生数量设定了上限。银行贷款派生存款(货币)过程如下:

    存款生贷款、贷款生存款,循环派生,但是每次新增贷款的数量都在减少,最终派生的货币数量会有上限,即创造的货币数量会有上限,它是初始存款金额的一个倍数,称为「派生倍数」,计算公式:1 / 准备金率。例如:100W / 20% = 500W,即 100W 的存款,理论上最多创造出 400W 货币,派生过程如下:

    80

    64

    51.2

    40.96

    32.768

    26.2144

    20.97152

    16.777216

    13.4217728

    10.73741824

    可见,存款准备金率和派生货币量存在反比关系。

  • 最后,央行还可以通过公开市场操作(OMO),影响商业银行体系的准备金数量,进而调节货币数量。(在多数发达国家,公开市场操作是中央银行吞吐基础货币,调节市场流动性的主要货币政策工具。)

货币政策工具

参考:

金融机构

金融机构是金融市场上最重要的中介机构,它的作用较为特殊:

  1. 它是金融市场上最重要的中介机构,是储蓄转化为投资的重要渠道。
  2. 金融机构在金融市场上充当资金的供给者、需求者和中间人等多重角色,它既发行、创造金融工具,也在市场上购买各类金融工具。既是金融市场的中介人,也是金融市场的投资者、货币政策的传递者和承受者。
  3. 金融机构作为机构投资者在金融市场具有支配性的作用。

金融机构的主要功能如下:

  • 便利支付结算
  • 促进资金融通
  • 降低交易成本
  • 改善信息不对称
  • 转移与管理风险
  • 创造信用与存款货币

金融机构的经营体制有两种:

  • 分业经营,即对金融机构业务范围进行某种程度的分离管制。
  • 混业经营,即允许各类金融机构业务范围有交叉,可以进行综合经营的金融制度。

两种经营体制存在的争论如下:

  • 对金融机构稳健经营的影响?
  • 混业经营是否会产生利益冲突?
  • 加强竞争和提高效率方面的比较?
  • 规模经济和范围经济优势的比较?

由于金融机构是如此重要,因此国家需要进行重点监管,避免出现系统性风险,下图我画了一张图总结中国的金融监管体系和金融机构体系:

中国的金融监管体系和金融机构体系

上图主要采用 IMF 的统计分类法,其将金融机构分为:

  • 存款类金融机构:以吸收存款作为资金主要来源,以发放贷款为主要的资金运用方式,以办理转账结算为主要中间业务,参与存款货币创造的金融机构,也称银行业金融机构。
  • 非存款类金融机构:以发行金融工具签订契约等方式获得资金,通过特定的方式运营这些资金,也称非银行类金融机构。

其中,商业银行作为存款类金融机构中最具代表性和占比最大的机构,其特点与主要业务如下:

商业银行

个人居民

个人居民是金融市场上主要的资金供给者。个人居民为了预防未来支出的不确定性或出于节俭等目的。将收入的一部分用于储蓄。不少个人居民动用储蓄资金投资于股票、债券、基金等资本市场工具,投资于保险市场或参与黄金市场交易。组合其金融资产,实现风险和收益的最佳匹配。个人居民投资者是金融市场供求均衡的重要力量。

金融工具

金融工具是金融市场上进行交易的载体。金融工具最初被称为信用工具,是证明债权债务关系并据以进行货币资金交易的合法凭证。金融工具是法律契约,交易双方的权利和义务受法律保护。

金融工具一般具有广泛的社会可接受性,随时可以流通转让。不同的金融工具具有不同的特点,能分别满足资金供需双方在数量、期限和条件等方面的不同需要,在不同的市场上为不同交易者服务。

金融工具的四个特征:

  • 法律性
  • 流动性
  • 收益性
  • 风险性

金融工具的分类方法如下:

金融工具的分类方法

在金融市场上,资金的供给者通过投资金融工具获得各种类型的金融资产。金融资产是代表未来收益或资产合法要求权的凭证,标示了明确的价值,表明了交易双方的所有权关系和债权关系。

金融交易的组织形式

金融交易的组织形式是指组织金融工具交易时采用的方式。受市场本身的发育程度、交易技术的发达程度、交易双方的交易意愿影响。
目前场内市场与场外市场之间的截然划分已经不复存在,出现了多层次的证券市场结构。很多传统意义上的场外市场由于报价商和电子撮合系统的出现而具有了集中交易特征,证券交易所市场也开始逐步推出兼容场外交易的交易组织形式,场内市场和场外市场的物理界限逐渐模糊。
如今,场内市场和场外市场的概念逐步演变为风险分层管理的概念,即不同层次市场按照上市品种的风险大小,通过对上市或挂牌条件、信息披露制度、交易结算制度、证券产品设计以及投资者约束条件等做出差异化安排,实现了资本市场交易产品的风险纵向分层。

参考

《金融学》——李健

国内银行业金融机构

17家民营银行大盘点

债权转让和收益权转让的区别?