Java基础|Java基础 - 面向对象

面向对象

对象实例化过程
  • 看方法区是否存在该对象类型,没有就使用类加载器去加载
  • 在栈中创建对象类型的变量,在堆中开辟内存空间创建对象,变量保存了指向对象的内存地址,两者是引用关系.
对象的生命周期
  • new 的时候创建对象.
  • 当没有任何变量引用该对象时,该对象就变成了垃圾对象,等待GC的回收,这时我们也不能再使用到该对象.
  • GC回收并不是即时发生的,取决于jvm的调度,程序员无法控制.
匿名对象
  • new创建后没有定义变量来引用的对象 如:new Test();
  • 只在堆中开辟内存空间, 在栈中不存在对它的引用
  • 使用一次之后就变成垃圾对象. 当希望某个对象只使用一次时,就可以创建匿名对象.
static修饰的类成员
  • 随着类的加载而加载(生命周期和类一样长)
  • 优先于对象存在,当字节码文件被加载进jvm的时候就存在,对象是后面new出来的.
  • 被所有对象共享
  • 可以直接被类名调用
    使用:
  • 利处:对对象的共享数据进行单独空间的存储,节省空间,没有必要每一个对象中都存储一份,可以直接被类名调用。
  • 弊端:生命周期过长
深入变量
分类
成员变量
  • 直接定义在类里面的变量,又称为全局变量或者字段
  • 初始化:基础数据类型统统是0,引用类型是null,boolean类型是false
局部变量
  • 除了全局变量,其他的都是局部变量
  • 根据位置划分为:方法形参,方法内变量,代码块变量。
  • 局部变量没有初始值,所以使用前要显示给初始化。
变量的作用域和生命周期
作用域:
  • 成员变量:在类中声明,作用域是整个类。
  • 局部变量:作用域是定义开始到定义所在的花括号结束
生命周期:
变量的生命周期指的是一个变量被创建并分配内存空间开始,到该变量被销毁并清除其所占内存空间的过程。
**类变量** -> 类中定义, 被static修饰 -> 从字节码加载开始,字节码被卸载时结束.**实例变量** -> 类中定义, 无static修饰 -> new对象开始,对象被销毁时结束**局部变量** -> 定义在方法,代码块,方法形参 -> 所在方法被调用开始,方法调用结束时结束

局部变量的初始化和在JVM中的运行机制
  • 局部变量定义后,必须显示初始化后才能使用,
    直到程序为这个变量初始化赋值时,系统才会为局部变量分配内存,并将初始值保存到该栈帧内存中。
  • 基本数据局部变量:直接把这个变量的值保存到该变量所对应的栈内存中。
  • 引用数据局部变量:这个变量在栈内存中存的是堆中的一个地址,地址指向堆中的对象,变量可以通过地址引用该对象。
    内存中的局部变量无需系统垃圾回收,其往往随方法或代码块的运行结束而结束。
修饰符
访问权限修饰符:
  1. private(类私有):本类内部可以访问,不能被子类继承。
  1. 无(包私有):本类内部可以访问,同包其他类也可以访问,能被同包的子类继承。
  1. protected(包和子类私有):本类内部可以访问,不同包的子类也可以访问,同包其他类也可以访问,能被子类所继承。
  1. public(公共):任何地方都可以访问,能继承到子类。
    注意:类的访问修饰符只有 public和缺省,即public class A{}或者class A{}
    接口的访问修饰符只有public或缺省
final 修饰符:
  • 用在类前,表示此类不能被继承
  • 用在方法前,表示此方法不能被覆写
  • 用在变量,表示此变量是常量,不可改变,且变量名要全部大写,单词间用_隔开
    修饰基本类型变量:表示该变量的值不能改变,即不能用“=”号重新赋值。
    修饰引用类型变量:表示该变量的引用的地址不能变,引用地址里的内容可以改,例如数组可以用ARR[索引]来改变。
abstract修饰符
  • 用在类前,表示此类是抽象类
  • 2.用在方法,表示此类是抽象方法.
