Go语言入门2 – 语法

一、变量定义

四种方式:

  • 完全体:var name type
  • 类型推断:var name = value
  • 最简体:name := value(仅用于函数内变量,包内变量不行)
  • 变量聚合定义:var( name1=value1 name2=value2 )
package main

import "fmt"

// 定义包内变量
var (
   aa = 1
   bb = "lanxin"
)

// 定义变量,只使用默认初值
func variableZeroValue()  {
   var a int       // 0
   var s string    // ""
   var b bool      // false
   fmt.Println(a, s, b)
}

// 定义变量,赋初值
func variableInitialValue()  {
   var a, b int = 1, 2
   b = 3
   var s string = "lanxin"
   fmt.Println(a, b, s)
}

// 类型推断
func variableTypeDeduction()  {
   var a, b, c, d  = 1, 2, true, "hi"
   fmt.Println(a, b, c, d)
}

// 最简定义变量方式
func variableShorter() {
   a, b, c, d := 3, 2, true, "hi"
   fmt.Println(a, b, c, d)
}

func main() {
   variableZeroValue()
   variableInitialValue()
   variableTypeDeduction()
   variableShorter()
   fmt.Println(aa, bb)
}

二、内建变量类型

8 类变量类型:

  • bool
  • string
  • (u)int、(u)int8、(u)int16、(u)int32、(u)int64
  • uintptr 指针
  • byte
  • rune 字符型,32 位,类比 char
  • float32、float64
  • complex32、complex64 复数 i = √-1

注意:类型转换必须强制转,转化成的类型可以不带小括号,如下:

// 强制类型转换
func triangle()  {
   var a, b int = 3, 4
   var c int
   // float64 和 int 可以不加小括号,也可以加上
   // 开方内建函数定义:func Sqrt(x float64) float64
   c = int(math.Sqrt(float64(a*a + b*b)))
   fmt.Println(c)
}

三、常量与枚举

  • 常量定义方式(常量必须有 value)
    • 完全体:const name type = value
    • 后续的使用自动补类型:const name = value
  • 枚举定义方式(Go 没有枚举,用 const 块来定义)
    • 自定义枚举:const ( name1=value1 name2=value2 )
    • iota 表达式枚举:const ( name1=iota表达式 name2 )
// 常量
func consts() {
   // 指定类型
   const filename string = "filename-const"
   // 不指定类型,表示类型不定
   const a, b = 3, 4
   var c int
   // 由于类型不定,所以这里不需要强转,如果定义为 const a, b int = 3, 4,则需要强转
   c = int(math.Sqrt(a*a + b*b))
   fmt.Println(filename, a, b, c)
}

// 枚举
func enums() {
   // 使用 const 块来实现枚举
   const (
      java = 0
      cpp  = 1
      c    = 2
   )
   fmt.Println(java, cpp, c) // 0 1 2
   // 使用 iota 块来实现自增枚举
   const (
      java1 = iota
      cpp1
      c1
   )
   fmt.Println(java1, cpp1, c1) // 0 1 2
}

 

四、条件语句

if:变量可以定义在 if 块内,其作用域就只在 if 块内。

package main

import (
   "fmt"
   "io/ioutil"
)

func readFile() {
   const filename = "abc.txt"
   // Go 函数可以返回两个值
   // func ReadFile(filename string) ([]byte, error)
   contents, err := ioutil.ReadFile(filename)
   if err != nil {
      fmt.Println(err)
   } else {
      // contents 是 []byte, 用%s 可以打印出来
      fmt.Printf("%s", contents)
   }
   // if 语句外部可访问
   fmt.Printf("%s", contents)
}

func readFileShorter() {
   const filename = "abc.txt"
   // Go 函数可以返回两个值
   // func ReadFile(filename string) ([]byte, error)

   if contents, err := ioutil.ReadFile(filename); err != nil {
      fmt.Println(err)
   } else {
      // contents 是 []byte, 用%s 可以打印出来
      fmt.Printf("%s", contents)
   }
   // if 语句外部不可访问
   //fmt.Printf("%s", contents) // 报错
}

