缺少功能的目录。
中断并继续循环
每次我看到这样的代码:
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进行并行编程