oop思想的核心,就是类(class)。在Java中类的作用是作为完整、独立的程序单元来完成特定功能,并不局限于扩展数据类型(比如C++的类的作用就是让用户自定义类型,让这种类型看起来像是基本类型一样,并且封装了关于它的操作)。
C++学习者一定要和Java区分,有些地方比较相似可以类比,有些地方则是干扰项。但是,如果你对C++的类设计了解透彻,我相信Java是可以速成的。
这篇文针对有Java类设计基础的读者,如果是初学者,老老实实回去看书。碎片化知识有害你的成长。
一、类字段
也叫成员或成员变量。它们有四种访问权限:public(公有)、默认、protected(保护)、private(私有)。
public:对外可见的,所有类都可以访问
默认:也叫“包内友好”,位于同一个包中的类可以访问
protected:保护成员,这个是在继承中的概念,位于同一个包中的类以及它的所有子类可以访问
private:私有,只有自己的成员函数和构造函数可以访问
方法和内部类的访问权限也一样。关于访问权限这里不细讲了,如果这点没弄清楚赶快去补。Java中访问权限的控制虽然没有C++那么繁琐,但是依然很重要。下面说几个原则:
(1)字段一般应设计成private,除非是final字段
(2)构造函数、成员函数(也叫方法)一般应设计成public或默认,但也有保护方法或私有方法,常见于一些危险操作,不允许用户直接调用而必须通过其他方法间接调用
(3)内部类可以自由选择访问权限,但一般不是public
另外,说一下public class。如果它是内部类,那就好理解,和成员权限一样属于公有。如果它是外部类,那表明这个类是它所在文件的主类,类名必须和文件名相同。比如public class Main,必须在Main.java中。一个Java源文件只能且必须有一个public class。在主文件中,这个类包含main方法,也就是JVM(Java解释器)的入口地址。外部类不可以用private和protected修饰。
用final修饰的字段类似于C++中的const成员,表示初始化之后就不可改变,是常量。Java中const是个“保留字”,它没有实际意义,但Java不允许用户用它做标识符。
静态字段是一种特殊的字段,用static关键字修饰。它的生存期和普通字段不同。普通字段是依存于对象的,当对象被解除引用之后它们就随之消灭了。而静态字段是依附于类本身存在的,只要这个文件还在运行,它们就不会消亡,所以叫静态字段。由于静态字段的这个性质,它们常被设置为与具体对象无关的东西,比如对象数目计数。
二、方法和构造方法
方法就是成员函数,封装了对类字段的操作。方法分为静态方法和对象方法两种,先讨论后者。通过 对象名.方法名 调用。
构造方法是一种特殊的方法,在对象创建时调用,它不可以手动调用,而是对象分配内存和初始化时通过new语句调用。
Classname objectname = new Classname (args...);
args必须和某个构造函数的参数表相匹配。
Java中没有直接的复制构造函数,因为赋值运算符仅仅是复制引用而不是对象。如果想实现对象的深拷贝,则应使用clone方法。
方法重载:方法名相同,参数列表不完全相同的几个函数,叫做重载函数。返回值可以相同可以不同。**但参数列表必须不同,否则将产生二义性。**还有一个概念叫方法重写,这是完全不同的两个概念,在继承中讲。
构造方法和普通方法一样,也可以重载。
Java和C++不一样,不提供运算符重载,也不提供默认参数。这些功能其实属于语法糖,都可以通过方法重载实现。但是,用起来就没有C++那么方便,比如BigInteger类,不能用加号,只能用add方法。
Java不提供析构函数,因为JVM是个全职保姆,它会检查有没有未引用的对象,并且会自动回收它们的内存(扫垃圾),这就没必要提供析构函数。Java这么设计的初衷是免得程序猿手动分配内存,但是在性能上有所损失。一般在简单操作上,Java程序的运行速度是C++的1/3左右。
静态方法是一种特殊的方法,用static关键字修饰。这里的静态不是指生存期,而是指依赖性。普通方法依赖于特定的对象,它们执行的是与对象有关的操作,必须通过对象名调用。**但是静态方法是不依赖于具体的对象,可以通过类名来调用。**它们被设计为执行与对象无关的操作,比如Math类中的sqrt(),不是执行关于具体对象的操作,它是用来求实数(double)的平方根。(其实Math类是个抽象类,不允许创建它的对象,因为创建Math类的对象没有意义)
**静态方法只能引用静态字段,不可以引用普通字段。**另外,有两个原则要注意,一是不要通过对象方法操作(操作指的是修改,读是允许的)静态字段;二是不要用对象名引用静态成员,而要用类名。
三、继承
继承是指子类与父类的关系,子类全盘拥有父类除私有成员和静态成员之外的所有成员。由于字段一般是私有的,所以继承一般是为了继承方法。
C++中继承有public,private,protected三种方式,但Java默认只能extends,也就是公有继承。特征就是,父类的公有成员和保护成员在子类中依然有相同的访问权限。Java规定一个类最多继承一个类,不支持多重继承(这是为了避免菱形继承问题),但允许一个类实现多个接口。
子类的构造函数要首先调用父类的构造函数(除非父类是抽象类),然后再做其它初始化工作。调用方法就是super(args…);
继承的功能就是扩展已有的类使它支持更多功能,以及变更某些函数的执行方式。说白了,就是扩展和多态。多态通过一种叫做方法重写的操作来实现。
方法重写是指子类中重新定义父类某个方法。和方法重载不同,重写(覆盖)的方法头必须和父类相应方法完全相同,也就是函数名、参数表完全相同。并且,方法重写有几个必须遵循的规则:
1、子类函数的返回值必须兼容父类函数返回值(兼容是指必须和父类函数返回值在同一继承链中,并且不能是比父类返回值更高的类型。如果返回值是基本类型,则必须完全相同)
2、子类函数抛出的异常不可以高于父类函数
3、子类函数的访问权限不可以低于父类函数
4、静态方法和被final修饰的方法不能重写
final的另外两种用法:用final修饰的类是指不可继承的类,它位于继承链的底端。试图extends它会引发编译错误。用final(注意不要和finally混淆,finally是异常处理中用到的控制关键字)修饰的函数是指不可被子类重写的函数。
四、抽象类和接口
抽象类是指被abstract关键字修饰的类。类似于C++中的纯虚类。它是一种不可以创建对象的类。抽象方法是指用abstract关键字修饰,只提供声明不提供实现的方法,类似于C++中的纯虚函数。
规定有抽象方法的类必须声明为抽象类。抽象类被继承时,必须实现其中的抽象方法,或者把子类也声明为抽象类。
没有抽象方法的类也可以声明为抽象类,目的是禁止用户创建关于它的对象,比如Math类。
抽象类的所有字段默认为静态字段。
接口(interface)是一种类似于类的东西,但它不是真正的类。它只提供类的蓝图,剩下的交给具体的类实现。而且,它的存在是为了弥补Java不支持多重继承造成的损失。一个类可以implements(实现)多个接口(同时还可以继承一个类),一个接口可以extends另一个接口。
接口中所有方法都是抽象方法,所有字段都是静态final字段。
类实现接口时,必须实现接口的所有方法,或者把类声明为abstract。
Java的类设计规则其实比C++简单的多,因为没有C++的指针、引用、const等啰嗦的考虑。所以,只要多写点代码,多总结就能摸清规律。