摘要:本篇blog主要讨论java面向对象基础,包括类与对象、封装、继承、多态、抽象类与接口等,同时初步分析了JVM内存机制
面向对象思想
1、面向对象VS面向过程
面向过程思想:
步骤清晰:第一步、第二步…..
面向过程适合处理一些简单问题
面向对象思想:
- 物以类聚,分类的思维:思考问题首先解决:应该分哪些类,然后对分类单独思考。最后才对某个分类下的细节进行面向过程思考。
- 适合处理复杂问题,适合处理多人协作问题
面向过程:从整体上把事物分为许多类,对每个类的某个具体方法进行面向过程分析
2、OOP(Object-Oriented Programming)
OOP的本质:以类的方式组织代码,以对象的方式组织数据
OO类的核心是抽象,把许多个体的相似特征提取出来,成为类,每个个体则是实例
先有对象、后有类
3、三大特性
封装:把一些方法、属性隐藏起来,暴露特定方法供外界使用
继承:子类继承父类,获得父类的方法和属性
多态:同一个方法,不同的实现
类与对象
1、类与对象的关系
类是一类抽象的数据类型,它是对某一类事物整体的描述/定义,但是不能代表某一个具体的事物
- 动物、植物
- Person、Pet类etc
对象是类抽象概念的具体实例
- 张三是人的实例,张三的狗是宠物的实例
类是对象的模板,对象是具体的表现
2、构造器
非静态方法可以访问类的非静态成员,但不能访问其他非静态方法中的成员
所以构造器中一定是对现有成员初始化,因为在构造器中新声明一个变量没意义,其他方法也用不了
要写带参数构造一定要先补上无参构造!!!
构造器格式
- 使用new关键字创建对象
- new关键字本质是在调用构造器
使用new关键字创建的时候,除了分配内存空间外,还会给创建好的对象进行默认的初始化以及对类中构造器的调用
类中的构造器也称为构造方法,作用是创建对象,即创建对象时必须调用构造器,构造器有以下特点:
- 必须和类的名字相同
- 必须没有返回类型,也不能写void
- 如果定义类时没有显示定义构造器,java会自动为类创建一个默认构造器
- 一旦定义了有参构造器,就必须显示声明无参构造,否则不能调用无参构造(一旦定义有参构造,会默认干掉无参构造)
构造器的作用
- 初始化实例属性
1 | public Person(String name){ |
3、内存分析(简易版)
一个java程序在运行的过程中会涉及以下内存区域
栈:保存局部变量的值
1、保存基本数据类型
2、保存对象的引用(指针)
堆:存放动态产生的数据,例如new出来的对象
注意创建出来的对象只包含各自的成员变量,并不包含成员方法。方法在方法区中,当对象需要调用方法时就去方法区中调用
1、java方法区:存放运行时需要加载的类(方法区不一定在堆中)
2、常量池:每个类中都有各自的常量池,常量池是这个类用到的常量的有序集合,包含直接常量(基本类型、String)和其他类型、方法、字段的符号引用
关于普通变量和引用类型的变量,都可以作为局部变量。局部变量保存在栈中,区别在于:
1、普通变量:栈中直接保存它的值
2、引用变量:栈中保存一个指向堆区对象的指针
因此,普通类型变量只占用一块内存VS引用类型变量占两块变量
内存变化过程:
1 | public class Application{ |
1、首先加载Application类到方法区
2、然后把main方法入栈
3、new→加载Pet类到方法区,加载完后
- Pet dog :声明了Pet类型的引用变量,放在栈中
- new Pet():在堆中创建了一个Pet类的实例,为其分配空间
- 在实例化的同时,就为其属性分配了默认值(定义中没声明的话就是null、0之类的)
4、把main常量池中的“wangcai”赋给堆中dog的name属性
5、创建cat引用变量和实例
静态方法区:和类一起加载,只要类还在,就可以调用,而对象方法,必须有实例才能调用
变量:除了基本类型都是引用类型,引用变量在栈中,存放的是堆中的实际地址
封装
1、“高内聚、低耦合”
高内聚:高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;
低耦合:暴露少量的方法给外部使用
2、封装(信息隐藏)
禁止访问一个对象中数据的实际表示,而应该通过操作接口来访问,这称为信息隐藏
3、属性private,get&set方法
通过在set方法中添加逻辑,防止数据被任意修改
4、封装的意义
- 提高程序的安全性,保护数据
- 隐藏代码的实现细节
- 统一接口
- 提高系统的可维护性
继承
1、继承的概念
继承的本质是对某一批类的抽象,抽象出父类
2、extends关键字
意味子类是父类的扩展
3、继承详解
继承是类和类之间的一种关系,除此之外,类和类之间的关系还有依赖、组合、聚合等
子类继承父类,使用extends关键字
继承本质是 “ is a ”
java中,所有类都默认继承Object类
父类中private修饰的,子类不能直接调用或访问,可以通过方法访问。而其他修饰的可以
不写的话默认是default
public > protected > default > private
4、java只有单继承
5、关于static
1 | System.out.println(Student.nation); |
经测试,类变量可以继承,也可以修改。如果修改了父类的变量,则子类的类变量也会改变。(如果类变量是private,则可以通过方法修改)
6、super&构造器(!!)
a、关于成员方法和类方法之间调用的问题:
方法体中:
成员方法可以互相调用(不用this关键字,用也可以)
成员方法可以直接调用成员变量(不用this关键字,用也可以)
成员方法可以调用类方法,修改类变量
类方法不能直接调用成员方法,除非初始化一个实例。
外界:
外界可以通过实例来调用类方法,不能通过类来调用实例方法
因为类方法加载的时候,成员方法还没加载
成员方法加载的时候,类已经加载过了
1
2
3
4
5
6
7
8
9 class Student{
int money = 10;
private void func1(){}
public void func2(){
func(1);
this.func(1);
System.out.println(money);
}
}
b、super关键字详解
super关键字的作用是调用父类被子类重写的方法、属性(如果子类没有重写父类方法的话,不用super关键字也可)
- 子类可以直接调用父类成员方法,不用初始化一个父类实例,因为“继承”,子类具有了父类的方法,所以相当于还是在调自己的成员方法。
- 类方法就不用说了,直接调
1 | class Person{ |
1 | class Student{ |
c、关于private
如果父类方法是private修饰的,那么子类无论如何也不能调用,即:
$$
\large 父类私有的不能被子类继承
$$
d、构造器详解
1 | //super()方法调用父类构造器 |
1 | class Person{ |
运行以上代码可以得到结果:
这是父类的构造器
这是子类的构造器
即,初始化子类对象时,会默认调用父类构造器。
原理是在子类构造器中,有一个默认方法 super();
1 | //子类构造器的真实样子如下: |
补充
子类也有自己的构造器 this();这个关键字代表无参构造器
子类可以在有参构造器中调用无参构造器,但不会默认调用
1 | class Studentt{ |
另注:
- 构造器方法,无论是super还是this,都必须在其他的构造器方法中的第一个
- super(),this() 只能用一个
- this方法都可以用,super方法必须在继承关系中使用
如果父类没有无参构造(造成这种情况通常是父类重载了有参构造后没有显示声明无参构造),子类构造器也必须至少有同样的参数(当然可以更多)。
1 | public static void main(String[] args) { |
多态
1、重写
重写特指对方法的重写,与属性无关
a、静态方法与非静态方法
非静态方法才能重写有效,静态方法重写无效(即可以重写,但调用时看是父类还是子类
1 | class B{ |
b、规定
- 方法名必须相同
- 参数列表必须相同
- 修饰符:范围可以放大 例如private可以扩大为public,反之不可。
- 抛出的异常:范围可以被缩小,但不能放大
一句话:子类重写父类,方法名相同,方法体不同
为什么要重写:
- 父类的功能,子类不一定需要或者子类不一定满足
2、多态
含义:同一方法根据发送对象的不同而采用多种不同的行为方式
一个对象的实际类型是确定的,但可以指向对象的引用类型有很多
使得程序能动态编译,提高可扩展性
a、方法执行限制:父类引用指向子类对象
非静态方法:
子类父类都有的方法,对于不同引用而言,都可以调用:重写了就都执行重写的,没重写就都用父类的
如果是子类独有的方法,父类引用则无法调用,必须转换类型为子类
- 对象能执行哪些类型,主要看对象左边的类型,和右边关系不大
1 | class Person{ |
即:
子类型能调用自己的和继承父类的方法
父类型只能调用父类的方法,如果被子类重写了,就调用重写的方法
b、注意事项
多态是方法的多态,属性没有多态
父类和子类必须要有联系,如果类型无关,不能转换(String转Person不是扯嘛):类型转换异常
存在继承关系,方法需要重写
父类引用指向子类对象
$$
Father \quad f1 = new \quad Son()
$$
c、不能被重写的:
1、static方法,属于类,不属于实例。即父类引用指向子类实例的情况下,也是执行父类的方法体
2、final方法:final方法可以被重载,不能被重写
3、private方法
3、instanceof——类型转换
a、instanceof
1 | class Person{} |
instanceof 关键字 返回boolean:
A instanceof B,如果实例A是B类或B的子类,返回true。与A的引用无关,考察A实例本身
b、类型转换
父类可以直接转子类,子类则需要强制转父类
人可以直接转学生
1 | //父类引用指向子类对象 |
Static 补充:
所有非静态的成员(变量、方法、代码块etc)都随着对象创建时创建
而静态成员,随着类一起创建,放在静态方法区中(?)
关于代码块:
1 | class Person{ |
抽象类
1、抽象类的概念
抽象类作为框架,亦或是一种约束(子类必须实现),只需起到提纲挈领作用即可
1 | public abstract class Action { |
抽象类的子类,必须要实现所有抽象方法(除非子类也是抽象类)
2、抽象类的局限性
抽象类本质也是类,只能被单继承,而有的类确实需要多继承,则通过接口实现
3、抽象类规则
a、不能new抽象类,只能靠子类实现
b、抽象类只能写普通的方法
c、抽象方法必须写在抽象类中
例如:考虑做一个穿越火线枪械数据小程序,如果每出一把枪都重做类,很麻烦,可以抽象出m4类,ak类,81类,然后,每新出一把枪,都继承,然后重写方法即可,提高开发效率
接口
0、Instructions
普通类:只有具体实现
抽象类:具体实现和规范(抽象方法)都有
接口:只有规范,没有实现。约束和实现分离:面向接口编程
接口就是规范,定义了一组规则。即:如果你是…你就必须能…
“如果你是天使,你就必须能飞”
接口的本质是契约,就像法律一样,制定好后大家都必须遵守
1、规则
1 | public interface UserService { |
接口间接实现了多继承!
重写(override)不是重载(overload),重写是多态
2、意义
- 作为约束
- 定义一些方法,让不同的人实现
注:
a、接口不能实例化,接口没有构造方法
b、可以实现多个接口
c、必须要重写接口中的方法
3、接口多态
接口变量(接口引用)指向实现类的实例
1 | USB usb = new Mouse(); |
接口的意义:
内部类
内部类就是在一个类的内部定义一个类,比如在A类内部定义一个B类、B类相对于A类来说就是内部类,而A类对B类就是外部类
1 | class Outer{ |
1 | public class Outer { |
1 | public class A{ |