搜索
首页Javajava教程写给Java程序员的Scala入门教程

Java 8拥有了一些初步的函数式编程能力:闭包等,还有新的并发编程模型及Stream这个带高阶函数和延迟计算的数据集合。在尝试了Java 8以后,也许会觉得意犹未尽。是的,你会发现Scala能满足你在初步尝试函数式编程后那求知的欲望。

安装Scala

到Scala官方下载地址下载:http://scala-lang.org/download/:

wget -c http://downloads.lightbend.com/scala/2.11.8/scala-2.11.8.tgztar zxf scala-2.11.8.tgz
cd scala-2.11.8./bin/scala
Welcome to Scala version 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_60).
Type in expressions to have them evaluated.Type :help for more information.

scala>

RELP

刚才我们已经启动了Scala RELP,它是一个基于命令行的交互式编程环境。对于有着Python、Ruby等动态语言的同学来说,这是一个很常用和工具。但Javaer们第一次见到会觉得比较神奇。我们可以在RELP中做一些代码尝试而不用启动笨拙的IDE,这在我们思考问题时非常的方便。对于Javaer有一个好消息,JDK 9干始将内建支持RELP功能。

对于Scala常用的IDE(集成开发环境),推荐使用IDEA for scala plugins和scala-ide。

Scala的强大,除了它自身对多核编程更好的支持、函数式特性及一些基于Scala的第3方库和框架(如:Akka、Playframework、Spark、Kafka……),还在于它可以无缝与Java结合。所有为Java开发的库、框架都可以自然的融入Scala环境。当然,Scala也可以很方便的Java环境集成,比如:Spring。若你需要第3方库的支持,可以使用Maven、Gradle、Sbt等编译环境来引入。

Scala是一个面向对象的函数式特性编程语言,它继承了Java的面向对特性,同时又从Haskell等其它语言那里吸收了很多函数式特性并做了增强。

变量、基础数据类型

Scala中变量不需要显示指定类型,但需要提前声明。这可以避免很多命名空间污染问题。Scala有一个很强大的类型自动推导功能,它可以根据右值及上下文自动推导出变量的类型。你可以通过如下方式来直接声明并赋值。

scala> val a = 1
a: Int = 1

scala> val b = true
b: Boolean = true

scala> val c = 1.0
c: Double = 1.0

scala> val a = 30 + "岁"
a: String = 30岁

Immutable

(注:函数式编程有一个很重要的特性:不可变性。Scala中除了变量的不可变性,它还定义了一套不可变集合scala.collection.immutable._。)

val代表这是一个final variable,它是一个常量。定义后就不可以改变,相应的,使用var定义的就是平常所见的变量了,是可以改变的。从终端的打印可以看出,Scala从右值自动推导出了变量的类型。Scala可以如动态语言似的编写代码,但又有静态语言的编译时检查。这对于Java中冗长、重复的类型声明来说是一种很好的进步。

(注:在RELP中,val变量是可以重新赋值的,这是`RELP`的特性。在平常的代码中是不可以的。)

基础数据类型

Scala中基础数据类型有:Byte、Short、Int、Long、Float、Double,Boolean,Char、String。和Java不同的是,Scala中没在区分原生类型和装箱类型,如:int和Integer。它统一抽象成Int类型,这样在Scala中所有类型都是对象了。编译器在编译时将自动决定使用原生类型还是装箱类型。

字符串

Scala中的字符串有3种。

分别是普通字符串,它的特性和Java字符串一至。

连线3个双引号在Scala中也有特殊含义,它代表被包裹的内容是原始字符串,可以不需要字符转码。这一特性在定义正则表达式时很有优势。

还有一种被称为“字符串插值”的字符串,他可以直接引用上下文中的变量,并把结果插入字符串中。

scala> val c2 = '杨'
c2: Char = 杨

scala> val s1 = "重庆誉存企业信用管理有限公司"
s1: String = 重庆誉存企业信用管理有限公司

