L03-面向对象编程
面向对象与面向过程
面向对象(OOP)与面向过程
二者都是一种思想,面向对象是相对于面向过程而言的。面向过程,强调的是功能行为。面向对象,将功能封装进对象,强调具备了功能的对象。
面向对象更加强调运用人类在日常的思维逻辑中采用的思想方法与原则,如抽象、分类、继承、聚合、多态等。
面向对象的三大特征
封装 (Encapsulation)
继承 (Inheritance)
多态 (Polymorphism)
类与对象
类与类之间的关系
- 关联关系
- 继承关系
聚合关系
- 聚集
- 组合
面向对象思想
程序员从执行者转化成了指挥者。
完成需求时:
- 先去找具有所需功能的对象来用。
- 如果该对象不存在,那么创建一个具有所需功能的对象。
- 这样简化开发并提高复用。
类(class)和对象(object)是面向对象的核心概念。
- 类是对一类事物描述,是抽象的、概念上的定义
- 对象是实际存在的该类事物的每个个体,因而也称实例(instance)。
“万事万物皆对象”
java类及类的成员
现实世界万事万物是由分子、原子构成的。同理,Java代码世界是由诸多个不同功能的类构成的。
现实世界中的分子、原子又是由什么构成的呢?原子核、电子!那么,Java中用类class来描述事物也是如此:
- 属性:对应类中的成员变量
- 行为:对应类中的成员方法
类的成员之一:属性
语法格式
修饰符 类型 属性名 = 初值;
- 修饰符 private:该属性只能由该类的方法访问。
- 修饰符 public:该属性可以被该类以外的方法访问。
- 类型:任何基本类型,如int、boolean或任何类。
- 例如:
1 | public class Person{ |
变量的分类
- 成员变量:在方法体外,类体内声明的变量
- 局部变量:在方法体内部声明的变量
两者在初始化值方面的异同
同:都有生命周期。
异:局部变量除形参外,需显式初始化。
成员变量与局部变量的区别
成员变量
- 成员变量定义在类中,在整个类中都可以被访问。
- 成员变量分为类成员变量和实例成员变量,实例变量存在于对象所在的堆内存中。
- 成员变量有默认初始化值。
- 成员变量的权限修饰符可以根据需要,选择任意一个。
局部变量
- 局部变量只定义在局部范围内,如:方法内,代码块内等。
- 局部变量存在于栈内存中。
- 作用的范围结束,变量空间会自动释放。
- 局部变量没有默认初始化值,每次必须显式初始化。
- 局部变量声明时不指定权限修饰符。
类的成员之二:方法
语法格式
1 | 修饰符 返回值类型 方法名 (参数列表) { |
- 修饰符:public, private, protected等
- 返回值类型:return语句传递返回值。没有返回值:void
- 例如:
1 | public class Person{ |
- 可以在方法内调用本类的其他方法,但是不可以在方法内定义新的方法
对象的创建和使用
- 使用
new + 构造器
创建一个新的对象; - 使用
对象名.对象成员
的方式访问对象成员(包括属性和方法);
类的访问机制
一个类中的访问机制
类中的方法可以直接访问类中的成员变量。(例外:static方法访问非static,编译不通过。)
不同类中的访问机制
先创建要访问类的对象,再用对象访问类中定义的成员。
对象的产生
1 | class Person{ |
当一个对象被创建时,会对其中各种类型的成员变量自动进行初始化赋值。除了基本数据类型之外的变量类型都是引用类型。
对象的使用
1 | class TestPerson{ |
对象的生命周期
匿名对象
我们也可以不定义对象的句柄,而直接调用这个对象的方法。这样的对象叫做匿名对象。
- 如:
new Person().shout();
- 如:
使用情况
- 如果对一个对象只需要进行一次方法调用,那么就可以使用匿名对象。
- 我们经常将匿名对象作为实参传递给一个方法调用。
练习
练习1:创建一个Person类,其定义如下:要求:
- 创建Person类的对象,设置该对象的name、age和sex属性,调用study方法,输出字符串“studying”,调用
showAge()
方法显示age值,调用addAge()
方法给对象的age属性值增加2岁。 - 创建第二个对象,执行上述操作,体会同一个类的不同对象之间的关系。
1 | public class TestPerson { |
练习2:利用面向对象的编程方法,设计类Circle计算圆的面积。
1 | //利用面向对象的编程方法,设计类Circle计算圆的面积 |
再谈方法
什么是方法(函数)?
方法是类或对象行为特征的抽象,也称为函数。
Java里的方法不能独立存在,所有的方法必须定义在类里。
1
2
3
4修饰符 返回值类型 方法名(参数类型 形参1,参数类型 形参2,…){
程序代码
return 返回值;
}其中:
- 形式参数:在方法被调用时用于接收外部传入的数据的变量。
- 参数类型:就是该形式参数的数据类型。
- 返回值:方法在执行完毕后返还给调用它的程序的数据。
- 返回值类型:方法要返回的结果的数据类型。
- 实参:调用方法时实际传给函数形式参数的数据。
方法的调用
方法只有被调用才会被执行
方法调用的过程分析
注意:
- 没有具体返回值的情况,返回值类型用关键字void表示,那么该函数中的return语句如果在最后一行可以省略不写。
- 定义方法时,方法的结果应该返回给调用者,交由调用者处理。
- 方法中只能调用方法,不可以在方法内部定义方法。
面向对象思想“落地”法则
关注于类的设计,即设计类的成员:属性 、方法
类的实例化,即创建类的对象(比如:Person p = new Person())
通过
对象.属性
、对象.方法”
执行
方法的重载(overload)
概念
在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
特点
与返回值类型无关,只看参数列表,且参数列表必须不同。(参数个数或参数类型)。
调用时,根据方法参数列表的不同来区别。
示例
1 | //返回两个整数的和 |
练习
练习1:判断
与void show(int a,char b,double c){}
构成重载的有:
a) void show(int x,char y,double z){}
//no
b) int show(int a,double c,char b){}
//yes
c) void show(int a*,*double c,char b){}
//yes
d) boolean show(int c,char b){}
//yes
e) void show(double c){}
//yes
f) double show(int x,char y,double z){}
//no
g) void shows(){double c}
//no
练习2:编写程序,定义三个重载方法并调用。方法名为mOL。
- 三个方法分别接收一个int参数、两个int参数、一个字符串参数。分别执行平方运算并输出结果,相乘并输出结果,输出字符串信息。
- 在主类的main ()方法中分别用参数区别调用三个方法。
1 | public class TestOverLoad { |
练习3:定义三个重载方法max(),第一个方法求两个int值中的最大值,第二个方法求两个double值中的最大值,第三个方法求三个double值中的最大值,并分别调用三个方法。
1 | public class TestOverLoad { |
可变个数的形参
- 下面采用数组形参来定义方法
public static void test(int a, String[] books);
- 以可变个数形参来定义方法
public static void test(int a, String…books);
- 说明:
- 可变参数:方法参数部分指定类型的参数个数是可变多个
- 声明方式:方法名(参数的类型名…参数名)
- 可变参数方法的使用与方法参数部分使用数组是一致的
- 方法的参数部分有可变形参,需要放在形参声明的最后
方法的参数传递
方法,必须有其所在类或对象调用才有意义。
参数:
形参:方法声明时的参数
实参:方法调用时实际传给形参的参数值
Java的实参值如何传入方法呢?
Java里方法的参数传递方式只有一种:值传递。 即将实际参数值的副本(复制品)传入方法内,而参数本身不受影响。
面向对象特征之一:封装和隐藏
问题:当创建了类的对象以后,如果直接通过“对象.属性”的方式对相应对象的属性赋值的话,可能会出现不满足实际情况的意外,也就是我们对类内部定义的属性(对象的成员变量)的直接操作有可能会导致数据的错误、混乱或安全性问题。
解决方法:那么,我们可以考虑不让对象来直接作用于属性,而是通过“对象.方法”的形式,来控制对象对属性的访问。
信息的封装和隐藏
Java中通过将数据声明为私有的(private),再提供公共的(public)方法:getXxx()
和setXxx()
实现对该属性的操作,以实现下述目的:
- 隐藏一个类中不需要对外提供的实现细节;
- 使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑,限制对属性的不合理操作;
- 便于修改,增强代码的可维护性;
访问权限修饰符
Java权限修饰符public、protected、private置于类的成员定义前,用来限定对象对该类成员的访问权限。
对于class的权限修饰只可以用public和default(缺省):
- public类可以在任意地方被访问。
- default类只可以被同一个包内部的类访问。
练习
练习:创建程序,在其中定义两个类:Person和TestPerson类。定义如下:用setAge()
设置人的合法年龄(0~130),用getAge()
返回人的年龄。在TestPerson类中实例化Person类的对象b,调用setAge()
和getAge()
方法,体会Java的封装性。
1 | public class TestPerson2 { |
类的成员之三:构造器
构造器的特征
它具有与类相同的名称
它不声明返回值类型(与声明为void不同)
不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值
构造器的作用
- 创建对象
- 给对象进行初始化
- 如:
Order o = new Order(); Person p = new Person(Peter,15);
语法格式
1 | 修饰符 类名 (参数列表) { |
分类
根据参数不同,构造器可以分为如下两类:
隐式无参构造器(系统默认提供)
显式定义一个或多个构造器(无参、有参)
注意:
- Java语言中,每个类都至少有一个构造器
- 默认构造器的修饰符与所属类的修饰符一致
- 一旦显式定义了构造器,则系统不再提供默认构造器
- 一个类可以创建多个重载的构造器
- 父类的构造器不可被子类继承
练习
练习1:在前面定义的Person类中添加构造器,利用构造器设置所有人的age属性初始值都为18。
1 | public class TestPerson2 { |
练习2:修改上题中类和构造器,增加name属性,使得每次创建Person对象的同时初始化对象的age属性值和name属性值。
1 | public class TestPerson2 { |
练习3:编写两个类,TriAngle和TestTriAngle,其中TriAngle中声明私有的底边长base和高height,同时声明公共方法访问私有变量;另一个类中使用这些公共方法,计算三角形的面积。
1 | public class TestTriAngle { |
构造器的重载
- 构造器一般用来创建对象的同时初始化对象。如:
1 | class Person{ |
- 构造器重载使得对象的创建更加灵活,方便创建各种不同的对象。如:
1 | public class Person{ |
- 构造器重载,参数列表必须不同
练习:
定义Person类,有4个属性:
String name; int age; String school; String major
定义Person类的3个构造方法:
第一个构造方法
Person(String n, int a)
设置类的name和age属性;第二个构造方法
Person(String n, int a, String s)
设置类的name, age 和school属性;第三个构造方法
Person(String n, int a, String s, String m)
设置类的name, age ,school和major属性;
- 在main方法中分别调用不同的构造方法创建的对象,并输出其属性值。
1 | public class TestPerson3 { |
类对象的属性赋值
先后顺序:
- 属性的默认初始化
- 属性的显式初始化
- 通过构造器给属性初始化
- 通过“
对象.方法
”的方式给属性赋值
关键字—this
介绍
在java中,this关键字比较难理解,它的作用和其词义很接近。
- 它在方法内部使用,即这个方法所属对象的引用;
- 它在构造器内部使用,表示该构造器正在初始化的对象。
this 表示当前对象,可以调用类的属性、方法和构造器
使用
什么时候使用this关键字呢?——当在方法内需要用到调用该方法的对象时,就用this。
当形参与成员变量重名时,如果在方法内部需要使用成员变量,必须添加this来表明该变量时类成员
在任意方法内,如果使用当前类的成员变量或成员方法可以在其前面添加this,增强程序的阅读性
this可以作为一个类中,构造器相互调用的特殊格式
注意:
- 构造器相互调用时,使用
this()
必须放在构造器的首行! - 使用this调用本类中其他的构造器,保证至少有一个构造器是不用this的。
练习
练习1:
- 写一个名为Account的类模拟账户。该类的属性和方法如下图所示。该类包括的属性:账号id,余额balance,年利率annualInterestRate;包含的方法:访问器方法(getter和setter方法),取款方法
withdraw()
,存款方法deposit()
。
提示:在提款方法withdraw中,需要判断用户余额是否能够满足提款数额的要求,如果不能,应给出提示。
- 创建Customer类
- 声明三个私有对象属性:firstName、lastName 和 account。
- 声明一个公有构造器,这个构造器带有两个代表对象属性的参数(f和l)
- 声明两个公有存取器来访问该对象属性,方法 getFirstName 和 getLastName 返回相应的属性
- 声明 setAccount 方法来对 account 属性赋值
- 声明 getAccount 方法以获取 account 属性
- 写一个测试程序。
创建一个Customer ,名字叫 Jane Smith, 他有一个账号为1000,余额为2000元,年利率为 1.23% 的账户。
对Jane Smith操作。
- 存入 100 元,再取出960元。再取出2000元。
- 打印出Jane Smith 的基本信息
1 | package L03.exer; |
1 | package L03.exer; |
1 | package L03.exer; |
练习2:添加必要的构造器,综合应用构造器的重载,this关键字。
1 | package L03.exer; |
1 | package L03.exer; |
1 | package L03.exer; |
JavaBean
概念
- JavaBean 是一种 Java 语言写成的可重用组件。
- 所谓 JavaBean,是指符合如下标准的Java类:
- 类是公共的
- 有一个无参的公共的构造器
- 有属性,且有对应的get、set方法
作用
用户可以使用 JavaBean 将功能、处理、值、数据库访问和其他任何可以用 java 代码创造的对象进行打包,并且其他的开发者可以通过内部的JSP页面、Servlet、其他JavaBean、applet程序或者应用来使用这些对象。用户可以认为 JavaBean 提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变。
示例
1 | public class TestJavaBean{ |
关键字—package
软件包
包帮助管理大型软件系统:将语义近似的类组织到包中;解决类命名冲突的问题。
包可以包含类和子包。
例:某航运软件系统包括:一组域对象、GUI和reports子系统
介绍
package 语句作为 Java 源文件的第一条语句,指明该文件中定义的类所在的包。(若缺省该语句,则指定为无名包)。它的格式为:
1 | package 顶层包名.子包名; |
包对应于文件系统的目录,package语句中,用 “.” 来指明包(目录)的层次
包通常用小写单词,类名首字母通常大写
关键字—import
介绍
为使用定义在不同包中的Java类,需用import语句来引入指定包层次下所需要的类或全部类(.)。*import语句告诉编译器到哪里去寻找类。
语法格式
1 | import 包名[.子包名…]. <类名 |*> |
示例
1 | import p1.Test; //import p1.*;表示引入p1包中的所有类 |
import 语句
若引入的包为:java.lang,则编译器默认可获取此包下的类,不需要再显示声明。
import语句出现在package语句之后、类定义之前
一个源文件中可包含多个import语句
可以使用
import lee.*;
语句,表明导入lee包下的所有类。而lee包下sub子包内的类则不会被导入。import lee.sub.*;
import语句不是必需的,可坚持在类里使用其它类的全名
JDK 1.5加入 import static 语句
JDK中主要的包介绍
java.lang——包含一些Java语言的核心类,如String、Math、Integer、 System和Thread,提供常用功能。
java.net——包含执行与网络相关的操作的类和接口。
java.io——包含能提供多种输入/输出功能的类。
java.util——包含一些实用工具类,如定义系统特性、接口的集合框架类、使用与日期日历相关的函数。
java.text——包含了一些java格式化相关的类。
java.sql——包含了java进行JDBC数据库编程的相关类/接口。
java.awt——包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界 面(GUI)。
java.applet——包含applet运行所需的一些类
面试题
哪个选项和show函数重载
1
2
3class Demo{
void show(int a,int b,float c){}
}
A. void show(int a,float c,int b){} //yes
B. void show(int a,int b,float c){} //一模一样。不可以出现在同一个类中。
C. int show(int a,float c,int b){return a;} //yes
D. int show(int a,float c){return a;} //yes
- 面向对象的特点
答:面向对象有三大特点:封装、继承、多态。(如果要回答四个,可加上 抽象性 这一特点)
继承:
继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。封装:
封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。多态性:
多态性是指允许不同类的对象对同一消息作出响应。多态性包括参数化多态性和包含多态性。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。抽象:
抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。抽象包括两个方面,一是过程抽象,二是数据抽象。
- 作用域 public,private,protected以及不写时的区别
1 | public class Something { |
- 有错吗?
答案: 错。局部变量前不能放置任何访问修饰符 (private,public,和protected)。final可以用来修饰局部变量
(final如同abstract和strictfp,都是非访问修饰符,strictfp只能修饰class和method而非variable)。
- 方法重载(overload)必须满足____。
A. 在不同class中定义的方法 B. 在同一类中定义的方法
C. 方法名必须相同 D. 返回类型必须相同
E. 参数一定不同 F. 参数可以相同
- 构造器Constructor是否可被override?
答:构造器Constructor不能被继承,因此不能重写Override,但可以被重载Overload
- 写出结果。(关于参数传递)
1 | public class Test |
- 写出结果。(关于参数传递)
1 | public class Demo |
- 写出结果。(关于参数传递)
1 | public class TestA { |
- 写出结果。(关于参数传递)
1 | class Value{ |
A. 15 0 20
B. 15 0 15
C. 20 0 20
D. 0 15 20
- 写出结果。(关于参数传递)
1 | public class TestArgsValue { |
- 当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?
答:是值传递。Java 编程语言只有值传递参数。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的内容可以在被调用的方法中改变,但对象的引用是永远不会改变的。
- What is the result when you compile and run the following code?
1 | public class Test{ |
A. 0122
B. 0123
C. compile error
D. none of these
- 写出输出结果。
1 | class Demo{ |
- 写出输出结果。
1 | class Demo{ |
- 补足compare函数内的代码,不许添加其他函数。
1 | class Circle{ |