JavaSE 新特性总结

前言

从JDK 1.5之后,JavaSE添加了不少新的特性,在这里分别对枚举、注解、接口定义加强、Lambda表达式、方法引用以及内建函数式接口进行了一定的整理与复习

枚举

问题引出

枚举常常用来限制对象实例化的个数,在JDK1.5之前,如果想要限定一个类产生的实例化对象的数量,一般使用的就是多例设计模式.但是此种模式不便于开发和维护,代码也不够简洁,于是JDK1.5后引出了枚举类
枚举实际上就是一种高级的多例设计模式,在枚举当中定义的对象就相当于多例设计模式中指定可以生成的对象,在枚举类加载的时候,JVM会自动为他们实例化对象,且这些对象都是不能改变的

Enum类

枚举类型是JDK 1.5中新增特性的一部分,他是一种特殊的数据类型,之所以特殊是因为他既是一种类(class)类型却又比类类型多了些特殊的约束,但是这些约束的存在也造就了枚举类型的简洁性,安全性以及便捷性.实际上,在我们使用关键字enum创建枚举类型并编译后,编译器会为我们生成一个相关的类,这个类继承自java.lang.Enum类
在Enum类中常用方法:

1
2
3
4
5
6
7
8
9
10
//构造方法
protected Enum(String name, int ordinal)

//取得枚举名字
public final String name()

//取得枚举序号
public final int ordinal()

//取得所有的枚举数据values():返回的是一个枚举的对象数组

方式的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class 枚举 {

public static void main(String[] args) {
//取得枚举变量的序号和对应名称
System.out.println(Color.BLUE.ordinal()+" = "+Color.BLUE.name());

for(Color arr:Color.values()){
System.out.println(arr.ordinal()+" = "+arr.name());
}
}
}

enum Color{
//以下五个对象就相当于多例设计模式中的五个实例化对象
BLUE,RED,GREEN,BLACK,WRITE
}

运行结果:

1
2
3
4
5
6
0 = BLUE
0 = BLUE
1 = RED
2 = GREEN
3 = BLACK
4 = WRITE

从运行结果当中可以看到,枚举变量的序号是从0开始由JVM指定的,还有就是枚举变量的指定使用全部大写,这两点需要注意!
思考: enum和Enum有什么关系?
enum是一个关键字,使用该关键字定义的枚举类本质上就相当于一个类继承了Enum类

定义结构

枚举相当于一个高级的多例设计模式,自然兼具多例设计模式的特性,所以在设计枚举的结构时,我们可以在枚举中定义属性,方法,实现接口
示例:在枚举中定义更多的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
enum Color{
BLUE("蓝色"),RED("红色"),GREEN("绿色"); //如果定义多个内容,那么枚举变量的定义必须写在第一行
private String title;

private Color(String title){ //构造方法私有化
this.title=title;
}

@Override
public String toString() {
return this.title ;
}
}

示例:枚举继承接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class 枚举继承接口 {

public static void main(String[] args) {
Icolor icolor = Color2.RED;
String resultColor = icolor.getColor();
System.out.println(resultColor);
}
}


enum Color2 implements Icolor {
BLUE("蓝色"),RED("红色"),YELLOW("黄色");//枚举中包含多个属性时,将枚举对象必须放在第一行

private String title;
private Color2(String title){
this.title = title;
}

@Override
public String toString() {
return this.title;
}

@Override
public String getColor() {
return this.title;
}

}

interface Icolor{
String getColor();
}

再次解释: 在定义一个枚举类时,直接使用关键字enum,其中的对象在第一行就指定好,这些对象就相当于多例(单例)模式中在类内部产生的实例化对象,他们不可改变,当JVM把一个枚举类型加载时就会自动实例化这些对象,给对象后面带上括号就相当于在对象实例化的时候给他指定了参数(和其他普通类实例化相同),枚举类也需要一个私有的构造器!

枚举的使用

枚举的最大特点就是只有指定的几个对象可以使用
示例:在某些场合,枚举可以限定对象的个数,例如使用性别时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class 枚举的应用 {
public static void main(String[] args) {
Person person = new Person(Sexy.MALE, "张三", 20);
System.out.println(person.toString());
}
}


enum Sexy{
MALE("男"),FEMALE("女");

private String sexy;
private Sexy(String sexy){
this.sexy = sexy;
}

@Override
public String toString() {
return this.sexy;
}
}


class Person{

private Sexy sex;
private String name;
private int age;

public Person(Sexy sex, String name, int age) {
this.sex = sex;
this.name = name;
this.age = age;
}

@Override
public String toString() {
return "Person{" +
"sex='" + sex + '\'' +
", name='" + name + '\'' +
", age=" + age +
'}';
}

}

在上例中便可以看到枚举限定了性别的个数,如果不使用枚举的话,很有可能在对象实例化的时候将性别设置为除过男女之外的其他性别,如果使用枚举进行了限定,那么就只能是枚举当中的对象,这就是枚举限定对象数量的好处.
示例2:枚举本身还支持Switch语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class 枚举的应用2 {

public static void main(String[] args) {


switch(Select.ONE){
case ONE:
System.out.println(1);
break;
case TWO:
System.out.println(2);
break;
case THREE:
System.out.println(3);
break;
default:
System.out.println(0);
}

}
}

enum Select{
ONE,TWO,THREE;
}

Java内置三个注解的用法解释

@Override:覆写

此注解是用来标明下面的方法是一个覆写方法并且能够帮助我们检查是否成功覆写了某一个方法

1
2
3
4
5
6
class Animal{

public String tostring(){
return "小动物";
}
}

在开发之中可能会出现如上情况,我们自己写了一个类,并且想要覆写toString方法,但是由于某些原因,不小心将toString写错了,但是我们不知道,这时就造成了覆写失败,我们以为已经将toString方法覆写过了,但实际上只是重新定义了一个方法而已,在后续的开发过程中就会造成一些不必要的麻烦
而@Override注解就可以为我们解决这样的问题
当我们想要覆写一个方法的时候可以在方法上面加上此注解,一旦我们出现类似的错误,就会出现报错,以此我们就知道覆写是错误的,便能及时的发现问题了.

@Deprecated:声明过期

在Java中我们经常会不断地去更新一个类,假如我们一个类从1.0版本一直更新到了99.0版本然后决定不再对该类进行研发更新,并且打算逐渐抛弃这个类的使用,那么就会在这个类或方法上添加此注解,来告诉用户这个类已经过期,他已经不会在继续进行更新,但是用户还是可以正常使用的

@SuppressWarnings:压制警告

在编程的过程中除了出现错误还会出现警告,警告不是错误,他不会影响程序的继续执行,但是会弹出来告诉我们这个程序可能会出现某些问题希望我们对此修改,在编程的过程中最好将警告全部处理掉.但是如果我们现在并不想处理警告也不想看到这个警告一直出现,那么就可以在程序的主方法中使用该注解声明希望压制的警告类型
例如:压制可能因为方法(类)过期而产生的警告

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class 压制警告 {
@SuppressWarnings({"deprecation"})
public static void main(String[] args) {
Person person = new Person() ;
}
}

class Person <T> {
@Deprecated // 表示该方法已经不建议使用了,但是即便你使用了也不会出错
public Person() {}
public Person(String name) {}
@Deprecated
public void fun() {}
}

接口增强

在JDK1.8之后,对接口进行了一个较大的改变,在之前的版本中,接口只是方法的声明,其中规范了子类继承时需要实现的方法,而在接口的内部是不对方法进行实现的,即接口中的方法只有声明而没有方法体,在JDK1.8接口增强之后,接口中也可以实现方法了,并且当子类实现了该接口也就自动继承了该接口中的方法(接口的其他特性仍然保留)

接口中定义普通方法

