java-面向对象
Last updated on a day ago
面向对象的概念
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一一实现,使用的时候依次调用。面向对象则是把构成问题的事务按照一定规则划分为多个独立对象,然后通过调用对象的方法来解决问题,通过多个对象配合来解决问题,这样当程序功能需要改动时,只需要修改个别的对象就可以了,从而更容易维护。
面向对象的特点
主要可以概括为:封装性、继承性、多态性
- 封装性:封装是面向对象的核心思想,将对象的属性和行为封装起来,不需要让外界知道具体实现环节,这就是封装思想。例如:用户使用计算机只需要手指敲键盘就行了,不需要了解计算机的工作原理,就算知道,在使用时也并不依赖这些原理。
- 继承性:继承性主要描述的时类与类之间的关系,通过继承,可以在无需重新编写原有类的情况下,对原有类的功能进行扩展。例如有个汽车的类,描述了汽车的普遍特征和功能,而轿车的类中不仅要包含汽车的特征和功能,还应该添加轿车特有的功能。这时可以让轿车类继承汽车类,然后单独添加轿车特征。继承不仅增强了代码的复用性,提高了开发效率,还为程序的维护提供了便捷。
- 多态性:多态性指的是在程序中允许出现重名的情况,它指在一个类中定义的属性和方法被其他类继承后,它们可以具有不同的数据类型或表现出不同的行为,这使得同一个属性和方法在不同的类中有不同的含义。
类与对象
类是对某一类事物的抽象描述,而对象用于表示现实中该类事物的个体。比如玩具模型可以看成一个类,将一个个玩具看成对象。
类用于描述多个对象的共同特征,它是对象的模板。对象用于描述现实中的个体,它是类的实例。
类的定义
要在程序中创建对象,首先需要定义一个类,类是对象的抽象,用于描述一组对象的共同特征和行为。类中可以定义成员变量和成员方法,其中成员变量用于描述对象的特征,也被称为属性;成员方法用于描述对象的行为,可简称为方法
1 |
|
在Java中,定义在类中的变量被称为成员变量,定义在方法中的变量称为局部变量,如果在某个方法中定义的局部变量与成员变量同名,是允许的,此时方法中通过变量访问到的是局部变量,而非成员变量
对象的创建与使用
在程序中,可以使用new关键字创造对象
1 |
|
在上述代码中,“new Person()”用于创建Person类的一个实例对象,”Person p”则是定义了一个Person类型的变量p。中间的等于号将Person对象在内存中的地址赋值给变量p,这样p就持有了对象的引用。
在创建对象后,可以通过对象的引用来访问对象的所有成员
1 |
|
运行的结果为”我的年龄是10岁”
在实例化对象时,针对成员变量的类型的不同,java虚拟机将会赋予不同的初值。
成员变量类型 | 初始值 |
byte | 0 |
short | 0 |
int | 0 |
long | 0L |
float | 0.0F |
double | 0.0D |
char | 空字符'/u0000' |
boolean | false |
引用数据类型 | null |
当对象被实例化后,在程序中可以通过对象的引用变量来访问该对象的成员。需要注意的是,当没有任何变量引用这个歌对象时,它将成为垃圾对象,不能再被使用。
1 |
|
运行结果:
我是一个人
报错信息
在将p1赋值null后,p1指向了一个空指针,被p1引用的Person对象就会失去引用,成为垃圾对象
类的封装
类的封装是指在定义一个类时,将类中的属性私有化,即使用private关键字来修饰私有属性,使他只能在它所在的类中被访问,如果外界想要访问私有属性,需要提供一些使用public修饰的公有方法,其中包括用于获取属性值的getXxx方法和设置属性值的setXxx方法。
构造方法
在实例化一个对象后,要修改对象的属性必须要通过直接访问对象的属性或setXxx方法的方式才可以。如果要在实例化的同时也为属性赋值,则需要通过构造方法。构造方法是类的一个特殊成员,在类实例化时自动调用。
构造方法的定义
首先构造方法必须满足三个要求
- 方法名与类名相同
- 方法名的前面没有返回值类型的声明
- 在方法中不能使用return语句返回一个值,但可以单纯些return语句作为方法的结束
无参构造方法实例
1 |
|
运行结果:
无参的构造方法被调用了
既然有无参的,那必有有参的
有参构造方法实例
1 |
|
运行结果:
今年我10岁了
构造方法的重载
和普通方法一样,构造方法也可以重载,重载就是允许多个同名方法存在,只要参数个数或参数类型不同即可。
每个类都至少有一种构造方法,若没定义,则系统会自动创建一个默认的构造方法,也就是说,以下两种方式是等效的
1
2
3
4
5
6
7
//不定义构造方法
class Person{}
//定义无参无内容的构造方法
class Person{
public Person(){}
}这也就是为什么,我们未定义任何构造方法却仍可以使用new Person()实例化对象
若定义了构造方法,则系统会舍弃默认构造方法,如定义了一个有参的Person(int a),则在实例化对象时无法使用new Person(),
所以在定义构造方法时,为了防止出错,最好在手动定义一个默认方法
this关键字
像我们刚刚定义有参构造方法时,将a赋值给age属性,这样的定义方法会影响程序的可读性,最好是将表示年龄的变量统一为age,可这样会导致局部变量和全局变量冲突,使得方法无法访问全局成员变量age,为了解决,java提供了this关键字指代当前对象
1 |
|
对于第三顶,需要注意以下几点
- 只能在构造方法中使用this调用其他构造方法,不能再成员方法中使用
- 在构造方法中,使用this调用构造方法的语句必须位于第一行,且只能出现一次
- 不能在一个类的两个构造方法中使用this互相调用
垃圾回收
在java中,一个对象成为垃圾后仍会占用内存,时间一长导致内存不足。于是java引入了垃圾回收机制,java虚拟机会自动回收垃圾对象所占用得内存。当垃圾堆积到一定程度时就会自动释放,也可以调用
1 |
|
方法来通知java虚拟机立即进行垃圾回收。当一个对象在内存中被释放时,它的finalize()方法会被自动调用,因此可以通过定义finalize()方法来观察对象何时被释放。
1 |
|
运行结果:
对象作为垃圾被回收
对象作为垃圾被回收
static 关键字
静态变量
在定义一个类时,只是描述某类事物的特征和行为,并不会产生具体的数据,只有在new创建实例对象后,系统才会为每个对象分配空间,存储各自的数据。但有时我们希望某个特征被所有对象共享,比如学校中的每个学生都在一个学校内,他们的学校名称地址等属性应是相同的。此时不需要在每个学生的内存空间中都定义学校名称,可以在对象以外的空间定义一个表示学校名称的变量,用于共享。
1 |
|
静态方法
在开发时,有时会希望不创建对象就调用某个方法,只需要在定义的方法钱加上static关键字即可。静态方法可以使用类名.方法名调用。
静态代码块
用一对大括号括起来的几行代码被称为代码块,用static修饰的代码块被称为静态代码块。当类被加载时,静态代码块会被自动执行,由于类只加载一次,因此静态代码块也只执行一次。
内部类
在Java中允许在一个类的内部定义类,称为内部类,根据内部类的位置、修饰符和定义的方式可分为成员内部类、静态内部类、匿名内部类、局部内部类。相对的,内部类所在的类称为外部类
成员内部类
成员内部类是普通的内部类,成员内部类可以无限制访问外部类的所有成语和方法(包括private和静态成员),当内部类存在和外部类同名的方法或成员时,默认访问的是内部类中的方法或成员,此时可以用this访问外部类。
虽然成员内部类可以无限制访问外部类,但外部类访问成员内部类的成员就需要先创建一个内部类对象,再通过这个对象来访问。
创建成员内部类对象
成员内部类是依靠外部类存在的,所以要创建成员内部类对象要先创建外部类对象,方法有2种
1 |
|
局部内部类
局部内部类和成员内部类类似,但是是定义在方法或作用域内,且没有访问外部类的权限
1 |
|
匿名内部类
静态内部类
静态内部类相当于外部类的一个类属性,可以脱离外部实例化,直接初始化
创建静态内部类
只需要在成员内部类的前面加public static
1 |
|
结果
1 |
|
继承
继承的概念
类的继承是指用现有的类去创建新的类,子类可以继承父类的属性和方法。
继承通过extends关键字声明,格式为:
1 |
|
比如dog狗这一类,可以从animal动物类继承,同时
1 |
|
在java中只有单继承,一个类只能有一个父类,但是可以有多个子类
重写父类方法
在继承中,子类会自动继承父类的方法,但有时需要对父类的方法进行一些修改,这就需要在子类的对父类的方法进行重写(不是重载,重载在前面)
1 |
|
super关键字访问父类成员
在重写父类方法后,我们直接调用的就是重写后的方法,如果想调用父类未重写的方法,就需要通过**super
关键字**
1 |
|
super
也可以访问父类的构造函数方法
1 |
|
在初始化子类的时候,父类的构造函数会自动调用,如果父类的构造函数会对成员进行修改,修改后的成员也会被继承到子类
1 |
|
注意:如果在父类只声明了有参构造函数,则在子类的继承中,必须显式的调用父类的构造方法,否则会报错
1 |
|
final关键字
final关键字可以用来修饰类、变量和方法
- 被修饰的类不能被继承
- 被修饰的方法不能被子类重写
- 被修饰的变量是常量,只能被赋值一次
抽象类和接口
抽象类
在定义类的时候一般都需要定义一些方法来描述它的行为特征,比如dog类可以有shout()方法,表示它的叫声,但对于比较广泛的类,比如animal类,dog可以从animal继承到shout方法,但是每个动物的叫声都不同,那么animal类中的shout方法该怎么写呢?
可以留空交给子类重写。不写方法体的方法就是抽象方法。带有抽象方法的类就是抽象类。抽象类必须用abstract关键字修饰,用abstract关键字修饰的抽象类不一定含有抽象方法
1 |
|
在使用的时候需要注意以下几点:
- 方法体为空不等于抽象方法:比如
void f(){}
并不是一个抽象方法,不可以用abstract
修饰。抽象方法是没有方法体,而不是方法体为空 - 抽象方法一定要被重写:在子类继承之后,抽象方法一定要进行重写,*否则会报错
- 抽象类不能被实例化:如果进行编译,会提示“xx是抽象的; 无法实例化”
接口
接口是由常量和抽象方法组成的特殊类,是对抽象类的进一步抽象。定义接口用**interface
**关键字修饰,格式如下:
1 |
|
接口克服了类单继承的缺点,一个接口可以通过extends
继承多个父接口,一个类可以实现多个接口(类重写接口的抽象方法称为实现,可以理解成不同类型间的继承)。接口中定义的方法默认是**public abstract
,如果接口为public
则接口中的变量和方法全部为public**。
由于接口里只有抽象方法,无法进行实例化,需要通过类似extends
继承的方法去调用并重写抽象方法,这需要用到关键字**implements
**(实现)。格式如下:
1 |
|
和抽象类一样,子类需要对父类进行重写,但是不同的是,如果实现接口的是一个抽象类,则可以只实现部分接口
多态
多态的概念
多态也就是一个对象拥有多种形态,具体指的为编译时和运行时的状态不同。代码中体现为父类做类型,子类做实现(父类引用指向子类),代码格式为:
1 |
|
举个例子
1 |
|
对比之下,多态的写法更加统一。
那么多态中会执行哪个类中的方法呢?答案是子类的中的方法,new的是哪个类,使用的就是谁的成员方法,如果没有,就会到父类找。而如果是访问成员变量,则是相反,用的是父类的成员变量,总结就是编译看(等号)左边,运行看右边
多态应用
有时,我们可能会有多个子类,比如Animal,可以有Cat、Dog等子类,假设现在我需要写一个方法,要求输入Animal类的对象,自动使用其中的方法,如果java没有多态性,那么你需要给每一种子类单独写一个方法
1 |
|
而由于多态性,现在你可以用父类Animal做形参类型,并支持传入子类类型
1 |
|
上面的方法可以把实例化省略,变成
1 |
|
而由于我们形参是Animal。所以在userAnimal方法内部,实际上是
1 |
|
类型转换
在上面的代码中,我们将Dog类传递给了Animal类,可以理解为进行了类型转换,这种称为”向上转型“,我们在使用useAnimal()
方法时,虽然传入了Dog对象,但是并不能使用Dog中特有的方法,只能使用Animal类中的方法,如果想使用,就需要对Animal进行类型转换,变为Dog()子类,这种转换称为”向下转型“。方法如下
1 |
|
其实就是进行强制类型转换
但是由于有多个子类,我们要怎么判断传入的类是不是Dog呢?就需要使用instanceof关键字
instanceof判断类
可以用来判断对象是否为某个类或接口的实例或子类实例,格式如下:
1 |
|
它会返回bool值。
Object 类
Object类是所有类的父类或间接父类,所有对象(包括数组)都实现了这个类的方法。Object类中的常用方法有
方法名称 | 说明 |
---|---|
equals() | 指示其他某个对象是否与此对象“相等” |
getClass() | 返回此Object的运行时类 |
hashCode() | 返回该对象的哈希值 |
toString() | 返回该对象的字符串表示 |
下面是个演示toString的例子
1 |
|
对象a中没有定义toString方法但仍能正常使用,这是因为从Obejct中继承了。
toString的代码具体如下
1 |
|
匿名内部类
匿名内部类是定义在类的内部,没有名字的内部类,是一种实现接口的简便方法
1 |
|