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

[学习笔记]Java 5特性

2014-12-30 00:48 405 查看


泛型机制

见《Java泛型机制(Java 5)


注解

1. 概述

注解(Annotation)相当于一种标记,在程序中加了注解就等于为程序打上了某种标记。编译器、开发工具和其他程序就可以利用注解来完成检查,提醒等功能。
普通程序要获取注解,可以通过反射的方式,以实现特定功能。

2. 特点

注解可以加在包,类(包括class,interface,annotation,enum),域,方法,方法的参数以及局部变量上。
java.lang包中提供了最基本的注解。

3. 格式

若注解无属性,或所有的属性均有默认定义

@注解名
若注解只有一个名为value的属性;或有一个名为value的属性,且其他属性均有默认定义

@注解名(值)

若注解有多个属性

@注解名(属性名=值, ...)

4. 元注解

元注解实际就是注解的注解,一般用于标记该注解的作用范围和生命周期。

常用的元注解有两种:@Target用于标记作用范围,@Retention用于标记生命周期。
@Target包含了1个属性:ElementType[] value,其中ElementType为枚举类型,包含了所有的作用范围的枚举。

PACKAGE(包)

FIELD(字段)

ANNOTATION_TYPE(注释类型)

CONSIRUCTOR(构造器)

METHOD(方法)

PARAMETER(参数)

TYPE(类、接口、注解或枚举)

LOCAL_VARIABLE(局部变量)
@Retention包含了1个属性:RetentionPolicy value,其中RetentionPolicy为枚举类型,包含:

SOURCE,表明该注解仅保留在源文件,即对编译过程有效;

CLASS,表明该注解一直保留到class文件中,该值为默认值;

RUNTIME,表明该注解一直保留到运行阶段的字节码实例中,运行时有效。

5. 三种基本注解

@Deprecated

用以表明该元素已过时,不推荐使用。

作用范围:构造器,域,方法,包,局部变量,方法参数,类(包括class,interface,annotation,enum)

生命周期:RUNTIME,由于调用别人开发的class仍然需要有过时提醒,所以运行时保留。
@Override

用以表明该方法为重写方法。

作用范围:方法。

生命周期:SOURCE,由于该注解是给编译器检查之用,所以源文件保留即可。

例如:equals方法若参数没有写成Object,则变为重载而非重写,若加上该注解,则编译器报错有效避免潜在错误。
@SuppressWarnings

用以表明忽略某种警告。

作用范围:构造器,域,方法,局部变量,方法参数,类(包括class,interface,annotation,enum)

声明周期:SOURCE,同理,由于该注解是给编译器警告提示之用,所以源文件保留即可。

属性:String[] value:用于表明忽略哪一种警告。

示例

package annotation;
public class AnnotationDemo implements Comparable<String>{
@SuppressWarnings("unused") // 表明忽略未使用的变量警告
public static void main(String[] args) {
int a = 0;
deprecatedFunc();
}
@Override // 表明重写方法
public int compareTo(String o) {
return 0;
}
@Deprecated // 表明该方法已过时
public static void deprecatedFunc() {
}
}


6. 自定义注解

6.1 注解调用

一般注解调用分为三个阶段:



定义:使用@interface关键字定义注解A。
使用:在类B上需要使用该注解时使用@A调用该注解。
反射使用:类C使用反射获取类B的注解A,并使用。(由于反射是运行时技术,所以要在反射时使用的注解必须由@Retention声明为RUNTIME。)



6.2 格式

使用@interface声明注解。
示例:

package annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) // 标示该注解保持到运行时有效
@Target({ElementType.TYPE, ElementType.METHOD}) // 标示该注解可以使用在类型(包括类,接口,注解和枚举)和方法上
public @interface AnnotationCustom {
String value(); // 该注解有1个属性,value属性较为特殊,在调用时可以不用声明value
}


6.3 注解调用示例

这里使用6.2定义好的注解:

package annotation;
@SuppressWarnings("unused")
public class AnnotationCustomDemo {
public static void main(String[] args) {
// 获取AnnotationLoader类字节码实例
Class<AnnotationLoader> clazz = AnnotationLoader.class;
// 判断该类是否有注解标示
if (clazz.isAnnotationPresent(AnnotationCustom.class)) {
// 获取该注解实例
AnnotationCustom ac = clazz.getAnnotation(AnnotationCustom.class);
// 获取注解属性
String str = ac.value(); // java
}
}
}
@AnnotationCustom("java")
class AnnotationLoader {
}


