scala语言学习-Map

Map

  • 构造 val m = Map("Alice", 1, "Bob"->2)
  • 默认的Map是imutable的,如果需要mutable的Map,需要引入scala.collection.mutable.Map
  • 如果需要空的Map,可以指定一种具体的实现val scores = new scala.collection.mutable.HashMap[String, Int]
  • ->符号就是创建pair的意思,"Alice"->1("Alice", 1)是等价的
  • 访问Map对应key的值还是用()符号,如m("Alice")
  • ()访问,如果key不存在,会直接报错,可以用m.getOrElse("not-exist-key", defaultValue)
  • m.get(key)返回的是一个Option对象,值要么是Some或者是None
  • 删除一个key的操作是-=,例如m -= "Alice"
  • 添加一些元素可以用m += ("Tom"->3, "Lisa"->4)
  • 遍历元素for((k,v) <- m) ...
  • yield语句也适用于pairfor((k,v)<-m) yield (v,k)
  • keys.zip(values).toMap 建立两个数组的映射,返回一个Map对象

Tuple

  • val _4tuple = ("Alice", 1, 2, 3) 定义多元组
  • _4tuple._1 元组的第一个元素"Alice"

scala语言学习-数组Array

Scala的数组

  • 初始化固定数组val arr = new Array[Int](10) // 长度为10,初始化为0 注意这里的new
  • 使用初始值初始化数组val arr = Array(1,2,3,4) // 长度为4,初始值为1,2,3,4
  • 可变长度数组val buffer = ArrayBuffer[Int]()// import scala.collection.mutable.ArrayBuffer 注意这里又没有new
    • buffer += 1 // ArrayBuffer(1)
    • buffer += (2,3,4) // ArrayBuffer(1,2,3,4)
    • buffer ++= Array(5,6,7) // append collection using ++=
    • buffer.toArray 转换为固定数组
  • 数组下标遍历for(i <- 0 until arr.length)...
  • 反过来下标遍历for(i <- (0 until arr.length).reverse)...
  • 直接遍历元素for(elem <- arr)...
  • 数组变换for(elem <- arr if elem ...) yield 2 * elem
  • 多维数组val matrix = Array.ofDim[Double](3,4) 定义多维数组,初始化为0,这里需要指定类型
    • 多维数组val matrix = new Array[Array[Int]](10) 定义数组的数组

scala语言学习-scala中的神奇的下划线_

这几天在学scala,发现语法定义还是有点多的,看到下划线(underscore)没错就是那个_的时候,混乱了,因为scala中的下划线用法太多了,以下尝试列举看到的几个

Import

import scala.util._ // 这里是相当于Java里面的import java.lang.*;语法
import java.util.{HashMap => _, _} // 引入java.util._并且隐藏java.util.HashMap避免和scala的HashMap冲突还有这设定好累

Pattern Matching

def matchx(x:Int):String = x match {
    case 1 => "one"
    case 2 => "two"
    case _ => "anything else" // 匹配其他值
}

匿名函数的参数占位符

这也许是最容易混乱的部分了

scala> val arr = Array(1,2,3,4,5,6,7,8,9,10)
arr: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

scala> arr.filter(_%2==0)
res44: Array[Int] = Array(2, 4, 6, 8, 10)

// 说明
// filter这个方法定义是这样的
def filter(p: (T) => Boolean): Array[T]
// 也就是说,这个方法调用实际上补全了是这样的
arr.filter((x:Int) => x % 2 == 0)
// 由于强大的编译器可以自动推导出匿名函数的参数类型,所以这里的类型不是必要的,可以简化成这样
arr.filter((x) => x % 2 == 0)
// 语言作者觉得,匿名函数费事敲那么多代码搞毛阿,干脆直接留个函数体得了,至于参数么,那就用万能的下划线 _ 好了!
arr.filter(_%2==0)
// 值得注意的是,多个参数的函数一样可以用下划线表达,例如
arr.reduceLeft((a:Int, b:Int) => a+b)
// 直接写成
arr.reduceLeft(_+_)
// 就完事了

部分应用函数 Partial Application

简单的说就是将_部分应用于一个函数的参数,使之变成另一个函数定义,例如

def add(m:Int, n:Int) = m+n
def add2 = add(2, _:Int) // add2 效果就变成了 n+2

函数引用赋值

def fun(a:Int, b:Int) = a+b
def funref = fun // 这么写会报错,需要写成以下这两种其一
def funref = fun _
def funref = fun(_, _)

其他

for(_ <- 1 to 10) println("ha") // 忽略参数,意思是我只要循环次数,不需要这个参数
f(xs: _*) // 数组xs作为参数调用可变参数函数f

关于Scala中的下划线,还有个很geek的冷调查

