【码上行动】Java[二] 面向对象

1. 类和对象

之前初学得时候写的文章👉Java类和对象

简单概括来说就是一句话:类是设计图,对象是产品,一个是抽象的不存在的东西,一个是现实的能用的东西。

  • 类是对一类事物的描述,是抽象的

  • 对象是类事物的实例,是貝体的

  • 类是对象的模板,对象是类的实体

那你要用这个类(设计图),你就要先实例化对象(把这个设计图上的东西造出来用)

就像楼盘,你得现有设计图纸,才能造房子呀。对于住房的人来说,设计图纸又用不了;但是没有图纸又没法造房子。

所以,得出了结论:

类是对象的抽象,对象是类的实例

在Java中,一切皆对象,因为类不能用呀。但是必须现有类才能有对象,因为对象的产生依赖于类的实例化。(设计图纸也没你谈什么造房子)

类的定义与使用

类的组成:

  • 成员变量:对应事物的属性

  • 成员方法:对应事物的行为

具体的使用参见👉Java类和对象

单个对象调用内存图

程序的入口是main方法,从mian方法处开始执行

  1. 先实例化对象,在栈上存放对象的地址

  2. 在堆上创建新的对象(new出来的)

  3. 实例化对象,为成员变量赋值

  4. 在引用成员方法时,先通过栈上存放的引用地址找到堆内存上成员方法的地址(成员方法放在成员方法区Method Area)

  5. 根据堆上的引用找到方法区的method调用

  6. 方法区的方法要想执行,先压入到栈中才能执行。在主方法中调用的顺序就是执行的顺序。压入栈中执行完后立即弹出释放

  7. 最后全部执行完成,主方法也销毁(主方法是最先执行的方法,由栈的结构可知最后释放)

两个对象调用同一个方法内存图

上面的例子是实例化了一个对象来调用方法,那一个类实例化两个甚至是多个对象,怎么来调用呢?

Phone类中有三个方法,实例化了两个对象onetwonew了几次,就在堆上开辟几个新空间。但是方法不会创建两次,两个在堆上实例的对象引用的都是方法区同一个地址。

这里还有一个概念,叫引用传递

引用传递:一块堆内存可以被多个栈内存所指向

User user1 = new User();
User user2 = user1;

就是将user1在堆上实例化对象的地址引用赋给user2,此时两个栈引用指向同一块堆内存

一个有意思的问题就来了。在方法之间,即可以传递值,也可以进行引用传递。那么,Java参数传递的方式,到底是值传递还是引用传递?

Java中到底是值传递还是引用传递

给出结论:Java参数传递方式的为值传递,并且只有值传递一种

详情参见:👇👇👇👇👇👇

Java中的参数传递,到底是值传递还是引用传递?

成员变量与局部变量

局部变量与成员变量:

1. 定义的位置不一样

  • 局部变量:定义在方法中

  • 成员变量:定义在类中

2. 作用范围不一样

  • 局部变量:作用在定义的方法中

  • 成员变量:作用在类中

3. 默认值不一样

  • 局部变量:无默认值,要想使用必须手动赋初值

  • 成员变量:如果没有赋值,会有默认值

    • 如果是整数 默认为0

    • 如果是浮点数 默认为0.0

    • 如果是字符 默认为’\u0000’

    • 如果是布尔 默认为 false

    • 如果是引用类型 默认为null

4. 内存位置不一样

  • 局部变量:栈中

  • 成员变量:堆中

5. 生命周期不一样

  • 局部变量:进栈诞生,出栈消失

  • 成员变量:对象创建产生,对象被回收消失

this关键字

this关键字主要有以下三个方面用途:

  1. this调用本类属性

  2. this调用本类方法(构造方法和成员方法)

  3. this表示当前对象

当方法的局部变量和类的成员变量重名的时候,根据“就近原则”,优先使用局部变量

如果需要访问本类当中的成员变量,需要使用格式:this.成员变量名


super关键字

this关键字主要有以下三个方面用途:

  1. 在子类的成员方法中,访问父类的成员变量。

  2. 在子类的成员方法中,访问父类的成员方法。

  3. 在子类的构造方法中,访问父类的构造方法。

super和this两种构造调用,不能同时使用,因为都得放在第一行,不能同时满足。

2. 面向对象的思想

面向对象,面向是什么?为什么面向的是对象?

面向谁,就更加关注谁。我们面向对象自然就是更加关注“对象”了。

俺没有对象,不知道有“对象”的思想是什么。有对象的铁汁萌好好关心一下对象,没有的也别new了。好好学习就完事了,等着国家分配吧。

那,为什么要面向对象呢?因为我们关注的不再是程序具体怎么实现的,而是更加关注实现了什么功能。

面向对象的思想来源于生活,大神们设计编程语言都是基于“来源于生活但是高于生活”的思想来设计的。面向对象,就是我关注的是”用什么来实现这件事“,面向过程更加关注“怎么来实现”。一个强调结果,谁能帮我干成;一个强调过程,我怎么来做。

就像洗衣服,面向过程就是自己洗,先把衣服放盆里用洗衣液浸泡,然后洗衣服,晾衣服。面向对象则是直接把衣服扔给我们的对象------“全自动洗衣机”来洗,我不关系它怎么洗的,我只关心洗干净没有.

其实,与面向过程相比,就是关注的点不一样了,我们来二者对比一下,更好的说明。

面向对象,也有偷懒一说。其实就是别人封装写好的功能,你不用管怎么实现的,你关心的是用别人写好的东西来实现自己想要的功能。

其实,在这一点上,相比于Java,Python(动态的、面向对象的脚本语言)更具有代表性,Python有很多很多第三方的类库,都是别人写好的功能,使用更加方便。

我们拿一个打印数组来说明:

【面向过程】

【面向对象】

Arrays.toString()方法源码:

面向过程全部需要自己来实现手工打印,而面向对象则直接调用打印数组的方法。就好像别人都在用打火机了,你还在钻木取货。但是对比而言,面向过程拥有更高的执行效率,这是面向过程不具备的优势。

3. 面向对象的三大特征

面向对象名词扩展

  • OOA:面向对象分析

  • OOD:面向对象设计

  • OOP:面向对象编程

面向对象三大特征

  1. 封装性:类内部操作对外部的不可见性(保护性)

  2. 继承性:子类继承了父类所有的功能,并在无需重新编写原来的类的情况下对这些功能进行扩展

  3. 多态性:多态是同一个行为具有多个不同表现形式的能力

4. 封装

详情参见:👇👇👇👇👇👇

private封装

5. 继承

继承是为了更好地复用之前的代码。子类继承了父类所有的功能(不能选择性的继承,只能是全继承),并在无需重新编写原来的类的情况下对这些功能进行扩展。

Java中的继承是单继承,要想实现多继承则可以使用接口或者多重继承

  • 类与类之间是单继承的。直接父类只有一个

  • 类与接囗之间是多实现的。一个类可以实现多个接口

  • 接囗与接囗之间是多继承的


继承注意事项:

  1. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有(因为封装对外的不可见性)。
  2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
  3. 子类可以用自己的方式实现父类的方法。

继承中成员变量访问规则:

在父子类的继承关系当中,创建子类对象,访问成员变量的规则:

  • 就近原则

继承中成员方法访问规则:

在父子类的继承关系当中,创建子类对象,访问成员方法的规则:

  • 创建的对象是谁,就优先用谁(new 的谁,就用谁),如果没有则向上找

继承中构造方法访问规则:

  • 子类构造方法中有一个默认隐含的super()调用,所以一定先调用父类构造,再调用子类(先有父亲才有儿子)

  • 子类构造可以通过super关键字来调用父类重载构造,不写则默认调用父类无参构造,写了则调用指定参数

  • 父类构造调用,必须是子类构造方法的第一个语句,而且只能调用一次


重载(overload)和重写(override)

重载就是同样的一个方法能够根据输入的数据的类型不同,做出不同的处理

重载就是同一个类中,多个同名方法根据不同的传参来执行不同的逻辑处理。

重写

重写就是当子类继承父类的相同方法,输入数据一样,但要实现和父类不同的功能时,就需要覆写父类方法

重写发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写。

  1. 返回值类型、方法名、参数列表必须相同,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。
  2. 如果父类方法访问修饰符为 private/final/static ,则子类就不能重写该方法,但是被 static 修饰的方法能够被再次声明。
  3. 构造方法无法被重写

重载就是产生一个新的,覆写就是把原来的覆盖了。


请解释 方法重载(overload) 和 方法覆写(override) 的区别:

子类方法重名问题

局部变量:直接写成员变量名

本类的成员变量:this.成员变量名

父类的成员变量:super.成员变量名

6. 多态

多态性:多态是同一个行为具有多个不同表现形式的能力

extends继承或者implements实现,是多态性的前提

【多态性的理解】:对象又有多种表现的形态

就好比说张三这个学生,是属于人类的。这种说法是正确的,因为一个学生,本来就是一个人。我们先说的是小范围的,再说大范围的。

那要是反过来说,人类就是一个学生没这肯定不行了。因为他可能早就毕业了,现在不是学生了,而是一个员工了。

那么,这也就刚好说明了,实现多态性的前提就是,发生在继承关系的类中(接口的实现,也可以看做是继承)。正因为学生类继承了人类,才能说“张三这个学生,是属于人类的”;但是人类没有继承学生类呀,所以“人类就是一个学生”的说法是错误的。


多态性的体现

现在我有两个类,一个是Fu类,一个是Zi类,Zi类继承了Fu类。

多态性在代码中的体现:父类引用指向子类对象

Fu odj = new Zi(); //父类引用指向子类对象

多态中成员变量的访问:

访问成员变量的两种方式:

  1. 直接通过对象名称访问成员变量:看等号左边是谁,优先用谁,没有则向上找

  2. 间接通过成员方法访问成员变量:看该方法属于谁,优先用谁,没有则向上找