6.4 注解属性

6.4.1 概述

注解属性可以为注解带来更加细分的标示作用,比如在忽略警告注解内,属性的作用就可以用来区分需要忽略的警告的类型。
注解可以理解为一个类,而注解的属性可以理解为类的方法,而属性的类型可以理解为方法的返回值类型。
注解名可以理解为类名,而加了@的具体注解的调用,可以理解为该注解的实例。

6.4.2 格式

同接口中定义方法类似,一般没有修饰符。例如:
String value();
int[] count();
可以使用关键字default来给属性加上默认值,这时调用该注解时就可以不必指定该属性的值。例如:
EnumColor color() default EnumColor.RED;
int[] num() default {1, 2, 3};

6.4.3 属性成分

注解属性的返回值类型可以为以下类型:

八种基本数据类型
String类型
Class类型
枚举类型
注解类型
前五种类型的数组

6.4.4 调用

示例注解

package annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface AnnotationAttribute {
String value(); // String
Class<?> clazz() default AnnotationAttribute.class; // Class类型
EnumColor color() default EnumColor.RED; // 枚举
int[] num() default {1, 2, 3}; // 数组
AnnotationCustom annotation() default @AnnotationCustom("java"); // 注解
}


示例调用(以下调用方式均可)

@AnnotationAttribute("java")
@AnnotationAttribute(value="java", clazz=String.class)
@AnnotationAttribute(value="java", num={1, 2, 3})
@AnnotationAttribute(value="java", num=1)
@AnnotationAttribute(value="java", annotation=@AnnotationCustom("java"))


6.4.5 反射调用

这里使用6.4.4的注解。

package annotation;
@AnnotationAttribute(value = "it", clazz = String.class, num = 6, annotation = @AnnotationCustom("java"))
public class AnnotationAttributeDemo {
@SuppressWarnings("unused")
public static void main(String[] args) {
// 获取该类字节码实例
Class<AnnotationAttributeDemo> clazz = AnnotationAttributeDemo.class;
// 判断该类是否有注解
if (clazz.isAnnotationPresent(AnnotationAttribute.class)) {
// 获取该类注解
AnnotationAttribute aa = clazz.getAnnotation(AnnotationAttribute.class);
// 获取该注解的属性
String str = aa.value(); // "it"
Class<?> c = aa.clazz(); // class java.lang.String
EnumColor ec = aa.color(); // RED
int[] num = aa.num(); // {6}
AnnotationCustom ac = aa.annotation(); // @annotation.AnnotationCustom(value=java)
}
}
}



枚举

1. 概述

枚举是一种类型,该类型的变量均是一些有限且特定的值。比如一个星期有7天,分别为周日到周六;比如交通灯的三种颜色等等。在枚举出现之前,我们常常使用数组来进行表示,用查表法来获取需要的数据。但是用这种方法往往产生很多意想不到的错误,比如下标越界,实际意义和值对应的随意性经常产生不可预知且较难发现的错误,比如一个不存在的值。所以Java 5引入了枚举类型。

以下的例子是在枚举出现以前的实现方法:

package enumtype;
public abstract class TrafficLights {
// 交通灯显示时间
private int time;
// 由于不允许额外创建实例,所以私有化构造器
private TrafficLights(int time) {
this.time = time;
}
/*
 * 因为不允许私自创建对象,所以类加载时即创建有限且特定的实例,使用static方法;
 * 因为外部需要访问,所以public权限;
 * 因为不可修改,所以final修饰
 * 使用匿名内部类创建实例,传入显示时间参数,并重写抽象方法,实现下个灯的获取。
 */
public static final TrafficLights RED = new TrafficLights(40) {
@Override
TrafficLights nextLight() {
return GREEN;
}
};
public static final TrafficLights GREEN = new TrafficLights(30) {
@Override
TrafficLights nextLight() {
return YELLOW;
}
};
public static final TrafficLights YELLOW = new TrafficLights(3) {
@Override
TrafficLights nextLight() {
return RED;
}
};
abstract TrafficLights nextLight();
public int getTime() {
return this.time;
}
}


调用

package enumtype;
public class EnumDemo {
public static void main(String[] args) {
int time1 = TrafficLights.GREEN.getTime(); // 30
int time2 = TrafficLights.YELLOW.nextLight().getTime(); // 40
}
}


