您的位置:首页 > 编程语言 > Java开发

Java 9:装B之前你必须要会的——泛型,注解,反射

2016-12-11 20:53 441 查看

1 泛型

1.1 基本概念

泛型提供了编译期的类型检查,但问题远非这么简单

///原生态类型
List list1 = new ArrayList();   ///规避的类型检查
List list1 = new ArrayList<String>();

///参数化类型
List<Object> list2 = new ArrayList<Object>();  //可以放任何类型的对象,没指导意义
List<String> list3 = new ArrayList<String>();

///通配符类型
//无限制通配符类型
List<?> list4 = new ArrayList<Dog>();
//有限制通配符类型
List<? extends Animal> list4 = new ArrayList<Dog>();
List<? extends Animal> list4 = new ArrayList<Cat>();

List<String>里的String是:实际类型参数

在类和方法定义时,引入形式类型参数
public class List<T>{
T[] data;
}
这里T是形式类型参数
List<T>就叫做:泛型

public class PrimitiveList<E extends Number>{
E[] data;
}
<E extends Number>是有限制通配符类型

泛型方法
public <E> void do_sth(E e){

}

类型令牌
String.class
List.class
String[].class


带泛型的类型可以降级为不带泛型的类型

public static void main(){
List<String> strings = new ArrayList<String>();
unsafeAdd(strings, new Integer(42));
String s = strings.get(0);
}

private static void unsafeAdd(List list, Object o){
list.add(o);  //unchecked call to add(E) in raw type list
}
这段代码可以通过编译,
但会收到警告,unchecked call to add(E) in raw type list,
而且明显在运行时会收到ClassCastException

这种使用方式并不是一无是处,如果unsafeAdd是一个不关心List具体泛型类型的实现逻辑,
或者没法知道List的泛型类型,或者需要处理各种泛型类型的List,则这种写法是可取的

如果这样,则无法通过编译
private static void unsafeAdd(List<Object> list, Object o){
list.add(o);  //unchecked call to add(E) in raw type list
}


无限制的通配符类型

如果一个方法需要传入List,但不关心List的具体泛型类型,则可以使用一个问号代替泛型,
这就是无限制的通配符

static int numElementsInCommon(Set<?> s1, Set<?> s2){
int result = 0;
for(Object o1: s1){
if(s2.contains(o1))){
result++;
}

}
return result;
}

Set<?> set1 = ..;
Set set2 = ..;
set1和set2的区别是什么?
1 这两个都能接受所有泛型类型的Set
2 但是set1只能add一个类型的对象,set2可以add任何类型的对象


有限制的通配符类型

Set<? extends Aminal> set = ..;

对于Stack<E>,如果提供如下方法
public void pushAll(Iterable<? exnteds E> src){
for(E e: src){
push(e);
}
}
试想,如果参数不用<? exnteds E>,怎么样才合适呢
<? exnteds E>在这里就是最好的选择

对于Stack<E>,如果提供如下方法
public void popAll(Collection<E> dst){
while(!isEmpty()) dst.add(pop());
}
如果是Stack<Dog>,本来你用List<Dog>作为参数传入,可以
但如果你要用List<Animal>传入,就不行了,但逻辑上这么做是没问题的,所以方法一改:
public void popAll(Collection<? super E> dst){

}
意思是Collection的泛型类型是E的某个超类

所以对于泛型类型对应的对象E e
如果你要生产(put),即给e值,则E和E的子类都可以给,<? extends E>
如果你要消费(get),即x = e,则x即可以是E,也可以是E的基类或接口,<? super E>
这就是PECS原则,Producer extends,Consumer super


1.2 关于class对象:

List.class

String[].class

int.class

都是合法的

但是List.class不合法, List

if(o instanceof Set){
Set<?> m = (Set<?>)o;
}

但你不能 o instanceof Set<String>


1.3 消除非受检警告:

情况1:

Set<Animal> animals = new HashSet();
这种情况可以很容易的得到编译器提醒unchecked


情况2:

如果你确定代码是类型安全的,可以@SuppressWarnings(“unchecked”)

1.4 数组和集合:

数组是协变的
Animal[] animals;
Dog[] dogs = new Dog[2];
animals = dogs;  //对的
animals[0] = new Cat(); //错的,ArrayStoreException

泛型是不可变的
List<Animal> animals;
List<Dog> dogs = new ArrayList<Dog>();
animals = dogs;  //错误的,编译报类型不匹配

数组是具体化的,reified,所以数组会在运行时才知道并检查他们的元素类型约束
泛型是通过擦除的,erasure,只能在编译时强化其类型信息,运行时丢弃

有泛型信息时,不能创建数组,以下都非法
List<E>[]
new List<String>[]
new E[]
为什么不可以这么创建数组呢,看下面的例子,如果可以这么创建数组的话:
List<String>[] stringLists = new List<String>[1];
List<Integer> intList = Arrays.asList(42);
Object[] objects = stringLists;
Object[0] = intList;
String s = stringLists[0].get(0);
上述代码的错误如果放到运行时,就是ClassCast错误,所以编译时第一行就会报错

