1. 面向过程与面向对象
1.1 二者区别
- 二者都是一种思想,面向对象是相对于面向过程而言的。面向过程,强调的是功能行为,以函数为最小单位,考虑怎么做。面向对象,将功能封装进对象,强调具备了功能的对象,以类、对象为最小单位,考虑谁来做。
- 面向对象更加强调运用人类在日常生活的思维逻辑中采用的思想方法和原则,如抽象、分类、继承、聚合、多态等。
面向对象的三大特征
- 封装(Encapsulation)
- 继承(inheritance)
- 多态(Polymorphism)
1.2 二者思想举例
人把大象装进冰箱的步骤
面向过程:
1. 打开冰箱 2. 把大象装进冰箱 3. 关闭冰箱
面向对象:
人{ 打开(冰箱){ 冰箱.开门(); } 操作(大象){ 大象.进入(冰箱); } 关闭(冰箱){ 冰箱.关门(); } } 冰箱{ 开门(){} 关门(){} } 大象{ 进入(冰箱){} }
1.3面向对象思想概述
- 程序员从面向过程的执行者转化成了面向对象的指挥者
面向对象分析方法分析问题的思路和步骤:
- 根据问题需要,选择问题所针对的现实世界中的实体
- 从实际中寻找解决问题相关的属性和功能,这些属性和功能就形成了概念世界中的类
- 把抽象的实体用计算机语言进行描述,形成计算机世界中类的定义。即借助某种程序语言,把类构造成计算机能够识别和处理的数据结构
- 将类实例化成计算机世界中的对象。对象是计算机世界中解决问题的最终工具
1.4 相关练习
- 我要开车去丽江,这句话包含的类有什么:
我, 车, 丽江
- 人在黑板上画圆:
人, 黑板, 圆
- 列车司机紧急刹车:
列车, 司机
- 售货员统计收获的小票的金额:
售货员, 小票
名词一般都是具体的类,而动词一般都是动作施加者类中的方法
2. 类和对象的创建
2.1 面向对象两个要素
类(class)和对象(Object)是面向对象的核心概念
- 类是对一类事物的描述,是抽象的、概念上的定义
- 对象是实际存在的该类事物的每个个体,因而也称为实例(instance)
万事万物皆对象
- 可以理解为:类 = 抽象概念的人; 对象 = 实实在在的某个人
- 面向对象程序设计的重点是类的设计
- 类的设计,其实就是类成员的设计
2.2 Java类及类成员的创建
- 现实世界的生物体,大到鲸鱼,小到蚂蚁,都是由最基本的细胞构成的。同理,Java代码世界是由诸多个不同功能的类构成的
现实生物世界中的细胞由细胞核、细胞质等构成,那么Java中用类class来描述事物也是如此。常见的类成员有:
- 属性:对应类中的成员变量
- 方法:对应类中的成员方法
Field = 属性 = 成员变量, Method = (成员)方法 = 函数
public class PersonTest {
}
class Person {
// 属性,或成员变量
String name;
int age;
boolean isMarried;
// 方法
public void eat() {
System.out.println("吃饭");
}
public void sleep() {
System.out.println("睡觉");
}
public void talk(String language) {
System.out.println("说话:" + language);
}
}
2.3 类的实例化
// 创建Person类的对象
Person p1 = new Person();
2.4 调用对象的结构
调用属性:
// 调用属性 p1.name = "Tom"; p1.isMale = false; System.out.println(p1.name);
调用方法:
// 调用方法 p1.eat(); p1.sleep(); p1.talk("中文");
如果创建了一个类的多个对象,则每个对象都独立的拥有一套类的属性。(非static),意味着修改一个属性A,不影响其他对象的对应属性值。
3. 对象的内存解析
- 堆(Heap),此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
- 通常所说的栈(Stack),是指虚拟机栈。虚拟机栈用于存放局部变量等,局部变量表存放了编译期可知长度的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不同于对象本身,是对象在堆内寸的首地址)。方法执行完,自动释放。
- 方法区(Method Area),用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
举例:
Person p1 = new Person();
p1.name = "Tom";
p1.isMale = true;
Person p2 = new Person();
Person p3 = p1;
p3.age = 10;
以上代码在内存中的结构为
4. 属性与局部变量的区别
相同点
- 定义变量的格式:数据类型 变量名 = 变量值
- 先声明,后使用
- 变量都有其对应的作用域
不同点
- 在类中定义的位置不同:属性直接定义在类的一对
{}
中,而局部变量声明在方法内、方法形参、代码块、构造器形参、构造器内部变量中
class User{ // 属性 String name; int age; boolean isMale; public void talk(String language) {// language叫做形参 System.out.println("使用"+language+"讲话"); } public void eat() { String food = "饼";// 局部变量 System.out.println("吃"+food); } }
- 权限修饰符不同:属性可以在声明时指明其权限,使用权限修饰符(private, public, protected, 缺省)。内部变量不可以使用权限修饰符。
- 默认初始化值不同:类的属性根据其类型都有默认的初始值,其值与对应变量的默认值相同;局部变量没有初始化值,意味着在调用之前一定要显式赋值。特别的,形参在调用时赋值即可。
- 在内存中加载的位置不同:属性(非static)加载在堆空间中,局部变量加载在栈中
- 在类中定义的位置不同:属性直接定义在类的一对
5. 方法的声明和使用
5.1 方法的概念
- 方法使类或对象行为特征的抽象,用来完成某个功能操作。在某些语言中也称为函数或过程。
- 将功能封装为方法的目的是,可以实现代码重用,简化代码
- Java中的方法不能单独存在,所有的方法必须定义在类里
5.2 方法的声明
修饰符 返回值类型 方法名 (参数类型 形参1, 参数类型 形参2, ...){
方法体程序代码;
return 返回值
}
其中,修饰符:public
,private
,protected
,“缺省”
返回值类型:
- 没有返回值:void
- 有返回值:声明出返回值类型。与方法体重“return 返回值”搭配使用
方法名:属于标识符,命名时遵循标识符命名规则和规范,要求“见名知意”
形参列表:可以包括零个、一个或多个参数。多个参数时,使用,
隔开
返回值:方法在执行完毕后返还给调用它的程序的数据。
5.3 方法的调用
方法通过方法名被调用,且只有被调用才会执行
5.4 方法使用中的注意点
- 方法被调用一次,就会执行一次
- 没有具体返回值的情况下,返回值类型用关键字void表示,那么方法体中可以不必使用return语句。如果使用,仅用来结束方法。
- 定义方法时,方法的结果应该返回给调用者,交由调用者处理。
- 方法中只能调用方法或属性,不可以在方法内部定义方法。
5.4 方法使用中的注意点
- 方法被调用一次,就会执行一次
- 没有具体返回值的情况下,返回值类型用关键字void表示,那么方法体中可以不必使用return语句。如果使用,仅用来结束方法。
- 定义方法时,方法的结果应该返回给调用者,交由调用者处理。
- 方法中只能调用方法或属性,不可以在方法内部定义方法。
5.5 方法的重载
- 重载的概念:在同一个类中,允许存在一个以上的同名方法,只要它们的参数类型不同即可。
- 重载的特点:与返回值类型五官,只看参数列表,且参数列表必须不相同(参数个数或参数类型)。调用时,根据方法参数列表不同来区别。
方法重载示例:
public class OverLoadTest {
// 以下四个方法为重载关系
public void getSum(int i, int j) {
}
public void getSum(double d1, double d2) {
}
public void getSum(int i, String s) {
}
public void getSum(String s, int i) {
}
}
以下为错误示范
public void getSum(int k, int j) {
}
即方法重载要求类相同,方法名相同,但是参数类型和数量不同,与参数变量的命名无关。若传入的参数没有对应的方法接收,但是存在“参数列表的类型可以进行隐式转换”的方法,则会选取该方法进行调用。
5.6 可变形参的方法
从JavaSE 5.0开始,Java提供了Varargs机制,允许直接定义能和多个实参相匹配的形参。从而,可以用一种更简便的方式来传递个数可变的实参。
public class MethodArgsTest {
public static void main(String[] args) {
MethodArgsTest test = new MethodArgsTest();
test.show(12);
}
public void show(int i) {
System.out.println("int:"+i);
}
public void show(String s) {
System.out.println("String:"+s);
}
// 可变个数形参
public void show(String ...strs) {
System.out.println("可变形参:"+strs.length);
}
}
第三个show()方法的形参就是可变个数形参,可以传入任意对类型的参数,并作为数组存储在形参变量中。其形式等价于定义一个数组参数,两者同时出现时会报错:
public void show(String[] strs) { System.out.println("可变形参:"+strs.length); }
5.7 方法的值传递机制
方法必须由其所在的类或对象调用才有意义。若方法含有参数:
- 形参:方法声明时的参数
- 实参:方法调用时实际传入形参的参数值
那么Java的实参值如何传入方法呢?
Java里方法的参数传递方式只有一种:值传递,即将实际参数值的副本传入方法内,而参数本身不受影响。
- 形参是基本数据类型,将实参基本数据类型变量的“数据值”传递给形参
- 形参是引用数据类型,将实参引用数据类型变量的”地址值“传递给形参
public class ValueTransferTest {
public static void main(String[] args) {
// 基本数据类型
int m = 10;
int n = 10;
System.out.println("m = " + m + ", n = " + n);
n = 20;
System.out.println("m = " + m + ", n = " + n);
// 引用数据类型
Order o1 = new Order();
o1.orderId = 1001;
Order o2 = o1;
System.out.println("o1.orderId = " + o1.orderId + ", o2.orderId = " + o2.orderId);
o2.orderId = 1002;
System.out.println("o1.orderId = " + o1.orderId + ", o2.orderId = " + o2.orderId);
}
}
class Order {
int orderId;
}
运行结果:
m = 10, n = 10 m = 10, n = 20 o1.orderId = 1001, o2.orderId = 1001 o1.orderId = 1002, o2.orderId = 1002
在基本数据类型中,赋值操作可以直接复制变量值到对应变量中,而引用类型中,赋值操作是将对应的地址复制给接收的变量,在进行该操作后,两个引用类型变量将指向同一块地址。
对于Java的方法,传递参数的过程与上述原理一致。
public class ValueTransferTest1 {
public static void main(String[] args) {
// 基本数据类型
int m = 10;
int n = 20;
System.out.println("m = " + m + ", n = " + n);
swap(m, n);
System.out.println("m = " + m + ", n = " + n);
}
public static void swap(int m, int n) {
int temp = m;
m = n;
n = temp;
}
}
运行结果:
m = 10, n = 20 m = 10, n = 20
发现并没有交换两个变量,原因是在向方法传递参数时,会创建两个变量并用m和n的值进行初始化,由于m和n是基本数据类型,所以直接使用变量值进行赋值操作,在方法体中进行交换,要注意,此时的交换操作是对新建的两个变量进行的,main方法中的两个变量值是不会被交换的。
public class ValueTransferTest2 {
public static void main(String[] args) {
Data data = new Data();
data.m = 10;
data.n = 20;
System.out.println("m = " + data.m + ", n = " + data.n);
swap(data);
System.out.println("m = " + data.m + ", n = " + data.n);
}
public static void swap(Data data) {
int temp = data.m;
data.m = data.n;
data.n = temp;
}
}
class Data {
int m;
int n;
}
运行结果
m = 10, n = 20 m = 20, n = 10
对于引用类型的参数,由于传递的是地址,所以方法内进行的操作是对改地址对应的对象进行的操作,也就是说操作的是实参所对应的同一个对象。这一点与变量赋值时的结果是相符的。
6. 封装和隐藏
6.1 封装性的引入
为什么需要封装以及封装的作用和含义
- 要使用洗衣机,只需要了解开关和模式就可以,没有必要了解电动机和洗衣机的内部结构
- 开车只需要了解如何操作和交通规则,没必要了解车的详细结构和原理
程序设计追求“高内聚,低耦合”
- 高内聚:类的内部数据操作细节自己完成,不允许外部干涉
- 低耦合:仅对外暴露少量方法供使用
- 隐藏对象内部的复杂性,只对外公开简单的接口,便于外界调用,从而提高系统的可扩展性、可维护性。通俗来说,就是把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
public class AnimalTest {
public static void main(String[] args) {
Animal a = new Animal();
a.name = "大黄";
a.age = 1;
a.legs = 4;
}
}
class Animal {
String name;
int age;
int legs;
public void eat() {
System.out.println("动物进食");
}
public void show() {
System.out.println("name:" + name + ", age:" + age + "legs:" + legs);
}
}
对于以上情况,我们并不希望外界可以直接操作一个类的部分属性,因为外界可能会进行一些超出对象限制的赋值操作,因此,对属性修改时需要添加额外的限制条件,需要使用方法来进行。在上述类
Animal
中添加以下方法:public void setLegs(int l) { if (l >= 0 && l % 2 == 0) { legs = l; } else { legs = 0; } }
此时外界可通过
setLegs()
方法对属性进行操作。但是此时外界仍然可以直接访问属性,在有了方法进行属性操作后我们不希望外界可以继续知己操作属性,故引入
private
权限修饰符。对legs
属性的声明进行如下修改即可:private int legs;
此时直接调用该属性则会直接报错。
我们将类的属性私有化(private),同时,提供公共(public)的方法来获取(getXxx())和设置(setXxx())属性.
6.2 四大权限修饰符
封装性的体现需要权限修饰符的配合
- Java规定的四种权限(从小到大排列):private、缺省、protected、public
- 四种权限可以用来修饰类及类内部结构:属性、方法、构造器、内部类。
- 具体来说,四种权限都可以修饰类的内部结构,但仍有区别:修饰类的话只能使用缺省和public
修饰符 | 类内部 | 同一个包 | 不同包的子类 | 同一个工程 |
---|---|---|---|---|
private | Yes | |||
(缺省) | Yes | Yes | ||
protected | Yes | Yes | Yes | |
public | Yes | Yes | Yes | Yes |
7. 构造器(构造方法, constructor)
作用:
- 创建对象,使用
new
关键字时即调用了类的构造器。 - 初始化对象属性
- 创建对象,使用
说明:
- 如果没有显示定义类的构造器的话,系统默认提供一个空参的构造器
- 定义构造器的格式:
权限修饰符 类名(形参列表)
- 可以定义多个构造器,带参数的构造器可以用来给属性赋值
- 如果自己显示定义了构造器,则系统不再提供默认空参构造器。
public class PersonTEst {
public static void main(String[] args) {
Person p = new Person();
Person p1 = new Person("Tom");// 使用带参数列表构造器
}
}
class Person {
String name;
int age;
// 空参构造器
public Person() {
System.out.println("构造器被调用");
}
// 带参数构造器
public Person(String n) {
name = n;
}
}
构造器和方法有本质区别,不可以混为一谈。一个类中定义的多个构造器构成重载。
总结:属性赋值的先后顺序
- 默认初始化
- 显式初始化
- 构造器初始化
- 通过“对象.方法”或者“对象.属性”方式赋值。
8. JavaBean的使用
JavaBean是一种Java语言写成的可重用组件,所谓JavaBean,是指符合如下标准的Java类:
- 类是公共的
- 有一个无参的公共构造器
- 有属性,且有对应的get、set方法。
用户可以使用JavaBean将功能、处理、值、数据库访问和其它任何可以用Java代码创造的对象进行打包,并且其它的开发者可以通过内部的JSP页面、Servlet、其它JavaBean、applet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地的复制和粘贴功能,而不用关心任何改变。
public class Customer {
private int id;
private String name;
public Customer() {
}
public void setId(int i) {
id = i;
}
public int getId() {
return id;
}
public void setName(String n) {
name = n;
}
public String getName() {
return name;
}
}
以上类即为一个简单的JavaBean
9. this关键字的使用
this
关键字可以用来修饰:属性、方法、构造器this
修饰属性和方法- 在类的方法中,this可以理解为“当前对象”,如在类的方法中,可以使用
this.属性
或者this.方法
调用当前对象的属性或方法,但是通常情况下会省略。如果方法的形参和类的属性同名,则必须显式使用this关键字。 - 在类的构造器中,我们可以使用
this.属性
或者this.方法
调用当前正在创建的类的属性和方法。
- 在类的方法中,this可以理解为“当前对象”,如在类的方法中,可以使用
this
修饰构造器- 在类的构造其中,可以显式使用
this(形参列表)
方式调用本类中指定的其它构造器。 - 构造器不可以通过
this(形参列表)
形式调用自己,且不能出现循环调用。 - 如果一个类中有N个构造器,则最多有
n - 1
个构造器使用此方法。 this(形参列表)
必须位于使用该方式的构造器的首行。- 一个构造器内部只能使用一次
this(形参列表)
- 在类的构造其中,可以显式使用
10. package关键字的使用
- 为了更好地实现项目的管理,提出了包的概念
- 使用
package
声明类或接口所在的包,写在源文件的首行。 package
属于标识符,需要遵循命名规范,即需要见名知意(一般都是小写字母,不同分类用.
隔开,包名一般使用域名倒置,例如com.hxuanyu.xxx
)。- 每一个
.
代表一层文件目录。 - 同一个包下不能命名同名的接口或类。
11. import关键字的使用
- 在源文件中显式使用
import
关键字导入指定包下的类、接口 - 声明在包声明和类声明之间
- 如果需要导入多个包,则并列写出即可
- 可以使用
xxx.xxx.*
的方式,表示导入包下的所有结构 java.lang
包下的类和接口可以省略import
语句- 同一个包下的结构互相使用可以省略
import
语句 - 如果再源文件中使用了不同包下的同名的类,则至少有一个类需要以全类名的方式进行使用,如
com.hxuanyu.test.Main main = new com.hxuanyu.test.Main();
- 如果使用
xxx.*
表示可以调用xxx
包下的所有结构,但如果使用的是xxx
子包下的结构,则仍需要显式导入子包中的内容。即只要包的完整名字不同,则表示不同的包。 import static xxx.xxx
表示导入指定类或接口中的静态结构(属性和方法)