static 修饰符
  • 表示是静态成员,可以用类名直接调用,
  • 只能访问类成员,不能访问对象成员;
  • 被所有对象共享;
面向对象-继承 子类继承了:
  • 1):SubClass类能继承SuperClass类中的public和protected成员(字段、方法、内部类)。静态方法也可以被继承,只是不能被复写
  • 2):当SubClass和SuperClass类是位于同一包中,SubClass类会继承SuperClass类中的默认访问权限成员(缺省)。
  • 3):私有成员、构造器,绝对继承不到。
    Java只支持单继承,不支持多继承,但是允许多重继承,java.lang.Object是所有类(除了Object)的根类,Object要么是直接父类要么是间接父类。
方法重写
企鹅(Penguin)和鸵鸟(Ostrich)是鸟类中的一个特殊品种,所以企鹅/鸵鸟类是鸟类的一个子类,但是鸟类有飞翔的功能,但是对应企鹅/鸵鸟,飞翔的行为显然不适合于它。
此时就要进行方法的覆写或者重写,意思就是在子类中重新编写方法体
重写原则:
建立在能继承的基础上,不能继承的方法不能改写!

  • ① 实例方法签名必须相同。 (方法签名= 方法名 + 方法的参数列表)
  • ② 子类方法的返回值类型是父类方法的返回值类型的子类或相同类。
  • ③ 子类方法声明抛出的异常应比父类方法声明抛出的异常更小或相等。
    子类方法中声明抛出的异常小于或等于父类方法声明抛出异常类型;
    子类方法可以同时声明抛出多个属于父类方法声明抛出异常类的子类(RuntimeException类型除外);
  • ④ 子类方法的访问权限比父类方法访问权限更大或相等。
    判断是否是覆写方法的必杀技:@Override标签:若方法是覆写方法,在方法前或上贴上该注解, 编译通过表示覆写成功,否则,编译报错。
super关键字
  • ① 在子类中访问父类的被隐藏的字段和被覆写的方法。
  • ② 在子类的构造方法中,可使用super语句调用该类的父类的指定构造器。super(参数)
  • 如果父类没有无参构造,则必须使用super手动调用父类的构造器,否则编译不能通过
this关键字
  • ① 解决成员变量和方法参数之间的二义性,必须使用(比如setter方法中);
  • ② 同类中实例方法间互调 this.getName(); 在本类找时,this可以忽略
  • ③ 讲当前对象作为方法的返回值(链式方法编程);
  • ④ 构造器重载的互调,在当前构造器中调用当前重载的构造器,this([参数])必须写在构造方法第一行,也表示只能有一个this(参数);
  • ⑤ static不能和this一起使用,static的成员在类加载到JVM就存在了,而this表示的是堆中的对象,该对象还不存在;
隐藏(小结)
  • 满足继承的访问权限下,隐藏父类字段:若子类中定义的字段和父类中的字段名相同(不管类型),此时就是隐藏父类字段,此时只能通过super访问被隐藏的字段。
  • 隐藏本类字段:若同类中某局部变量名和字段名相同,此时就是隐藏本类字段,此时只能通过this访问被隐藏的字段。
  • 满足继承的访问权限下,隐藏父类静态方法:若子类定义的静态方法的签名和父类中的静态方法签名相同,那么此时就是隐藏父类静态方法。
面向对象-多态
编译类型和运行类型
如:Animal a = new Dog();
此时对象a的类型有两种:
  • 编译时类型:声明该变量时使用的类型,表示把对象看做是什么类型:Animal
  • 运行时类型:创建对象的类型——对象的真正类型,表示对象实际上是什么类型:Dog
多态的特点
Animal a = null;
a = new Cat(); //此时a表示猫
a = new Dog(); //此时a表示狗
可以看出对象a会出现多种形态的情况。此时我们把这种父类指向了子类对象的引用的现象称为多态。

  1. 用父类的类型来声明变量,并引用了子类的对象,在运行时体现出了子类的特征.
  1. 用接口的类型来声明变量,并引用了实现类的对象,在运行时表现了实现类的行为特征.
多态的方法调用问题
若SuperC是SubC的父类或其接口:
SuperC s = new SubC(); s.doWork();

  • 若doWork方法只存在于SubC类中,不存在于SuperC类中。不能由父类型来调用
  • 若doWork方法是SuperC和SubCs类中的静态方法,此时子类doWork方法被隐藏,调用的是父类的doWork方法,此时变量s调用doWork方法时不具备多态特征,也就不存在多态。底层依然是类名掉用
  • 若doWork方法是子类覆写父类的方法,此时变量s调用的doWork方法实际上是子类里的doWork方法。(运行多态)
构造代码块 特点:
优先于构造方法执行,每次实例化对象之前都会执行构造代码块。
其实编译时就是把这个代码块中的代码放到了构造方法中代码的前面。
类实例化时的初始顺序:
成员变量的赋值 -> 构造代码块 -> 构造方法
静态代码块 使用static修饰的构造代码块,称为静态代码块,用于初始化类中的静态成员变量.
特点:
  1. 当字节码被加载到JVM的时候自动执行
  1. 只会执行1次
类实例化时的初始顺序:
静态代码块 -> 构造代码块 -> 构造方法
Java设计模式
分类
  • 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
  • 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
  • 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
单例模式`
目的:保证某一个类在整个应用中有且只有一个实例(一个类在内存只存在一个对象),即所有指向该类型实例的引用都指向同一块内存空间。
private staticinstance ;
private $CLASS_NAME$($param1$){ $init$ }public static $CLASS_NAME$ getInstance($param1$){ if (instance == null) { synchronized ($CLASS_NAME$.class){ if (instance == null) { instance = new $CLASS_NAME$($param2$) ; } } } return instance ; }

享元模式
以基本类型包装类为例:
Byte、Short、Integer、Long:缓存[-128,127]区间的数据;
Character:缓存[0,127]区间的数据;
当取值范围在这区间时,不会重新创建对象,而是直接从缓存中获取对象.
特点:
把经常使用的数据先缓存起来,以后要用的时候直接去缓存中获取,缓存中没有再创建,使用享元模式后能够提高系统的性能和不会带来更多的复杂度时
模板方法设计模式
public abstract Class template{ //模板方法 子类不能复写 public final protected templateMethod(){ ...//相同逻辑absMethod(); ...//相同逻辑 } protected abstract void absMethod(); //抽象方法,由子类重写实现 }

在父类的一个方法中定义一个总体算法的骨架,而将某一些步骤延迟到子类中,因为不同的子类实现细节不同。
抽象父类至少提供的方法:
  • 模板方法:一种通用的处理方式,即模板; 通用的算法,用final修饰,不准子类重写
  • 抽象方法:一种具体的业务功能实现,由子类完成;专门留给子类来重写,用protected修饰
注意:抽象父类提供的模版方法只是定义了一个通用算法,其实现必须依赖子类的辅助。
组合模式
解决代码复用问题:
组合关系/包含关系:"has A"关系.
在一个类中包含了另一个类型(基本类型和引用类型)的成员变量,通过对传入这类型的对象后可以在本类中使用其他类的功能
interface SIM{ 打电话(); } class 移动 implement SIM{ 重写方法... } class 手机{ //手机要打电话,打电话的方法在SIM接口中,此时我们使用组合模式,即不继承SIM SIM sim ; 手机(SIM sim){//传入SIM的实现对象,手机就可以达到打电话了 this.sim = sim; } 打电话(){ sim.打电话(); } }

抽象类 抽象类需要的部分功能,由于自己不能实现,而交由子类去实现,此时就需要定义抽象方法,让子类去重写,实现具体功能.
特点:
使用abstract关键字修饰的类。
  • 不能创建实例即不能new一个抽象类。
  • 可以不包含抽象方法,若一旦包含,该类必须作为抽象类。
  • 若子类没有实现父类所有的抽象方法,那么子类也得作为抽象类(抽象派生类)。
  • 构造方法不能全部定义成私有的,否则不能有子类(创建子类对象前先调用父类构造方法)。
  • 抽象类不能使用final修饰,因为必须有子类,抽象方法才能得以实现。
  • 是不完整的类,需作为基类,功能留给子类实现。
接口 例子:
主板提供了USB规范的插槽(F类中向外提供了一个传递I接口类型参数的公共方法),一个遵循USB规范的鼠标(一个实现了I接口的IMPL类),就可以插入主板(向F类中传递实例对象),与主板正常通信(IMPL类可与F类进行数据交互),主板不需要关心鼠标是怎么生产的,内部是怎么样的(F类只需调用IMPL类已实现的接口方法即可,不需要关心怎么实现).鼠标坏了也不会影响主板.
接口定义一种规范,规定某一类事物类必须具备某些功能,但它不管类如何具体实现这些功能。
特点:
  • 没有构造方法,不能实例化。
  • 接口只能继承接口,不能继承类,且接口支持多继承。
  • 接口里的方法方法全是抽象的,默认修饰符是public abstract。
  • 接口里的字段全是全局静态常量,默认修饰符是public static final。
  • 接口里的内部类(包括内部类、内部接口、内部枚举)全是静态的,默认修饰符是public static。
接口和抽象类
相同点:
  1. 都位于继承的顶端,用于被其他实现或继承。
  1. 都不能实例化。
  1. 都可以定义抽象方法,其子类都必须覆写这些抽象方法。
区别:
  1. 接口没有构造方法,抽象类有构造方法。
  1. 抽象类可包含普通方法和抽象方法,接口只能包含抽象方法;
  1. 一个类只能继承一个直接父类(可能是抽象类),却可以实现多个接口(接口弥补了Java的单继承)。
    变量:接口里默认是public static final,抽象类是默认包权限。
    方法:接口里默认是public abstract,抽象类默认是默认包访问权限。
    内部类:接口里默认是public static,抽象类默认是默认包访问权限。
二者的选用:
优先选用接口,尽量少用抽象类。若需要定义子类的行为,又要为子类提供共性功能时选用抽象类。
面向接口编程
我们以后所看到的多态形式都是大多数都是接口和实现类之间关系
接口类型 变量 = new 接口实现类
面向接口编程的好处在于能屏蔽不同子类之间的差异,使用一种规范去表示他们的行为,达到通用编程(多态的好处)的好处
我们以后在类中出现要创建对象的时候,统统都不new对象,改为使用工厂模式来生产对象,但是要生产怎样的对象才是我们要的呢?在类中留下一个规范,即接口。
内部类 内部类(静态内部类除外)可以访问外部类的私有数据,外部类不能直接访问内部类的实现细节,比如字段。
四种内部类:
  1. 非静态内部类:
    ClsOut.ClsIn in = new ClsOut().new ClsIn();
  2. 静态内部类:
    ClsOut.ClsIn in = new ClsOut().ClsIn;
  3. 局部内部类:
    在方法中定义的类,若在局部内部类对象中访问方法中的局部变量, 则变量必须加final修饰,否则方法调用结束,变量就被回收了
  4. 匿名内部类适合于仅使用一次使用的类:
    适合只使用一次的类。
    不能是抽象类,因为系统在创建匿名内部类的时候,会立即创建匿名内部类的对象。
    不能定义构造器,因为匿名内部类没有类名。
    注意:匿名内部类必须继承一个父类或者实现一个接口,但最多只能一个父类或实现一个接口。
枚举类
  • 枚举的直接父类java.lang.Enum,但是不能显示继承Enum。
  • 枚举就相当于一个类,可以定义构造方法、成员变量、普通方法和抽象方法。
  • 枚举的构造方法必须是私有的 private。
  • 每个实例分别用一个全局常量表示,枚举类的对象是固定的,实例个数有限,不能使用new关键字。
  • 枚举实例必须位于枚举体中的最开始部分,枚举实例列表的后要有逗号与其他成员相分隔。
  • 【Java基础|Java基础 - 面向对象】枚举实例后有花括号时,该实例是枚举类的匿名内部类对象(查看编译后的class文件)。
    public enum Gender{ MAN,WOMAN; }

    推荐阅读