OOP
面向对象编程(Object-Oriented Programming,OOP)
Java的编程单位是类,对象通过类进行实例化(“创建”)
三个特性:
- 封装 Encapsulation (C++在类外可以定义函数)
- 继承 Inheritance
- 多态 Polymorphism
抽象数据类型
类就是模版
Java中用class表示一个类,类是一个抽象的数据类型 (C++中用struct表示一个类)
类和对象
面向对象的开发方法把软件系统看成各种对象的集合,对象就是最小的子系统,一组相关的对象能够组合成更复杂的子系统。
对象是对问题领域中事件的抽象。对象具有以下特性:
- 万物皆为对象 (问题领域中的实体和概念都可以抽象为对象。)
- 每个对象都是惟一的
- 对象具有属性和行为
- 对象具有状态 (状态是指某个瞬间对象的各个属性的取值。)
- 每个对象都是某个类的实例
类是具有相同属性和行为的对象的集合。
- 同一个类的所有实例都有相同属性,但属性取值不一定相同,事实上它们的状态不一定相同。
- 同一个类的所有实例都有相同行为,意味着它们具有一些相同的功能。
类是一组具有相同属性和行为对象的模板。面向对象编程的主要任务就是定义对象模型中的各个类。
- 类是一种类型:是引用类型
- 类是元数据:描述数据的数据
(因为数据在面向对象领域里以对象的形式存在,所以类是对象共有属性和方法的抽象描述。)
Java程序是各种对象相互交互作用、而不是类。
在java中,类的声明和实现在同一时间,而且必须在一起.
在C++中,类的声明和实现可以被分开.
类中方法的定义
修饰符 返回类型 方法名(参数列表)异常抛出类型 { …… }
- 必须有返回值,如果方法没有返回值,必须用void声明返回类型。
- 构造器没有返回类型,构造器加上了返回类型就变成了一个普通方法的声明了。
- 方法的修饰符可以同时有多个(多个修饰符之间没有先后顺序)
return_type:
如果方法定义了返回类型,那么在方法体里面就必须出现return语句,而且返回的数据类型要和声明一致;
如果没有返回类型的话(void),那么方法体里面可以写return,也可以不写return。当代码执行了return语句,就不往下执行了,直接退出这个方法并返回。
方法中定义的参数通常叫做形参,调用有参数的方法时,通常会传递一些实参给方法
参数传递
- 值传递: 对于基本数据类型,参数通过值传递。 (把实参的值复制一份再传给形参)
- 引用传递: 对于引用类型,参数通过引用(对象的引用)传递。 (把实参引用中地址值复制一份再传给形参)
this关键字
在方法调用、参数传递过程中,极有可能出现参数名称与实例变量名同名的情况。
在一个方法内,可以定义和成员变量同名的局部变量或参数,此时成员变量被屏蔽。
作用:
1. 区别成员变量和局部变量
2. 在类中可以表示当前类的对象
3. 在类的构造器中用this关键字可以调用类的其他的构造器
注意:
this只能出现在构造函数代码块中的第一句(前提是this这个时候代表的是调用其他构造器)
由this调用的构造器不会再创建新的对象
例如:
public class Car{
private String name;
private double price;
public Car(){
//无参构造器中调用了有一个参数的构造器
this("Tom");
}
public Car(String name){
//有一个参数的构造器中调用了有两个参数的构造器
this(name,100000);
}
public Car(String name,double price){
this.name = name;
this.price = price;
}
}
数据隐藏
这里的数据是指类中的属性
如何隐藏?
在封装的属性前面用private修饰,表示该属性不能被其它类访问和修改,只能被本类访问和修改,范围限制在本类内。
Java中封装有两个方面:
- 属性的封装
- 方法的封装
封装
对属性的封装
可以修饰属性的修饰符:
private
public
protected
“default”
(这四种修饰符可以修饰成员变量,也可以修饰方法)
在属性(实例变量)前加private, 然后通过统一的方法访问以及修改这些属性值;
使用get/set方法来访问类中被private修饰的属性。方法的封装:(用方法来封装代码)
使用者的角度:用户只关心方法的使用,不管里面到底怎么实现的细节.
编程的角度 :为了代码的重用.
方法重载(overload)
类的同一种功能有多种实现方式(方法名相同,参数不同)
一个方法是另一个方法的重载方法。
重载必须满足以下条件:
1) 方法名称相同。
2) 参数列表不同(参数类型、个数和顺序)。
注意: 返回类型可以不相同。
在一个类中不允许定义两个方法名相同,并且参数签名也完全相同的方法。
因为假如存在这样的两个方法,Java虚拟机在运行时就无法决定到底执行哪个方法。参数签名是指参数的类型、个数和顺序。
创建和初始化对象
定义类的形式、定义方法的形式,构建好类之后,程序要真实的运行,还得通过对象的交互来完成。
创建好了类,只是创建了构建对象的模板。
可以通过new操作符,快速地构建出对象。
使用new有以下作用(有以下几步):
1.为对象分配内存空间,将对象的实例变量自动初始化默认值;
2.如实例变量显式初始化,将初始化值赋给实例变量(把默认值覆盖掉);
3.调用构造方法;
4.返回对象的地址值;
(步骤细分。。。)
构造方法
(构造器/构造函数/构造方法)
定义:
- 构造方法的名字和类的名字相同
- 没有返回类型,有返回类型的构造器就变成了普通方法。
调用时刻:
在创建对象的时候调用;
- 注意:
是先创建对象,然后初始化对象中的属性值,最后在调用构造器。
- 注意:
- 作用:
在创建对象的时候做一些对象中数据的初始化工作。(因为在new的后面就是写的构造器)
- 构造方法的调用:
* 当前类的其他构造方法通过this语句调用它;
* 当前类的子类的构造方法通过super语句调用它;
* 在程序中通过new语句调用它;
构造方法重载
通过重载构造方法来表达对象的多种初始化行为。在一个类的多个构造方法中,可能会出现一些重复操作。为了提高代码的可重用性,Java语言允许在一个构造方法中,用this语句来调用另一个构造方法。
1.假如在一个构造方法中使用了this语句,那么它必须作为构造方法的第一条要执行的语句(不考虑注释语句)。
2.只能在一个构造方法中用this语句来调用类的其他构造方法,而不能在实例方法中用this语句来调用类的其他构造方法;
3.只能用this语句来调用其他构造方法,而不能通过方法名来直接调用构造方法。
默认的构造方法
构造方法可分为两种:
1) 隐含的默认构造方法(无参构造器);
2) 程序显示定义的构造方法;
在Java语言中,每个类至少有一个构造方法。
为了保证这一点,如果用户定义类中没有提供任何构造方法,那么在运行的时候JVM将自动提供一个隐含的默认构造方法。
该构造方法没有参数,用public修饰,而且方法体为空,格式如下:
public ClassName(){} //隐含的默认构造方法
在程序中也可以显示地定义默认构造方法。
如果类中显式定义了一个或多个构造方法,那么Java语言便不再分配隐含的默认构造方法。
子类
什么是继承?
父子类间的继承关系也叫“is a”关系。这种关系通过类声明上的extends关键字体现。
一个子类只有一个父类,一个父类可有多个子类。
为什么要继承?
- 站在巨人的肩膀上;通过继承,我们可以快速构建出一个带有丰富功能的新类;
- 不修改源代码,修改既有类的行为;通过继承,在子类中构建父类中一样的方法,可以改变父类方法的行为。
Object类简略介绍
所有的Java类都直接或间接地继承了java.lang.Object类。
Object类是所有Java类的祖先,在这个类中定义了所有的Java对象都具有相同行为。
继承
子类继承了父类的属性和方法:
1) 父子类同包,子类继承父类中public、protected和默认访问级别的成员变量和成员方法;
2) 父子类不同包,子类继承父类中public、protected的成员变量和成员方法;
细节:
1、构造器不能被继承
2、方法和实例变量可以被继承
3、子类构造器隐式地调用父类的默认无参构造器;
4、如果父类中没有定义无参构造器,只定义了有参构造器,
那么子类构造器则必须显式地调用父类的有参构造器(通过super(…)),且必须放置在第一条语句,否则会有语法错误。
5、this()和super()在构造器中都必须为第一条语句,两者不能同时出现。
6、当一个子类继承了一个父类后,父类中所有的字段和方法都被子类继承拥有,子类可以任意的支配使用,每个子类对象中都拥有了父类中的所有字段。
当构造一个子类的实例对象时,该对象的实例变量包括了子类本身以及父类中的所有实例变量,实例方法也包括了子类和父类中的所有实例方法。
7、子类构造器用来初始化子类中所有的实例变量,而父类构造器super(实参)用来初始化父类中所有的实例变量。
所以在堆中为子类实例对象分配的内存区域中包括了子类和父类中所有初始化后的实例变量
方法覆盖(重写)
1. 方法覆盖只存在于子类和父类(包括直接父类和间接父类)之间。在同一个类中方法只能被重载,不能被覆盖;
2. 静态方法:
不能覆盖;
a. 父类的静态方法不能被子类覆盖为非静态方法 //编译出错
b. 子类可以定义与父类的静态方法同名的静态方法 (但是这个不是覆盖)
c. 父类的非静态方法不能被子类覆盖为静态方法 //编译出错
3. 私有方法:
不能被子类覆盖
4. 抽象方法:(之后会详细讨论抽象方法)
可以覆盖:
a. 父类的抽象方法可以被子类覆盖为非抽象方法: 子类实现父类抽象方法;
b. 父类的抽象方法可以被子类覆盖为抽象方法: 重新声明父类的抽象方法;
c. 父类的非抽象方法可以被子类覆盖为抽象方法;
super关键字
1. 为什么要使用super关键字?
1) 子类中要访问父类方法或变量。
2) 子类中调用父类的构造器
2. 使用注意事项:
a. 只能在构造方法或实例方法内使用super关键字,在静态方法和静态代码块内不能使用super关键字。
b. 在子类构造方法中如没有使用super关键字,会隐式调用父类的无参构造方法;
c. 构造方法中this(...)和super(...)不能同时出现;
多态
多态的两种描述:
1.一个父类类型的引用可以指向他任何一个子类的对象
2.[相同]类域的[不同]对象执行[同一]方法的时候会有[不同]的表现多态是出现在具有继承关系的两个类的对象之间,所以它不像方法重载(发生在一个类中)在编译期间发生(也就是确定下来),而是在运行期间发生(确定下来)。
Java中的方法是在代码运行时动态绑定的。
类型转换
转换:
1) 先使用instanceof 识别类型
2) 子类型隐式地扩展到父类型(自动转换)
3) 父类型必须显式地缩小到子类型
转换规则:被转换的实际对象类型一定是转换以后对象类型的自身或者子类。
Person p = new Person();
Student s = (Student)p;// 编译不会错,运行时错误
Person p2 = new Student();
Student s = (Student)p2 或者 Person p = (Student)p2;// 正确
继承现象总结:
1. 子类重写父类方法,调用子类方法;
2. 子类属性与父类同名(不管子类属性前修饰符如何均允许),如获取属性,看获取属性方法位置,
如在父类中,获取的是父类属性,如在子类中,获取的是子类属性;
3. 子类私有方法与父类私有方法同名,如调用该方法,看私有方法被调用的位置,
如在父类中,调用的是父类方法,如在子类中,调用的是子类方法;
4. 子类静态方法与父类静态方法同名,子类静态方法屏蔽父类静态方法。如调用该静态方法,看实例化对象时所声明的类型,
如声明为父类,调用的是父类中静态方法,反之是子类中静态方法。