Day7-第5章 面向对象基础上(续)
5.6 面向对象
5.6.1 面向对象概述
1、C语言和Java语句
C语言是一种面向过程的程序设计语言,因为C语言是在面向过程思想的指引下去设计、开发计算机程序的。
Java语言是一种面向对象的程序设计语言,因为Java语言是在面向对象思想的指引下去设计、开发计算机程序的。
其中面向对象和面向过程都是一种编程思想,基于不同的思想会产生不同的程序设计方法。
-
面向过程的程序设计思想(Process-Oriented Programming),简称POP
- 关注的焦点是过程:过程就是操作数据的步骤。面向过程是分析出解决问题所需要的步骤,不同的步骤可以抽象为一个一个的函数。
- 代码结构:以函数为组织单位。独立于函数之外的数据称为全局数据,在函数内部的称为局部数据。
-
面向对象的程序设计思想( Object Oriented Programming),简称OOP
- 关注的焦点是类和对象:面向对象思想就是在计算机程序设计过程中,参照现实中事物,将事物的属性特征、行为特征抽象出来,用类来表示。某个事物的一个具体个体称为实例或对象。面向对象是把构成问题事务分解成各个对象,关注的是解决问题需要哪些对象。
- 代码结构:以类为组织单位。每种事物都具备自己的属性(即表示和存储数据,在类中用成员变量表示)和行为/功能(即操作数据,在类中用成员方法表示)。
2、面向过程与面向对象的区别
面向过程:
- 优点是性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源。而系统软件(例如各种操作系统)等一般采用面向过程开发,性能是最重要的因素。
- 缺点是没有面向对象易维护,易复用,易扩展。可维护性差,不易修改。
面向对象:
- 优点是易维护,易复用,易扩展。由于面向对象由封装,继承,多态性的特性,可以设计出耦合度低的系统,使系统更加灵活,更加易于维护。
- 缺点是性能比面向过程低。
举例说明:
3 故事:非活字印刷与活字印刷
....
如果是有了活字印刷,则只需更改几个字就可,其余工作都未白做。
一、要改,只需更改要改之字,此为可维护;
二、这些字并非用完这次就无用,完全可以在后来的印刷中重复使用,此乃可复用;
三、此诗若要加字,只需另刻字加入即可,这是可扩展;
四、字的排列其实可能是竖排可能是横排,此时只需将活字移动就可做到满足排列需求,此是灵活性好。”
在活字印刷术出现之前,上面的四种特性都无法满足,要修改,必须重刻,要加字,必须重刻,要重新排列,必须重刻,印完这本书后,此版已无任何可再利用价值。
5.6.2 类和对象
1、什么是类
类是一类具有相同特性的事物的抽象描述,是一组相关属性和行为的集合。
- 属性:就是该事物的状态信息。
- 行为:就是在你这个程序中,该状态信息要做什么操作,或者基于事物的状态能做什么。
2、什么是对象
对象是一类事物的一个具体个体(对象并不是找个女朋友)。即对象是类的一个实例,必然具备该类事物的属性和行为。
3、类与对象的关系
- 类是对一类事物的描述,是抽象的。
- 对象是一类事物的实例,是具体的。
- 类是对象的模板,对象是类的实体。
5.6.3 如何定义类
1、类的定义格式
关键字:class(小写)
【修饰符】 class 类名{
}
类的定义格式举例:
public class Student{
}
2、对象的创建
关键字:new
new 类名()//也称为匿名对象
//给创建的对象命名
//或者说,把创建的对象用一个引用数据类型的变量保存起来,这样就可以反复使用这个对象了
类名 对象名 = new 类名();
那么,对象名中存储的是什么呢?答:对象地址
public class TestStudent{
public static void main(String[] args){
System.out.println(new Student());//Student@7852e922
Student stu = new Student();
System.out.println(stu);//Student@4e25154f
int[] arr = new int[5];
System.out.println(arr);//[I@70dea4e
}
}
发现学生对象和数组对象类似,直接打印对象名和数组名都是显示“类型@对象的hashCode值",所以说类、数组都是引用数据类型,引用数据类型的变量中存储的是对象的地址,或者说指向堆中对象的首地址。
那么像“Student@4e25154f”是对象的地址吗?不是,因为Java是对程序员隐藏内存地址的,不暴露内存地址信息,所以打印对象时不直接显示内存地址,而是JVM帮你调用了对象的toString方法,将对象的基本信息转换为字符串并返回,默认toString方法返回的是“对象的运行时类型@对象的hashCode值的十六进制值”,程序员可以自己改写toString方法的代码(后面会讲如何改写)。
5.7 包(Package)
5.7.1 包的作用
(1)可以避免类重名:有了包之后,类的全名称就变为:包.类名
(2)可以控制某些类型或成员的可见范围
如果某个类型或者成员的权限修饰缺省的话,那么就仅限于本包使用。
(3)分类组织管理众多的类
例如:
- java.lang----包含一些Java语言的核心类,如String、Math、Integer、 System和Thread等,提供常用功能
- java.net----包含执行与网络相关的操作的类和接口。
- java.io ----包含能提供多种输入/输出功能的类。
- java.util----包含一些实用工具类,如集合框架类、日期时间、数组工具类Arrays,文本扫描仪Scanner,随机值产生工具Random。
- java.text----包含了一些java格式化相关的类
- java.sql和javax.sql----包含了java进行JDBC数据库编程的相关类/接口
- java.awt和java.swing----包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。
5.7.2 如何声明包
关键字:package
package 包名;
注意:
(1)必须在源文件的代码首行
(2)一个源文件只能有一个声明包的package语句
包的命名规范和习惯: (1)所有单词都小写,每一个单词之间使用.分割 (2)习惯用公司的域名倒置开头
例如:com.atguigu.xxx;
建议大家取包名时不要使用“java.xx"包
5.7.3 如何跨包使用类
==注意:==只有public的类才能被跨包使用
(1)使用类型的全名称
例如:java.util.Scanner input = new java.util.Scanner(System.in);
(2)使用import 语句之后,代码中使用简名称
import语句告诉编译器到哪里去寻找类。
import语句的语法格式:
import 包.类名;
import 包.*;
注意:
使用java.lang包下的类,不需要import语句,就直接可以使用简名称
import语句必须在package下面,class的上面
当使用两个不同包的同名类时,例如:java.util.Date和java.sql.Date。一个使用全名称,一个使用简名称
示例代码:
import java.util.Date;
import java.util.Scanner;
public class TestPackage {
public static void main(String[] args) {
/* java.util.Scanner input = new java.util.Scanner(System.in);
com.atguigu.test01.oop.Student stu = new com.atguigu.test01.oop.Student();*/
Scanner input = new Scanner(System.in);
Student student = new Student();
Date d1 = new Date();
java.sql.Date d2 = new java.sql.Date(0);
}
}
5.8 成员变量之实例变量
5.8.1 如何声明实例变量
【修饰符】 class 类名{
【修饰符】 数据类型 成员变量名;
}
Java类的成员变量分为两大类,静态变量(加staitc修饰)和非静态变量(不加static修饰)。其中静态变量又称为类变量,非静态变量又称为实例变量或者属性。==接下来先学习实例变量。==
示例:
public class Person{
String name;
char gender;
int age;
}
位置要求:必须在类中,方法外
类型要求:可以是Java的任意类型,包括基本数据类型、引用数据类型(类、接口、数组等)
修饰符:例如:public、protected、private、volatile、transient、final等,后面会一一学习。暂时都写public。
==注意:==如果实例变量的权限修饰符public没写的话,也仅限于本包使用,其他包的类是无法访问的。
5.8.2 对象的实例变量
1、实例变量的特点
(1)实例变量的值是属于某个对象的
- 必须通过对象才能访问实例变量
- 每个对象的实例变量的值是独立的
(2)实例变量有默认值
| 分类 | 数据类型 | 默认值 |
|---|---|---|
| 基本类型 | 整数(byte,short,int,long) | 0 |
| 浮点数(float,double) | 0.0 | |
| 字符(char) | '\u0000' | |
| 布尔(boolean) | false | |
| 数据类型 | 默认值 | |
| 引用类型 | 数组,类,接口 | null |
2、实例变量的访问
对象.实例变量
例如:
public class TestPerson {
public static void main(String[] args) {
Person p1 = new Person();
p1.name = "张三";
p1.age = 23;
p1.gender = '男';
Person p2 = new Person();
/*
(1)实例变量的值是属于某个对象的
- 必须通过对象才能访问实例变量
- 每个对象的实例变量的值是独立的
(2)实例变量有默认值
*/
System.out.println("p1对象的实例变量:");
System.out.println("p1.name = " + p1.name);
System.out.println("p1.age = " + p1.age);
System.out.println("p1.gender = " + p1.gender);
System.out.println("p2对象的实例变量:");
System.out.println("p2.name = " + p2.name);
System.out.println("p2.age = " + p2.age);
System.out.println("p2.gender = " + p2.gender);
}
}
3、实例变量的内存分析
内存是计算机中重要的部件之一,它是与CPU进行沟通的桥梁。其作用是用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。只要计算机在运行中,CPU就会把需要运算的数据调到内存中进行运算,当运算完成后CPU再将结果传送出来。我们编写的程序是存放在硬盘中的,在硬盘中的程序是不会运行的,必须放进内存中才能运行,运行完毕后会清空内存。Java虚拟机要运行程序,必须要对内存进行空间的分配和管理,每一片区域都有特定的处理数据方式和内存管理方式。
JVM的运行时内存区域分为:方法区、堆、虚拟机栈、本地方法栈、程序计数器几大块。
| 区域名称 | 作用 |
|---|---|
| 程序计数器 | 程序计数器是CPU中的寄存器,它包含每一个线程下一条要执行的指令的地址 |
| 本地方法栈 | 当程序中调用了native的本地方法时,本地方法执行期间的内存区域 |
| 方法区 | 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 |
| 堆内存 | 存储对象(包括数组对象),new来创建的,都存储在堆内存。 |
| 虚拟机栈 | 用于存储正在执行的每个Java方法的局部变量表等。局部变量表存放了编译期可知长度的各种基本数据类型、对象引用,方法执行完,自动释放。 |
Java对象保存在内存中时,由以下三部分组成:
-
对象头
- Mark Word:记录了和当前对象有关的GC、锁等信息。(Java高级再详细讲)
- 指向类的指针:每一个对象需要记录它是由哪个类创建出来的,而Java对象的类数据保存在方法区,指向类的指针就是记录创建该对象的类数据在方法区的首地址。该指针在32位JVM中的长度是32bit,在64位JVM中长度是64bit。
- 数组长度(只有数组对象才有)
-
实例数据
- 即实例变量的值
-
对齐填充
- 因为JVM要求Java对象占的内存大小应该是8bit的倍数,如果不满足该大小,则需要补齐至8bit的倍数,没有特别的功能。
5.8.3 实例变量是引用数据类型(警惕空指针异常)
实例变量也是变量,数据类型可以是8种基本数据类型,也可以是引用数据类型(数组、类等)。
注意:引用数据类型的实例变量,如果没有赋值给他一个对象,那么通过它再访问成员或元素,将会发生空指针异常。
class Husband{
String name;
Wife wife;
}
class Wife{
String name;
Husband husband;
}
class TestMarry{
public static void main(String[] args){
Husband h = new Husband();
h.name = "张三";
Wife w = new Wife();
w.name = "翠花";
System.out.println("丈夫的姓名:" + h.name + ",妻子:" + h.wife);
h.wife = w;
System.out.println("丈夫的姓名:" + h.name + ",妻子:" + h.wife.name);//警惕空指针异常
System.out.println("妻子的姓名:" + w.name + ",丈夫:" + w.husband);
w.husband = h;
System.out.println("妻子的姓名:" + w.name + ",丈夫:" + w.husband.name);//警惕空指针异常
System.out.println("---------------------------------------");
//离婚
h.wife = null;
w.husband = null;
h.wife = new Wife();
h.wife.name = "小何";
h.wife.husband = h;
System.out.println("丈夫的姓名:" + h.name + ",妻子:" + h.wife.name);
System.out.println("妻子的姓名:" + h.wife.name + ",丈夫:" + h.wife.husband.name);
}
}
总结:
(1)一个引用数据类型的变量,可以.出什么,和这个变量的类型有关,这个类型中有什么成员,就可以.出什么成员。
(2).操作是不是会发生空指针异常,要看.前面的变量有没有“引用”一个对象
5.8.4 对象的实例方法
1、在实例方法中直接使用当前对象的实例变量
实例方法依赖于对象,因为实例方法必须由对象来调用,那么调用当前方法的对象称为当前对象,在实例方法中使用this表示。
如果当前实例方法中没有局部变量与实例变量重名,也可以省略this.,如果有局部变量与实例变量重名,就必须加this.。
//(1)声明一个MyDate类型,有属性:年,月,日
public class MyDate {
public int year;
public int month;
public int day;
public void setValue(int year, int month ,int day){
this.year = year;
this.month = month;
this.day = day;
}
public String getInfo(){
return year+"年" + month+"月" + day+"日";
}
}
2、调用格式(其他类中)
当出现某个类的多个对象都要进行相同操作时,这些操作的重复性代码,就可以封装为实例方法。
在其他类中调用实例方法:
对象名.实例方法(【实参列表】)
//1、创建Scanner的对象
Scanner input = new Scanner(System.in);//System.in默认代表键盘输入
//2、提示输入xx
System.out.print("请输入一个整数:"); //对象.非静态方法(实参列表)
//3、接收输入内容
int num = input.nextInt(); //对象.非静态方法()
案例演示:
public class MyDate {
public int year;
public int month;
public int day;
public void setValue(int year, int month ,int day){
this.year = year;
this.month = month;
this.day = day;
}
public String getInfo(){
return year+"年" + month+"月" + day+"日";
}
}
public class Employee {
public String name;
public MyDate birthday;
public void setBirthday(int year, int month, int day){
birthday = new MyDate();
// birthday.year = year;
// birthday.month = month;
// birthday.day = day;
birthday.setValue(year, month, day);
}
public String getInfo(){
// return "姓名:" + name +",生日:" + birthday.year+"年" + birthday.month+"月" + birthday.day+"日";
return "姓名:" + name +",生日:" + birthday.getInfo();
}
}
public class EmployeeTest {
public static void main(String[] args) {
Employee e1 = new Employee();
e1.name = "张三";
// e1.birthday = new MyDate();
// e1.birthday.year = 1990;
// e1.birthday.month = 5;
// e1.birthday.day = 1;
e1.setBirthday(1990,5,1);
// System.out.println("e1的姓名:" + e1.name +",生日:" + e1.birthday.year+"年" + e1.birthday.month+"月" + e1.birthday.day+"日");
System.out.println("e1的" + e1.getInfo());
Employee e2 = new Employee();
e2.name = "李四";
// e2.birthday = new MyDate();
// e2.birthday.year = 1995;
// e2.birthday.month = 6;
// e2.birthday.day = 1;
e2.setBirthday(1996,6,1);
// System.out.println("e2的姓名:" + e2.name +",生日:" + e2.birthday.year+"年" + e2.birthday.month+"月" + e2.birthday.day+"日");
System.out.println("e2的" + e2.getInfo());
}
}
3、调用格式(本类中)
在本类中:直接使用,但是仅限于在本类中的另一个实例方法中直接使用,不能在本类中的另一个静态方法中直接使用。(关于静态方法与实例方法的区别请看下一章)
实例方法(【实参列表】)
案例演示:
public class Circle {
double radius;
double area(){
return Math.PI * radius * radius;
}
double perimeter(){
return 2 * Math.PI * radius;
}
String getInfo(){
return "半径:" + radius +",周长:" + perimeter() + ",面积:" + area();
}
}
public class TestCircle{
public static void main(String[] args) {
//创建两个Circle圆对象
Circle c1 = new Circle();
Circle c2 = new Circle();
//为两个圆对象的半径属性赋值
c1.radius = 1.2;
c2.radius = 2.5;
//显示
//System.out.println("c1的半径:" + c1.radius + ",周长:" + 2 * Math.PI * c1.radius + ",面积:" + Math.PI * c1.radius * c1.radius);
//System.out.println("c2的半径:" + c2.radius + ",周长:" + 2 * Math.PI * c2.radius + ",面积:" + Math.PI * c2.radius * c2.radius);
System.out.println("c1" + c1.getInfo()) ;
System.out.println("c2" + c2.getInfo()) ;
}
}
4、实例方法的内存分析
5.9 对象数组
数组是用来存储一组数据的容器,一组基本数据类型的数据可以用数组装,那么一组对象也可以使用数组来装。
即数组的元素可以是基本数据类型,也可以是引用数据类型。当元素是引用数据类型是,我们称为对象数组。
注意:对象数组,首先要创建数组对象本身,即确定数组的长度,然后再创建每一个元素对象,如果不创建,数组的元素的默认值就是null,所以很容易出现空指针异常NullPointerException。
5.9.1 对象数组的声明和使用
案例:
(1)定义矩形类,包含长、宽属性,area()求面积方法,perimeter()求周长方法,String getInfo()返回圆对象的详细信息的方法
(2)在测试类中创建长度为5的Rectangle[]数组,用来装3个矩形对象,并给3个矩形对象的长分别赋值为10,20,30,宽分别赋值为5,15,25,遍历输出
public class Rectangle {
double length;
double width;
double area(){//面积
return length * width;
}
double perimeter(){//周长
return 2 * (length + width);
}
String getInfo(){
return "长:" + length +
",宽:" + width +
",面积:" + area() + //直接调用本类的另一个实例方法
",周长:" + perimeter();
}
}
public class ObjectArrayTest {
public static void main(String[] args) {
//声明并创建一个长度为3的矩形对象数组
Rectangle[] array = new Rectangle[3];
//创建3个矩形对象,并为对象的实例变量赋值,
//3个矩形对象的长分别是10,20,30
//3个矩形对象的宽分别是5,15,25
//调用矩形对象的getInfo()返回对象信息后输出
for (int i = 0; i < array.length; i++) {
//创建矩形对象
array[i] = new Rectangle();
//为矩形对象的成员变量赋值
array[i].length = (i+1) * 10;
array[i].width = (2*i+1) * 5;
//获取并输出对象对象的信息
System.out.println(array[i].getInfo());
}
}
}
5.9.2 对象数组的内存图分析
对象数组中数组元素存储的是元素对象的首地址。