缺少功能的目录。

中断并继续循环

每次我看到这样的代码:

while(cond1) {
    work();
    if(cond2)
        continue;
    rest();
}

我感觉好像是一个人写的,他确实错过了goto尚未被认为有害的时代。 举起手来寻找该版本的读者:

while(cond1) {
    work();
    if(!cond2)
        rest();
}

摆脱中断还需要更多,但是通常将循环提取到单独的方法/函数(或至少将其放在现有方法的末尾)并使用return可以解决问题。 为何Scala允许您在其他函数中定义函数,所以您不会使用仅使用一次的大量小方法来污染全局类名称空间-当认真地提取Java中的方法时,有时会出现此问题。

中断并继续

数组

长度

String[] array = new String[10];
List<string> list = Arrays.asList(array);
String[] array2 = list.toArray(new String[list.size()]);

从集合到数组的转换是我最喜欢的Java习惯用法…为什么不仅具有相同的语法,相同的方法,相同的抽象,多态的行为—而且只有不同的实现名称?

val array = Array("ab", "c", "def")
println(array(1))
array filter (_.size > 1)

val list = List("ab", "c", "def")
println(list(1))
println(list filter (_.size > 1))

不用担心,Scala编译器将在后台使用与在Java中使用普通数组相同的有效数组字节码。 没有魔术抽象和多层包装。

原语

