Java进阶知识

在使用Java开发当中,一些非常必要的知识,它会使你的代码更加的优美。

泛型
简单的理解,泛型就是限制类的类型。泛型提供了类型检查,减少了数据的类型转换,同时保证了类型安全。
下面我们来看下栗子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args){
List list =new ArrayList();
list.add("abc");
list.add(1);
list.add(true);
list.add(new Object());

for (Object object : list) {
try {
System.out.println((String)object);
}catch (ClassCastException e){
System.out.println("类型不对:"+object.getClass());
}
}
}

可以List的add()方法默认是任意类型的数据,这样的话我们如果需要统一类型的话,我们添加数据的时候需要非常严谨。 因为我们即使添加了不同类型编译器也不会返回任何提醒。 而且当我们拿到数据的时候需要做强制转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args){
//添加泛型
List<String> list =new ArrayList();
list.add("aa");
list.add("bb");
list.add("cc");
//添加其他类型编译器报错
list.add(123);

for (String s : list) {
System.out.print(s);
}
}

明显可以看出泛型的最主要的优点就是让编译器追踪参数类型。Java类库中又很多使用的了泛型。例如整个集合框架,都被修改为泛型了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface List<E> extends Collection<E> {
// Query Operations

/**
* Returns the number of elements in this list. If this list contains
* more than <tt>Integer.MAX_VALUE</tt> elements, returns
* <tt>Integer.MAX_VALUE</tt>.
*
* @return the number of elements in this list
*/


E get(int index);

boolean add(E e);

boolean addAll(Collection<? extends E> c);

}

上面就是List的部分源码。显而易见整个Collection都有使用的泛型。

我们来实现我们实际使用过程中的栗子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Created by yugai on 16/6/15.
* 类头引入泛型
*/

public class Test<T> {

T t;

public T getT() {
return t;
}

public void setT(T t) {
this.t = t;
}

public static void main(String[] args){
Test<Integer> test=new Test<>();
test.setT(1);

System.out.print(test.getT());
}
}
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
/**
* Created by yugai on 16/6/15.
* 泛型的方法
*/

public class Test {

/**
* 添加一个数组到List中
* @param a
* @param c
* @param <T>
* @return
*/

public static <T> List<T> addArray(T[] a,List<T> c){
for (T t : a) {
c.add(t);
}
return c;
}

public static void main(String[] args){
List<String> list=new ArrayList<>();
list.add("a");
list.add("c");
list.add("e");

List<String> copylist=Test.<String>addArray(new String[] {"b","a"},list);
System.out.print(copylist);
}
}

我们在List的addAll(Collection<? extends E> c)可以看到 <? extends E> 这个又是什么东西呢?
因为有的时候我们在定义泛型的时候需要控制其类型。不能是所有类型,所以<? extends E>表示的就是该泛型必须继承子E类型.

1
2
3
4
5
6
7
8
9
10
11
12
13

public static <T extends Collection> void isList(T t){
if (t instanceof List){
System.out.print("这个类型是List");
}
}

public static void main(String[] args){
Test.isList(new ArrayList());

//传入String编译器报错,因为所传入的类型必须是继承Collection类型的
// Test.isList("abcd");
}

为什么我们用的是T,而不是。因为T指的是一种类型,?是泛指(就是通配符)。
例如List<? extends Object> list=new ArrayList<>();这里就不能用T。

1
2
3
4
5
6
7
8
public class Test<T extends Collection> {

public static void main(String[] args){
Test<? extends Collection> test=new Test<ArrayList>();
// 下面这种方式会报错。因为类型不一致,而 ? 并不代表着一种类型
// Test<Collection> test =new Test<ArrayList>();
}
}

好吧,完了,泛型是种很好理解的东西。

枚举

在初级使用的时候枚举的工作有点想静态常量,不过相比较常量类型,枚举类型可以为申明的变量提供更大的取值范围。
定义一个枚举类

1
2
3
public enum People {
USA,CHINA,JAPAN
}

使用枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test{
public static void main(String[] args){
People people=People.CHINA;
switch (people){
case USA:

break;
case CHINA:

break;
case JAPAN:

break;
}
}
}

除了表示类型,我们可能有属性需要添加在枚举里面,我们可以像类一样给枚举添加属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public enum People {
USA("JJ长",80),CHINA("长得帅",100),JAPAN("个子矮",60);
private String feature;//特长
private int IQ;

private People(String feature, int IQ) {
this.feature = feature;
this.IQ = IQ;
}

public int getIQ() {
return IQ;
}

public String getFeature() {
return feature;
}

}

需要注意的是,枚举不能直接new对象。因为其构造方法必须是private的,枚举类的所有实例必须在类的第一行列出,否则这个枚举类不会产生对象。

枚举可以通过values()方法类获取所有的枚举属性。

1
2
3
for (People people : People.values()) {
people.getFeature();
}

