面向对象(上)
- 一、面向对象的概念
- 1.1 何谓“面向对象”的编程思想
- 1.2 面向过程(POP)与面向对象(OOP)
- 1.3 面向对象的思想概述
- 二、类和对象
- 2.1 面向对象的思想概述
- 三、对象的创建和使用
- 3.1 设计类以及类和对象的使用
- 3.2 类和对象
- 四、类的成员之一:属性
- 4.1 语法格式
- 4.2 变量的分类:成员变量与局部变量
- 五、类的成员之二:方法(method)
- 5.1 什么是方法(method、函数)
- 5.2 方法的声明格式
- 5.3 方法的分类
- 5.4 方法的调用
- 六、再谈方法
- 6.1 方法的重载
- 6.2 可变形参的方法
- 6.3 方法参数的值传递机制
- 6.4 递归方法
一、面向对象的概念
1.1 何谓“面向对象”的编程思想
首先解释一下“思想”。先问你个问题:你想做个怎样的人?可能你会回答:我想做个好人,孝敬父母,尊重长辈,关爱亲朋…你看,这就是思想。这是你做人的思想,或者说,是你做人的原则。做人有做人的原则,编程也有编程的原则。这些编程的原则呢,就是编程思想。
1.2 面向过程(POP)与面向对象(OOP)
- 二者都是一种思想,面向对象是相对于面向过程而言的。面向过程,
强调的是功能行为,以函数为最小单位,考虑怎么做
。面向对象,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做
。 - 面向对象更加强调运用人类在日常的思维逻辑中采用的思想方法与原则,如抽象、分类、继承、聚合、多态等。
- 面向对象的三大特征
- 封装(Encapsulation)
- 继承(Inheritance)
- 多态(Polymorphism)
面向对象:Object Oriented Programming
面向过程:Procedure Oriented Programming
1.3 面向对象的思想概述
- 程序员从面向过程的
执行者
转化成了面向对象的指挥者
- 面向对象分析方法分析问题的思路和步骤
- 根据问题需要,选择问题所针对的
现实世界中的实体
。 - 从实体中寻找解决问题相关的属性和功能,这些属性和功能就形成了
概念世界中的类
。 - 把抽象的实体用计算机语言进行描述,
形式计算机世界中类的定义
。即借助某种程序语言,把类构造成计算机能够识别和处理的数据结构。 - 将
类实例化成计算机世界中的对象
。对象是计算机世界中解决问题的最终工具。
- 根据问题需要,选择问题所针对的
二、类和对象
2.1 面向对象的思想概述
类(Class)和对象(Ojbect)
是面向对象的核心概念。- 类是对一类事物的描述,是抽象的、概念上的定义
- 对象是实际存在的该类事物的每个个体,因此也称为实例(instance)。
- “万事万物皆对象”
三、对象的创建和使用
3.1 设计类以及类和对象的使用
/**
* 一、设计类,其实就是设计类的成员
* 属性 = 成员变量 = filed = 域、字段
* 方法 = 成员方法 = 函数 = method
* 创建类的对象 = 类的实例化 = 实例化类
*
* 二、类和对象的使用(面向对象思想的落地实现)
* 1、创建类,设计类的成员
* 2、创建类的对象
* 3、通过 “对象.属性” 或 “对象.方法” 调用对象的结构
*
* 三、如果创建了一个类的多个对象,则每个对象都独立的拥有一套类的属性。(非static的)
* 意味着:我们修改一个对象的属性a,则不影响另外一个对象属性a的值
*
* 四、对象的内存解析
*/
public class PeopleTest {
public static void main(String[] args) {
//2、创建People类的对象
People people = new People();
//3、调用对象的结构:属性、方法
//调用属性:“对象.属性”
people.name = "张三";
people.isMale = true;
System.out.println(people.name);
//调用方法:“对象.方法”
people.eat();
people.sleep();
people.talk("Chinese");
People people1 = new People();
System.out.println(people1.name);
System.out.println(people1.isMale);
//将people保存的对象地址值赋给people1,导致people和people1指向了堆空间中的同一个对象的实体。
People people2 = people;
System.out.println(people2.name);//张三
people2.age = 10;
System.out.println(people.age);//10
}
}
//1、创建类,设计类的成员
class People{
//属性
String name;
int age;
boolean isMale;
//方法
public void eat(){
System.out.println("人可以吃饭");
}
public void sleep(){
System.out.println("人可以睡觉");
}
public void talk(String language){
System.out.println("人可以说话,使用的语言是:" + language);
}
}
3.2 类和对象
说明:如果创建了一个类的多个对象,对于类中定义的属性,每个对象都拥有各自的一套副本,且互不干扰。
//2、创建People类的对象
People people = new People();
people.name = "张三";
people.isMale = true;
System.out.println(people.name);
People people1 = new People();
System.out.println(people1.name);
System.out.println(people1.isMale);
//将people保存的对象地址值赋给people1,导致people和people1指向了堆空间中的同一个对象的实体。
People people2 = people;
System.out.println(people2.name);//张三
people2.age = 10;
System.out.println(people.age);//10
- 类的访问机制
- 在一个类中的访问机制:类中的方法可以直接访问类中的成员变量。(例外:static方法访问非static,编译不通过)
- 在不同类中的访问机制:先创建要访问类的对象,再用对象访问类中定义的成员。
- 对象的产生
Person p1 = new Person();//执行完后的内存状态。其中类定义如下:
class Person {
int age;
void shout() {
System.out.println("oh my god! I am " + age);
}
}
- 对象的使用
class PersonTest {
public static void main(String[] args) {//程序运行的内存布局如下图
Person p1 = new Person();
Person p2 = new Person();
p1.age = -30;
p1.shout();
p2.shout();
}
}
- 对象的声明周期
- 内存解析
堆(Heap)
,此内存区域的唯一目的就是存放对象实例
,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述的是:所有的对象实例以及数组都要在堆上分配。- 通常所说的
栈(Stack)
,是指虚拟机栈。虚拟机栈用于存储局部变量等
。局部变量表存放了编译器可知长度的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,是对象在堆内存的是首地址)。方法执行完,自动释放。 方法区(Method Area)
,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码
等数据。
四、类的成员之一:属性
4.1 语法格式
修饰符 数据类型 属性名 = 初始化值;
-
修饰符:常用的权限修饰符有:private、缺省、protected、public
- 其他修饰符:static、final(暂不考虑)
-
数据类型:任何基本类型(如int、Boolean)或任何引用类型
-
属性名:属于标识符,符合命名规则和规范即可。
4.2 变量的分类:成员变量与局部变量
- 在方法体外,类体内声明的变量称为成员变量。
- 在方法体内部声明的变量称为局部变量。
- 成员变量(属性)和局部变量的区别
成员变量 | 局部变量 | |
---|---|---|
声明的位置 | 直接声明在类中 | 方法形参或内部、代码块内、构造器内等 |
修饰符 | private、public、static、final等 | 不能用权限修饰符修饰,可以用final修饰 |
初始化值 | 有默认初始化值 | 没有默认初始化值,必须显示赋值,方可使用 |
内存加载位置 | 堆空间或静态域内 | 栈空间 |
五、类的成员之二:方法(method)
5.1 什么是方法(method、函数)
- 方法是类或对象行为特征的抽象,用来完成某个功能操作。在某些语言中也称为函数或过程。
- 将功能封装为方法的目的是,可以实现代码重用,简化代码
- Java里的方法不能独立存在,所有的方法必须定义在类里。
- 举例:
public class Person {
private int age;
public int getAge() { //声明方法getAge()
return age;
}
public void setAge(int i) {//声明方法setAge
age = i;//将参数i的值赋给类的成员变量age
}
}
5.2 方法的声明格式
修饰符 返回值类型 方法名(参数类型 形参1, 参数类型 形参2,...) {
方法体程序代码
return 返回值;
}
-
修饰符:public、缺省、private、protected等
-
返回值类型
- 没有返回值:void
- 有返回值,声明出返回值的类型。与方法体中“return 返回值”搭配使用
-
方法名:属于标识符,命名时遵循标识符命名规则和规范,“见名知意”
-
形参列表:可以包含零个,一个或多个参数。多个参数时,中间用“,”隔开
5.3 方法的分类
按照是否有形参及返回值
无返回值 | 有返回值 | |
---|---|---|
无形参 | void 方法名 () { } | 返回值的类型 方法名 () { } |
有形参 | void 方法名 (形参列表) { } | 返回值的类型 方法名 (形参列表) { } |
5.4 方法的调用
- 方法通过方法名被调用,且只有被调用才会执行。
- 方法调用的过程分析
-
注 意:
-
方法被调用一次,就会执行一次
-
没有具体返回值的情况,返回值类型用关键字void表示,那么方法体中可以不必使用return语句。如果使用,仅用来结束方法。
-
定义方法时,方法的结果应该返回给调用者,交由调用者处理。
-
方法中只能调用方法或属性,不可以在方法内部定义方法。
-
-
return关键字的使用
- 使用范围:使用在方法体中
- 作用一:结束方法
- 作用二:针对于有返回值类型的方法,使用“return 数据”方法返回所要的数据。
- 注意点:return 关键字后面不可以声明执行语句。
-
Java生成[a, b]之间的随机整数公示推导
推导公式:(int)(Math.random() * (b-a+1) + a) 取值范围在[a, b]中
推导过程:
证明:
① 因为Math.random()取值范围在[0,1)内
所以 Math.random()*b 取值范围在[0, b)内
② 又因为证明的(int)(Math.random() * (b-a+1) + a) 取值范围在[a, b]中 b为闭区间,
int取的是整数部分
所以(int)(Math.random()*b)取值范围在[0, b-1]区间内
③ 又因为(int)(Math.random()*b + a) 取值范围在[a, b-1+a]范围内
此时将b用b-a+1替换得(int)(Math.random()*(b-a+1) + a) 取值范围在[a, b]范围内
④ 所以最终证明 (int)(Math.random() * (b-a+1) + a) 取值范围在[a, b]中
六、再谈方法
6.1 方法的重载
- 重载的概念
在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可
。 - 重载的特定
与返回值类型无关,只看参数列表,且参数列表必须不同。(参数个数或者参数类型
)。调用时,根据方法参数列表的不同来区别。 - 重载示例
//返回两个整数的和
int add(int x, int y){return x+y;}
//返回三个整数的和
int add(int x, int y, int z){return x+y+z;}
//返回两个小数的和
double add(double x, double y) {return x+y;}
6.2 可变形参的方法
- JavaSE 5.0中提供了Varargs(variable number of arguments)机制,允许直接定义能和多个实参相匹配的形参。从而,可以用一种更简单的方式,来传递个数可变的实参。
//JDK 5.0以前:采用数组形参来定义方法,传入多个同一类型变量
public static void test(int a, String[] books);
//JDK 5.0:采用可变个数形参来定义方法,传入多个同一类型变量
public static void test(int a ,String... books);
下面两者只能存在一个
public static void SyoutNumber(int[] nums) {
for (int i = 0; i < nums.length; i++) {
System.out.println("nums[i] = " + nums[i]);
}
}
public static void SyoutNumber(int... nums) {
for (int i = 0; i < nums.length; i++) {
System.out.println("nums[i] = " + nums[i]);
}
}
- 具体使用
- 可变个数形参的格式:数据类型… 变量名
- 当调用可变个数形参的方法时,传入的参数个数可以是:0个,1个,2个。。。
- 可变个数形参的方法与本类中方法名相同,形参不同的方法之间构成重载。
- 可变个数形参的方法与本类中方法名相同,形参类型也相同的数组之间不构成重载。换句话说,二者不能共存
可变个数形参在方法的形参中,必须声明在末尾
。可变个数形参在方法的形参中,最多只能声明一个可变形参
。
6.3 方法参数的值传递机制
- 方法,必须由其所在类或对象调用才有意义。若方法含有参数:
- 形参:方法声明时的参数
- 实参:方法调用时实际传给形参的参数值
- Java的实参值如何传入方法呢?
Java里方法的参数传递方法只有一种:值传递
。即将实际参数值的副本(复制品)传入方法内,而参数本身不受影响。- 形参是基本数据类型:将实参基本数据类型变量的“
数据值
”传递给形参。 - 形参是引用数据类型:将实参引用数据类型变量的“
地址值
”传递给形参。
- 形参是基本数据类型:将实参基本数据类型变量的“
- 举例代码内存解析
public class TransferTest3 {
public static void main(String[] args) {
TransferTest3 test = new TransferTest3();
test.first();
}
public void first() {
int i = 5;
Value v = new Value();
v.i = 25;
//该val在second调用过后,就会别垃圾回收机制回收
second(v, i);
//此时的v表示的是本方法中的Value对象
System.out.println(v.i);//v.i = 20
}
public void second(Value v, int i) {
i = 0;
v.i = 20;
Value val = new Value();
v = val;
System.out.println(v.i + " " + i);//v.i=15 i=0
}
}
class Value {
int i = 15;
}
- 举例二:
String s1 = “hello”; 存储在字符串 常量池中;s="h1~~"时,需要给其重新在字符串常量池中赋值。常量池中的值不可变。
public class ValueTransferTest {
public static void main(String[] args) {
//成员变量存储在静态域或者堆空间
//局部变量存储在栈中
String s1= "hello";
String s2 = new String("hello2");
ValueTransferTest test = new ValueTransferTest();
test.change(s1);
System.out.println("s1 = " + s1);//hello~~
test.change(s2);
System.out.println("s2 = " + s2);//hello2~~
int num = 10;
test.changeInt(num);
System.out.println(num);//10
}
public void change(String s) {
s = "hi~~";
}
public void changeInt(int s) {
s = 265;
}
}
- 举例三:
public static void main(String[] args) {
int a = 10;
int b = 10;
//method(a, b);
method2(a, b);
System.out.println("main... a = " + a);
System.out.println("main... b = " + b);
}
/**
* @description:
* @author: cjw
* @date: 2023/8/11 13:11
* @param: []
* @return: void
* 方法一:在method方法中进行输出
**/
public static void method(int a, int b) {
a = a * 10;
b = b * 10;
System.out.println("method... a = " + a);
System.out.println("method... b = " + b);
System.exit(0);
}
/**
* @description:
* @author: cjw
* @date: 2023/8/11 13:19
* @param: [a, b]
* @return: void
* 方法二: 重写输出方法
**/
public static void method2(int a, int b) {
PrintStream ps = new PrintStream(System.out) {
@Override
public void println(String s) {
if ("main... a = 10".equals(s)) {
s = "main... a = 100";
} else if ("main... b = 10".equals(s)) {
s = "main... b = 100";
}
super.println(s);
}
};
System.setOut(ps);
}
- 扩展一
/*
* 定义一个int型的数组:int[] arr = {12, 3, 3, 34, 56, 77, 432}
* 让数组的每个位置上的值去除以首位置的元素,得到的结果,作为该位置上的新值。遍历新的数组
*/
public static void main(String[] args) {
int[] arr = {12, 3, 3, 34, 56, 77, 432};
//arrayInversion(arr);
//arrayInversionOne(arr);
arrayInversionTwo(arr);
System.out.println(Arrays.toString(arr));
}
public static void arrayInversion(int[] arr) {
int flag = arr[0];
for (int i = arr.length - 1; i >= 0; i--) {
arr[i] /= flag;
// System.out.println("arr["+i+"] = " + arr[i]);
}
}
public static void arrayInversionTwo(int[] arr) {
int flag = arr[0];
for (int i = 0; i < arr.length; i++) {
arr[i] /= flag;
}
}
public static void arrayInversionOne(int[] arr) {
for (int i = arr.length - 1; i >= 0; i--) {
arr[i] /= arr[0];
}
}
- 扩展二
public static void testD() {
int[] a= new int[5];
System.out.println(a);
char[] b = new char[3];
System.out.println(b);
}
public static void testE() {
int[] a= new int[]{0,1,2};
System.out.println(a);
char[] b = new char[]{'a','b','c'};
System.out.println(b);
}
6.4 递归方法
递归方法:一个方法体内调用它自身。
- 方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。
- 递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环。
- 示例:
/*
* @description:给定一个数据n,返回1~n之间所有数据的和
**/
public static void main(String[] args) {
System.out.println("请开始输入数据================》");
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
int n = scanner.nextInt();
if (n == 0) {
System.exit(0);
}
System.out.println("自然数和为:" + getSum(n));
}
}
public static int getSum(int n) {
if (n == 1) {
return 1;
}
return n + getSum(n-1);
}