目录

关于Java10

    Java9之后,Oracle改变策略,把OpenJDK的更新频率变成每6个月发布一次二进制文件,淦!不过把Oracle JDK转型成LTS,每三年发布一次二进制代码,Java10新策略的第一个版本,它并不是LTS版本。Java10正式发布于2018年3月20日。本次更新比较吸引我的就是这个局部变量类型推断,这个新特性出自于JEP 286提案。


    在Java10里面,新增了一个var关键字,可以使用var来代替类型名称声明局部变量。所谓的类型推断,指的是Java编译器具有根据方法或者声明的类型来确定所使用的参数类型的能力。类型推断不仅仅是在Java中,例如c、c艹的auto。现在,有了var关键字,在Java中也可以使用类似的功能了。

String str= "Hello!";    //Java10以前
var str= "Hello"    //Java10


本质

    在执行包含var的语句时,编译器会直接查看语句的右侧,也就是语句的初始化器的部分,并根据右侧的表达式的值(RHS),推断出类型。但是这并不意味着Java就变成动态类型语言,可以从编译后的class文件中看到这一切的本质。

编译前的代码:

//test.java

import java.util.List;

public class test {
    public static void main(String[] args) {
        var num=10;
        var integer=new Integer(num);
        var str="Hello";
        var list= List.of(str,integer);
        System.out.println(list);
    }
}

编译后的代码:

//test.class

import java.util.List;

public class test {
    public test() {
    }

    public static void main(String[] args) {
        int num = 10;
        Integer integer = new Integer(num);
        String str = "Hello";
        List list = List.of(str, integer);
        System.out.println(list);
    }
}

    可以看到Java编译器还是正确的将var推断为基本类型并装入class中。值得一提的是var并不是关键字,而是保留类型名称,你可以使用var做变量名,方法名称,甚至包名,但你不可以把var做类名和接口的名称。综上所述,var的本质就是一颗语法糖


使用场景

Java类型推断仅适用于如下场景:

  • 表达式具有初始化的部分
  • for循环的索引
  • for循环中的局部变量

举例:

    //具有初始化的部分
    var nums=List.of(1,2,3,4,5);
    //for循环索引
    for(var out:nums){
        System.out.print(out);
    }
    //for循环的局部变量
    for(var i=0;i<nums.size();i++){
        System.out.print(nums.get(i));
    }


使用限制

