面向对象
面向对象思想
概述
面向对象强调通过调用对象的的行为来实现自己需要的功能,而不是自己去逐步实现。
特点
将复杂的事务简单化,将我们从执行者变为指挥者。
面向对象三大基本特征
封装、继承、多态
封装
对事物进行包装,隐藏事物的部分细节,防止该类的代码和数据被其他类随意访问,想要访问该类的数据,就必须通过指定的方式(getter/setter)。
通过封装,可以加强代码的安全性,也能让代码更容易让别人理解与维护。
封装的原则
将属性隐藏(通过pravite修饰符),若是需要对某个属性进行访问,则提供公共方法对其访问。
封装的好处
- 可以隐藏事物的细节,通过第三方暴露的按钮或者接口来保证外部可以间接的访问与调用这些被隐藏的事物。
- 提高程序中代码的复用性,节省大量时间
- 封装是隐藏实现的细节,对外暴露某些使用细节的功能。
封装的实现步骤
- 使用 private 关键字来修饰成员变量。
- 对需要访问的成员变量,提供对应的一对 getXxx方法 、setXxx 方法。
private的含义
- private是java中的关键字。一个权限修饰符,代表最小权限。
- public、和private关键字都属于java中的一种修饰符,用来修饰成员(成员变量、成员函数)的访问范围。
private:私有的 访问范围只能在本类中使用 (在java中属于最小的访问权限)
public:公共的、公开的 访问范围没有底限 (在java中属于最大的访问权限) - 被private修饰后的成员变量和成员方法,只在本类中才能访问
案例
需求:描述员工。
-
定义一个Employee类,并在类中定义两个变量name、age和函数say(),并在函数中打印name和age两个属性的值;
-
定义一个测试Employee类的一个类EmployeeTest,在这个类中创建刚才定义好的Employee类的对象,并使用创建的employee对象给属性赋值,并且调用say()函数。
class Employee{
private String name;
private int age;
void say(){
System.out.println("姓名:"+name+"年龄:"+age);
}
public Employee(){
}
public Employee(String name,int age){
this.name=name;
this.age=age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name= name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age= age;
}
}
/**
员工测试类
*/
class EmployeeTest{
public static void main(String[] args){
//创建员工的对象
Employee employee = new Employee();
//通过set方法对其赋值
employee.setname="萌新";
employe.setage=18;
//调用方法
employee.say();
}
}
封装优化 -----this关键字
this的含义
this代表所在类的当前对象的引用(地址值),即对象自己的引用。
Ps:方法被哪个对象调用,方法中的this就代表那个对象。即谁在调用,this就代表谁。
this的使用格式
this.成员变量名
当成员变量和局部变量重名时,可以使用this进行区分。
Ps:
- 方法中只有一个变量名时,默认也是使用 this 修饰,可以省略不写。
- 在函数中,如果局部变量和成员变量名字和类型都一致,那么函数会先使用局部变量。(就近原则)
继承
所描述的事物和事物之间的关系。
代码体现:
在需要继承其他类的类名后使用extends关键字。
这时被继承的类称为父类,实现继承的这个类称为子类。
例:
class 类名1 extends 类名2{
//代码块
}
特点
- Java只支持单继承,不支持多继承。
(单继承:一个子类只能有一个父类。多继承:一个子类可以有多个父类) - Java支持多重继承(继承体系,子类继承父类后,就可以使用父类中的成员,private修饰的成员不能被子类继承)
继承后的成员变量
好处:父类定义好的成员,子类可以直接使用,提高代码的复用性。
弊端:打破封装性。
成员变量重名:子类的成员与父类同名,可以使用super关键字来访问父类的同名成员。
super.父类成员(变量,函数),
super(参数);访问父类的构造函数。
this与super的区别
- this表示的是本类中的成员,super表示父类的成员
- Java中,this是一个引用变量,保存的是当前调用函数的对象的内存地址。super表示的是父类的内存空间,但不是父类对象,super的作用是标记哪个类是父类。
- this表示的是子类对象在堆中开辟的空间,而super只是标记的子类对象空间中父类所占用的部分。
继承后的成员方法
子父类中的方法重名:
子类父类中出现的不重名方法,调用不受影响,对象在调用方法时,会扫描子类中存在的方法,若子类中存在,便执行子类方法,若父类存在,则执行父类方法。
若是子类父类中的成员方法重名,这是会出现一种特殊情况,方法重写(声明不变,重新实现)。
重写
子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。
重写与重载的区别
重写:发生在子父类中,且需有继承关系。(返回值类型相同,方法名相同,参数列表相同)
重载:发生在同一个类中,方法名相同、参数列表不相同。与返回值无关。
重写注意事项
1、必须有继承关系。
2、父类中的private成员函数,不参与子类中方法重写。
如果子类中存在一个和父类中私有方法相同的方法时,子类中的方法不是重写方法,它是子类中定义的属于子类自己的方法(父类中私有的方法不参与子类方法重写)
3、函数的复写要求子类中的函数返回值类型 、 函数名 、 参数必须和父类全部相同。
4、子类在复写父类的函数时,子类的方法访问权限,必须大于等于父类的权限。
构造方法
当我们在创建一个对象的时候,如果需要对象创建好之后,就必须拥有一些属于自己本身的特定数据,而不是后面通过程序去修改。那么也就是说在创建对象的过程中,就应该把这些数据赋值完成。
构造方法介绍
构造函数(方法):构造,就是构建和创造。
所谓的构造函数就是在创建对象时(使用new时),会自动调用的函数。
构造方法格式
修饰符 构造方法名(参数列表)
{
}
构造方法定义的细节
- 构造函数是用来创建对象的,它不需要书写返回值类型。
- 构造函数的名字要求必须和当前的类名一致。因此构造函数的名称书写和类名一致。
- 参数列表,可以和一般函数的参数列表一样。
构造函数的作用:就是为了给对象初始化时使用的。(就是为了给对象成员变量初始化值使用的)。
__ 当我们在程序描述一个事物的时候,就是在写一个类,这时如果这类事物产生的对象,在对象创建完成之后,就必须明确属于这个对象的一些初始化数据,这时在描述这个事物的那个类中,一定要书写对应的构造函数。__
构造方法的功能
在创建对象的过程中,根据new对象时传递的数据,对当前创建出来的这个对象中的成员变量进行指定数据的初始化。
默认构造方法
默认构造方法:其实就是没有参数的构造方法(无参构造方法)。
当我们创建一个类时,若是不写任何构造方法,那么使用javac编译源码后,会在自动生成的.class文件中自动添加一个无参的构造方法。
我们把这成为默认的构造方法。
构造方法与一般方法的异同
-
它们的执行时间不同:
构造函数是在创建对象的过程中执行。当对象创建完成了,构造函数就已经执行结束。一般函数执行时间有两种情况:
-
如果调用其他类中的函数时:一般函数通过是在对象创建完成之后,通过对象的引用来调用。
-
如果调用本类中的函数时:什么时候使用,什么时候调用。
-
-
它们的调用次数不同:
构造函数只有在new对象的时候,会被调用,一旦对象创建完成,我们不能手动的人为去调用构造函数。
一般函数可以通过对象随意的调用,没有次数限制。
-
构造方法的写法上,方法名与它所在的类名相同。它没有返回值,所以不需要返回值类型,甚至不需要void。
注意事项
- 如果你不提供构造方法,编译器会给出无参数构造方法。
- 如果你提供了构造方法,编译器将不再提供无参数构造方法。
- 构造方法是可以重载的,既可以定义参数,也可以不定义参数。
匿名对象
指没有名称的对象。创建对象时,只有创建对象的语句,却没有把对象地址值赋值给某个变量。虽然是创建对象的简化写法,但是应用场景非常有限。
格式:
new 类名(参数列表);
例:
new Scanner(System.in);
new preson();
局限性
因为匿名对象通常在对象仅需访问一次成员的情况下使用,使用如果对象需要多次使用某个成员,必须要给对象起名,原因是在程序中每次使用new关键字创建对象,那么堆中都会生成一个新的对象,而匿名对象一旦结束使用,就会立刻变成垃圾,等待回收。
抽象类
我们把没有方法主体的方法称为抽象方法,需要使用abstract关键字,在Java语法中,包含抽象方法的类被称为抽象类。
抽象类注意事项
1、子类在继承抽象类后,需要把抽象类中所有的抽象方法全部复写(重写)完成。
抽象类中定义所有子类的共性行为,然后由具体的子类针对自己的实际情况复写父类提供的基本行为,建立适合自己的功能体。
2、抽象类和一般类有什么区别?
抽象类肯定需要abstract修饰,一般类不能使用。
不管是抽象类,还是一般类,它们都是描述事物体系中的某个事物,只不过抽象类描述的一定是这个事物体系中的共性内容,抽象类不一定是最顶层的类,但一定不是最底层的类。
3、抽象类有没有构造函数,能不能创建对象?
有构造函数,但不能创建对象。
构造函数是给成员变量初始化的,抽象类中的构造函数就是为了子类对象初始化使用的,因为抽象类一定有子类,而创建子类对象的时候,在子类的构造函数中一定有super()语句会找自己的父类构造函数进行初始化动作。
多态
表示的是一个事物的多种表现形态。同一个事物,以不同的形态表现出来.
多态的前提
1、继承或者实现
2、父类的引用指向子类的对象
格式
//父类类型 变量名 = new 子类对象;
//变量名.方法名
Chinese c = new Chinese();
//使用本类表示自己,不会发生多态
Person p = new Chinese();
//使用人类来表示,会发生多态
多态成员变量与成员方法的使用特点
左右以等号为分界
左边的变量a的类型,称为声明类型
右边实际创建对象的类型,称为实际类型
成员变量:编译和运行都看左边
- 编译看左边:编译阶段看变量的声明类型,如果声明类型存在访问的变量名,可以编译通过
- 运行看左边:在代码运行阶段,实际获取的值,还是声明类型中定义的值
成员方法:编译看左边,运行看右边 - 编译看左边:编译阶段看声明类型中是否含有访问的方法,如果没有编译失败
- 运行看右边:运行阶段具体看子类中是否有这个方法,有执行子类的方法,如果子类没有该方法动态选择最近父类中的方法执行【动态绑定】
注意事项
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,执行的是子类重写后方法。
在使用多态时,只能使用父类的类型接收子类的对象。不可以使用子类的类型接受父类对象
多态的弊端
在有多个子类时,若是将其中一个子类的类型提升,那么编译时,编译器只会根据当前的父类类型去操作,而不会考虑子类类型,通过父类的引用调用方法时,只会去父类类型中的类去寻找成员,若是找不到则会编译失败。
多态不能使用子类特有的功能或属性:程序编译时,使用多态调用成员,则要求被调用的成员在父类中一定要存在,如果父类中没有会出现编译失败。
只要有多态的地方,一定发生类型的提升(肯定是把子类对象使用父类类型在表示)
多态中的转型
在多态中,若是想要使用子类特有的功能,就需要在多态操作时进行类型转换
两种类型转换:
1、向上转型(隐式类型提升)
父类引用指向子类对象
例:Animal an = new Dog();
2、向下转型(强制类型转换或者把父类类型转成子类类型)
把父类引用强制转换为子类引用
例:Dog d=(Dog) an;
强制转换格式:子类类型 子类引用名=(子类类型)父类引用名;
什么时候使用向下转型?
只要在程序中我们需要使用子类的特有属性或行为(方法、函数)的时候,才会使用向下转型.
无论是向上还是向下转型,最终都是子类对象做着类型的变化。和父类对象没有关系.
类型转换常见异常的解决办法
ClassCastException(类型转换异常)
只要有多态,就会有类型的转换。
把子类对象赋值给父类的引用,这时发生了向上的转型(隐式类型转换)。
如果我们需要使用子类的特有行为或属性,这时必须向下转型,需要把父类的引用转成具体所指的那个对象的类型。
在向下转型的时候一定要做类型的判断,防止ClassCastException异常的发生。
在Java中要使用向下转型,必须先做类型的判断,然后在转型Java中的类型判断 需要使用关键字 instanceof。(向下转型有风险,使用需谨慎)
格式
转型格式
被转的引用变量名 instanceof 被转成的类型
例:父类引用变量名 instanceof 子类对象所属的类名
判断格式
if( 父类引用变量名 instanceof 子类对象所属的类名 )
{
进行转换。
}
if( a instanceof Dog)
{
进行转换
}
多态的好处
实际开发的过程中,父类类型作为方法形式参数,传递子类对象给方法,进行方法的调用,更能体现出多态的扩展性与便利。