10-面向对象
一、 类与对象
Java是一门纯面向对象的语言(Object Oriented Program,简称OOP),在面向对象的世界里,一切皆为对象。
一个程序就是一个世界,有很多事物(对象[属性,行为])
1.1 什么是面向对象?什么是面向过程?
面向对象:面向对象是解决问题的一种思想,主要依靠对象之间的交互完成一件事情。
面向过程:面向过程是以过程为中心的编程思想,其原理就是将问题分解成一个一个详细的步骤,然后通过函数实现每一个步骤,并依次调用。
1.2 什么是类?
类是用来对一个实体(对象)来进行描述的,主要描述该实体(对象)具体有哪些属性,哪些功能,描述完成之后计算机就可以识别了。
这里需要注意:类和对象的区别和联系
a) 类是抽象的,概念的,代表一类事物。比如人类、猫咪等等,即它是数据类型
b) 对象是具体的,实际的,代表一个具体事物。即它是实例
c) 类是对象的模版,对象是类的一个个体,对应一个实例
public class Object01{ public static void main(String[] args){ /** * 张老太养了两只猫猫: * 一只名字叫小白,今年三岁,白色。还有一只叫小花,今年100岁,花色。 * 请编写一个程序,当用户输入小猫的名字时,则显示该猫的名字,年龄,颜色。 * 如果用户输入的小猫名字错误,则显示"张老太没有这只猫" */ //单独变量来解决 => 不利于数据的管理 //第一只猫的信息 String cat1Name = "小白"; int cat1Age = 3; String cat1Color = "白色"; //第二只猫的信息 String cat2Name = "小花"; int cat2Age = 100; String cat2Color = "花色"; //数组 => 数据类型体现不出来;只能通过下标获取信息,容易造成变量名字和内容的对象关系不明确;不能体现猫的行为 //第一只猫的信息 String[] cat1 = {"小白","3","白色"}; //第二只猫的信息 String[] cat2 = {"小花","100","花色"}; //使用OOP面向对象解决 //实例化一只猫【创建一只猫对象】 //1. new Cat() 创建一只猫 //2. Cat cat3 = new Cat(); 把创建的猫赋给 cat3 Cat cat3 = new Cat(); cat3.name = "小白"; cat3.age = 3; cat3.color = "白色"; //创建第二只猫 Cat cat4 = new Cat(); cat4.name = "小花"; cat4.age = 100; cat4.color = "花色"; //访问对象的属性 System.out.println("第一只猫的信息:" + cat3.name + "\t" + cat3.age + "\t" + cat3.color); System.out.println("第二只猫的信息:" + cat4.name + "\t" + cat4.age + "\t" + cat4.color); } } //使用面向对象的方式来解决养猫问题 //定义一个猫类 Cat -> 自定义的数据类型 class Cat{ //属性 String name; //名字 int age; //年龄 String color; //颜色 }
二、属性(成员变量)
2.1 基本介绍
从概念或叫法上看:成员变量 = 属性 = field(字段)。即成员变量是用来表示属性的。
属性是类的一个组成部分,一般是基本数据类型,也可以是引用类型(对象,数组)。
2.2 属性的注意事项
a) 属性的定义语法同变量,示例:访问修饰符 属性类型 属性名;
b) 属性的定义类型可以为任意类型,包含基本类型或引用类型
c) 属性如果不赋值,有默认值,规则和数组一致
扩展:访问修饰符可以控制属性的访问范围。总共有四种访问修饰符 public , protected , default(默认) , private
public:表示公共访问级别,可以被任何类访问。
protected:表示受保护访问级别,可以被类本身、子类和同一包中的类访问。
default(缺省):表示默认访问级别,即如果没有使用访问修饰符,默认是此级别,可以被同一包中的类访问。
private:表示私有访问级别,只能在类内部访问。
2.3 如何创建对象
a) 先声明再创建
Cat cat;
cat = new Cat();
b) 直接创建
Cat cat = new Cat();
2.4 如何访问对象
基本语法:对象名.属性名; 例如 cat.name; cat.age;
public class Object03 { public static void main(String[] args){ Person p1 = new Person(); p1.age = 10; p1.name = "小明"; //把p1赋给p2,让p2指向p1 Person p2 = p1; p2.age = 20; System.out.println("p1的年龄:" + p1.age); //p1的年龄:20 System.out.println("p2的年龄:" + p2.age); //p2的年龄:20 } } class Person{ String name; int age; }
2.5 Java 创建对象的流程简单分析
a) 先加载Person类信息(属性和方法信息,只会加载一次)
b) 在堆中分配空间,进行默认初始化(看规则)
c) 把地址赋给p1,p1就指向对象
d) 进行指定初始化,例如 p1.age = 10; p1.name = "小明";
三、成员方法
3.1 基本介绍
在某些情况下,我们需要定义成员方法(简称方法)。比如人类除了有一些属性外(年龄、姓名...),还需要有一些行为,可以说话、跑步,还可以做算术题。这时就要用成员方法才能完成
3.2 成员方法的定义格式:
访问修饰符 返回数据类型 方法名(形参列表){
语句;
return 返回值;
}
a) 参数列表:表示成员方法输入
b) 返回数据类型:表示成员方法输出,void:表示没有返回值
c) 方法主体:表示为了实现某一功能代码块
d) return 语句不是必须的,有的方法不一定有返回值
e) 调用方法前需要先创建对象,再调用方法
public class Method01 { public static void main(String[] args){ //方法使用 //1.方法写好后,不去调用是没有效果的 //2.调用方法前需要先创建对象,再调用方法 Person1 p1 = new Person1(); p1.speak(); //把调用getSum方法返回的值,赋给变量sum int sum = p1.getSum(10,20); System.out.println("sum = " + sum); } } class Person1{ //属性(成员变量) String name; int age; //方法(成员方法) //1.public: 表示方法是公开的,可以被任何类访问 //2.void: 表示方法没有返回值 //3.speak(): speak是方法名,()是形参列表 //4.{}是方法体,可以写我们要执行的代码 public void speak(){ System.out.println("我是马铃薯"); } //getSum成员方法 //int:表示方法执行后,返回一个int值;return res;表示把res的值返回 //(int num1,int num2)形参列表,表示当前有两个形参可以接收用户传入 public int getSum(int num1,int num2){ int res = num1 + num2; return res; } }
3.3 方法的好处:
a) 提高了代码的复用性
b) 将实现的细节封装起来,供其他用户调用就行
例如:遍历一个数组,输出数组的各个元素值
import java.net.SocketTimeoutException; public class Method02 { public static void main(String[] args){ //请遍历一个数组,输出数组的各个元素值 int[][] map = {{0,0,1},{1,1,1},{1,1,3}}; //传统的解决方法就是直接遍历 for(int i = 0; i < map.length; i++){ for(int j = 0; j < map[i].length; j++){ System.out.print(map[i][j] + "\t"); } System.out.println(); } //使用方法遍历数组 MyTools tool = new MyTools(); tool.printArr(map); } } //定义一个类MyTools,把输出的功能写到一个类的方法中,然后调用该方法即可 class MyTools{ //方法,接收一个二维数组 public void printArr(int[][] map){ //对传入的map数组进行遍历输出 for(int i = 0; i < map.length; i++) { for (int j = 0; j < map[i].length; j++) { System.out.print(map[i][j] + "\t"); } System.out.println(); } } }
3.4 方法的注意事项和使用细节
1> 一个方法最多有一个返回值 【当要返回多个结果,可以返回数组】
2> 返回数据类型
a) 返回类型可以为任意类型,包括基本类型或引用类型(数组,对象)
b) 如果方法要求有返回数据类型,方法体中最后的执行语句必须为return值,并且要求返回值类型必须和return的值类型一致或兼容
3> 方法名遵循驼峰命名法,多单词组成时,第一个单词的首字母小写,第二个单词开始,每个单词的首字母大写。例如 tankShotGame
4> 形参列表
a) 一个方法可以有零个参数,也可以有多个参数,中间用逗号隔开
b) 参数类型可以是任意类型,包含基本类型或引用类型
c) 调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型的参数
d) 方法定义时的参数为形式参数又称形参,方法调用时传入的参数称为实际参数,又称实参,实参和形参的类型要一致或兼容即个数、顺序必须一致
5> 方法体,完成功能的具体语句,可以是输入、输出、变量、运算、分支、循环、方法调用,但不允许方法里面再定义方法
6> 方法的调用
a) 同一个类中的方法调用,直接调用就行
b) 跨类中的方法A类调用B类的方法,需要通过对象名调用。
这里需要注意:跨类的方法调用也和方法的访问修饰符相关
public class MethodDetail { public static void main(String[] args){ //跨类中的方法A类调用B类的方法,需要通过对象名调用 A a = new A(); a.a2(); } } class A{ public void a1(){ System.out.println("a1方法被调用"); } public void a2(){ //同一个类中的方法调用,直接调用就行 a1(); System.out.println("a2方法执行"); } }
3.5 成员方法的传参机制
a) 基本数据类型传参
基本数据类型,传递的是值(值拷贝),形参的任何改变不影响实参。
public class MethodParameter01 { public static void main(String[] args){ int a = 10; int b = 20; //创建AA对象 AA2 aa = new AA2(); aa.swap(a,b); System.out.println("a = " + a + "\tb = " + b); //a = 10 b = 20 } } class AA2{ public void swap(int a,int b){ System.out.println("交换前:a = " + a + "\tb = " + b); //交换前:a = 10 b = 20 //完成 a 和 b 的值交换 int temp = a; a = b; b = temp; System.out.println("交换后:a = " + a + "\tb = " + b); //交换后:a = 20 b = 10 } }
b) 引用类型传参
引用数据类型传递的是地址(传递也是值,但是值是地址),可以通过形参影响实参。
public class MethodParameter02 { public static void main(String[] args){ //测试 BB bb = new BB(); int[] arr = {1,2,3}; bb.test100(arr); //遍历数组 System.out.println("====main方法的 arr数组 输出===="); for(int i= 0; i < arr.length; i++){ System.out.print(arr[i] + "\t"); } System.out.println(); } } class BB{ //BB类中编写一个方法,test100 //可以接收一个数组,在方法中修改该数组,看看原来的数组是否发生变化 public void test100(int[] arr){ //修改数组 arr[0] 的值 arr[0] = 200; //遍历数组 System.out.println("====test100的 arr数组 输出===="); for(int i = 0; i < arr.length; i++){ System.out.print(arr[i] + "\t"); } System.out.println(); } }
public class MethodParameter03 { public static void main(String[] args){ //测试 Person2 p = new Person2(); p.name = "马铃薯"; p.age = 25; BB2 b = new BB2(); b.test200(p); System.out.println("main方法的 p.age = " + p.age); //main方法的 p.age = 10000 这是因为对象也是存储在堆里 } } class Person2{ String name; int age; } class BB2{ public void test200(Person2 p){ //修改对象属性 p.age = 10000; } }
思考一个问题:我们前面学习到,String是一种引用类型,那么JAVA中String类传参的话,还是以地址进行传递吗,形参会影响实参吗
public class Test04 { public static void main(String[] args){ Person p1 = new Person(); String a1 = "10"; p1.test01(a1); System.out.println("test01:" + a1); // test02:10 String a2 = new String("10"); p1.test02(a2); System.out.println("test02:" + a2); // test02:10 p1.test03(a2); System.out.println("test03:" + a2); // test02:10 } public static void sleep(){ System.out.println("睡觉"); } } class Person{ public void test01(String a){ a = a + "abc"; System.out.println("a = " + a); } public void test02(String a){ a = new String(a + "abc"); System.out.println("a = " + a); } public void test03(String a){ a = a.concat("abc"); System.out.println("a = " + a); } }
从上面的结果可以看到,String类型传递的形参,不论形参如何改变,都无法影响到实参。
但这跟我们前面学习到的内容,基本类型传参是值传递,引用类型传参是地址传递,似乎有些矛盾。
通过分析String的源码,我们发现:
这个问题真正原因,是因为String类的存储是通过final修饰的char[]数组来存放结果的,也就是说是不可更改的。
所以每次当外部一个String类型的引用传递到方法内部时候,只是把外部String类型变量的引用传递给了方法参数变量。外部String变量和方法参数变量都是实际char[]数组的引用而已,所以当我们在方法内部改变这个参数的引用时候,因为char[]数组不可改变,所以每次新建变量都是新建一个新的String实例。很显然外部String类型变量没有指向新的String实例。所以也就不会获取到新的更改。
也就是说,字符串一旦被创建,就不可更改(如果想更改,只能使用新的对象做替换)
// 字符串一旦被创建,就不可更改(如果想更改,只能使用新的对象做替换) s = "def"; System.out.println(s); // def,字符串一旦被创建,就应该不可更改,这里为什么改变了,这是因为s指向了新的字符串对象"def" // 等价于上面的,s指向了新的字符串对象"def" Student stu = new Student("张三", 20); stu = new Student("李四", 21); System.out.println(stu.getName() + "\t" + stu.getAge()); // 李四 21
扩展:final修饰基本类型变量,和引用类型变量的区别
1、final修饰基本类型变量时,不能对基本类型变量重新赋值,因为基本类型变量不能被改变
2、对于引用类型变量而言,保存的仅仅是一个引用地址,final只是保证这个引用地址不被改变,而引用指向的对象完全可以改变