1、没有初始化部分时,不能使用var(废话

    var num;    //error

2、不能定义多变量,例如:

    var a=10,b=20;    //error

3、初始化部分不能为null

    var fyatto=null;    //error

null不是类型,所以编译器无法推断右边的类型(RHS)

4、用于数组时不能有多余的大括号( [ ] )

    var arrays[]=new int[47];    //error    47 is a magic number!

5、具有数组初始值的将会错误、具有方法引用将会错误

    var nums={1,2,3,4,5,47};    //error    47 is a magic number!
    var sqrt=Math::sqrt;    //error

    方法引用符号(::)出自于Java8的更新~ 值得一提的是,上述的两种特例在编译器推断类型时,编译器会依赖于左侧的明确的类型或是方法的定义,而var使用的是RHS,这就会导致编译器的错误~


当var遇到泛型

    var具备对范型的类型推断,且会为范型语句进行类型擦除。

    Java的范型并不是“真正的范型”,在编译期间,编译器会将范型所携带的类型信息全部抹掉,Java生成的字节码里面的范型是不包含任何的类型信息的,保留在class文件里面的只是原始类型。关于原始类型,就是抹掉类型信息以后留下的真正类型。

ArrayList<String> list1=new ArrayList<String>();
ArrayList<Integer> list2=new ArrayList<String>()
list1==list2    ///:~ true
//编译过后都会被擦除为ArrayList,所以对比两个类型会返回true

    在使用var推断范型的时候,还应该注意边界的问题,Java有三种关于边界的通配符,分别是:?、extends、super,代表的意思分别为无界、上界、下界。无界通配符不关心你操作的是什么类型或者你不知道将要操作什么类型。上界通配符表示你操作的类型可能是这个类型或者这个类型的子类。下界通配符表示这个类可能是这个类型或者这个类型的父类,直到Object。

下面接着叨叨var

var map1=new HashMap();
var map2=new HashMap<>();

1、编译器将map1推断为HashMap,不带范型
2、编译器遇到<>(棱形运算符?)时依赖于LHS进行类型推断,遇到var时编译器无法进行LHS,因此它推断map2为HashMap且具有所有的对象的下界,即<Object,Object>


不可表达类型

  • 捕获变量:简单地说就是在使用lambda或者匿名内部类的实现接口的时候,应该更倾向于实现一种类似于static方法的函数,同时它还应该尽量的是无状态lambda。如果该表达式需要使用类的成员或者方法的局部变量,应该声明该外部变量为final,防止原值被修改,此时这个外部变量就是捕获变量(捕获指表达式获取到外部的引用),而这个lambda就被称为捕获lambda。
        final double d=4.70;   //love
        DoubleUnaryOperator doubleUnaryOperator = x->{
        // d=3.14  ERROR!!捕获,禁止修改。
            return d;
        };

小技巧:

如果需要修改外部的变量又不想违背规则,可以把变量放进一个数组,即使数组被声明final,也可以修改数组内对象的值(数组引用不指向数组对象)。

        final double d=3.14;
        final double[] tmp_d={d};
        DoubleUnaryOperator doubleUnaryOperator = x-> tmp_d[0]+=1;
  • 交集类型:出自jdk8(jdk8划时代更新 :@(高兴) )

当你想把一个接口序列化时

    @FunctionalInterface
    public interface calculator {
        long add(long x, long y);
    }
       //lambda里面使用交集类型
         Serializable serializable = (Serializable & calculator) (x, y) -> (x + y);    //方便
        //下面的和上面的等价;多余的代码,不过可读性更好
        public interface calculatot extends Serializable{
          long add(long x,long y);
        }

简单的说交集类型就是让当前类同时成为交集双方两个类共同的子类。

  • 匿名类

越来越刺激了~

    匿名类没有名字,常被用来实现一些接口之类的(暂且认为lambda是简化匿名类的写法,但它绝对不是匿名类的语法糖!),匿名类很容易理解,他们也是、也只是类,var允许对象是一个匿名类类型,这样就可以更简便的实现一个单例。

     Runnable out=new Runnable(){
            @Override
            public void run(){
                var list= List.of("Hello","World");
                for(var out:list){
                    System.out.println(out);
                }
            }
        };
        out.run();


使用var推断不可表达的类型

    @FunctionalInterface
    public interface calculator {
        long add(long x, long y);
    }

    public static void main(String[] args) {
        //var推断交集类型
        var serializable = (calculator & Serializable)(x,y)->(x+y);
        //var推断内部类
        var inter_class=new calculator(){
          @Override
          public long add(long x,long y){
              return x+y;
          }  
        };
    }


民意调查 ::(滑稽)

    来自SurveyMonkey的调查表示:在接受调查的2453位开发者中,有1817位(74.07%)睿智的开发者认为这是一个好主意。而仅有251位(%10.23)开发者认为这不行。


题外话

  • var的好处

    • 干净整洁
    • 写代码更快乐了
  • var的坏处:

    • 在某种意义上的可读性变差了

我需要为了照顾别人的感受而不使用编程语言的语法糖吗?


资料来源

imkiva:upper-and-lower-bounded-wildcards
Java 10: Local Variable Type Inference - JournalDev
Local Variable Type Inference for Java
附:原文源码↑↑↑

JDK13更新了var的功能:Upgrade Local Variable Type Inference
博主的环境为:ubuntu 18.04.4 LTS openjdk11.0.6

最后编辑:2020年07月28日 ©著作权归作者所有

发表评论

正在加载 Emoji