scala> val s2 = s"重庆誉存企业信用管理有限公司${c2}景"
s2: String = 重庆誉存企业信用管理有限公司

scala> val s3 = s"""重庆誉存企业信用管理有限公司"工程师"\n${c2}景是江津人"""
s3: String =
重庆誉存企业信用管理有限公司"工程师"
杨景是江津人

运算符和命名

Scala中的运算符其实是定义在对象上的方法(函数),你看到的诸如:3 + 2其实是这样子的:3.+(2)。+符号是定义在Int对象上的一个方法。支持和Java一至的运算符(方法):

(注:在Scala中,方法前的.号和方法两边的小括号在不引起歧义的情况下是可以省略的。这样我们就可以定义出很优美的DSL)

==、!=:比较运算

!、|、&、^:逻辑运算

>>、ae9a7f8fd651ba04724d5e92a2dea499是定义Tuple2的一种便捷方式。

scala> map + ("z" -> "Z")
res23: scala.collection.immutable.Map[String,String] = Map(a -> A, b -> B, z -> Z)

scala> map.filterNot(entry => entry._1 == "a")
res24: scala.collection.immutable.Map[String,String] = Map(b -> B)

scala> val map3 = map - "a"
map3: scala.collection.immutable.Map[String,String] = Map(b -> B)

scala> map
res25: scala.collection.immutable.Map[String,String] = Map(a -> A, b -> B)

Scala的immutable collection并没有添加和删除元素的操作,其定义+(List使用::在头部添加)操作都是生成一个新的集合,而要删除一个元素一般使用 - 操作直接将Key从map中减掉即可。

(注:Scala中也scala.collection.mutable._集合,它定义了不可变集合的相应可变集合版本。一般情况下,除非一此性能优先的操作(其实Scala集合采用了共享存储的优化,生成一个新集合并不会生成所有元素的复本,它将会和老的集合共享大元素。因为Scala中变量默认都是不可变的),推荐还是采用不可变集合。因为它更直观、线程安全,你可以确定你的变量不会在其它地方被不小心的更改。)

Class

Scala里也有class关键字,不过它定义类的方式与Java有些区别。Scala中,类默认是public的,且类属性和方法默认也是public的。Scala中,每个类都有一个“主构造函数”,主构造函数类似函数参数一样写在类名后的小括号中。因为Scala没有像Java那样的“构造函数”,所以属性变量都会在类被创建后初始化。所以当你需要在构造函数里初始化某些属性或资源时,写在类中的属性变量就相当于构造初始化了。

在Scala中定义类非常简单:

class Person(name: String, val age: Int) {
  override def toString(): String = s"姓名:$name, 年龄: $age"
}

默认,Scala主构造函数定义的属性是private的,可以显示指定:val或var来使其可见性为:public。

Scala中覆写一个方法必需添加:override关键字,这对于Java来说可以是一个修正。当标记了override关键字的方法在编译时,若编译器未能在父类中找到可覆写的方法时会报错。而在Java中,你只能通过@Override注解来实现类似功能,它的问题是它只是一个可选项,且编译器只提供警告。这样你还是很容易写出错误的“覆写”方法,你以后覆写了父类函数,但其实很有可能你是实现了一个新的方法,从而引入难以察觉的BUG。

实例化一个类的方式和Java一样,也是使用new关键字。

scala> val me = new Person("杨景", 30)
me: Person = 姓名:杨景, 年龄: 30scala> println(me)
姓名:杨景, 年龄: 30scala> me.name
<console>:20: error: value name is not a member of Person
       me.name
          ^

scala> me.ageres11: Int = 30

case class(样本类)

case class是Scala中学用的一个特性,像Kotlin这样的语言也学习并引入了类似特性(在Kotlin中叫做:data class)。case class具有如下特性:

不需要使用new关键词创建,直接使用类名即可

