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 {@Overridepublic int compareTo(Student o) {return 0;}@Overridepublic 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();}@Deprecatedpublic 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 {// 如果有只有一个配置参数,一般命名为valueString 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() {@Overridepublic void run() {System.out.println("线程任务");}}).start();// Lamda使用1new Thread(() -> System.out.println("线程任务")).start();TreeSet<Integer> set = new TreeSet<Integer>(new Comparator<Integer>() {@Overridepublic 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>() {@Overridepublic void accept(String s) {System.out.println(s);}};list.forEach(consumer);*/list.forEach((o) -> System.out.println(o));/*Predicate predicate = new Predicate<String>() {@Overridepublic 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 8default void method2() {method5();};// JDK 8default void method3() {method5();}// JDK 8static void method4() {}// JDK 9private 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文件会转为推断后的具体类型