从上边可以看出 Go 对文件操作也非常简单。

switch:默认自带break,如果想穿下去执行,使用 fallthrough

// switch 默认自带break,如果想穿下去执行,使用 fallthrough
func eval(a, b int, op string) int {
   var value int
   switch op {
   case "+":
      value = a + b
   case "-":
      value = a - b
   default:
      panic("unsupport operator" + op)
   }
   return value
}

五、循环

for:for 的三个组件都可省略,Go 没有 while,用 for 来替代

package main

import (
   "bufio"
   "fmt"
   "os"
)

func sum() int {
   var value int
   for i := 0; i <= 100; i++ {
      value += i
   }
   return value
}

// 等同于 while(true)
func deadLoop() {
   for {
      fmt.Println("this is a deadLoop")
   }
}

// Go 没有while,循环全部用 for,for的三个组件都可以省略
func printFile(filename string) {
   // 打开文件
   file, err := os.Open(filename)
   // 如果出错,结束进程
   if err != nil {
      panic(err)
   }
   // 获取读取器
   scanner := bufio.NewScanner(file)
   // 读取:It returns false when the scan stops, either by reaching the end of the input or an error
   for scanner.Scan() {
      fmt.Println(scanner.Text())
   }
}

六、函数

func 函数:

  • 可以有多个返回值
  • 函数的参数类型可以是 func – 函数式编程
  • 支持可变长参数
======================== 可以有多个返回值 =========================
// 函数可返回多个值
// 接收:q, r := div(10, 3)
// 如果只用其中一个值,另一个用下划线:q, _ := div(10, 3)
func div(a, b int) (int, int) {
   return a / b, a % b
}
// use
q, _ := div(10, 3)

======================== 函数的参数类型可以是 func =========================
// 可以使用函数作为参数,函数参数与内部参数一样,函数名在前,函数类型在后
// 后续传参,可以使用匿名内部函数,也可以先定义函数再传入
func apply(op func(int, int) int, a,b int) int {
   return op(a, b)
}
// use
result := apply(func(x int, y int) int {
   return x + y
}, 10,4)

======================== 支持可变长参数 =========================
// 可变长参数
func sum2(nums ...int) int {
   s := 0
   for i := range nums {
      s += nums[i]
   }
   return s
}
// use
sum2(1, 2, 3)

七、指针

Go 只有值传递,引用传递需要借助指针实现,引用传递实际上也是值传递,只是传递的是地址

// 值传递,函数参数拷贝了一份外界的 a, b
func swap_by_value(a, b int) {
   b, a = a, b
}
// use
a, b := 3, 4
swap_by_value(a, b)
fmt.Println(*(&a), b) // 3 4 没有实现交换

函数参数拷贝了一份外界的 a, b

// Go 只有值传递,想实现引用传递,使用指针
// *int 代表是指针类型,此时会将外界传入的 &a 拷贝给 这里的a,即这里的 a 拿到的是外界的 a 的地址
// 通过 *a,由于 a 是 &a,这里的 *a 相当于 *(&a) ,即从地址中取值
// 由于函数内部直接操作的是外界的 a,b 的内存地址,所以可以实现引用传递
func swap_by_pointer(a, b *int) {
   *b, *a = *a, *b
}
// use
swap_by_pointer(&a, &b)
fmt.Println(a, b)
*int 代表是指针类型,此时会将外界传入的 &a 拷贝给 这里的a,即这里的 a 拿到的是外界的 a 的地址
通过 *a,由于 a 是 &a,这里的 *a 相当于 *(&a) ,即从地址中取值
由于函数内部直接操作的是外界的 a,b 的内存地址,所以可以实现引用传递

 

Go语言入门1 – 环境搭建

官方文档:https://golang.org/doc/install,被墙了,所以国内打不开,可以在这个网站下载:https://studygolang.com/dl 。本文以 windows 自动安装版为例:

一、下载:https://studygolang.com/dl/golang/go1.12.4.windows-amd64.msi

安装就不需要说了吧。

➜ ~ go version
go version go1.12.4 windows/amd64

 

二、第一个 Go 程序