使用default定义普通方法,需要通过类的实例对象来调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Test {

public static void main(String[] args) {
Person student = new Student();
student.Jump();
student.Run();
}
}

interface Person{
void Run();

default void Jump(){
System.out.println("JUMP EVERY DAY");
}

}

class Student implements Person{

@Override
public void Run() {
System.out.println("RUN EVERY DAY");
}
}

可以看到,此时就可以在接口中定义一个完整的方法了,并且当子类实现了这个接口也就继承了由default修饰的这个方法.需要注意的是default修饰的方法只能由实例化对象进行调用.并且增强后的接口仍遵循旧原则,该覆写的方法还是需要进行覆写
使用static定义静态方法,可以通过接口直接调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//static定义的静态方法
public class Test2 {

public static void main(String[] args) {
//接口中的静态方法可以直接通过接口名调用
Imessage.Print();

Imessage imessage = new Message();
//接口中的普通方法通过实例化对象进行调用
imessage.Say();

imessage.Tell(); //一般的方法调用
}
}

interface Imessage{


static void Print(){
System.out.println("接口中的静态方法");
}

default void Say(){
System.out.println("接口中的普通方法");
}

void Tell();
}

class Message implements Imessage{
@Override
public void Tell() {
System.out.println("接口中需要实现的方法");
}
}

接口增强不属于一个标准的设计,而是一个挽救设计,整体而言,接口更加的抽象了,越来越像抽象类,但是他仍然比抽象类要好用很多,因为他可以多继承

Lambda表达式

函数式编程和面向对象编程可以算作俩大开发阵营.很多人认为面向对象编程概念过于完整,结构操作不明确
传统面向对象编程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test {

public static void main(String[] args) {
IPrint iPrint = new IPrint() {
@Override
public void print() {
System.out.println("匿名内部类覆写接口");
}
};

iPrint.print();
}
}

interface IPrint{
void print();
}

传统面向对象编程即使是这么一个简单的接口实现问题都需要写这些代码,其实还是比较冗余的,这段代码有更加简便的实现方式,即Lamda表达式
函数式编程示范相同功能:

1
2
3
4
5
6
7
8
9
10
11
public class Test2 {

public static void main(String[] args) {
Iprint iprint = () -> System.out.println("Lamda表达式实现接口");
iprint.print();
}
}

interface Iprint{
void print();
}

相比较传统的面向对象编程要简洁很多

  • 面向对象语法最大的局限:结构必须非常完整
  • 使用函数式编程的前提:接口必须只有一个方法,如果有两个方法,则无法使用函数式编程.如果现在某个接口就是为函数式编程而生的,最好在定义时就让其只能定义一个方法,可以加上注解—>@FunctionalInterface

    Lamda表达式语法

    1
    (参数)->单行语句;

如果一个方法具有多个语句,那么就要将语句全部写在 {}中
将上面代码改改举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test2 {

public static void main(String[] args) {
Iprint iprint = () -> {
System.out.println("Lamda表达式实现接口");
System.out.println("1");
System.out.println("2");
};
iprint.print();
}
}

@FunctionalInterface
//函数式编程接口(有且只有一个方法)
interface Iprint{
void print();
}

如果现在你的表达式里只有一行数据的返回,那么直接使用语句即可,可以不使用return

1
2
3
4
5
6
7
8
9
10
11
12
public class Test3 {

public static void main(String[] args) {
IAdd iAdd = (p1,p2)-> p1+p2;
System.out.println(iAdd.add(3,5));
}
}

@FunctionalInterface
interface IAdd{
int add(int a, int b);
}

引用