其实泛型的内容不是多,而且使用起来也很简单。我们可以像一个类一样在里面写各种方法,各种判断。我们再仔细想想其实枚举其实就是一个类,只不过Java对它进行了一定的约束。其实我们创建了一个枚举,然后系统就会帮我们继承Enum抽象类,只不过是编译将其隐藏了。例如:public final class People extends Enum。我们在上一篇也说过,枚举是实现单例的最好方式,其实枚举就是一个经过编译器约束过的普通类。

反射
反射在Java进阶中非常重要,但是在实际开发中可能不会怎么使用到。它就像黑科技一样,可以通过非正常渠道获取到Class对象,大部分框架都有使用到反射机制。
java.lang.reflect包中的类就是反射机制所需要使用到的。
对于可以有访问权限的类可以使用
getField(获取成员属性)getMethod(获取方法),getConstructor(获取构造方法)
对于没有访问访问权限(或者static)需要在get后面加上Declared(如:getDeclaredField
反射一个类的三种方式

1
2
3
4
5
6
7
8
try {
Class clazz1 = Class.forName("yugai.test.Person");
Class clazz2 = new Person().getClass();
Class class3 = Person.class;
} catch (ClassNotFoundException e) {
//系统提示强制捕获异常
e.printStackTrace();
}

获取无参构造方法

1
2
3
Class clazz1 = Class.forName("yugai.test.Person");
Constructor c = clazz1.getConstructor(null);
Person p = (Person) c.newInstance(null);

获取有参构造方法

1
2
3
Class clazz1 = Class.forName("yugai.test.Person");
Constructor c = clazz1.getConstructor(new Class[]{String.class,Integer.class});
Person p = (Person) c.newInstance("yg",1);

获取private构造方法

1
2
3
4
Class clazz1 = Class.forName("yugai.test.Person");
Constructor c = clazz1.getDeclaredConstructor(null);
c.setAccessible(true);
Person p = (Person) c.newInstance(null);

获取属性(获取私有的方法同上)

1
2
3
4
5
6
7
8
Class clazz1 = Class.forName("yugai.test.Person");
Constructor c = clazz1.getDeclaredConstructor(null);
c.setAccessible(true);
Person p = (Person) c.newInstance(null);

Field f = clazz1.getField("name");
String value = (String) f.get(p);//拿到对象实例的 域成员的值,static可以传null
Class type = f.getType();//获取成员的类型

获取方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Class clazz1 = Class.forName("yugai.test.Person");
Constructor c = clazz1.getDeclaredConstructor(null);
c.setAccessible(true);
Person p = (Person) c.newInstance(null);
//无参数方法
Method method1 = clazz1.getDeclaredMethod("getInfo", null);
method1.setAccessible(true);//打开私有方法
String info= (String) method1.invoke(p, null);//执行方法并返回值
System.out.print(info);

//有静态参方法
Method method2 = clazz1.getDeclaredMethod("getInfo",new Class[]{String.class,int.class});
info= (String) method2.invoke(null,"yugai",23);
System.out.print(info);

从上面的获取方法可以看到,static的不用传递对象执行,但需要传入null,对于拿到static的方法或者属性,我们需要通过Declared的方法获取。

对于上面说的拿到单个方法或者属性,还有些方法是拿到所有的属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
try {
Class cls=Class.forName("yugai.test.Person");
Field[] fields=cls.getFields();//获取所有的属性
Constructor[] constructors=cls.getConstructors();//获取所有的构造方法
Method[] methods=cls.getMethods();//获取所有的方法

for (Field field : fields) {
System.out.println(field.getType().getName());//获取属性类型
}

for (Constructor constructor : constructors) {
System.out.println(constructor.getGenericParameterTypes());//获取传入的参数
}

for (Method method : methods) {
System.out.println(method.getGenericReturnType());//获取返回的参数
}

} catch (ClassNotFoundException e) {
//系统提示强制捕获异常
e.printStackTrace();
}

注解

当我们开发过程当中其实会遇到很多注解,例如@Override@Deprected… 这些都是系统提供的注解方法,用来表示一些代码的行为。
定义注解

1
2
3
public @interface 注解名{
String value() default "";
}

上述注解中我们设置value的值为空字符串,那么我们在使用此注解时,可以不设置value的值,即让value使用设置的空字符串的默认值。

这样一个简单的注解就出来了,接下来我们来了解一下注解参数定义。

支持的类型

  • 基本数据类型(int,float….)
  • String
  • Class
  • enum
  • Annotaion
  • 数组(数组的类型必须是以上其中一种)

元注解

类型 描述
@Documented 用于描述其他类型的annotation应该被作为被标注的程序成员的公共API
@Target 说明Annotation所修饰的对象范围,即描述注解的使用范围
@Retention 定义了Annotation被保留的时间长短
@Inherited 阐述了某个被标注的类型是被继承的

其实日常使用当中我们几乎没有使用到自定义注解,但是有时候有些小技巧还是很有用的。例如Android当中不建议使用枚举,因为内存开销比较大,这个时候我们可以利用注解提到枚举,利用support包中@IntDef

//不知为何,插入代码快导致hexo出问题

一般使用注解的时候都需要定义注解解析器,我们通过反射来得到注解的信息,再实现注解的逻辑。