面向对象编程
一、面向对象概述
1. 面向对象的编程思想
1.1 面向过程(POP)与面向对象(OOP)
- 二者都是一种思想,面向对象是相对于面向过程而言。面向过程强调的是功能行为,以函数为最小单位。面向对象,将功能封装进对象中,强调具备了功能的对象,以类为最小单位。
- 面向对象更加强调运用人类在日常的思维逻辑中采用的思想方法与原则,如抽象、分类、继承、聚合、多态等
- “人把大象装进冰箱”
人{
打开(冰箱){
冰箱.打开();
}
抬起(大象){
大象.进入(冰箱);
}
关闭(冰箱){
冰箱.闭合();
}
}
冰箱{
打开();
闭合();
}
大象{
进入(冰箱){
}
}
1.2 面向对象三大特征
- 封装(Encapsulation)
- 继承(Inheritance)
- 多态(Polymorphism)
二、Java类及类的成员
1. 类(Class)和对象(Object)
1.1 概述
- 类是对一类事物的描述,是抽象的、概念上定义的
- 对象是实际存在的该事物的具体实例
- “万事万物皆对象”
1.2 类和对象的使用
- 创建类,设计类的成员
- 创建类的对象
- 通过“对象.属性”或“对象.方法”调用对象的结构
1.3 关于类和对象的内存解析
-
如果创建了一个类的多个对象,则每个对象都独立拥有一套类的属性(非static),意味着:如果修改一个对象的属性a,则不会影响另外一个对象的属性a的值。
-
堆(Heap):存放对象实例,几乎所有对象实例都在此处分配内存。Java虚拟机规范中描述:所有对象实例及数组都在堆上分配内存
-
栈(Stack)(指虚拟机栈):存储局部变量等。局部变量表存放了编译器可知长度的各种数据类型(Boolean、byte、char、short、int、float、long、double)、对象的引用。方法执行完自动释放。
-
方法区:用于存储以被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。
public class Test {
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = new Person();
Person p3 = new Person();
p1.name="Tom";
p1.age=10;
p1.sex="man";
p2.name="Rouse";
p2.age=20;
p2.sex="woman";
p3=p2;
p3.eat();
}
}
class Person{
String name;
int age;
String sex;
public void eat(){
System.out.println("吃!");
}
}
1.4 匿名对象
- 创建对象时没有显示赋给一个变量名即为匿名对象
- 特征:匿名对象只能调用一次
public class Test {
public static void main(String[] args) {
Person2 p1 = new Person2();
p1.show(new Person());
}
}
class Person{
String name;
int age;
String sex;
public void eat(){
System.out.println(name+"吃!");
}
public void printAge(){
System.out.println(age);
}
}
class Person2{
//匿名对象
public void show(Person p){
p.printAge();
p.eat();
}
}
2. 属性
- 对应类中的成员变量(field)
- 调用属性:“对象.属性”
public class Test {
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = new Person();
Person p3 = new Person();
//“对象.属性”
p1.name="Tom";
p1.age=10;
p1.sex="man";
}
2.1 属性(成员变量) VS 局部变量
-
相同点
- 定义变量的格式:数据类型 变量名=变量值
- 先声明,后使用
- 变量都有其对应的作用域
-
不同点
-
在类中声明的位置不同:
- 属性:直接定义在一对 { } 内;
- 局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部
-
关于权限修饰的不同:
- 属性:可以在声明属性时指明其权限,使用权限修饰符;常用的权限修饰符:private、public、缺省、protected
- 局部变量:不能使用权限修饰符
-
默认初始化值不同:
-
属性:整形(byte、short、int、long):0
浮点型(float、double):0.0
字符型(char):0或(’\u0000’)
布尔型(boolean):false
引用数据类型(类、数组、接口):null
-
局部变量:没有默认初始化值:在调用之前要显式赋值;形参在调用时赋值即可
-
-
内存加载的位置不同
- 属性:加载在堆空间(非static)
- 局部变量:加载到栈空间
-
3. 方法
- 对应类中的成员方法(函数method)
- 调用方法:“对象.方法”
3.1 方法分类
- 按照是否有形参和返回值
无返回值 | 有返回值 | |
---|---|---|
无形参 | void 方法名 ( ) { } | 返回值类型 方法名 ( ) { } |
有形参 | void 方法名 (形参列表) { } | 返回值类型 方法名 (形参列表) { } |
3.2 方法的声明
权限修饰符 返回值类型 方法名 (形参列表){
方法体
}
3.3 说明
-
关于权限修饰符
- Java规定的四种权限修饰符:private、public、缺省、protected —>在封装性具体描述
-
返回值类型:有返回值 vs无返回值
- 有返回值则必须在方法声明时指定返回值类型,同时使用return关键字来返回指定类型的变量或常量
- 若没有返回值则声明为void
-
方法名:标识符,遵循标识符规则和规范,“见名知意”
-
形参列表:可以有0个、一个或多个形参:数据类型1 形参1,数据类型2 形参2 . . .
-
return关键字:结束方法;针对于有返回值类型的方法,使用“return 数据”方法返回所要的数据
return关键字后不可以声明执行语句。
3.4 注意
- 方法的使用中可以调用当前类的属性或方法(递归调用)
- 方法中不可以定义方法
3.5 方法的重载(overload)
- 概念
- 在同一个类中允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可
- 特点
- 与返回值类型无关,只与参数列表有关,调用时根据方法的参数列表不同来区别
public class OverLoadTest {
//四个方法构成重载
public void getSum(int i,int j){
System.out.println(i+j);
}
public void getSum(double d1,double d2){
}
public void getSum(String s,int i){
}
public void getSum(int i,String s){
}
//不能构成重载
// public int getSum(int i,int j){
// return 0;
// }
}
3.6 可变个数的形参
-
概述
JavaSE 5.0中提供了Varargs(variable number of argument)机制,允许直接定义能和多个实参相匹配的形参。从而可以用一种更简单的方式来传递个数可变的形参
-
具体使用:
- 可变个数形参格式:数据类型 . . . 变量名
- 当调用可变个数形参时,传入的参数可以是0个、1个、2个、、、
- 可变个数形参的方法与本类中方法名相同,形参不同的方法之间构成重载。
- 可变个数形参的方法与本类中方法名相同,形参类型也相同的数组之间不构成重载,即不能同时出现
- 可变个数形参在方法的形参中,必须声明在形参列表的末尾
/**
* @author DELL
* @Date 2019/12/24 15:51
**/
public class VariableNumber {
public static void main(String[] args) {
VariableNumber v = new VariableNumber();
v.show(10);
v.show("hello");
//调用第三个show
v.show("hello","world");
}
public void show(int n){
}
public void show(String s){
}
//可变个数形参
public void show(String ... str){
for (String s : str) {
System.out.print(s+" ");
}
}
// public void show(String[] str){
// }
//可变个数形参只能声明在形参列表末尾
// public void show(String ... str,int n){
//
// }
}
3.7 方法参数的值传递机制
- 概述
- 如果变量是基本数据类型,此时赋值为变量所保存的数据值
- 如果变量是引用数据类型,此时赋值的是变量的地址值
- 方法形参的传递机制:值传递
- 形参:方法定义时,声明的小括号内的参数
- 实参:方法调用时,实际传递给形参的参数
public class VariableNumber {
public static void main(String[] args) {
VariableNumber v = new VariableNumber();
int m=10;
int n=20;
System.out.println("m="+m+",n="+n);
int tmp=0;
tmp=m;
m=n;
n=tmp;
System.out.println("m="+m+",n="+n);
v.swap(m,n);
System.out.println("m="+m+",n="+n);
}
public void swap(int m,int n){
int tmp=0;
tmp=m;
m=n;
n=tmp;
}
}
- 上面的swap(int m,int n)方法不能交换两个变量的值
4. 构造器
4.1 作用
- 用于创建类的对象
- 初始化对象的属性
- 如果没有显示定义构造器,则系统提供默认空参构造器
4.2 详解
- 定义
权限修饰符 类名(形参列表){
}
public Person(int i,String str){
this.i=i;
this.str=str;
}
- 多个构造器彼此构成重载
- 当类中显示的定义了构造器,则类不再提供空参构造器
- 属性赋值的先后顺序:
- 默认初始化值
- 显式初始化/代码块中赋值
- 构造器
- 通过"对象.方法"或"对象.属性"
5. 代码块
- 代码块,也称为初始化块
{
需要执行的逻辑
}
-
通常用于初始化类、对象
-
用static修饰的称为静态代码块
-
静态代码块随着类的加载而执行,只执行一次,只能调用静态结构
-
非静态代码块,随着对象创建而执行,每创建一个对象,就会执行一次非静态代码块
6. 内部类
-
当一个类内部需要一个完整的结构进行描述,则该结构可以使用内部类
-
将类A声明在类B中,则类A称为内部类
-
分类:
- 成员内部类
- 局部内部类(方法内、代码块内、构造器内)
-
如何实例化:
Person.Dog dog=new Person.Dog()
三、面向对象三大特征
1. 封装性
-
隐藏对象内部的复杂性,只对外提供简单的接口,便于外界调用,从而提高系统的课扩展性、可维护性
-
将类属性私有化
-
类外部不能直接访问
-
提供公共的方法来获取和设置该属性的值,即提供get和set方法
2. 继承性
2.2 概述
-
使用extends关键字
-
减少了代码冗余,提高了代码的复用性
-
便于功能扩展
-
为多态性的使用,提供了前提
class A extends B{ }
- A:子类、派生类
- B:父类、超类、基类
- A继承了父类B,则A中就获取了父类中的结构
- 子类继承父类之后可以声明自己特有的方法,即功能的扩展
- 一个类可以被多个子类继承
- 一个类只能有一个父类:单继承
- 所有类都继承于Object类
2.2 子类对象实例化的过程
- 通过子类的构造器创建子类对象时,我们一定会直接或间接调用其父类的构造器,进而调用父类的父类的构造器……
- 虽然创建对象时,调用了父类的构造器,但是自始至终就创建了一个对象,即为new的子类对象
3. 多态性
- 可以理解为一个事物的多种形态
- 父类引用指向子类对象
- 编译看左边,运行看右边
- 使用前提
- 需要类的继承关系
- 需要有方法的重写
- 对象多态性只适用于方法,不适用于属性(编译运行都看左边)
- 当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法
- 有了对象的多态性后,内存中实际上加载了子类特有的属性和方法,但是由于变量声明为父类类型,导致编译时只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用
- 如果要调用子类特有的属性和方法,则需要将父类强转为子类类型,即向下转型(子类 子类对象=(子类)父类对象)
- instanceof关键字:a instanceof A:判断对象a是否是类A的实例,如果是则返回true,否则返回false
四、其他关键字
1. this
-
可以将this理解为当前对象
-
this可以用来修饰属性、方法、构造器
-
this(形参列表)调用本类中其他构造器,一个类中有n个构造器,则最多有n-1个类中使用this调用构造器
-
构造器内部,最多只能声明一个this(形参列表)的形式调用构造器,必须在首行
2. super
- super理解为父类的引用
- 可以通过super.显式调用父类的属性和方法
- 当子类和父类定义了同名的属性时,想要调用父类的属性,则必须通过super显式调用父类的属性
- 可以在子类构造器中通过super(参数列表)的方式显式调用父类声明的构造器
- super(参数列表)的使用,必须声明在子类构造器的首行
- 在类的构造器中,this(形参列表)和super(形参列表)只能二选一
3. static
- 在特定的环境下,某些数据在内存中只需要一份
- static可以用来修饰属性、方法、代码块、内部类
- static修饰属性:静态变量或类变量
- 属性按是否用static修饰又分为:静态属性和非静态属性(实例变量)
- 实例变量:创建类的多个对象,每个对象都独立拥有一套类中的非静态属性,当修改其中一个非静态属性时,不会导致其他对象中同样的属性值被修改
- 静态变量:创建类的多个对象,多个对象共享同一个静态变量,当通过某个对象修改静态变量时,会导致其他对象调用此静态变量时,也是修改后的值
- 静态变量随着类的加载而加载
- 可以通过"类.静态变量"的方式调用
- static修饰方法:静态方法
- 随着类加载而加载,可以使用"类.方法名"进行调用
- 静态方法中只能调用静态的方法或属性
- 静态方法中不能使用this和super关键字
- 非静态方法既可以调用静态方法和属性也可以调用静态方法和属性
4. final
- final可以用来修饰类、方法、变量
- 用来修饰类时,则该类不能被其他类继承,例如:String、StringBuffer、System
- 用来修饰方法,表示该方法不能被重写
- 用来修饰变量,此时的变量被称为是一个常量
- final修饰属性可以赋值的位置有:显式初始化、代码块中初始化、构造器中初始化
- final修饰局部变量,则表明该变量就是一个常量,不能重新赋值
五、抽象类和接口
1. 抽象类
-
abstract:抽象的
-
可以修饰类和方法,则说明该类是抽象类或抽象方法
-
abstract修饰类时,则该类不可以实例化
-
抽象类中有构造器,便于子类对象实例化
-
开发过程中,一般提供抽象类的子类,让子类实例化,完成相应的操作
-
抽象方法只有方法的声明,没有方法体
-
包含抽象方法的类一定是抽象类。反之,抽象类中可以没有抽象方法
-
若子类重写了父类中所有抽象方法后,则子类可以实例化
-
子类没有重写父类的抽象方法,则子类也必须是抽象的
-
注意点:
- abstract不能修饰属性、构造器等结构
- 不能用来修饰私有方法、静态方法、final的方法
2. 接口
- interface:接口
- Java中接口和类是并列结构
- 接口中的成员:
- JDK7及以前:只能定义全局常量(public static final)和抽象方法
- JDK8:除了定义全局常量和抽象方法外,还可以定义静态方法、默认方法
- 接口中不能定义构造器,即接口不可以实例化
- Java开发中让类实现接口,从而使用相应的功能
- 如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化,若实现类没有覆盖接口中的所有抽象方法,则此实现类仍为一个抽象类
- 接口和接口之间是继承关系,可以多继承