前言
之前我们所写的代码几乎都是在main方法里面,我们也操作了一些对象如String,Math等。但是将大量的代码写入main里面不是面向对象编程的做法,接下来我们将脱离面向过程,走向面向对象,慢慢的培养自己设计对象的能力,慢慢的感受对象是如何让我们的生活过的丰富多彩。本篇会重点讲解面向对象与面向过程的区别以及面向对象三大特征之封装等知识。
文章目录
🚀面向对象的初步认知
Java是一门纯面向对象的语言(Object Oriented Program,继承OOP),在面向对象的世界里,一切皆为对象。面向对象是解决问题的一种思想,主要依靠对象之间的交互完成一件事情。用面向对象的思想来涉及程序,更符合人们对事物的认知,对于大型程序的设计、扩展以及维护都非常友好。
🚀面向对象语言的有如下特征
- 面向对象一种符合人们思考问题的思想
- 面向对象可以将复杂的业务逻辑简单化,增强代码的复用性
- 面向对象语言具有封装,继承,多态等特性。
常见的面向对象的编程语言有Java,C++,C#等
🚀面向对象与面向过程的区别?
首先面向对象与面向过程都是编程思想,思想是一种非常玄幻的东西,我们不妨把面向对象与面向过程理解成编程所需遵循的原则,就比如我们写作文,标题最好写在第一行的中间,每个段落首行都要空2个格子再起笔。同理我们编程程序也如同写作文,要遵循语言的规则去编写程序,不是随随便便就写代码。我们之前所学的C语言是一门面向过程的语言,它更加强调功能行为,以函数为最小单位,考虑怎么做。之前写过的通讯录就是要书写大量的函数如最基本对通讯录的增删改查,这四种行为就是四个函数。而我们现在学的是Java一门纯粹的面向对象的语言,它是将功能封装进对象,强调具备功能的对象,以类为最小单位,考虑谁来做。
🚀面向过程与面向对象的生活实例
🚀 传统洗衣服
传统洗衣服就是一个面向过程,它更加注重于洗衣服的过程,少了一个环节所洗的衣服都可能没有洗干净,而且还要考虑不同的衣服清洗的方式,凉衣服的时间长短以及拧干衣服的方式都有所不同,我们以这种方式来写代码将来维护代码和扩展代码将有所麻烦。
🚀现代洗衣服
这种洗衣服的过程非常简单,只需一个人将衣服加入到洗衣机中,再将洗衣粉倒入洗衣机中,最后启动洗衣机即可。整个过程就是人,洗衣粉,洗衣机,衣服之间互相交完成的,我们不需要考虑洗衣机内部是怎么样将衣服洗干净的。这就是面向对象,解决一个问题通过对象之间的交互来解决该问题。现在我们可以思考一下为什么面向对象更加符合我们对事物的认知,我们不妨联系现实生活,假如有一天张三在学校生病了,要出去看医生,那么他首先要找班主任请假,他再去医院看病。在这个过程中就是张三,班主任,医院这三者互相交互来完成张三成功看病的事情。我们大脑思考的是一件事情的解决过程,但是一件事的解决通常是自己无法单独解决的,所以我们需要面向其它对象,互相交互以此来解决问题。
🚀Java类及类的成员
类:类是用来对一个实体(对象)来进行描述的,类是一种复杂类型,是自定义类型。
地球上生活着许许多多的生物,不管是海洋霸王鲸鱼还是弱小的蚂蚁,都是由对基本的细胞一个一个构成。在我们Java中也有人类,也有牛,羊,马等物种,这些物种都是通过某一对应模板即类制作的,比如我们要在Java中造牛,就得先准备一个牛的模板(类),然后通过这个模板就能制造成千上万头牛。所以类是Java中的最基本单位,正是有了不同功能的类才构成了Java世界,那么类又是由什么构成的?答案是属性与行为。属性对应类中的成员变量,行为对应成员方法。
当我么要在Java中创造一个人,首先得将人身上的属性与行为抽象出来,供我们来制作人类的模板。所谓的属性就是身高,体重,年龄,姓名等,所谓的行为就是吃饭,能喝水,能打游戏等。我们可以简单粗暴的理解名词即属性,动作即行为。
通过一张简历来理解属性与行为
知道了类的基本知识,接下来就来学会如何创建类
🚀类的创建格式
在java中定义类时需要用到class关键字,具体语法如下
// 创建类 class ClassName{ field; // 字段(属性) 或者 成员变量 method; // 行为 或者 成员方法 }
class为定义类的关键字,ClassName为类的名字,{}中为类的主体。
比如创建一个汽车类
public class Car { String name; double price; public void start() { System.out.println(name+"启动了"); } public void run() { System.out.println("售价为:"+price+"的"+name+"跑的快!"); } }
注意事项
类名注意采用大驼峰定义
目前成员前写法统一为public
目前此处写的方法不带 static 关键字.
创建Car后我们要学会使用类,在上文我们已经提到,类只是一个模板,我们可以通过类去创建无数对象,在Java中将用类类型创建对象的过程,称为类的实例化,采用new关键字,配合类名来实例化对象。当我们创建对象后,对象中的属性已经有了默认值,但这并不是我们想要的,我们需要对类的属性进行赋值,让该对象真正的活起来。
🚀对象的创建
想必大家都听过在java中万物皆对象这句话,虽然是万物皆对象,我们直接操作的不是对象而是引用,我们是通过操作引用来操作对象。引用与对象好比车钥匙与车。当你要开车的时候,需要拿出车钥匙插入钥匙孔启动车,当你下车后需要停车,也需要车钥匙使用钥匙上的关闭键使车锁住。车钥匙作为引用驱动对象(车)的启动与停止,并且即使没有车的存在,车钥匙依旧是个独立的个体,也就是说可以有一个对象的引用,但是不一定需要有一个对象与之关联。
Car carKey;
此时只创建了Car的引用,并没有创建对象,若这个时候去使用它,会返回一个异常,这说明我们需要创建一个对象与之关联。
异常
Car carKey=new Car();
由上图可知,当我们创建了一个引用,java就希望有一个对象与之关联,通常使用new关键字来创建对象那个,new的意思就是给一个新的对象,如果不想找女朋友直接new一个对象。
Car的测试类
java中的类有点类似于C语言中的结构体,我们可以通过引用 加符号. 来访问属性为其赋值,可以通过引用.来操作成员方法。
public class CarText { public static void main(String[] args) { //对象的创建 Car c1=new Car();//实例化对象 c1.name="奔驰";//通过c1.来为name赋值 c1.price=23.23; System.out.println(c1.name); System.out.println(c1.price); c1.start();//通过c1.来操作成员方法 c1.run(); Car c2=new Car(); c2.name="宝马"; c2.price=23.33; System.out.println(c2.name); System.out.println(c2.price); c2.start(); c2.run(); System.out.println(c1); System.out.println(c2); } }
Car与CarText内存解析图
类只是一个模板,我们可以通过它创建多个对象,对象生存在堆区,每个对象当没有被引用时就会被JVM自动销毁。
注意事项
- 1.new 关键字用于创建一个对象的实例
- 2.使用 . 来访问对象中的属性和方法.
- 3.同一个类可以创建对个实例.
🚀类和对象的说明
- 1.类是对一类事物的描述,是抽象的,概念上的定义。(具体事物的抽象)
- 2.对象是实际存在的该类事物的每个个体,,因此有称为实例。(类的具体)
- 3.一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量
- 4.类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图。
🚀面向对象小结
面向对象编程的第一步是从具体问题中抽象出类,第二步则是把抽象出的类设计出来,最后
this引用
为什要有this引用?
先看一个其日期类的例子
public class Date { public int year; public int month; public int day; public void setDay(int y, int m, int d){ year = y; month = m; day = d; } public void printDate(){ System.out.println(year + "-" + month + "-" + day); } }
测试类
public class DateText { public static void main(String[] args) { // 构造三个日期类型的对象 d1 d2 d3 Date d1 = new Date(); Date d2 = new Date(); Date d3 = new Date(); // 对d1,d2,d3的日期设置 d1.setDay(2020,9,15); d2.setDay(2020,9,16); d3.setDay(2020,9,17); // 打印日期中的内容 d1.printDate(); d2.printDate(); d3.printDate(); } }
运行结果
上述代码是没有问题的,但是当对setDay()中你的形参以及局部变量稍微修改改时就会出问题了
修改后的Date
public class Date { public int year; public int month; public int day; public void setDay(int year, int month, int day){ year = year; month = month; day = day; } public void printDate(){ System.out.println(year + "-" + month + "-" + day); } }
修改后的运行结果
修改前的代码,year是成员变量作用范围是在Date的大括号中,所以可以成功赋值。修改后形参名不小心与成员变量名相同,那函数体中到底是谁给谁赋值?成员变量给成员变量?参数给参数?参数给成员变量?成员变量参数?估计自己都搞不清楚了。而year赋值给year实际是赋值给自己,不是赋值给成员变量,所以打印出来的结果是0,因为成员变量的默认值都为0。上述代码中创建了3个对象,三个对象都调用了setDay()和printDate(),但是这两个函数中没有任何有关对象的说明,setDate和 printDate函数如何知道打印的是那个对象的数据呢???这个问题就由this来解决。
🚀什么是this引用?
this引用指向当前对象(成员方法运行时调用该成员方法的对象),在成员方法中所有成员变量的操作,都是通过该 引用去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
对上述错误的Date类使用this
public class Date { public int year; public int month; public int day; public void setDay(int year, int month, int day){ this.year = year; this.month = month; this.day = day; } public void printDate(){ System.out.println(year + "-" + month + "-" + day); } }
通过调试对this有深刻的认知
通过观察我们可以知道引用d1里面存放着该对象的地址,当我们进入d1.setDay();时通过调试可知this的存放的也是引用d1的地址。这样通过this我们就可以知道是那个对象调用该方法。
注意:this引用的是调用成员方法的对象。
🚀this引用的特性
- 1.this的类型:对应类类型引用,即哪个对象调用就是哪个对象的引用类型
- 2.this只能在"成员方法"中使用
- 3.在"成员方法"中,this只能引用当前对象,不能再引用其他对象
对象的构造及初始化
我们之前创建的局部变量通常都需要进行初始化,否则会发生编译错误。
public class Demo { public static void main(String[] args) { int a; System.out.println(a); } }
要让上述代码通过编译,非常简单,只需在正式使用a之前,给a设置一个初始值即可。如果是对象:
public class Demo { public static void main(String[] args) { Date d = new Date(); d.printDate(); d.setDay(2021,6,9); d.printDate(); } }
需要调用之前写的SetDay()方法才可以将具体的日期设置到对象中。通过上述例子发现两个问题:
1.每次对象创建好后调用SetDate方法设置具体日期,比较麻烦,那对象该如何初始化?
2.局部变量必须要初始化才能使用,为什局部变量必须要初始化才能使用,为什么字段声明之后没有给值依然可以使用?
🚀构造方法
在java中,有一种特殊的方法被称为构造方法,也被称为构造器等。在java中,通过提供构造器,来为对象进行初始化。构造器只能在对象创建时期调用一次,保证对象初始化的进行。构造器比较特殊,它没有参数类型和返回值。它的名称需和类名一致,并且构造器可以多个,
代码示例
🚀有参构造方法
public class Apple { public String name;; public String color; public String type; public Apple(String name, String color, String type) { this.name = name; this.color = color; this.type = type; } public Apple(String name) { this.name = name; } public Apple(String name, String color) { this.name = name; this.color = color; } @Override//重写toString()为了待会打印方便 public String toString() { return "Apple{" + "name='" + name + '\'' + ", color='" + color + '\'' + ", type='" + type + '\'' + '}'; } }
测试类
public class AppleText { public static void main(String[] args) { Apple a=new Apple("青苹果","青色","青春苹果"); System.out.println(a); } }
运行结果
注意:构造方法的作用就是对对象中的成员进行初始化,并不负责给对象开辟空间。
如果我们不给构造器,系统会默认提供无参构造
🚀无参构造
代码示例
public class Apple { public String name;; public String color; public String type; @Override public String toString() { return "Apple{" + "name='" + name + '\'' + ", color='" + color + '\'' + ", type='" + type + '\'' + '}'; } }
测试类
ublic class AppleText { public static void main(String[] args) { Apple a=new Apple();//Apple()实际就是在调用默认给的无参构造方法 a.name="青苹果"; a.color="青色"; a.type="青春苹果"; System.out.println(a); } }
如果我们已经提供了有构造器,此时系统将不会提供无参构造,我们就不能使用无参构造去初始化对象。如果需要使用无参构造器则需自己加。
此时就需添加一个无参构造器即可。
代码示例
public class Apple { public String name;; public String color; public String type; public Apple(String name, String color, String type) {//有参构造 this.name = name; this.color = color; this.type = type; } public Apple() {//无参构造 } @Override public String toString() { return "Apple{" + "name='" + name + '\'' + ", color='" + color + '\'' + ", type='" + type + '\'' + '}'; } }
🚀this在构造方法中的使用
public class Apple { public String name;; public String color; public String type; public Apple(String name, String color, String type) { this.name = name; this.color = color; this.type = type; } public Apple() { this("青苹果","青色","青春苹果");//调用有参构造初始化对象 } @Override public String toString() { return "Apple{" + "name='" + name + '\'' + ", color='" + color + '\'' + ", type='" + type + '\'' + '}'; } }
this();的方式可以调用构造方法大,但是需要放在无参构造方法中的第一行,否则会报错。
🚀this调用构造器导致闭环
是不能你中调我,我中调你,否则直接挂了。
🚀神奇的代码
public class Apple { int i = 0; public Apple eatApple() { i++; return this; } public static void main(String[] args) { Apple a = new Apple(); System.out.println(a.eatApple().eatApple().eatApple().i);//i=3 } }
我们可以发现eatApple();可调用多次,并且我们还可以继续调用,这是件神奇的事情,这是为什么呢?答案在this身上,我在eatApple()中加了return this,也就是说那个对象调用eatApple方法都能返回对象的自身。
🚀this总结
就地初始化
public class Apple { public String name="苹果"; public String color="青色"; public String type="青苹果"; @Override public String toString() { return "Apple{" + "name='" + name + '\'' + ", color='" + color + '\'' + ", type='" + type + '\'' + '}'; } public static void main(String[] args) { Apple a=new Apple(); System.out.println(a); } }
默认初始化
public class Apple { public String name; public String color; public int age; public Apple(String name, String color, int age) { this.name = name; this.color = color; this.age = age; } public Apple() { } @Override public String toString() { return "Apple{" + "name='" + name + '\'' + ", color='" + color + '\'' + ", age=" + age + '}'; } } //测试类 public class AppleText { public static void main(String[] args) { Apple a=new Apple(); System.out.println(a);//运行结果:Apple{name='null', color='null', age=0} Apple a1=new Apple("青苹果","青色",2); System.out.println(a1);//运行结果:Apple{name='青苹果', color='青色', age=2} } }
类中的成员变量系统会自动给予默认值,所以不会报错。
属性赋值的总结
截止目前为止,我们已经使用多种方式对对象进行初始化。先在就来总结这几种方式赋值的先后顺序。
赋值的方式
- 1.默认初始化
- 2.就地初始化
- 3.构造器初始化
- 4.通过对象.属性或者对象.方法的方式赋值
赋值的先后顺序:默认初始化>就地初始化>构造器初始化>通过对象.属性或者对象.方法的方式赋值
🚀封装
定义:封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互
面向对象程序三大特性:封装、继承、多态。而类和对象阶段,主要研究的就是封装特性。何为封装呢?简单来说 就是套壳屏蔽细节。比如:我们使用手机我们不会把手机拆开去看内部的电板,不会去关心手机壳里面电路板上那些硬件的工作,对于用户而言只需要关心,手机是怎么样充电,关机,开机,知道这些基本操作即可。而手机壳就屏蔽了手机内部的工作细节,同时它也暴露出一些充电接口,耳机接口,摄像头等。供用户能够正常的操作。
🚀访问权限修饰符
🚀什么是权限修饰符?
- 权限修饰符:是用来控制一个成员的够被访问的范围
- 可以修饰成员变量,方法,构造器,内部类,不同的权限修饰符修饰的成员能够被访问的范围将受到限制。
权限修饰符的分类和具体范围
权限修饰符:四种,范围由小到大(private->缺省->protect->public)
public:从字面意思来理解为公开的,可以简单的理解为一个人的外貌,所有人都能看见。
default:对于自己家族(同一个包中)不是什么秘密,但是相对于其它包则是隐私。
private:自己独有小秘密,其他人都不知道。
🚀为什么需要封装?
首先来看一段代码
public class Student { public int age; public static void main(String[] args) { Student s = new Student(); s.age = -12; System.out.println(s.age); } }
我们创建了学生对象,但是给他的年龄赋值为-12,我们都知道一个人年龄是不可能为负数的。这就造成了一个隐藏的Bug.
🚀封装的实现步骤
一般对成员变量使用private(私有)关键字修饰进行隐藏,private修饰后该成员变量就只能在当前类中访问。
提供public修饰的公开的getter、setter方法暴露其取值和赋值。
setter与geter就是暴露在外的接口,供我们对象中的数据进行操作。
而我们借助封装可以解决此问题
public class Student { private int age; public int getAge() { return age; } public void setAge(int age) { if(age>=1&&age<=100){ this.age = age; }else{ System.out.println("检查年龄的合法性"); } } } public class StudentText { public static void main(String[] args) { Student s = new Student(); s.setAge(12); System.out.println(s.getAge()); } }
我们在setAge()里面过滤了年龄。
🚀封装的好处
- 加强了程序代码的安全性。
- 适当的封装可以提升开发效率,同时可以让程序更容易理解与维护。
🚀封装扩展之包
包的概念:在面向对象体系中,提出了一个软件包的概念,即:为了更好的管理类,把多个类收集在一起成为一组,称为软件 包。有点类似于目录。
比如:我自己为了更加方便的找到与管理图片,会将自己常用的图片按网站,按图片类型进行划分以便自己来能更快的找到。
在Java中也引入了包,包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式,比如:一 个包中的类不想被其他包中的类使用。包还有一个重要的作用:在同一个工程中允许存在相同名称的类,只要处在 不同的包中即可。
导入包中的类
Java 中已经提供了很多现成的类供我们使用. 例如Date类:可以使用 java.util.Date 导入 java.util 这个包中的 Date 类.
比如手动导入 java.util.Date
public class Text { public static void main(String[] args) { java.util.Date date = new java.util.Date();//手动带入包 System.out.println(date.getTime()); } }
这种方式用的少,毕竟 我们用的工具是idea可以自动导入我们所需的包。
使用 import语句导入包.
import java.util.Date;//idea导入的 public class Text { public static void main(String[] args) { Date date = new Date(); System.out.println(date.getTime()); } }
如果需要使用 java.util 中的其他类, 可以使用 import java.util.*
import java.util.Arrays; import java.util.Date; public class Text { public static void main(String[] args) { Date date = new Date(); System.out.println(date.getTime()); int [] arr=new int[]{1,2,3,4}; Arrays.sort(arr); } }
当我们导入一个包下的多个类时,为了避免多次导包可直接 import java.包名.*便可以导入包下所有的类。
当使用的类来自不同包下,必有一个包中类需要在类前面加上完整的类名,否则编译器无法识别
import java.util.*; import java.sql.*; public class Text { public static void main(String[] args) { Date date = new Date(); Date date2=new Date(); } }
运行结果
解决办法,在sql下的Date前加完整的类名即可
import java.util.*; public class Text { public static void main(String[] args) { Date date = new Date(); java.sql.Date date2=new java.sql.Date(2); } }
可以使用import static导入包中静态的方法和字段。
import static java.lang.Math.*; public class Text { public static void main(String[] args) { double x = 30; double y = 40; // 静态导入的方式写起来更方便一些. // double result = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); double result = sqrt(pow(x, 2) + pow(y, 2)); System.out.println(result); } }
这只是为了简化代码而简化代码,不建议对于静态类这样操作,我们还是通过类名.方法名的方式去使用静态类中方法更好,这样写出来的代码可读性高。
🚀成员变量和局部变量的区别
代码示例
public class Student { public int age; public String name; public void method() { int age = 90; } } public class StudentText { public static void main(String[] args) { Student s = new Student(); double score=23.5; } }
内存解析图
最后的话
各位看官如果觉得文章写得不错,点赞评论关注走一波!谢谢啦!