1. OOP概念
概念: 面向对象编程(Object Oriented Programming,OOP)是种计算机编程架构,在考虑问题时以具体的事物对象为单位,考虑它的属性和方法,它可以使人们的编程与实际的世界更加接近,所有的对象被赋予属性和方法,这样变成就更加富有人性化、模拟现实世界中的概念、是一种设计和实现软件系统的思想。
- 面向过程思想:
- 面向过程的设计思想在考虑问题时,是以一个具体的流程为单位,考虑它的实现办法,关心的是功能的实现,比如我们完成一个需求的步骤,要做什么,怎么做,第一步,第二步...得到结果。每一个步骤,每一个过程,我们都参与其中,然后得到最终我们想要的结果。
- C语言就是典型的面向过程的语言
- 面向对象思想:
- 面向对象和面向过程是相辅相成的,而不是互斥的,比如说有个需求是将大象装进冰箱:第一步打开冰箱门,第二步把大象装进去,第三步把冰箱门带上...这种简单的、不需要协作的问题,使用面向过程的思想显然是最好的。
- 但如果需求是制作一个冰箱这种复杂的事务,面向过程就很困难了,需要面向对象的思维来设计冰箱的结构图、颜色、大小、噪音...等等一系列的属性和行为。
- 当然,宏观上使用面向对象设计,微观上仍然要使用面向过程来实现。
- OOP四大特征:
- OOP的四大特征是:抽象、封装、继承、多态。
- OOP的三大特征是:封装、继承、多态。(因为抽象不涉及代码)
1.1 对象
概念: 对象Object一词是19世纪的现象学大师胡塞尔提出并定义的,他认为,对象是世界中的物体在人脑中的映像,是人的意识之所以为意识的反映,是作为一种概念而存在的先念的东西,比如当我们认识到一种新的物体,它叫树,于是在我们的意识当中就形成了树的概念,这个概念会一直存在于我们的思维当中,并不会因为这棵树被砍掉而消失,这个概念就是现实世界当中的物体在我们意识当中的映象,就是一个对象。
- 万事万物皆对象,如一个苹果,一项政策,一种心情等。
- 对象具有唯一性,就像世界上不可能出现两片相同的叶子。
- 对象可以具有属性和行为(方法)。
1.2 抽象
概念: 所谓抽象即抽出相象的部分,也叫提取,提炼或者归纳总结,就是忽略一个主题中与当前目标无关的那些方面,以便充分地注意与当前目标有关的方面,然后将这些我们关注的部分,归纳总结在一起,形成一个概念类。
狼狗引导图
总结:
- 首先,这些动物的身高、体重、血型、出生日期等等属性,我们会自动的忽略掉,因为我们不关心(忽略无关)。
- 然后,眼神凶恶、尾巴向下、群居动物、责任心重、团结、会捕猎、会嗷嗷叫... 这些属性和方法被我们抽取出来,归纳总结成狼类,这个狼类会成为你心里的一个"标准",或者叫"模板",如果让你创建一只真实的狼出来,你一定会按照这个"模板",将这些属性和方法赋予你创造出来的"狼"。
- 最后,眼神二逼、尾巴向上、独居动物、无责任心、忠诚、会卖萌、会汪汪叫... 这些属性和方法被我们抽取出来,归纳总结成犬类,这个犬类会成为你心里的一个"标准",或者叫"模板",如果让你创建一只真实的狗出来,你一定会按照这个"模板",将这些属性和方法赋予你创造出来的"狗"。
- "狼类模板"和"犬类模板"是我们通过抽象思想获得出来的,而"一只真实的狼"和"一只真实的狗",是你通过对应的"模板"创造(new)出来的。
1.3 封装
概念:
- 封装(Encapsulation)是一个过程,先"装"后"封",就是将属性和方法一起包装到一个程序单元中,并隐藏方法的实现过程,封装过程的结果,以类的形式实现。
- 类就是对象通过抽象的思想将比较关注的属性和方法提取出来,然后再将这些属性和方法封装到一个单元中而形成的最终产物。
- 类与实例的关系:JAVA中,类就是"模板",实例就是"狼"或者"狗",我们通过对一个一个的实例抽象归纳,从而得到模板,我们通过模板实例化,得到对应的实例。
1.3.1 装
概念:
- 对于上面的案例,我们已经通过抽象得到了一个"狼类"和"狗类",也得到了一堆它们对应的属性和方法,但这些东西不能只存在于我们的想象和记忆中,我们需要把它们纪录下来,对于java来说,就是纪录在一个类中。
- 通过抽象思想得到想要关注的属性和方法,然后把它们写在一个类中,这个过程就是"装"。
伪代码: 狼类.java
狼类 {
属性:
眼神 = 凶恶;
尾巴 = 向下;
生活习性 = 群居;
性格 = [责任心重,团结]
方法:
捕猎方法();
嗷嗷叫方法();
}
伪代码: 犬类.java
犬类 {
属性:
眼神 = 二逼;
尾巴 = 向上;
生活习性 = 独居;
性格 = [无责任心,忠诚]
方法:
卖萌方法();
汪汪叫方法();
}
1.3.2 封
概念:
- "封"是为了保护对象隐私,保护对象内部实现细节,隔离变化,因为对象内部是非常容易变化的,就像机箱内部的硬件或者配置,实时更新,但是封装到一个机箱后,你不需要实时跟进,无论里面怎么变化,给你的功能都是不会变的,开机重启,usb接口等等。
- 封装会产生隔离,面对不同的调用者,是可以有不同的权限的,这被称之为封装的权限,比如现实生活中去转户口,需要透露自己大量甚至全部的信息,父母亲面前,需要透露稍微少一些的信息,朋友面前就会透露的更少了,关于一些自己的小秘密,只有自己知道...
- Java的属性和方法也有相对应的权限,通过很明显的标记来标识,叫权限修饰符。
public:同项目中均可访问。protected:同类,同包,不同包父子类均可访问。friendlly:同类,同包均可访问,该关键字不能显示使用。private:只有同类可访问。
2. 类的设计
概念: 之前的学习中,我们一直在使用JDK提供给我们的类,如String、Scanner等,接下来我们要创建一个属于自己的类。
- 类以
class为关键字,在一个java文件中可以有多个并列的class,但是public修饰的class只能有一个。 - 类名其实是类的简称,建议首字母大写驼峰制度,类的全名是包名加上类名的组合。
- 类根据不同的修饰和位置,可以分为五种:
public class A{}:公共类:所有类都可以访问。class A{}:友元类:同包可以访问的。final class A{}: 最终类:不能被继承,且final类中的所有属性也都是final的。static class A{}:静态类:只能写在其他类内部。abstract class A{}:抽象类:可以包含抽象方法的类。
类的单一职责:在系统中,每个类都具有一定的职责,职责指的是类要完成什么样的功能,要承担什么样的义务,一个类可以有多种职责,但优秀的类一般只有一种职责,在设计类的时候,将类的职责分解成为类的属性和方法,类的属性负责存储数据,类的方法负责操作数据。
3. 类的使用
概念: 在软件系统运行时,通过类来创建实例 instance,这一过程叫做实例化,创建出来的对象被称为该类的实例。
- 声明赋值: 实例化的过程就是通过new关键字来在堆内存中开辟一块空间a,然后再调用该类的构造器将该类构造出来并放到空间a里面的过程。
- 在我们设计模板的时候必须设计一个构造器(默认使用模板自带的隐式构造器)。
- 在实例化的过程中,必须调用构造器才可以。
- 成员调用:Java中的调用使用分量符"."来调用成员。
- 非静态的成员属性属于某个实例,是互相独立的,使用所属的实例名调用。
- 静态的成员属性属于类(也成为类属性),是共享且唯一的,使用类名直接调用。
源码: /javase-oop/
- src:
c.y.start.MyClassTest
/**
* @author yap
*/
public class MyClassTest {
public int id = 1;
String name = "张三";
public static int age = 23;
private String gender = "女";
@Test
public void instance() {
MyClassTest zhaosi = new MyClassTest();
System.out.println(zhaosi);
}
@Test
public void changeForNoStaticField() {
MyClassTest zhaosi = new MyClassTest();
MyClassTest liuneng = new MyClassTest();
zhaosi.id = 50;
System.out.println(liuneng.id);
}
@Test
public void changeForStaticField() {
MyClassTest zhaosi = new MyClassTest();
MyClassTest liuneng = new MyClassTest();
MyClassTest.age = 50;
System.out.println(MyClassTest.age);
}
}
4. 类的成员属性
概念: 类中可以存放属性变量,简称属性。
- 位置:
- 如果一个变量定义在方法体中,就叫做局部变量。
- 如果一个变量定义在方法体外,类体中,就叫这个类的成员属性。
- 分类:属性根据修饰符而分为六种:
public String name:公共属性:跨包任何类可见。protected String name:保护属性:跨包父子类可见。String name:默认属性:同包可见。private String name:私有属性:本类可见。static String name:静态属性:共享且唯一。final String name:常量属性:不可二次赋值属性,建议全大写。
- 默认值:属性变量跟局部变量不同,属性变量拥有默认值。
byte,short,int,long默认值是0。float,double默认值是0.0。char默认值是空格,\u0000。boolean默认值是false。- 引用数据类型的默认值全都是null。
5. 类的成员方法
概念: 成员方法和成员属性一样,都是类中的一个成员。
- 分类:方法根据修饰符分为8种:
public void fun(){}:公共方法:跨包任何类可见。protected void fun(){}:保护方法:跨包父子类可见。void fun(){}:默认方法:同包可见。private void fun(){}:私有方法:本类可见。static void fun(){}:静态方法:共享且唯一。public fun(){}:构造方法:俗称构造器,创造类时必须调用的方法。abstract void fun();抽象方法:一个没有方法体的方法。final void fun(){}最终方法:不能被重写。
- 方法四要素:
- 方法名:方法的名字,命名规范同变量名,首字母小写驼峰,尽量使用动词。
- 返回类型:方法要返回给调用者一个什么类型的数据如果没返回任何数据则类型为void。
- 形参列表:方法要接收哪些类型的数据,多个数据用逗号隔开如果不需要传入数据,则该方法叫做无参方法。
- 方法体:方法的逻辑代码,注意最好一个方法只做一件事。
5.1 方法的设计思路
流程: 当我们想要设计一个方法的时候,建议遵循以下顺序:
- 用途:我想设计一个能够将两个数字加起来得出结果的方法。
- 访问权限:这个方法我只想对本类其他方法开启访问:
private
- 静态与否:这个方法我想让它属于每一个实例,而不是属于类:
- 不写
static
- 不写
- 返回值:我想让这个方法最终返回给我一个
int类型的结果:private int
- 方法名:方法名就叫
addNum:private int addNum()
- 形参列表:我想调用这个方法的时候,肯定要接收两个int类型的变量:
private int addNum(int numA, int numB)
- 方法体:编写方法的逻辑代码,一个方法只做一件事:
private int addNum(int numA, int numB){...}
源码: /javase-oop/
- src:
c.y.start.MethodDesignTest
/**
* @author yap
*/
public class MethodDesignTest {
private int addNum(int numA, int numB) {
return numA + numB;
}
@Test
public void addNum() {
int result = addNum(1, 5);
System.out.println(result);
}
}
5.2 方法参数的传递
概念: 方法的参数在传递的时候有一个特性:
- 当传递的参数是基本数据类型的时候,代表传递副本。
- 当传递的参数是引用数据类型的时候,代表传递引用。
源码: /javase-oop/
- src:
c.y.start.MethodParamsTest
/**
* @author yap
*/
public class MethodParamsTest {
private void methodA(int num) {
num = 1000;
}
private void methodB(int[] arr) {
arr[0] = 1000;
}
@Test
public void passCopy() {
int num = 10;
methodA(num);
System.out.println(num);
}
@Test
public void passReference() {
int[] arr = {1, 2};
methodB(arr);
System.out.println(Arrays.toString(arr));
}
}
5.3 方法的递归
概念:
- 递归的核心就一句话:自己调用自己。
- 递归虽然简单,但是比较耗时耗力,而且所有使用递归可以解决的问题,都能用循环来解决,所以不到万不得已的时候,不要使用递归。
- 递归必须要有出口!否则就是一个死递归,死递归导致堆栈溢出:StackOverflowError。
源码: /javase-oop/
- src:
c.y.start.DeadRecursionTest
/**
* @author yap
*/
public class DeadRecursionTest {
private void methodA() {
System.out.println("methodA...");
methodA();
}
@Test
public void deadRecursion() {
methodA();
}
}