parameters的坑和Java8增加的特性

1,635 阅读7分钟

@TOC


前言


-parameters

为什么想写这篇博客,起因是被-parameters参数坑了一把。

Java 8 可以使用反射API和Parameter.getName()方法获得方法参数的名字,并且参数名可以保存在字节码里,通过Java编译命令javac的-parameters参数。

不过万万没想到parameters参数居然是默认关闭的,因为这个问题让我纳闷了很久。

保留参数名这一选项的编译开关都是javac -parameters 开了就可以得到正确的参数名,不开只能得到无意义的arg0 arg1 ……

因为我项目是基于Maven的,为了可移植性,选择将-parameters参数添加到maven-complier-plugin的配置部分。 偶对了,compiler插件是用于编译 Java 源文件的。

如果你的Maven版本<3.6.2:

			<plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <compilerArgs>
                        <arg>-parameters</arg>
                    </compilerArgs>
                </configuration>
            </plugin>

如果>=3.6.2

			<plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <parameters>true</parameters>
                </configuration>
            </plugin>

我是不太想直接在IDEA里设置的,毕竟不是每个项目都有这种小众的需求。


既然解决了这个坑,那顺便再回顾一下Java增加的特性吧。 也不能称之为新的了,Java8不新了

但是大家为什么津津乐道Java8的新特性,因为Java8是Java5发布以来最大的一次版本升级。


1. Lambda表达式和函数式接口


1.1 介绍

Lambda表达式——闭包,允许我们将一个函数当作方法的参数。 主要使用lambda表达式来简化匿名内部类的书写(并不能取代所有)

//编译器会根据上下文推测参数的类型
Arrays.asList("H","e","l").forEach(e->System.out.print(e));

//也可以显示指定
Arrays.asList("H","e","l").forEach((String e)->System.out.print(e));

Lambda表达式可能会引用类的成员变量或者局部变量(会被隐式转变为final类型)

String s = "hhh"; //这里其实相当于final String s
Arrays.asList("Alice","Bob","Cindy").forEach(
	e -> System.out.println(e+s)
);

Lambda表达式可能会有返回值,编译器会根据上下文推断返回值的类型。如果lambda的语句块只有一行,不需要return关键字。下面两个写法是等价的:

Arrays.asList("Alice","Bob","Cindy").sort(
	(e1,e2)->e1.compareTo(e2)
);
Arrays.asList("Alice","Bob","Cindy").sort(
	(e1,e2)->{
		int result = e1.compareTo(e2);
		return result;
	}
);

1.2 常见用法

有且只有一个抽象方法的接口被称为函数式接口 取代函数接口的简写:

原本:
new Thread(new Runnable(){
	@Override
	public void run(){
		System.out.println("Thread run()");
	}
}).start();

现在:
new Thread(
	//省略接口名和方法名
	() -> System.out.println("Thread run()")
).start();

Runable run = ()->System.out.println("hhh");
原本:
Collections.sort(Arr,new Comparator<String>(){
	@Override
	public int compare(String s1,String s2){
		//逻辑
		return xxx;
	}
});

现在:
Collections.sort(Arr,(s1,s2)->{
	//逻辑
	return xxx;
})

自定义函数接口:

@FunctionalInterface //可加可不加,重点是只有一个方法
public interface Hello{
	public void sayHello(String s);
}

Hello hello = str -> System.out.print(str);

2. 接口的默认方法和静态方法

Java 8 在接口声明的时候增加了默认方法和静态方法。

默认方法可以不被实现,接口会提供一个默认的方法实现。 所有这个接口的实现类都会通过继承得到这个方法。

使用default关键字修饰默认方法。

接口里可以声明静态方法,并且可以实现。


3. 方法引用

方法引用提供了一个很有用的语义来直接访问类或者实例的已经存在的方法或者构造方法。结合Lambda表达式使用。

public class TestMao {
    public static void main(String[] args){
        List<String> list = Arrays.asList("h","Hh","hHh");
        list.forEach(TestMao::printMess);

        list = list.stream().map(String::toUpperCase).collect(Collectors.toList());
        System.out.println(list.toString());
    }

    public static void printMess(String str){
        System.out.println("it is "+str);
    }
}


4. 重复注释

允许在同一申明类型(类,属性,或方法)的多次使用同一个注解。

JDK8的@Repeatable

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    Class<? extends Annotation> value();
}

使用方法就是,我们定义注解的时候,比如定义@ComponentScan注解 除了原本定义注解要的 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) 这些元注解,还可以加上@Repeatable(),这样我们在使用注解的时候就可以在一个类上使用多个@ComponentScan来扫描多个路径

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
    @AliasFor("basePackages")
    String[] value() default {};
    ……
}
    
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
public @interface ComponentScans {
    ComponentScan[] value();
}

4. 注解的扩展

Java 8扩展了注解可以使用的范围,现在我们几乎可以在所有的地方:局部变量、泛型、超类和接口实现、甚至是方法的Exception声明。


5. 更好的类型判断

Java 8在类型推断方面改进了很多,在很多情况下,编译器可以推断参数的类型


