04-Java面向对象基础

04-Java面向对象基础
Java面向对象基础类、对象、属性与内存分析引言学完变量、控制流、数组和方法之后我们已经能用Java写出不少能跑的程序。但代码一多就会发现问题所有逻辑都堆在main里函数和函数之间靠参数传来传去数据和行为被强行割裂。面向对象Object-OrientedOO就是用来解决这个痛点的。它把数据和操作数据的方法打包成一个整体让代码组织更贴近现实世界的结构。本篇博客面向Java初学者带你从面向过程平滑过渡到面向对象。一、面向过程 vs 面向对象1.1 两种思想的对比面向过程关注的是怎么做把一个大问题拆成若干步骤每一步用一个函数实现按顺序调用即可。面向对象关注的是谁来做先分析问题里有哪些角色对象每个角色有哪些属性和行为再让它们协作完成任务。经典例子把大象装进冰箱。面向过程的写法打开门 → 装大象 → 关上门。面向对象的写法先设计Person、Fridge、Elephant三个类让人去操作冰箱和大象。publicclassMain{publicstaticvoidmain(String[]args){PersonpnewPerson(张三);FridgefnewFridge();ElephantenewElephant();p.pull(f);// 人拉开冰箱p.pull(e);// 人拉大象过来p.push(e);// 人把大象推进去p.push(f);// 人关上冰箱}}1.2 何时用哪种思想面向过程执行效率高、思路直接适合小型脚本、单片机程序等场景。面向对象更适合大型软件模块清晰、易于维护和扩展。Java是纯面向对象语言一切代码都必须写在类里。小贴士面向对象并非高级于面向过程二者是不同维度的思想。Java中依然大量使用方法过程只是这些方法被组织到了类和对象里。二、面向对象的三大特征面向对象有三大核心特征本篇只做简介后续博客会逐一深入。1. 封装Encapsulation把属性和行为封装进对象隐藏内部实现细节只暴露必要的接口。就像汽车你只需要知道踩油门能加速不需要懂发动机内部结构。2. 继承Inheritance子类可以复用父类的属性和方法避免重复造轮子。比如Dog继承Animal自动拥有吃睡等行为。3. 多态Polymorphism同一个方法调用不同对象表现出不同行为。比如同样调用animal.cry()狗叫汪汪猫叫喵喵。理解了三大特征的整体轮廓后我们就能从最基础的类与对象开始动手了。三、类与对象的概念3.1 生活化比喻类Class是设计图对象Object是按图生产的产品。类对象楼房的设计图纸一栋真正盖起来的楼房汽车的车型设计一辆停在楼下的真车学生类的描述张三这个具体的学生一张图纸可以盖出很多栋楼但每栋楼都有自己的门牌号、住户、装修。同样一个类可以创建多个对象每个对象的属性值各自独立。3.2 概念定义类是一类具有共同特征的事物的抽象描述。对象是这类事物的一个具体的个体、实体、实例。以学生类为例属性姓名、学号、性别、专业、成绩……行为注册、学习、考试……类只描述有哪些属性和行为具体值要由对象来确定。四、声明类的格式4.1 基本语法【修饰符】class类名{// 成员变量属性// 成员方法行为}修饰符可以是public、final、abstract等初学阶段先用public。类名使用大驼峰命名法每个单词首字母大写如HelloWorld、Student。一份.java文件中只能有一个public类且类名要和文件名一致。4.2 第一个自定义类packagecom.atguigu.oop;publicclassStudent{publicStringname;// 姓名publicintage;// 年龄publicStringtel;// 电话publicintscore;// 成绩}注意package语句声明了类所属的包即文件夹用于避免类名冲突并组织代码。包名习惯用公司域名倒置例如com.atguigu.xxx。五、创建对象new关键字5.1 语法格式类名 对象名new类名();// 无参类名 对象名new类名(实参列表);// 有参需配合构造方法new关键字做了三件事在堆中开辟内存、初始化属性默认值、返回对象的首地址。对象名本质是一个引用变量存的是对象在堆中的地址。5.2 命名对象与匿名对象publicclassCreateObjectDemo{publicstaticvoidmain(String[]args){// 命名对象用变量接收引用可重复使用StudentsnewStudent();System.out.println(s);// com.atguigu.oop.Student34c45dca// 匿名对象不赋给任何变量用一次即丢System.out.println(newStudent());}}5.3 匿名对象的使用场景匿名对象没有名字创建后无法再次访问常见于以下场景只调用一次方法new Scanner(System.in).nextInt();作为实参传递printInfo(new Student());作为方法的返回值return new MyDate();小贴士如果同一个对象要被使用多次必须用命名对象。匿名对象每new一次就开辟一块新内存滥用会浪费资源。六、类的成员之一属性成员变量6.1 声明格式【修饰符】class类名{【修饰符】 数据类型 成员变量名;}成员变量也叫属性、字段。它写在类里、方法外。6.2 访问方式对象名.属性6.3 完整示例publicclassStudentTest{publicstaticvoidmain(String[]args){Students1newStudent();// 赋值前输出默认值System.out.println(s1.name);// nullSystem.out.println(s1.age);// 0// 赋值s1.name张三;s1.age23;// 赋值后System.out.println(s1.name);// 张三System.out.println(s1.age);// 23// 新建一个对象属性值独立Students2newStudent();System.out.println(s2.name);// null}}小贴士成员变量有默认值整数0、浮点0.0、布尔false、字符’\u0000’、引用null无需手动初始化即可使用。这是和局部变量最大的不同。七、成员变量分类静态 vs 非静态7.1 何时加static判断原则非常简单如果属性的值是每个对象独立的 → 不加static → 实例变量。如果属性的值是所有对象共享的 → 加static → 静态变量类变量。7.2 代码对比publicclassAccount{publicstaticdoublerate;// 利率所有人共享 → 静态变量publicdoublebalance;// 余额每人独立 → 实例变量publicStringid;// 账号每人独立 → 实例变量}classAccountTest{publicstaticvoidmain(String[]args){Accounta1newAccount();a1.id111;a1.balance5000;Accounta2newAccount();a2.id222;a2.balance10000;// 静态变量推荐用类名.访问Account.rate0.035;System.out.println(a1.ida1.balancea1.rate);System.out.println(a2.ida2.balancea2.rate);}}注意a1.rate 0.035语法上也合法但不推荐。因为静态变量属于类而非某个对象用类名.静态变量才能体现共享语义。7.3 详细对比表对比维度静态成员变量类变量非静态成员变量实例变量是否加static是否共享性所有对象共享同一份每个对象各自独立一份内存存储位置JDK6及以前方法区JDK7堆Class对象的反射字段JDK8堆堆随对象一起初始化时机类加载时初始化new对象时初始化访问方式推荐类名.变量也可对象名.变量只能对象名.变量生命周期类存在即存在类卸载才销毁对象创建时存在对象被GC回收时销毁默认值有默认值有默认值JDK版本变化小贴士JDK6及之前静态变量存储在方法区永久代。JDK7静态变量的引用从方法区移到了堆中。JDK8永久代被废除方法区改名为元空间Metaspace使用本地内存静态变量随Class对象存放在堆中。八、类的成员之二方法8.1 声明格式【修饰符】class类名{【修饰符】static返回值类型 方法名(【形参列表】){// 静态方法方法体语句;}【修饰符】 返回值类型 方法名(【形参列表】){// 实例方法方法体语句;}}8.2 静态方法 vs 实例方法判断原则方法体中需要访问本类的实例变量→ 必须是实例方法。方法体中不访问本类的实例变量→ 可以定义为静态方法调用更方便无需new对象。工具类的方法大多是静态的比如Math.random()、Arrays.sort()调用时直接类名.方法名即可。8.3 跨类调用类名.静态方法();// 静态方法对象名.实例方法();// 实例方法8.4 为什么要定义方法为了让代码可复用。下面的矩形类把求面积“求周长”获取信息分别封装成方法每次需要时直接调用即可。publicclassRectangle{publicdoublelength;publicdoublewidth;// 求面积实例方法直接使用实例变量publicdoublearea(){returnlength*width;}// 求周长publicdoubleperimeter(){return2*(lengthwidth);}// 获取信息本类内部方法之间可以互相调用publicStringgetInfo(){return长length宽width面积area()周长perimeter();}}classRectangleTest{publicstaticvoidmain(String[]args){Rectangler1newRectangle();r1.length5;r1.width3;System.out.println(r1的r1.getInfo());}}8.5 同类中成员的互相访问在同一类中静态方法只能访问本类的静态变量和静态方法不能直接访问实例变量和实例方法。实例方法既能访问静态成员也能访问实例成员。小贴士因为静态方法不依赖对象存在而实例变量必须依附于对象。静态方法被调用时可能还没有任何对象所以它无法直接使用实例变量。九、static关键字详解static可以修饰成员变量、成员方法、代码块、内部类。它修饰的成员属于类级别不属于某个对象。核心特点随着类的加载而加载生命周期最长。优先于对象存在可以不new对象就直接通过类名访问。所有对象共享同一份静态成员。可以直接用类名调用类名.静态变量、类名.静态方法()。publicclassChinese{publicstaticStringcountryName;// 国名所有中国人共享publicStringname;// 姓名每人独立publicintage;// 年龄每人独立}classChineseTest{publicstaticvoidmain(String[]args){Chinese.countryName中国;// 类名访问静态变量Chinesec1newChinese();c1.name李涛;c1.age25;Chinesec2newChinese();c2.name王芳;c2.age22;System.out.println(c1.namec1.countryName);// 李涛中国System.out.println(c2.namec2.countryName);// 王芳中国}}十、this与super简介本篇源笔记未深入讲解this和super但它们在面向对象中至关重要这里做简要介绍后续博客会展开。this代表当前对象的引用。当实例方法的局部变量与成员变量同名时用this.变量名区分构造方法间互相调用也用this(...)。super代表父类对象的引用用于在子类中访问父类的成员常用于方法重写后调用父类原版方法。publicclassStudent{privateStringname;publicvoidsetName(Stringname){this.namename;// this.name指成员变量name指参数}}注意this和super都是对象级别的引用不能在静态方法中使用。十一、对象的内存分析理解对象在内存中如何存放是真正掌握面向对象的关键。Java虚拟机内存主要涉及三块区域11.1 三大内存区域栈Stack存放方法的栈帧方法内的局部变量、对象的引用地址都存在这里。方法执行完即出栈。堆Heap存放new出来的对象本体对象的成员变量实例变量也在这里。由GC自动回收。方法区/元空间Method Area / Metaspace存放类的信息Class对象、静态变量、常量、方法的字节码等。JDK8后改名为元空间使用本地内存。11.2 new对象的过程执行Student s new Student();时发生了什么类加载JVM检测到Student类把它的Class信息加载到方法区。堆中开辟空间在堆中为对象分配内存。默认初始化实例变量被赋默认值int→0引用→null等。显式初始化执行成员变量的显式赋值语句。构造方法执行执行构造方法体如果有的话。返回地址把对象在堆中的首地址赋给栈中的引用变量s。11.3 内存图示意以如下代码为例Students1newStudent();s1.name张三;s1.age23;Students2newStudent();s2.name李四;栈 堆 方法区/元空间 ----------------- --------------------- ------------------ | main栈帧 | | Student对象 a1 | | Student类信息 | | s1 a1 | ----- | name 张三 | | 字节码 | | s2 a2 | | age 23 | | 静态变量 | ----------------- | tel null | ------------------ | score 0 | --------------------- | Student对象 a2 | | name 李四 | | age 0 | | ... | ---------------------可以看到每个对象在堆中独立占用一块内存但它们都指向方法区里同一份类信息。多个对象通过各自的引用变量区分。11.4 引用类型成员变量的内存分析当成员变量是引用类型时对象内部存的是另一个对象的地址形成对象套对象的结构。publicclassEmployee{publicStringname;publicMyDatebirthday;// 引用类型成员变量}classEmployeeTest{publicstaticvoidmain(String[]args){EmployeeenewEmployee();e.name张三;e.birthdaynewMyDate();// 堆中再开一块MyDate对象e.birthday.year2000;// 通过引用逐级访问e.birthday.month5;e.birthday.day1;}}小贴士e.birthday.year的访问路径是栈中e → 堆中Employee对象 → 堆中MyDate对象 → year。任何一环为null都会抛NullPointerException。十二、成员变量与局部变量的区别这是初学者最容易混淆的点下面用表格梳理。对比维度成员变量局部变量声明位置类中、方法外方法中、方法的形参、代码块中作用域整个类从声明处到所在{}结束默认值有默认值可直接使用无默认值必须先赋值再使用存储位置实例变量在堆中静态变量在堆中JDK8栈中方法栈帧内生命周期实例变量随对象生灭静态变量随类生灭随方法调用而生方法结束即亡修饰符可用public/private/static/final等只能用final不能用访问修饰符和staticpublicclassDemo{publicintmemberVar10;// 成员变量有默认值publicvoidtest(){intlocalVar;// 局部变量无默认值// System.out.println(localVar); // 编译报错可能尚未初始化localVar20;System.out.println(localVar);}}小贴士如果局部变量和成员变量同名方法内优先使用局部变量就近原则。若要访问被遮蔽的成员变量需用this.变量名。十三、基本类型 vs 引用类型作为成员变量Java的数据类型分两大类8种基本类型byte、short、int、long、float、double、char、boolean。引用类型数组、类、接口等。成员变量无论是基本类型还是引用类型都存在堆中。但存储内容不同基本类型成员变量直接存值。引用类型成员变量存的是另一个对象的地址引用。13.1 引用类型成员变量示例publicclassMyDate{publicintyear;publicintmonth;publicintday;publicStringgetDateInfo(){returnyear年month月day日;}}publicclassEmployee{publicStringname;// String是引用类型publicMyDatebirthday;// 自定义类也是引用类型publicStringgetEmployeeInfo(){return姓名name生日birthday.getDateInfo();}}classEmployeeTest{publicstaticvoidmain(String[]args){EmployeeenewEmployee();e.name张三;e.birthdaynewMyDate();e.birthday.year2000;e.birthday.month5;e.birthday.day1;System.out.println(e.getEmployeeInfo());// 姓名张三生日2000年5月1日}}13.2 链式访问引用类型成员变量可以一直点下去e.birthday.year// 逐级解引用每一级.都是一次从引用到对象的跳转。这就是面向对象程序中对象协作的体现一个对象内部包含另一个对象多个对象组合成复杂结构。注意自己写的类也是数据类型可以像int、String一样作为成员变量、方法参数、返回值。这是面向对象编程的核心能力。十四、匿名对象再深入14.1 什么是匿名对象newStudent();// 这就是一个匿名对象没有变量名接收它有对象实体的内存但没有引用变量指向它只能使用一次。14.2 典型使用场景场景1只调用一次方法newScanner(System.in).nextInt();调用完毕Scanner对象立即成为垃圾等待GC回收。场景2作为方法实参printInfo(newStudent());publicstaticvoidprintInfo(Students){System.out.println(s.name);}场景3作为方法返回值publicstaticMyDatecreateToday(){returnnewMyDate();// 返回匿名对象}14.3 注意事项newStudent().name张三;// 赋值成功但对象立刻丢弃无意义newStudent().study();// 调用方法OKnewStudent().study();// 又new了一个新对象与上一行无关小贴士匿名对象一旦不使用就成垃圾。需要多次访问同一对象时必须用命名对象接收引用。十五、小结本篇博客带大家从思想到代码、从语法到内存建立了面向对象的基础认知。回顾要点思想转变从怎么做面向过程转向谁来做面向对象。三大特征封装、继承、多态是面向对象的根基。类与对象类是模板对象是实例用new创建对象。属性成员变量有默认值区分静态变量共享和实例变量独立。方法静态方法属于类实例方法属于对象同类中静态不能直接访问实例成员。static类级别成员生命周期长所有对象共享。内存分析栈存引用、堆存对象、方法区存类信息理解new的全过程。变量对比成员变量vs局部变量、基本类型vs引用类型成员变量差别巨大。匿名对象用于一次性场景避免冗余引用。掌握了这些基础下一步就可以进入封装private getter/setter、构造方法、this关键字的深入学习了。面向对象的大门已经打开欢迎继续探索。写完这篇博客建议动手敲一遍所有示例代码并自己画一画内存图。代码会忘但内存图一旦画明白面向对象就再也不会忘。