📚博客主页:Zhui_Yi_
🔍:上期回顾:二叉树
❤️感谢大家点赞👍🏻收藏⭐评论✍🏻,您的三连就是我持续更新的动力❤️
🎇追当今朝天骄,忆顾往昔豪杰。
文章目录
- 前言
- 一、面向对象思想
- 1.引入
- 2.面向过程和面向对象概述
- ①面向过程
- ②面向对象
- I概述
- II特点
- III开发
- IV设计
- V特征
- 代码示例
- 面向过程
- 面向对象
- 二、类与对象
- 1.类的定义
- 2.对象的创建与使用
- 3.对象的内存图
- ①一个对象的内存图
- ②2个对象的内存图
- ③三个对象的内存图
- 4.局部变量与成员变量的不同
- 5.形式参数的问题
- 6.匿名对象
- 三、封装
- 1.定义
- 2.引入
- 3.private关键字
- 4.应用
- 快捷键
- 应用
- 5.this关键字
- 注意事项
- 6.toString方法
- 四、构造方法
- 1.注意事项
- 2.格式
- 3.举例
- 五、访问控制权限
- 介绍
- 举例示例
- 缺省
- protected
- private
- 总结
- 总结
前言
又见面喽,本篇文章会给大家带来以下内容:面向对象思想、类与对象及其使用对象的内存图、成员变量和局部变量的区别、匿名对象、封装(private)、this关键字、构造方法。
一、面向对象思想
1.引入
我们想一下我们在做数组的时候,当有多个数组都需要遍历时,我们可以将遍历的代码封装到方法中,需要遍历时,就调用相应的方法即可,提高代码的复用性。但是如果我们要再增添一些功能的时候,比如获取最值,数值逆序等,同样需要将这些功能封装到相应的方法中。但是这样继续封装会发现方法越来越多,于是就想能不能将这些方法继续进行封装呢?
当然有了,通过前面的讲解我们知道类是可以存放方法的,所以,我们就考虑使用类封装来这多个方法,将来再做数组的操作时,不用去找具体的方法,先找到这个类,然后使用这个类中的方法。
2.面向过程和面向对象概述
①面向过程
我们在前面写程序的时候,我们需要一步一步去实现,而具体的每一步都需要我们去实现和操作,这其实是面向过程开发,即:
就是面向着具体的每一个步骤和过程,把每一个步骤和过程完成,然后由这些功能方法相互调用,完成需求。
②面向对象
I概述
那么当我们随着需求的更改,功能的增多,发现需要面对每一个步骤很麻烦了,那我们能不能把这些步骤和功能在进行封装,封装时根据不同的功能,进行不同的封装,功能类似的封装在一起。这就是面向对象的思想:
其概述为
面向对象是基于面向过程的编程思想
II特点
是一种更符合我们思想习惯的思想
可以将复杂的事情简单化
将我们从执行者变成了指挥者
III开发
就是不断的创建对象,使用对象,指挥对象做事情
IV设计
其实就是在管理和维护对象之间的关系。
V特征
- 封装(encapsulation)
- 继承(inheritance)
- 多态(polymorphism)
关于各个特征的概念我们会分开详细的讲,在这里不过多阐述。
代码示例
这里以把大象装进冰箱为例:
把大象装进冰箱分三步:打卡冰箱门,把大象装进去,关闭冰箱门。
面向过程
面向过程就是分布用函数实现,代码如下:
public class FaceToProcess { public static void main(String[] args) { open(); in(); close(); } public static void open(){ System.out.println("打开冰箱门"); } public static void close(){ System.out.println("关闭冰箱门"); } public static void in(){ System.out.println("把大象装进冰箱"); } }
面向对象
面向对象则是把构成问题的事务按照一定规则划分为多个独立的对象。那么我们思考一下,对于本题,应该划分多少个对象?
两个。
- 大象:被装进冰箱
- 冰箱:打卡冰箱门,关闭冰箱门
那么此时我们就要定义类了:
一个大象类,大象要干什么?被装进冰箱里,要定义一个实现方法:
public class Elephant { public void in(){ System.out.println("把大象装进冰箱"); } }
然后是冰箱类,要打卡冰箱门,关闭冰箱门,需要定义两个实现方法:
public class Refrigerator { public void open(){ System.out.println("打开冰箱门"); } public void close(){ System.out.println("关上冰箱门"); } }
那么如何实现上述类呢?
需要定义一个测试类Demo,代码实现如下:
public class Demo { public static void main(String[] args) { Refrigerator rg = new Refrigerator(); rg.open(); Elephant elephant = new Elephant(); elephant.in(); rg.close(); } }
结果一样。
当然,我们现在看,面向对象不如面向过程简单,but诸君请随我往下看:
二、类与对象
1.类的定义
我们考虑一下我们上边举得一个例子:我们可以从大象类中定义一个方法,让大象被放进冰箱里面,这是从一个抽象到具体。那么反过来就是:
类是对象的抽象,用于描述一组对象的共同特征和行为。
类中可以定义成员变量和成员方法,其中:
- 成员变量用于描述对象的特征,成员变量也被称作对象的属性
- 成员方法用于描述对象的行为,可简称为方法。
- 也就是如下定义:
- class 类名{ 成员变量; 成员方法; }
在这里举出一个例子:
学生类:
成员变量:姓名,年龄,地址等等
成员方法:学习,吃饭,睡觉
在这里代码实现以下:
首先我们需要顶一个学生类,学生类中包含以上信息,即:
但是我们要记住,我们之前声明的变量都是在方法里面,我们称为局部变量,而我们现在声明的都在类的里面,方法的外面,我们称为成员变量(关于两者的区别,我们会在下文给出)。
成员变量定义如下:
String name; int age; String address;
我们可以看出此时也有些区别,成员变量是不用跟局部变量一样赋初值的。
接下我们定义成员方法:
public void study(){ System.out.println(name+"在学习Java"); } public void eat(){ System.out.println(name+"学习饿了,去吃饭"); } public void sleep(){ System.out.println(name+"学习困了,去睡觉"); }
整体类如下:
public class Student { String name; int age; String address; public void study(){ System.out.println(name+"在学习Java"); } public void eat(){ System.out.println(name+"学习饿了,去吃饭"); } public void sleep(){ System.out.println(name+"学习困了,去睡觉"); } }
接下来定义一个实现类:
在实现类中我们可以先看一下各个变量的初始值:
Student s=new Student(); //访问默认值 System.out.println(s.name+"\t"+s.age+"\t"+s.address);
然后我们再赋初值:
s.name="追忆"; s.age=18; s.address="洛阳";
然后我们再打印一下,看是否被修改:
然后我们再调用方法:
s.study(); s.eat(); s.sleep();
看一下效果:
测试类完整程序:
public class Demo { public static void main(String[] args) { Student s=new Student(); //访问默认值 System.out.println(s.name+"\t"+s.age+"\t"+s.address); s.name="追忆"; s.age=18; s.address="洛阳"; System.out.println(s.name+"\t"+s.age+"\t"+s.address); s.study(); s.eat(); s.sleep(); } }
2.对象的创建与使用
在上面使用Student类的时候,我们是如何创建对象的?
类名 对象名称 = new 类名();
会创建了,那么该如何使用对象呢,我们通过.,也就是如下
对象名称.属性名
对象名称.方法名
3.对象的内存图
①一个对象的内存图
如图,左边是代码,右边是内存图,我将讲述,代码如何在内存图中实现:
- 首先,JVM启动会先去找main方法,main方法加载进栈
- 然后执行第一条语句,Phone p,为p赋初值,然后new,存地址,在堆里创建空间,此时Phone中有三个成员变量,所以开辟三个空间,并为三个变量初始化默认值
- 然后再通过p来访问三个变量,对三个变量赋值
- 而对于,三个成员方法,为什么没在堆里面开空间?:方法会在堆空间里开辟空间,存地址,通过地址的访问,来实现加载进栈。
- 而在call方法内,有一个形参,此时就会在栈空间里面,也就是call所在的那一段空间为其开辟空间。
- 在执行成员方法时,执行完一个,释放掉一个,直到在实现到主方法最后,对main方法释放空间。
- 然后p断开与堆空间的联系,堆空间就成为了垃圾,在空闲时释放掉。
②2个对象的内存图
如图,理论同上,不解释。
③三个对象的内存图
4.局部变量与成员变量的不同
在类中的位置不同
成员变量 类中方法外
局部变量 方法内或者方法声明上
在内存中的位置不同
成员变量 堆内存
局部变量 栈内存
生命周期不同
成员变量 随着对象的存在而存在,随着对象的消失而消失
局部变量 随着方法的调用而存在,随着方法的调用完毕而消失
初始化值不同
成员变量 有默认的初始化值
局部变量 没有默认的初始化值,必须先定义,赋值,才能使用。
5.形式参数的问题
在这里我们先给出概念,然后再给出代码,实现:
基本类型:形式参数的改变不影响实际参数。实参应该有确定的值,且其值的数据类型级别不能高于对应的形参数据类型的级别,参数的传递相当于赋值语句,即实参的值赋给形参。
引用类型:形式参数的改变直接影响实际参数。参数的传递相当于赋值语句,实参的地址赋给形参,使得形参和实参指向同一个堆内存空间。
如果你看到一个方法需要的参数是-一个类名,就应该知道这里实际需要的实参是-一个具体的对象。
public class ArgsText { public static void main(String[] args) { int a = 10; int b = 20; Demo d = new Demo(); //参数为基本数据类型 int result=d.sum(a,b); System.out.println(result); } } //形式参数时基本类型 class Demo{ public int sum(int a,int b){ return a+b; } }
此时我们将主函数定义的a,b为实参,传递给Demo类中的方法sum做形参。即,赋值调用。
此时我们再举一个例子,形式参数做引用类型:
此时我们先定义一个Student类:
class Student{ public void show(){ System.out.println("我们爱Java"); } }
此时Student类型就相当于int
然后我们再让它作为引用类型:
class StudnetDemo{ public void method(Student s){ s.show(); } }
我们在实现一下上述代码:
StudnetDemo sd = new StudnetDemo(); //必须先创建一个Student对象作为实参 Student s=new Student(); //参数为引用(类)类型 sd.method(s);
完整函数如下:
public class ArgsText { public static void main(String[] args) { int a = 10; int b = 20; Demo d = new Demo(); //参数为基本数据类型 int result=d.sum(a,b); System.out.println(result); System.out.println("=========="); StudnetDemo sd = new StudnetDemo(); //必须先创建一个Student对象作为实参 Student s=new Student(); //参数为引用(类)类型 sd.method(s); } } //这个Student类型就相当于int class Student{ public void show(){ System.out.println("我们爱Java"); } } //形式参数是引用(类)类型 class StudnetDemo{ public void method(Student s){ s.show(); } } //形式参数时基本类型 class Demo{ public int sum(int a,int b){ return a+b; } }
6.匿名对象
这里直接给出定义:
匿名对象:就是没有名字的对象。是对象的一种简化表示形式
匿名对象的两种使用情况
- A:调用方法,仅仅只调用一次的时候。 注意:调用多次的时候,不适合。 那么,这种匿名调用有什么好处吗? 有,匿名对象调用完毕就是垃圾。
B:匿名对象可以作为实际参安传递给形式参数,相当于赋值语句
下面给出示例:
我们先定义一个Student类:
public class Student { public void show(){ System.out.println("我们爱Java!"); } }
然后我们定义一个测试类,调用两次:
public class Demo { public static void main(String[] args) { Student s = new Student(); s.show(); s.show(); } }
但如果我们只调用一次呢?
可以用下述方法:
new Student().show();
调用如下:
好处是调用完之后,就是垃圾,回收的比较快!
而对于匿名对象可以作为实际参安传递给形式参数,我们给出下述例子:
我们先定义一个StudentDemo类,让Student作为类型
public class StudentDemo { public void method(Student s) { s.show(); } }
我们再主函数中实现以下:
StudentDemo sd = new StudentDemo(); sd.method(new Student());
我们再来个更特别的:
new StudentDemo().method(new Student());
结果如上。
三、封装
铺垫了辣么久,终于该讲重点了!!!开森
1.定义
这里直接给出,我相信不用解释了:
封装是指一种将类的实现细节包装、隐藏起来的方法。
它有两层含义,第一层含义是指把对象的属性和行为看成是一个密不可分的整体,将这两者“封装”在一起(即封装在对象中);另外一层含义指“信息隐藏”,将不想让外界知道的信息隐藏起来。
我们为什么需要封装:
就比如我我定义一个学生类的时候,定义了一个成员变量age,如果我不将其封装起来,此时任何人都可以看见,都可以改变。这样可以吗?
当然不可以。
我们在使用这个案例的过程中,发现了一个问题:
通过对象去给成员变量赋值,可以赋值一些非法的数据。
这是不合理的。
应该是这个样子的:在赋值之前,先对数据进行判断。
判断到底在哪里做比较合适呢?
StudentDemo类是-一个测试类,测试类一般只创建对象,调用方法。
所以,这个判断应该定义在Student类中。
而我们在成员变量的位置可不可以进行数据判断呢?
是不可以的,因为做数据校验必须要依靠一 些逻辑语句。
逻辑语句是应该定义在方法中的,所以,我们最终决定在Student类中提供一个方法来对数据进行校验。
2.引入
但是呢,我们能在定义变量时就对其判断嘛?
不能。
所以我们只能给出一个方法来校验。
此时我们先给出一个Student类,类中用名字,年龄两个变量。
我们要给出一个方法来判断即:
public class Student { String name; int age; public void setAge(int a){ if(a<0){ System.out.println("年龄输入错误"); }else{ age=a; } } }
此时我们写一个主函数来测试,
Student s1 = new Student(); s1.name="追忆"; s1.age=-18;
上述代码存在一个问题,年龄可能是-18嘛?
不可能,所以我们要调用方法来修改年龄,故我们在Java中引入了一个关键字private,所以刚才的Student类就可以这么写:
`private int age;`
我们此时就可以看到,直接改变age的值就会报错!
此时,我们就要调用setAge方法来赋值。如下:
s1.setAge(-18);
此时就会出现:
而由于此时我们无法直接读取age的值:
所以我们此时要用一个方法来实现:如下:
public int getAge(){ return age; }
此时我们调用一下:
s1.setAge(18); System.out.println(s1.getAge());
3.private关键字
(1)私有的意义:是一个访问权限修饰符,可以修饰成员变量和成员方法
(2)特点:被private修 饰后的成员只能在本类中被访问
在这里我们给出例子:
public class Private { private int num=10; public void show(){ System.out.println("num="+num); } private void method(){ System.out.println("私有的method方法"); } public void function(){ method(); } }
上述代码,我们给出了私有的num变量和method的方法,我们试一下能否在内部里面调用:
当然是可以的,没有报错。
那能否在外部使用呢?
显而易见,不能调用成员变量,那方法呢?
也是不行的!!!!
4.应用
private的应用:
以后再写一个类的时候:把所有的成员变量给private了。
提供对应的getXxx()/setXxx()方法
而对于set和get方法我们有以下:
Setter和getter方法:
a:为实现数据封装,成员变量常用private封装
b:public修饰的Setter和getter方法来设置和获取这些private成员变量
c:命名规则: set或get后跟变量名,变量名首字母大写
在这里给出一个Student类作为例子:
private String name; private int age;
先给出两个成员变量。然后再写出set和get方法:
public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; }
如上,我们可以手打,也可以使用快捷键:
快捷键
Alt+Insert
选择
记住可以按Ctrl,来进行多选。
那么有的人可能找不到快捷键该怎么办?
此时我们可以右键
选择Generate,点一下跟上面操作就一样了!!!
应用
此时我们在测试类中试一下:
public class StudentDemo { public static void main(String[] args) { Student s1 = new Student(); s1.setAge(18); s1.setName("追忆"); System.out.println(s1.getName()+"\t"+s1.getAge()); } }
我们试一下:
5.this关键字
我们看这个代码
public void setName(String name) { this.name = name; }
为什么会出现this?去掉行不行?
这里是无用的,自己给自己赋值,是没有任何意义的。
所以我们用了this关键字:
this关键字(掌握)
(1)代表当前类的对象引用,简单的记,它就代表当前类的一一个对象。
指代的是 正在调用当前方法的对象,是对象引用,
在栈空间,出现的位置一般是在方法体内部
记住:哪个对象调用方法,该方法内部的this就代表哪个对象
(2)this的应用场景:
A:在定义类时如果方法参数或方法中定义的局部变量与类中的变量同名,
可以通过this做前缀代表成员变量以示和q部变量进行区别,解决了局部变量隐藏成员变量的问题
B:其实this还有其他的应用,以后再讲解。
this的内存图解如图。
注意事项
(1)只能在构造方法中使用this调用其他的构造方法,不能在成员方法中
通过this调用构造方法。
(2)在构造方法中,使用this调用其他构造方法的语句必须位于第一行,
且只能出现一次。
(3)不能在一个类的两个构造方法中使用this互相调用
6.toString方法
toString方法:
(1):方法toString( )是object类中已定义的方法
(2):toString用于返回对象的字符串表示
(3):直接写对象的引用变量名,Java编译器会自动调用toString方法,获取对象的字符串表现形式
(4):建议所有子类都覆盖此方法
我们在使用封装的时候会不会有一个疑问?
我们如果要打印所有变量的内容,是不是要一个一个调用get方法?有没有一种简单的方法呢?
有,就是用toString方法。
这里我们继续用上面的那个Student类做例子:
如果我们此时不重写toString方法,直接打印s1结果为:
类+地址。
如果我们写上toString方法:
@Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; }
此时我们再打印s1
那么有没有快捷键,当然有啦。
快捷键同上:
点击toString就行了。
四、构造方法
1.注意事项
(1)构造方法的名称必须与类名一致。
(2)构造方法名称前不能有任何返回值类型的声明。
(3)不能在构造方法中使用return返回一个值,但可以单独写return语句作为方法的结束。
2.格式
方法名与类名相同
没有返回值类型,连void都没有
没有具体的返回值
3.举例
我们还是在Student类中给出例子:
我们可以给出无参构造和带参构造:
public Student(){ System.out.println("无参构造方法!"); } public Student(String name){ System.out.println("带String参数的构造方法!"); this.name = name; } public Student(int age){ System.out.println("带int参数的构造方法!"); this.age = age; } public Student(String name, int age){ System.out.println("带多个参数的构造方法!"); this.name = name; this.age = age; }
这其实也是构造方法的重写。
那么我们该如何调用方法呢?
直接调用吗?
我们来演示一下:
Student s1 = new Student("追忆"); System.out.println(s1); System.out.println("=========="); Student s2 = new Student(18); System.out.println(s2); System.out.println("=========="); Student s3=new Student("Z",19); System.out.println(s3);
结果如下:
五、访问控制权限
访问权限有以下四种:
private
缺省(default)
protected
public
其访问级别如下:
介绍
(1)private:private属于私有访问权限,用于修饰类的属性和方法,也可以修饰内部类。类的成员一旦使用了private关键字修饰,则该成员只能在本类中进行访问。
(2)default:default属于默认访问权限,如果一个类中的属性或方法没有任何的访问权限声明,则该属性或方法就是默认的访问权限,默认的访问权限可以被本包中的其他类访问,但是不能被其他包的类访问。
(3)protected:protected属于受保护的访问权限。如果一个类中的成员使用了protected访问权限,则只能被本包及不同包的子类访问。
(4)public:public属于公共访问权限。如果一个类中的成员使用了public访问权限,则该成员可以在所有类中被访问,不管是否在同一包中。
举例示例
缺省
package a; public class A { double weight; void f2(){ } double f1(double a,double b){ this.weight=23.5; f2(); return a+b; } }
我们此时在a包中声明了一个类A,在类A中我们定义的成员方法和变量,都是缺省,我们此时试一下在相同和不同的包能否调用?
相同包:
package a; public class B { public static void main(String[] args) { A a = new A(); a.weight=23.5; System.out.println(a.weight); System.out.println(a.f1(3,5)); } }
运行结果为:
结果为可以调用
在不同包中:
package b; import a.A; public class B { public static void main(String[] args) { A a = new A(); a.weight=23.5; System.out.println(a.weight); System.out.println(a.f1(3,5)); } }
我们此时在b包中调用a包中类,能否调用?
不能。
protected
相同包:
package a; public class A { protected double weight; protected void f2(){ } protected double f1(double a,double b){ this.weight=23.5; f2(); return a+b; } }
没报错,可以
在不同包
报错,不可以。
对于在子类中,由于我们还未学习继承,在这里先不讲。
private
若此时,改变为private
package a; public class A { private double weight; private void f2(){ } private double f1(double a,double b){ this.weight=23.5; f2(); return a+b; } }
在相同包中:
报错,不可。
不同包也不可。
总结
总结
由于内容是在过多,我将其分为上中下来讲解,希望大家见谅,也希望大家能三连,谢谢大家,我们下期再见。
还没有评论,来说两句吧...