面向对象
面向对象编程
-
面向过程的程序设计思想(Process-Oriented Programming),简称
POP
-
关注的焦点是
过程
:过程就是操作数据的步骤,如果某个过程的实现代码在很多地方重复出现,那么就可以把这个过程抽象为一个函数
,这样就可以大大简化冗余代码,也便于维护。 -
代码结构:以
函数
为组织单位。 -
是一种“
执行者思维
”,适合解决简单问题
-
-
面向对象的程序设计思想( Object Oriented Programming),简称
OOP
- 关注的焦点是
类
:面向对象思想就是在计算机程序设计过程中,参照现实中事物,将事物的属性特征、行为特征抽象出来,用类来表示。 - 代码结构:以
类
为组织单位。每种事物都具备自己的属性
和行为/功能
。 - 是一种“
设计者思维
”,适合解决复杂问题。
- 关注的焦点是
类比举例:人把大象装进冰箱
-
面向过程
1.打开冰箱2.把大象装进冰箱3.把冰箱门关住
面向过程开发,其实就是面向着具体的每一个步骤和过程,把每一个步骤和过程完成,然后由这些功能方法相互调用,完成需求。
-
面向对象
人{打开(冰箱){冰箱.开门(); }操作(大象){大象.进入(冰箱);}关闭(冰箱){ 冰箱.关门(); } }冰箱{开门(){ } 关门(){ } }大象{进入(冰箱){ } }
类和对象概述
类(Class)
和对象(Object)
是面向对象的核心概念。
-
什么是类:具有相同特征的事物的抽象描述,是
抽象的
、概念上的定义 -
什么是对象:是实际存在的该类事物的
每个个体
,是具体的
,因而也称为实例(instance)
-
可以理解为:
类 => 抽象概念的人
;对象 => 实实在在的某个人
-
属性:就是该类事物的状态信息。对应类中的
成员变量
- 成员变量 <=> 属性 <=> Field
-
行为:就是该类事物要做什么操作,或者基于事物的状态能做什么。对应类中的
成员方法
- (成员)方法 <=> 函数 <=> Method
面向对象完成功能的三步骤
1、类的定义
类的定义使用关键字:class。格式如下:
[修饰符] class 类名{属性声明;方法声明;
}
举例1:
public class Person{//声明属性ageint age ; //声明方法showAge()public void eat() { System.out.println("人吃饭");}
}
举例2:
public class Dog{//声明属性String type; //种类String nickName; //昵称String hostName; //主人名称//声明方法public void eat(){ //吃东西System.out.println("狗狗进食"); }
}
public class Person{String name;char gender;Dog dog;//喂宠物public void feed(){dog.eat();}
}
2、对象的创建
- 创建对象,使用关键字:new
- 创建对象语法:
//方式1:给创建的对象命名
//把创建的对象用一个引用数据类型的变量保存起来,这样就可以反复使用这个对象了
类名 对象名 = new 类名();//方式2:
new 类名()//也称为匿名对象
- 举例:
class PersonTest{public static void main(String[] args){//创建Person类的对象Person per = new Person();//创建Dog类的对象Dog dog = new Dog();}
}
3、对象调用属性或方法
-
对象是类的一个实例,必然具备该类事物的属性和行为(即方法)。
-
使用"
对象名.属性
" 或 "对象名.方法
"的方式访问对象成员(包括属性和方法)
举例1:
//声明Animal类
public class Animal { //动物类public int legs;public void eat() {System.out.println("Eating.");}public void move() {System.out.println("Move.");}
}
//声明测试类
public class AnimalTest {public static void main(String args[]) {//创建对象Animal xb = new Animal();xb.legs = 4;//访问属性System.out.println(xb.legs);xb.eat();//访问方法xb.move();//访问方法}
}
java内存结构划分
-
堆(Heap)
:此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。 -
栈(Stack)
:是指虚拟机栈。虚拟机栈用于存储局部变量等。局部变量表存放了编译期可知长度的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,是对象在堆内存的首地址)。 方法执行完,自动释放。 -
方法区(Method Area)
:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
说明:
- 堆:凡是new出来的结构(对象、数组)都放在堆空间中。
- 对象的属性存放在堆空间中。
- 创建一个类的多个对象(比如p1、p2),则每个对象都拥有当前类的一套"副本"(即属性)。当通过一个对象修改其属性时,不会影响其它对象此属性的值。
- 当声明一个新的变量使用现有的对象进行赋值时(比如p3 = p1),此时并没有在堆空间中创建新的对象。而是两个变量共同指向了堆空间中同一个对象。当通过一个对象修改属性时,会影响另外一个对象对此属性的调用。
成员变量
- 语法格式:
[修饰符1] class 类名{[修饰符2] 数据类型 成员变量名 [= 初始化值];
}
- 说明:
- 位置要求:必须在类中,方法外
- 修饰符2(暂不考虑)
- 常用的权限修饰符有:private、缺省、protected、public
- 其他修饰符:static、final
- 数据类型
- 任何基本数据类型(如int、Boolean) 或 任何引用数据类型。
- 成员变量名
- 属于标识符,符合命名规则和规范即可。
- 初始化值
- 根据情况,可以显式赋值;也可以不赋值,使用默认值
示例:
public class Person{private int age; //声明private变量 agepublic String name = “Lila”; //声明public变量 name
}
1、变量的分类:成员变量与局部变量
- 在方法体外,类体内声明的变量称为成员变量。
- 在方法体内部等位置声明的变量称为局部变量。
其中,static可以将成员变量分为两大类,静态变量和非静态变量。其中静态变量又称为类变量,非静态变量又称为实例变量或者属性。接下来先学习实例变量。
2、成员变量 与 局部变量 的对比
-
相同点
- 变量声明的格式相同: 数据类型 变量名 = 初始化值
-
变量必须先声明、后初始化、再使用。
- 变量都有其对应的作用域。只在其作用域内是有效的
-
不同点
1、声明位置和方式
(1)实例变量:在类中方法外
(2)局部变量:在方法体{}中或方法的形参列表、代码块中
2、在内存中存储的位置不同
(1)实例变量:堆
(2)局部变量:栈
3、生命周期
(1)实例变量:和对象的生命周期一样,随着对象的创建而存在,随着对象被GC回收而消亡,
而且每一个对象的实例变量是独立的。
(2)局部变量:和方法调用的生命周期一样,每一次方法被调用而在存在,随着方法执行的结束而消亡,
而且每一次方法调用都是独立。
4、作用域
(1)实例变量:通过对象就可以使用,本类中直接调用,其他类中“对象.实例变量”
(2)局部变量:出了作用域就不能使用
5、修饰符(后面来讲)
(1)实例变量:public,protected,private,final,volatile,transient等
(2)局部变量:final
6、默认值
(1)实例变量:有默认值
(2)局部变量:没有,必须手动初始化。其中的形参比较特殊,靠实参给它初始化。
方法
方法
是类或对象行为特征的抽象,用来完成某个功能操作。在某些语言中也称为函数
或过程
。- 将功能封装为方法的目的是,可以
实现代码重用
,减少冗余
,简化代码 - Java里的方法
不能独立存在
,所有的方法必须定义在类里。 - 必须先声明后使用
- 不调用不执行
- 方法中只能调用方法或属性,不可以在方法内部定义方法
正确示例:
类{方法1(){}方法2(){}
}
错误示例:
类{方法1(){方法2(){ //位置错误}}
}
方法头可能包含5个部分
-
修饰符:可选的。方法的修饰符也有很多,例如:public、protected、private、static、abstract、native、final、synchronized等,后面会一一学习。
- 其中,权限修饰符有public、protected、private。在讲封装性之前,我们先默认使用pulbic修饰方法。
- 其中,根据是否有static,可以将方法分为静态方法和非静态方法。其中静态方法又称为类方法,非静态方法又称为实例方法。咱们在讲static前先学习实例方法。
-
返回值类型: 表示方法运行的结果的数据类型,方法执行后将结果返回到调用者。
- 无返回值,则声明:void
- 有返回值,则声明出返回值类型(可以是任意类型)。与方法体中“
return 返回值
”搭配使用
-
方法名:属于标识符,命名时遵循标识符命名规则和规范,“见名知意”
-
形参列表:表示完成方法体功能时需要外部提供的数据列表。可以包含零个,一个或多个参数。
- 无论是否有参数,()不能省略
- 如果有参数,每一个参数都要指定数据类型和参数名,多个参数之间使用逗号分隔,例如:
- 一个参数: (数据类型 参数名)
- 二个参数: (数据类型1 参数1, 数据类型2 参数2)
- 参数的类型可以是基本数据类型、引用数据类型
-
throws 异常列表:后面会说
(3)方法体:方法体必须有{}括起来,在{}中编写完成方法功能的代码
(4)关于方法体中return语句的说明:
-
return语句的作用是结束方法的执行,并将方法的结果返回去
-
如果返回值类型不是void,方法体中必须保证一定有 return 返回值; 语句,并且要求该返回值结果的类型与声明的返回值类型一致或兼容。
-
如果返回值类型为void时,方法体中可以没有return语句,如果要用return语句提前结束方法的执行,那么return后面不能跟返回值,直接写return ; 就可以。
-
return语句后面就不能再写其他代码了,否则会报错:Unreachable code
补充:方法的分类:按照是否有形参及返回值
- 对象.方法名([实参列表])
关键字return的使用
- return在方法中的作用:
- 作用1:结束一个方法
- 作用2:结束一个方法的同时,可以返回数据给方法的调用者
- 注意点:在return关键字的直接后面不能声明执行语句
方法调用内存分析
-
方法
没有被调用
的时候,都在方法区
中的字节码文件(.class)中存储。 -
方法
被调用
的时候,需要进入到栈内存
中运行。方法每调用一次就会在栈中有一个入栈
动作,即给当前方法开辟一块独立的内存区域,用于存储当前方法的局部变量的值。 -
当方法执行结束后,会释放该内存,称为
出栈
,如果方法有返回值,就会把结果返回调用处,如果没有返回值,就直接结束,回到调用处继续执行下一条指令。 -
栈结构:先进后出,后进先出。
package
package语句作为Java源文件的第一条语句,指明该文件中定义的类所在的包。(若缺省该语句,则指定为无名包)。
package 顶层包名.子包名 ;
package com.shujia.day01; //指定类PackageTest属于包
com.shujia.day01
public class PackageTest{public void display(){System.out.println("in method display()");}
}
说明:
-
一个源文件只能有一个声明包的package语句
-
包名,属于标识符,满足标识符命名的规则和规范(全部小写)
-
包对应于文件系统的目录,package语句中用 “.” 来指明包(目录)的层次,每.一次就表示一层文件目录。
-
包通常使用所在公司域名的倒置:com.atguigu.xxx。
- 大家取包名时不要使用"
java.xx
"包
- 大家取包名时不要使用"
-
同一个包下不能定义同名的结构(类、接口),不同的包下可以定义同名的结构(类、接口)
-
包帮助
管理大型软件
系统:将功能相近的类划分到同一个包中。比如:MVC的设计模式 -
包可以包含类和子包,划分
项目层次
,便于管理 -
解决
类命名冲突
的问题 -
控制
访问权限
import
为使用定义在不同包中的Java类,需用import语句来引入指定包层次下所需要的类或全部类。import语句告诉编译器到哪里去寻找类
。
import 包名.类名;
import pack1.pack2.Test; //import pack1.pack2.*;表示引入pack1.pack2包中的所有结构public class PackTest{public static void main(String args[]){Test t = new Test(); //Test类在pack1.pack2包中定义t.display();}
}
- 在源文件中使用import显式的导入指定包下的类或接口
- 声明在包的声明和类的声明之间。
- 如果需要导入多个类或接口,那么就并列显式多个import语句即可
- 如果使用
a.*
导入结构,表示可以导入a包下的所有的结构。举例:可以使用java.util.*的方式,一次性导入util包下所有的类或接口。 - 1如果导入的类或接口是java.lang包下的,或者是当前包下的,则可以省略此import语句。
- 如果已经导入java.a包下的类,那么如果需要使用a包的子包下的类的话,仍然需要导入。
- 如果在代码中使用不同包下的同名的类。那么就需要使用类的全类名的方式指明调用的是哪个类。
import static
组合的使用:调用指定类或接口下的静态的属性或方法
面向对象特征一:封装性
- 我要用洗衣机,只需要按一下开关和洗涤模式就可以了。有必要了解洗衣机内部的结构吗?有必要碰电动机吗?
- 我要开车,…,我不需要懂离合、油门、制动等原理和维修也可以驾驶。
- 客观世界里每一个事物的内部信息都是隐藏在其内部的,外界无法直接操作和修改,只能通过指定的方式进行访问和修改。
随着我们系统越来越复杂,类会越来越多,那么类之间的访问边界必须把握好,面向对象的开发原则要遵循“高内聚、低耦合
”,而“高内聚,低耦合”的体现之一:
高内聚
:类的内部数据操作细节自己完成,不允许外部干涉;低耦合
:仅暴露少量的方法给外部使用,尽量方便外部调用
封装可以被认为是一个保护屏障,防止该类的代码和数据被其他类随意访问,加强了代码的安全性
。隐藏对象内部的复杂性,只对外公开简单和可控的访问方式,从而提高系统的可扩展性、可维护性。
通俗的讲,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
权限修饰符
修饰符 | 本类内部 | 本包内 | 其他包的子类 | 其他包非子类 |
---|---|---|---|---|
private | √ | × | × | × |
缺省 | √ | √ | × | × |
protected | √ | √ | √ | × |
public | √ | √ | √ | √ |
概述:私有化类的成员变量,提供公共的get和set方法,对外暴露获取和修改属性的功能。
实现步骤:
① 使用 private
修饰成员变量
private 数据类型 变量名 ;
代码如下:
public class Person {private String name;private int age;private boolean marry;
}
② 提供 getXxx
方法 / setXxx
方法,可以访问成员变量,代码如下:
public class Person {private String name;private int age;private boolean marry;public void setName(String n) {name = n;}public String getName() {return name;}public void setAge(int a) {age = a;}public int getAge() {return age;}public void setMarry(boolean m){marry = m;}public boolean isMarry(){return marry;}
}
③ 测试:
public class PersonTest {public static void main(String[] args) {Person p = new Person();//实例变量私有化,跨类是无法直接使用的/* p.name = "张三";p.age = 23;p.marry = true;*/p.setName("张三");System.out.println("p.name = " + p.getName());p.setAge(23);System.out.println("p.age = " + p.getAge());p.setMarry(true);System.out.println("p.marry = " + p.isMarry());}
}
成员变量封装的好处:
- 让使用者只能通过事先预定的方法来
访问数据
,从而可以在该方法里面加入控制逻辑,限制对成员变量的不合理访问。还可以进行数据检查,从而有利于保证对象信息的完整性。 便于修改
,提高代码的可维护性。主要说的是隐藏的部分,在内部修改了,如果其对外可以的访问方式不变的话,外部根本感觉不到它的修改。例如:Java8->Java9,String从char[]转为byte[]内部实现,而对外的方法不变,我们使用者根本感觉不到它内部的修改。
构造器
我们new完对象时,所有成员变量都是默认值,如果我们需要赋别的值,需要挨个为它们再赋值,太麻烦了。我们能不能在new对象时,直接为当前对象的某个或所有成员变量直接赋值呢?
可以,Java给我们提供了构造器(Constructor)
,也称为构造方法
。
构造器的作用
new对象,并在new对象的时候为实例变量赋值。
举例:Person p = new Person(“Peter”,15)
;
解释:如同我们规定每个“人”一出生就必须先洗澡,我们就可以在“人”的构造器中加入完成“洗澡”的程序代码,于是每个“人”一出生就会自动完成“洗澡”,程序就不必再在每个人刚出生时一个一个地告诉他们要“洗澡”了。
构造器的语法格式
[修饰符] class 类名{[修饰符] 构造器名(){// 实例初始化代码}[修饰符] 构造器名(参数列表){// 实例初始化代码}
}
说明:
- 构造器名必须与它所在的类名必须相同。
- 它没有返回值,所以不需要返回值类型,也不需要void。
- 构造器的修饰符只能是权限修饰符,不能被其他任何修饰。比如,不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值。
代码如下:
public class Student {private String name;private int age;// 无参构造public Student() {}// 有参构造public Student(String n,int a) {name = n;age = a;}public String getName() {return name;}public void setName(String n) {name = n;}public int getAge() {return age;}public void setAge(int a) {age = a;}public String getInfo(){return "姓名:" + name +",年龄:" + age;}
}
public class TestStudent {public static void main(String[] args) {//调用无参构造创建学生对象Student s1 = new Student();//调用有参构造创建学生对象Student s2 = new Student("张三",23);System.out.println(s1.getInfo());System.out.println(s2.getInfo());}
}
使用说明
-
当我们没有显式的声明类中的构造器时,系统会默认提供一个无参的构造器并且该构造器的修饰符默认与类的修饰符相同
-
当我们显式的定义类的构造器以后,系统就不再提供默认的无参的构造器了。
-
在类中,至少会存在一个构造器。
-
构造器是可以重载的。
this关键字
-
在Java中,this关键字不算太难理解,它的作用和其词义很接近。
- 它在方法(准确的说是实例方法或非static的方法)内部使用,表示调用该方法的对象
- 它在构造器内部使用,表示该构造器正在初始化的对象。
-
this可以调用的结构:成员变量、方法和构造器
什么时候使用this
实例方法或构造器中使用当前对象的成员
在实例方法或构造器中,如果使用当前类的成员变量或成员方法可以在其前面添加this,增强程序的可读性。不过,通常我们都习惯省略this。
但是,当形参与成员变量同名时,如果在方法内或构造器内需要使用成员变量,必须添加this来表明该变量是类的成员变量。即:我们可以用this来区分成员变量
和局部变量
。
另外,使用this访问属性和方法时,如果在本类中未找到,会从父类中查找。这个在继承中会讲到。
举例1:
class Person{ // 定义Person类private String name ; private int age ; public Person(String name,int age){ this.name = name ; this.age = age ; }public void setName(String name){this.name = name;}public void setAge(int age){this.age = age;}public void getInfo(){ System.out.println("姓名:" + name) ;this.speak();}public void speak(){System.out.println(“年龄:” + this.age); }
}
举例2:
public class Rectangle {int length;int width;public int area() {return this.length * this.width;}public int perimeter(){return 2 * (this.length + this.width);}public void print(char sign) {for (int i = 1; i <= this.width; i++) {for (int j = 1; j <= this.length; j++) {System.out.print(sign);}System.out.println();}}public String getInfo(){return "长:" + this.length + ",宽:" + this.width +",面积:" + this.area() +",周长:" + this.perimeter();}
}
测试类:
public class TestRectangle {public static void main(String[] args) {Rectangle r1 = new Rectangle();Rectangle r2 = new Rectangle();System.out.println("r1对象:" + r1.getInfo());System.out.println("r2对象:" + r2.getInfo());r1.length = 10;r1.width = 2;System.out.println("r1对象:" + r1.getInfo());System.out.println("r2对象:" + r2.getInfo());r1.print('#');System.out.println("---------------------");r1.print('&');System.out.println("---------------------");r2.print('#');System.out.println("---------------------");r2.print('%');}
}
同一个类中构造器互相调用
this可以作为一个类中构造器相互调用的特殊格式。
- this():调用本类的无参构造器
- this(实参列表):调用本类的有参构造器
public class Student {private String name;private int age;// 无参构造public Student() {
// this("",18);//调用本类有参构造器}// 有参构造public Student(String name) {this();//调用本类无参构造器this.name = name;}// 有参构造public Student(String name,int age){this(name);//调用本类中有一个String参数的构造器this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getInfo(){return "姓名:" + name +",年龄:" + age;}
}
注意:
- 不能出现递归调用。比如,调用自身构造器。
- 推论:如果一个类中声明了n个构造器,则最多有 n - 1个构造器中使用了"this(形参列表)"
- this()和this(实参列表)只能声明在构造器首行。
- 推论:在类的一个构造器中,最多只能声明一个"this(形参列表)"