泛型(Generics)是一种允许在定义类、接口或方法时使用类型参数的机制。泛型的主要目的是提供类型安全性,并减少类型转换的需要。通过泛型,可以编写更通用、更灵活的代码,同时避免运行时类型转换错误。
主要是为了实现类型安全和使用时的一点点方便(不需要再转换类型),元素拿出来就可以直接用
规定这个容器只能装什么类型的数据(元素)
1
List < String > list = new ArrayList <> (); // 只能装String类型的容器
Java的泛型是在编译时期规定这个容器只能装什么类型的数据的,在运行时对于JVM来说,是没有泛型这个区别的,因为你在编译时期添加元素的时候,已经限定了它们都是同一个类型的,因此在运行时,是不存在类型安全问题的,所以在运行时JVM会擦除掉泛型限定,这个叫做泛型的擦除
Java的泛型是假泛型,它只在编译期有效,运行时期完全不保留,因此可以通过泛型的擦除来绕过编译器的检查,如下代码所示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Main {
public static void main ( String [] args ) {
/*
本来list只能装String类型,但通过泛型擦除绕过了编译器的检查
添加了其他类型的数据,这是很危险的,这就是泛型擦除带来的问题
*/
ArrayList < String > list = new ArrayList <> ();
ArrayList raw = list ;
raw . add ( 1 );
raw . add ( new Object ());
ArrayList < String > newList = list ;
System . out . println ( list . get ( 0 ));
}
}
ArrayList 不是 ArrayList的子类
1
2
3
4
5
6
7
8
9
10
11
12
public class Main {
public static void main ( String [] args ) {
ArrayList < String > arrayList = new ArrayList <> ();
arrayList . add ( "Hello" );
print ( arrayList ); // 报错
}
public static void print ( ArrayList < Object > arrayList ) {
for ( Object o : arrayList ) {
System . out . println ( o . toString ());
}
}
}
当你使用一个裸类型时,编译器会警告
1
ArrayList list = new ArrayList ();
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
public class Main {
public static void main ( String [] args ) {
System . out . println ( max ( 1 , 2 ));
System . out . println ( max ( 1 . 0 , 2 . 0 ));
System . out . println ( max ( "AAA" , "BBB" ));
}
// 将参数,返回值,等改成泛型参数,在未来动态决定是什么类型,这种操作我们称为泛型绑定
public static < T > T max ( T a , T b ) {
return a ;
}
// <T extends Comparable> 就是泛型限定符,它限制了T类型必须是Comparable的子类或实现了Comparable接口的类
public static < T extends Comparable < T >> T max ( T a , T b ) {
return a . compareTo ( b ) >= 0 ? a : b ;
}
// 泛型类型可以不同,例如这里的T和E它代表两个不同的类型,泛型限定符可以绑定多个,多个限定符用逗号隔开
public static < T extends Comparable < T > , E extends Comparable < T >> T max ( T a , E b ) {
return a ;
}
/*
public static T max (T a, T b) {
return a > b ? a : b; // 我们想把它泛型化,但这里依然不能比较,因为它不知道你这个T是什么玩意
}
*/
// 没有泛型的年代,就只能如下方式处理,非常啰嗦
/*
public static Integer max(Integer a, Integer b) {
return a > b ? a : b;
}
public static Long max(Long a, Long b) {
return a > b ? a : b;
}
public static Double max(Double a, Double b) {
return a > b ? a : b;
}
*/
}
? extends 要求泛型是某种类型及其子类型
1
2
3
4
5
6
7
8
9
10
11
12
public class Main {
public static void main ( String [] args ) {
System . out . println ( max ( 1 , 2 ));
System . out . println ( max ( 1 . 0 , 2 . 0 ));
System . out . println ( max ( "AAA" , "BBB" ));
}
// <T extends Comparable> 就是泛型限定符,它限制了T类型必须是Comparable的子类或实现了Comparable接口的类
public static < T extends Comparable < T >> T max ( T a , T b ) {
return a . compareTo ( b ) >= 0 ? a : b ;
}
}
? super 要求泛型是某种类型及其父类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.Comparator ;
import java.util.List ;
public class Main {
// 接受2个泛型参数,Comparator<? super T>表示传进来的这个Comparator必须是T的父类型或T类型
public static < T > void sort ( List < T > list , Comparator <? super T > c ) {
list . sort ( c );
}
// 接受1个泛型参数,T必须继承Comparable,而Comparable必须是T类型的父类
public static < T extends Comparable <? super T >> void sort ( List < T > list ) {
list . sort ( null );
}
}
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
public class Main {
static class IntBinaryTreeNode {
int value ;
IntBinaryTreeNode left ;
IntBinaryTreeNode right ;
}
static class StringBinaryTreeNode {
String value ;
StringBinaryTreeNode left ;
StringBinaryTreeNode right ;
}
static class DoubleBinaryTreeNode {
double value ;
DoubleBinaryTreeNode left ;
DoubleBinaryTreeNode right ;
}
// 你看,上面三种"二叉树节点"结构相似,内容重复,请将其泛型化,以节省代码
static class BinaryTreeNode < T > {
T value ;
BinaryTreeNode < T > left ;
BinaryTreeNode < T > right ;
}
// 泛型化之后,请再编写一个算法,对二叉树进行中序遍历,返回中序遍历的结果
public static < T > List < T > inorderTraversal ( BinaryTreeNode < T > root ) {
List < T > list = new ArrayList <> ();
if ( root . left != null ) {
list . addAll ( inorderTraversal ( root . left ));
}
list . add ( root . value );
if ( root . right != null ) {
list . addAll ( inorderTraversal ( root . right ));
}
return list ;
}
}
我们之前说了,Class对象是类的说明书,而注解就是说明书中的一小段说明信息
注解只是一小段信息,至于如何处理,和注解没有半毛钱关系,如何处理它是由使用这个注解的对象决定的
注解的创建和普通的类没有什么太大区别,关键字是@interface
1
2
public @interface MyAnnotation {
}
元注解就是可以放在注解上面的注解
Retention 指定这个注解在编译后的保留等级
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
}
Target 指定这个注解可以被标注在什么东西上面
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
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE ,
/** Field declaration (includes enum constants) */
FIELD ,
/** Method declaration */
METHOD ,
/** Formal parameter declaration */
PARAMETER ,
/** Constructor declaration */
CONSTRUCTOR ,
/** Local variable declaration */
LOCAL_VARIABLE ,
/** Annotation type declaration */
ANNOTATION_TYPE ,
/** Package declaration */
PACKAGE ,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER ,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
Documented 不常用,用@Documented注解修饰的注解类会被Javadoc工具提取成文档
Inherited 不常用,指定这个注解能否被子类所看到
Repeatable 不常用,指定这个注解是否可以被重构
1
2
3
4
5
6
7
8
9
10
11
12
@Target ( ElementType . METHOD )
public @interface MyAnnotation {
// 属性类型,属性名,默认值
String name () default "Hello Annotation" ;
}
public class Main {
@MyAnnotation ( name = "Jerry" )
public static void main ( String [] args ) {
System . out . println ( "Hello World!" );
}
}
基本数据类型+String+类以及它们的数组
当只有一个value属性时,调用时可以不指定属性名
1
2
3
4
5
6
7
8
9
10
11
12
@Target ( ElementType . METHOD )
public @interface MyAnnotation {
// 属性类型,属性名,默认值
String value () default "Hello Annotation" ;
}
public class Main {
@MyAnnotation ( "Jerry" )
public static void main ( String [] args ) {
System . out . println ( "Hello World!" );
}
}
@Override 编译器会检查你是否覆盖了父类的方法
@Deprecated 编译器会标记这个方法已经过时
@SuppressWarnings 抑制一些警告
@FunctionalInterface 标记这是一个函数式接口
我们知道注解本身是不具备任何功能的,它只是一段说明,要想给注解赋予真正的功能,
我们需要通过反射在运行时动态的赋予它各种能力(为什么只能在运行时动态赋予呢?)
因为它本身就不具备什么功能嘛,源码层面上没有体现,这就需要在运行时动态生成代码
1
2
3
4
5
6
7
8
9
10
package com.github.hcsp.annotation ;
import java.lang.annotation.Retention ;
import java.lang.annotation.RetentionPolicy ;
@Retention ( RetentionPolicy . RUNTIME )
public @interface Cache {
// 标记缓存的时长(秒),默认60s
int cacheSeconds () default 60 ;
}
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
package com.github.hcsp.annotation ;
import java.util.List ;
import java.util.Random ;
import java.util.stream.Collectors ;
import java.util.stream.IntStream ;
public class DataService {
/**
* 根据数据ID查询一列数据,有缓存。
*
* @param id 数据ID
* @return 查询到的数据列表
*/
@Cache
public List < Object > queryData ( int id ) {
// 模拟一个查询操作
Random random = new Random ();
int size = random . nextInt ( 10 ) + 10 ;
return IntStream . range ( 0 , size )
. mapToObj ( i -> random . nextInt ( 10 ))
. collect ( Collectors . toList ());
}
/**
* 根据数据ID查询一列数据,无缓存。
*
* @param id 数据ID
* @return 查询到的数据列表
*/
public List < Object > queryDataWithoutCache ( int id ) {
// 模拟一个查询操作
Random random = new Random ();
int size = random . nextInt ( 10 ) + 1 ;
return IntStream . range ( 0 , size )
. mapToObj ( i -> random . nextBoolean ())
. collect ( Collectors . toList ());
}
}
CacheClassDecorator 实现@Cache注解
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
package com.github.hcsp.annotation ;
import net.bytebuddy.ByteBuddy ;
import net.bytebuddy.implementation.MethodDelegation ;
import net.bytebuddy.implementation.bind.annotation.AllArguments ;
import net.bytebuddy.implementation.bind.annotation.Origin ;
import net.bytebuddy.implementation.bind.annotation.RuntimeType ;
import net.bytebuddy.implementation.bind.annotation.SuperCall ;
import net.bytebuddy.implementation.bind.annotation.This ;
import net.bytebuddy.matcher.ElementMatchers ;
import java.lang.reflect.Method ;
import java.util.concurrent.Callable ;
import java.util.concurrent.ConcurrentHashMap ;
public class CacheClassDecorator {
// 将传入的服务类Class进行增强
// 使得返回一个具有如下功能的Class:
// 如果某个方法标注了@Cache注解,则返回值能够被自动缓存注解所指定的时长
// 这意味着,在短时间内调用同一个服务的同一个@Cache方法两次
// 它实际上只被调用一次,第二次的结果直接从缓存中获取
// 注意,缓存的实现需要是线程安全的
@SuppressWarnings ( "unchecked" )
public static < T > Class < T > decorate ( Class < T > klass ) {
return ( Class < T > ) new ByteBuddy ()
. subclass ( klass ) // 动态创建一个klass的子类,因为你要扩展klass嘛,因此你肯定需要一个子类
// .method(methodDescription -> klass.getAnnotation(Cache.class) != null) // 过滤出所有带有@Cache注解的方法
. method ( ElementMatchers . isAnnotatedWith ( Cache . class )) // 这句和上面的意思一样
. intercept ( MethodDelegation . to ( Target . class )) // 拦截这些方法并委托给谁处理
. make () // 创建出这个Class对象
. load ( klass . getClassLoader ()) // 让ClassLoader加载它
. getLoaded (); // 返回加载后的Class对象
}
public static class Target {
private static final ConcurrentHashMap < CacheK , CacheV > cache = new ConcurrentHashMap <> ();
@RuntimeType // 这个注解指定当发生委托时,要调用这个方法,如果你不想加这个注解,那必须保证方法的返回值和被拦截的方法返回值一致(目前我看到的是这样)
public static Object intercept (
@SuperCall Callable < Object > supperCall , // 既然我们这个Class是继承的klass,那么我们通过这个参数就能调用父类的方法
@Origin Method method , // 当委托发生的时候,可以拿到当前被调用的方法(换句话说就是,调用哪个方法的时候发生了这个委托)
@This Object thisObject , // 哪个对象调用了这个方法
@AllArguments Object [] allArguments // 可以拿到所有的参数
) throws Exception {
CacheK key = new CacheK ( thisObject , method . getName (), allArguments );
CacheV cached = cache . get ( key );
if ( cached != null ) {
if ( System . currentTimeMillis () - cached . getTime () > method . getAnnotation ( Cache . class ). cacheSeconds () * 1000L ) {
return cacheValue ( supperCall , key );
}
return cached . getValue ();
}
return cacheValue ( supperCall , key );
}
private static Object cacheValue ( Callable < Object > supperCall , CacheK key ) throws Exception {
CacheV value = new CacheV ( supperCall . call (), System . currentTimeMillis ());
cache . put ( key , value );
return value . getValue ();
}
}
public static void main ( String [] args ) throws Exception {
DataService dataService = decorate ( DataService . class ). getConstructor (). newInstance ();
// 有缓存的查询:只有第一次执行了真正的查询操作,第二次从缓存中获取
System . out . println ( dataService . queryData ( 1 ));
Thread . sleep ( 1 * 1000 );
System . out . println ( dataService . queryData ( 1 ));
// 无缓存的查询:两次都执行了真正的查询操作
System . out . println ( dataService . queryDataWithoutCache ( 1 ));
Thread . sleep ( 1 * 1000 );
System . out . println ( dataService . queryDataWithoutCache ( 1 ));
}
}
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
package com.github.hcsp.annotation ;
import java.util.Arrays ;
import java.util.Objects ;
public class CacheK {
private Object object ;
private String methodName ;
private Object [] allArguments ;
public CacheK ( Object object , String methodName , Object [] allArguments ) {
this . object = object ;
this . methodName = methodName ;
this . allArguments = allArguments ;
}
@Override
public boolean equals ( Object o ) {
if ( this == o ) {
return true ;
}
if ( o == null || getClass () != o . getClass ()) {
return false ;
}
CacheK cacheK = ( CacheK ) o ;
return Objects . equals ( object , cacheK . object ) && Objects . equals ( methodName , cacheK . methodName ) && Arrays . equals ( allArguments , cacheK . allArguments );
}
@Override
public int hashCode () {
int result = Objects . hash ( object , methodName );
result = 31 * result + Arrays . hashCode ( allArguments );
return result ;
}
}
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
package com.github.hcsp.annotation ;
public class CacheV {
private Object value ;
private long time ;
public CacheV ( Object value , long time ) {
this . value = value ;
this . time = time ;
}
public Object getValue () {
return value ;
}
public void setValue ( Object value ) {
this . value = value ;
}
public long getTime () {
return time ;
}
public void setTime ( long time ) {
this . time = time ;
}
}