JDK 1.5 新特性
静态导入
1、写法:
import staticjava.util.Arrays.;//导入的是Arrays这个类中的所以静态成员。
import staticjava.lang.System.//导入了Ssytem类中所以静态成员。
没加static导入的是类,加上static导入的全是某一个类中所以的静态成员。这样写在调用该类的静态方法时可以不用再写类名。如:Arrays.sort(数组);就可以直接写sort(数组);
2、注意:
当导入的两个类中有同名成员时,需要在成员前加上相应的类名。
当类名重名时,需要指定具体的包名。当方法重名时,指定具体所属的对象或者类。
可变参数
如果一个方法在参数列表中传入多个参数,个数不确定,那么每次都要复写该方法。这时可以用数组作为形式参数。但是在传入时,每次都需要定义一个数组对象,作为实际参数。在JDK1.5版本后,就提供了一个新特性:可变参数。
用…这三个点表示,且这三个点位于变量类型和变量名之间,前后有无空格皆可。
可变参数其实就是数组参数的简写形式。不用每一次都手动的建立数组对象。只要将要操作的元素作为参数传递即可。隐式将这些参数封装成了数组。
在使用时注意:可变参数一定要定义在参数列表的最后面。
增强for循环
1、格式:
- for(数据类型 变量名 :被遍历的集合(collection)或者数组) {执行语句}
2、说明
- 对集合进行遍历。只能获取集合元素。但是不能对集合进行操作。可以看作是迭代器的简写形式。
- 迭代器除了遍历,还可以进行remove集合中元素的动作。如果使用ListIterator,还可以在遍历过程中对集合进行增删改查的操作。
3、传统for和高级for的区别: - 高级for有一个局限性。必须有被遍历的目标(集合或数组)。
- 传统for遍历数组时有索引。
- 建议在遍历数组的时候,还是希望使用传统for。因为传统for可以定义角标。
注意:变量类型前可加修饰符,如final(可被局部内部类访问到)。
自动拆箱和自动装箱
1、自动装箱:Integer iObj = 3; 装箱就是 自动将基本数据类型转换为包装器类型;
2、自动拆箱:int n = iObj ; //拆箱就是 自动将包装器类型转换为基本数据类型。
装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的 xxxValue方法实现的。(xxx代表对应的基本数据类型)。
3、对于基本数据类型的说明:整数在-128 ~ 127之间的数,包装成Integer类型对象,会存入常量池中的缓存,再创建一个对象的时候,如果其值在这个范围内,就会直接到常量池中寻找,因为这些小数值使用的频率很高,所以缓存到常量池中,被调用时就方便很多。
枚举
一、为什么要有枚举?
问题:要定义星期几或性别的变量,该怎么定义?假设用1-7分别表示星期一到星期日,但有人可能会写成int weekday = 0;或即使使用常量方式也无法阻止意外。
枚举就是要让某个类型的变量的取值只能为若干个固定值中的一个,否则,编译器就会报错。枚举可以让编译器在编译时就可以控制源程序中填写的非法值,普通变量的方式在开发阶段无法实现这一目标。
二、枚举的基本应用
Enum是Java 语言枚举类型的公共基本类。
1、通过enum关键字定义枚举类,枚举类是一个特殊的类,每个元素都是该类的一个实例对象。
2、用枚举类规定值,如上面的WeekDay类。以后用此类型定义的值只能是这个类中规定好的那些值,若不是这些值,编译器不会通过。
3、好处:在编译时期就会发现错误,表明值不符合,减少了运行时期的错误。
4、如果调用者想打印枚举类中元素的信息,需由编写此类的人定义toString方法。
注:枚举类是一个class,而且是一个不可被继承的final类,其中的元素都是类静态常量。
5、常用方法:
构造器:
- 构造器只是在构造枚举值的时候被调用。
- 构造器只有私有private,绝不允许有public构造器。这样可以保证外部代码无法重新构造枚举类的实例。因为枚举值是public static final的常量,但是枚举类的方法和数据域是可以被外部访问的。
- 构造器可以有多个,调用哪个即初始化相应的值。
非静态方法:(所有的枚举类都继承了Enum类中的方法)
- String toString() ;//返回枚举量的名称
- int ordinal() ;//返回枚举值在枚举类中的顺序,按定义的顺序排
- Class getClass() ;//获取对应的类名
- String name();//返回此枚举常量的名称,在其枚举声明中对其进行声明。
静态方法:
- valueOf(String e) ;//转为对应的枚举对象,即将字符串转为对象
- values() ;//获取所有的枚举对象元素
三、枚举的高级应用
1、枚举就相当于一个类,其中也可以定义构造方法、成员变量、普通方法和抽象方法。
2、枚举元素必须位于枚举体中的最开始部分,枚举元素列表的后要有分号与其他成员分隔。把枚举中的成员方法或变量等放在枚举元素的前面,编译器报告错误。
3、构造方法必须定义成私有的
小结:
1、匿名内部类比较常用
2、类的方法返回的类型可以是本类的类型
3、类中可定义静态常量,常量的结果就是自己这个类型的实例对象
4、枚举只有一个成员时,就可以作为一种单例的实现方式。
注:
1、所有的枚举都继承自Java.lang.Enum类。
2、switch语句支持int,char,enum类型,使用枚举,能让我们的代码可读性更强。
JDK 1.7 新特性
switch语句支持字符串变量
public String getTypeOfDayWithSwitchStatement(String dayOfWeekArg) {
String typeOfDay;
switch (dayOfWeekArg) {
case "Monday":
typeOfDay = "Start of work week";
break;
case "Tuesday":
case "Wednesday":
case "Thursday":
typeOfDay = "Midweek";
break;
case "Friday":
typeOfDay = "End of work week";
break;
case "Saturday":
case "Sunday":
typeOfDay = "Weekend";
break;
default:
throw new IllegalArgumentException("Invalid day of the week: " + dayOfWeekArg);
}
return typeOfDay;
}
switch 语句比较表达式中的String对象和每个case标签关联的表达式,就好像它是在使用String.equals方法一样;因此,switch语句中 String对象的比较是大小写敏感的。相比于链式的if-then-else语句,Java编译器通常会从使用String对象的switch语句中生成更高效的字节码。
泛型实例化类型自动推断
以下两个语句等价:
ArrayList<String> al1 = new ArrayList<String>(); // Old
ArrayList<String> al2 = new ArrayList<>(); // New
新的整数字面表达方式 - “0b"前缀和”_"连数符
① 表示二进制的字面值
byte b1 = 0b00100001; // New
byte b2 = 0x21; // old
byte b3 = 33; // old
②字面常量数字的下划线。
用下划线连接整数提升其可读性,自身无含义,不可用在数字的起始和末尾。
Java编码语言对给数值型的字面值加下划线有严格的规定。如上所述,你只能在数字之间用下划线。你不能用把一个数字用下划线开头,或者已下划线结尾。这里有一些其它的不能在数值型字面值上用下划线的地方:
- 在数字的开始或结尾
- 对浮点型数字的小数点附件
- F或L下标的前面
- 该数值型字面值是字符串类型的时候
float pi1 = 3_.1415F; // 无效的; 不能在小数点之前有下划线
float pi2 = 3._1415F; // 无效的; 不能在小数点之后有下划线
long socialSecurityNumber1=999_99_9999_L;//无效的,不能在L下标之前加下划线
int a1 = _52; // 这是一个下划线开头的标识符,不是个数字
int a2 = 5_2; // 有效
int a3 = 52_; // 无效的,不能以下划线结尾
int a4 = 5_______2; // 有效的
int a5 = 0_x52; // 无效,不能在0x之间有下划线
int a6 = 0x_52; // 无效的,不能在数字开头有下划线
int a7 = 0x5_2; // 有效的 (16进制数字)
int a8 = 0x52_; // 无效的,不能以下划线结尾
int a9 = 0_52; // 有效的(8进制数)
int a10 = 05_2; // 有效的(8进制数)
int a11 = 052_; // 无效的,不能以下划线结尾
在单个catch代码块中捕获多个异常,以及用升级版的类型检查重新抛出异常
Java 7之前的版本:
catch (IOException ex) {
logger.error(ex);
throw new MyException(ex.getMessage());
catch (SQLException ex) {
logger.error(ex);
throw new MyException(ex.getMessage());
}catch (Exception ex) {
logger.error(ex);
throw new MyException(ex.getMessage());
}
在Java 7中,我们可以用一个catch块捕获所有这些异常:
catch(IOException | SQLException | Exception ex){
logger.error(ex);
throw new MyException(ex.getMessage());
}
JDK 1.8 新特性
接口的扩展方法
在JDK1.8之前,接口中只允许有抽象方法,但是在1.8之后,接口中允许有一个非抽象的方法,但是必须使用default进行修饰,叫做扩展方法
public interface UserService {
void deleteUser(User user);
default User showUser(User user){
System.out.println("show User");
user = Optional.ofNullable(user).orElse(new User());
user.setName("这是经过扩展方法之后的user");
return user;
}
}
在我们实现接口之后,可以选择对方法直接使用或者重写。
lambda表达式的用法
创建匿名内部类
在1.8之前,我们创建匿名内部类的时候,是这样的:
Converter<String ,Integer> converter = new Converter<String, Integer>() {
@Override
public Integer converter(String s) {
return Integer.parseInt(s);
}
};
在1.8之后呢,我们可以这样来创建
Converter<String, Integer> converter = (f) -> {
return Integer.valueOf(f);
};
更简单额:如果你的方法只有一句话,你可以这样来创建
Converter<String, Integer> converter = (f) -> Integer.parseInt(f);
方法引用
方法引用通过方法的名字来指向一个方法。
方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
方法引用使用一对冒号 ::
函数式接口,方法与构造方法的引用
函数式接口:接口中只有一个方法,可以对应一个lambda表达式。通过匿名内部类或者方法传递进行连接,调用接口即调用对应的方法。
@FunctionalInterface
public interface Converter<F, T> {
T converter(F f);
}
//方法传递,使用的是Integer.valueOf()的方法
Converter<String, Integer> converter = Integer::valueOf;
常用方法
1、foreach方法
public static void main(String[] args) {
String[] arrs = {"hello","world","welcome","meet","you","aaa"};
List<String> list = Arrays.asList(arrs);
test1(list);
}
public static void test1(List list){
list.stream().forEach(x -> System.out.println(x)); //拿到每个元素打印
list.stream().forEach(System.out::println); //如果只进行打印,可以使用方法的引用。
}
2、对数据进行过滤
list.stream().filter((s) -> s.startsWith("a")).forEach(System.out::println); //aaa,过滤开始元素为a的字符串。
3、对数据进行排序
list.stream().filter((user) -> user.getName().startsWith("a")).sorted().forEach(System.out::println);
//需要特别注意的是:你对user进行排序,那么user类必须实现了Comparable<User>接口,并重写compareTo方法,来定义比较方法才可以,否则会报无法转换为Compare错误!
public class User implements Comparable<User>,Serializable {
private Integer age;
getter,setter省略。。。
@Override
public int compareTo(User user) {
return user.getAge() - this.getAge() ;
}
}
//实现了Comparable接口之后,系统会知道如何进行排序比较。
需要注意的是:这里排序之后只是流中进行了排序,如果想要得到排序之后的集合,需要对流进行toArray操作,然后重新转换成集合。
4、map操作
返回由给定函数应用于此流的结果组成的流。也就是我定义一个方法,对流中的每个元素进行操作。如果需要返回修改之后的list,还需要toArray
list.stream().map((user -> {
user.setName(user.getName().toUpperCase());
return user;
})).forEach(System.out::println); //定义了一个Function<User,User>接口的匿名内部类,使用lambda表达式来实现。
//写的更加明显一点如下:
Function<User,User> function = (user -> {
user.setName(user.getName().toUpperCase());
return user;
}); //Function接口的匿名内部类
list.stream().map(function).forEach(System.out::println);
5、count操作:最终操作
long count = list.stream().filter((user) -> user.getName().startsWith("a")).count();
System.out.println("所有以a开头用户名字个数为: " + count); // 所有以a开头用户名字个数为: 5
6、reduce操作
Stream<String> stream1 = Stream.of("aa", "bb", "cc"); //直接使用静态方法,获取指定值的顺序排序流
stream1.reduce((s1,s2) ->s1 +"#" +s2).ifPresent(System.out::println);
操作的目的是:允许通过指定的函数来将stream中的多个元素规约为一个元素,最后得到的是一个Optional接口容器表示,如果想拿到值,需要get()来拿到。
7、关于Stream的串行和并行
list.stream()-->即进行串行操作,单线程。时间长
list.parallelStream()-->并行操作,多线程同时进行。需要时间短
时间表达式
LocalDate today = LocalDate.now(); //现在的日期
LocalDate tomorrow = today.plusDays(1); //今天之后的一天的日期
LocalTime time = LocalTime.of(10,10,10,358); //设定时间
LocalTime now = LocalTime.now(); //时间
LocalDateTime atDate = now.atDate(today); //日期+时间
LocalDateTime dateTime = LocalDateTime.now(); //当前日期和时间