2. 特点

枚举相当于一个类,而其中的特定的值相当于这个类的实例。
在比较两个枚举类型的值时,永远不需要调用equals方法,而直接使用“==”就可以了。
枚举中可以添加域,构造器和方法。当然,构造器只是在构造枚举常量时被调用。
枚举类型不可以被继承,即相当于被fianl修饰。
所有的枚举类均是java.lang.Enum<E>的子类,所以可以使用Enum类中的方法。

3. 泛型说明

Enum类型在Java API的定义如下:

public abstract class Enum<E extends Enum<E>>
extends Object
implements Comparable<E>, Serializable


这里的泛型使用类似递归定义的形式:<E extends Enum<E>>,目的是为了实现父类实例(即Enum实例)能够获取或者使用子类实例作为方法的参数或返回值。

例如要设计一个能够获取子类对象的类:
a. 首先,定义父类,并且定义获取子类的抽象方法(由于子类不确定,所以具体实现必须由具体子类实现)。

abstract class Father {
abstract <T> T get();
}


b. 其实,定义子类,复写方法。

class Son extends Father {
@Override
Son get() {
return new Son();
}
}


c. 这里出现问题,警告:Type safety: The return type Son for func() from the type Son needs unchecked conversion to conform to T from the type Father.也就是说,返回值类型Son必须和父类中的定义T保持一致,否则需要取消安全检查。所以这里父类中就不能使用泛型方法,而是使用泛型类,将子类类型作为类型参数传入父类,重新设计如下:

abstract class Father<T> {
abstract T get();
}
class Son extends Father<Son> {
@Override
Son get() {
return new Son();
}
}


d. 大部分问题是解决了,但是仍然不够严谨,因为父类中的泛型参数T没有丝毫限制,导致子类可以传入任何类型到父类的类型参数中,所以这里父类使用泛型限定<T extends Father>来解决问题,而Father是带泛型的,所以修改为类似这种递归形式的泛型定义:<T extends Father<T>>,具体代码如下:

abstract class Father<T extends Father<T>> {
abstract T get();
}
class Son extends Father<Son> {
@Override
Son get() {
return new Son();
}
}


而这里的枚举类型的定义就是如此定义的,以方便其方法对子类类型的使用,如下方法:

static <T extends Enum<T>> TvalueOf(Class<T> enumType, String name)


4. 常用方法

一般枚举的方法都是从父类Enum处继承而来,但是values()和valueOf(String name)方法例外,通过反汇编可以看到,该方法是编译器编译阶段添加入枚举中的。

获取枚举名称,优先使用toString方法

Stringname()

StringtoString()
获取枚举的顺序

intordinal()
通过枚举名称获取该枚举,是toString的逆方法

static <T extends Enum<T>> TvalueOf(Class<T> enumType, String name)
获取包含枚举中所有元素的枚举数组

static <T extends Enum<T>> T[]values()
通过名称获取该枚举

static <T extends Enum<T>> TvalueOf(String name)

示例

package enumtype;
public class EnumDemo {
@SuppressWarnings("unused")
public static void main(String[] args) {
Week weekday = Week.MON;
// 获取枚举值的名称
String str1 = weekday.name(); // "MON"
String str2 = weekday.toString(); // "MON"
// 获取枚举索引
int i = weekday.ordinal(); // 1
// 获取所有枚举值
Week[] ws = Week.values(); // {SUN, MON, TUE, WED, THU, FIR, SAT}
// 通过枚举名称获取枚举值
Week w1 = Week.valueOf("WED"); // WED
Week w2 = Week.valueOf(Week.class, "THU"); // THU
}
}
enum Week {
SUN, MON, TUE, WED, THU, FIR, SAT;
}


5. 枚举成员

5.1 概述

由于枚举也相当于一个特殊的Java类,所以枚举中也可以定义域,构造器和方法。

5.2 特点

枚举中的其他成员必须定义在枚举列表之后,并且枚举列表后必须用“;”结束。
枚举中的构造器必须是私有的。
枚举中在构造枚举常量时常使用匿名内部类。
当枚举只有一个特定元素时,可以作为单例使用。

5.3 示例

