文章目录
声明:
本博客是本人在学习《Java 编程的逻辑》后整理的笔记,旨在方便复习和回顾,并非用作商业用途。
本博客已标明出处,如有侵权请告知,马上删除。
开头语
程序主要就是数据以及对数据的操作,为方便理解和操作,高级语言使用数据类型这个概念,不同的数据类型有不同的特征和操作。
Java 定义了八种基本数据类型,四种整型 byte/short/int/long,两种浮点类型 float/double,一种真假类型 boolean,一种字符类型 char,其他类型的数据都用类这个概念表达。
3.1 类的基本概念
在第1章,我们暂时将类看做函数的容器,在某些情况下,类也确实基本上只是函数的容器,但类更多表示的是自定义数据类型。我们先从容器的角度,然后从自定义数据类型的角度谈谈类。
3.1.1 函数容器
我们看个例子,Java API 中的类 Math,它里面主要就包含了若干数学函数,表 3-1 列出了其中一些。
使用这些函数,直接在前面加 Math. 即可,例如 Math.abs(-1) 返回 1。
这些函数都有相同的修饰符,public static。
static 表示类方法,也叫静态方法,与类方法相对的是实例方法。实例方法没有 static 修饰符,必须通过实例或者叫对象(待会介绍)调用,而类方法可以直接通过类名进行调用的,不需要创建实例。
public 表示这些函数是公开的,可以在任何地方被外部调用。与 public 相对的有 private, 如果是 private,表示私有,这个函数只能在同一个类内被别的函数调用,而不能被外部的类调用。在 Math 类中,有一个函数 Random initRNG() 就是 private 的,这个函数被 public 的方法 random() 调用以生成随机数,但不能在 Math 类以外的地方被调用。
将函数声明为 private 可以避免该函数被外部类误用,调用者可以清楚的知道哪些函数是可以调用的,哪些是不可以调用的。类实现者通过 private 函数封装和隐藏内部实现细节,而调用者只需要关心 public 的就可以了。可以说,通过 private 封装和隐藏内部实现细节,避免被误操作,是计算机程序的一种基本思维方式。
除了 Math 类,我们再来看一个例子 Arrays,Arrays 里面包含很多与数组操作相关的函数,表 3-2 列出了其中一些。
这里将类看做函数的容器,更多的是从语言实现的角度看,从概念的角度看,Math 和 Arrays 也可以看做是自定义数据类型,分别表示数学和数组类型,其中的 public static 函数可以看做是类型能进行的操作。接下来让我们更为详细的讨论自定义数据类型。
3.1.2 自定义数据类型
我们将类看做自定义数据类型,所谓自定义数据类型就是除了八种基本类型以外的其他类型,用于表示和处理基本类型以外的其他数据。
一个数据类型由其包含的属性以及该类型可以进行的操作组成,属性又可以分为是类型本身具有的属性,还是一个具体数据具有的属性,同样,操作也可以分为是类型本身可以进行的操作,还是一个具体数据可以进行的操作。
这样,一个数据类型就主要由四部分组成:
- 类型本身具有的属性,通过类变量体现
- 类型本身可以进行的操作,通过类方法体现
- 类型实例具有的属性,通过实例变量体现
- 类型实例可以进行的操作,通过实例方法体现
不过,对于一个具体类型,每一个部分不一定都有,Arrays 类就只有类方法。
类变量和实例变量都叫成员变量,也就是类的成员,类变量也叫静态变量或静态成员变量。类方法和实例方法都叫成员方法,也都是类的成员,类方法也叫静态方法。
类方法我们上面已经看过了,Math 和 Arrays 类中定义的方法就是类方法,这些方法的修饰符必须有 static。下面解释下类变量,实例变量和实例方法。
3.1.2.1 类变量
类型本身具有的属性通过类变量体现,经常用于表示一个类型中的常量。比如 Math 类,定义了两个数学中常用的常量,如下所示:
public static final double E = 2.7182818284590452354;
public static final double PI = 3.14159265358979323846;
E 表示数学中自然对数的底数,自然对数在很多学科中有重要的意义,PI 表示数学中的圆周率 π。与类方法一样,类变量可以直接通过类名访问,如 Math.PI。
这两个变量的修饰符也都有 public static,public 表示外部可以访问,static 表示是类变量。与 public 相对的主要也是 private,表示变量只能在类内被访问。与 static 相对的是实例变量,没有 static 修饰符。
这里多了一个修饰符 final,final 在修饰变量的时候表示常量,即变量赋值后就不能再修改了。使用 final 可以避免误操作,比如说,如果有人不小心将 Math.PI 的值改了,那么很多相关的计算就会出错。另外,Java 编译器可以对 final 变量进行一些特别的优化。所以,如果数据赋值后就不应该再变了,就加 final 修饰符吧。
表示类变量的时候,static 修饰符是必需的,但 public 和 final 都不是必需的。
3.1.2.2 实例变量和实例方法
所谓实例,字面意思就是一个实际的例子。实例变量表示具体的实例所具有的属性,实例方法表示具体的实例可以进行的操作。如果将微信订阅号看做一个类型,那"老马说编程"订阅号就是一个实例,订阅号的头像、功能介绍、发布的文章可以看做实例变量,而修改头像、修改功能介绍、发布新文章可以看做实例方法。与基本类型对比,“int a;” 这个语句,int 就是类型,而 a 就是实例。
接下来,我们通过定义和使用类,来进一步理解自定义数据类型。
3.1.3 定义第一个类
我们定义一个简单的类,表示在平面坐标轴中的一个点,代码如下:
class Point {
public int x;
public int y;
public double distance(){
return Math.sqrt(x*x+y*y);
}
}
我们来解释一下:
public class Point
表示类型的名字是 Point,是可以被外部公开访问的。这个 public 修饰似乎是多余的,不能被外部访问还能有什么用?在这里,确实不能用 private 修饰 Point。但修饰符可以没有(即留空),表示一种包级别的可见性,我们后续章节介绍,另外,类可以定义在一个类的内部,这时可以使用 private 修饰符,我们也在后续章节介绍。
public int x;
public int y;
定义了两个实例变量,x 和 y,分别表示 x 坐标和 y 坐标,与类变量类似,修饰符也有 public 或 private 修饰符,表示含义类似,public 表示可被外部访问,而 private 表示私有,不能直接被外部访问,实例变量不能有 static 修饰符。
public double distance(){
return Math.sqrt(x*x+y*y);
}
定义了实例方法 distance,表示该点到坐标原点的距离。该方法可以直接访问实例变量 x 和 y,这是实例方法和类方法的最大区别。实例方法直接访问实例变量,到底是什么意思呢?其实,在实例方法中,有一个隐含的参数,这个参数就是当前操作的实例自己,直接操作实例变量,实际也需要通过参数进行。
实例方法和类方法更多的区别如下所示:
- 类方法只能访问类变量,但不能访问实例变量,可以调用其他的类方法,但不能调用实例方法。
- 实例方法既能访问实例变量,也可以访问类变量,既可以调用实例方法,也可以调用类方法。
关于实例方法和类方法更多的细节,后续会进一步介绍。
3.1.4 使用第一个类
定义了类本身和定义了一个函数类似,本身不会做什么事情,不会分配内存,也不会执行代码。方法要执行需要被调用,而实例方法被调用,首先需要一个实例。实例也称为对象,我们可能会交替使用。
下面的代码演示了如何使用:
public static void main(String[] args) {
Point p = new Point();
p.x = 2;
p.y = 3;
System.out.println(p.distance());
}
我们解释一下:
Point p = new Point();
这个语句包含了 Point 类型的变量声明和赋值,它可以分为两部分:
Point p;
p = new Point();
Point p 声明了一个变量,这个变量叫 p,是 Point 类型的。这个变量和数组变量是类似的,都有两块内存,一块存放实际内容,一块存放实际内容的位置。声明变量本身只会分配存放位置的内存空间,这块空间还没有指向任何实际内容。因为这种变量和数组变量本身不存储数据,而只是存储实际内容的位置,它们也都称为引用类型的变量。
p = new Point(); 创建了一个实例或对象,然后赋值给了 Point 类型的变量 p,它至少做了两件事:
- 分配内存,以存储新对象的数据,对象数据包括这个对象的属性,具体包括其实例变量 x 和 y。
- 给实例变量设置默认值,int 类型默认值为 0。
与方法内定义的局部变量不同,在创建对象的时候,所有的实例变量都会分配一个默认值,这与在创建数组的时候是类似的,数值类型变量的默认值是 0,boolean 是 false, char 是 ‘\u0000’,引用类型变量都是 null,null 是一个特殊的值,表示不指向任何对象。这些默认值可以修改,我们待会介绍。
p.x = 2;
p.y = 3;
给对象的变量赋值,语法形式是:<对象变量名>.<成员名>。
System.out.println(p.distance());
调用实例方法 distance,并输出结果,语法形式是:<对象变量名>.<方法名>。实例方法内对实例变量的操作,实际操作的就是 p 这个对象的数据。
我们在介绍基本类型的时候,是先定义数据,然后赋值,最后是操作,自定义类型与此类似:
- Point p = new Point(); 是定义数据并设置默认值
- p.x = 2; p.y = 3; 是赋值
- p.distance() 是数据的操作
可以看出,对实例变量和实例方法的访问都通过对象进行,通过对象来访问和操作其内部的数据是一种基本的面向对象思维。本例中,我们通过对象直接操作了其内部数据 x 和 y,这是一个不好的习惯,一般而言,不应该将实例变量声明为 public,而只应该通过对象的方法对实例变量进行操作。原因也是为了减少误操作,直接访问变量没有办法进行参数检查和控制,而通过方法修改,可以在方法中进行检查。
3.1.5 变量默认值
之前我们说,实例变量都有一个默认值,如果希望修改这个默认值,可以在定义变量的同时就赋值,或者将代码放入初始化代码块中,代码块用 {} 包围,如下面代码所示:
int x =