使用 GoLand 进行开发,IDE下载地址:https://www.jetbrains.com/go/

package main

import (
   "fmt"
)

func init() {
   fmt.Printf("init\n")
}

func main() {
   fmt.Printf("main\n")
}

运行两种方式:

使用 GoLand 直接 debug / run
执行 go build,然后执行生成的可执行文件 ./helloworld 即可

Windows下JetBrains CLion中文输出乱码的解决方法

JetBrains公司推出的 C/C++ IDE CLion,在编写代码时效率非常高,相信以后也会成为开发C/C++项目的首选。

CLion输出中文时,都会产生乱码,不过也有解决办法,示例代码如下:

#include <iostream>

using namespace std;

int main() {
std::cout << “Hello, World!” << std::endl;
cout << “速度嘎是的反垃圾开始都看过是对方感觉啊” << endl;
return 0;
}

输出效果:

C:\Users\Administrator\CLionProjects\untitled\cmake-build-debug\untitled.exe
Hello, World!
閫熷害鍢庢槸鐨勫弽鍨冨溇寮€濮嬮兘鐪嬭繃鏄鏂规劅瑙夊晩

Process finished with exit code 0

解决方法:

File->Setting->Editor->File Encodings

QQ截图20190428152912

 

代码文档要转成GBK,然后就可以了。

 

QQ截图20190428152933

Java JDK10新特性

Java10它号称有109项新特性,包含12个JEP。

需要注意的是,本次Java10并不是Oracle的官方LTS版本,所以咱们可以先了解新特性。然后坐等java11的发布再考虑在生产中使用吧

特性列表:

  • 局部变量的类型推断 var关键字
  • GC改进和内存管理 并行全垃圾回收器 G1
  • 垃圾回收器接口
  • 线程-局部变量管控
  • 合并 JDK 多个代码仓库到一个单独的储存库中
  • 新增API:ByteArrayOutputStream
  • 新增API:List、Map、Set
  • 新增API:java.util.Properties
  • 新增API: Collectors收集器
  • 其它特性

1、局部变量的类型推断 var关键字

这个新功能将为Java增加一些语法糖 – 简化它并改善开发者体验。新的语法将减少与编写Java相关的冗长度,同时保持对静态类型安全性的承诺。

这可能是Java10给开发者带来的最大的一个新特性。下面主要看例子:

public static void main(String[] args) {
    var list = new ArrayList<String>();
    list.add("hello,world!");
    System.out.println(list);

}

这是最平常的使用。注意赋值语句右边,最好写上泛型类型,否则会有如下情况:

public static void main(String[] args) {
    var list = new ArrayList<>();
    list.add("hello,world!");
    list.add(1);
    list.add(1.01);
    System.out.println(list);
}

list什么都可以装,非常的不安全了。和js等语言不同的是,毕竟Java还是强类型的语言,所以下面语句是编译报错的:

public static void main(String[] args) {
    var list = new ArrayList<String>();
    list.add("hello,world!");
    System.out.println(list);

    list = new ArrayList<Integer>(); //编译报错
}

!!!:下面几点使用限制

  • 局部变量初始化
  • for循环内部索引变量
  • 传统的for循环声明变量
  • 方法参数
  • 全局变量
  • 构造函数参数
  • 方法返回类型
  • 字段
  • 捕获表达式(或任何其他类型的变量声明)
public static void main(String[] args) {
    //局部变量初始化
    var list = new ArrayList<String>();
    
    //for循环内部索引变量
    for (var s : list) {
        System.out.println(s);
    }
    
    //传统的for循环声明变量
    for (var i = 0; i < list.size(); i++) {
        System.out.println(i);
    }
}

 

public static var list = new ArrayList<String>(); //编译报错
public static List<String> list = new ArrayList<>(); //正常编译通过

2、GC改进和内存管理 并行全垃圾回收器 G1

JDK 10中有2个JEP专门用于改进当前的垃圾收集元素。
Java 10的第二个JEP是针对G1的并行完全GC(JEP 307),其重点在于通过完全GC并行来改善G1最坏情况的等待时间。G1是Java 9中的默认GC,并且此JEP的目标是使G1平行。

3、垃圾回收器接口

这不是让开发者用来控制垃圾回收的接口;而是一个在 JVM 源代码中的允许另外的垃圾回收器快速方便的集成的接口。

4、线程-局部变量管控

这是在 JVM 内部相当低级别的更改,现在将允许在不运行全局虚拟机安全点的情况下实现线程回调。这将使得停止单个线程变得可能和便宜,而不是只能启用或停止所有线程。

5、合并 JDK 多个代码仓库到一个单独的储存库中

在 JDK9 中,有 8 个仓库: root、corba、hotspot、jaxp、jaxws、jdk、langtools 和 nashorn 。在 JDK10 中这些将被合并为一个,使得跨相互依赖的变更集的存储库运行 atomic commit (原子提交)成为可能。

6、新增API:ByteArrayOutputStream

String toString(Charset): 重载 toString(),通过使用指定的字符集解码字节,将缓冲区的内容转换为字符串。

7、新增API:List、Map、Set

这3个接口都增加了一个新的静态方法,copyOf(Collection)。这些函数按照其迭代顺序返回一个不可修改的列表、映射或包含给定集合的元素的集合。

8、新增API:java.util.Properties

增加了一个新的构造函数,它接受一个 int 参数。这将创建一个没有默认值的空属性列表,并且指定初始大小以容纳指定的元素数量,而无需动态调整大小。还有一个新的重载的 replace 方法,接受三个 Object 参数并返回一个布尔值。只有在当前映射到指定值时,才会替换指定键的条目。

9、新增API: Collectors收集器

toUnmodifiableList():
toUnmodifiableSet():
toUnmodifiableMap(Function, Function):
toUnmodifiableMap(Function, Function, BinaryOperator):
这四个新方法都返回 Collectors ,将输入元素聚集到适当的不可修改的集合中。

10、其它特性

线程本地握手(JEP 312)

其他Unicode语言 – 标记扩展(JEP 314)

基于Java的实验性JIT编译器

根证书颁发认证(CA)

删除工具javah(JEP 313)

从JDK中移除了javah工具,这个很简单并且很重要。

Java JDK9的新特性

(一):jdk和jre的改变

JDK和JRE已经在Java SE 9中进行了模块化处理。在Java SE 9之前,JDK构建系统用于生成两种类型的运行时映像 ——Java运行时环境(JRE)和Java开发工具包(JDK)。 JRE是Java SE平台的完整实现,JDK包含了JRE和开发工具和类库。

可下图显示了Java SE 9之前的JDK安装中的主目录。JDK_HOME是安装JDK的目录。 如果你只安装了JRE,那么你只有在jre目录下的目录。

1556257207-9564-2ud3f7y6go

在 Java SE 9之前,JDK中:

bin目录用于包含命令行开发和调试工具,如javac,jar和javadoc。 它还用于包含Java命令来启动Java应用程序。

include目录包含在编译本地代码时使用的C/C++头文件。

lib目录包含JDK工具的几个JAR和其他类型的文件。 它有一个tools.jar文件,其中包含javac编译器的Java类。

jre\bin目录包含基本命令,如java命令。 在Windows平台上,它包含系统的运行时动态链接库(DLL)。

jre\lib目录包含用户可编辑的配置文件,如.properties和.policy文件。

jre\lib\approved目录包含允许使用标准覆盖机制的JAR。 这允许在Java社区进程之外创建的实施标准或独立技术的类和接口的更高版本被并入Java平台。 这些JAR被添加到JVM的引导类路径中,从而覆盖了Java运行时中存在的这些类和接口的任何定义。

jre\lib\ext目录包含允许扩展机制的JAR。 该机制通过扩展类加载器(该类加载器)加载了该目录中的所有JAR,该引导类加载器是系统类加载器的子进程,它加载所有应用程序类。 通过将JAR放在此目录中,可以扩展Java SE平台。 这些JAR的内容对于在此运行时映像上编译或运行的所有应用程序都可见。