从最初开始,只要是进行引用都是针对于引用类型完成的,也就是只有数组、类、接口具备引用操作。但是JDK1.8
开始追加了方法引用的概念。实际上引用的本质就是别名。所以方法的引用也是别名的使用。而方法引用的类型有
四种形式

  • 引用静态方法: 类名称::static方法名称
  • 引用某个对象的方法: 实例化对象::普通方法
  • 引用某个特定类方法: 类名称::普通方法
  • 引用构造方法: 类名称::new
    注意:方法引用一般都是结合函数式编程使用的,即是lamda表达式的一种更简洁写法

    引用静态方法

    此处拿String类中的静态方法valueOf来举例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /**
    *
    * 引用静态方法
    * 举例:引用String类的valueof方法
    * 本质:就相当于为方法起了一个别名 -->将String的valueOf起了个别名 : IMessage的print
    */

    public class 静态方法引用 {

    public static void main(String[] args) {
    IMessage<Integer,String> iMessage = String::valueOf;
    String resule = iMessage.print(1000);
    System.out.println(resule);
    }
    }

    @FunctionalInterface
    interface IMessage<P,R>{
    R print(P p);
    }

其实引用静态方法就像给一个方法重新取了一个别名一样.
在IMessage接口中只有一个方法,所以可以看作将静态方法引用了过来赋值给了该接口,于是接口中的抽象方法就被覆写为了和此静态方法相同的方法.在使用接口中的方法时就相当于使用了此静态方法

引用对象方法

1
2
3
4
5
6
7
8
9
10
11
public class 引用对象方法 {

public static void main(String[] args) {
IUtil<String> iUtil = new String("hello")::toUpperCase; //进行方法引用
System.out.println(iUtil.print()); //转换的就是这个hello
}
}

interface IUtil<R>{
R print();
}

引用类中普通方法

String有一个compareTo方法,此方法为普通方法

1
2
3
4
5
6
7
8
9
10
11
public class 引用某个特定类种方法 {

public static void main(String[] args) {
Is<Integer,String> is = String::compareTo;
System.out.println(is.max("1000","哈哈"));
}
}

interface Is<R,T>{
R max(T p1,T p2);
}

引用构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class 引用构造方法 {

public static void main(String[] args) {
IPerson<Person,String,Integer> instance = Person::new;
System.out.println(instance.getInstance("Jack",22));
}
}

class Person{
private String name;
private int age;

public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

interface IPerson<P,Q,T>{
P getInstance(Q p1,T p2);
}

以上都属于函数式编程的补充

内建式函数接口

对于方法的引用,严格来讲都需要定义一个接口。不管我们如何操作实际上有可能操作的接口只有四种。

  Java 8 提供了函数式接口包java.util.function.*,在该包下有许多Java 8内建的函数式接口。不过基本上分为四种基本的接口类型:

  • 功能型接口:

    1
    public interface Function<T, R> R apply(T t);
  • 供给型接口

    1
    public interface Supplier T get();
  • 消费型接口

    1
    public interface Consumer void accept(T t);
  • 断言型接口

    1
    public interface Predicate boolean test(T t);

有了这四个接口,我们在使用方法引用的时候便会方便了很多,不需要自己去重新定义函数接口,拿来直接用就行了

功能型接口

1
2
3
4
5
6
7
8
9
import java.util.function.Function;

public class 功能型接口 {

public static void main(String[] args) {
Function function = String::valueOf;
System.out.println(function.apply(1000));
}
}

供给型接口

1
2
3
4
5
6
7
8
9
import java.util.function.Supplier;

public class 供给型接口 {

public static void main(String[] args) {
Supplier<String> supplier = new String("hello")::toUpperCase;
System.out.println(supplier.get());
}
}

消费型接口

1
2
3
4
5
6
7
8
9
import java.util.function.Consumer;

public class 消费型接口 {

public static void main(String[] args) {
Consumer<String> consumer = System.out::println;
consumer.accept("hello world");
}
}

断言型接口

1
2
3
4
5
6
7
8
9
import java.util.function.Predicate;

public class 断言型接口 {

public static void main(String[] args) {
Predicate<String> predicate = new String("##adadf45")::startsWith;
System.out.println(predicate.test("##"));
}
}