下篇链接见:面向对象编程(基础)(下)
面向对象,是软件开发中的一类编程风格、开发范式。除了面向对象
,还有面向过程
、指令式编程
和函数式编程
。在所有的编程范式中,我们接触最多的还是面向过程和面向对象两种。
类比:史书类型
- 纪传体:以人物传记为中心,“本纪”叙述帝王,“世家”记叙王侯封国和特殊人物,“列传”记叙民间人物。
- 编年体:按年、月、日顺序编写。
- 国别体:是一部分国记事的历史散文,分载多国历史。
早期先有面向过程思想,随着软件规模的扩大,问题复杂性的提高,面向过程的弊端
越来越明显,出现了面向对象思想并成为目前主流的方式。
POP
过程
:过程就是操作数据的步骤。如果某个过程的实现代码重复出现,那么就可以把这个过程抽取为一个函数
。这样就可以大大简化冗余代码,便于维护。函数
为组织单位。执行者思维
。适合解决简单问题。扩展能力差、后期维护难度较大。OOP
关注的焦点是类
:在计算机程序设计过程中,参照现实中事物,将事物的属性特征、行为特征抽象出来,用类来表示。
典型的语言:Java、C#、C++、Python、Ruby和PHP等
代码结构:以 类
为组织单位。每种事物都具备自己的 属性
和 行为/功能
。
是一种“ 设计者思维
”,适合解决复杂问题。代码扩展性强、可维护性高。
思考1:如何开车?
面向过程思想思考问题时,我们首先思考“ 怎么按步骤实现?
”并将步骤对应成方法,一步一步,最终完成。 这个适合 简单任务
,不需要过多协作
的情况。针对如何开车,可以列出步骤:
面向过程适合简单、不需要协作的事务,重点关注如何执行。
思考2:如何造车?
造车太复杂,需要 很多协作
才能完成。此时我们思考的是“ 车怎么设计?
”,而不是“怎么按特定步骤造车的问题”。这就是思维方式的转变,前者就是面向对象思想。所以,面向对象(Oriented-Object)思想更契合人的思维模式。
用面向对象思想思考“如何设计车”:
自然地,我们就会从“车由什么组成”开始思考。发现,车由如下结构组成:
我们找轮胎厂完成制造轮胎的步骤,发动机厂完成制造发动机的步骤,…;这样,大家可以同时进行车的制造,最终进行组装,大大提高了效率。但是,具体到轮胎厂的一个流水线操作,仍然是有步骤的,还是离不开面向过程思维!
因此,面向对象可以帮助我们从宏观上把握、从整体上分析整个系统。 但是,具体到实现部分的微观操作(就是一个个方法),仍然需要面向过程的思路去处理。
注意:
我们千万不要把面向过程和面向对象对立起来。他们是相辅相成的。面向对象离不开面向过程!
类比举例1:
当需求单一,或者简单时,我们一步步去操作没问题,并且效率也挺高。
可随着需求的更改,功能的增多,发现需要面对每一个步骤很麻烦了,这时就开始思索,能不能把这些步骤和功能进行封装,封装时根据不同的功能,进行不同的封装,功能类似的封装在一起。这样结构就清晰了很多。用的时候,找到对应的类就可以了。这就是面向对象的思想。
类比举例2:人把大象装进冰箱
人{
打开(冰箱){
冰箱.开门();
}
操作(大象){
大象.进入(冰箱);
}
关闭(冰箱){
冰箱.关门();
}
}
冰箱{
开门(){ }
关门(){ }
}
大象{
进入(冰箱){ }
}
练习:抽象出下面系统中的“类”及其关系
人认识世界,其实就是面向对象的。比如,我们认识一下美人鱼(都没见过)
经过“仔细学习”,发现美人鱼通常具备一些特征:
这个总结的过程,其实是 抽象化
的过程。抽象出来的美人鱼的特征,可以归纳为一个 美人鱼类
。而图片中的都是这个类呈现出来的 具体的对象
。
类(Class)
和 对象(Object)
是面向对象的核心概念。
1、什么是类
类:具有相同特征的事物的抽象描述,是 抽象的
、概念上的定义。
2、什么是对象
对象:实际存在的该类事物的每个个体
,是具体的
,因而也称为实例(instance)
。
可以理解为: 类 => 抽象概念的人
;对象 => 实实在在的某个人
3、类与对象的关系错误理解
曰:“白马非马,可乎?”
曰:“可。”
曰:“何哉?”
曰:“马者,所以命形也。白者,所以命色也。命色者,非命形也,故曰白马非马。”
面向对象程序设计的重点是
类的设计
类的设计,其实就是类的成员的设计
细胞
构成的。同理,Java代码世界是由诸多个不同功能的类
构成的。现实生物世界中的细胞又是由什么构成的呢?细胞核、细胞质、…
Java中用类class来描述事物也是如此。类,是一组相关属性
和行为
的集合,这也是类最基本的两个成员。
成员方法
步骤1:类的定义
类的定义使用关键字:class。格式如下:
[修饰符] class 类名{
属性声明;
方法声明;
}
举例1:
public class Person {
//声明属性age
int 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:对象的创建
//方式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();//访问方法
}
}
图示理解:
举例2:针对前面步骤1的举例2:类的实例化(创建类的对象)
public class Game {
public static void main(String[] args) {
Person p = new Person();
//通过Person对象调用属性
p.name = "康师傅";
p.gender = '男';
p.dog = new Dog(); //给Person对象的dog属性赋值
//给Person对象的dog属性的type、nickname属性赋值
p.dog.type = "柯基犬";
p.dog.nickName = "小白";
//通过Person对象调用方法
p.feed();
}
}
HotSpot Java虚拟机的架构图如下。其中我们主要关心的是运行时数据区部分(Runtime Data Area)。
其中:
堆(Heap)
:此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
栈(Stack)
:是指虚拟机栈。虚拟机栈用于存储局部变量等。局部变量表存放了编译期可知长度的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类 型,它不等同于对象本身,是对象在堆内存的首地址)。 方法执行完,自动释放。
方法区(Method Area)
:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
举例:
class Person { //类:人
String name;
int age;
boolean isMale;
}
public class PersonTest { //测试类
public static void main(String[] args) {
Person p1 = new Person();
p1.name = "赵同学";
p1.age = 20;
p1.isMale = true;
Person p2 = new Person();
p2.age = 10;
Person p3 = p1;
p3.name = "郭同学";
}
}
内存解析图:
说明:
- 堆:凡是new出来的结构(对象、数组)都放在堆空间中。
- 对象的属性存放在堆空间中。
- 创建一个类的多个对象(比如p1、p2),则每个对象都拥有当前类的一套"副本"(即属
性)。当通过一个对象修改其属性时,不会影响其它对象此属性的值。- 当声明一个新的变量使用现有的对象进行赋值时(比如p3 = p1),此时并没有在堆空间中创建新的对象。而是两个变量共同指向了堆空间中同一个对象。当通过一个对象修改属性时,会影响另外一个对象对此属性的调用。
面试题:对象名中存储的是什么呢?
答:对象地址
public class StudentTest {
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值",所以说类、数组都是引用数据类型,引用数据类型的变量中存储的是对象的地址,或者说指向堆中对象的首地址。
根据代码,画出内存图
class Car {
String color = "red";
int num = 4;
void show() {
System.out.println("color=" + color + ",num=" + num);
}
}
class CarTest {
public static void main(String[] args) {
Car c1 = new Car(); //建立对象c1
Car c2 = new Car(); //建立对象c2
c1.color = "blue"; //对对象的属性进行修改
c1.show(); //使用对象的方法
c2.show();
}
}
[修饰符1] class 类名{
[修饰符2] 数据类型 成员变量名 [= 初始化值];
}
说明:
示例:
public class Person {
private int age; //声明private变量 age
public String name = “Lila”; //声明public变量 name
}
1、变量的分类:成员变量与局部变量
其中,static可以将成员变量分为两大类,静态变量和非静态变量。其中静态变量又称为类变量、非静态变量又称为实例变量或者属性。接下来先学习实例变量。
2、成员变量 与 局部变量 的对比
声明位置和方式 (1)实例变量:在类中方法外 (2)局部变量:在方法体{}中或方法的形参列表、代码块中
在内存中存储的位置不同 (1)实例变量:堆 (2)局部变量:栈
生命周期 (1)实例变量:和对象的生命周期一样,随着对象的创建而存在,随着对象被GC回收而消亡, 而且每一个对象的实例变量是独立的。 (2)局部变量:和方法调用的生命周期一样,每一次方法被调用而在存在,随着方法执行的结束而消亡, 而且每一次方法调用都是独立。
作用域 (1)实例变量:通过对象就可以使用,本类中直接调用,其他类中“对象.实例变量” (2)局部变量:出了作用域就不能使用
修饰符 (1)实例变量:public,protected,private,final,volatile,transient等 (2)局部变量:final
默认值 (1)实例变量:有默认值 (2)局部变量:没有,必须手动初始化。其中的形参比较特殊,靠实参给它初始化。
3、对象属性的默认初始化赋值
当一个对象被创建时,会对其中各种类型的成员变量自动进行初始化赋值。
4、举例
class Person {//人类
//1.属性
String name;//姓名
int age = 1;//年龄
boolean isMale;//是否是男性
public void show(String nation) {
//nation:局部变量
String color;//color:局部变量
color = "yellow";
}
}
//测试类
class PersonTest {
public static void main(String[] args) {
Person p = new Person();
p.show("CHN");
}
}
《街霸》游戏中,每次人物出拳、出脚或跳跃等动作都需要编写50-80行的代码,在每次出拳、出脚或跳跃的地方都需要重复地编写这50-80行代码,这样程序会变得 很臃肿 ,可读性也非常差。为了解决代码 重复编写的问题,可以将出拳、出脚或跳跃的代码提取出来放在一个{}中,并为这段代码起个名字,这样在每次的出拳、出脚或跳跃的地方通过这个名字来调用这个{}的代码就可以了。
上述过程中,所提取出来的代码可以被看作是程序中定义的一个方法,程序在需要出拳、出脚或跳跃时调用该方法即可。
方法
是类或对象行为特征的抽象,用来完成某个功能操作。在某些语言中也称为 函数
或 过程
。实现代码重用,减少冗余,简化代码
public class Person {
private int age;
public int getAge() { //声明方法getAge()
return age;
}
public void setAge(int i) { //声明方法setAge
age = i; //将参数i的值赋给类的成员变量age
}
}
1、声明方法的语法格式
[修饰符] 返回值类型 方法名([形参列表])[throws 异常列表]{
方法体的功能代码
}
(1)一个完整的方法 = 方法头 + 方法体。
[修饰符] 返回值类型 方法名([形参列表])[throws 异常列表]
,也称为 方法签名
。通常调用方法时只需要关注方法头就可以,从方法头可以看出这个方法的功能和调用格式。(2)方法头可能包含5个部分
return 返回值
”搭配使用(3)方法体:方法体必须有{}括起来,在{}中编写完成方法功能的代码
(4)关于方法体中return语句的说明:
补充:方法的分类:按照是否有形参及返回值
2、类比举例
3、代码示例:
package com.atguigu.test04.method;
/**
* 方法定义案例演示
*/
public class MethodDefineDemo {
/**
* 无参无返回值方法的演示
*/
public void sayHello() {
System.out.println("hello");
}
/**
* 有参无返回值方法的演示
*
* @param length int 第一个参数,表示矩形的长
* @param width int 第二个参数,表示矩形的宽
* @param sign char 第三个参数,表示填充矩形图形的符号
*/
public void printRectangle(int length, int width, char sign) {
for (int i = 1; i <= length; i++) {
for (int j = 1; j <= width; j++) {
System.out.print(sign);
}
System.out.println();
}
}
/**
* 无参有返回值方法的演示
*
* @return
*/
public int getIntBetweenOneToHundred() {
return (int) (Math.random() * 100 + 1);
}
/**
* 有参有返回值方法的演示
*
* @param a int 第一个参数,要比较大小的整数之一
* @param b int 第二个参数,要比较大小的整数之二
* @return int 比较大小的两个整数中较大者的值
*/
public int max(int a, int b) {
return a > b ? a : b;
}
}
方法通过方法名被调用,且只有被调用才会执行。
1、方法调用语法格式
对象.方法名([实参列表])
2、示例
举例1:
package com.atguigu.test04.method;
/**
* 方法调用案例演示
*/
public class MethodInvokeDemo {
public static void main(String[] args) {
//创建对象
MethodDefineDemo md = new MethodDefineDemo();
System.out.println("-----------------------方法调用演示-------------------------");
//调用MethodDefineDemo类中无参无返回值的方法sayHello
md.sayHello();
md.sayHello();
md.sayHello();
//调用一次,执行一次,不调用不执行
System.out.println("------------------------------------------------");
//调用MethodDefineDemo类中有参无返回值的方法printRectangle
md.printRectangle(5, 10, '@');
System.out.println("------------------------------------------------");
//调用MethodDefineDemo类中无参有返回值的方法getIntBetweenOneToHundred
md.getIntBetweenOneToHundred();//语法没问题,就是结果丢失
int num = md.getIntBetweenOneToHundred();
System.out.println("num = " + num);
System.out.println(md.getIntBetweenOneToHundred());
//上面的代码调用了getIntBetweenOneToHundred三次,这个方法执行了三次
System.out.println("------------------------------------------------");
//调用MethodDefineDemo类中有参有返回值的方法max
md.max(3, 6);//语法没问题,就是结果丢失
int bigger = md.max(5, 6);
System.out.println("bigger = " + bigger);
System.out.println("8,3中较大者是:" + md.max(8, 9));
}
}
举例2:
//1、创建Scanner的对象
Scanner input = new Scanner(System.in);//System.in默认代表键盘输入
//2、提示输入xx
System.out.print("请输入一个整数:"); //对象.非静态方法(实参列表)
//3、接收输入内容
int num = input.nextInt(); //对象.非静态方法()
正确示例:
类{
方法1(){
}
方法2(){
}
}
错误示例:
类{
方法1(){
方法2(){ //位置错误
}
}
}
没有被调用
的时候,都在 方法区
中的字节码文件(.class)中存储。被调用
的时候,需要进入到 栈内存
中运行。方法每调用一次就会在栈中有一个 入栈
动作,即给当前方法开辟一块独立的内存区域,用于存储当前方法的局部变量的值。出栈
,如果方法有返回值,就会把结果返回调用处,如果没有返回值,就直接结束,回到调用处继续执行下一条指令。举例分析:
public class Person {
public static void main(String[] args) {
Person p1 = new Person();
p1.eat();
}
public static void eat() {
sleep();
System.out.println("人:吃饭");
}
public static void sleep() {
System.out.println("人:睡觉");
doSport();
}
public static void doSport() {
System.out.println("人:运动");
}
}
内存分析:
练习1:创建一个Person类,其定义如下:
要求:
(1)创建Person类的对象,设置该对象的name、age和sex属性,调用study方法,输出字符串“studying”,调用showAge()方法显示age值,调用addAge()方法给对象的age属性值增加2岁。
(2)创建第二个对象,执行上述操作,体会同一个类的不同对象之间的关系。
练习2:利用面向对象的编程方法,设计圆类Circle,包含属性(半径)和计算圆面积的方法。定义测试类,创建该Circle类的对象,并进行测试。
练习3:
10*8的*型矩形
,在main方法中调用该方法。10*8的*型矩形
外,再计算该矩形的面积,并将其作为方法返回值。在main方法中调用该方法,接收返回的面积值并打印。m*n的*型矩形
,并计算该矩形的面积, 将其作为方法返回值。在main方法中调用该方法,接收返回的面积值并打印。练习4:声明一个日期类型MyDate:有属性:年year,月month,日day。创建2个日期对象,分别赋值为:你的出生日期,你对象的出生日期,并显示信息。
练习5:用面向对象的方式编写用户登录程序。
用户类:
界面类:
参考代码:
public class User {
String name;
String password;//密码
/**
* 实现用户登录的判断
*
* @param inputName 输入的用户名
* @param inputPwd 输入的密码
*/
public void login(String inputName, String inputPwd) {
if (name.equals(inputName) && password.equals(inputPwd)) {
System.out.println("登录成功:欢迎你," + name);
} else {
System.out.println("登录失败:用户名或密码错误!");
}
}
/**
* 实现用户登录的判断
*
* @param inputName 输入的用户名
* @param inputPwd 输入的密码
* @return true:登录成功 false:登录失败
*/
public boolean login1(String inputName, String inputPwd) {
// if(name.equals(inputName) && password.equals(inputPwd)){
// return true;
// }else{
// return false;
// }
//简化为:
return name.equals(inputName) && password.equals(inputPwd);
}
}
/**
* 用户界面类UserInterface:
* - 在用户界面类中添加main方法,接受用户输入,并调用用户类的登录方法进行验证。
* - 输出:
* - 登录失败:用户名或密码错误!
* - 登录成功:欢迎你,用户名!
*/
public class UserInterface {
public static void main(String[] args) {
User u1 = new User();
u1.name = "Tom";
u1.password = "abc123";
Scanner scanner = new Scanner(System.in);
System.out.print("请输入用户名:");
String name = scanner.next();
System.out.print("请输入密码:");
String pwd = scanner.next();
//演示1:
// u1.login(name,pwd);
//演示2:
boolean isLogin = u1.login1(name, pwd);
if (isLogin) {
System.out.println("登录成功:欢迎你," + u1.name);
} else {
System.out.println("登录失败:用户名或密码错误!");
}
scanner.close();
}
}
(来源:尚硅谷-宋红康)