默认变量都是public final的,不可变的。当然也可以显示指定var、private等特性,但一般不推荐这样用

自动实现了:equals、hashcode、toString等函数

自动实现了:Serializable接口,默认是可序列化的

可应用到match case(模式匹配)中

自带一个copy方法,可以方便的根据某个case class实例来生成一个新的实例

……

这里给出一个case class的使用样例:

scala> trait Person
defined trait Person

scala> case class Man(name: String, age: Int) extends Person
defined class Man

scala> case class Woman(name: String, age: Int) extends Person
defined class Woman

scala> val man = Man("杨景", 30)
man: Man = Man(杨景,30)

scala> val woman = Woman("女人", 23)
woman: Woman = Woman(女人,23)

scala> val manNextYear = man.copy(age = 31)
manNextYear: Man = Man(杨景,31)

object

Scala有一种不同于Java的特殊类型,Singleton Objects。

object Blah {  def sum
(l: List[Int]): Int = l.sum
}

在Scala中,没有Java里的static静态变量和静态作用域的概念,取而代之的是:object。它除了可以实现Java里static的功能,它同时还是一个线程安全的单例类。

伴身对象

大多数的object都不是独立的,通常它都会与一个同名的class定义在一起。这样的object称为伴身对象。

class IntPair(val x: Int, val y: Int)

object IntPair {
  import math.Ordering
  implicit def ipord: Ordering[IntPair] =
    Ordering.by(ip => (ip.x, ip.y))
}

注意

伴身对象必需和它关联的类定义定义在同一个.scala文件。

伴身对象和它相关的类之间可以相互访问受保护的成员。在Java程序中,很多时候会把static成员设置成private的,在Scala中需要这样实现此特性:

class X {
  import X._
  def blah = foo
}
object X {
  private def foo = 42
}

函数

在Scala中,函数是一等公民。函数可以像类型一样被赋值给一个变量,也可以做为一个函数的参数被传入,甚至还可以做为函数的返回值返回。

从Java 8开始,Java也具备了部分函数式编程特性。其Lamdba函数允许将一个函数做值赋给变量、做为方法参数、做为函数返回值。

在Scala中,使用def关键ygnk来定义一个函数方法:

scala> def calc(n1: Int, n2: Int): (Int, Int) = {
     |   (n1 + n2, n1 * n2)
     | }
calc: (n1: Int, n2: Int)(Int, Int)

scala> val (add, sub) = calc(5, 1)
add: Int = 6
sub: Int = 5

这里定义了一个函数:calc,它有两个参数:n1和n2,其类型为:Int。cala函数的返回值类型是一个有两个元素的元组,在Scala中可以简写为:(Int, Int)。在Scala中,代码段的最后一句将做为函数返回值,所以这里不需要显示的写return关键字。

而val (add, sub) = calc(5, 1)一句,是Scala中的抽取功能。它直接把calc函数返回的一个Tuple2值赋给了add他sub两个变量。

函数可以赋给变量:

scala> val calcVar = calc _
calcVar: (Int, Int) => (Int, Int) = <function2>

scala> calcVar(2, 3)
res4: (Int, Int) = (5,6)

scala> val sum: (Int, Int) => Int = (x, y) => x + y
sum: (Int, Int) => Int = <function2>

scala> sum(5, 7)
res5: Int = 12

在Scala中,有两种定义函数的方式:

将一个现成的函数/方法赋值给一个变量,如:val calcVar = calc _。下划线在此处的含意是将函数赋给了变量,函数本身的参数将在变量被调用时再传入。

直接定义函数并同时赋给变量,如:val sum: (Int, Int) => Int = (x, y) => x + y,在冒号之后,等号之前部分:(Int, Int) => Int是函数签名,代表sum这个函数值接收两个Int类型参数并返回一个Int类型参数。等号之后部分是函数体,在函数函数时,x、y参数类型及返回值类型在此可以省略。