Can you name all the uses of "_"?
-------------------------------------
Yes ========(7%)
 No =========================(40%)
  _ =====================================(52%)

以上列举的用法肯定不全,所以

To Be Continued ...

scala语言学习-语法和Java的区别

走出自己的comfort zone之学习一门新的语言,先从JVM语言开始,Scala因Apache Spark项目而流行起来,提供了一个实时数据处理和机器学习的平台。Scala语法相较于Java而言简洁许多,偏向脚本语言。

以下笔记来自《Scala for the Impatient》部分记录,记录一个Java程序员视角的主要区别。

在操作中学习是一个非常好的方式,REPL is your friend.

vivi@ssd:/tmp$ scala
Welcome to Scala version 2.11.6 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_79).
Type in expressions to have them evaluated.
Type :help for more information.

scala> println("hello Scala")
hello Scala

基本语法

  • val关键字定义常量, var关键字定义变量
  • 常量/变量定义时可以指定类型,类型在变量名后面: val msg:String = "hello"
  • 没有基本类型,一切都是对象, 1.toString调用是合法的
  • 操作符其实也是方法 a.+(b)
  • a.method(b)可以写成a method b,但是有多个参数的时候还是要写成a.method(p1, p2 ..)
  • 没有参数的方法调用通常省略括号,例如"Hello".length等价于"hello".length()
    • 通常来说只有不修改对象的无参方法调用才省略括号
  • 没有++/--操作符,用+=1/-=1代替
  • import scala.math._ // In Scala, the _ character is a “wildcard,” like * in Java
  • 没有静态方法,有singleton对象
  • 使用apply函数(可省略)来构造对象是常见的方式: BigInt("1234")等价于BigInt.apply("1234")
  • Array元素的访问方式是 array($index), 如array(1), "hello"(0)(实际上也是方法调用,省略了apply方法)
  • 数字类型需要关注的类是RichInt, RichDouble, 字符串类型需要关注的类型是StringOps,另外还关注 Range, Seq
  • 函数(Function)可以作为参数def count(p:(Char) => Boolean):Int,例如"Hello".count(_.isUpper)// 返回1
  • if/else语句是有返回值的val s = if( x > 0 ) 1 else -1
  • {}也是有返回值的:val distance = { val dx = x - x0; val dy = y - y0; sqrt(dx * dx + dy * dy) }
  • 循环语法:while( expr ) ...for( i <- expr ),没有break/continue语句。。有别的办法可以实现,蛋疼
    • 高级循环1, for( i <- 1 to 10 if i % 2 == 0 ) println(i)
    • 高级循环2, val col = for( i <- 1 to 10 if i % 2 == 0 ) yield iyield语句收集符合条件的元素生成Vector

函数

  • 函数定义def abs(x:Double) = if ( x > 0 ) x else -x,非递归函数省略返回类型
  • 函数不需要return语句,直接用表达式作为返回
  • 参数可以有默认值def hello(s:String = "default") = println(s),这里调用如果使用默认值,也是需要()的:hello()
  • 可变参数def sum(args:Int*) ={ var res = 0; for( i <- args ) res += i; res } 参数实际是Seq类型
    • 这里参数虽然是Seqsum(x)只有一个参数时,x必须是一个Int,如果要把Seq作为单个参数,需要写成sum(1 to 10 : _*)不要问我为什么。。
  • lazy变量,直到被使用时才会初始化:lazy val content = scala.io.Source.fromFile("/tmp/file.txt").mkString
  • 没有checked exception,异常处理语法如下
try {
    process(new URL("http://horstmann.com/fred-tiny.gif"))
} catch {
    case _: MalformedURLException => println("Bad URL: " + url)
    case ex: IOException => ex.printStackTrace()
}
  • try { ... } catch { ... } finally { ... }句式依旧可用
  • 函数定义可以嵌套
// 快速排序函数示例
def sort(xs: Array[Int]) {
    def swap(i: Int, j: Int) {
        val t = xs(i); xs(i) = xs(j); xs(j) = t
    }
    def sort1(l: Int, r: Int) {
        val pivot = xs((l + r) / 2)
        var i = l; var j = r
        while (i <= j) {
            while (xs(i) < pivot) i += 1
            while (xs(j) > pivot) j -= 1
            if (i <= j) {
                swap(i, j)
                i += 1
                j -= 1
            }
        }
        if (l < j) sort1(l, j)
        if (j < r) sort1(i, r)
    }
    sort1(0, xs.length - 1)
}
  • 函数式编程
