1 接口的默认方法
Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default或static关键字即可,这个特征又叫做扩展方法。
public interface Jdk8Interface {
int interfaceMethod();
default void defaultMethod(){
System.out.println("DefalutTest defalut method");
}
static void staticMethod() {
System.out.println("DefalutTest static method");
}
}
1.1 static
接口里的静态方法,即static修饰的有方法体的方法不能被继承,也不能被实现类调用,只能被自身调用,但是静态变量会被继承。
接口.static方法调用,如:Jdk8Interface.staticMethod();
public class TestDemo {
public static void main(String[] args) {
Jdk8Interface.staticMethod();
}
}
1.2 default
public class TestDemo {
public static void main(String[] args) {
Jdk8InterfaceImp imp = new Jdk8InterfaceImp();
imp.defaultMethod();
}
}
default方法可以被子接口继承亦可被其实现类所调用
public interface SubJdk8Interface extends Jdk8Interface {
}
public class SubJdk8InterfaceImp implements SubJdk8Interface {
@Override
public int interfaceMethod() {
// TODO Auto-generated method stub
return 0;
}
}
public class TestDemo {
public static void main(String[] args) {
SubJdk8InterfaceImp imp = new SubJdk8InterfaceImp();
imp.defaultMethod();
}
}
default方法被继承时,可以被子接口覆写
public interface SubJdk8Interface extends Jdk8Interface {
@Override
public default void defaultMethod() {
System.out.println("SubJdk8Interface defalut method");
}
}
2 Lambda表达式
2.1 Lambda表达式的基础语法:Java8引入了一个新的操作符“->”,该操作符成为箭头操作符或者Lambda操作符,箭头操作符将Lambda表达式拆分成两部分
左侧:Lambda表达式的参数列表
右侧:Lambda表达式中所需执行的功能,即Lambda体。
Java 8 中传统的匿名对象的方式可以被更简洁的语法替代,lambda表达式:
List<String> names = Arrays.asList("Angelia", "Snowdrop", "Lili");
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareTo(b);
}
});
Collections.sort(names, (String a, String b) -> {
return a.compareTo(b);
});
//还可以更简洁:左边参数类型省略,根据实际传参确定,右边Lambda体中只有一条语句,return和大括号都可以省略不写。
Collections.sort(names, (a, b)->a.compareTo(b));
3 函数式接口
所谓的函数式接口,当然首先是一个接口,然后就是在这个接口里面只能有一个抽象方法。@FunctionalInterface注解接口,如果接口不满足条件,会报错。
/**
* 声明函数式接口,接口中声明抽象方法,public String getName(String str);
* 声明类TestDemo,类中编写方法使用接口作为参数,讲一个字符串转换成大写,并作为方法的返回值。
*/
public class TestDemo {
public static void main(String[] args) {
TestDemo test = new TestDemo();
String str = test.getName("Hello Angelia", x->x.toUpperCase());
System.out.println(str);
}
public String getName(String str, MyInterface my){
return my.getName(str);
}
@FunctionalInterface
public interface MyInterface{
public String getName(String str);
}
}
/*Java8中的核心函数式接口
Consumer<T> :消费型接口
void accept(T t);
Supplier<T> :供给型接口
T get();
Function<T,R> :函数型接口
R apply(T t);
Predicate<T> :断言型接口
boolean test(T t);*/
4 方法与构造函数引用
Java 8 允许关键字 :: 来传递方法或者构造函数引用。
4.1 方法引用
若Lambda体中的内容有方法已经实现了,我们可以使用“方法引用”。主要语法:
对象::实例方法名
类::静态方法名
类::实例方法名
public class TestDemo {
public static void main(String[] args) {
/*TestDemo test = new TestDemo();
String str = test.getName("Hello Angelia", x->x.toUpperCase());
System.out.println(str);*/
MyInterface my = x->x.toUpperCase();
String str = my.getName("Hello Angelia");
System.out.println(str);
MyInterface my2 = String::toLowerCase;
String str2 = my2.getName("Hello Angelia");
System.out.println(str2);
}
public String getName(String str, MyInterface my){
return my.getName(str);
}
@FunctionalInterface
public interface MyInterface{
public String getName(String str);
}
}
注意:
①Lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型一致。
②若Lambda参数列表中的第一参数是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用ClassName::method进行调用。
格式:ClassName::new
public class PersonDemo {
public static void main(String[] args) {
/**
* 3.这里我们使用构造函数引用来将他们关联起来,而不是实现一个完整的工厂:
*/
PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Angelia", "Zheng");
System.out.println(person);
}
}
/**
* 1.首先定义一个包含多个构造函数的简单类
*/
class Person {
String firstName;
String lastName;
Person() {}
Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
@Override
public String toString() {
return "Person [firstName=" + firstName + ", lastName=" + lastName + "]";
}
}
/**
* 2.接下来我们指定一个用来创建Person对象的对象工厂接口
*/
interface PersonFactory<P extends Person> {
P create(String firstName, String lastName);
}
注意:需要引用的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致。
5 Annotation 注解:支持多重注解
Java 8允许我们把同一个类型的注解使用多次,只需要给该注解标注一下@Repeatable即可。
public class AnnotationDemo {
public static void main(String[] args) {
MyAnnotation myAnnotation = AnnotationTest1.class.getAnnotation(MyAnnotation.class);
System.out.println(myAnnotation); //null
MyAnnotations myAnnotations1 = AnnotationTest1.class.getAnnotation(MyAnnotations.class);
System.out.println(myAnnotations1.value().length); //2
MyAnnotation[] myAnnotations2 = AnnotationTest1.class.getAnnotationsByType(MyAnnotation.class);
System.out.println(myAnnotations2.length); //2
}
}
/**
* 将使用反射技术来解析此注解。注解的RetentionPolicy应该设置为RUNTIME。 否则注解信息在执行过程中将不可用。
*/
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotations {
MyAnnotation[] value();
}
@Repeatable(MyAnnotations.class)
@interface MyAnnotation {
String value();
}
/** 老方法:使用包装类当容器来存多个注解 */
@MyAnnotations({@MyAnnotation("MyAnnotation1"), @MyAnnotation("MyAnnotation2")})
class AnnotationTest1{
}
/** 新方法:使用多重注解 */
@MyAnnotation("MyAnnotation1")
@MyAnnotation("MyAnnotation2")
class AnnotationTest2{
}
6 新的日期时间 API
Java 8 在包java.time下包含了一组全新的时间日期API。新的日期API和开源的Joda-Time库差不多,但又不完全一样。其中比较常用的类有Clock、LocaleDate、LocalTime、LocaleDateTime、ZonedDateTime、Timezones等。
Clock类提供了访问当前日期和时间的方法,Clock是时区敏感的,可以用来取代 System.currentTimeMillis() 来获取当前的微秒数。某一个特定的时间点也可以使用Instant类来表示,Instant类也可以用来创建老的java.util.Date对象。
Clock clock = Clock.systemDefaultZone();
long mills = clock.millis();
Instant instant = clock.instant();
Date legacyDate = Date.from(instant);
LocalDate
本地日期LocalDate 表示了一个确切的日期,比如 2018-05-10。该对象值是不可变的,用起来和LocalTime基本一致。下面的例子展示了如何给Date对象加减天/月/年。另外要注意的是这些对象是不可变的,操作返回的总是一个新实例。
LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
LocalDate yesterday = tomorrow.minusDays(2);
LocalDate independenceDay = LocalDate.of(2018, Month.MAY, 10);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
LocalTime
LocalTime 定义了一个没有时区信息的时间,例如 晚上10点,或者 15:30:15。下面的例子使用前面代码创建的时区创建了两个时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差:
LocalTime now1 = LocalTime.now(); //15:30:15
LocalTime now2 = LocalTime.now(ZoneId.of("Europe/Berlin")); //10:05:36
long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);
LocalDateTime 本地日期时间
LocalDateTime 同时表示了时间和日期,相当于前两节内容合并到一个对象上了。LocalDateTime和LocalTime还有LocalDate一样,都是不可变的。LocalDateTime提供了一些能访问具体字段的方法。LocalDateTime dateTime = LocalDateTime.of(2018, Month.MAY, 10, 16, 9, 59);
DayOfWeek dayOfWeek = dateTime.getDayOfWeek();
System.out.println(dayOfWeek); //THURSDAY
Month month = dateTime.getMonth();
System.out.println(month); //MAY
long minuteOfDay = dateTime.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay); //969
Timezones 时区
在新API中时区使用ZoneId来表示。时区可以很方便的使用静态方法of来获取到。 时区定义了到UTS时间的时间差,在Instant时间点对象到本地日期对象之间转换的时候是极其重要的。
System.out.println(ZoneId.getAvailableZoneIds());
ZoneId zone1 = ZoneId.of("America/Cuiaba");
ZoneId zone2 = ZoneId.of("Etc/GMT+9");
System.out.println(zone1.getRules());
System.out.println(zone2.getRules());
7 Stream