一直在使用Java,但是对于Java的面向对象的彻底理解不是很到位,初始化对象时会发生什么,向上转型和向下转型的目的是什么? 所以写下这篇文章.
面向过程
&
面向对象
面向过程的思维模式
面向过程的思维模式是简单的线性思维,思考问题首先陷入第一步做什么、第二步做什么的细节中。这
种思维模式适合处理简单的事情,比如:上厕所。
面向对象的思维模式
面向对象的思维模式说白了就是分类思维模式。思考问题首先会解决问题需要哪些分类,然后对这些分
类进行单独思考。最后,才对某个分类下的细节进行面向过程的思索。
这样就可以形成很好的协作分工。比如:设计师分了
10
个类,然后将
10
个类交给了
10
个人分别进行详细
设计和编码!
显然,面向对象适合处理复杂的问题,适合处理需要多人协作的问题!
OOP
详解
1
、什么是面向对象
Java
的编程语言是面向对象的,采用这种语言进行编程称为面向对象编程
(Object-Oriented
Programming, OOP)
。
面向对象编程的本质就是:以类的方式组织代码,以对象的组织
(
封装
)
数据。
三大特点
封装
(Encapsulation)
封装是面向对象的特征之一,是对象和类概念的主要特性。封装是把过程和数据包围起来,对数据的访
问只能通过指定的方式。
一般设定为private,通过get set方法操作数据
继承
(inheritance)
继承是一种联结类的层次模型,并且允许和支持类的重用,它提供了一种明确表述共性的方法。
新类继承了原始类后
,
新类就继承了原始类的特性,新类称为原始类的派生类
(
子类
)
,而原始类称为新
类的基类
(
父类
)
。
多态
(polymorphism)
多态性是指允许不同类的对象对同一消息作出响应。
多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。
2
、类与对象的关系
类是一种抽象的数据类型
,
它是对某一类事物整体描述
/
定义
,
但是并不能代表某一个具体的事物
.
对象是抽象概念的具体实例
例如:张三就是人的一个具体实例
,
张三家里的旺财就是狗的一个具体实例。能够体现出特点
,
展现出功能
的是具体的实例
,
而不是一个抽象的概念
.
3
、对象和引用的关系
引用
"
指向
"
对象
使用类类型、数组类型、接口类型声明出的变量
,
都可以指向对象
,
这种变量就是引用类型变量
,
简称引 用
创建与初始化对象
使用
new
关键字创建对象
使用
new
关键字创建的时候,除了分配内存空间之外,还会给 创建好的对象 进行默认的初始化 以 及对
类中构造器的调用。
1 分配内存空间 同时将实例变量自动初始化复制
2 显示赋值(如果有的话)
例如
:
显式赋值
private
String
name
=
"tom"
;
3 调用构造器
4
把对象内存地址值赋值给变量。
(
=
号赋值操作
)
构造器
类中的构造器也称为构造方法,是在进行创建对象的时候必须要调用的。并且构造器有以下俩个特点
:
1.
必须和类的名字相同
2.
必须没有返回类型
,
也不能写
void
构造器的作用
:
1.
使用
new
创建对象的时候必须使用类的构造器
2.
构造器中的代码执行后
,
可以给对象中的属性初始化赋值
构造器重载
默认有一个无参构造器,创建了有参构造器,无参自动被覆盖
除了无参构造器之外
,
很多时候我们还会使用有参构造器
,
在创建对象时候可以给属性赋值
内存分析
JAVA
程序运行的内存分析
栈
stack
:
1.
每个线程私有,不能实现线程间的共享!
2.
局部变量放置于栈中。
3.
栈是由系统自动分配,速度快!栈是一个连续的内存空间!
堆
heap
:
1.
放置
new
出来的对象!
2.
堆是一个不连续的内存空间,分配灵活,速度慢!
方法区
(
也是堆
)
:
1.
被所有线程共享!
2.
用来存放程序中永远是不变或唯一的内容。(类代码信息、静态变量、字符串常量)
![](https://img-blog.csdnimg.cn/20210821150257870.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NzI3Nzg5Nw==,size_16,color_FFFFFF,t_70)
学习完类与对象终于认识到什么是类,什么是对象了。接下来要看的就是
java
的三大特征:继承、封
装、多态。
封装
在定义一个对象的特性的时候,有必要决定这些特性的可见性,即哪些特性对外部是可见的,哪些特性
用于表示内部状态。
通常,应禁止直接访问一个对象中数据的实际表示,而应通过操作接口来访问,这称为信息隐藏
1
、封装的步骤
1.
使用
private
修饰需要封装的成员变量。
2.
提供一个公开的方法设置或者访问私有的属性
设置 通过
set
方法,命名格式:
set
属性名()
;
属性的首字母要大写
访问 通过
get
方法,命名格式:
get
属性名()
;
属性的首字母要大写
idea可以通过快捷键生成 构造器和方法
2
、作用和意义
1.
提高程序的安全性,保护数据。
2.
隐藏代码的实现细节
3.
统一用户的调用接口
4.
提高系统的可维护性
5.
便于调用者调用。
良好的封装,便于修改内部代码,提高可维护性。
良好的封装,可进行数据完整性检测,保证数据的有效性
3
、方法重载
类中有多个方法
,
有着相同的方法名
,
但是方法的参数各不相同
,
这种情况被称为方法的重载。方法的重载
可以提供方法调用的灵活性
继承
为什么需要继承?继承的作用?
第一好处:继承的本质在于抽象。类是对对象的抽象,继承是对某一批类的抽象。
第二好处:为了提高代码的复用性。
extands
的意思是
“
扩展
”
。子类是父类的扩展
1
、继承
1.
继承是类和类之间的一种关系。除此之外
,
类和类之间的关系还有依赖、组合、聚合等。
2.
继承关系的俩个类,一个为子类
(
派生类
),
一个为父类
(
基类
)
。子类继承父类
,
使用关键字
extends
来
表示。
public class student extends Person{ }
子类中继承了父类中的属性和方法后
,
在子类中能不能直接使用这些属性和方法
,
是和这些属性和方法原有
的修饰符
(public protected default private)
相关的。
例如
:
父类中的属性和方法使用
public
修饰
,
在子类中继承后
"
可以直接
"
使用
父类中的属性和方法使用
private
修饰
,
在子类中继承后
"
不可以直接
"
使用
父类中的构造器是不能被子类继承的
,
但是子类的构造器中
,
会隐式的调用父类中的无参构造器
(
默认使用
super
关键字
)
。
2
、
Object
类
java
中的每一个类都是
"
直接
"
或者
"
间接
"
的继承了
Object
类
.
所以每一个对象都和
Object
类有
"is a"
的关
系。从
API
文档中
,
可以看到任何一个类最上层的父类都是
Object
。
(Object
类本身除外
)AnyClass is a
Object
。
在
Object
类中
,
提供了一些方法被子类继承
,
那么就意味着
,
在
java
中
,
任何一个对象都可以调用这些被继承
过来的方法。
(
因为
Object
是所以类的父类
)
例如
:toString
方法、
equals
方法、
getClass
方法等
注
:Object
类中的每一个方法之后都会使用到
.
3
、
Super
关键字
子类继承父类之后
,
在子类中可以使用
this
来表示访问或调用子类中的属性或方法
,
使用
super
就表示访问
或调用父类中的属性和方法
【
super
使用的注意的地方】
1.
用
super
调用父类构造方法,必须是构造方法中的第一个语句。
2. super
只能出现在子类的方法或者构造方法中。
3. super
和
this
不能够同时调用构造方法。(因为
this
也是在构造方法的第一个语句)
4
、方法重写
方法的重写(
override
)
1.
方法重写只存在于子类和父类
(
包括直接父类和间接父类
)
之间。在同一个类中方法只能被重载,不
能被重写
.
2.
静态方法不能重写
1.
父类的静态方法不能被子类重写为非静态方法
//
编译出错
2.
父类的非静态方法不能被子类重写为静态方法;
//
编译出错
3.
子类可以定义与父类的静态方法同名的静态方法
(
但是这个不是覆盖
)
1.
重写的语法
1.
方法名必须相同
2.
参数列表必须相同
3.
访问控制修饰符可以被扩大
,
但是不能被缩小:
public protected default private
4.
抛出异常类型的范围可以被缩小
,
但是不能被扩大
ClassNotFoundException ---> Exception
5.
返回类型可以相同
,
也可以不同
,
如果不同的话
,
子类重写后的方法返回类型必须是父类方法返回
类型的子类型
例如
:父类方法的返回类型是
Person,
子类重写后的返回类可以是
Person
也可以是
Person
的
子类型
1.
总结:
方法重写的时候,必须存在继承关系。
方法重写的时候,方法名和形式参数 必须跟父类是一致的。
方法重写的时候,子类的权限修饰符必须要大于或者等于父类的权限修饰符。
( private < protected <
public
,
friendly < public )
方法重写的时候,子类的返回值类型必须小于或者等于父类的返回值类型。
(
子类
<
父类
)
数据类型没有
明确的上下级关系
方法重写的时候,子类的异常类型要小于或者等于父类的异常类型。
多态
1
、认识多态
多态性是
OOP
中的一个重要特性,主要是用来实现动态联编的,换句话说,就是程序的最终状态只有在
执行过程中才被决定而非在编译期间就决定了。这对于大型系统来说能提高系统的灵活性和扩展性。
多态可以让我们不用关心某个对象到底是什么具体类型,就可以使用该对象的某些方法,从而实现更加
灵活的编程,提高系统的可扩展性。
允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方
式。
相同类域的不同对象
,
调用相同的方法
,
执行结果是不同的
1.
一个对象的实际类型是确定的
例如
: new Student(); new Person();
等
2.
可以指向对象的引用的类型有很多
Student s1
=
new
Student
();
Person s2
=
new
Student
();
Object
s3
=
new
Student
();
1.
一个父类引用可以指向它的任何一个子类对象
Object
o
=
new
AnyClass
();
Person p
=
null
;
p
=
new
Student
();
p
=
new
Teacher
();
p
=
new
Person
();
2.
多态中的方法调用
注:子类继承父类
,
调用
a
方法,如果
a
方法在子类中没有重写
,
那么就是调用的是子类继承父类的
a
方法
,
如果重写了
,
那么调用的就是重写之后的方法。
Student s
=
new
Student
();
Person p
=
new
Student
();
变量
s
能调用的方法是
Student
中有的方法
(
包括继承过来的
),
变量
p
能调用的方法是
Person
中有的方法
(
包
括继承过来的
)
。
但是变量
p
是父类型的
,p
不仅可以指向
Student
对象
,
还可以指向
Teacher
类型对象等
,
但是变量
s
只能指
Studnet
类型对象
,
及
Student
子类型对象。变量
p
能指向对象的范围是比变量
s
大的。
Object
类型的变量
o,
能指向所有对象
,
它的范围最大
,
但是使用变量
o
能调用到的方法也是最少的
,
只能调用
到
Object
中的声明的方法
,
因为变量
o
声明的类型就是
Object.
注:
java
中的方法调用
,
是运行时动态和对象绑定的
,
不到运行的时候
,
是不知道到底哪个方法被调用的。
2
、重写、重载和多态的关系
重载是编译时多态
重写是运行时多态
3
、多态的注意事项
1.
多态是方法的多态,属性没有多态性。
2.
编写程序时,如果想调用运行时类型的方法,只能进行类型转换。不然通不过编译器的检查。但是
如果两个没有关联的类进行强制转换,会报:
ClassCastException
。 比如:本来是狗,我把它转成
猫。就会报这个异常。
3.
多态的存在要有
3
个必要条件:要有继承,要有方法重写,父类引用指向子类对象
4
、多态存在的条件
1.
有继承关系
2.
子类重写父类方法
3.
父类引用指向子类对象
那么以下三种类型的方法是没
有办法表现出多态特性的(因为不能被重写):
1. static
方法,因为被
static
修饰的方法是属于类的,而不是属于实例的
2. fifinal
方法,因为被
fifinal
修饰的方法无法被子类重写
3. private
方法和
protected
方法,前者是因为被
private
修饰的方法对子类不可见,后者是因为尽管被
protected
修饰的方法可以被子类见到,也可以被子类重写,但是它是无法被外部所引用的,一个
不能被外部引用的方法.
5、
instanceof
和类型转换
System
.
out
.
println
(
x
instanceof
Y
);
该代码能否编译通过
,
主要是看声明变量
x
的类型和
Y
是否存在子父类的关系
.
有
"
子父类关
"
系就编译通过
,
没有子父类关系就是编译报错
.
之后学习到的接口类型和这个是有点区别的。
System
.
out
.
println
(
x
instanceof
Y
);
输出结果是
true
还是
false
,
主要是看变量
x
所指向的对象实际类型是不是
Y
类型的
"
子类型
"
.
2.
类型转换
【为什么要类型转换】
//
编译报错
,
因为
p
声明的类型
Person
中没有
go
方法
Person p
=
new
Student
();
p
.
go
();
//
需要把变量
p
的类型进行转换
Person p
=
new
Student
();
Student s
=
(
Student
)
p
;
s
.
go
();
或者
//
注意这种形式前面必须要俩个小括号
((
Student
)
p
).
go
();
【总结】
1
、父类引用可以指向子类对象,子类引用不能指向父类对象。
2
、把子类对象直接赋给父类引用叫
upcasting
向上转型,向上转型不用强制转型。
如
Father father = new Son();
3
、把指向子类对象的父类引用赋给子类引用叫向下转型(
downcasting
),要强制转型。
如
father
就是一个指向子类对象的父类引用,把
father
赋给子类引用
son
即
Son son =
(
Son
)
father
;
其中
father
前面的(
Son
)必须添加,进行强制转换。
4
、
upcasting
会丢失子类特有的方法
,
但是子类overriding 父类的方法,子类方法有效
5
、向上转型的作用,减少重复代码,父类为参数,调有时用子类作为参数,就是利用了向上转型。这样
使代码变得简洁。体现了
JAVA
的抽象编程思想。
修饰符
1
、
static
修饰符
1
、
static
变量
在类中
,
使用
static
修饰的成员变量
,
就是静态变量
,
反之为非静态变量。
静态变量和非静态变量的区别
静态变量属于类的
,"
可以
"
使用类名来访问
,
非静态变量是属于对象的
,"
必须
"
使用对象来访问
静态变量对于类而言在内存中只有一个
,
能被类的所有实例所共享。实例变量对于类的每个实例都有一份
,
它们之间互不影响
在加载类的过程中为静态变量分配内存
,
实例变量在创建对象时分配内存,所以静态变量可以使用类名来
直接访问
,
而不需要使用对象来访问
.
2
、
static
方法
在类中
,
使用
static
修饰的成员方法
,
就是静态方法
,
反之为非静态方法。
静态方法
"
不可以
"
直接访问类中的非静态变量和非静态方法
,
但是
"
可以
"
直接访问类中的静态变量和静态
方法
注意
:this
和
super
在类中属于非静态的变量
.(
静态方法中不能使用)
:
为什么静态方法和非静态方法不能直接相互访问
?
加载顺序的问题!
父类的静态方法可以被子类继承
,
但是不能被子类重写
父类的非静态方法不能被子类重写为静态方法 ;
3
、代码块和静态代码块
【类中可以编写代码块和静态代码块】
public class
Person
{
{
//
代码块
(
匿名代码块
)
}
static
{
//
静态代码块
}
}
【匿名代码块和静态代码块的执行】
因为没有名字
,
在程序并不能主动调用这些代码块。
匿名代码块是在创建对象的时候自动执行的
,
并且在构造器执行之前。同时匿名代码块在每次创建对象的
时候都会自动执行
.
静态代码块是在类加载完成之后就自动执行
,
并且只执行一次
.
注
:
每个类在第一次被使用的时候就会被加载
,
并且一般只会加载一次
.
public class Person {
{
System.out.println("匿名代码块");
}
static{
System.out.println("静态代码块");
}
public Person(){
System.out.println("构造器");
}
}
main:
Student s1 = new Student();
Student s2 = new Student();
Student s3 = new Student();
4
、创建和初始化对象的过程
【
Student
类之前没有进行类加载】
1.
类加载
,
同时初始化类中静态的属性
2.
执行静态代码块
3.
分配内存空间
,
同时初始化非静态的属性
(
赋默认值
,0/false/null)
4.
调用
Student
的父类构造器
5.
对
Student
中的属性进行显示赋值
(
如果有的话
)
6.
执行匿名代码块
7.
执行构造器
8.
返回内存地址
2
、
fifinal
修饰符
1
、修饰类
用
fifinal
修饰的类不能被继承
,
没有子类
2
、修饰方法
用
fifinal
修饰的方法可以被继承
,
但是不能被子类的重写
3
、修饰变量
用
fifinal
修饰的变量表示常量
,
3
、
abstract
修饰符
abstract
修饰符可以用来修饰方法也可以修饰类
,
如果修饰方法
,
那么该方法就是抽象方法
;
如果修饰类
,
那
么该类就是抽象类。
1
、抽象类和抽象方法的关系
抽象类中可以没有抽象方法
,
但是有抽象方法的类一定要声明为抽象类。
3
、特点及作用
抽象类
,
不能使用
new
关键字来创建对象
,
它是用来让子类继承的。
抽象方法
,
只有方法的声明
,
没有方法的实现
,
它是用来让子类实现的。
接口
1
、接口的本质
普通类:只有具体实现
抽象类:具体实现和规范
(
抽象方法
)
都有!
接口:只有规范!
【为什么需要接口
?
接口和抽象类的区别
?
】
接口就是比
“
抽象类
”
还
“
抽象
”
的
“
抽象类
”
,可以更加规范的对子类进行约束。全面地专业地实现了:
规范和具体实现的分离。
抽象类还提供某些具体实现,接口不提供任何实现,接口中所有方法都是抽象方法。接口是完全面
向规范的,规定了一批类具有的公共方法规范。
从接口的实现者角度看,接口定义了可以向外部提供的服务。
从接口的调用者角度看,接口定义了实现者能提供那些服务。
接口是两个模块之间通信的标准,通信的规范。如果能把你要设计的系统之间模块之间的接口定义
好,就相当于完成了系统的设计大纲,剩下的就是添砖加瓦的具体实现了。大家在工作以后,做系
统时往往就是使用
“
面向接口
”
的思想来设计系统。
2
、接口与抽象类的区别
抽象类也是类
,
除了可以写抽象方法以及不能直接
new
对象之外
,
其他的和普通类没有什么不一样的。接
口已经另一种类型了
,
和类是有本质的区别的
,
所以不能用类的标准去衡量接口。
声明类的关键字是
class,
声明接口的关键字是
interface
。
抽象类是用来被继承的
,java
中的类是单继承。
类
A
继承了抽象类
B,
那么类
A
的对象就属于
B
类型了
,
可以使用多态
一个父类的引用
,
可以指向这个父类的任意子类对象
注
:
继承的关键字是
extends
接口是用来被类实现的
,java
中的接口可以被多实现。
类
A
实现接口
B
、
C
、
D
、
E..,
那么类
A
的对象就属于
B
、
C
、
D
、
E
等类型了
,
可以使用多态
一个接口的引用
,
可以指向这个接口的任意实现类对象
注
:
实现的关键字是
implements
3
、接口中的方法都是抽象方法
4
、接口中的变量都是静态常量
(public static fifinal
修饰
)
一般接口中不写变量 只写方法
5
、一个类可以实现多个接口
6
、一个接口可以继承多个父接口
内部类
匿名内部类
什么是匿名对象?如果一个对象只要使用一次,那么我们就是需要
new Object().method()
。 就可以
了,而不需要给这个实例保存到该类型变量中去。这就是匿名对象。
public class Test {
public static void main(String[] args) {
//讲new出来的Apple实例赋给apple变量保存起来,但是我们只需要用一次,就可以这样写
Apple apple = new Apple();
apple.eat();
//这种就叫做匿名对象的使用,不把实例保存到变量中。
new Apple().eat();
}
}
class Apple{
public void eat(){
System.out.println("我要被吃了");
}
}
匿名内部类跟匿名对象是一个道理:
匿名对象:我只需要用一次,那么我就不用声明一个该类型变量来保存对象了,
匿名内部类:我也只需要用一次,那我就不需要在类中先定义一个内部类,而是等待需要用的时候,我
就在临时实现这个内部类,因为用次数少,可能就这一次,那么这样写内部类,更方便。不然先写出一
个内部类的全部实现来,然后就调用它一次,岂不是用完之后就一直将其放在那,那就没必要那样。
1.
匿名内部类需要依托于其他类或者接口来创建
如果依托的是类
,
那么创建出来的匿名内部类就默认是这个类的子类
如果依托的是接口
,
那么创建出来的匿名内部类就默认是这个接口的实现类。
2.
匿名内部类的声明必须是在使用
new
关键字的时候
匿名内部类的声明及创建对象必须一气呵成
,
并且之后能反复使用
,
因为没有名字。
main
:
A a
=
new
A
(){
//
实现
A
中的抽象方法
//
或者重写
A
中的普通方法
};
注
:
这个大括号里面其实就是这个内部类的代码
,
只不过是声明该内部类的同时就是要
new
创建了其对象
,
并且不能反复使用
,
因为没有名字。
例如
:
B
是一个接口,依托于
B
接口创建一个匿名内部类对象
B b
=
new
B
(){
//
实现
B
中的抽象方法
};
public static void main(String[] args) {
// 通过一个类的方式匿名内部类
//实现A中的抽象方法
// 或者重写A中的普通方法
new Persion() {
@Override
public void st() {
System.out.println("234");
}
}.st(); // 可以直接在后面调用方法
// 通过接口实现匿名内部类 也可以创造一个对象调用,推荐使用直接在后面调用
Car car = new Car() {
@Override
public void run() {
System.out.println("汽车也可以实现匿名内部类");
}
};
car.run();
1.
匿名内部类除了依托的类或接口之外
,
不能指定继承或者实现其他类或接口
,
同时也不能被其他类所
继承
,
因为没有名字。
2.
匿名内部中
,
我们不能写出其构造器
,
因为没有名字。
3.
匿名内部中
,
除了重写上面的方法外
,
一般不会再写其他独有的方法
,
因为从外部不能直接调用到。
(
间
接是调用到的
)
只能使用这一次,我们知道了这是一个类, 将其
new
出来,就能获得一个实现了
Test1
接口的类的实例
对象,通过该实例对象,就能调用该类中的方法了,因为其匿名类是在一个类中实现的,