泛型的概念 在集合中存储对象并在使用前进行类型转换 是多么的不方便。泛型防止了那种情况的发生。 它提供了编译期 的类型安全,确保你只能把正确类型的对象放入集合中,避免了在运行时出现ClassCastException。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Box { private String object; public void set (String object) { this .object = object; } public String get () { return object; } }
1 2 3 4 5 6 public class GenericBox <T> { private T t; public void set (T t) { this .t = t; } public T get () { return t; } }
限定通配符和非限定通配符
限定通配符对类型进行了限制。有两种限定通配符:
一种是<? extends T>它通过确保类型必须是T及T的子类 来设定类型的上界,
另一种是<? super T>它通过确保类型必须是T及T的父类 设定类型的下界。
泛型类型必须用限定的类型来进行初始化,否则会导致编译错误。
非限定通配符
>表示了非限定通配符,因为>可以用任意类型来替代。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class BoundaryCharExample { public static <T> int countGreaterThan (T[] array,T elem) { int count = 0 ; for (T e : array) { if (e > elem) { ++count; } } return count; } }
1 2 3 4 5 6 7 8 9 10 11 12 public class BoundaryCharExample2 { public static <T extends Comparable <T>> int countGreaterThan (T[] array,T elem) { int count = 0 ; for (T e : array) { if (e.compareTo(elem)>0 ) { ++count; } } return count; } }
面试题:List<? extends T> 和List <? super T>之间有什么区别 ?
List<? extends T>可以接受任何继承自T的类型的List,
List<? super T>可以接受任何T的父类构成的List。
例如List<? extends Number>可以接受List或List。
PECS(Producer Extends Consumer Super)原则 PECS原则即Producer Extends,Consumer Super原则
Producer Extends:如果你需要一个只读List,用它来produce T,那么使用? extends T 。
Consumer Super:如果你需要一个只写List,用它来consume T,那么使用? super T 。
1 2 3 4 5 6 7 8 9 10 11 12 13 public class GenericExample { public static void main (String[] args) { List<? extends Fruit > fruits = new ArrayList <Apple>(); } }
Compile Error: can’t add any type of object: 从编译器的角度去考虑。因为List<? extends Fruit> fruits它自身可以有多种含义: 因为List<? extends Fruit> fruits它自身可以有多种含义(参照下面的代码 GenericReading.java):
1 2 3 4 5 6 7 List<? extends Fruit > fruits = new ArrayList <Fruit>(); List<? extends Fruit > fruits = new ArrayList <Apple>(); List<? extends Fruit > fruits = new ArrayList <Orange>();
当我们尝试add一个Apple的时候,fruits可能指向new ArrayList();
当我们尝试add一个Orange的时候,fruits可能指向new ArrayList();
当我们尝试add一个Fruit的时候,这个Fruit可以是任何类型的Fruit,
而fruits可能只想某种特定类型的Fruit,编译器无法识别所以会报错。
所以对于实现了<? extends T>的集合类只能将它视为 Producer 向外提供元素(只能读) , ,而不能作为 Consumer 来向外获取元素。
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 public class GenericReading { private List<Apple> apples = Arrays.asList(new Apple ()); private List<Fruit> fruit = Arrays.asList(new Fruit ()); private class Reader <T>{ T readExact (List<? extends T> list) { return list.get(0 ); } } @Test public void test () { Reader<Fruit> fruitReader=new Reader <Fruit>(); Fruit f=fruitReader.readExact(apples); System.out.println(f); } }
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 public class GenericWriting { private List<Apple> apples = new ArrayList <Apple>(); private List<Orange> oranges = new ArrayList <Orange>(); private List<Fruit> fruit = new ArrayList <Fruit>(); <T> void writeExact (List<T> list, T item) { list.add(item); } <T> void writeWithWildcard (List<? super T> list, T item) { list.add(item); } void func1 () { writeExact(apples,new Apple ()); writeExact(fruit,new Apple ()); } void func2 () { writeWithWildcard(apples, new Apple ()); writeWithWildcard(fruit, new Apple ()); } @Test public void test () { func1(); func2(); } }
JDK 8 Collections.copy() 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public static <T> void copy (List<? super T> dest, List<? extends T> src) { int srcSize = src.size(); if (srcSize > dest.size()) throw new IndexOutOfBoundsException ("Source does not fit in dest" ); if (srcSize < COPY_THRESHOLD || (src instanceof RandomAccess && dest instanceof RandomAccess)) { for (int i=0 ; i<srcSize; i++) dest.set(i, src.get(i)); } else { ListIterator<? super T> di=dest.listIterator(); ListIterator<? extends T > si=src.listIterator(); for (int i=0 ; i<srcSize; i++) { di.next(); di.set(si.next()); } } }
泛型的应用 泛型实现 LRU 缓存
LRU(Least Recently Used,最近最久未使用)缓存思想:
(1)固定缓存大小,需要给缓存分配一个固定的大小
(2)每次读取缓存都会改变缓存的使用时间,将缓存的存在时间重新刷新
(3)需要在缓存满了后,将最近最久未使用的缓存删除 ,再添加最新的缓存
实现思路:使用LinkedHashMap来实现LRU缓存。
LinkedHashMap的一个构造函数:
1 2 3 4 5 6 public LinkedHashMap (int initialCapacity, float loadFactor, boolean accessOrder) { super (initialCapacity, loadFactor); this .accessOrder = accessOrder; }
传入的第三个参数:
accessOrder为true的时候,就按访问顺序对LinkedHashMap排序,
accessOrder为false的时候,就按插入顺序(默认情况)。
当把accessOrder设置为true后(按照访问顺序),就可以将最近访问的元素置于最前面。这样就可以满足上述的(2)。
LinkedHashMap是自动扩容 的,当table数组中元素大于Capacity * loadFactor的时候,就会自动进行两倍扩容。 但是为了使缓存大小固定,就需要在初始化的时候传入容量大小 和负载因子 。 为了使得到达设置缓存大小不会进行自动扩容,需要将初始化的大小进行计算再传入, 将初始化大小设置为(缓存大小 / loadFactor) + 1,这样就可以在元素数目达到缓存大小时, 也不会进行扩容了。这样就解决了上述的(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 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 public class LRUCache <K,V> { private final int MAX_CACHE_SIZE; private final float DEFAULT_LOAD_FACTORY = 0.75f ; private LinkedHashMap<K, V> map;; public LRUCache (int cacheSize) { MAX_CACHE_SIZE=cacheSize; int capacity=(int )Math.ceil(MAX_CACHE_SIZE/DEFAULT_LOAD_FACTORY)+1 ; map=new LinkedHashMap <K,V>(capacity,DEFAULT_LOAD_FACTORY,true ){ @Override protected boolean removeEldestEntry (Map.Entry<K, V> eldest) { return size()>MAX_CACHE_SIZE; } }; } public synchronized void put (K key,V value) { map.put(key,value); } public synchronized V get (K key) { return map.get(key); } public synchronized void remove (K key) { map.remove(key); } public synchronized Set<Map.Entry<K,V>> getAll(){ return map.entrySet(); } @Override public String toString () { StringBuilder sb=new StringBuilder (); for (Map.Entry<K,V> entry:map.entrySet()){ sb.append(String.format("%s: %s\n" ,entry.getKey(),entry.getValue())); } return sb.toString(); } public static void main (String[] args) { LRUCache<Integer,Integer> lru=new LRUCache <Integer, Integer>(5 ); lru.put(1 , 1 ); lru.put(2 , 2 ); lru.put(3 , 3 ); System.out.println(lru); lru.get(1 ); System.out.println(lru); lru.put(4 ,4 ); lru.put(5 ,5 ); lru.put(6 ,6 ); System.out.println(lru); } }
输出结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 1: 1 2: 2 3: 3 2: 2 3: 3 1: 1 3: 3 1: 1 4: 4 5: 5 6: 6
泛型实现 FIFO 缓存
FIFO设计思路:FIFO就是先进先出,可以使用LinkedHashMap进行实现。
LinkedHashMap 的构造函数:
1 2 3 4 5 6 public LinkedHashMap (int initialCapacity, float loadFactor, boolean accessOrder) { super (initialCapacity, loadFactor); this .accessOrder = accessOrder; }
当第三个参数传入为false或者是默认的时候,就可以实现按插入顺序排序 ,就可以实现FIFO缓存了。
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 public class FIFOCache <K,V> { private final int MAX_CACHE_SIZE; private final float DEFAULT_LOAD_FACTORY = 0.75f ; private LinkedHashMap<K, V> map; public FIFOCache (int cacheSize) { this .MAX_CACHE_SIZE = cacheSize; int capacity = (int )Math.ceil(MAX_CACHE_SIZE / DEFAULT_LOAD_FACTORY) + 1 ; map=new LinkedHashMap <K,V>(capacity,DEFAULT_LOAD_FACTORY,false ){ @Override protected boolean removeEldestEntry (Map.Entry<K, V> eldest) { return size() > MAX_CACHE_SIZE; } }; } public synchronized void put (K key,V value) { map.put(key,value); } public synchronized V get (K key) { return map.get(key); } public synchronized void remove (K key) { map.remove(key); } public synchronized Set<Map.Entry<K,V>> getAll(){ return map.entrySet(); } @Override public String toString () { StringBuilder sb=new StringBuilder (); for (Map.Entry<K,V> entry:map.entrySet()){ sb.append(String.format("%s: %s\n" ,entry.getKey(),entry.getValue())); } return sb.toString(); } public static void main (String[] args) { FIFOCache<Integer,Integer> fifo=new FIFOCache <Integer, Integer>(5 ); fifo.put(1 , 1 ); fifo.put(2 , 2 ); fifo.put(3 , 3 ); System.out.println(fifo); fifo.get(1 ); System.out.println(fifo); fifo.put(4 ,4 ); fifo.put(5 ,5 ); fifo.put(6 ,6 ); System.out.println(fifo); } }
输出结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 1: 1 2: 2 3: 3 1: 1 2: 2 3: 3 2: 2 3: 3 4: 4 5: 5 6: 6
泛型的实现方式 Java 的泛型是一种伪泛型,编译为字节码时参数类型会在代码中被擦除 ,单独记录在 Class 文件的 attributes 域内,而在使用泛型处做类型检查与类型转换。
假设参数类型的占位符为T,擦除规则(保留上界)如下:
(1) <T> 擦除后变为 Object
(2) <? extends A> 擦除后变为 A
(3) <? super A> 擦除后变为 Object
注解-Annontation 注解概述 Annontation 是 Java5 开始引入的新特征,中文名称叫注解。 注解是插入到代码中的一种注释 或者说是一种元数据 。
这些注解信息可以在编译期使用编译工具进行处理,也可以在运行期使用 Java 反射机制进行处理。
注解的用处
生成文档。这是最常见的,也是java 最早提供的注解。常用的有@param @return 等
跟踪代码依赖性,实现替代配置文件 功能。如Spring中@Autowired;
在编译时进行格式检查。如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。
注解的原理 注解本质是一个继承了Annotation的特殊接口 ,其具体实现类是Java运行时生成的动态代理类 。 我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象 $Proxy1。 通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。 该方法会从memberValues这个Map中索引出对应的值。 而memberValues的来源是Java常量池。
元注解 java.lang.annotation提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):
注解
说明
@Documented
是否将注解包含在JavaDoc中
@Retention
什么时候使用该注解
@Target
注解用于什么地方
@Inherited
是否允许子类继承该注解
一个简单的Annotations标记注解,表示是否将注解信息添加在java文档中。
定义该注解的生命周期。
(1)RetentionPolicy.SOURCE : 在编译阶段丢弃。 这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。 @Override, @SuppressWarnings都属于这类注解。
(2)RetentionPolicy.CLASS : 在类加载的时候丢弃。 在字节码文件的处理中有用。注解默认使用这种方式
(3)RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解, 因此可以使用反射机制读取该注解的信息 。我们自定义的注解通常使用这种方式。
表示该注解用于什么地方。 默认值为任何元素,表示该注解用于什么地方。可用的ElementType参数包括:
参数
说明
ElementType.CONSTRUCTOR
用于描述构造器
ElementType.FIELD
成员变量、对象、属性(包括enum实例)
ElementType.LOCAL_VARIABLE
用于描述局部变量
ElementType.METHOD
用于描述方法
ElementType.PACKAGE
用于描述包
ElementType.PARAMETER
用于描述参数
ElementType.TYPE:用于描述类、接口(包括注解类型) 或enum声明
定义该注释和子类的关系。 @Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。 如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
常见标准的Annotation
Override (RetentionPolicy.SOURCE : 在编译阶段丢弃。属于@Retention)
Override是一个标记类型注解,它被用作标注方法。 它说明了被标注的方法重载了父类的方法,起到了断言的作用。 如果我们使用了这种注解在一个没有覆盖父类方法的方法时,java编译器将以一个编译错误来警示 。
Deprecated也是一种标记类型注解。 当一个类型或者类型成员使用@Deprecated修饰的话,编译器将不鼓励使用这个被标注的程序元素。 所以使用这种修饰具有一定的“延续性”: 如果我们在代码中通过继承或者覆盖的方式使用了这个过时的类型或者成员, 虽然继承或者覆盖后的类型或者成员并不是被声明为@Deprecated,但编译器仍然要报警。
SuppressWarning不是一个标记类型注解。 它有一个类型为String[]的成员,这个成员的值为被禁止的警告名 。 对于javac编译器来讲,对-Xlint选项有效的警告名也同样对@SuppressWarings有效,同时编译器忽略掉无法识别的警告名。 @SuppressWarnings(“unchecked”)
自定义注解使用的规则 自定义注解类编写的一些规则: (1) Annotation型定义为@interface, 所有的Annotation会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口
(2)参数成员只能用public或默认(default)这两个访问权修饰
(3)参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型 和String、Enum、Class、Annotations等数据类型,以及这一些类型的数组
(4)要获取类方法和字段的注解信息,必须通过Java的反射技术来获取Annotation对象, 因为除此之外没有别的获取注解对象的方法
(5)注解也可以没有定义成员, 不过这样注解就没啥用了
注意:自定义注解需要使用到元注解
自定义注解示例
自定义水果颜色注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Target(FIELD) @Retention(RUNTIME) @Documented @interface FruitColor { public enum Color {绿色,红色,青色}; Color fruitColor () default Color.绿色; }
自定义水果名称注解
1 2 3 4 5 6 7 8 9 @Target(FIELD) @Retention(RUNTIME) @Documented public @interface FruitName { public String fruitName () default "" ; }
水果供应商注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Target(FIELD) @Retention(RUNTIME) @Documented public @interface FruitProvider { public int id () default -1 ; public String name () default "" ; public String address () default "" ; }
通过反射来获取水果信息
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 public class FruitInfoUtil { public static void getFruitInfo (Class<?> clazz) { String strFruitName=" 水果名称:" ; String strFruitColor=" 水果颜色:" ; String strFruitProvider="供应商信息:" ; Field[] fields=clazz.getDeclaredFields(); for (Field field:fields){ if (field.isAnnotationPresent(FruitName.class)){ FruitName fruitName=field.getAnnotation(FruitName.class); strFruitName=strFruitName+fruitName.fruitName(); System.out.println(strFruitName); }else if (field.isAnnotationPresent(FruitColor.class)){ FruitColor fruitColor=field.getAnnotation(FruitColor.class); strFruitColor=strFruitColor+fruitColor.fruitColor().toString(); System.out.println(strFruitColor); }else if (field.isAnnotationPresent(FruitProvider.class)){ FruitProvider fruitProvider=field.getAnnotation(FruitProvider.class); strFruitProvider=strFruitProvider + "[ 供应商编号:" +fruitProvider.id() +" 供应商名称:" +fruitProvider.name() +" 供应商地址:" +fruitProvider.address()+"]" ; System.out.println(strFruitProvider); } } } }
使用注解初始化实例类
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 public class Apple { @FruitName(fruitName = "苹果") private String appleName; @FruitColor(fruitColor = FruitColor.Color.红色) private String appleColor; @FruitProvider(id=1,name="红富士",address="陕西省西安市延安路89号红富士大厦") private String appleProvider; public String getAppleName () { return appleName; } public void setAppleName (String appleName) { this .appleName = appleName; } public String getAppleColor () { return appleColor; } public void setAppleColor (String appleColor) { this .appleColor = appleColor; } public String getAppleProvider () { return appleProvider; } public void setAppleProvider (String appleProvider) { this .appleProvider = appleProvider; } }