package enumtype;
public enum TrafficLightsEnum {
// 调用int参数构造器初始化,并使用匿名内部类的方式重写抽象方法并具体实现。
// 相当于TrafficLightsEnum RED = new TrafficLightsEnum(40) {...}
RED(40) {
@Override
public TrafficLightsEnum nextLight() {
return GREEN;
}
},
GREEN(30) {
@Override
public TrafficLightsEnum nextLight() {
return YELLOW;
}
},
YELLOW {
@Override
public TrafficLightsEnum nextLight() {
return RED;
}
};
// 显示时间,默认为3秒
private int time = 3;
// 空参数构造器,必须私有
private TrafficLightsEnum() {};
// int参数构造器,对显示时间初始化,必须私有
private TrafficLightsEnum(int time) {
this.time = time;
}
// 公有抽象方法,返回下一个灯
public abstract TrafficLightsEnum nextLight();
// 公有方法,返回该灯的显示时间
public int getTime() {
return time;
}
}


6. 枚举单例

使用枚举来构造单例非常简单,因为枚举中的元素是固定的,不允许修改的,所以当一个枚举只有一个元素的时候就可以当做单例使用。
示例

package enumtype;
public enum Singleton {
INSTANCE;
}


简单三行代码就可以创建一个单例,而且具有其他单例模式不具备的优势,所以推荐使用:

线程安全:使用类加载器机制来保证线程安全。
抗序列化攻击:由于枚举提供了自己的序列化机制,能够在序列化时仍然保持单例(细节未知)。
抗反射攻击:在反射攻击中创建额外的枚举实例将会报告非法参数错误,不可以在反射中创建枚举实例(细节未知)。

但是其劣势也很明显,显然他不支持延迟加载,遇到加载需要消耗大量资源希望能够延迟加载的单例将不适合使用枚举单例。


Lock锁机制

见《Java多线程》线程安全/线程通讯。


静态导入

1. 概述

在使用某些静态方法时,可以使用静态导入的方式来免去类名,尤其在类名较长的情况下可以让代码更加简洁。当然如果出现方法重名的情况下,类名是不可避免的。

2. 格式

import static 类名.静态方法名
这样在调用该方法时就可以直接使用方法名调用而不需要书写类名。

3. 示例

import static java.lang.Math.max;
public class StaticImport {
public static void main(String[] args) {
int max = max(3, 4);
System.out.println(max);
}
}



增强for循环

1. 格式

for ({VariableModifier} UnannType VariableDeclaratorId : Expression ) {
Statement;
}


2. 特点

Expression必须是数组或者Iterator的实现。
对集合进行遍历时,无法对元素进行操作。可以看成是迭代器的简洁形式。
增强for循环必须制定迭代目标,且无索引,所以功能上较为简单。

3. 示例

int[] arr = {1, 2, 3};
List<String> list = new ArrayList<String>();
list.add("java");
list.add("it");
list.add("android");
for (int i : arr) {
System.out.println(i);
}
for (String string : list) {
System.out.println(string);
}



可变参数

1. 概述

当方法需要传入多个同类型参数时,以往都是使用数组作为参数传入,这样一来就必须定义数组才能作为实际参数。而可变参数的意义在于不需要定义数组,可以将多个同类型参数直接填入参数列表中传入方法。而方法中仍然是将这些参数作为数组看待。

2. 格式

{VariableModifier} UnannType {Annotation} ... VariableDeclaratorId


3. 特点

可变参数只能作为方法的最后一个参数。
参数类型和“...”之间不建议加入空格。
方法内仍然将可变参数作为数组处理。

4. 示例

public static void main(String[] args) {
int result1 = add(1, 2); // 3
int result2 = add(1, 2, 3); // 6
int result3 = add(new int[]{1, 2, 3}); // 6
}
public static int add(int... elements) {
int sum = 0;
for (int i : elements) {
sum += i;
}
return sum;
}



自动装箱和拆箱


1. 概述

自动装箱:将基本数据类型自动转换为对应的基本数据类型包装类实例。

自动拆箱:将基本数据类型包装类实例自动转换为基本数据类型数据。


2. 示例

Integer i = 13; // 自动装箱
int num = i + 1; // 自动拆箱



3. 内存驻留机制(享元模式)

和String类似,基本数据类型包装类也采用了享元模式,在自动装箱时,如果数值范围在byte范围内,则共享内存空间。

Integer a1 = 127;
Integer a2 = 127;
System.out.println( a1 == a2); // true
Integer b1 = 128;
Integer b2 = 128;
System.out.println( b1 == b2); // false


[align=left][/align]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: