我最早接触面向对象是在某家IT培训机构的基础班上。当时老师放了一张PPT上面写着“面向对象三大特性封装、继承、多态”下面坐着的同学一半在记笔记一半在发呆。我也是发呆的那一半——封装是啥继承谁多态又是个什么状态后来真正靠一段段代码把这三个概念磨出来才意识到面向对象并不是什么玄学它就是我们观察世界、组织代码的一种方式。这篇笔记我会把面向对象基础阶段的重点全部拆开讲类与对象怎么理解、三大特性怎么用、构造方法背后的执行逻辑、还有那些笔试面试常见的坑。看完之后你至少能做到看到一个需求知道该建什么类、该把哪些逻辑放进类里、类与类之间该是什么关系。1. 类与对象先认清“图纸”和“房子”的关系1.1 类的本质是模板对象的本质是实例在Java里类就是一张“图纸”对象就是用图纸造出来的“房子”。图纸不占地方房子占地方图纸可以反复用造出来的房子却各自独立。写代码的时候也是这个逻辑类只是定义不占内存只有通过new关键字创建出来的对象才真正占内存并参与运行。public class Car { String brand; String color; double price; public void start() { System.out.println(brand 启动了); } }这段代码定义了一个Car类里面有品牌、颜色、价格三个属性还有一个启动方法。但注意现在世界上一辆车都还不存在它只是“图纸”。要让一辆车真正出现必须写Car myCar new Car(); myCar.brand 某品牌; myCar.color 白色; myCar.start();很多人刚学的时候会混淆“类”和“对象”这两个词考试也喜欢在这里挖坑。记住一句话类是抽象的对象是具体的。Car是类myCar是对象Student是类某个正在看这篇笔记的人是对象。凡是能说“某一种”的是类能说“某一个”的是对象。1.2 属性、方法、以及内存里的那点事类里面有两个核心组成属性描述状态方法描述行为。属性是名词比如学生的姓名、年龄方法是动词比如学习、考试、吃饭。很多初学者设计类的时候容易把方法写歪常见的问题是把不该属于这个类的方法塞进来比如给Student类加一个printReport()方法。这个方法如果是打印全校成绩单就不属于学生自己如果打印的是这个学生自己的成绩那放进来还说得过去。还有一个特别重要的内存概念我当年是在培训机构听老师画图才彻底弄懂的。对象用new创建后存放在堆内存中而方法里的局部变量、对象的引用变量存在栈内存中。写Car myCar new Car()这行代码时new Car()在堆里开了一块空间myCar这个引用变量存的是这块空间的内存地址。所以Java里说“对象是引用类型”就是这个意思。为什么要懂这个因为后面学数组、集合、继承、多态时只要遇到修改对象内容的问题最终都要回到“引用指向的是哪个对象”这个根上来。比如两个引用指向同一个对象不管通过谁修改对象都变。这是项目里最常见的隐形Bug来源之一。2. 封装把数据藏起来把方法露出去2.1 为什么非要封装封装这个词听起来抽象实际讲的是类的内部细节不让外面直接碰只留几个“出入口”让外面用。为什么因为如果不加控制数据就很容易被搞乱。public class Student { public int age; }如果这样写外面随便一行代码就能把age设成10000岁。数据一旦坏掉整个程序的运算结果都跟着错。封装的核心就是属性用private私有化对外提供受控的访问方法。用生活里的例子说就像银行不会把金库大门敞开而是给你一个ATM机入口有钱款进出都在它的规则里处理。我在实际项目中见过太多不封装的代码。一开始图省事所有字段直接public结果后面需求一变某处修改了字段格式全工程几十处引用点全部报错。封装上去了这种问题基本不会扩散。所以现在接手新模块我第一件事就是看实体类的字段是不是private如果不是先出重构方案。2.2 访问修饰符与JavaBean的约定Java提供了四个访问级别。这是面试高频点必须背清楚访问修饰符同类同包子类任意位置private可以不行不行不行默认不写可以可以不行不行protected可以可以可以不行public可以可以可以可以这里最容易记混的是protected它能被同包访问也能被子类访问但不能被无关类访问。默认权限却只能同包访问子类跨包继承也不能访问。笔试里特别喜欢把默认权限和protected放在一起考。完整封装的标准写法是JavaBean规范企业项目里的实体类基本都是这个套路。要点有三个属性全部private、提供无参构造方法、每个属性提供getter和setter。写出来长这样public class User { private String username; private String password; public User() { } public String getUsername() { return username; } public void setUsername(String username) { this.username username; } public String getPassword() { return password; } public void setPassword(String password) { this.password password; } }有同学觉得getter/setter写了一堆像是脱裤子放屁不如直接public。这种想法能理解但在团队协作中会吃大亏。比如某个业务要求密码字段在控制层读出时统一脱敏如果外面全部直接访问字段这个需求就只能在几十处改如果只有getter改一个方法就完事了。封装节省的从来不是写代码的时间而是改代码的时间。3. 继承代码复用的双刃剑3.1 继承能做什么继承不能做什么继承解决的问题是“复用”和“抽象”。子类用extends继承父类就能免费获得父类的非私有属性和方法。比如有个Animal父类子类Cat、Dog都能直接使用父类的eat()方法不用重写一遍。public class Animal { public void eat() { System.out.println(动物吃东西); } } public class Cat extends Animal { public void catchMouse() { System.out.println(抓老鼠); } }Cat类里没有任何eat方法但new Cat().eat()完全可以运行这就是继承带来的便利。核心要记住的有几点第一Java是单继承一个类只能有一个直接父类没有C那种多继承。但可以“多层继承”比如Cat extends AnimalPersianCat extends Cat一层套一层。第二继承描述的是“is-a”关系。猫是一种动物所以Cat extends Animal成立。如果是“有一个”关系比如汽车有一个发动机就不能用继承而是用组合——车类里放一个Engine属性。笔试常考这两个关系的区分。第三父类的private成员不能被子类直接访问protected和public可以。所以设计父类时要考虑清楚哪些是留给子类用的哪些是自己私有的。3.2 super关键字和构造方法执行顺序super有三个用法访问父类成员变量、调用父类方法、调用父类构造方法。使用时有几条硬性规则考试几乎是年年必考子类构造方法的第一行如果没有显式写super(...)系统会默认调用父类的无参构造即自动加一句super()。如果父类没有无参构造子类构造方法必须显式写明super(参数)。super调用父类构造方法必须放在子类构造方法的第一行。public class Animal { public Animal() { System.out.println(Animal构造); } } public class Cat extends Animal { public Cat() { super(); // 就算不写系统也会默认加 System.out.println(Cat构造); } }创建Cat对象时输出顺序是“Animal构造”再“Cat构造”。这个“先父后子”的顺序是内存初始化的需要先有父类的部分再在这个基础上叠加子类的部分。如果父类状态没初始化好子类方法运行时就可能拿到一个残缺的对象。我在面试中考察新人时经常会问“创建子类对象时父类无参构造执行了吗”能答对的人往往基础都比较扎实。3.3 重写规则的四个“必须”继承还有一个很关键的行为叫方法重写override。子类觉得父类的方法不满足需求就重新写一遍同名方法。写重写方法时四个规则必须同时满足方法名必须相同参数列表必须相同。返回值类型必须相同或者父类返回值类型的子类型。访问权限不能比父类更严格父类是public子类就不能是private。不能抛出比父类更宽泛的异常。写重写方法时最好在方法上加上Override注解。好处是如果签名写错编译器会直接报错提示而不是等到运行期才暴露问题。这一点是培训机构老师反复强调的我也是养成这个习惯后改代码时少犯了很多错。4. 多态同一个调用不同的表现多态是面向对象里最有意思的部分也是很多初学者迈不过去的坎。它的核心一句话就能说清父类引用指向子类对象调用同一个方法时表现的是子类的行为。Animal a new Cat(); a.eat(); // 实际输出的是Cat里重写后的内容4.1 多态的三个前置条件形成多态必须有三个条件缺一不可有继承关系或实现关系。子类重写了父类方法。父类引用指向子类对象。尤其是第二条如果没有方法重写多态就无从谈起调用的一定是父类自己的方法。很多同学在IDE里跑过这个例子才真正理解Animal a new Cat()编译期看的是Animal类型但运行期Java会找真正的对象类型去执行方法这个机制叫动态绑定。4.2 向上转型与向下转型的真正用法Animal a new Cat()这种写法叫向上转型子类对象当作父类类型使用。向上转型后的引用只能调用父类中存在的方法子类新增的方法是调不出来的。比如Cat里的catchMouse()a引用就没法直接调用。那子类独有的方法怎么用需要向下转型把父类引用强制转回子类类型。Animal a new Cat(); if (a instanceof Cat) { Cat c (Cat) a; c.catchMouse(); }向下转型在项目中经常发生在集合里。比如一个ListAnimal里存了Cat、Dog多种对象遍历时想调用各自特有的方法就必须先判断instanceof再强转。重点提醒强转之前一定先用instanceof判断否则运行期直接抛ClassCastException程序当场崩溃。笔试里经常考“强转不判断instanceof会怎样”答案就是抛这个异常。4.3 多态在项目里最常出现的场景多态最实际的价值在参数传递和返回值上。写一个处理动物叫声的方法可以写成void makeSound(Animal a)这样不管传Cat还是Dog都能接收而且运行时自动调用对应子类的方法。我在做项目时多态用得多的场景包括统一的DAO接口有多个实现类、日志框架切换、支付方式选择。比如程序里有微信支付、支付宝支付、银行卡支付它们都实现同一个支付接口业务层只需要持有接口类型具体传哪个实现进来由调用方决定。这样新增一种支付方式不需要改动业务代码这就是多态带来的扩展性。如果不用多态写出的代码大概率是一长串if-else判断对象类型每加一种新类型就要改一遍原有代码维护成本直线上升。面试官看到这种情况会直接认为候选人对面向对象理解不够深。5. 构造方法和初始化对象是怎么“从无到有”的构造方法名字和类名一模一样没有返回值类型new对象时自动执行。它的作用就是给对象做初始化把属性设成初始值。5.1 无参构造和有参构造每个类都默认带一个无参构造。如果你自己写了任意一个有参构造默认的无参构造就“消失”了。下面这种情况就是笔试常考的坑public class User { private String name; public User(String name) { this.name name; } }这个类现在没有无参构造了。如果代码里写了new User()编译直接报错。很多框架比如Spring的Bean管理、各种JSON反序列化工具底层都依赖无参构造来创建对象。所以写实体类时哪怕提供了有参构造建议也手动保留无参构造。这一条已经帮我规避过好几次线上问题值得养成习惯。有参构造本质上是给属性快速赋初始值。在学校学的时候常写一堆重载构造但在工业级项目里实体类的有参构造并不一定常用更多是用Set方法或Builder模式。原因是有参构造的参数太多时调用方根本分不清顺序容易传错。5.2 this到底指的是谁this是一个“当前对象引用”。它最常见的三个用途区分局部变量和成员变量、调用本类构造方法、把当前对象作为参数传递。public class Student { private String name; public Student(String name) { this.name name; // 左边的name是成员变量右边是参数 } }这里不加this的话会形成“自己给自己赋值”的假象成员变量根本没被赋值。这是初学者写代码最常见的低级错误也是IDE给黄色警告最多的地方。this还能调用本类别的构造方法比如无参构造里调用有参构造减少重复初始化代码。但需要注意this(...)调用构造方法也必须是第一行不能和super(...)同时出现。因为第一行只能写一个构造调用。5.3 继承关系下的初始化顺序对象初始化的完整顺序是个面试高频题。简单总结成口诀就是父类静态代码块 → 子类静态代码块 → 父类构造代码块 → 父类构造方法 → 子类构造代码块 → 子类构造方法。这里静态代码块只在类第一次加载时执行一次。很多人在学校只背了“先父后子”实际项目里遇到极端场景还是会踩坑。有一次我们排查一个服务启动缓慢的问题最后定位到是一个父类的静态代码块里做了大量耗时操作而子类一加载就触发了它。后来把耗时操作从静态代码块里挪出去启动时间立刻降了下来。所以学到初始化顺序时不要只背答案要和实际性能场景结合在一起想。6. 重载与重写同名不同命的两兄弟重载和重写是面向对象里最容易被混为一谈的两个概念。我的经验是只要对比着记忆就不会乱。对比项重载 overload重写 override位置同一个类中父子类之间方法名相同相同参数列表必须不同必须相同返回值可以不同相同或父类返回值的子类型访问权限没有限制不能比父类更严格调用方式编译期根据参数个数/类型决定运行期根据实际对象类型决定6.1 两者的对比重载解决的是“同一个动作不同入参”的问题。比如println()方法传入int、double、String都有对应的版本。你在代码里写System.out.println(123)和println(abc)编译器自动根据参数类型挑对应的重载版本所以重载是编译期就决定好的。重写解决的是“同一个动作子类有不同实现”的问题。它发生在继承关系里调用时由运行期动态判断。考试特别爱考“重载是编译时多态重写是运行时多态”这句话背下来但更重要的是理解背后的含义重载看的是参数列表重写看的是真实对象类型。6.2 面试中怎么考面试官很喜欢就地出题给一段代码一个父类有doSomething(String s)子类有doSomething(Object o)问调用doSomething(null)会执行哪个。这种题的核心是看null能匹配哪个参数Java编译器会优先选择更具体的类型所以会走String版本。如果两个都是String和Integer这种兄弟类型null无法确定编译直接报错。这类题刷起来很有意思也能快速检验一个人是不是真的理解编译期方法分派规则。7. 笔试面试高频考点与易错点汇总面向对象基础在笔试中的出题率极高几乎每家公司的Java初级岗位笔试题里都有它的影子。我整理几个出现频率高的考点供刷题时重点留意。7.1 面向对象和面向过程的经典对比这个题一定不能只背定义。要用例子说明对比项面向过程面向对象思路以步骤为主先做什么再做什么以对象为主谁来做这件事例子洗衣服接水、放衣服、倒洗衣液、开机洗衣机类负责洗人只负责操作代码形态一堆函数类与方法可维护性改一处可能牵连很多各对象之间解耦改动影响小面向对象的出现不是为了淘汰面向过程而是面对越来越复杂的业务逻辑时能更好地分工、复用和维护。但小逻辑里你不一定要硬套面向对象一段十来行的脚本用过程式写法更直接。7.2 笔试中反复出现的陷阱public、protected、默认、private的访问范围背了拿分不背丢分。构造方法没有返回值类型写void User()时它就不是构造方法普通方法而已。this和super不能同时出现在构造方法的第一行。被private修饰的方法不存在重写一说override注解直接报错。父类引用指向子类对象时访问属性看父类调用重写方法看子类。这是很经典的坑。其中最后一条特别容易被轻视。Java里的成员变量和成员方法在继承中的访问规则不一致变量是编译期绑定方法是运行期绑定。面试题里经常放出类似“父类属性x和子类属性x同名父类引用取的是父类的x调用方法却执行子类逻辑”的题平时自己多写几个demo就会发现规律。7.3 现场手写标准类的规范面试手写题里让你写一个标准JavaBean的情况很常见。写的时候注意几个细节类用public修饰类名首字母大写且和文件名一致属性用privategetter/setter命名规范get加属性名boolean类型习惯用is开头提供无参构造如果实现了接口记得实现全部抽象方法。这些细节看着小但在代码评审和面试评分中是实实在在的加分点。我后来带团队做代码Review也会先看类结构规不规范类结构清晰的代码业务逻辑一般也干净。8. 项目实战中的类设计经验与避坑心得基础语法学完只是第一步真正拉开差距的是面对一个业务需求能不能设计出合理的类结构。下面分享几个我从培训到做项目一路沉淀下来的设计套路和调试技巧。8.1 从需求到类的四步拆解法看到一个需求先不要急着写代码按四步思考找名词、找动词、分关系、定方法。比如做个“图书管理系统”。名词有图书、读者、管理员动词有借书、还书、查询关系有读者借图书、管理员管理系统。于是很自然就能拆出Book、Reader、Admin三个类借书和还书方法放Reader类里图书查询放Book类里。这四步听起来简单但能坚持做的人不多。很多新人一上来就写一个大Main类全部逻辑堆在一起几百行以后自己都看不懂。养成先画简单的类图再写代码的习惯后面维护成本会低很多。8.2 类设计的三条实用原则第一条单一职责原则。一个类只做一件事别搞“上帝类”。如果一个类里既管登录又管订单又管发邮件任何一处改动都可能影响其它两处线上出事往往就是这样来的。第二条高内聚低耦合。类内部的代码联系紧密类与类之间的依赖降到最低。判断标准很简单如果删除某个类其它类会不会大面积报错会的话说明耦合太高。第三条面向接口或抽象类编程而不是面向具体实现。在业务层里变量类型尽量声明成父类或接口方便切换实现。比如数据访问层接口叫UserDao实现是UserDaoImpl业务代码里只依赖UserDao将来换实现类不用动业务代码。8.3 调试面向对象代码的土办法最后说点实用的调试经验。很多同学学到这里代码一报错就蒙。我自己的土办法是三步走第一步看堆栈信息找到第几行第二步用debug断点看对象的属性值第三步如果属性值不对回查构造方法和setter方法看看是不是没初始化或者传参顺序错了。还有一个看起来笨但很好用的习惯写完一个类马上去main里new一个对象测一下属性对不对、方法通不通。不要攒到整个模块写完才一起测那样出错就不知道该查哪里。培训机构老师当年也是这么带我们过来的一段一段地验证知识点和代码始终对得上信心也在这个过程中积攒起来。面向对象基础这块难点不在语法而在思维方式转变。用类去规划代码结构用封装去保护数据用继承去抽象共性用多态去应对变化。把这四件事想透后面的集合、泛型、设计模式学起来都会轻松很多。每一步都有数不清的小函数我在实际开发中还发现自己在调试过程中用到的知识往往就是当初最不起眼的那几行代码。多写、多试、多踩坑这些东西最终都会变成你自己的判断力。