Java从入门到精通!第四天(面向对象(一))
一、面向对象编程
1. 面向过程和面向对象编程
面向过程,强调的是功能行为,以函数为最小单位(比如 C 语言),面向对象,将功能封装到对象中,强调具备了功能的对象,以类/对象为基本单位(比如C++、Java等等)。Java是一个纯的面向对象的编程语言,在 Java 中,都是以类/对象为基本单位的。
2. 面向对象的三大基本特征
(1) 封装性
(2) 继承性
(3) 多态性
3. 类和对象
(1) 类是对一类事物的描述,是抽象的,概念上的定义。
(2) 对象是实际存在的该类事物的一个实体,也称为实例(Instance)。
(3) 我们可以将类比作是修建房屋的结构图,而对象就是根据这个结构图修建成的一栋一栋的具体的房屋。
(4) 万物皆对象,任何一个事物都是对象,也会有一个抽象的类来描述这个对象。类是对对象的抽象,对象是对类的具体化。
(5) 类具有属性和行为
1) 属性:描述类的静态特征
2) 行为:该类可以执行的操作
举例:
根据教师类创建多个实例(对象):
这里的“王五”对象不属于教师类,因为他多了一个“聚餐”的行为
4. 创建类的语法:
5. 创建类的对象(实例/Instance)
语法:
类名 对象名 = new 类名();
示例:
上例的内存结构图:
练习:创建一个学生类(id,name,age,sex,show()),然后产生对象打印对应的信息
6. 类的访问机制
(1) 在同一个类中的访问机制:类中的方法可以直接访问类的成员变量
(2) 在不同类的访问机制:需要先创建对象,再使用对象访问类中的成员
示例:
1) 在同一个类中:
2) 不同的类中:
7. 对象的使用
8. 对象的生命周期
9. Java 中的内存
(1)堆(Heap),此内存区域的唯一目的就是存放对象实例,几乎所有的实例都是在这里分配,类的对象和数组的内存分配都是放在堆上面。堆上面分配的内存不会自动释放,需要等待垃圾回收器回收。
(2)栈(Stack),指虚拟机栈,用于存放局部变量,方法参数等,包含基本数据类型变量和引用数据类型变量,对于引用数据类型变量,其中存放的是对象在堆中的首地址。栈上的变量在方法执行完之后,自动释放。
(3)方法区(Method Area):用于存储被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。
10. 匿名对象
我们在创建对象的时候,可以不定义对象句柄,而直接 new 一个对象之后就可以调用其属性或方法,例如:
11. 类的成员:属性(成员变量)
语法:
修饰符 数据类型 属性名 = 初始值;
说明:
修饰符:缺省的、private、protected、public
示例:
扩展:变量的分类
变量分为成员变量(属性)和局部变量
成员变量:在类的内部声明的变量称为成员变量(属性),其中包括实例变量(没有被static修饰)和类变量(被static修饰)
局部变量:在方法体内部的变量,主要有形参,方法内部变量,代码块等,超出作用域就释放。
12. 类的成员之二:方法(Method)
(1) 方法:
方法是类或对象行为特征的抽象,用于完成某个功能,在某些语言中称为函数或过程。
(2) 定义方法的语法:
说明:
修饰符:缺省的、private、protected、public
示例:
13. 方法的重载(overload)
(1) 方法的重载:
在同一个类中,允许有多个同名的方法,只要它们的参数个数或参数的类型不同即可,我们将这一类方法称为重载方法,重载方法跟返回值无关,方法重载的目的是为了完成一系列功能相同的计算。
示例:
public class OverLoadDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
//调用重载方法时,根据传递的实际参数的类型和个数不同来决定调用哪一个重载方法
Demo2 demo2 = new Demo2();
int s1 = demo2.sum(12, 23);//调用第一个重载方法
System.out.println(s1);
double s2 = demo2.sum(23.43, 45.6);//调用第二个重载方法
System.out.println(s2);
double s3 = demo2.sum(23.3f, 45.6f);//调用第二个重载方法
System.out.println(s3);
int s4 = demo2.sum(12, 23, 34);//调用第三个重载方法
System.out.println(s4);
}
}
class Demo1 {//没有使用方法重载的类
/**
* 计算累加和的方法定义,对于同一个功能,我们可以写多个不同的方法来完成,但是这样写的话就增加了代码的复杂度,而且代码不清晰
*/
int sum1(int a, int b) {
return a + b;
}
int sum2(int a, int b, int c) {
return a + b + c;
}
}
class Demo2 {//使用了方法重载的类
/**
* 方法重载的目的就是为了完成相似功能的计算
* 方法重载的特点:方法名相同,方法参数的个数或类型不同,跟返回值无关
*/
int sum(int a, int b) {
return a + b;
}
double sum(double a, double b) {//参数类型不同的方法重载
return a + b;
}
int sum(int a, int b, int c) {//参数个数不同的方法重载
return a + b + c;
}
}
14. 可变参数
在 JDK5.0 之后,可以采用可变的形参来传入同类型的多个参数值
语法:
示例:
15. 方法参数的值传递机制
Java 中的方法参数传递的方式只有一种:值传递,将实参的副本传递给形参
(1) 对于基本数据类型的参数传递:
传递的是实参值的副本给形式参数,原来的实际参数值保持不变。
(2) 对于引用类型的参数传递:
传递的是指向堆内存的地址值的副本。
示例:基本类型的参数传递
示例:引用类型的参数传递
上例的内存结构图:
二、封装性和隐藏性
1. 举例:
我们用洗衣机的时候,不需要碰电机,也不需要去碰内部的电子设备就可以直接洗衣服,相当于洗衣机将电机和电子设备封装起来了(隐藏起来了)。
2. 我们写程序要追求“高内聚,低耦合”的设计理念
(1) 高内聚:类的内部数据操作细节自己完成,不允许外部干涉。
(2) 低耦合:仅对外暴露少量的方法供外部调用。
3. 封装性的实现:
将属性(程序的数据)私有化(private),私有化的目的就是将成员变量限定在类的内部使用,而对外提供公有的(public)的 getter/setter 方法来访问私有的成员变量,相当于把属性(数据)隐藏在了类的内部,而通过 getter/setter 方法向外暴露操作属性的方法。
示例:
public class FengZhuangDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
Employee emp = new Employee();
emp.setName("张三");
System.out.println(emp.getName());
emp.sal = 20000;//不能访问
}
}
class Employee {
//封装性(隐藏性)的实现:
//1. 将属性(成员变量)私有化(private),私有的成员仅限于在类的内部访问,外部不能访问
private String name;
private int age;
private double sal;
//2. 对外提供公有的(public)的 getter/setter 方法来访问属性,相当于对外暴露接口,这样我们可以控制外部的访问权限
//其中public 的成员是完全公开的,外部可以访问 public 的成员
public String getName() {//getter 方法,获取 name 属性的值
return name;
}
public void setName(String pname) {//setter 方法,设置name属性的值
name = pname;
}
public int getAge() { //只有 getter 方法,那么外部就只能读取 age,不能修改 age 属性
return age;
}
//sal 属性没有提供 getter/setter,那么外部就不能访问
}
4. 构造函数(构造方法)
(1) 构造函数的特点
1) 构造函数的方法名和类名相同。
2) 构造函数没有返回值(跟 void 不一样)。
3) 构造函数的作用是用于创建对象,并完成初始化的工作。
示例:
public class ConstructorDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
/**
* 回顾之前创建对象的语法:类名 对象名 = new 类名(); 其实这里的"类名()"就是构造函数,所以创建对象的语法修改为:
* 类名 对象名 = new 构造函数; //对象是通过 new 操作符去调用构造函数来产生的
*/
Man man = new Man();//这里通过 new 调用 Man() 构造函数的时候,就会完成属性的初始化
man.show();
}
}
class Man{
private String name;//属性私有化
private int age;
/**
*1) 构造函数的方法名和类名相同
2) 构造函数没有返回值(跟 void 不一样)
3) 构造函数的作用是用于创建对象,并完成初始化的工作
*/
public Man() {//无参构造函数(默认构造函数)
//构造函数的作用就是用于完成初始化工作
name = "张三";
age = 20;
}
//一个普通的方法,public 表示公有的,外部任何一个类都可以访问
public void show() {
System.out.println("name=" + name + ",age=" + age);
}
}
(2) 构造函数的分类
1) 无参构造函数
也称为默认构造函数,如果一个类没有提供任何构造函数,那么系统会自动给这个类提供一个默认构造函数,但是要注意,如果开发人员提供了构造函数,那么系统就不再提供默认构造函数了。
示例1:无参构造函数示例
示例2:如果开发人员写了构造函数,那么系统就不再提供默认构造函数
(3) 构造方法的重载
构造方法是允许被重载的,即多个构造方法可以参数个数或参数类型不同从而形成构造方法的重载
示例:
public class ConstructorOverLoadDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
Woman w1 = new Woman();//调用第一个构造函数
w1.show();
Woman w2 = new Woman("丽丽");//调用第二个构造函数
w2.show();
Woman w3 = new Woman("娜娜", 21);//调用第三个构造函数
w3.show();
}
}
class Woman {
private String name;
private int age;
/**
* 下面三个构造函数名称相同,参数的个数不同,从而形成了参数个数不同的方法重载,在创建对象的时候,通过传递的实际参数的情况
* 来决定调用哪一个构造函数
*/
public Woman() {
// TODO Auto-generated constructor stub
name = "李四";
age = 20;
}
public Woman(String pname) {
name = pname;
age = 20;
}
public Woman(String pname, int page) {
name = pname;
age = page;
}
public void show() {
System.out.println("name=" + name + ",age=" + age);
}
}
补充:构造函数的内存结构:
上述代码演示了创建对象时构造方法的调用。即在创建对象时,会调用与参数列表对应的构造方法。
上述代码的图解:
图解说明:
1、首先会将main方法压入栈中,执行main方法中的 new Person("张三",23);
2、在堆内存中分配一片区域,用来存放创建的Person对象,这片内存区域会有属于自己的内存地址(0x88)。然后给成员变量进行默认初始化(name=null,age=0)。
总结:执行构造方法中的代码( name = n;age = a ;),将变量a对应的23赋值给age,将变量n对应的”张三”赋值给name,这段代码执行结束后,成员变量age和name的值已经改变。执行结束之后构造方法弹栈,Person对象创建完成。将Person对象的内存地址0x88赋值给p2。
5. this 关键字
(1) 作用
1) 在方法内部,this 代表当前对象本身
2) 在构造方法内部,this 表示调用该类的另一个构造方法
注意:在 Java 中,不能在构造函数中通过构造函数名直接调用其他构造函数
示例1:this 代表当前对象本身
public class ThisDemo1 {
public static void main(String[] args) {
// TODO Auto-generated method stub
//调用第二个构造函数产生对象,此时的 this 就是指 dog 对象,并且该 dog 对象的 name 和 age 属性值已经在构造函数中赋值了
Dog dog = new Dog("汪汪", 1);
System.out.println(dog.getName() + "," + dog.getAge());
}
}
class Dog {
private String name;
private int age;
public Dog() {
// TODO Auto-generated constructor stub
}
public Dog(String name, int age) {
//我们本来的意思是想用形式参数 name 给成员变量 name 赋值,由于两个名称相同,直接这样赋值就没有意义了
//name = name;
/**
* 我们可以使用 this 关键字来区分,this 表示的就是当前对象(待会儿通过 new 调用构造函数产生)
* this.name 表示当前对象的name 属性
* this.age 表示当前对象的 age 属性
*/
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;//this.name表示当前对象的 name 属性
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
示例2:this 在构造函数中调用其他构造函数
注意:使用 this 调用其他构造函数时,只能在构造函数中调用,不能普通方法中调,并且要作为该构造函数的第一条语句。我们也不能直接在构造函数中通过构造函数名直接调用构造函数,要使用 this 来调用。
修改正确:
6. 包(package)
(1) 包的作用
包相当于是命名空间,通过将类放置在不同的包中,可以防止命名冲突,同时可以将功能相似的类放在同一个包中,方便管理。
(2) 创建包的语法:
package 包名;
示例:注意:包名习惯上用小写字母
(3) 包的作用
1) 帮助管理大型软件系统:将功能相似的类划分到同一个包中
2) 可以包含类和子包,划分项目层次,便于管理
3) 解决了命名冲突的问题
4) 控制访问权限
(4) JDK 中常用的包(内置包)
1) java.lang 包:
包含了 Java语句的核心类,比如:String、Math、Integer、System、Thread 等,注意使用该包中的类不用导入(import)就可以使用,默认系统会自动导入。
2) java.net 包:
包含网络相关的各个类和接口
3) java.io 包:
包含输入输出流相关的类
4) java.util 包:
包含系统特性,接口的集合框架类
5) java.text 包:
包含一些 Java 格式化相关的类
6) java.sql 包:
包含 Java 操作数据库的 JDBC 组件相关的类
7. 导入包(import)
为了在 Java 源代码中使用包中定义的类,需要在使用前导入包,可以通过 import 关键字导入包
语法:导入类
示例:
Person.java:
/**
* 创建包的语句必须是 Java 源文件的第一条语句,注释除外。
* 包的命名规范:一般是公司域名的倒序,比如公司的域名是:xxx.edu.com,那么包名就是:com.edu.xxx
* 包的各个层级是通过"."进行分隔的,比如 com.edu.xxx 表示com包下面的edu子包下面的xxx子包,
* 包反应到文件系统中就是父目录和子目录的关系
*/
package com.edu.demo;
public class Person {
private String name;
private int age;
public Person() {
// TODO Auto-generated constructor stub
}
public Person(String name, int age) {
this.name = name;
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;
}
}
再创建一个类来使用上面的Person类: