类html
上节咱们介绍了函数调用的基本原理,本节和接下来几节,咱们探索类的世界。编程
程序主要就是数据以及对数据的操做,为方便理解和操做,高级语言使用数据类型这个概念,不一样的数据类型有不一样的特征和操做,Java定义了八种基本数据类型,其中,四种整形byte/short/int/long,两种浮点类型float/double,一种真假类型boolean,一种字符类型char,其余类型的数据都用类这个概念表达。swift
前两节咱们暂时将类看作函数的容器,在某些状况下,类也确实基本上只是函数的容器,但类更多表示的是自定义数据类型,咱们先从容器的角度,而后从自定义数据类型的角度谈谈类。数组
函数容器微信
咱们看个例子,Java API中的类Math,它里面主要就包含了若干数学函数,下表列出了其中一些:dom
Math函数函数 |
功能优化 |
int round(float a)this |
四舍五入spa |
double sqrt(double a) |
平方根 |
double ceil(double a) |
向上取整 |
double floor(double a) |
向下取整 |
double pow(double a, double b) |
a的b次方 |
int abs(int a) |
绝对值 |
int max(int a, int b) |
最大值 |
double log(double a) |
天然对数 |
double random() |
产生一个大于等于0小于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里面包含不少与数组操做相关的函数,下表列出了其中一些:
Arrays函数 |
功能 |
void sort(int[] a) |
排序,按升序排,整数数组 |
void sort(double[] a) |
排序,按升序排,浮点数数组 |
int binarySearch(long[] a, long key) |
二分查找,数组已按升序排列 |
void fill(int[] a, int val) |
给全部数组元素赋相同的值 |
int[] copyOf(int[] original, int newLength) |
数组拷贝 |
boolean equals(char[] a, char[] a2) |
判断两个数组是否相同 |
这里将类看作函数的容器,更多的是从语言实现的角度看,从概念的角度看,Math和Arrays也能够看作是自定义数据类型,分别表示数学和数组类型,其中的public static函数能够看作是类型能进行的操做。接下来让咱们更为详细的讨论自定义数据类型。
自定义数据类型
咱们将类看作自定义数据类型,所谓自定义数据类型就是除了八种基本类型之外的其余类型,用于表示和处理基本类型之外的其余数据。
一个数据类型由其包含的属性以及该类型能够进行的操做组成,属性又能够分为是类型自己具备的属性,仍是一个具体数据具备的属性,一样,操做也能够分为是类型自己能够进行的操做,仍是一个具体数据能够进行的操做。
这样,一个数据类型就主要由四部分组成:
不过,对于一个具体类型,每个部分不必定都有,Arrays类就只有类方法。
类变量和实例变量都叫成员变量,也就是类的成员,类变量也叫静态变量或静态成员变量。类方法和实例方法都叫成员方法,也都是类的成员,类方法也叫静态方法。
类方法咱们上面已经看过了,Math和Arrays类中定义的方法就是类方法,这些方法的修饰符必须有static。下面解释下类变量,实例变量和实例方法。
类变量
类型自己具备的属性经过类变量体现,常常用于表示一个类型中的常量,好比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都不是必需的。
实例变量和实例方法
实例字面意思就是一个实际的例子,实例变量表示具体的实例所具备的属性,实例方法表示具体的实例能够进行的操做。若是将微信订阅号看作一个类型,那"老马说 编程"订阅号就是一个实例,订阅号的头像、功能介绍、发布的文章能够看作实例变量,而修改头像、修改功能介绍、发布新文章能够看作实例方法。与基本类型对 比,int a;这个语句,int就是类型,而a就是实例。
接下来,咱们经过定义和使用类,来进一步理解自定义数据类型。
定义第一个类
咱们定义一个简单的类,表示在平面坐标轴中的一个点,代码以下:
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,这是实例方法和类方法的最大区别。实例方法直接访问实例变量,究竟是什么意思呢?其实,在实例方法中,有一个隐含的参数,这个参数就是当前操做的实例本身,直接操做实例变量,实际也须要经过参数进行。实例方法和类方法更多的区别以下所示:
关于实例方法和类方法更多的细节,后续会进一步介绍。
使用第一个类
定义了类自己和定义了一个函数相似,自己不会作什么事情,不会分配内存,也不会执行代码。方法要执行须要被调用,而实例方法被调用,首先须要一个实例,实例也称为对象,咱们可能会交替使用。下面的代码演示了如何使用:
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类型的变量声明和赋值,它能够分为两部分:
1 Point p; 2 p = new Point();
Point p声明了一个变量,这个变量叫p,是Point类型的。这个变量和数组变量是相似的,都有两块内存,一块存放实际内容,一块存放实际内容的位置。声明变量自己只会分配存放位置的内存空间,这块空间尚未指向任何实际内容。由于这种变量和数组变量自己不存储数据,而只是存储实际内容的位置,它们也都称为引用类型的变量。
p = new Point();建立了一个实例或对象,而后赋值给了Point类型的变量p,它至少作了两件事:
与方法内定义的局部变量不一样,在建立对象的时候,全部的实例变量都会分配一个默认值,这与在建立数组的时候是相似的,数值类型变量的默认值是 0,boolean是false, char是'\u0000',引用类型变量都是null,null是一个特殊的值,表示不指向任何对象。这些默认值能够修改,咱们待会介绍。
p.x = 2; p.y = 3;
给对象的变量赋值,语法形式是:对象变量名.成员名。
System.out.println(p.distance());
调用实例方法distance,并输出结果,语法形式是:对象变量名.方法名。实例方法内对实例变量的操做,实际操做的就是p这个对象的数据。
咱们在介绍基本类型的时候,是先定义数据,而后赋值,最后是操做,自定义类型与此相似:
能够看出,对实例变量和实例方法的访问都经过对象进行,经过对象来访问和操做其内部的数据是一种基本的面向对象思惟。本例中,咱们经过对象直接操做了其内部数据x和y,这是一个很差的习惯,通常而言,不该该将实例变量声明为public,而只应该经过对象的方法对实例变量进行操做,缘由也是为了减小误操做,直接访问变量没有办法进行参数检查和控制,而经过方法修改,能够在方法中进行检查。
修改变量默认值
以前咱们说,实例变量都有一个默认值,若是但愿修改这个默认值,能够在定义变量的同时就赋值,或者将代码放入初始化代码块中,代码块用{}包围,以下面代码所示:
int x = 1; int y; { y = 2; }
x的默认值设为了1,y的默认值设为了2。在新建一个对象的时候,会先调用这个初始化,而后才会执行构造方法中的代码。
静态变量也能够这样初始化:
static int STATIC_ONE = 1; static int STATIC_TWO; static { STATIC_TWO = 2; }
STATIC_TWO=2;语句外面包了一个 static {},这叫静态初始化代码块。静态初始化代码块在类加载的时候执行,这是在任何对象建立以前,且只执行一次。
修改类 - 实例变量改成private
上面咱们说通常不该该将实例变量声明为public,下面咱们修改一下类的定义,将实例变量定义为private,经过实例方法来操做变量,代码以下:
class Point { private int x; private int y; public void setX(int x) { this.x = x; } public void setY(int y) { this.y = y; } public int getX() { return x; } public int getY() { return y; } public double distance() { return Math.sqrt(x * x + y * y); } }
这个定义中,咱们加了四个方法,setX/setY用于设置实例变量的值,getX/getY用于获取实例变量的值。
这里面须要介绍的是this这个关键字,this表示当前实例, 在语句this.x=x;中,this.x表示实例变量x,而右边的x表示方法参数中的x。前面咱们提到,在实例方法中,有一个隐含的参数,这个参数就是this,没有歧义的状况下,能够直接访问实例变量,在这个例子中,两个变量名都叫x,则须要经过加上this来消除歧义。
这四个方法看上去是很是多余的,直接访问变量不是更简洁吗?并且上节咱们也说过,函数调用是有成本的。在这个例子中,意义确实不太大,实际上,Java编译器通常也会将对这几个方法的调用转换为直接访问实例变量,而避免函数调用的开销。但在不少状况下,经过函数调用能够封装内部数据,避免误操做,咱们通常仍是不将成员变量定义为public。
使用这个类的代码以下:
public static void main(String[] args) { Point p = new Point(); p.setX(2); p.setY(3); System.out.println(p.distance()); }
将对实例变量的直接访问改成了方法调用。
修改类 - 引入构造方法
在初始化对象的时候,前面咱们都是直接对每一个变量赋值,有一个更简单的方式对实例变量赋初值,就是构造方法,咱们先看下代码,在Point类定义中增长以下代码:
public Point(){ this(0,0); } public Point(int x, int y){ this.x = x; this.y = y; }
这两个就是构造方法,构造方法能够有多个。不一样于通常方法,构造方法有一些特殊的地方:
与普通方法同样,构造方法也能够重载。第二个构造方法是比较容易理解的,使用this对实例变量赋值。
咱们解释下第一个构造方法,this(0,0)的意思是调用第二个构造方法,并传递参数0,0,咱们前面解释说this表示当前实例,能够经过this访问实例变量,这是this的第二个用法,用于在构造方法中调用其余构造方法。
这个this调用必须放在第一行,这个规定应该也是为了不误操做,构造方法是用于初始化对象的,若是要调用别的构造方法,先调别的,而后根据状况本身再作调整,而若是本身先初始化了一部分,再调别的,本身的修改可能就被覆盖了。
这个例子中,不带参数的构造方法经过this(0,0)又调用了第二个构造方法,这个调用是多余的,由于x和y的默认值就是0,不须要再单独赋值,咱们这里主要是演示其语法。
咱们来看下如何使用构造方法,代码以下:
Point p = new Point(2,3);
这个调用就能够将实例变量x和y的值设为2和3。前面咱们介绍 new Point()的时候说,它至少作了两件事,一个是分配内存,另外一个是给实例变量设置默认值,这里咱们须要加上一件事,就是调用构造方法。调用构造方法是new操做的一部分。
经过构造方法,能够更为简洁的对实例变量进行赋值。
默认构造方法
每一个类都至少要有一个构造方法,在经过new建立对象的过程当中会被调用。但构造方法若是没什么操做要作,能够省略。Java编译器会自动生成一个默认构造方 法,也没有具体操做。但一旦定义了构造方法,Java就不会再自动生成默认的,具体什么意思呢?在这个例子中,若是咱们只定义了第二个构造方法(带参数的),则下面语句:
Point p = new Point();
就会报错,由于找不到不带参数的构造方法。
为何Java有时候帮助自动生成,有时候不生成呢?你在没有定义任何构造方法的时候,Java认为你不须要,因此就生成一个空的以被new过程调用,你定义了构造方法的时候,Java认为你知道本身在干什么,认为你是有意不想要不带参数的构造方法的,因此不会帮你生成。
私有构造方法
构造方法能够是私有方法,即修饰符能够为private, 为何须要私有构造方法呢?大概可能有这么几种场景:
关键字小结
本节咱们提到了多个关键字,这里汇总一下:
类和对象的生命周期
类
在程序运行的时候,当第一次经过new建立一个类的对象的时候,或者直接经过类名访问类变量和类方法的时候,Java会将类加载进内存,为这个类型分配一块空间,这个空间会包括类的定义,它有哪些变量,哪些方法等,同时还有类的静态变量,并对静态变量赋初始值。后续文章会进一步介绍有关细节。
类加载进内存后,通常不会释放,直到程序结束。通常状况下,类只会加载一次,因此静态变量在内存中只有一份。
对象
当经过new建立一个对象的时候,对象产生,在内存中,会存储这个对象的实例变量值,每new一次,对象就会产生一个,就会有一份独立的实例变量。
每一个对象除了保存实例变量的值外,能够理解还保存着对应类型即类的地址,这样,经过对象能知道它的类,访问到类的变量和方法代码。
实例方法能够理解为一个静态方法,只是多了一个参数this,经过对象调用方法,能够理解为就是调用这个静态方法,并将对象做为参数传给this。
对象的释放是被Java用垃圾回收机制管理的,大部分状况下,咱们不用太操心,当对象再也不被使用的时候会被自动释放。
具体来讲,对象和数组同样,有两块内存,保存地址的部分分配在栈中,而保存实际内容的部分分配在堆中。栈中的内存是自动管理的,函数调用入栈就会分配,而出栈就会释放。
堆中的内存是被垃圾回收机制管理的,当没有活跃变量指向对象的时候,对应的堆空间就可能被释放,具体释放时间是Java虚拟机本身决定的。活跃变量,具体的说,就是已加载的类的类变量,和栈中全部的变量。
小结
本 节咱们主要从自定义数据类型的角度介绍了类,谈了如何定义类,以及如何建立对象,如何使用类。自定义类型由类变量、类方法、实例变量和实例方法组成,为方 便对实例变量赋值,介绍了构造方法。本节引入了多个关键字,咱们介绍了这些关键字的含义。最后咱们介绍了类和对象的生命周期。
经过类实现自定义数据类型,封装该类型的数据所具备的属性和操做,隐藏实现细节,从而在更高的层次上(类和对象的层次,而非基本数据类型和函数的层次)考虑和操做数据,是计算机程序解决复杂问题的一种重要的思惟方式。
本节介绍的Point类,其属性只有基本数据类型,下节咱们介绍类的组合,以表达更为复杂的概念。