龙空技术网

Java语言的品味(一)

多肉小主 167

前言:

而今大家对“c语言如何调用sin函数”可能比较看重,兄弟们都想要学习一些“c语言如何调用sin函数”的相关知识。那么小编在网摘上搜集了一些有关“c语言如何调用sin函数””的相关知识,希望咱们能喜欢,我们快快来学习一下吧!

不用进行深入地学习,只要是听说过Java的人,都知道Java是一门面向对象的语言。其实面向对象往宽泛了说,应该是一种编程思想,它是一种思维方式。而面向对象的编程语言则是指,编程语言提供了一种机制,使得我们可以用比较简洁的语法表达数据的抽象和封装。例如,PostgreSQL这个数据库就是用C编写的,但是它的源代码却很有趣,用struct 定义了很多结构体,然后用struct的嵌套模拟了类的继承(要知道,在C++中,struct甚至都可以和class换用),而对象的布局在结构体里也显得更加清晰。这也是一种面向对象的编程方式,但我们却不认为C是一种面向对象的编程语言,这是因为C并不提供方便的语法让我们轻松地使用面向对象编程。而C++, Java, Python这些语言则提供了class关键字来进行类的定义,我们会说这些语言是面向对象的。

我们知道,C++里是可以定义全局变量,全局函数的。很多脚本语言,虽然是面向对象,但仍然可以在类定义之外声明变量,定义函数,执行操作。举个最简单的例子,我们看几种语言的hello world

// C++的写法int main() { cout << "hello world" << endl; return 0;}# Python 的写法print "hello world"// Scala的写法println("hello world")/* Java 的写法 */public class Main { public static void main(String args[]) { System.out.println("hello world"); }}

很明显,Java最啰嗦,C++次之,python和scala就都比较简洁。从这个简单的例子就能看出来语言的不同品味。python和scala倾向于灵活,一个具体的操作可以脱离函数实体而直接运行。C++就会严谨一些,所有的动作都必须被封装到函数中去,然后通过执行一个函数来做一些事情(比如往控制台打印字符)。而Java的要求就更严格了,Java要求语言中,所有的函数都必须定义在一个类里,函数不能脱离类而存在。

这样的规定使得Java显得死板,但有时候太过灵活,反而很难掌握。比如scala,内部分裂为两个流派,一派把scala做为Java的改进,另一派完全使用函数式编程,而不主张对Java进行改进。对于初学者,在还没掌握一门语言的全貌之前,就听各种“专家”,“大牛”大谈编程范式,相互鄙视,然后听了一堆似是而非的概念,其实是有害的。但是Java不同,语法的死板意味着更少的歧义。强对象使得Java成为了学习面向对象编程的最佳入门语言。

面向对象的不足

凡是有利必有弊,强对象编程,使得语法简单统一,但也有其缺点,而且有很多。我们在接下来的课程里会一点点接触到。我们今天先看第一个。

有些变量和函数确实没必要定义在一个类里。强行规定这些函数在类里,反而显得累赘。想一个例子,比如正弦函数sin,常数PI,这些函数或者常量值为什么要定义在类里呢?一定要定义的话,定义在哪个类里合适呢?

Java的做法是把数学函数封装到一个叫做Math的类里。叹气...一个叫Math的类,太不直观了。再来思考一个问题,如果说,我们这样写

class Math { public double sin(double x) {/*code goes here*/}}

那么,我每次要调用sin函数都得写成

new Math().sin(x)

因为,我们只能通过Math类型的对象去调用定义在Math类中的函数。这太不科学了。为了调用一个本来可以全局存在的函数,我们却要新建一个对象?!语言的设计者肯定也不会这么傻。于是,他们引入了static这个关键字。

Static关键字

当我们把一个函数或者变量加上static限制以后,就可以在不创建一个对象的情况下,直接使用类里的函数或者变量了。

class Math { public static double sin(double x) {/*code goes here*/}}Math.sin(x)

编程语言的关键字是有它内在的逻辑的,不要去死记硬背,通过上面的分析,我们就能知道static关键字用于修饰变量和函数是不得不这样做,而不是大家闲得慌,去加这么一个关键字故意去难为新手们。

好了,有了static关键字,世界好像变得合理了一点。但是做为语言设计者,还是不满意,如果我有一个程序,里面会用到大量的数学函数,然后我就看到了满屏幕的。要是能把这个Math去掉就好了。然后设计者们就把主意打到了import那里。我们能不能把这些本来就是全局函数,全局常量的值导入到当前文件中,好像他们本身就没有被封装到类里一样?于是这种语法就出现了:

import static java.lang.Math.*;public class Main { public static void main(String args[]) { System.out.println(sin(PI / 2)); }}

好了,通过这种方法,就把Math中的所有static方法和常量都引入到当前文件了,我们再也不用使用Math开头去引用一个静态函数了。

其实static的语义到这一步就基本说清楚了。如果我是Java的设计者,我不会再为static增加其他用法了。但是不幸的是,Java有一个坏品味,那就是重用关键字。这让一些本来简单的事情又变复杂了。

static关键字还可以用来定义静态代码块。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。

这种与类加载有关的逻辑,显然应该甩锅给ClassLoader。比如 ClassLoader在加载一个类的时候,调用类中的static onLoad()方法,也比静态代码块这种引起混淆的语法好。这样的设计才会显得清晰。静态代码块是什么鬼!?这显然是一个关键字复用的错误例子。我们没办法改变这种设计,只能去适应。记住这个用法吧。

static 函数里不能使用this, super?废话!你只要明白了static 函数其实是全局函数,它只是因为Java的强对象的要求,而不得不找个类“挂靠”,它本身与任何类都没有关系。所以在static 方法里,当然不能直接访问某一个类的成员变量和成员函数了。但是呢,一个类让一个static函数挂靠了,总得有点好处吧?要说好处,倒也有一个,那就是类的成员函数调用static方法不用带类名了。

class Example { public static void sayHello() { System.out.println("Hello, everybody~"); // 这个当然不能用。static函数与其挂靠的那个类的对象没有任何关系。 // static函数是全局唯一的。 // this.sayBye(); }  public void sayBye() { System.out.println("Good Bye~"); }  public void saySomething() { // 唯一的一点好处,大概就是成员函数里这样三种写法都是OK的。 // 但这个没卵用。我更喜欢Java只保留第三种写法,免得大家误会。 this.sayHello(); sayHello(); Example.sayHello(); this.sayBye(); }}
final关键字

final 用于修饰一个类,那么这个类就不能再被其他类继承。用于修饰一个方法,这个方法不能被覆写。这是非常好的一个东西。可以避免我们造出混乱的继承结构。比如Java中的String类就是final的,它是不能被继承的。

// 想创建一个自己的String类是不行的。因为String是final的。class MyString extends String {}

再看修饰method的情况:

class A {  public final void f() { }  // 这里是OK的,只是一次重载 public final void f(int a) { } }class B extends A {  // 会报错,说f是final的,不能覆写 public void f() { } }

好。到此为止,final是如此地清晰。

但不幸的是,Java的设计者不知道是出于什么考虑,把final也拿来定义变量。这就让人无语了。这是const关键字做的事情啊。重用关键字决不会让语法变得更简洁。

对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。

而且,事实证明,Java的final和C++的const还真就是同样的。就连一些容易混淆的地方都原封不动地迁移过来。

我们挨着看。先看基本用法。

 final int a = 1; a += 2; // 会报错,我们不能修改一个final变量

再看一下容易引起混乱的地方。

public class Hello { public static void main(String args[]) { String a = "go die, "; final String b = "final"; String c = "go die, " + b; String d = a + b; String e = "go die, final"; System.out.println(e == c); //true,比较两个变量是否指向同一个对象 System.out.println(e == d); //false System.out.println(c.equals(d));//true,比较两个字符串的值是否相同 } }

结果可能出乎你的意料。我来解释一下。在编译阶段,变量c其实已经是"go die, final"了,等到我们后面分析Java字节码文件的时候就会看到,c和e是指向了常量池中的同一个字符串,也就是“go die, final"。所以它们其实是同一个对象。但是d却是运行时生成的,并不引用常量池中的"go die, final"这个字符串,所以,e和d并不是同一个对象,虽然它们的值相同。这和C++中编译时const变量转成编译时常量如出一辙。究其根本原因,还是在于b 在编译阶段就已经被当作常量“final” 去做下面的编译了。

final关键字被用来当做const用,实在不是个好的品味。当然,const这个关键字是保留字,就是说在Java中虽然现在没用,但不保证以后不会用。你是不能拿这个词来当变量名的。

额外加一句,如果一个变量在整个执行阶段不会被修改,那么加上final进行修饰是一个好的编程习惯。

好了。我们今天通过static 和 final 这两个关键字来初步了解了一下Java的设计品味。语言的设计品味是一个很大的话题。我们会就这个话题继续我们的课程。

标签: #c语言如何调用sin函数