E, List<E>, List<String>
这些都是不可具体化的类型,其运行时表示法包含的信息,要少于其编译时表示法包含的信息

注意:创建List<?>[] arr是合法的


1.5 泛型的类型推导

很复杂,规则很多,据说光文档就有16页

Lang.isEmpty(s)这种形式叫做显式类型参数

1.6 运行时获取泛型的具体类型:

参考TypeToken

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;

public class TypeToken<T> {

private final Type type;

protected TypeToken(){
Type superClass = getClass().getGenericSuperclass();

type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
}

public Type getType() {
return type;
}

public final static Type LIST_STRING = new TypeToken<List<String>>() {}.getType();

}

System.out.println(new TypeToken<List<Animal>>(){}.getType());
打印:
java.util.List<com.cowthan.Animal>


2 注解

注解是元数据

2.1 内置注解

提醒编译器的

@Override

@Deprecated

@SuppressWarnings 关闭警告

2.2 自定义注解

四大元注解


@Target:注解能用在哪儿

@Target(ElementType.METHOD)

ElementType可能的值:

TYPE 用于class定义

CONSTRUCTOR 用于构造方法

METHOD 用于方法

FIELD 用于成员变量

LOCAL_VARIABLE 局部变量声明

PACKAGE 包声明

PARAMETER 方法参数声明

ANNOTATION_TYPE

TYPE_PARAMETER

TYPE_USE

@Retention:注解信息在哪个阶段有效

@Retention(RetentionPolicy.RUNTIME)

RetentionPolicy可能的值:

SOURCE:源码阶段,编译时,一般是用来告诉编译器一些信息,将被编译器丢弃

CLASS:注解在class文件中可用,会被VM丢弃

RUNTIME:运行时,VM在运行时也保留注解,这个才能通过反射获取到

Documented

将此注解包含在JavaDoc中

Inherited

允许子类继承父类中的注解

注解 处理器:通过反射拿到注解


2.3 自定义注解实例

简单的例子,只展示了如何定义和使用注解,没有阐明如何处理注解

/**
* 描述一个测试用例
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {

public int id();
public String description() default "no description";

}


public class UseCaseDemo {

@UseCase(id = 1, description = "密码的格式必须规范")
public boolean testPassword(String password){
return false;
}

@UseCase(id = 2, description = "用户名的格式必须规范")
public boolean testUsername(String username){
return false;
}

@UseCase(id = 3, description = "新密码不能等于旧密码")
public boolean testNewPwd(String password){
return false;
}
}


2.4 处理注解:运行时

下面是一个简单的序列化框架,使用了注解@Seriable的字段可以被序列化为json

@Target({ ElementType.FIELD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Seriable
{

}

public class User
{
@Seriable
private String username;
@Seriable
private String password;

private String three;
private String four;
}

public boolean isSeriable(Field f){
f.setAccessible(true);
Seriable annotation = f.getAnnotation(Seriable.class);
if(id == null){
return false;
}else{
return true;
}
}


2.5 处理注解:编译期

ButterKnife能根据注解生成代码,怎么做到的呢,涉及到apt和编译期处理器

http://www.cnblogs.com/avenwu/p/4173899.html

http://www.bubuko.com/infodetail-826234.html 这篇讲的挺好

http://www.jianshu.com/p/1910762593be 安卓版

http://blog.csdn.net/lmj623565791/article/details/43452969

重点有三个

定义注解和注解处理器

打成jar包

在任何项目里使用注解,编译时触发注解处理器

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;

@SupportedAnnotationTypes({"com.avenwu.annotation.PrintMe"})
public class BeanProcessor extends AbstractProcessor {

// 元素操作的辅助类
Elements elementUtils;

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
// 元素操作的辅助类
elementUtils = processingEnv.getElementUtils();
}

@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {

for (TypeElement currentAnnotation : annotations) {
Name qualifiedName = currentAnnotation.getQualifiedName();
if (qualifiedName.contentEquals("com.avenwu.annotation.PrintMe")){
Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(currentAnnotation);
for (Element element : annotatedElements) {
Version v = element.getAnnotation(Version.class);
int major = v.major();
int minor = v.minor();
if(major < 0 || minor < 0) {
String errMsg = "Version cannot be negative. major = " + major + " minor = " + minor;
Messager messager = this.processingEnv.getMessager();
messager.printMessage(javax.tools.Diagnostic.Kind.ERROR,errMsg,element);
}
}
}
}
return true;
}

@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}

}


3 反射

反射等以后吧,安卓里面反射用的也不多,EventBus2就是基于反射,在EventBus3里已经

改成了预处理注解的方式
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息