6. 编译器的新特性


6.1 参数名字

简介里已经说过了。


7. Java库的新特性


7.1 Optional

解决空指针异常问题。 很久以前Google Guava项目引入了Optional作为解决空指针异常的一种方式,不赞成代码被null检查的代码污染,期望程序员写整洁的代码。受Google Guava的鼓励,Optional 现在是Java 8库的一部分。

Optional 只是一个容器,它可以保存一些类型的值或者null。


7.2 Stream

流是Java 8 里添加的一个抽象层。不是数据结构、不存储数据,是数据源到目的地的媒介。

聚合操作,有时也称流操作。

顺序和并行指的是在完成流操作时实现并发的能力。

流的特点

  • 使用源集合或数组创建
  • 可以转换组但不能改变组内的数据
  • 轻松允许一次操作整个集合
  • 流可以声明式处理
  • 既不存储数据也不调整它处理的数据,它不是一种数据结构
  • 可通过Lambda表达式进行调整

流和循环: 流通常被比作循环,两者都用于在程序中创建迭代行为。与循环相比流看起来更清晰。

Stream<Integer> stream = Stream.of(1,2,3,4,5);
stream.forEach(p -> System.out.println(p));
int array[]={1,2,3,4,5};
for(int i: array){
System.out.println(i);
}

流的优缺点 优点:

  • 减少代码中的视觉混乱
  • 无需编写迭代器
  • 可以写"what"而不是"how",一目了然
  • 执行速度与for循环一样快,使用并行操作更快
  • 非常适合大型列表
    缺点:
  • 巨大的间接成本
  • 小集合的矫枉过正

filter() 获取一个流并根据传递的条件选择其中的一部分。

List<Integer> list = new ArrayList<>();
List.add(1);
List.add(23);
……
List.add(11);

list.stream()
	.filter(num->num>10)
	.forEach(System.out::println);//打印大于10的

list.stream().forEach(System.out::println);//打印结果就是原本的list

map() 这个方法将一个流和另一个方法作为输入,将该函数应用于流中的每个元素。

List<String> list = new ArrayList<>();
list.add("Dave");
list.add("Joe");
list.add……

list.stream()
	.map(name->name.toUpperCase())
	.forEach(System.out::println);

forEach() 该方法以流为输入,遍历流,对其中的每个元素完成一个动作,并输出该动作的结果。

Stream<Integer> stream = Stream.of(1,2,3,4,5);
stream.forEach(p -> System.out.println(p));

7.3 日期时间API

JDK8的LocalDateTime

JDK8新特性里提供了3个时间类:LocalData、LocalTime、LocalDateTime

Date如果不进行格式化,打印出的时间可读性差:

Tue Sep 10 09:34:04 CST 2019

可以使用SimpleDateFormat对时间进行格式化,但这个类是线程不安全的。它使用了一个calendar共享变量,但这个共享变量没有做线程安全控制。

当多个线程同时使用相同的SimpleDateFormat对象调用format方法时,多个线程会同时调用calendar.setTime方法,可能一个线程刚设置好time值,另外的一个线程马上把time值给改了。

SimpleDateFormat除了format方法是线程不安全的,parse方法也是线程不安全的(包括3个步骤,非原子操作)

LocalDate

LocalDate是日期处理类

LocalDate now = LocalDate.now();//获取当前时间
LocalDate localDate = LocalDate.of(2021,8,1);
int year = localDate.getYear() //获取年
Month month = localDate.getMonth();
int day = localDate.getDayOfMonth();
DayOfWeek dayOfWeek = localDate.getDayOfWeek(); //获取星期

LocalTime

LocalTime是时间处理类

LocalTime now = LocalTime.now(); //获取当前时间
LocalTime localTime = LocalTime.of(14,22,22);//设置时间
int hour = localTime.getHour(); //获取小时
int minute = localTime.getMinute(); //获取分
int second = localTime.getSecond(); //获取秒

LocalDateTime

LocalDateTime可以设置年月日时分秒

LocalDateTime localDateTime = LocalDateTime.now(); 
LocalDateTime localDateTime = LocalDateTime.of(2021,Month.JULY,3,14,28,12);

7.4 Nashorn javaScript引擎

Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。


7.5 Base64

对Base64的支持最终成了Java 8标准库的一部分


7.6 并行数组

Java 8新增加了很多方法支持并行的数组处理。最重要的大概是parallelSort()这个方法显著地使排序在多核计算机上速度加快。


7.7 并发


8. 新的工具


8.1 Nashorn引擎:jjs

jjs是个基于Nashorn引擎的命令行工具。它接受一些JavaScript源代码为参数,并且执行这些源代码。


8.2 类依赖分析工具:jdeps

Jdeps是一个功能强大的命令行工具,它可以帮我们显示出包层级或者类层级java类文件的依赖关系。


9. JVM新特性

永久代被元空间代替了,JVM参数 -XX:PermSize 和 –XX:MaxPermSize被XX:MetaSpaceSize 和 -XX:MaxMetaspaceSize代替。