def sort(xs: Array[Int]): Array[Int] = {
    if (xs.length <= 1) xs
    else {
        val pivot = xs(xs.length / 2)
        Array.concat(
            sort(xs filter (pivot >)), // def >(x: Byte): Boolean 操作符号也是function
            xs filter (pivot ==),
            sort(xs filter (pivot <)))
    }
}
  • 以函数作为参数的函数,或者返回函数的函数,被称为高阶函数(higher-order)
  • 可以给一个expression命名,expression的值只有到被使用时才会计算,可以认为是别名,效果实际上是替换{expression}
    • def h(i:Int):Int = { println("i = "+i); i }
    • def s = 3 * h(5) // 这里并不会打印出 i = 5,注意和val/var s = 3 * h(5)的区别
  • by name vs by value

书上也是说成call-by-value/name但其实理解下来应该描述成pass-by-value/name比较合适,看demo代码就清楚了

scala> def f(x:Int, y:Int) = { println("f is called!"); x+y }
f: (x: Int, y: Int)Int

scala> def by_value(x:Int) = x + x
by_value: (x: Int)Int

scala> def by_name(x: =>Int) = x + x // 注意冒号后面的空格,不可省略写成x:=>
by_name: (x: => Int)Int

scala> by_value(f(3,2)) // by_value好理解,就是先把函数调用的值计算出来,传给by_value
f is called!
res9: Int = 10

scala> by_name(f(3,2)) // by_name我的理解就是把x全部替换成f(3,2)的效果,类似说x是f(3,2)的别名
f is called!
f is called!
res10: Int = 10

// 利用这个特性可以做一些神奇的事情,比如自己实现一个while循环
scala> :paste
// Entering paste mode (ctrl-D to finish)

def _while(cond: =>Boolean)(f: =>Unit){
  if(cond){
    f
    _while(cond)(f)
  }
}

// Exiting paste mode, now interpreting.

_while: (cond: => Boolean)(f: => Unit)Unit

scala> var c=5
c: Int = 5

scala> _while(c>0){ println(c); c-=1 }
5
4
3
2
1

Pattern Matching

def fibonacci(in: Any): Int = in match {
    case 0 => 0
    case 1 => 1
    case n: Int if n > 1 => fibonacci(n - 1) + fibonacci(n - 2)
    case n: String => fibonacci(n.toInt)
    case _ => 0
}

Java 日志框架

Java 日志框架整理 Java Logging

日志框架对于 Java 程序员来说肯定不会陌生,而且第一印象估计都是 log4j,由于其配置&用法都相对简单,所以很容易忽略掉背后的日志框架,大部分的日志打印代码类似这样

import org.apache.log4j.Logger;

public class LoggingTest {
	private static final Logger logger = Logger.getLogger(LoggingTest.class);

	@Test
	public void test() {
	    logger.info("this is logging message");
	}
}

这么写有个不好的地方是,日志框架从此就绑定了 log4j,如果要使用别的日志框架,必须大面积的修改代码。更优雅的方案是结合 commons-logging 和 log4j,面向 commons-logging API 编程

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class SkeletonTest {
    private static Log log = LogFactory.getLog(SkeletonTest.class);
    @Test
    public void test(){
        log.info("this is log from commons-loggin API");
    }
}

我们测试发现这段代码的效果和上面是一样的,这是因为 commons-logging 框架设计了一种动态查找日志框架实现的机制,我们想要使用 log4j 的实现,只需要把 log4j 的 jar 包放在 classpath 下即可

首先,动态查找 LogFactory 实现类

  • 查找系统属性中的 org.apache.commons.logging.LogFactory 定义
  • 扫描 META-INF/services/org.apache.commons.logging.LogFactory 文件,加载里面的配置(JDK1.3中的服务发现机制)
  • 查找 commons-logging.properties 文件中的 org.apache.commons.logging.LogFactory 定义
  • 使用默认的 org.apache.commons.logging.impl.LogFactoryImpl 实现类

其次,动态查找 Log 实现类

  • 查找配置属性中的 org.apache.commons.logging.Log 定义
  • 查找系统属性中的 org.apache.commons.logging.Log 定义
  • 使用标准实现类的尝试加载顺序
/**
 * The names of classes that will be tried (in order) as logging
 * adapters. Each class is expected to implement the Log interface,
 * and to throw NoClassDefFound or ExceptionInInitializerError when
 * loaded if the underlying logging library is not available. Any 
 * other error indicates that the underlying logging library is available
 * but broken/unusable for some reason.
 */
private static final String[] classesToDiscover = {
        LOGGING_IMPL_LOG4J_LOGGER,
        "org.apache.commons.logging.impl.Jdk14Logger",
        "org.apache.commons.logging.impl.Jdk13LumberjackLogger",
        "org.apache.commons.logging.impl.SimpleLog"
};
Apache Commons Logging
log4j
slf4j
logback
http://en.wikipedia.org/wiki/Java_logging_framework