编程如同修炼武功,面向对象就是你的九阳神功。练成此功,万般代码皆可破!现在,让我们正式踏上这场OOP的修行之旅吧!
目录
6、子类构造器super(...)调用父类构造器(调用父类成员)
7、子类构造器this(...)调用兄弟构造器(实现填写默认信息)
1、多态前提:有继承/实现关系;存在父类引用子类对象;存在方法重写
📢 文章提示
-
本文包含完整代码示例和图解说明
-
学习前需要掌握Java基础语法
-
建议配合IDE边看边实践J
🌟 文章前言
面向对象编程(OOP)是现代编程的基石,掌握它就像获得打开编程世界的万能钥匙。本文将通过生动比喻+实战代码,带你从零构建OOP知识体系。无论你是刚入门的新手,还是需要巩固基础的中级开发者,这里都有你需要的干货!
一、首先我们来谈谈面型对象和面向过程的区别
面向过程 vs 面向对象
面向过程 | 面向对象 | |
---|---|---|
思维 | 怎么做(How) | 谁来做(Who) |
特点 | 线性思维 | 模块化思维 |
适用场景 | 简单任务(计算器) | 复杂系统(电商平台) |
示例 | 做菜:洗菜→切菜→炒菜→装盘 | 开餐厅:厨师、服务员、收银员分协作 |
Q: 谈谈面向对象和面向过程的理解?
S:面向过程:就像你自己动手做一切,从头到尾一步步按顺序来。面向对象:更像是管理一个团队,给不同的人分配不同的任务,然后大家各司其职共同完成目标。
对于描述复杂的事务 为了宏观上把握 从整体上合理分析 我们需要使用面向对象的思路来分析整个系统 但是,从具体到微观操作 仍然需要面向过程的思路去处理。
二、面向对象核心概念
类与对象的关系
类是一种抽象的数据类型,它定义了某一类事物的属性(字段)和行为(方法)。类是对现实世界实体的一种描述或蓝图,并不表示具体的实体。
例如,
Person
类可以包含人的姓名、年龄等属性以及走路、说话等行为
对象是类的具体实例,代表了现实中某个具体的实体。例如,“张三”就是Person
类的一个具体实例,具有特定的名字和年龄等属性值。
代码体现:
// 类定义(设计图)
class Phone {
// 属性(状态)
String brand;
double price;
// 方法(行为)
void call(String number) {
System.out.println("正在拨打:" + number);
}
}
public class Main {
public static void main(String[] args) {
// 创建对象(根据设计图生产产品)
Phone myPhone = new Phone();
myPhone.brand = "华为";
myPhone.price = 5999.0;
myPhone.call("10086");
}
}
创建与初始化对象
在Java中,创建对象通常使用new
关键字。这个过程不仅分配了内存空间,还会调用相应的构造器来初始化对象的状态。
内存分配
java在内存的JVM虚拟机上运行,JAVM虚拟机又分为堆内存、栈内存和方法区一同执行程序。堆是存放对象的地方,栈是存放方法的地方,方法区是放类文件的地方。
变量存在栈里,变量指向对象,对象存在堆里,对象指向类,类存在方法区,将方法区中的方法调到栈中执行
万物皆对象,一个数据由一个对应的对象处理,我们设计对象时就是在设计类(对象的模板)。
构造器
(1)、构造器:类中定义的方法,用来初始化对象,在类中定义的方法,称为方法,在类中定义的变量,称为属性。名字与类名一致,无返回值,无参数,无返回值类型,无访问修饰符。
(2)、特点:创建对象时,对象会自动调用构造器,如果没有定义构造器,JVM会自动生成一个无参构造器。
(3)、应用场景:创建对象时,调用构造器,立即初始化对象成员变量的值。
(4)、注意:类默认有一个无参构造器(没有显示而已),若你自己定义了有参构造器,那么JVM就不会自动生成一个无参构造器。
有参构造:一旦定义了有参构造,无参就必须显示定义
public class User {
//构造器,特殊方法,不能写返回值,名称与类名一致。
public User() {System.out.println("==无参构造器执行了==");
}//无参构造器
public User(String name, Integer age, boolean gender, String email, String password, Integer math, Integer chinese) {
this.name = name;
this.age = age;
this.gender = gender;
this.email = email;
this.password = password;
this.math = math;
this.chinese = chinese;
}//有参构造器,构造器重载
}
//在mian方法中调用构造器
public class main() {
public static void main(String[] args) {
User user = new User();//无参构造器返回对象
user.setName("Rasion");//传入数据给无参构造器对象
user.setAge(20);
user.setGender(true);
user.setEmail("rasion@gmail.com");
user.setPassword("123456");
user.setMath(100);
user.setChinese(100);
User s2 = new User("Rasion", 20, true, "rasion@gmail.com", "123456", 100, 100);
//有参构造器返回对象,创建对象时,调用构造器,立即初始化对象。
}
}
this关键字
(1)、this 关键字:是一个变量,可以用在方法中,用来拿到当前对象;哪个对象调用方法,this就指向那个对象,也就是哪个对象。
(2)、应用场景:解决变量名称冲突问题。(见有参构造器)。
public void print(String name){//(二)、2、this关键字解决变量冲突问题
System.out.println(name+this.name);//this.name拿到的是对象变量(成员变量)name,而不是局部变量name
}
实体类(JavaBean)
(1)、实体类:只能用来封装数据的类,也叫JavaBean。
(2)、特点:类中成员变量私有,提供getter和setter方法;类中需要一个无参构造器,和有参构造器可选
(3)、应用场景:实体类的对象只负责数据的封装,不涉及任何业务逻辑。而数据的业务处理交给其他类的对象来完成,以实现数据和业务处理相分离(解耦)。
static修饰成员变量
(1)、static关键字:修饰成员变量、方法、类,修饰后,该成员变量、方法、类属于类,而不是对象。
(2)、静态变量(类变量):有static修饰,属于类,被类的全部对象共享,所有对象都可以访问。
(3)、实例变量(对象的变量):没有static修饰,属于每个对象,每个对象都有自己的变量,对象可以访问。
(4)、应用场景:如果某一个数据只需要一份,并且希望能够被共享、访问、修改,则该数据被定义成静态变量。 (如用户类,记录了创建了多少个用户对象)
public class User {
static String name;//静态变量(类变量)
int age;//实力变量(对象的变量)
}
public class main(){
public static void main(String[] args) {
User s1= new User();//s1对象
User s2= new User();//s2对象
//其中,s1和s2各自都有各自的age变量,但s1不能访问s2的age。
//但name是静态变量,所有对象都可以访问,存储在类中的。
//所以我们访问静态变量一般都直接用 类名.静态变量 如 User.name
User.name="Rasion";
s1.age=10;
s1.name="Rasion1";
s2.age=20;
s2.name="Rasion2";
System.out.println(User.name+s1.age+s1.name+s2.age+s2.name);
//Rasion210Rasion220Rasion2
}
}
static修饰方法
(1)、static修饰静态方法:有static修饰的成员方法,属于类,最好用类名调用,少用对象名调用。
(2)、实例方法:无static修饰的成员方法,属于对象,用对象名调用,不能用类名调用。
规范:如果一个方法只是为了做一个功能且不需要直接访问对象的数据,这个方法直接定义为静态方法。
如果这个方法是关于对象的行为,需要访问对象的数据,这个方法必须定义为实例方法
(3)、比如在main方法中,我们直接调用其他方法,是用类名调用,只不过类名在同一类中可以忽略不写。(main方法是静态方法)
工具类与静态方法
(1)、工具类:封装了多个静态方法,每个方法用来完成一个功能,给开发人员直接使用。
(2)、区别:实例方法需要创建最想来调用,此时对象占用内存,而静态方法不需要,可以减少内存消耗;静态方法可以用类名调用,调用方便,能节省内存。
public class StaticAbout {//工具类
//工具类没有创建对象的需求,建议讲工具类的构造器私有。
private StaticAbout() {
}
public static String getCode(int n) {//静态方法与工具类
String code = "";
for (int i = 0; i < n; i++) {
int type = (int) (Math.random() * 3);//0-9 1-26 2-26
switch (type) {
case 0:
code += (int) (Math.random() * 10);
break;
case 1:
code += (char) (Math.random() * 26 + 'a');//得到小写字母的区间
break;
case 2:
code += (char) (Math.random() * 26 + 'A');
}
}
return code;
}
}
静态方法与实例方法访问注意事项
(1)、静态方法中可直接访问静态成员,不可直接访问实例成员。
(2)、实例方法中即可直接访问静态成员,也可直接访问实例成员。
(3)、实例方法中可以出现this关键字,静态方法中不可出现this关键字。
public class Attention{
public static int count=100;//静态变量
public static void print(){//静态方法
System.out.println("Hello World!");
}
public String name;//实例变量,属于对象
public void prints(){//实例方法,属于对象
}
public static void main(String[] args) {
}
//(1)、静态方法中可直接访问静态成员,不可直接访问实例成员。
public static void printTest1(){
System.out.println(count);
print();
//System.out.println(name);//报错
//prints();//报错
//System.out.println(this);//报错,this代表的只能是对象
System.out.println(Attention.count);
}
//(2)、实例方法中即可直接访问静态成员,也可直接访问实例成员。
public void printTest2(){
System.out.println(count);
print();
System.out.println(name);
prints();
System.out.println(this);//实例方法中,this代表的是当前对象
}
}
三、面向对象三大特性
面向对象编程(OOP)的核心在于其三大特性:封装、继承和多态。这些概念帮助我们创建更加模块化、易于维护和扩展的代码。
封装
封装是将数据(属性)和操作数据的方法捆绑在一起,并隐藏对象内部的具体实现细节,只暴露必要的接口给外部使用。
高内聚低耦合:鼓励将相关功能放在一起,减少不同部分之间的依赖。
属性私有化与get/set
方法:通过将类的属性设置为私有,并提供公共的get
和set
方法来访问和修改这些属性,可以控制对属性的访问权限,增加安全性。
封装的设计要求
(1)、封装的设计要求:合理隐藏、合理暴露。
(2)、(合理隐藏)使用private关键字封装变量,防止用户在其他类中随意对本类内的变量修改数据,只允许在本类中直接被访问。
(3)、(合理暴露)使用get和set方法,封装变量,让用户在类外直接调用,修改数据。
public class Person {
private String name;
private int age;
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; }
}
继承
1、extends
继承关键字:extends,可以让一个类与另一个类建立起父子关系
public calss B extends A{}//其中A是父类,B是子类
只能继承父类的非私有成员(成员变量、成员方法)
继承后的创建:子类的对象是由子类、父类共同完成的,所以子类对象是完整的,父类对象是部分。
继承的好处:代码重用,减少代码量,提高效率。
2、权限修饰符
用来限制类中的成员能够被访问的范围。
修饰符 | 本类里 | 同一个包中的其他类 | 子孙类(包括不同包的) | 任意类 |
private | √ | |||
缺省 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
public class Father {
private void privateMethod() {//二、2、权限修饰符之私有方法
System.out.println("private method");
}
void defaultMethod() {//二、2、权限修饰符之缺省方法
System.out.println("default method");
}
protected void protectedMethod() {//二、2、权限修饰符之受保护方法
System.out.println("protected method");
}
public void publicMethod() {//二、2、权限修饰符之公共方法
System.out.println("public method");
}
public static void main(String[] args) {//本类中可以调用的所有方法
Father father = new Father();
father.privateMethod();
father.defaultMethod();
father.protectedMethod();
father.publicMethod();//所有类都可以调用public方法
}
}
//以下为同一包的子类
class SonA extends Father {
public void test() {
//privateMethod();//子类中不可以访问父类的私有方法
defaultMethod();//子类中可以访问父类的缺省方法
protectedMethod();//子类中可以访问父类的受保护方法
publicMethod();//子类中可以访问父类的公共方法
}
}
//以下为不同包的子类
class SonC extends Father {
public void test() {
//privateMethod();//子类中不可以访问父类的私有方法
//defaultMethod();//不同包的子类中可以访问父类的缺省方法
protectedMethod();//不同包的子类中可以访问父类的受保护方法
publicMethod();//不同包的子类中可以访问父类的公共方法
}
}
//以下为不同包的非子类
class main {
public static void main(String[] args) {
Father father = new Father();
//father.privateMethod();
//father.defaultMethod();
//father.protectedMethod();
father.publicMethod();//其他包中,只能访问public方法
}
}
3、继承的特点
单继承 | 一个类只能继承一个直接父类 |
多层继承 | 不支持多继承,但支持多层继承(多继承的话若两个父类出现相同方法,无法判断调用哪个) |
祖宗类 | Java中的所有类都是Object类的子类(一个类要么默认继承Object,要么间接继承) |
就近原则 | 优先访问自己类中,自己类中的没有才访问父类(访问父类的成员要加super) |
4、方法重写(声明不变,重新实现)
子类重写父类的方法,子类对象调用父类方法,实际调用子类重写的方法。(声明不变,重新实现)
子类重写时,访问权限必须大于或等于父类的该方法的权限(public>protected>default>private)
重写的方法,必须与重写前的方法具有相同的参数列表和返回值类型(返回值类型一样或更小)。
私有方法(不能被继承所以不能被重写)和静态方法(自己调用)不能被重写,否则报错。
public class TestOverride {
public static void main(String[] args) {//二、4、方法重写
animal a=new cat();
//用父类申明对象,利用多态性和向上转型的概念,以实现更加灵活和可扩展的程序设计,降低代码的耦合性
a.eat();
cat b=new cat();
b.eat();
}
}
class animal{
public void eat(){System.out.println("animal eat");}
}
class cat extends animal{
@Override//方法重写的校验注解(标志),要求重写的方法与父类方法签名一致,否则编译报错,可读性好
public void eat(){System.out.println("cat eat!!!");}
}
5、重写toString方法
public class TestOverride {
public static void main(String[] args) {
animal a=new animal();
System.out.println(a);
//若没重写toString方法,则返回:com.rasion.extendANDpolymorphism.extend.cat@4e50df2e
System.out.println(a.toString());
//我们直接输出对象,会默认调用Object的toString方法,返回对象的地址信息
//故我们可以重写toString方法,返回我们想要的信息
}
}
class animal{
private String name;
@Override
public String toString() {//重写toString方法,返回我们想要的信息
return "animal{" +
"name='" + name + '\'' +
'}';
}
}
6、子类构造器super(...)调用父类构造器(调用父类成员)
子类构造器,必须先调用父类的构造器,再调用自己的构造器。(可以用super(null)指定调用父类的有参构造器)
子类构造器第一行默认都是super(),他会调用父类的无参构造器,若父类没有无参构造器,则报错。
应用场景:有一些参数是处在父类中,子类没有,但子类需要用到,这时,子类构造器中,可以先调用父类的有参构造器,再调用自己的构造器。
public class TestOverride {
public static void main(String[] args) {
animal a=new cat("小猫","可爱");
System.out.println(a);//返回:cat{name='小猫', cute='可爱'}
}
}
class animal{
private String name;
public animal(){}
public animal(String name){//父类的有参构造器
this.name=name;
}
protected String getName() {return name;}//父类的getter和setter,方便子类调用
protected void setName(String name) {this.name = name;}//我认为父类的参数最好只允许子类访问
}
class cat extends animal{
private String cute;
public cat(){}
public cat(String name,String cute){//子类有参构造器
super(name);//===调用父类的有参构造器===
this.cute=cute;
}
@Override
public String toString() {//只需要重写toString方法,返回我们想要的信息
return "cat{" +
"name='" + getName() + '\'' +
", cute='" + cute + '\'' +
'}';
}
}
7、子类构造器this(...)调用兄弟构造器(实现填写默认信息)
一般调用兄弟构造器,可以实现代码复用。
注意:super(..) this(...)必须写在构造器第一行,并且不能同时出现。(兄弟构造器只需要一个调用父类构造器即可)
public class User{
private String name;
private int age;
private String school;
//...此处省略get、set方法toString方法
public User(String name, int age) {
this(name, age, "CTGU");//调用本类其他构造器,以实现填写默认信息
}
public User(String name, int age, String school) {
this.name = name;
this.age = age;
this.school=school;
}
}
多态
1、多态前提:有继承/实现关系;存在父类引用子类对象;存在方法重写
多态是在继承/实现的情况下的一种现象、表现为:对象多态、行为多态(对象的多样性,行为的多样性,但是没有成员变量的多态性)
people p1=new student();//对象多态
p1.run();//行为多态
people p2=new teacher();//对象多态
p2.run();//方法:编译看左边,运行看右边
System.out.println(p2.name);//成员变量:编译看左边,运行也看左边
2、多态的好处
右边的对象是解耦合的,更便于扩展和维护。父类类型的变量作为参数可以接收一切子类变量
问题:多台下不能调用子类独有功能。
public class TestPolymorphism {
public static void main(String[] args) {
people p1=new student();//多态调用不了子类的独有功能
people p2=new teacher();
show(p1);
show(p2);
System.out.println(p1.name);//成员变量:编译看左边,运行也看左边
}
public static void show(people p) {
//父类类型作为参数,可以接收一切子类变量,不能为Student或者teacher
System.out.println("=====+++=====");
p.run();//调用方法:编译看左边,运行看右边
}
}
3、什么不能被重写
- static 方法 属于类 他不属于实例
- final 常量
- private 方法
4、多态下的类型转换(instanceof判断)
instanceof (类型转换) 引用类型 判断一个对象是什么类型 如果匹配进行类型转换
强制类型转换:子类 对象名=(子类) 父类对象名;
强制类型转换后能够调用子类的私有方法
在运行时,如果发现对象的正式类型与强制转换后的类型不同,就会报类型异常(ClassCastException)的错误
在强转前可以用instanceof关键字判断对象的真实类型,再进行强转
- 父类引用指向子类的对象
- 把子类转换为父类 向上转型
- 把父类转换为父子类 向下转型 强制转换
- 方便方法的调用 减少重复代码
public class TestPolymorphism {
public static void main(String[] args) {
people p1=new student();
student s=(student)p1;//强制类型转换
// teacher t=(teacher)p1;//转换错误,因为p1是student类型
//编译阶段有类型强转不会报错,运行阶段会报错
}
public static void show(people p) {
if(p instanceof teacher) {//判断类型,一般会在方法中写,来判断p是否为teacher类型
teacher t=(teacher)p;
...//调用teacher的独有功能
}else if(p instanceof student) {
student s=(student)p;
...
}
}
}
🚀 文章总结
面向对象编程就像组建一支分工明确的团队。每个类都是不同岗位的员工:
-
封装:财务部管钱,其他部门不能直接动保险箱
-
继承:新员工继承老员工的经验,还能发展新技能
-
多态:同样汇报工作,程序员用代码演示,产品经理用PPT讲解