jre\lib目录包含几个JAR。 rt.jar文件包含运行时的Java类和资源文件。 许多工具依赖于rt.jar文件的位置。

jre\lib目录包含用于非Windows平台的动态链接本地库。

jre\lib目录包含几个其他子目录,其中包含运行时文件,如字体和图像。

Java SE 9调整了JDK的目录层次结构,并删除了JDK和JRE之间的区别。 下图显示了Java SE 9中JDK安装的目录。JDK 9中的JRE安装不包含include和jmods目录。

1556257207-3382-ykfybxx5s6

在Java SE 9 的JDK中: 没有名为jre的子目录。 bin目录包含所有命令。 在Windows平台上,它继续包含系统的运行时动态链接库。 conf目录包含用户可编辑的配置文件,例如以前位于jre\lib目录中的.properties和.policy文件。 include目录包含要在以前编译本地代码时使用的C/C++头文件。 它只存在于JDK中。 jmods目录包含JMOD格式的平台模块。 创建自定义运行时映像时需要它。 它只存在于JDK中。 legal 目录包含法律声明。 lib目录包含非Windows平台上的动态链接本地库。 其子目录和文件不应由开发人员直接编辑或使用。

(二):访问资源

资源是应用程序使用的数据,例如图像,音频,视频,文本文件等。Java提供了一种通过在类路径上定位资源来访问资源的位置无关的方式。 需要与在JAR中打包类文件相同的方式打包资源,并将JAR添加到类路径。 通常,类文件和资源打包在同一个JAR中。 访问资源是每个Java开发人员执行的重要任务。

1. 在JDK 9之前访问资源

在Java代码中,资源由资源名称标识,资源名称是由斜线(/)分隔的一串字符串。 对于存储在JAR中的资源,资源名称仅仅是存储在JAR中的文件的路径。

例如,在JDK 9之前,存储在rt.jar中的java.lang包中的Object.class文件是一个资源,其资源名称是java/lang/Object.class。 在JDK 9之前,可以使用以下两个类中的方法来访问资源:

java.lang.Class java.lang.ClassLoader资源由ClassLoader定位。 一个Class代理中的资源寻找方法到它的ClassLoader。 因此,一旦了解ClassLoader使用的资源加载过程,将不会在使用Class类的方法时遇到问题。

在两个类中有两种不同的命名实例方法:

  • URL getResource(String name)
  • InputStream getResourceAsStream(String name)

两种方法都会以相同的方式找到资源。 它们的差异仅在于返回类型。 第一个方法返回一个URL,而第二个方法返回一个InputStream。 第二种方法相当于调用第一种方法,然后在返回的URL对象上调用openStream()。

ClassLoader类包含三个额外的查找资源的静态方法:

  • static URL getSystemResource(String name)
  • static InputStream getSystemResourceAsStream(String name)
  • static Enumeration<URL> getSystemResources(String name)

这些方法使用系统类加载器(也称为应用程序类加载器)来查找资源。 第一种方法返回找到的第一个资源的URL。 第二种方法返回找到的第一个资源的InputStream。 第三种方法返回使用指定的资源名称找到的所有资源的URL枚举。 要找到资源,有两种类型的方法可以从——getSystemResource*和getResource*中进行选择。 在讨论哪种方法是最好的之前,重要的是要了解有两种类型的资源: 系统资源非系统资源你必须了解他们之间的区别,以了解资源查找机制。

系统资源是在bootstrap类路径,扩展目录中的JAR和应用程序类路径中找到的资源。

非系统资源可以存储在除路径之外的位置,例如在特定目录,网络上或数据库中。

getSystemResource()方法使用应用程序类加载程序找到一个资源,委托给它的父类,它是扩展类加载器,后者又委托给它的父类(引导类加载器)。如果你的应用程序是独立的应用程序,并且它只使用三个内置的JDK类加载器,那么你将很好的使用名为getSystemResource *的静态方法。它将在类路径中找到所有资源,包括运行时映像中的资源,如rt.jar文件。如果你的应用程序是在浏览器中运行的小程序,或在应用程序服务器和Web服务器中运行的企业应用程序,则应使用名为getResource*的实例方法,它可以使用特定的类加载器来查找资源。如果在Class对象上调用getResource*方法,则会使用当前类加载器(加载Class对象的类加载器)来查找资源。 传递给ClassLoader类中所有方法的资源名称都是绝对的,它们不以斜线(/)开头。

例如,当调用ClassLoader的getSystemResource()方法时,将使用java/lang/Object.class作为资源名称。 Class类中的资源查找方法可以指定绝对和相对资源名称。绝对资源名称以斜线开头,而相对资源名称不用。 当使用绝对名称时,Class类中的方法会删除前导斜线并委派给加载Class对象的类加载器来查找资源。 以下调用 Test.class.getResource(“/resources/test.config”); 会被转换成 Test.class.getClassLoader().getResource(“resources/test.config”); 当使用相对名称时,Class类中的方法预先添加了包名称,在使用斜线后跟斜线替换包名中的点,然后再委托加载Class对象的类加载器来查找资源。 假设测试类在com.jdojo.test包中,以下调用: Test.class.getResource(“resources/test.config”); 会被转换成 Test.class.getClassLoader().getResource(“com/jdojo/test/resources/test.config”);

2. 在JDK 9 中访问资源

在JDK 9之前,可以从类路径上的任何JAR访问资源。 在JDK 9中,类和资源封装在模块中。 在第一次尝试中,JDK 9设计人员强制执行模块封装规则,模块中的资源必须对该模块是私有的,因此它们只能在该模块内的代码中访问。 虽然这个规则在理论上看起来很好,但是对于跨模块共享资源的框架和加载的类文件作为来自其他模块的资源,就会带来问题。 为了有限地访问模块中的资源,做了一些妥协,但是仍然强制执行模块的封装。

JDK 9包含三类资源查找方法:

  • java.lang.Class java.lang.ClassLoader
  • java.lang.Module Class
  • ClassLoader类没新增任何新的方法。

Module类包含一个getResourceAsStream(String name)方法,如果找到该资源,返回一个InputStream;否则返回null。

(三):模块化

模块化特性是Java 9 最大的一个特性,Java 9起初的代号就叫Jigsaw,后来被更改为Modularity,Modularity提供了类似于OSGI框架的功能,模块之间存在相互的依赖关系,可以导出一个公共的API,并且隐藏实现的细节,Java提供该功能的主要的动机在于,减少内存的开销,我们大家都知道,在JVM启动的时候,至少会有30~60MB的内存加载,主要原因是JVM需要加载rt.jar,不管其中的类是否被classloader加载,第一步整个jar都会被JVM加载到内存当中去,模块化可以根据模块的需要加载程序运行需要的class,那么JVM是如何知道需要加载那些class的呢?这就是在Java 9 中引入的一个新的文件module.java我们大致来看一下一个例子(module-info.java)

module com.lanxinbase.java9.modules.car
{
    requires com.lanxinbase.java9.modules.engines;
    exports com.lanxinbase.java9.modules.car.handling;
}

Java 平台级模块系统

Java 9的定义功能是一套全新的模块系统。当代码库越来越大,创建复杂,盘根错节的“意大利面条式代码”的几率呈指数级的增长。这时候就得面对两个基础的问题: 很难真正地对代码进行封装, 而系统并没有对不同部分(也就是 JAR 文件)之间的依赖关系有个明确的概念。每一个公共类都可以被类路径之下任何其它的公共类所访问到, 这样就会导致无意中使用了并不想被公开访问的 API。此外,类路径本身也存在问题: 你怎么知晓所有需要的 JAR 都已经有了, 或者是不是会有重复的项呢? 模块系统把这俩个问题都给解决了。 模块化的 JAR 文件都包含一个额外的模块描述器。在这个模块描述器中, 对其它模块的依赖是通过 “requires” 来表示的。另外, “exports” 语句控制着哪些包是可以被其它模块访问到的。所有不被导出的包默认都封装在模块的里面。如下是一个模块描述器的示例,存在于 “module-info.java” 文件中:

module blog
{
    exports com.pluralsight.blog;

    requires cms;
}

我们可以如下展示模块:

1556257208-2617-2yi6gh3ifi

请注意,两个模块都包含封装的包,因为它们没有被导出(使用橙色盾牌可视化)。 没有人会偶然地使用来自这些包中的类。Java 平台本身也使用自己的模块系统进行了模块化。通过封装 JDK 的内部类,平台更安全,持续改进也更容易。当启动一个模块化应用时, JVM 会验证是否所有的模块都能使用,这基于 requires 语句——比脆弱的类路径迈进了一大步。模块允许你更好地强制结构化封装你的应用并明确依赖。

(四):JShel,交互式 Java REPL

许多语言已经具有交互式编程环境,Java 现在加入了这个俱乐部。您可以从控制台启动 jshell ,并直接启动输入和执行 Java 代码。 jshell 的即时反馈使它成为探索 API 和尝试语言特性的好工具。

1556257208-9610-rhrwnmw0e4

测试一个 Java 正则表达式是一个很好的说明 jshell 如何使您的生活更轻松的例子。 交互式 shell 还可以提供良好的教学环境以及提高生产力,您可以在此了解更多信息。在教人们如何编写 Java 的过程中,不再需要解释 “public static void main(String [] args)” 这句废话。

JShell详解

JShell项目是第一个官方的Java REPL (Read-Eval-Print-Loop的缩写,即交互式编程环境),是一种命令行工具。它允许你无需使用类或者方法包装来执行Java语句。它与Python的解释器类似,或其它本地支持REPL的JVM语言,如Scala和Groovy。在Java 9新特性中,这绝对是更有趣的特性之一。下面让我们看看JShell一些最有趣的特性。

1 分号对于纯语句是可选的

1556257207-2708-jqrnq48x1g

实际上,在发起的一个关于未来Java特性的调查中,该特性是受多数人认可的。当然分号仍被保留了下来,无论是作为终结符还是分隔符。REPL允许一次性键入纯表达式和语句,因此分号对于JShell终端用例是可选的。

2 没有受检异常

如果你一直担心受检异常会毁掉你的REPL经历——无需再担心,JShell在后台为你隐藏好了。在下面的例子中,本应当强迫我们捕获一个IOException,却没有出现。下面的例子是我们在读取和打印一个文件,不需要处理IOException。

1556257207-9420-tfpltc1q9b

有一种情况的确会有受检异常弹出,就是当我们尝试运行一个线程,并在里面使用了 Thread.sleep() 语句。由于这是一个整体的方法而非单独的纯语句,它必须是完全有效的Java语句:

1556257213-1371-u3wi84h1ak

3 Java表达式

JShell终端还可以自己计算Java表达式。字符串连接、方法回调、算法,诸如此类。基本上,任何你可以包装在 System.out.println(/ expression here /) 里的都可以计算。正如你可能已经知道到的其它计算方式,它会立即将结果赋给自己的一个变量并打印出来。

1556257213-8809-uj36cf8swg

4 向前引用

JShell给向前引用提供了很棒的支持,所以你在定义方法时可以引用其他方法或变量,且这些方法或变量仅会在一段时间后被定义。这是AdoptOpenJDK提供的REPL指南中的一个例子:

1556257213-9160-v91kln4ao1

5 REPL网络

使用JShell时,我们不会受限于机器和网络访问,这带来了一些有趣的机会。例如,想想把它当做一个终端来与服务器交流,远程连接到服务器并且从外面控制一些参数。另一个选择是查询数据库,这里真的是有无限可能。

1556257214-7611-sg4ejltya6

(五):集合工厂方法

通常,您希望在代码中创建一个集合(例如,List 或 Set ),并直接用一些元素填充它。 实例化集合,几个 “add” 调用,使得代码重复。 Java 9,添加了几种集合工厂方法:

Set<Integer> ints = Set.of(1, 2, 3);
List<String> strings = List.of("first", "second");

除了更短和更好阅读之外,这些方法也可以避免您选择特定的集合实现。 事实上,从工厂方法返回已放入数个元素的集合实现是高度优化的。这是可能的,因为它们是不可变的:在创建后,继续添加元素到这些集合会导致 “UnsupportedOperationException” 。

(六):改进的 Stream API

长期以来,Stream API 都是 Java 标准库最好的改进之一。通过这套 API 可以在集合上建立用于转换的申明管道。在 Java 9 中它会变得更好。Stream 接口中添加了 4 个新的方法:dropWhile, takeWhile, ofNullable。还有个 iterate 方法的新重载方法,可以让你提供一个 Predicate (判断条件)来指定什么时候结束迭代:

IntStream.iterate(1, i -> i < 100, i -> i + 1).forEach(System.out::println);

第二个参数是一个 Lambda,它会在当前 IntStream 中的元素到达 100 的时候返回 true。因此这个简单的示例是向控制台打印 1 到 99。 除了对 Stream 本身的扩展,Optional 和 Stream 之间的结合也得到了改进。现在可以通过 Optional 的新方法 stram将一个 Optional 对象转换为一个(可能是空的) Stream 对象:

Stream<Integer> s = Optional.of(1).stream();

在组合复杂的 Stream 管道时,将 Optional 转换为 Stream 非常有用。

(七):多版本兼容JAR

多版本兼容JAR这个特性对于库的维护者而言是个特别好的消息。当一个新版本的 Java 出现的时候,你的库用户要花费数年时间才会切换到这个新的版本。这就意味着库得去向后兼容你想要支持的最老的 Java 版本 (许多情况下就是 Java 6 或者 7)。这实际上意味着未来的很长一段时间,你都不能在库中运用 Java 9 所提供的新特性。幸运的是,多版本兼容 JAR 功能能让你创建仅在特定版本的 Java 环境中运行库程序时选择使用的 class 版本:

multirelease.jar
    ├──META-INF
    │   └──versions
    │       └── 9
            │           └──multirelease
    │               └──Helper .class
    ├──multirelease
        ├──Helper .class
        └──Main .class

在上述场景中, multirelease.jar 可以在 Java 9 中使用, 不过 Helper 这个类使用的不是顶层的 multirelease.Helper 这个 class, 而是处在“META-INF/versions/9”下面的这个。这是特别为 Java 9 准备的 class 版本,可以运用 Java 9 所提供的特性和库。同时,在早期的 Java 诸版本中使用这个 JAR 也是能运行的,因为较老版本的 Java 只会看到顶层的这个 Helper 类。

(八):私有接口方法

Java 8 为我们带来了接口的默认方法。 接口现在也可以包含行为,而不仅仅是方法签名。 但是,如果在接口上有几个默认方法,代码几乎相同,会发生什么情况? 通常,您将重构这些方法,调用一个可复用的私有方法。 但默认方法不能是私有的。 将复用代码创建为一个默认方法不是一个解决方案,因为该辅助方法会成为公共API的一部分。 使用 Java 9,您可以向接口添加私有辅助方法来解决此问题:

public interface MyInterface {

    void normalInterfaceMethod();

    default void interfaceMethodWithDefault() {
        init();
    }

    default void anotherDefaultMethod() {
        init();
    }

    // This method is not part of the public API exposed by MyInterface
    private void init() {
        System.out.println("Initializing");
    }
}

如果您使用默认方法开发 API ,那么私有接口方法可能有助于构建其实现。

(九):HTTP/2

Java 9 中有新的方式来处理 HTTP 调用。这个迟到的特性用于代替老旧的 HttpURLConnection API,并提供对 WebSocket 和 HTTP/2 的支持。注意:新的 HttpClient API 在 Java 9 中以所谓的孵化器模块交付。也就是说,这套 API 不能保证 100% 完成。不过你可以在 Java 9 中开始使用这套 API:

HttpClient client = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder(URI.create("http://www.google.com"))
                .header("User-Agent", "Java")
                .GET()
                .build();
HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandler.asString());

除了这个简单的请求/响应模型之外,HttpClient 还提供了新的 API 来处理 HTTP/2 的特性,比如流和服务端推送。

12425262728115
 
Copyright © 2008-2021 lanxinbase.com Rights Reserved. | 粤ICP备14086738号-3 |