Java 面向对象
面向对象
要理解面向对象思想,我们先要知道什么是对象?
《Java程序语言》中可以给“对象”下一个定义,即对象是现实世界中的一个实体,它具有如下特征:有一个状态用来描述它的某些特征。有一组操作,每个操作决定对象的种功能或行为。
因此,对象是其自身所具有的状态特征及可以对这些状态施加的操作结合在一起所构
成的实体。一个对象可以非常简单,也可以非常复杂。复杂的对象往往是由若干个简单对
象组合而成的。例如,一辆汽车就是由发动机、轮胎、车身等许多其他对象组成。
三大特征
封装:核心思想就是“隐藏细节”、“数据安全”,将对象不需要让外界访问的成员变量和方法私有化,只提供符合开发者意愿的公有方法来访问这些数据和逻辑,保证了数据的安全和程序的稳定。所有的内容对外部不可见。
继承:子类可以继承父类的属性和方法,并对其进行拓展。将其他的功能继承下来继续发展 。
多态:同一种类型的对象执行同一个方法时可以表现出不同的行为特征。通过继承的上下转型、接口的回调以及方法的重写和重载可以实现多态。方法的重载本身就是一个多态性的体现。
三大思想
面向对象思想从概念上讲分为以下三种:OOA、OOD、OOP
OOA:面向对象分析(Object Oriented Analysis)
OOD:面向对象设计(Object Oriented Design)
OOP:面向对象程序(Object Oriented Programming )
类与对象
类表示一个共性的产物,是一个综合的特征,而对象,是一个个性的产物,是一个个体的特征。 (类似生活中的图纸与实物的概念。)
类必须通过对象才可以使用,对象的所有操作都在类中定义。
类由属性和方法组成:
属性:就相当于人的一个个的特征
方法:就相当于人的一个个的行为,例如:说话、吃饭、唱歌、睡觉
一个类要想真正的进行操作,则必须依靠对象,对象的定义格式如下:
类名称 对象名称 = new 类名称() ;
如果要想访问类中的属性或方法(方法的定义),则可以依靠以下的语法形式:
访问类中的属性: 对象.属性 ;
调用类中的方法: 对象.方法(实际参数列表) ;
类必须编写在.java文件中;
一个.java文件中,可以存在N个类,但是只能存在一个public修饰的类;
.java文件的文件名必须与public修饰的类名完全一直;
同一个包中不能有重名的类;
匿名对象
没有对象名称的对象就是匿名对象。 即栈内存中没有名字,而堆内存中有对象。
匿名对象只能使用一次,因为没有任何的对象引用,所以将称为垃圾,等待被GC回收。
只使用一次的对象可以通过匿名对象的方式完成,这一点在以后的开发中将经常使用到。
创建对象的内存分析
栈(stack)
Java栈的区域很小 , 大概2m左右 , 特点是存取的速度特别快
栈存储的特点是:先进后出
存储速度快的原因:
栈内存, 通过 ‘栈指针’ 来创建空间与释放空间 !
指针向下移动, 会创建新的内存, 向上移动, 会释放这些内存 !
这种方式速度特别快 , 仅次于PC寄存器 !
但是这种移动的方式, 必须要明确移动的大小与范围 ,
明确大小与范围是为了方便指针的移动 , 这是一个对于数据存储的限制, 存储的数据大小是固定的 , 影响了程序 的灵活性 ~
所以我们把更大部分的数据存储到了堆内存中
堆存储的是:
基本数据类型的数据以及引用数据类型的引用!
堆在逻辑上分为三部分:
新生代(Young Generation,常称为YoungGen)
老年代(Old Generation,常称为OldGen、TenuringGen)
永久代(Permanent Generation,常称为PermGen)
新生区(New/Young Generation):
新生代(Young Generation),常称为YoungGen,位于堆空间。
新生区又分为Eden区和Survior(幸存区)。
Eden:新创建的对象
Survior 0、1:经过垃圾回收,但是垃圾回收次数小于15次的对象。
养老区(Old Generation):
老年代常称为OldGen,位于堆空间
Old:垃圾回收次数超过15次,依然存活的对象。
永久区(Permanent Generation):
永久代常称为PermGen,位于非堆空间。
永久区是一个常驻内存区域,用于存放JDK自身所携带的Class,Interface的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭JVM才会释放此区域所占用的空间。
方法区
方法区(Method Area),又称永久代,又称非堆区(Non-Heap space)
方法区是被所有线程共享:
所有的字段和方法字节码,以及一些特殊方法如构造函数,接口代码也再此定义。
简单说,所有定义的方法的信息都保存在该区域,此区属于共享区间。
这些区域储存的是:静态变量+常量+类信息(构造方法/接口定义)+运行时常量池。
但是,实例变量存在堆内存中,和方法区无关。
以上,只是逻辑上的定义。在HotSpot中,方法区仅仅只是逻辑上的独立,实际上还是包含在java堆中,也就是说,方法区在物理上属于java堆区中的一部分,而永久区(Permanent Generation)就是方法区的实现。
存放的是
- 类信息
- 静态的变量
- 常量
- 成员方法
方法区中包含了一个特殊的区域 ( 常量池 )(存储的是使用static修饰的成员)
方法区的实现的演变
jdk1.7之前:hotspot虚拟机对方法区的实现为永久代。
jdk1.8及之后:hotspot移除了永久代用元空间(Metaspace)。
运行时 常量池和 字符串常量池的变化
jdk1.7之前:运行时常量池(包含字符串常量池)存放在方法区,此时hotspot虚拟机对方法区的实现为永久代。
jdk1.7:字符串常量池被方法区拿到了堆中;运行时常量池剩下的东西还在方法区,也就是hotspot中的永久代。
jdk1.8:hotspot移除了永久代,用元空间(Metaspace)取而代之。这时候,字符串常量池还在堆中,运行时常量池还在方法区,只不过方法区的实现从永久代变成元空间(Metaspace)。
PC寄存器
PC寄存器保存的是当前正在执行的 JVM指令的 地址 ;
在Java程序中, 每个线程启动时, 都会创建一个PC寄存器 ;
本地方法栈
保存本地(native)方法的地址
内部类
在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。
广泛意义上的内部类一般来说包括这四种:
1、成员内部类
2、局部内部类
3、匿名内部类
4、静态内部类
基本数据类型和包装类型的区别
1、包装类是对象,拥有方法和字段,对象的调用都是通过引用对象的地址,基本类型不是
2、包装类型是引用的传递,基本类型是值的传递
3、声明方式不同,基本数据类型不需要new关键字,而包装类型需要new在堆内存中进行new来分配内存空间
4、存储位置不同,基本数据类型直接将值保存在值栈中,而包装类型是把对象放在堆中,然后通过对象的引用来调用他们
5、初始值不同,eg: int的初始值为 0 、 boolean的初始值为false 而包装类型的初始值为null
6、使用方式不同,基本数据类型直接赋值使用就好 ,而包装类型是在集合如 coolection Map时会使用
常见问题
1、 抽象类能否使用final声明?
不能,因为final属修饰的类是不能有子类的 , 而抽象类必须有子类才有意义,所以不能。
2、 抽象类能否有构造方法?
能有构造方法,而且子类对象实例化的时候的流程与普通类的继承是一样的,都是要先调用父类中的构造方法(默认是无参的),之后再调用子类自己的构造方法。
抽象类和普通类的区别
1、抽象类必须用public或protected修饰(如果为private修饰,那么子类则无法继承,也就无法实现其抽象方法)。 默认缺省为 public ;
2、抽象类不可以使用new关键字创建对象, 但是在子类创建对象时, 抽象父类也会被JVM实例化 ;
3、如果一个子类继承抽象类,那么必须实现其所有的抽象方法。如果有未实现的抽象方法,那么子类也必须定义为 abstract类 ;
接口
如果一个类中的全部方法都是抽象方法,全部属性都是全局常量,那么此时就可以将这个类定义成一个接口。
面向接口编程思想
这种思想是接口是定义(规范,约束)与实现(名实分离的原则)的分离。
优点:
1、 降低程序的耦合性
2、 易于程序的扩展
3、 有利于程序的维护
接口的继承 extends
继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实力域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
继承的限制:Java中只有单继承,多重继承,没有多继承(即一个子类只能有一个父类)。多重继承通俗来讲就是爷爷、爸爸、孙子。
supper
通过supper可以访问父类的构造方法、属性、方法。
通过supper调用父类构造方法的代码,必须写在第一行。
supper和this调用构造函数时都需要放在第一行,但是两者不会同时使用,因为不可能调用自身构造函数的同时还调用父类的构造方法
接口与抽象类的区别
1、抽象类要被子类继承,接口要被类实现。
2、接口只能声明抽象方法,抽象类中可以声明抽象方法,也可以写非抽象方法。
3、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
4、抽象类使用继承来使用, 无法多继承。 接口使用实现来使用, 可以多实现
5、抽象类中可以包含static方法 ,但是接口中不允许(静态方法不能被子类重写,因此接口中不能声明静态方法)
6、接口不能有构造方法,但是抽象类可以有
7、1.8后接口允许出现有方法体的方法
多态
多态:就是对象的多种表现形式,(多种体现形态)
多态的体现
对象的多态性,从概念上非常好理解,在类中有子类和父类之分,子类就是父类的一种形态 ,对象多态性就从此而来。
ps: 方法的重载 和 重写 也是多态的一种, 不过是方法的多态(相同方法名的多种形态)。
重载: 一个类中方法的多态性体现 。
重写: 子父类中方法的多态性体现。
多态的使用:对象的类型转换
类似于基本数据类型的转换:
向上转型:将子类实例变为父类实例 |- 格式:父类 父类对象 = 子类实例 ;
向下转型:将父类实例变为子类实例 |- 格式:子类 子类对象 = (子类)父类实例 ;
注意:向上转型的对象,是通过父类调用子类覆盖或继承父类的方法,不是父类的方法。而且此时父类对象不能调用子类特有的方法。
Instanceof
作用:
判断某个对象是否是指定类的实例,则可以使用instanceof关键字
格式:
实例化对象 instanceof 类 //此操作返回boolean类型的数据
Object类
Object类是所有类的父类(基类),如果一个类没有明确的继承某一个具体的类,则将默认继承Object类。
toString()
建议重写Object中的toString方法。 此方法的作用:返回对象的字符串表示形式 ;
Object的toString方法, 返回对象的内存地址 ;
System.out.println(对象名)一般输出时调用的时对象的toString方法 ;
equals()
建议重写Object中的equals(Object obj)方法,此方法的作用:指示某个其他对象是否“等于”此对象。
***Object的 equals方法:***实现了对象上最具区别的可能等价关系; 也就是说,对于任何非空引用值x和y ,当且仅当 x和y引用同一对象
( x == y具有值true )时,此方法返回true 。
equals方法重写时的五个特性:
自反性 :对于任何非空的参考值x , x.equals(x)应该返回true 。
对称性 :对于任何非空引用值x和y , x.equals(y)应该返回true当且仅当y.equals(x)回报true 。
传递性 :对于任何非空引用值x , y和z ,如果x.equals(y)回报true个y.equals(z)回报true ,然后 x.equals(z)应该返回true 。
一致性 :对于任何非空引用值x和y ,多次调用x.equals(y)始终返回true或始终返回false ,前提是未修改对象上的equals比较中使用的信息。
非空性 :对于任何非空的参考值x , x.equals(null)应该返回false 。
构造方法(构造器)
Person p = new Person();
在右侧Person后面出现的小括号, 其实就是在调用构造方法
作用:
用于对象初始化。
执行时机:
在创建对象时,自动调用
特点:
所有的Java类中都会至少存在一个构造方法
如果一个类中没有明确的编写构造方法, 则编译器会自动生成一个无参的构造方法, 构造方法中没有任何的代
码!
如果自行编写了任意一个构造器, 则编译器不会再自动生成无参的构造方法。
定义的格式 :
与普通方法基本相同, 区别在于: 方法名称必须与类名相同, 没有返回值类型的声明 ;
建议自定义无参构造方法,不要对编译器形成依赖,避免错误发生。
当类中有非常量成员变量时,建议提供两个版本的构造方法,一个是无参构造方法,一个是全属性做参数的构造方法。
当类中所有成员变量都是常量或者没有成员变量时,建议不提供任何版本的构造。
重载(Overload)
方法的重载
方法名称相同, 参数类型或参数长度不同或顺序不同, 可以完成方法的重载 ;
方法的重载与返回值无关;
方法的重载 ,可以让我们在不同的需求下, 通过传递不同的参数调用方法来完成具体的功能。
构造方法的重载
一个类, 可以存在多个构造方法 ;
参数列表的长度或类型不同即可完成构造方法的重载 ;
构造方法的重载 ,可以让我们在不同的创建对象的需求下, 调用不同的方法来完成对象的初始化 ;
重写(Override)
参数列表必须完全与被重写的方法相同;
返回类型必须完全与被重写的返回类型相同;
访问权限不能比父类被重写的方法的访问权限更低。例如父类方法为public,子类就不能为protected;
父类的成员方法只能被它的子类继承;
声明为static和private的方法不能被重写,但是能够被再次声明;
重写与重载的区别
-
重写方法名返回值相同参数相同;
-
重载方法名相同返回值相同参数可以不同,个数可以不同;
-
重写发生在父子类中,重载发生在一个类中;
-
重载与访问权限无关 ;
-
异常处理:重载与异常无关 ; 重写异常范围可以更小,但是不能抛出新的异常 ;
JVM 可以理解成一个可运行 Java 字节码的虚拟计算机系统
它有一个解释器组件,可以实现 Java 字节码和计算机操作系统之间的通信
对于不同的运行平台,有不同 的 JVM。
JVM 屏蔽了底层运行平台的差别,实现了“一次编译,随处运行”。
垃圾回收器(Garbage Collection) GC
不再使用的内存空间应当进行回收-垃圾回收。
在 C/C++等语言中,由程序员负责回收无用内存。
Java 语言消除了程序员回收无用内存空间的责任:
JVM 提供了一种系统线程跟踪存储空间的分配情况。并在 JVM 的空闲时,检查并释放那些可以被释放的存储空间。
垃圾回收器在 Java 程序运行过程中自动启用,程序员无法精确控制和干预。
static
概述
static表示“静态”的意思,可以用来修饰成员变量和成员方法(后续还会学习 静态代码块 和 静态内部类)。 static的主要作用在于创建独立于具体对象的域变量或者方法 。
简单理解:
被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。 并且不会因为对象的多次创建 而在内存中建立多份数据 。
重点 :
静态成员 在类加载时加载并初始化 ;
无论一个类存在多少个对象 , 静态的属性, 永远在内存中只有一份( 可以理解为所有对象公用 ) ;
在访问时: 静态不能访问非静态 , 非静态可以访问静态 ;
静态修饰的方法,被调用时,有可能对象还未创建 ;
final
final用于修饰属性(类里定义的标识符称为属性)和变量(方法体里定义的标识符成为变量:
通过final修饰的属性和变量都是常量,就是不能再次赋值的变量或属性 ;
final修饰的局部变量,只能赋值一次(可以先声明后赋值);
final修饰的成员属性,必须在声明时赋值 ;
全局常量(public static final)可以在任何位置被访问 ;
常量的命名规范:由一个或多个单词组成,单词之间必须使用下划线隔开,所有字母大写,例如:SQL_INSERT ;
final用于修饰类:
final修饰的类,不能被继承。
final用于修饰方法:
final修饰的方法,不能被子类重写。
代码块
普通代码块
在执行的流程中 出现的 代码块, 我们称其为普通代码块。
构造代码块
在类中的成员代码块, 我们称其为构造代码块, 在每次对象创建时执行, 执行在构造方法之前。
静态代码块
在类中使用static修饰的成员代码块, 我们称其为静态代码块, 在类加载时执行。 每次程序启动到关闭 ,只会 执行一次的代码块。
同步代码块
在后续多线程技术中学习。
面试题:
构造方法 与 构造代码块 以及 静态代码块的执行顺序:
静态代码块 --> 构造代码块 --> 构造方法
包
把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
包如同文件夹一样,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。
包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。
包的使用规则
- 包中java文件的定义:
在.java文件的首部, 必须编写类所属哪个包, 格式:
package 包名;
- 包的定义:
通常由多个单词组成, 所有单词的字母小写, 单词与单词之间使用.隔开 ,一般命名为“com.公司名.项目名.模块名…”。
规范由来:
由于Java面向对象的特性,每名Java开发人员都可以编写属于自己的Java Package,为了保障每个Java Package命名的唯一性,在最新的Java编程规范中,要求开发人员在自己定义的包名前加上唯一的前缀。由于互联网上的域名称是不会重复的,所以多数开发人员采用自己公司在互联网上的域名称作为自己程序包的唯一前缀。