目录
面向对象简介
java是面向对象的编程语言。类就是一个自定义的类型,它包含自身的属性,也能执行各种方法。
类相当于对现实世界事物的抽象,现实的事物比如汽车,汽车也有各种属性,比如重量、速度、颜色等,也能执行各种方法,比如加速、停车、开音乐、倒车等。
JAVA的类就是通过自定义新的类型,来模拟现实世界的事物。
和现实世界一样,汽车只是一个类别,世界上有非常多汽车,它们有很多相似之处。它们有类似的属性和方法,比如都有速度、加速度等属性,也都能执行行驶、停车等方法。但是具体每一辆车,它们的属性的值和方法的执行有区别,比如不同品牌的车的速度、颜色等属性会有差异,行驶的表现也有差异,跑车就是比普通小轿车行驶更快。
所以,JAVA的类和对象就是现实世界事物的模拟,类相当于你定义了一个新的事物的类别,对象就是你创建出具体属于这个类别的事物。
所以,我们需要先定义类,确定该类别的属性和方法,再创建出具体的对象,让这些对象通过互相交互达成我们想要的结果。
类之间的关系
类之间常见的关系有:
- 依赖(use a)
- 聚合(has a)
- 继承(is a)
A对象的方法如果使用了B对象作为参数,那就是A use a B,比如订单对象使用商品对象作为参数,可以添加或者删除商品。
我们要尽可能减少依赖关系,目的是降低类之间的耦合性。
A对象的实例变量的值包含B对象,那就是A has a B,比如人这个对象包含手这个类对象。
A类继承自B类,那就是A is a B,比如狗继承了动物这个类。
# 定义类 **定义类的通用语法简单概括为:** [修饰符] class 类名 { 零个或多个构造器定义... 零个或多个成员变量... 零个或多个方法... } 从上可以发现,类的组成核心有三部分:**构造器、成员变量、方法**。 **构造器**用于**生成属于该类的具体对象**。 **成员变量**也就是类的属性,用于**定义该类所包含的状态数据**。 **方法**是类的行为,用于**定义该类的行为特征或者功能实现**。
定义成员变量
只要明白成员变量是类的状态数据,就很好理解了。
程序肯定需要划分内存空间来记录属于每个对象的状态值,这些值肯定是通过变量的形式存储在内存中。
所以,只需要从定义变量的角度去理解就很简单了。
定义成员变量的语法格式如下:
[修饰符] 类型 成员变量名 [ = 初始值];
修饰符:可以省略,也可以加上访问修饰符:public、protected、private,也可以加上类修饰符:static,以及可以加上final修饰符表示该成员变量被第一次赋值后再也不可变。
成员变量的修饰符可以分为3部分:
1.访问修饰符:这决定了成员变量可被访问的范围,比如:如果是private,说明该成员变量只能在该类的内部访问,是私有的。如果是public说明该变量是公开的,可以在任何地方被访问。
2.类修饰符static:它的作用是表明该成员变量是类变量,它是属于这个类的状态。如果某个状态与具体对象无关,就应该把该成员变量定义为类变量。比如人一定只有一个脑袋,脑袋的数量和具体的人无关,所有人类都有一个脑袋,脑袋的数量就是一个类变量。
3.final修饰符:final修饰符表示该成员变量的值一旦被初始化后,就再也不能改变。也就是只能被赋值一次的意思。这在现实中也有对照,比如生成一只小狗,一旦生成,它毛发的颜色就不会改变,所以就可以给小狗的毛发颜色加上final修饰符。
这里只是简单介绍下这3类修饰符,之后会单独出博文具体讲解。
除开修饰符,定义一个成员变量和定义一个局部变量类似,需要有变量的类型,可以是基本数据类型,也可以是引用类型。成员变量的名字最好是一个英文名词,名词适合描述状态数据。也可以在定义成员变量的同时进行初始化。
关于初始化值得注意:
假如没有给局部变量设定初始值,系统不会给局部变量设定默认的零值(0、0.0、null之类),没办法直接访问未初始化的局部变量。但是,如果没有给成员变量显示设定初始值,系统会自动给成员变量设定默认的零值。
定义方法
定义方法的通用语法为:
[修饰符] 方法返回值类型 方法名(形参列表)
{
//方法体
}
方法非常类似C语言的函数,只是多了修饰符。
方法的修饰符有很多,可以分为4类:
1.访问控制修饰符:这和成员变量一样,用于控制方法的访问权限,包括private、protected、public。
2.类修饰符static:表明该方法属于这个类而不是某个对象,这也和成员变量类似,表示该行为是这个类具有的。
3.final修饰符:这表明当前类的该方法不能被子类重写(注意是重写而不是方法的重载)。
4.abstract修饰符:表明该方法为抽象方法,该方法没有方法体,必须被子类重写才有价值。
分析可知:final修饰符和abstract修饰符,前者不能被重写,后者必须被重写,所以final和abstract修饰符只能二选一。
方法的返回值类型需要和return语句对应的值匹配。这里的匹配包含了能自动类型转换和父类子类的情况。
比如:返回值类型为double,return一个int类型的值是被允许的,因为可以进行自动类型转换;返回值类型为父类,return子类也是允许的,因为子类就是特殊的父类,可以兼容。
方法的参数列表与方法名组合成方法的签名,也就是说这两部分可以唯一确定一个方法。方法名和参数列表完全相同,但返回值不同的两个方法,是不能在一个类中被定义的,因为返回值不能用来区分方法。
这其实很好理解,就像数学里面的函数y = f(x),f(x)已经确定了函数,和返回的最终结果无关。
构造器
定义构造器的语法格式如下:
[修饰符] 构造器名(形参列表)
{
// 构造器的执行体
}
构造器用于创建对象,它的组成相比方法更简单。
修饰符:构造器的修饰符只能是访问控制符,也就是:private、protected、public三种。
与方法有关的其他修饰符构造器都不需要,因为构造器不能被子类继承,所以就没有重写的必要(但是构造器可以被重载)。所以abstract和final这两个与重写有关的修饰符都不需要。而构造器用于创建对象,不可能用static修饰。
构造器没有返回值类型,因为构造器的返回值类型是固定的,返回的就是当前生成的对象的索引,所以类型一定是当前类本身。因此,构造器省略了返回值类型。
如果有个“构造器”有返回值,那系统不会把它当做构造器,而是当成一个普通的方法,只不过方法名和类名相同。
构造器的名字一定和类名同名,这是一种合理的规定。
构造器的形参列表和方法的形参列表完全相同,都是由参数类型和形参名确定。
由于构造器的名字是确定的。所以,不同的构造器只能靠形参列表来区分。
创建和使用对象
创建对象
创建对象只需要用new关键字调用构造器即可。
创建对象的通用语法为:
new 构造器(参数列表);
构造器会返回新创建对象的引用。所以,还需要定义对应的引用变量存储对象的引用。
定义引用变量的通用语法为:
类名 引用变量名;
所以,创建对象实际需要两步:第一步用构造器创建对象,第二步把对象的引用赋值给对应的引用变量。也可以直接将两步合二为一。
创建对象并赋值给引用变量的通用语法为:
类名(var) 引用变量名 = new 构造器(参数列表);
如果两步一起写,通过构造器就可以知道该对象的类型,所以开头的类名可以改写成var,会更加简洁。
匿名对象
这是一种很特殊的用法,对象创建出来是一次性使用的。
语法为:
new 构造器(参数列表).方法名(参数列表);
相当于创建该对象的目的就是调用这一次方法。
这种方式虽然很少见,但也可能会使用,比如和final有关的用法可能会用到它(暂时不用深究)。
使用对象
对象一般有2个用途:
1.访问对象的实例变量(由于有封装,第一个用途一般不会直接调用)
2.调用对象的方法
语法非常简单:
1.类.类变量|实例.实例变量
2.类.类方法|实例.实例方法
对象与引用的底层机制
对象是存储在堆内存中的,只能通过引用的方式访问对象。引用变量相当于一把钥匙,可以找到对应的对象。可以用C语言的指针来理解引用变量。引用变量存储的只不过是找到对象的方式,对象的真正数据并没有存储在引用变量中。
引用变量的存储位置得看情况:如果引用变量是局部变量,那就存储在栈内存中,如果引用变量本身是某个对象的实例变量,那就跟随该对象存储在堆内存中。
可能会存在多个引用指向同一个对象,它们都可以访问或者改变对象的实际数据。
如果没有引用指向某个对象,那这个对象迟早会被垃圾回收器回收掉。
this关键字
this的含义为:当前对象。
我的理解是,this是为了解决鸡生蛋和蛋生鸡的问题。
现实生活中是,我们需要先创建某个类的对象,才能执行对象的方法或者访问对象的实例变量。
但是,JAVA里面需要在类的内部做成员之间的交互,分为以下几种:
1.构造器通过this(形参列表)调用其他被重载的构造器。
2.构造器、方法、实例变量初始化时或者初始化块里通过this.实例变量或者this.方法名调用类里的实例变量或者其他方法。
这些内部成员在交互时,主调自然是同一个对象。但是,此时正在构建类的代码内部,对象并没有被创建出来,也就是还只是鸡蛋,没有生出小鸡。但是在做类的构建工作时,需要让这些成员进行交互。所以我们用this来代表当前对象,表明这些成员之间的交互发生在同一个对象中。等之后对象被创建出来,this就等价于那个对象。
这在现实生活中也很常见,人的很多动作都需要其他动作协调。比如打羽毛球的动作需要依赖跳跃的动作,this的作用就是表明,这2个动作的交互是同一个对象做出的(是同一个人在打羽毛球的时候,依赖了跳跃这个动作)。
在类的内部,this暂时没有确定的值,因为对应的对象并没有被创建出来。
在外部程序创建出某个对象后,当调用该对象的某个方法时,会把该对象的引用作为参数传递给那个方法。对象的引用这个参数并没有写到方法的参数列表里面,而是作为第一个参数隐式的传递。我们把它称为隐式参数。这个参数传递进某个对象的方法的内部后,所有的this都会被赋值为当前对象的引用。
所以,通过这种方式,当调用某个对象的方法后,方法内进行成员之间的交互时,系统会明白这些实例成员都是属于同一个对象,而且根据this的值,能清楚知道是哪一个对象。
this与构造器
构造器其实是一种特殊的方法,方法里面的this使用方式,构造器里也可以使用。可以通过this.实例变量名和this.方法名在构造器体内调用同一个类里的方法和实例变量。此时this代表当前正在被初始化的对象。
除此之外,如果想在构造器内调用重载的构造器,可以使用:this(形参列表);
这行代码必须放到构造器的第一行,而且一个构造器只能有一次这种形式的this调用。
此时,程序会跳转到与该形参列表符合的另一个重载构造器。
这里的this也是代表当前正在被初始化的对象,它的含义是:当前构造器的执行需要依赖同一个对象的另一个构造器代码。
this的省略
理解this是为了表明成员之间的交互来源于同一个对象后,实际写代码经常会省略this,就跟人说话喜欢省略主语一样,比如“在干嘛?”其实是“(你)在干嘛?”。
实际写代码,省略this是大部分情况,但是有的情况必须使用this才能达成目的:
1.方法或者构造器体内的局部变量和想要访问的实例变量同名
如果只写变量名,那系统会认为你是在访问局部变量,实例变量虽然存在,但是会在方法内被屏蔽掉。
如果确实想访问的是实例变量,就需要加上主调this.
比如最常见的setter方法里的:this.实例变量名 = 同名的局部变量名
2.构造器调用重载的另一个构造器:this(形参列表);
这算是this的另一种用法,语法上去掉了点(.),增加了(形参列表)。这是因为某个构造器的执行需要依赖同一对象的另一个重载构造器,所以用this表明调用当前正在初始化对象的另一个重载构造器。
this与静态关键字static
类里的成员,实例变量和实例方法与对象绑定,而类变量、类方法、静态初始化块加上了static修饰符,与类本身绑定。
而this与这些static修饰的成员无关,因为this表示当前对象,是和某个对象绑定的。
所以,所有static修饰的类成员代码内部,不能出现this关键字,因为这些类成员根本与单个对象无关,它们是所有对象共享的。
但是,java有一个非常不合理的规定。可以用对象引用名.类变量和对象引用名.类方法这种格式,访问类变量和类方法。这种不合理的设计的底层,其实也会把对象引用名替换为类名。
这导致在类的内部,可以在对象成员内部,用this.类变量和this.类方法的格式,访问同一个类内部的类变量和类方法。同样的,这也只是java的设计缺陷,底层实现会把this关键字替换为类名。所以,不推荐用这种方式使用this。
你只需要记住:this就代表当前对象,在这种含义下,才需要使用this。如果要代表当前类,请使用类名.的形式。
this作为返回值和实参
只需要理解this表示当前对象,就可以很自然的理解这两种用法。
如果this作为返回值:return this; 这就表明返回值是当前对象的引用。
如果this作为实参,就表明传递的参数值是当前对象的引用。