一个函数示例:自动资源管理

在我们的日常代码中,资源回收是一个很常见的操作。在Java 7之前,我们必需写很多的try { ... } finally { xxx.close() }这样的样版代码来手动回收资源。Java 7开始,提供了try with close这样的自动资源回收功能。Scala并不能使用Java 7新加的try with close资源自动回收功能,但Scala中有很方便的方式实现类似功能:

def using[T <: AutoCloseable, R](res: T)(func: T => R): R = {  try {
    func(res)
  } finally {    if (res != null)
      res.close()
  }
}val allLine = using(Files.newBufferedReader(Paths.get("/etc/hosts"))) { reader =>  @tailrec
  def readAll(buffer: StringBuilder, line: String): String = {    if (line == null) buffer.toString    else {
      buffer.append(line).append(&#39;\n&#39;)
      readAll(buffer, reader.readLine())
    }
  }

  readAll(new StringBuilder(), reader.readLine())
}

println(allLine)

using是我们定义的一个自动化资源管帮助函数,它接爱两个参数化类型参数,一个是实现了AutoCloseable接口的资源类,一个是形如:T => R的函数值。func是由用户定义的对res进行操作的函数代码体,它将被传给using函数并由using代执行。而res这个资源将在using执行完成返回前调用finally代码块执行.close方法来清理打开的资源。

这个:T d2201fbeac34073ff11a26fd6415de65 x + y这个的函数字面量定义函数形式。所以,既然通过变量定义的函数可以放在其它函数代码体内,通过def定义的函数也一样可以放在其它代码体内,这和Javascript很像。

@tailrec注解的含义是这个函数是尾递归函数,编译器在编译时将对其优化成相应的while循环。若一个函数不是尾递归的,加上此注解在编译时将报错。

模式匹配(match case)

模式匹配是函数式编程里面很强大的一个特性。

之前已经见识过了模式匹配的简单使用方式,可以用它替代:if else、switch这样的分支判断。除了这些简单的功能,模式匹配还有一系列强大、易用的特性。

match 中的值、变量和类型

scala> for {
     |   x <- Seq(1, false, 2.7, "one", &#39;four, new java.util.Date(), new RuntimeException("运行时异常"))
     | } {
     |   val str = x match {
     |     case d: Double => s"double: $d"
     |     case false => "boolean false"
     |     case d: java.util.Date => s"java.util.Date: $d"
     |     case 1 => "int 1"
     |     case s: String => s"string: $s"
     |     case symbol: Symbol => s"symbol: $symbol"
     |     case unexpected => s"unexpected value: $unexpected"
     |   }
     |   println(str)
     | }
int 1
boolean false
double: 2.7
string: one
symbol: &#39;four
java.util.Date: Sun Jul 24 16:51:20 CST 2016
unexpected value: java.lang.RuntimeException: 运行时异常

上面小试牛刀校验变量类型的同时完成类型转换功能。在Java中,你肯定写过或见过如下的代码:

public void receive(message: Object) {    
if (message isInstanceOf String) {     
  String strMsg = (String) message;
        ....
    } else if (message isInstanceOf java.util.Date) {
        java.util.Date dateMsg = (java.util.Date) message;
        ....
    } ....
}

对于这样的代码,真是辣眼睛啊~~~。

序列的匹配

scala> val nonEmptySeq = Seq(1, 2, 3, 4, 5)

scala> val emptySeq = Seq.empty[Int]

scala> val emptyList = Nil

scala> val nonEmptyList = List(1, 2, 3, 4, 5)

scala> val nonEmptyVector = Vector(1, 2, 3, 4, 5)

scala> val emptyVector = Vector.empty[Int]

scala> val nonEmptyMap = Map("one" -> 1, "two" -> 2, "three" -> 3)

scala> val emptyMap = Map.empty[String, Int]

scala> def seqToString[T](seq: Seq[T]): String = seq match {
     |   case head +: tail => s"$head +: " + seqToString(tail)
     |   case Nil => "Nil"
     | }

scala> for (seq <- Seq(
     |   nonEmptySeq, emptySeq, nonEmptyList, emptyList,
     |   nonEmptyVector, emptyVector, nonEmptyMap.toSeq, emptyMap.toSeq)) {
     |   println(seqToString(seq))
     | }
1 +: 2 +: 3 +: 4 +: 5 +: Nil
Nil
1 +: 2 +: 3 +: 4 +: 5 +: Nil
Nil
1 +: 2 +: 3 +: 4 +: 5 +: Nil
Nil
(one,1) +: (two,2) +: (three,3) +: Nil
Nil

模式匹配能很方便的抽取序列的元素,seqToString使用了模式匹配以递归的方式来将序列转换成字符串。case head +: tail将序列抽取成“头部”和“非头部剩下”两部分,head将保存序列第一个元素,tail保存序列剩下部分。而case Nil将匹配一个空序列。

case class的匹配

scala> trait Person

scala> case class Man(name: String, age: Int) extends Person

scala> case class Woman(name: String, age: Int) extends Person

scala> case class Boy(name: String, age: Int) extends Person

scala> val father = Man("父亲", 33)

scala> val mather = Woman("母亲", 30)

scala> val son = Man("儿子", 7)

scala> val daughter = Woman("女儿", 3)

scala> for (person <- Seq[Person](father, mather, son, daughter)) {
     |   person match {
     |     case Man("父亲", age) => println(s"父亲今年${age}岁")
     |     case man: Man if man.age < 10 => println(s"man is $man")
     |     case Woman(name, 30) => println(s"${name}今年有30岁")
     |     case Woman(name, age) => println(s"${name}今年有${age}岁")
     |   }
     | }
父亲今年33岁
母亲今年有30岁
man is Man(儿子,7)
女儿今年有3岁

在模式匹配中对case class进行解构操作,可以直接提取出感兴趣的字段并赋给变量。同时,模式匹配中还可以使用guard语句,给匹配判断添加一个if表达式做条件判断。

并发

Scala是对多核和并发编程的支付做得非常好,它的Future类型提供了执行异步操作的高级封装。

Future对象完成构建工作以后,控制权便会立刻返还给调用者,这时结果还不可以立刻可用。Future实例是一个句柄,它指向最终可用的结果值。不论操作成功与否,在future操作执行完成前,代码都可以继续执行而不被阻塞。Scala提供了多种方法用于处理future。

scala> :paste
// Entering paste mode (ctrl-D to finish)

import scala.concurrent.duration.Duration
import scala.concurrent.{Await, Future}
import scala.concurrent.ExecutionContext.Implicits.global

val futures = (0 until 10).map { i =>
  Future {
    val s = i.toString
    print(s)
    s
  }
}

val future = Future.reduce(futures)((x, y) => x + y)

val result = Await.result(future, Duration.Inf)

// Exiting paste mode, now interpreting.

0132564789

scala> val result = Await.result(future, Duration.Inf)
result: String = 0123456789

上面代码创建了10个Future对象,Future.apply方法有两个参数列表。第一个参数列表包含一个需要并发执行的命名方法体(by-name body);而第二个参数列表包含了隐式的ExecutionContext对象,可以简单的把它看作一个线程池对象,它决定了这个任务将在哪个异步(线程)执行器中执行。futures对象的类型为IndexedSeq[Future[String]]。本示例中使用Future.reduce把一个futures的IndexedSeq[Future[String]]类型压缩成单独的Future[String]类型对象。Await.result用来阻塞代码并获取结果,输入的Duration.Inf用于设置超时时间,这里是无限制。

这里可以看到,在Future代码内部的println语句打印输出是无序的,但最终获取的result结果却是有序的。这是因为虽然每个Future都是在线程中无序执行,但Future.reduce方法将按传入的序列顺序合并结果。

除了使用Await.result阻塞代码获取结果,我们还可以使用事件回调的方式异步获取结果。Future对象提供了几个方法通过回调将执行的结果返还给调用者,常用的有:

onComplete: PartialFunction[Try[T], Unit]:当任务执行完成后调用,无论成功还是失败

onSuccess: PartialFunction[T, Unit]:当任务成功执行完成后调用

onFailure: PartialFunction[Throwable, Unit]:当任务执行失败(异常)时调用

import scala.concurrent.Future
import scala.util.{Failure, Success}
import scala.concurrent.ExecutionContext.Implicits.global

val futures = (1 to 2) map {
  case 1 => Future.successful("1是奇数")
  case 2 => Future.failed(new RuntimeException("2不是奇数"))
}

futures.foreach(_.onComplete {
  case Success(i) => println(i)
  case Failure(t) => println(t)
})

Thread.sleep(2000)

futures.onComplete方法是一个偏函数,它的参数是:Try[String]。Try有两个子类,成功是返回Success[String],失败时返回Failure[Throwable],可以通过模式匹配的方式获取这个结果。

总结

本篇文章简单的介绍了Scala的语言特性,本文并不只限于Java程序员,任何有编程经验的程序员都可以看。现在你应该对Scala有了一个基础的认识,并可以写一些简单的代码了。

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
带你搞懂Java结构化数据处理开源库SPL带你搞懂Java结构化数据处理开源库SPLMay 24, 2022 pm 01:34 PM

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于结构化数据处理开源库SPL的相关问题,下面就一起来看一下java下理想的结构化数据处理类库,希望对大家有帮助。

Java集合框架之PriorityQueue优先级队列Java集合框架之PriorityQueue优先级队列Jun 09, 2022 am 11:47 AM

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于PriorityQueue优先级队列的相关知识,Java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的,下面一起来看一下,希望对大家有帮助。

完全掌握Java锁(图文解析)完全掌握Java锁(图文解析)Jun 14, 2022 am 11:47 AM

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于java锁的相关问题,包括了独占锁、悲观锁、乐观锁、共享锁等等内容,下面一起来看一下,希望对大家有帮助。

一起聊聊Java多线程之线程安全问题一起聊聊Java多线程之线程安全问题Apr 21, 2022 pm 06:17 PM

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于多线程的相关问题,包括了线程安装、线程加锁与线程不安全的原因、线程安全的标准类等等内容,希望对大家有帮助。

详细解析Java的this和super关键字详细解析Java的this和super关键字Apr 30, 2022 am 09:00 AM

本篇文章给大家带来了关于Java的相关知识,其中主要介绍了关于关键字中this和super的相关问题,以及他们的一些区别,下面一起来看一下,希望对大家有帮助。

Java基础归纳之枚举Java基础归纳之枚举May 26, 2022 am 11:50 AM

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于枚举的相关问题,包括了枚举的基本操作、集合类对枚举的支持等等内容,下面一起来看一下,希望对大家有帮助。

java中封装是什么java中封装是什么May 16, 2019 pm 06:08 PM

封装是一种信息隐藏技术,是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法;封装可以被认为是一个保护屏障,防止指定类的代码和数据被外部类定义的代码随机访问。封装可以通过关键字private,protected和public实现。

归纳整理JAVA装饰器模式(实例详解)归纳整理JAVA装饰器模式(实例详解)May 05, 2022 pm 06:48 PM

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于设计模式的相关问题,主要将装饰器模式的相关内容,指在不改变现有对象结构的情况下,动态地给该对象增加一些职责的模式,希望对大家有帮助。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
2 周前By尊渡假赌尊渡假赌尊渡假赌
仓库:如何复兴队友
4 周前By尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒险:如何获得巨型种子
4 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )专业的PHP集成开发工具