摘要:这篇笔记主要讲解了Java中的自定义类、以及构造一个类时所需要了解的一些重点知识。
众所周知,Java是书写在类中的语言,我们在写Java代码时用到的很多操作都要先拥有一个类的实例,进而调用类得方法,或者是有一些类拥有静态方法,我们直接使用类名调用这个方法,但总之都离不开类,这些类多是别人写好的,我们自己写Java程序也离不开类,我们需要经常的自己写各种各样的类,那么一个类需要那些组成部分呢?
一个类的组成部分为:域(属性)、构造方法以及自身具备的普通方法。如下:
class ClassName{
field1;
field2;
...
consturctor1;
consturctor2;
...
method1;
method2;
}
类中还有一种东西叫注解,属于类信息的一种,严格意义上讲它并不参与Java类的结构性组成,并不是一个必要的部分,因此我们通常认为一个Java类由域(属性)、构造方法、普通方法构成,下面我们就详细说明这三个基本组成部分。
域实际上就是属性,在欧美国家的文献中,他们称一个在Java类中定义的变量为“field”,直接翻译的话就是“领域”的意思,实际上它对应的就是我们经常说的属性,使用属性这一个词对我们中国人来说更加容易理解。类中直接定义的变量就是类的属性,类的属性是一个类最重要的组成部分之一,它直接描述了一个类的特征,同时类的属性也基本上是问题的处理中所以数据信息的载体了,我们是通过一系列的属性来确定一个类的性质的,正如我们根据各种各样的特征确定一种现实中的物品。在各种项目的实施中,基本上我们都在和属性这个东西打交道,属性也被称为域,之后见到域这个名词,知道它指的是属性即可。
在之前我们已经比较详细的解答过构造方法是什么以及构造方法在一个对象生成语句中的作用了。构造方法没有返回值类型,它必须和类同名,除此之外,我们可以随意定义一个构造方法。构造方法并不是真正构造一个对象,构造对象的实际上是new引起的各种操作,构造方法起到的作用实际上是在构造好对象实例之后,在返回实例地址之前初始化这个实例,至于如何初始化,这和我们在构造方法中书写的代码有关系。之前我们也成构造方法为构造器,这只是叫法不同,我们可以称这种结构为构造方法,也可以称呼为构造器,有时为了和普通方法进行区分,就会称它为构造器。
人类有着姓名、性别、年龄之类的属性,他们同时也可以行走,跑步,吃喝,而Java中的类也一样,既有自己的属性,也可以做出一些可以被观测到的行为,这些行为我们称之为“方法”。这些方法不同于构造器,它们在类中就是单纯的作为方法存在,它们通常是一段封装的代码段,使用方法定义的形式进行书写之后,调用方法名就可以直接执行这一段代码。
Java类中的普通方法的定义基本上如下所示:
修饰符(如public、private) 返回值类型(如int、String) 方法名(形参列表){
代码块;
}
一个方法在调用时通常需要指明:隐式参数、方法名、显式参数(形参列表)。其中隐式参数是这个方法所在的对象或者是类,在Java中,方法存在于类中,如果这个类不是静态方法,那么在这个类有一个实例之前,这个方法是不存在于内存中的,首先要对类进行实例化获得一个对象,然后通过这个对象调用这个方法。如果这个方法是静态方法,那么这个方法就会获取一块在方法区中的内存,这个方法就不会属于任何类的对象而是属于这个类,这是需要通过这个类的路径来调用方法。调用方式为:
类名 变量名 = new 类名();
变量名.方法名(形参列表);
类名.方法名(形参列表);
通常来说,我们不能直接调用一个方法,因为我们需要通过路径找到方法在内存上的地址,如果想使用一个类中的方法,这个类的路径必须是在当前程序目录中能够被找到,也就是说必须将这个类的路径导入到当前程序中,在Java中我们使用import的关键字进行导入路径。在一个类的内部,我们可以不用隐式参数来调用这个类的所有方法,因为在类的内部,所有方法的地址都是已知的,不需要任何路径就可以找到任一方法,就像在同一个目录下,我们无需加入任何前缀路径就可以访问当前目录下的所有文件。但是为了进行更好的区分,或者刻意体现隐式参数这一部分,很多人喜欢使用this关键词来在类的内部调用一个类自己的方法,如:
class Cat{
fields;
private method1(){
...
}
public method2(){
this.method1();
}
}
这实际上和下面的代码无甚区别:
class Cat{
fields;
private method1(){
...
}
public method2(){
method1();
}
}
只不过看起来更加清晰明了,我建议这样写,因为这样代码的可读性更高。this实际上是表示当前对象的意思,隐式参数为this,实际上指的是使用自身对象,调用一个方法。
类的构造器以及普通方法都可以进行方法的重载,当方法名相同,但是整体的方法签名不同时,这两个方法仍然可以被认定为是不同的方法,这样两个同名方法就可以同时存在并且被正常调用,这个过程是重载。方法签名在之前有过详细的讲解,由方法名和形参列表构成,它们的组合才是区分不同方法的标识。
重载和覆写不同,覆写是子类中拥有父类的同名方法时,这个新写的方法会覆盖掉从父类中继承来的同名方法,两个概念是截然不同的,这里需要注意。
关于final修饰符之前重点提过多次,这里再重复一次:**被final修饰的类不可以被继承;被final修饰的方法不可被重写;final修饰的常量不可以被第二次赋值;final防止指令重新排序,保证多线程下的安全。**这个知识点是面试重点,一定要记住!
final修饰符和static修饰符不是一个概念的修饰符,二者相互使用不相互冲突。当我们对一个变量使用final修饰符时,受到限制的是这个变量的句柄,这个变量的句柄的值变得不可被修改,因此一个基本类型的变量被设定上final修饰符之后,它在被第一次赋值之后就真的不能改变了,而引用类型的变量的值实际上不是它的真实值,而是真实值的地址,因此当我们为一个引用类型变量加上final修饰符之后,这个引用类型变量的指向就不能改变了,但是它的值可以被改变,我们仍然可以在原地址上进行值的修改,因此使用final修饰一个引用类型变量意义并不是特别大,当然对于字符串这种不可在原地址上修改的变量类型,使用final修饰仍然和final修饰基本类型一样奏效。
当final修饰一个属性的时候,它的真正含义为:**在这个属性被分配内存空间的时候,必须被初始化并且不可变。**关于初始化,实际上就是第一次赋值的这个过程,尽管每个变量被声明出来的时候都有一个默认值0,但这个值被系统认定为除防止空指针异常之外没有更多的意义,简而言之就是这个值仅仅是为了安全性,对于问题的解决没有一点意义,因此Java虚拟机系统不认为这是初始化,必须是人为的赋值才是初始化。因此对于一个属性来说,当它被final修饰的时候,在被分配内存空间的时候必须进行初始化。因此对于一个类我们可以这样写:
public class Cat{
public final int a = 1;
public Test(){
}
}
在声明时就进行初始化。也可以像下边这样:
public class Cat{
public final int a;
public Test(){
a = 1;
}
}
在构造器中初始化,同时需要注意的是,当我们有多个构造器的时候,必须保证每个构造器中都要对a变量赋值,这样才能保证a在被分配内存的时候一定会进行初始化。
既然知道了final这个特性,那么结合static的特性,我们可以得出一个结论:静态常量在被声明的时候就必须被初始化。这是因为static的特性,被static修饰的属性在类加载的时候,就会被分配给内存,因此这种静态常量属性在实例化之前,或者说整个类被加入到方法区的时候,在它进入系统的最开始的开始的那一刻,里边的一个静态常量属性就已经被分配给内存了,因此在这个时候它就需要进行初始化了,因此我们必须在声明它的地方就给它赋值,否则就会直接违背final的规则,因为当系统检测到类代码中它被声明的那个地方时,它的内存就已经要被创建了,这时如果不为它初始化,就会违反final对于属性的限制规范。
final对于普通的变量没有这样的约束,当使用final标识符来修饰普通变量时,这个变量只要在当前的方法块中被初始化一次就行,至于在哪里进行初始化没有关系,但是不能在整个块中都不初始化,否则也会报错。
工厂模式是设计模式的一种,在大学期间的Java课中会讲到工厂模式,但实际上这种模式是一种非常复杂的设计模式,没有大量的代码经验是很难真正理解的,因此初学的我在此不进行深入学习,先了解即可。常见的设计模式有23中,目前先记住这些即可。
在类中可以加入代码块,也就是类似于方法中的逻辑代码。代码块在类加载的时候就被被执行,因此当我们在一个类中写了代码块的时候,这个类被加载到内存的方法区上的时候,也就是这个类第一次进入系统的时候,就会被执行。实际上类中的域的声明就属于一种代码块,域的声明也会在类加载的时候进行,只不过普通的域不会在这个时候被分配内存,只有静态域才会被分配内存。当然代码块和域有着本质上的区别,但是他们都会在类加载的时候被执行,关于类加载的时候,类中代码的执行顺序在之后会进行详细解析。
包在Java中实际上就是文件夹,Java类存在包中,一个包内的类可以进行相互访问,而不同包内的类难以进行相互访问,那么如何进行跨包访问呢?实际上我们只要将一个类的路径导入到一个程序中,这个程序就可以通过路径直接访问这个类,我们如果将包路径导入进一个程序,那么这个程序就可以直接访问这个包中类型为public的类了。这个过程实际上就是使用import操作进行导包的过程。
需要注意的是只有类声明前加了public的类也就是主类才能挎包调用,我们都知道在一个Java源文件中只能存在一个主类,其他的类是不能跨包使用的,只能供包内使用。
静态方法不是属于某一个对象的,而是属于所有对象的,或者说是属于这个类的,静态方法也会在类加载的时候获得内存,从Java运行时上来看,静态方法会在类加载的时候在方法区的静态资源区获得一片内存空间并变得可以被调用,调用静态方法直接使用类名进行调用。
这个问题之前已经进行了详细的解释说明,Java中仅存在值传递,不存在任何引用传递,详情点击此处查看。