输出结果:

10

10

多态中成员方法的访问:

  • new的是谁,就优先用谁,没有则向上找

  • 编译看左边,运行看右边

输出结果:

方法执行👉子类
父类特有方法.

那我再访问一下子类的methodZi()呢?

  • 会出现编译报错

编译看左边,左边是Fu,Fu当中没有methodeZi()方法,所以编译报错


【多态在代码中的体现总结:】

  • 成员变量:编译看左边,运行还看左边

  • 成员方法:編译看左边,运行看右边new的谁,就执行谁

向上转型

向上转型:其实就是多态的写法

【格式】:父类名称 对象名 = new 子类名称( );

【含义】:右侧创建一个子类对象,把它当做父类来看待使用

Animal animal =  new Cat();

【注意事项】:向上转型一定是安全的。从小范围转向了大范囤,从小范围的猫,向上转换成为更大范囤的动物。

可以类比为自动类型的转换

double num = 100; //int----->double

向下转型

向下转型:其实是一个【还原】的动作。

Q:还原的是谁呢?

  • 在发生向下转型之前,一定先发生向上转型,还原的是向上转型时子类的对象。

【格式】:子类名称 对象名 =(子类名称) 父类对象

【含义】:将父类对象,[ 还原 ] 成为本来的子类对象

Animal animal = new Cat();  //本来是猫,向上转型成为动物
Cat cat =(cat) animal;  //本来是猫,已经被当做动物了,还原回来成为本来的猫

运行结果:

Exception in thread “main” java.lang.ClassCastException: 狗类转换异常
😼猫吃鱼
猫捉老鼠

【注意事项】

a. 在发生向下转型的时候,必须先向下转型。就是必须保证对象本来创建的时候,就是猫,才能向下转型成为猫。

b. 如果对象创建的时候本来不是猫,现在非要向下转型成为猫,就会报错ClassCastException<类型转换异常>

可以类比为强制转换

int num = (int) 10.0;

做个总结:

多态就是一个对象拥有多种表现的形态

这就是面向对象的一些知识要点了。其实,随着学习的不断地深入,面向对象的精华是思想在项目和框架中的应用。

自动拆装箱

包装类

【定义】:将基本的数据类型以及一些辅助方法包装到类中

Java中的数据类型int,double等不是对象,无法通过向上转型获取到Object提供的方法,基本数据类型由于这样的特性,导致无法参与转型,泛型,反射等过程,为了更好地通过对象来调用其中的方法,Java提供了包装类。

基本数据类型包装类
booleanBoolean
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter

基本类型与包装类型的相互转换

【基本类型转为包装类对象】

I. 直接传值转换

Integer a = new Integer(3);

II. valueOf() 推荐使用

Integer b = Integer.valueOf(3);

【包装类型转为基本类型】

数值型用xxxValue()

int c = b.intValue();

字符串String用toString() 或者 +空字符串

String str = msg.toString();
String str = msg + "";

自动拆装箱

自动装箱和拆箱就是将基本数据类型和包装类之间进行自动的互相转换。JDK1.5后,Java引入了自动装箱/拆箱

【自动装箱】

将基本数据类型转为“对象”

我们以Integer为例:在JDK1.5以前,这样的代码:

Integer i = 5是错误的,必须要通过

Integer i = new Integer(5);

这样的语句来实现基本数据类型转换成包装类的过程:而在JDK1.5以后,Java提供了自动装箱的功能,因此只需 Integer i = 5这样的语句就能实现基本数据类型转换成包装类,这是因为JVM为我们执行了

Integer i = Integer.valueOf(5);

这样的操作,这就是Java的自动装箱

【自动拆箱】

每当需要一个值时,对象会自动转成基本数据类型,没必要再去显式调用intValue()、 doubleValue()等转型方法。如 :

Integer i=5;
int j = i;

这样的过程就是自动拆箱。

自动装箱/拆箱自动装箱过程是通过调用包装类的valueOf()方法实现的,而自动拆箱过程是通过调用包装类的xxxValue()方法实现的(xxx代表对应的基本数据类型,如intvalue()、 doubleValue()等)

自动装箱与拆箱的功能事实土是编译器来帮的忙,编译器在编译时依据您所编写的语法,决定是否进行装箱或拆箱动作,如

[ 自动装箱 ]
在这里插入图片描述
[ 自动拆箱 ]

在这里插入图片描述
所以自动装箱与拆箱的功能是所谓的 编译器蜜糖( Compiler Sugar)

比如这样写会造成空指针异常:
在这里插入图片描述
原因就是自动装箱调用的是i.intValue()方法,此时 i 为null

Integer的缓存问题
在这里插入图片描述
源码探析,当我们调用 valueOf()的时候,首先检查是否在[-128~127]之间

  • 如果是[-128 ~ 127]之间的数它会放到cache的缓存数组中,使用直接从存数组中拿出;

  • 如果不在,则创建新的Integer对象
    在这里插入图片描述
    在这里插入图片描述


【参考链接】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值