DHTML
在所有情况下,Java仍然代表着我学习和判断其他语言的基准。 这是语言的一些有趣特征,我发现这些特征来自Java背景,颇具挑战性。
JavaScript:原型
JavaScript是我必须与Java一起使用的第一语言。 尽管JavaScript在过去的这些年里一直在发展,但其中一个共同的功能却非常奇怪地实现了:新对象的实例化。
在Java中,首先创建一个类 :
public class Person {
private final String name ;
private final LocalDate birthdate ;
public Person ( String name , LocalDate birthdate ) {
this . name = name ;
this . birthdate = birthdate ;
}
public String getName () {
return name ;
}
public LocalDate getBirthdate () {
return birthdate ;
}
}
然后,可以继续创建该类的实例 :
var person1 = new Person ( "John Doe" , LocalDate . now ());
var person2 = new Person ( "Jane Doe" , LocalDate . now ());
JavaScript与Java语法非常相似:
class Person {
constructor ( name , birthdate ) {
this . name = name ;
this . birthdate = birthdate ;
}
}
let person1 = new Person ( " John Doe " , Date . now ());
let person2 = new Person ( " Jane Doe " , Date . now ());
平行线在那里停止。 得益于JavaScript动态的特性,可以向现有实例添加属性和函数。
person1 . debug = function () {
console . debug ( this );
}
person1 . debug ();
但是,这些仅添加到实例。 其他实例缺少这些补充:
person2 . debug (); // Throws TypeError: person2.debug is not a function
为了向现在和将来的所有实例添加函数(或属性),需要利用原型的概念:
Person . prototype . debug = function () {
console . debug ( this );
}
person1 . debug ();
person2 . debug ();
let person3 = new Person ( " Nicolas " , Date . now ());
person3 . debug ();
Kotlin:扩展功能/属性
几年前,我尝试自学Android。 我发现这种体验并不十分适合开发人员:当然,我了解目标之一是尽可能减小占用空间,但这是以非常简洁的API为代价的。
我记得必须调用带有很多参数的方法,其中大多数为null 。 我试图找到一种解决该问题的方法...并找到Kotlin的扩展属性-具有默认参数。 我停止了学习Android的道路,但继续使用Kotlin。
我爱Kotlin。 许多人赞扬Kotlin的零安全方法。 我喜欢它,但对我来说,最高的价值在于其他地方。
假设我们经常需要大写字符串。 在Java中实现该目标的方法是使用静态方法创建一个类:
public class StringUtils {
public static String capitalize ( String string ) {
var character = string . substring ( 0 , 1 ). toUpperCase ();
var rest = string . substring ( 1 , string . length () - 1 ). toLowerCase ();
return character + rest ;
}
}
在早期,没有一个没有StringUtils和DateUtils类的项目。 幸运的是,现在,现有的库提供了最需要的功能, 例如 Apache Commons Lang和Guava 。 但是,它们遵循基于静态方法的相同设计原理。 令人遗憾的是,因为Java被认为是一种OOP语言。 不幸的是,静态方法不是面向对象的。
Kotlin允许在扩展函数和属性的帮助下向现有类分别添加行为。 语法非常简单,并且与面向对象的方法完全兼容:
fun String . capitalize (): String {
val character = substring ( 0 , 1 ). toUpperCase ()
val rest = substring ( 1 , length - 1 ). toLowerCase ()
return character + rest
}
在编写Kotlin代码时,我经常使用它。
在后台,Kotlin编译器生成的字节码类似于Java代码中的字节码。 它只是“唯一的”语法糖,但是从设计的角度来看,与Java代码相比,这是一个巨大的改进!
转到:隐式接口实现
在大多数OOP语言(Java,Scala,Kotlin等)中,类可以遵守合同,也称为接口 。 这样,客户端代码可以引用该接口,而不关心任何特定的实现。
public interface Shape {
float area ();
float perimeter ();
default void display () {
System . out . println ( this );
System . out . println ( perimeter ());
System . out . println ( area ());
}
}
public class Rectangle implements Shape {
public final float width ;
public final float height ;
public Rectangle ( float width , float height ) {
this . width = width ;
this . height = height ;
}
@Override
public float area () {
return width * height ; (1)
}
@Override
public float perimeter () {
return 2 * width + 2 * height ; (1)
}
public static void main ( String ... args ) {
var rect = new Rectangle ( 2.0f , 3.0f );
rect . display ();
}
}
BigDecimal
- 来达到精确的目的-但这不是重点
重要的一点是:由于Rectangle实现Shape ,因此可以在Rectangle任何实例上调用Shape上定义的display()方法。
Go不是一种OOP语言:它没有类的概念。 它提供结构 ,并且功能可以与这种结构相关联。 它还提供了结构可以实现的接口 。
但是,Java实现接口的方法是明确的 : Rectangle类声明其实现Shape 。 相反,Go的方法是隐式的 。 实现接口所有功能的结构隐式实现此接口。
这将转换为以下代码:
package main
import (
"fmt"
)
type shape interface { (1)
area () float32
perimeter () float32
}
type rectangle struct { (2)
width float32
height float32
}
func ( rect rectangle ) area () float32 { (3)
return rect . width * rect . height
}
func ( rect rectangle ) perimeter () float32 { (3)
return 2 * rect . width + 2 * rect . height
}
func display ( shape shape ) { (4)
fmt . Println ( shape )
fmt . Println ( shape . perimeter ())
fmt . Println ( shape . area ())
}
func main () {
rect := rectangle { width : 2 , height : 3 }
display ( rect ) (5)
}
shape
- 界面
rectangle
- 结构
shape
- 函数都添加到
rectangledisplay()
- 函数仅接受
shaperectangle
- 实现了
shape
- 所有功能,并且由于隐式实现,所以
rect
- 也是
shape
- 。 因此,调用
display()
- 函数并将
rect
- 作为参数传递是完全合法的
Clojure:“依赖类型”
我以前的公司在Clojure投入了很多资金。 因此,我尝试学习该语言,甚至写了几篇文章来总结我对语言的理解。
Clojure在很大程度上受到LISP的启发。 因此,表达式用括号括起来,要执行的功能首先位于它们的内部。 另外,Clojure是一种动态类型化的语言:虽然有类型,但它们并未声明。
一方面,该语言提供了基于合同的编程。 可以指定前置条件和后置条件:它们在运行时进行评估。 这样的条件可以进行类型检查, 例如,参数是字符串,布尔值等吗? -甚至可以更进一步,类似于_dependent类型 :
在计算机科学和逻辑中,从属类型是其定义取决于值的类型。 “整数对”是一种类型。 由于对值的依赖性,“第二对大于第一对的整数对”是从属类型。
—维基百科 https://zh.wikipedia.org/wiki/Dependent_type
它是在运行时强制执行的,因此不能真正称为依赖类型。 但是,这是我所涉猎的语言中最接近的一种。
之前,我曾详细地写过一个依赖于岗位的类型和基于契约的程序。
长生不老药:模式匹配
一些语言自称为提供模式匹配功能。 通常,模式匹配用于评估变量, 例如在Kotlin中:
var statusCode : Int
val errorMessage = when ( statusCode ) {
401 -> "Unauthorized"
403 -> "Forbidden"
500 -> "Internal Server Error"
else -> "Unrecognized Status Code"
}
此用法是类固醇的转换语句。 但是,通常,模式匹配的应用范围更加广泛。 在以下代码段中,将检查第一个常规的HTTP状态错误代码,如果未找到,则默认为更通用的错误消息:
val errorMessage = when {
statusCode == 401 -> "Unauthorized"
statusCode == 403 -> "Forbidden"
statusCode - 400 < 100 -> "Client Error"
statusCode == 500 -> "Internal Server Error"
statusCode - 500 < 100 -> "Server Error"
else -> "Unrecognized Status Code"
}
不过,它是有限的。
Elixir是一种在Erlang OTP上运行的动态类型化语言,将模式匹配提升到了一个全新的水平。 Elixir的模式匹配可用于简单的变量解构:
{ a , b , c } = { :hello , "world" , 42 }
将为a分配:hello ,为b “ world”分配c 。
它也可以用于集合的更高级解构:
[ head | tail ] = [ 1 , 2 , 3 ]
head将被分配为1,而tail将被分配为[2, 3] 。
然而,对于函数重载甚至更是如此。 作为一种功能语言,Elixir没有用于循环的关键字( for或while ):循环需要使用递归来实现。
例如,让我们使用递归来计算List的大小。 在Java中,这很容易,因为有一个size()方法,但是Elixir API没有提供这种功能。 让我们以Elixir方式伪代码实现它-使用递归。
public int lengthOf ( List <?> item ) {
return lengthOf ( 0 , items );
}
private int lengthOf ( int size , List <?> items ) {
if ( items . isEmpty ()) {
return size ;
} else {
return lengthOf ( size + 1 , items . remove ( 0 ));
}
}
这可以几乎逐行转换为Elixir:
def length_of ( list ), do : length_of ( 0 , list )
defp length_of ( size , list ) do
if [] == list do
size
else
[ _ | tail ] = list (1)
length_of ( size + 1 , tail )
end
end
_
- 变量,这意味着以后无法引用它-因为它没有用。
但是,如前所述,Elixir模式匹配也适用于函数重载。 因此,写Elixir的名义方法是:
def list_len ( list ), do : list_len ( 0 , list )
defp list_len ( size , []), do : size (1)
defp list_len ( size , list ) do (2)
[ _ | tail ] = list
list_len ( size + 1 , tail )
end
- 如果列表为空,则调用此函数
- 否则调用此函数
请注意,模式是按照声明的顺序进行评估的:在上面的代码段中,Elixir首先评估具有空列表的函数,并且仅在不匹配的情况下评估第二个函数, 即列表不为空。 如果要以相反的顺序声明函数,则每次都会在非空列表上进行匹配。
Python:理解
Python是一种动态类型的趋势语言。 与Java中一样,Python通过for关键字提供循环。 以下片段循环遍历集合中的所有项目,并逐一打印它们。
for n in [ 1 , 2 , 3 , 4 , 5 ]:
print ( n )
要收集新集合中的所有项目,可以创建一个空集合,然后将每个项目添加到循环中:
numbers = []
for n in [ 1 , 2 , 3 , 4 , 5 ]:
numbers . append ( n )
print ( numbers )
但是,可以使用漂亮的Python功能: 进行理解 。 尽管它与标准循环使用相同的for关键字,但是for comprehension是实现相同结果的功能构造。
numbers = [ n for n in [ 1 , 2 , 3 , 4 , 5 ]]
print ( numbers )
上一个代码段的输出为[1, 2, 3, 4, 5] 。
也可以变换每个项目。 例如,以下代码片段将计算每个项目的平方:
numbers = [ n ** 2 for n in [ 1 , 2 , 3 , 4 , 5 ]]
print ( numbers )
输出[1, 4, 9, 16, 25] 。
理解的好处是可以使用条件句。 例如,以下代码段将仅过滤偶数项,然后对其平方:
numbers = [ n ** 2 for n in [ 1 , 2 , 3 , 4 , 5 ] if n % 2 == 0 ]
print ( numbers )
输出为[4, 16] 。
最后,为了理解,允许使用笛卡尔积。
numbers = [ a : n for n in [ 1 , 2 , 3 ] for a in [ 'a' , 'b' ]]
print ( numbers )
将会输出[('a', 1), ('b', 1), ('a', 2), ('b', 2), ('a', 3), ('b', 3)] 。
上面的理解也称为列表理解,因为它们旨在创建新列表。 地图理解非常相似,旨在创建...地图。
结论
尽管Java是一门不断发展的语言,但这是一件好事。 但是,其他语言中发现的方法也值得研究。 请记住,一种语言构成了人们对问题的思考方式以及解决方案的设计方式。 学习或至少熟悉其他语言是考虑其他观点的好方法。
更进一步:
- JavaScript:Object.prototype
- Kotlin:扩展
- 开始:接口是隐式实现的
- Clojure:规格
- Elixir:模式匹配
- Python:列表推导
翻译自: https://blog.frankel.ch/six-interesting-features-programming-languages/