(也称为:这到底会引发NullPointerException吗?

在Scala中,您不再具有选择权,每个基本类型都是一个对象,而大多数时候大多数情况下仍是内存和字节码中的一个基本类型。 那怎么可能? 看下面的流行示例:

val x = 37     //x and y are objects of type Int
val y = 5
val z = x + y  //x.+(y) - yes, Int class has a "+" method
assert(z.toString == "42")

x,y和z是Int类型的实例。 它们都是对象,即使在语义上加两个整数也是x上带有y参数的方法+。 如果您认为它必须非常出色地执行-再次在幕后将其编译为普通的原始加法。 但是现在您可以轻松地在集合中使用原语,在需要任何类型(Java中的Object,Scala中的Any)时传递它们,或者简单地创建文本表示形式而无需笨拙的Integer.toString(7)习惯用法。 过多的不良习惯。

检查异常

我几乎不会错过的另一个功能。 这里没有太多要说的。 除了Java之外,没有任何主流语言都提供它们,也没有任何主流JVM语言(Java除外)。 这个主题仍然是一个有争议的话题,但是,如果您曾经尝试处理无处不在的SQLException或IOException,您就会知道它引入了多少样板而没有充分的理由。 无论如何,请看下面的示例…

介面

这个好! Scala没有接口。 相反,它引入了特性-抽象类(某些特性方法可能具有实现)和接口(一个接口可以混入多个特性)之间的某种特性。 因此,本质上,特征使您可以实现多重继承,同时避免可怕的钻石问题 。 如何完成这项工作需要自己撰写一篇文章(简而言之:最后一个特征胜出),但我想向您展示一个示例,说明如何利用特征减少重复。

假设您正在编写抽象二进制协议的接口。 大多数实现都采用原始字节数组,因此在Java中您可以简单地说:

public interface Marshaller {
    long send(byte[] content);
}

从实现的角度来看,这很棒–只需实现一个方法就可以抽象了。 但是,该界面的用户抱怨它很麻烦并且不太方便。 他们想发送字符串,二进制和文本流,序列化的对象等。 他们可以围绕此界面创建外观(每个用户将使用一组独特的错误来创建自己的界面),也可以强制API的作者对其进行扩展:

public interface Marshaller {

    long send(byte[] content);

    long send(InputStream stream);

    long send(Reader reader);

    long send(String s);

    long send(Serializable obj);

}

现在,API轻而易举,但是每个实现都必须实现五个方法,而不是一个。 还要注意,由于大多数抽象协议都是基于字节数组的,因此所有方法都可以按照第一个方法实现。 而且只有第一个包含实际的编组代码。 反过来,这导致每个实现都具有完全相同的四种方法-复制并没有消失-它只是被移动了。 实际上,这个问题被称为瘦与富接口,并且在Scala的 《 Programming》一书中对此进行了描述。 我通常要做的是为服务提供者提供一个抽象类,其中包含除根目录以外的所有方法的典型实现,而根目录被所有其他方法使用:

import org.apache.commons.io.IOUtils;

public abstract class MarshallerSupport implements Marshaller {

    @Override
    public abstract long send(byte[] content);

    @Override
    public long send(InputStream stream) {
        try {
            return send(IOUtils.toByteArray(stream));
        } catch (IOException e) {
            throw new RuntimeException(e);  //choose something more specific in real life
        }
    }

    @Override
    public long send(Reader reader) {
        try {
            return send(IOUtils.toByteArray(reader));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public long send(String s) {
        try {
            return send(s.getBytes("UTF8"));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public long send(Serializable obj) {
        try {
            final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
            new ObjectOutputStream(bytes).writeObject(obj);
            return send(bytes.toByteArray());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

}

MarshallerSupport子类化并实现您需要的内容即可。 但是,如果您的接口实现还必须继承某些其他类怎么办? 那你真不走运 但是,在Scala中,您将接口更改为特征,从而打开了混入其他特征(在扩展和实现之间进行思考)的可能性。 顺便说一句,您还记得我说过的关于检查异常的内容吗?

trait MarshallerSupport extends Marshaller {

    def send(content: Array[Byte]): Long

    def send(stream: InputStream): Long = send(IOUtils.toByteArray(stream))

    def send(reader: Reader): Long = send(IOUtils.toByteArray(reader))

    def send(s: String): Long = send(s.getBytes("UTF8"))

    def send(obj: Serializable): Long = {
        val bytes = new ByteArrayOutputStream
        new ObjectOutputStream(bytes).writeObject(obj)
        send(bytes.toByteArray)
    }
}

切换语句

Scala中没有switch语句。 调用模式匹配更好的开关将是亵渎神灵。 不仅因为Scala中的模式匹配是一个返回值的表达式,而且还不是因为您可以根据需要切换字面值。 甚至不是因为没有失败,突破和违约。 这是因为Scala的模式匹配使您甚至可以使用通配符来匹配整个对象结构和列表。 考虑这种表达简化方法,该方法最初取自已经在Scala书中提到的Programming :

abstract class Expr
case class Var(name: String) extends Expr
case class Number(num: Double) extends Expr
case class UnOp(operator: String, arg: Expr) extends Expr
case class BinOp(operator: String, left: Expr, right: Expr) extends Expr

//...

def simplify(expr: Expr): Expr = expr match {
    case UnOp("-", UnOp("-", e)) => e  //double negation
    case BinOp("+", e, Number(0)) => e //adding zero
    case BinOp("*", e, Number(1)) => e //multiplying by one
    case _ => expr
}

仔细看一下这段代码有多聪明! 如果我们的表达式是一元“-”运算,并且参数是第二个一元“-”运算,且任何表达式e为参数(请考虑:-(-e)),则只需返回e即可。 如果发现此模式匹配示例难以阅读,请查看大致等效的Java代码。 但是请记住:大小无关紧要(Perl单行代码可能会做同样的事情)–它与可读性和可维护性有关:

public Expr simplify(Expr expr) {
    if (expr instanceof UnOp) {
        UnOp unOp = (UnOp) expr;
        if (unOp.getOperator().equals("-")) {
            if (unOp.getArg() instanceof UnOp) {
                UnOp arg = (UnOp) unOp.getArg();
                if (arg.getOperator().equals("-"))
                    return arg.getArg();
            }
        }
    }
    if (expr instanceof BinOp) {
        BinOp binOp = (BinOp) expr;
        if (binOp.getRight() instanceof Number) {
            Number arg = (Number) binOp.getRight();
            if (binOp.getOperator().equals("+") && arg.getNum() == 0 ||
                    binOp.getOperator().equals("*") && arg.getNum() == 1)
                return binOp.getLeft();
        }
    }
    return expr;
}

实例/广播

instanceof和downcast的内置语法。 相反,该语言为您提供了有关实际对象的方法:

val b: Boolean = expr.isInstanceOf[UnOp]
val unOp: UnOp = expr.asInstanceOf[UnOp]

在Scala中,通常被视为语言一部分的许多功能实际上是在语言本身中实现的,或者至少它们不需要特殊的语法。 我喜欢这个想法,事实上,我发现Ruby创建对象的方法(Foo.new –方法而不是new运算符)非常有吸引力,甚至缺少在Smalltalk中的条件是否需要注意的情况 。

枚举

Scala没有对枚举的内置支持。 众所周知,Java中的枚举具有一些精美的功能,其他语言羡慕这些功能,例如类型安全性和向每个枚举添加方法的能力。 在Scala中至少有两种方法来模拟枚举:

object Status extends Enumeration {
   type Status = Value

   val Pending = Value("Pending...")
   val Accepted = Value("Accepted :-)")
   val Rejected = Value("Rejected :-(")
}

assume(Status.Pending.toString == "Pending...")
assume(Status.withName("Rejected :-(") == Status.Rejected)

或者,如果您不关心文本枚举表示形式:

object Status extends Enumeration {
   type Status = Value

   val Pending, Accepted, Rejected = Value
}

但是,模拟枚举的第二种也是最全面的方法是使用用例类。 旁注:name实际上是基类中定义的抽象方法。 当您声明一个方法而不定义方法主体时,它被隐式假定为抽象–无需使用额外的关键字标记明显的对象:

sealed abstract class Status(val code: Int) {
 def name: String
}

case object Pending extends Status(0) {
 override def name = "?"
}

case object Accepted extends Status(1) {
 override def name = "+"
}

case object Rejected extends Status(-1) {
 override def name = "-"
}

//...

val s: Status = Accepted

assume(s.name == "+")
assume(s.code == 1)

s match {
    case Pending =>
    case Accepted =>
    case Rejected =>  //comment this line, you'll see compiler warning
}

这种方法虽然与枚举本身无关,但具有许多优点。 最大的问题是,在执行非穷举模式匹配时,编译器会警告您–思考:切换Java中的枚举而不显式引用每个值或默认块。

静态方法/字段

object关键字定义一个类时,Scala运行时会急切地创建该类的一个实例,并使其在类名下可用。 这本质上是语言内置的单例模式,但最重要的是这种方法引入的思维方式转变。 而不是在类(在这种情况下只是事实上的名称空间)中人为地聚集在一起的一堆静态函数,而是拥有带有真实方法的单例:

sealed abstract class Status
case object Pending extends Status
case object Accepted extends Status
case object Rejected extends Status

case class Application(status: Status, name: String)

object Util {

    def groupByStatus(applications: Seq[Application]) = applications groupBy {_.status}

}

这是语法的工作方式(以及出色的ScalaTest DSL示例):

@RunWith(classOf[JUnitRunner])
class UtilTest extends FunSuite with ShouldMatchers {

   type ? = this.type

   test("should group applications by status") {
      val applications = List(
         Application(Pending, "Lorem"),
         Application(Accepted, "ipsum"),
         Application(Accepted, "dolor")
      )

      val appsPerStatus = Util.groupByStatus(applications)

      appsPerStatus should have size (2)
      appsPerStatus(Pending) should (
            have size (1) and
            contain (Application(Pending, "Lorem"))
      )
      appsPerStatus(Accepted) should (
            have size (2) and
            contain (Application(Accepted, "ipsum")) and
            contain (Application(Accepted, "dolor"))
      )
   }
}

volatile / transient / native和serialVersionUID不见了

serialVersionUID转换为类级别的注解是一个不错的选择。 我知道这个领域早在Java语言引入注解之前就已经存在,因此我们不应该怪它。 但是我总是讨厌当使用静态类型的语言时,某些名称/字段具有特殊含义,除了语言规范本身( 魔术数字? )以外,其他地方都没有体现出来不幸的是,在Scala中也有这种不愉快行为的示例,即对apply()方法的特殊处理和以冒号结尾的方法。 太糟糕了。

前/后增量

i ++++ i 。 期。 您需要更详细的i + = 1 –更糟糕的是,该表达式返回Unit (认为: void

您有两个大小相同的数组:一个带有名称,另一个带有年龄。 现在,您要显示每个名称的相应年龄-以某种方式并行遍历两个数组。 在Java中,很难实现干净整洁:

String[] names = new String[]{"Alice", "Bobby", "Eve", "Jane"};
Integer[] ages = new Integer[]{27, 31, 29, 25};

int curAgeIdx = 0;
for (String name : names) {
    System.out.println(name + ": " + ages[curAgeIdx]);
    ++curAgeIdx;
}

//or:

for(int idx = 0; idx < names.length; ++idx)
    System.out.println(names[idx] + ": " + ages[idx]);
}

在Scala中,它可能更短,但一开始非常神秘:

var names = Array("Alice", "Bobby", "Eve", "Jane")
var ages = Array(27, 31, 29, 25)

names zip ages foreach {p => println(p._1 + ": " + p._2)}

邮编

$ scala
scala> Array("one", "two", "three") zip Array(1, 2, 31)   
res1: Array[(java.lang.String, Int)] = Array((one,1), (two,2), (three,31))

压缩

Scala的发明者对Java语言非常了解,他们不仅添加了语法糖(例如函数文字或隐式转换)。 他们在Java中发现了许多不一致和烦恼的地方,摆脱了它们,并提供了更简洁,更刻意的替换。 尽管像原始对象和数组对象这样的高级结构,在引擎盖下仍会生成相同的快速直接的字节码。

参考: Scala中删除了Java的哪些功能? 来自我们的JCG合作伙伴Tomek Nurkiewicz在NoBlogDefFound

编码愉快! 不要忘记分享!

相关文章:

  • Java最佳实践系列
  • 正确记录应用程序的10个技巧
  • 每个程序员都应该知道的事情
  • 生存在狂野西部开发过程中的9条提示
  • 软件设计法则
  • Java Fork / Join进行并行编程