1. 注解
1.1. 认识注解
Annotation:JDK1.5新提供的技术
- 编译检查:比如@SuppressWarnings, @Deprecated和@Override都具有编译检查的作用
- 替代配置文件:使用反射来读取注解的信息
注解就是代码里的特殊标记,用于替代配置文件,开发人员可以通过注解告诉类如何运行
注解的应用:通过反射得到类中的注解,以决定类的运行。注解可以标记在包、类、属性、方法,方法参数以及局部变量上,且同一个地方可以同时标记多个注解。
注解可以在编译,类加载,运行时被读取,并执行相应的处理,以便于其他工具补充信息或部署。
1.2. 内置注解
- @Override:检查该方法是否是重载方法,如发现其父类,或者引用的接口中并没有该方法,会报编译错误
- @Deprecated:编辑过时的方法,使用该方法,会报编译警告
- @SuppressWarnings:指示编译器忽略注解中注明的警告
从Java7开始,新增了3个注解: - @SafeVarags:忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告
- @FunctionalInterface:标识一个匿名函数或函数式接口
- @Repeatable:标识某注解可以在同一个声明上使用多次
@SuppressWarnings(value = {"all"})
public class Student implements Comparable<Student>, Serializable {
@Override
public int compareTo(Student o) {
return 0;
}
@Override
public String toString() {
return super.toString();
}
public static void main(String[] args) {
Date date = new Date();
System.out.println(date.toLocaleString());
Student stu = new Student();
stu.method1();
List list = new ArrayList();
}
@Deprecated
public void method1() {
System.out.println("========");
}
public void method2() {
Date date = new Date();
System.out.println(date.toLocaleString());
}
}
public class TestStudent {
@SuppressWarnings(value = "deprecation")
public static void main(String[] args) {
Student stu = new Student();
stu.method1();
}
}
1.3. 元注解
元注解是指注解的注解
- @Retention:约束注解的生命周期:源码级别(SOURCE)、类文件级别(CLASS)或运行时级别(RUNTIME),若没有@Retention,则默认是RetentionPolicy.CLASS,含义如下:
- SOURCE:注解将被编译器丢弃(就是不会保留在class文件中)
- CLASS:注解在class文件中可用,但会被VM丢弃(不会加载到虚拟机中)
- RUNTIME:注解在运行期间(JVM)也保留,因此可以通过反射机制读取注解的信息,如SpringMVC中的@Controller、@ Autowired、@RequestMappling等
- @Target:用来约束注解可以应用的地方(如方法、类或字段),其中ElementType是枚举类型。若没有@Target,则该Annotation可以用于任何地方
public enum ElementType {
// 标明该注解可以用于类、接口(包括注解类型)或enum声明
TYPE,
// 标明该注解可以用于字段(域)声明,包括enum实例
FIELD,
// 标明该注解可以用于方法声明
METHOD,
// 标明该注解可以用于参数声明
PARAMETER,
// 标明注解可以用于构造函数声明
CONSTRUCTOR,
// 标明注解可以用于局部变量声明
LOCAL_VARIABLE,
// 标明注解可以用于注解声明(应用于另一个注解上)
ANNOTATION_TYPE,
// 标明注解可以用于包声明上
PACKAGE,
// 标明注解可以用于类型参数声明(1.8+)
TYPE_PARAMETER,
// 类型使用声明(1.8+)
TYPE_USE
}
- @Documented:标记这些注解是否包含在用户文档中
- @Inherited:指示注解类型被自动继承。如果在注解类型声明中存在Inherited元注解,并且用户在某一类声明中查询该注解类型,同时该类声明中没有此类型的注解,则将在该类的超类中自动查询该注解类型
2. 注解
2.1. 自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD, ElementType.TYPE})
public @interface MyAnnoation {
int id() default 0;
String name() default "";
double[] scoreArr() default {};
}
public @interface MyAnnoation2 {
// 如果有只有一个配置参数,一般命名为value
String value();
}
@MyAnnoation2("wyb")
@MyAnnoation
public class TestAnnotation {
@MyAnnoation(id=5, name="wyb", scoreArr = {78,89,34})
public static void main(String[] args) {
}
@MyAnnoation2(value="wyb")
public void method1() {
}
}
总结:
- 定义注解的关键字是@interface
- 自定义注解中可以定义多个配置参数,不是成员方法或成员变量;说明参数的名称,以及参数值的类型
- 如果只有一个配置参数,一般命名为value
- 如果配置参数是value,并且只有一个配置参数,value可省略
注意: - 定义注解时,意味着他实现了java.lang.annotation.Anntotation接口,即该注解就是一个Anntotation
- 和implements实现接口的方法不同。Anntotation接口的实现细节都由编译器来完成。通过@interface定义注解后,该注解不能继承其他注解或接口
- 注解常见的API及其关系如下:
2.2. 使用反射读取注解
模拟实现MyBatis的注解并使用反射读取
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = ElementType.TYPE)
public @interface Table {
String value();
}
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = ElementType.FIELD)
public @interface Column {
String columnName();
String columnType();
int length();
int precision() default 0;
}
@Table(value = "t_student")
public class Student1 {
@Column(columnName = "id", columnType = "int", length = 6)
private int id;
@Column(columnName = "sname", columnType = "varchar", length = 10)
private String name;
@Column(columnName = "score", columnType = "double", length = 4, precision = 1)
private double score;
}
public class TestORM {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
String className = "com.wyb.annotation.Student";
Class clazz = Class.forName(className);
// 获取类的所有注解
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
// 获取类的指定注解
Table annotation = (Table) clazz.getAnnotation(Table.class);
System.out.println(annotation);
System.out.println(annotation.value());
// 获取id属性的注解
Field idField = clazz.getDeclaredField("id");
Column idColumn = idField.getAnnotation(Column.class);
System.out.println(idColumn.columnName());
System.out.println(idColumn.columnType());
System.out.println(idColumn.length());
System.out.println(idColumn.precision());
}
}
3. JDK新特性
3.1. JDK8新特性
3.1.1. Lamda表达式
Lamda表达式是JDK8的一个新特性,可以取代大部分的匿名内部类,写出更优雅的Java代码,尤其在集合的遍历和其他集合操作中,可以极大的优化代码结构
public class TestLamda {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程任务");
}
}).start();
// Lamda使用1
new Thread(() -> System.out.println("线程任务")).start();
TreeSet<Integer> set = new TreeSet<Integer>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return -(o1.intValue() - o2.intValue());
}
});
TreeSet<Integer> set2 = new TreeSet<>((o1, o2) -> (o1.intValue() - o2.intValue()));
TreeSet<Integer> set3 = new TreeSet<>((o1, o2) -> {
int v1 = o1.intValue();
int v2 = o2.intValue();
return -(v1 - v2);
});
Collections.addAll(set, 34,55,77,99,22);
System.out.println(set);
}
}
Lambda表达式是一种匿名函数(不是匿名内部类),他是没有声明的方法,也没有访问修饰符、返回值声明和名字。实质是属于函数式编程的概念。
Lambda表达式只能引用标记了final的外层局部变量。Lambda表达式的局部变量可以不用声明为final,但是必须不可被后面的代码修改
虽然Lambda表达式可以对某些接口进行简单的实现,但并不是所有接口都可以使用Lambda表达式来实现。Lambda规定接口中只能有一个需要被实现的抽象方法,不是规定接口中只能有一个方法,称为函数式接口。
3.1.2. 函数式接口
函数式接口:只能有一个抽象方法,其他的可以有default、static、Object里public方法等
JDK8专门提供了@FunctionalInterface注解,用来进行编译检查
作用:在Java中主要用在Lambda表达式和方法引用上
@FunctionalInterface
public interface FunInterface {
// 只能有一个抽象方法
void method1();
default void method2() {}
static void method3() {}
// 从Object类继承的public方法不会计数
public int hashCode();
public boolean equals(Object obj);
}
JDK也提供了大量的函数式接口,使得Lambda表达式的运用更加方便、高效。这些内置的函数式接口可以解决开发中的大部分问题,只有小部分特殊情况需要自己去定义函数式接口
- Consumer<T>:消费型接口(void accept(T t))。有参数,无返回值
- Supplier<T>:供给型接口(T get())。只有返回值,没有入参
- Function<T, R>:函数型接口(R apply(T t))。一个输入参数,一个输出参数
- Predicate<T>:断言型接口(boolean test(T t))。输入一个参数,输出一个boolean类型的返回值
public class TestFunctionInterface {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Collections.addAll(list, "Java", "JS", "MySQL", "Oracle");
/*Consumer consumer = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
list.forEach(consumer);*/
list.forEach((o) -> System.out.println(o));
/*Predicate predicate = new Predicate<String>() {
@Override
public boolean test(String s) {
return s.length() == 4;
}
};
list.removeIf(predicate);*/
list.removeIf((o) -> o.length() == 4);
System.out.println(list);
}
}
3.1.3. Stream API
Stream API与java.io包里的InputStream和OutputStream是完全不同的概念。他是对容器对象功能的增强,专注于对容器对象进行各种便利、高效的聚合操作或大批量数据操作
Stream API提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用fork/join并行方式来拆分任务和加速处理过程。
Stream有三个操作步骤:
- 创建Stream:从一个数据源,如集合、数组中获取流
- 中间操作:一个操作的中间链,对数据源的数据进行操作
- 终止操作:一个终止操作,执行中间操作链,并产生结果
当数据源的数据上了流水线之后,这个过程对数据进行的所有操作都称为中间操作
中间操作会返回一个流对象,因此多个中间操作可以串连起来形成一个流水线,比如map、filter、distinct、sorted、peek、limit等
当所有的中间操作完成后,若要执行终止操作。终止操作将返回一个执行结果,这就是最终的数据。
多个中间操作可以连接成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何处理,而在终止操作全部处理,称为惰性求值
public class TestStream {
public static void main(String[] args) {
List<Integer> list = new ArrayList();
Collections.addAll(list, 99,22,334,66,77);
// list.stream().forEach((x) -> System.out.println(x));
list.stream().sorted((o1, o2) -> o2 - o1).forEach(System.out::println);
System.out.println("---------");
list.stream().limit(2).forEach(System.out::println);
System.out.println("---------");
list.stream().filter((o) -> o >= 60).sorted((o1, o2) -> o2 - o1).forEach(System.out::println);
}
}
3.1.4. 新的日期类
3.2. 其他版本新特性
3.2.1. JDK9新特性
模块化系统:允许开发者模块化开发的应用程序,同时也对JDK本身进行了模块化,因此一个应用程序就可以只包含他所需要的类库
模块化就是在package外面包裹一层,成员的作用范围在当前包和当前项目之间又增加了一个层次:模块
String类的底层由char数组变为byte数组,节省空间。
接口中可以定义private的非抽象方法,便于将多个方法中的冗余代码进行提取且不对外公开。
public interface interface9 {
void method1();
// JDK 8
default void method2() {
method5();
};
// JDK 8
default void method3() {
method5();
}
// JDK 8
static void method4() {}
// JDK 9
private void method5() {}
}
3.2.2. JDK10新特性
局部变量类型推断:将前端思想var关键字引入,自动检测所属类型
/*
* JDK 10 之前的局部变量定义
* */
public class Test1 {
public static void main(String[] args) {
int num = 10;
Scanner sc = new Scanner(System.in);
List<String> list = new ArrayList<>();
Map<Integer, String> map = new HashMap<>();
map.put(1, "wyb");
map.put(2, "xz");
map.put(3, "bjy");
Set<Map.Entry<Integer, String>> entries = map.entrySet();
for (Map.Entry<Integer, String> e : entries) {
System.out.println(e.getKey() + "\t" + e.getValue());
}
int[] arr = {1, 2, 3};
for (int a : arr) {
System.out.println(a);
}
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
其实:=左侧的类型,不用显示编写出来,完全可以由右侧推断出左侧的类型
/*
* JDK 10的局部变量类型推断
* */
public class Test2 {
public static void main(String[] args) {
var num = 10;
var sc = new Scanner(System.in);
var list = new ArrayList<String>();
var map = new HashMap<>();
map.put(1, "wyb");
map.put(2, "xz");
map.put(3, "bjy");
var entries = map.entrySet();
for (var e : entries) {
System.out.println(e.getKey() + "\t" + e.getValue());
}
var arr = new int[]{1,2,3};
for (var a : arr) {
System.out.println(a);
}
for (var i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
局部变量的类型推断必须具类型推断的基础才行。并不是任何地方都可以进行类型推断,比如:方法的返回值、行参,构造方法的行参等
注意:类型推断只能发生在编译阶段,在编译后的class文件会转为推断后的具体类型