Java是完全面向对象的语言。Java通过虚拟机的运行机制,实现“跨平台”的理念。我在这里想要呈现一个简单的入门教程,从最简单的“Hello World!”程序见到面向对象的基本概念。希望对你有用。
"Hello World!"
先来看一个HelloWorld.java程序。这个程序在屏幕上打印出一串字符"Hello World!":
public class HelloWorld
{
public static void main(String[] args)
{
System.out.println("Hello World!");
}
}
程序中包括Java的一些基本特征:
- 类(class):上面程序定义了一个类HelloWorld,该类的名字与.java文件的名字相同。
- 方法(method):类的内部定义了该类的一个方法main。
- 语句(statement):真正的“打印”功能由一个语句实现,即: System.out.println("Hello World!");
下面两点有关Java的书写方式:
- Java中的语句要以;结尾 (与C/C++相同)。
- 用花括号{}来整合语句,形成程序块。通过程序块,我们可以知道程序的不同部分的范围,比如类从哪里开始,到哪里结束。
编译与运行
Java程序要经过编译器编译才能执行。在Linux或Mac下,可以下载安装Java JDK。我们使用javac来编译。在命令行中输入下面语句编译:
$javac HelloWorld.java
当前路径下,将有一个名为HelloWorld.class的文件生成。
使用java命令来运行。Java会搜寻该类中的main方法,并执行。
$java HelloWorld
变量
计算机语言通常需要在内存中存放数据。编程语言可以用变量(variable)的方式来往内存中读写数据。Java也有变量。Java的变量在使用前,要声明变量的类型。变量占据一定的内存空间。不同类型的变量占据不同的大小。Java中的变量类型如下:
存储大小 例值 注释
byte 1byte 3 字节
int 4bytes 3 整数
short 2bytes 3 短整数
long 8bytes 3 长整数
float 4bytes 1.2 单精度浮点数
double 8bytes 1.2 双精度浮点数
char 2bytes 'a' 字符
boolean 1bit true 布尔值
在Java中,变量需要先声明(declare)才能使用。在声明中,我说明变量的类型,赋予变量以特别名字,以便在后面的程序中调用它。你可以在程序中的任意位置声明变量。 比如:
public class Test
{
public static void main(String[] args)
{
System.out.println("Declare in the middle:");
int a;
a = 5;
System.out.println(a); // print an integer
}
}
上面a是变量名。可以在声明变量的同时,给变量赋值,比如 int a = 5。 “变量”的概念实际上来自于C语言那样的面向过程语言。在Java中,所谓的变量实际上是“基本类型” (premitive type)。我将在类的讲解中更多深入。
上面的程序还可以看到,Java中,可用//引领注释。
数组
Java中有数组(array)。数组包含相同类型的多个数据。我用下面方法来声明一个整数数组:
int[] a;
在声明数组时,数组所需的空间并没有真正分配给数组。我可以在声明的同时,用new来创建数组所需空间:
int[] a = new int[100];
这里创建了可以容纳100个整数的数组。相应的内存分配也完成了。
我还可以在声明的同时,给数组赋值。数组的大小也同时确定。
int[] a = new int[] {1, 3, 5, 7, 9};
使用int[i]来调用数组的i下标元素。i用来表示目标元素在数组中的位置,从0开始。其他类型的数组与整数数组相似。
表达式
表达式是变量、常量和运算符的组合,它表示一个数据。1 + 1是常见的表达式。再比如:
public class Test
{
public static void main(String[] args)
{
System.out.println("Declare in the middle:");
int a;
a = 5 + 1;
System.out.println(a); // print an integer }
}
上面的5 + 1也是一个表达式,等于6。
1)数学表达式
数学运算,结果为一个数值:
1 + 2 加法
4 - 3.4 减法
7 * 1.5 乘法
3.5 / 7 除法
7 % 2 求余数
2)关系表达式
判断表达式是否成立。结果为boolean值,即真假值:
a > 4.2 大于
3.4 >= b 大于等于
1.5 < 9 小于
6 <= 1 小于等于
2 == 2 等于
2 != 2 不等于
3)布林表达式
两个boolean值的与、或、非的逻辑关系。结果是boolean值:
true && false and
(3 > 1) || (2 == 1) or
!true not
4)位运算
对整数的二进制形式逐位进行逻辑运算,得到一个整数:
& and
| or
^ xor
~ not
5 << 3 0b101 left shift 3 bits
6 >> 1 0b110 right shift 1 bit
还有下列运算符:
m++ 变量m加1
n-- 变量n减1
condition ? x1 : x2 condition为一个boolean值。根据condition,取x1或x2的值
控制结构
Java中控制结构(control flow)的语法与C类似。Java的控制结构使用{}来构成隶属于结构的代码块。
1)选择结构(if)
if (conditon1) {
statements;
...
}
else if (condition2) {
statements;
...
}
else {
statements;
...
}
if...else...结构用于表达选择结构。上面的condition是一个表示真假值的表达式,称为条件。statements;是语句。当if后面的条件成立时,将执行隶属于if的代码块,即if后{}内的语句。如果不成立,那么就执行后面的else语句。如果if后面是else if,那么会在if条件失败后,判断else if的条件是否成立。if结构的流程见图:
作为选择结构的练习,你可以写一个Java程序,判断2018年是否是闰年。
2)循环 (while)
while (condition) {
statements;
}
while结构用于循环。condition也是一个表示真假的条件。当条件为真时,while隶属的代码块就会重复执行。while结构的流程见图:
3)循环 (do... while)
do {
statements;
} while(condition); // 注意结尾的;
do...while也利用循环。它和while的区别在于,会先执行do隶属的代码,再进行条件判断。condition是条件。当条件为真时,do隶属的代码块就会重复执行。do...while流程如图:
4)循环 (for)
for (initial; condition; update) {
statements;
}
for也是循环结构。它同样根据condition条件,决定是否继续执行for隶属代码块。但在流程上,它在进入循环之前,会先执行一个initial语句。在执行完一次隶属代码块时,会执行一次update语句。流程如图:
5)跳过或跳出循环
在循环中,可以使用:
break; // 跳出循环
continue; // 直接进入下一环
练习:写一个Java程序,计算从1加2,加3…… 一直加到999的总和
6)选择 (switch)
switch(expression) {
case 1:
statements;
break;
case 2:
statements;
break;
...
default:
statements;
break;
}
switch也是一种分支的结构。expression是一个表达式。当expression的值和某一case的值相同时,就执行隶属于该case的代码块。
讲到这里,我们做一个总结。我们了解了变量、表达式和控制结构。这些语法在其他高级编程语言中也很常见,如C、C++、Python等。如果你已经有了编程经验,就不会对这些语法感到陌生。众所周知,Java是一门面向对象语言。前一部分的叙述中,我们还没有怎么涉及面向对象的语法。下面,我们将专注于Java面向对象的方面。
面向对象
“对象”是计算机抽象世界的一种方式。“面向对象”可以用很多方式表达。下面是一种并不精确,但比较直观的理解方式:
- 世界上的每一个事物都可以称为一个对象(object),比如张三。对象有身份(Identity),状态(State)和行为(Behavior)。
- 对象的状态由数据成员(data member)表示。数据成员又被称作域(field)。我们用其他对象作为该对象的数据成员。比如一个表示身高的整数,比如一个鼻子。
- 对象的行为由成员方法(member method)表示。我们简称为方法(method)。一个对象可以有多个方法,比如呼吸,睡觉。
- 对象可以归类(class),或者说归为同一类型(type)。同一类型的对象有相同的方法,有同类型的数据成员。某个类型的一个对象被称为该类型的一个实例(instance)。
人类与个体
定义类的语法:
class ClassName
{
member1;
member2;
...
}
我们定义一个human类:
class Human
{
void breath()
{
System.out.println("hu...hu...");
}
int height;
}
在{}范围内,Human类有两个成员: 一个数据成员height,一个方法breath()。
- 数据成员height是整数类型,可以用于存储一个整数。
- 方法代表了对象所能进行的动作,也就是计算机所能进行的操作。方法可以接受参数,并能返回值。在breath()的定义中,breath后面的()中为参数列表。由于参数列表为空,所以breath()不接受参数。在breath()之前的void为返回值的类型,说明breath不返回值。
现在,我们创建对象aPerson,并调用对象的方法breath:
public class Test
{
public static void main(String[] args)
{
Human aPerson = new Human();
aPerson.breath();
System.out.println(aPerson.height);
}
}
class Human
{
void breath()
{
System.out.println("hu...hu...");
}
int height;
}
在main方法中,使用new关键字创建对象。即使是来自同一个类的对象,各个对象占据的内存也不相同,即对象的身份也不同。Human aPerson声明了aPerson对象属于Human类,即说明了对象的类型。对象建立后,我们可以用 对象.数据成员 来引用数据成员,使用 对象.方法() 的方式来调用方法。正如我们在后面打印aPerson.height。
调用同一对象的数据成员
方法可以调用该对象的数据成员。比如下面我们给Human类增加一个getHeight()的方法。该方法返回height数据成员的值:
public class Test
{
public static void main(String[] args)
{
Human aPerson = new Human();
System.out.println(aPerson.getHeight());
}
}
class Human
{
/**
* accessor
*/
int getHeight()
{
return this.height;
}
int height;
}
我们新增了getHeight方法。这个方法有一个int类型的返回值。Java中使用return来返回值。
注意this,它用来指代对象自身。当我们创建一个aPerson实例时,this就代表了aPerson这个对象。this.height指aPerson的height。this是隐性参数(implicit argument)。方法调用的时候,尽管方法的参数列表并没有this,Java都会“默默”的将this参数传递给方法。
this并不是必需的,上述方法可以写成:
/**
* accessor
*/
int getHeight()
{
return height;
}
Java会自己去判断height是类中的数据成员。但使用this会更加清晰。
我们还看到了/** comments */的添加注释的方法。
方法的参数列表
Java中的方法定义与C语言中的函数类似。Java的方法也可以接收参数列表(argument list),放在方法名后面的括号中。下面我们定义一个growHeight()的方法,该方法的功能是让人的height增高:
public class Test
{
public static void main(String[] args)
{
Human aPerson = new Human();
System.out.println(aPerson.getHeight());
aPerson.growHeight(10);
System.out.println(aPerson.getHeight());
}
}
class Human
{
/**
* accessor
*/
int getHeight()
{
return this.height;
}
/**
* pass argument
*/
void growHeight(int h)
{
this.height = this.height + h;
}
int height;
}
在growHeight()中,h为传递的参数。在类定义中,说明了参数的类型(int)。在具体的方法内部,我们可以使用该参数。该参数只在该方法范围,即growHeight()内有效。
在调用的时候,我们将10传递给growHeight()。aPerson的高度增加了10。
调用同一对象的其他方法
在方法内部,可以调用同一对象的其他方法。在调用的时候,使用this.method()的形式。我们还记得,this指代的是该对象。所以this.method()指代了该对象自身的method()方法。
比如下面的repeatBreath()函数:
public class Test
{
public static void main(String[] args)
{
Human aPerson = new Human();
aPerson.repeatBreath(10);
}
}
class Human
{
void breath()
{
System.out.println("hu...hu...");
}
/**
* call breath()
*/
void repeatBreath(int rep)
{
int i;
for(i = 0; i < rep; i++) {
this.breath();
}
}
int height;
}
为了便于循环,在repeatBreath()方法中,我们声明了一个int类型的对象i。i的作用域限定在repeatBreath()方法范围内部。
数据成员初始化
在Java中,数据成员有多种初始化(initialize)的方式。比如上面的getHeight()的例子中,尽管我们从来没有提供height的值,但Java为我们挑选了一个默认初始值0。
基本类型的数据成员的默认初始值:
- 数值型: 0
- 布尔值: false
- 其他类型: null
我们可以在声明数据成员同时,提供数据成员的初始值。这叫做显式初始化(explicit initialization)。显示初始化的数值要硬性的写在程序中:
public class Test
{
public static void main(String[] args)
{
Human aPerson = new Human();
System.out.println(aPerson.getHeight());
}
}
class Human
{
/**
* accessor
*/
int getHeight()
{
return this.height;
}
int height = 175;
}
这里,数据成员height的初始值为175,而不是默认的0了。
显式初始化要求我们在写程序时就确定初始值,这有时很不方便。我们可以使用构造器(constructor)来初始化对象。构造器可以初始化数据成员,还可以规定特定的操作。这些操作会在创建对象时自动执行。
定义构造器
构造器是一个方法。像普通方法一样,我们在类中定义构造器。构造器有如下基本特征:
- 构造器的名字和类的名字相同
- 构造器没有返回值
我们定义Human类的构造器:
public class Test
{
public static void main(String[] args)
{
Human aPerson = new Human(160);
System.out.println(aPerson.getHeight());
}
}
class Human
{
/**
* constructor
*/
Human(int h)
{
this.height = h;
System.out.println("I'm born");
}
/**
* accessor
*/
int getHeight()
{
return this.height;
}
int height;
}
上面的程序会打印
I'm born 160
构造器可以像普通方法一样接收参数列表。这里,构造器Human()接收一个整数作为参数。在方法的主体中,我们将该整数参数赋予给数据成员height。构造器在对象创建时做了两件事:
- 为数据成员提供初始值 this.height = h;
- 执行特定的初始操作 System.out.println("I'm born");
这样,我们就可以在调用构造器时,灵活的设定初始值,不用像显示初始化那样拘束。
构造器是如何被调用的呢?我们在创建类的时候,采用的都是new Human()的方式。实际上,我们就是在调用Human类的构造器。当我们没有定义该方法时,Java会提供一个空白的构造器,以便使用new的时候调用。但当我们定义了构造器时,在创建对象时,Java会调用定义了的构造器。在调用时,我们提供了一个参数160。从最后的运行结果中也可以看到,对象的height确实被初始化为160。
初始化方法的优先级
可以看到,如果我们提供显式初始值,那么数据成员就会采用显式初始值,而不是默认初始值。但如果我们既提供显式初始值,又在构造器初始化同一数据成员,最终的初始值将由构造器决定。比如下面的例子:
public class Test
{
public static void main(String[] args)
{
Human aPerson = new Human(160);
System.out.println(aPerson.getHeight());
}
}
class Human
{
/**
* constructor
*/
Human(int h)
{
this.height = h;
}
/**
* accessor
*/
int getHeight()
{
return this.height;
}
int height=170; // explicit initialization
}
运行结果为:
160
对象最终的初始化值与构建方法中的值一致。因此:
构建方法 > 显式初始值 > 默认初始值
方法重载
一个类中可以定义不止一个构造器,比如:
public class Test
{
public static void main(String[] args)
{
Human neZha = new Human(150, "shit");
System.out.println(neZha.getHeight());
}
}
class Human
{
/**
* constructor 1
*/
Human(int h)
{
this.height = h;
System.out.println("I'm born");
}
/**
* constructor 2
*/
Human(int h, String s)
{
this.height = h;
System.out.println("Ne Zha: I'm born, " + s);
}
/**
* accessor
*/
int getHeight()
{
return this.height;
}
int height;
}
运行结果:
Ne Zha: I'm born, shit 150
上面定义了两个构造器,名字都是Human。两个构造器有不同的参数列表。
在使用new创建对象时,Java会根据提供的参数来决定构建哪一个构造器。比如在构建neZha时,我们提供了两个参数: 整数150和字符串"shit",这对应第二个构建方法的参数列表,所以Java会调用第二个构建方法。
在Java中,Java会同时根据方法名和参数列表来决定所要调用的方法,这叫做方法重载(method overloading)。构建方法可以进行重载,普通方法也可以重载,比如下面的breath()方法:
public class Test
{
public static void main(String[] args)
{
Human aPerson = new Human();
aPerson.breath(10);
}
}
class Human
{
/**
* breath() 1
*/
void breath()
{
System.out.println("hu...hu...");
}
/**
* breath() 2
*/
void breath(int rep)
{
int i;
for(i = 0; i < rep; i++) {
System.out.println("lu...lu...");
}
}
int height;
}
运行结果:
lu...lu...
lu...lu...
lu...lu...
lu...lu...
lu...lu...
lu...lu...
lu...lu...
lu...lu...
lu...lu...
lu...lu...
可以看到,由于在调用的时候提供了一个参数: 整数10,所以调用的是参数列表与之相符的第二个breath()方法。
Java面向对象的基础知识我就聊到这儿。现在做一个总结。我们了解了类和对象的区别和关系,以及对象的数据成员和成员方法。我们用类来创建对象,并通过构造器来初始化对象。这些知识,是进一步了解面向对象编程所必备的。
在这篇入门介绍中,我用一些简单的例子展示了Java的基础语法。这篇文章能让你浅尝Java编程的滋味。如果你了解其他编程语言,那么文中的很多语法看起来并不陌生。语言之间相互借鉴是常事,更何况Java相对年轻,自然借鉴了C语言这样的前辈。Java最有趣的地方是它的面向对象编程。我将在下一篇“Java核心概念”中详细叙述。
小编是一个有着5年工作经验的JAVA程序员,关于java,自己有做材料的整合,一个完整的java编程学习路线,学习材料和工具,能够进我的群723197800获取,免费送给大家,希望你也能凭着自己的努力,成为下一个优秀的程序员。