4.0 面向对象的学习内容
4.1 类和对象
- 类和对象是面向对象的核心概念。
- 类是对一类事物的描述,是抽象、概念上的定义。
- 对象是实际存在的该类事物的每个个体,因而也被称为实例。
- 类和对象的书面理解:
- Java语言范畴中,将功能、结构等封装到类中,通过类的实例化,来调用具体的功能、结构。
- Scanner、String等
- 文件:File
- 网络资源:URL
- 涉及到Java语言与HTML、后端数据库交互时,前后端的结构在java层面交互时,都体现为类、对象。
4.1.1 类
- 类的语法格式:
修饰符class类名{ 属性声明; 方法声明; } publicclassPerson{ privateintage ; //声明私有变量 agepublicvoidshowAge(inti) { //声明方法showAge( )age=i; } }
- 类的成员:
- 属性:对应类中的成员变量
- Field = 属性 = 成员变量
- 行为:对应类中的成员方法
- Method = 方法 = 函数
- 类权限修饰符只能缺省或public
4.1.2 类的成员——属性
- 属性的定义(声明):语法同变量:
访问修饰符 属性类型 属性名;
- 细节和注意事项:
- 访问修饰符:public、proctected、默认、private。
- 属性的类型:可以是任意类型,包括基本数据类型和引用数据类型。
- 属性的默认值:属性不赋值时,有默认值,规则同数组
- byte、byte、short、int、long:0
- float、double:0.0
- char:\u0000(空字符)
- boolean:false
- String:null
- 属性赋值:遵循变量赋值的规定,即:
- 如果变量是基本数据类型,此时赋值的是变量所保存的数据值。
- 如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值。
4.1.3 类的成员——方法
- 方法的定义:
访问修饰符返回数据类型方法名(形参列表……){ 语句; return返回值; }
- 形参列表:表示成员方法的输入
- 返回数据类型:表示成员变量的输出类型。void表示没有返回值。
{}
:表示方法体,实现某一功能的代码块- return语句:不是必须的。
- 无返回值类型的方法也可以用return,用于结束方法。
- 细节和注意事项
- 访问修饰符:
- public、proctected、默认、private
- 控制方法的使用范围。
- 返回数据类型:
- 一个方法只能有一个返回值。返回多个值可以使用数组接收。
- 返回类型可以是任意类型包含基本类型或引用类型(数组、对象)
- 如果要求有返回值,则必须有return语句,且return语句的数据类型与方法的返回数据类型一致或兼容。
- 如果方法时void,则方法体中可以没有return语句,或者只写return。
- 方法名:
- 见名知义
- 小驼峰命名方法,遵循标识符的规则、规范
- 形参列表:
- 一个方法可以有0个参数,也可以有多个参数,多个参数用逗号分开。
- 参数类型可以是任意类型,包括基本类型和引用类型。
- 调用带参数的方法时,必须传入对应类型或兼容类型的实参。
- 调用方法时,实参和形参的数量、类型、顺序必须一致。
- 方法体:
- 方法体的语句可以为输入、输出、变量、运算、分支、循环、方法调用,但是不能嵌套定义——方法中不能再声明方法。
- 方法调用的细节
- 同一个类中的方法A(非main方法)中调用方法B时,可以直接调用
方法名()
- main方法需要调用同一个类中的方法B时,需要通过实例化类,
对象名.B方法名()
调用
- 跨类中的方法A调用方法B时,需要通过对象名(实例化对象)调用
类名 对象名 = new 类名();对象名.方法名()
- 跨类调用的方法和它的访问修饰符有关。
- 方法的调用机制:
- 当程序执行(main方法)到方法时,会在jvm的栈内存内开辟一个独立的空间
- 当方法执行完毕时,或执行到return语句时,就会将方法的运行结果返回给栈内存中调用方法的地方。同时销毁开辟的独立空间。
- 返回后,程序继续执行后面的代码
- 当jvm内存内的main方法执行完毕,整个程序退出。
- 使用方法的优点:
- 提高了代码的复用性。
- 将实现的细节封装起来,供其他用户调用。
- 方法的传参机制(值传递):
- 基本数据类型:传递的是值(值拷贝),形参的任何改变不影响实参。
- 引用数据类型:传递的是地址,可以通过形参影响实参。
- 形参不影响实参的情况:形参在方法内开辟了新的内存空间(需要在方法体内的语句实现)。
- 字符串的传参?
- 克隆对象:创建两个独立的对象,只是属性、方法一致。
- 利用引用数据类型传参的机制(在方法体内创建新对象,逐一赋值)。
4.1.4 类的实例化——对象
- 创建对象:
- 方式一:
类名 对象名 = new 类名();
- 方式二:
类名 对象名;对象名 = new 类名();
Person p1 = new Person();
- p1是对象的名(对象的引用),保存实例化对象的内存地址
- new Person()创建的对象空间(数据)才是真正的对象
- 方式三:创建对象数组
类名[] 对象数组名 = new 类名[n];
- 访问成员:
对象名.对象成员
- 访问属性:
对象名.属性
- 访问方法:
对象名.方法()
- 匿名对象:
- 含义:创建对象时,如果没有显式地给对象起名,只是进行了
new 类名()
,则new 类名()
为匿名对象。 - 特征:匿名对象只能调用一次。
- 第二次调用时已经是新对象了,跟前一个对象没有关系。
- 使用:
- 临时使用、不需要保留
- 作为方法的参数
4.1.5 方法重载(OverLoad)
- 定义:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
- 判断是否是重载:
- “两同一不同”:
- 在同一个类中
- 相同方法名
- 参数列表不同:
- 参数个数不同
- 参数类型不同
- 同类型参数顺序不同
- 跟方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系!
- 在通过对象调用方法时,如何确定某一个指定的方法:
- 判断方法名是否一致
- 判断参数列表是否一致:参数类型、参数个数、参数顺序一致
- 注意点:子类可以重载父类同名不同参的方法。
4.1.6 方法递归(Recrusion)
- 含义:方法自己调用自己,每次调用时传入的参数不同
- 调用规则:
- 递归执行一次,会在栈内存内创建一个受保护的独立空间(栈空间)
- 方法的局部变量相互独立,不会相互影响
- 方法中使用引用数据类型的变量时,就会共享该引用类型的数据。
- 递归必须向退出递归的条件逼近,否则就是无限递归。
- 当一个方法执行完毕,或者遇到return语句,就会返回,遵循谁调用,结果就返回给谁。同时该方法执行完毕或返回时,该方法也就执行完毕。
4.1.7 构造方法/构造器(Constructor)
- 基本语法:
[修饰符] 方法名(形参列表){}
- 语法说明:
- 修饰符可以默认(无),也可以用private、protected、public。
- 修饰符为默认(无)时,与类的修饰符一致。
- 构造器没有返回值,也不可以写
void
- 方法名必须和类名一模一样
- 参数列表和方法的规则一致
- 在创建对象时,系统会自动调用该类的对象完成属性的初始化。
- 完成对象的初始化不是创建对象
- 不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值
- 主要作用:完成新对象(属性)的初始化。
- 构造器重载:
- 一个类可以定义多个构造器
- 如果一个构造器没写,系统会自动生成一个无参、默认的构造器(该构造器的修饰符与类一致)。
- 一旦定义了一个构造器,默认的无参构造器就不能使用了,使用了会报错。
- 如果想继续使用,需要再重新显式定义一下。
- 对象创建流程:
- 加载类信息,加载到方法区,只会加载一次
- 在堆空间中分配空间(地址)
- 完成对象的初始化
- 默认初始化
- 显式初始化
- 构造器初始化
- 将对象在堆空间的地址返回给创建对象时定义的对象
- 父类的构造器不可被子类继承
- 注意点:使用了构造器后,实例化对象要用构造器的方法,否则会报错。
- 原因为:显式声明构造器后,默认的无参构造器会不可用。
4.1.8 方法重写(override/overwrite)
- 定义:子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作
- 应用:重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。
- 语法:
权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型{}
- 方法名:父子一致。
- 形参列表:父子一致。
- 权限修饰符:子类不小于父类
- 不能重写父类中private修饰的方法,因为private修饰的方法对外部不可见。
- 返回值类型:
- void:父子一致。
- 基本数据类型:父子一致。
- 引用数据类型:子类不大于父类
- 异常类型:子类不大于父类
- 注意点:子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)。
4.1.9 代码块
- 作用:用来初始化类、对象
- 修饰:
- 权限修饰:只能缺省。
- 关键字:只能用static或没有。
- 分类:静态代码块与非静态代码块,
- 相同点:
- 都可以用于对类的属性、声明初始化
- 都可以声明多个代码块,但一般每种最多写一个
- 多个代码块默认执行顺序都是先上后下
- 不同点:
- 静态代码块:
- 只能调用静态的属性和方法
- 随类的加载而执行,只执行一次
- 类加载的三个时机:
- 创建对象实例时(new)
- 创建子对象实例时,父类会被加载
- 使用类的静态成员时。
- 非静态代码块:
- 既可以调用静态的属性和方法,也可以调用非静态的属性方法
- 随对象的创建而执行,创建一次执行一次。先于构造器执行
- 可以将构造器相同的部分写到代码块内,减少不同构造器间的代码冗余。
- 创建对象时,类的调用顺序:
- 静态代码块和静态属性。取决于书写顺序。
- 普通代码块和普通属性。取决于书写顺序。
- 构造器。
- 创建子类对象时,类的调用顺序:
- 父类的静态代码块和静态属性。取决于书写顺序。
- 子类的静态代码块和静态属性。取决于书写顺序。
- 父类的普通代码块和普通属性。取决于书写顺序。
- 父类的构造器。
- 子类的普通代码块和普通属性。取决于书写顺序。
- 子类的构造器。
4.1.10 内部类(InenerClass)
- 含义:定义在类内部的一个类,包含内部的类叫外部类
- 外部引用时需要完整写出类名称(含包名)
- 编译以后生成OuterClass$InnerClass.class字节码文件
- 分类:
- 局部内部类:定义在方法、代码块、构造器中。
- 成员内部类(static修饰和无修饰):定义在成员位置,类内可定义类的五大组成部分(属性、方法、构造器、代码块、内部类)
- 可以被final修饰,表示此类不能被继承。
- 可以被abstract修饰,表示不能被实例化
- 局部内部类:
- 可以直接访问外部类的所有成员,包含私有的
- 但是如果调用局部内部类所在方法中的局部变量时,要求该方法中的局部变量为final修饰,JDK8之前显式声明,JDK8之后可省略。
- 不能添加权限修饰符(只能缺省,同局部变量的修饰符范围),可以被final修饰,修饰后不可被继承
- 不能被static修饰,也不能包含static成员
- 内部类的成员与外部类的成员重名时,内部类调用遵循就近原则,需要调用外部成员时,需要通过
外部类.this.成员名
的方式 - 由于局部内部类定义在方法、代码块、构造器中,实际上是一个局部变量,只能在定义它的位置生效,即只能在这个位置实例化。
- 外部类需要访问内部类的成员时,需要通过上述流程,在外部类的方法中,将内部类实例化。
- 外部其他类不能访问。
- 成员内部类:
- 可以直接访问外部类的所有成员,包含私有的
- 使用static修饰时,只能调用外部类声明为static的结构
- 非静态内部类,内部不能声明静态成员
- 前面省略了
外部类.this.
,不能使用this.
- 可以使用四种权限修饰符修饰
- 实例化内部类:
- 静态内部类:
外部类.内部类 变量名 = new 外部类.内部类();
- 非静态内部类:
外部类.内部类 变量名 = 外部类的引用.new 内部类();
外部类.内部类 变量名 = 外部类的引用.new 外部类.内部类();
- 内部类的成员与外部类的成员重名时,内部类调用遵循就近原则,需要调用外部成员时,需要通过
外部类.this.成员名
的方式,调用内部类自身的属性,this.成员名
- 匿名内部类:
- 语法:
new 父类构造器(实参列表)|实现接口(){ //匿名内部类的类体部分 };
- 可以基于接口实现、也可基于父类实现
- 分号不能少
- 可以是成员内部类、也可以是局部内部类。
/*** @date 2022年5月24日下午9:25:26*/packagecom.infinity.interfacetest; publicclassCellphone{ // 1.1 匿名内部类是成员内部类doubleb=newCalculator() { doublec=9.0; publicdoublework() { // TODO Auto-generated method stubreturn0; } }.c; // 1.2 匿名内部类是成员内部类doublea=newCalculator() { publicdoublework() { // TODO Auto-generated method stubreturn0; } }.work(); publicdoubletestWork(Calculatorc) { doubleresult; result=c.work(); // 2.1 匿名内部类是局部内部类doubleb=newCalculator() { doublec=9.0; publicdoublework() { return0; } }.c; // 2.2 匿名内部类是局部内部类doublea=newCalculator() { publicdoublework() { // TODO Auto-generated method stubreturn0; } }.work(); returnresult; } publicstaticvoidmain(String[] args) { Cellphonecp=newCellphone(); // 2.3 匿名内部类是局部内部类doublea=cp.testWork(newCalculator() { publicdoublework() { return1+1; } }); System.out.println(a); } }
- 匿名内部类本身也是一个对象,因此它也可以调用内部类内部的方法,执行时遵循多态的规则(匿名内部类重写了就执行内部类里的方法)
- 其他有关问题参见4.4.8第6部分。
- 外部类不能被static修饰
- 成员内部类和局部内部类,在编译以后,都会生成字节码文件。
- 成员内部类:外部类$内部类名.class
- 局部内部类:外部类$数字 内部类名.class
4.2 jvm内存分析
4.2.1 内存结构
- 图结构:
- 引用类型的变量只能存储两类值:
- null
- 包含变量类型的地址值。
4.2.2 内存分配
- 栈:一般指虚拟机栈,存储局部变量。
- 堆:存放对象(含对象的属性)、数组。
- 方法区:常量池(常量、字符串)、静态变量、即时编译后的代码。
4.2.3 成员变量与局部变量的异同
- 相同点:
- 定义变量的格式相同:
数据类型 变量名 = 变量值
- 先声明,后使用
- 都有其对应的作用域以及生命周期
- 不同点:
- 在类中声明的位置的不同
- 属性:直接定义在类的一对
{}
内 - 局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量
- 作用域范围不同:
- 属性:可以被本类使用、也可以被其他类使用(通过对象调用)
- 局部变量:只能在本类中对应的方法使用。
- 关于权限修饰符的不同
- 属性:可以在声明属性时,指明其权限,使用权限修饰符。
- 局部变量:不可以使用权限修饰符。
- 默认初始化值的情况不同:
- 属性:类的属性,根据其类型,都有默认初始化值。
- 局部变量:没有默认初始化值。在调用局部变量之前,一定要显式赋值。形参在调用时赋值即可。
- 在内存中加载的位置不同:
- 属性:加载到堆空间中(非static),因为对象加载到了堆空间
- 局部变量:加载到栈空间
- 生命周期不同:
- 属性:生命周期长,伴随对象的创建而创建,伴随对象的销毁而销毁。
- 局部变量:生命周期短,伴随代码块的执行而创建,伴随代码块的执行结束而销毁。
- 属性和局部变量可以重名,访问时遵循就近原则。
- 属性赋值过程:
- 默认初始化
- 显式初始化 / 代码块
- 取决于类中书写的顺序
- 构造器中初始化
- 通过“对象.属性“或“对象.方法”的方式赋值
4.2.4 可变个数形参
- 语法:
- JDK 5.0以前:采用数组形参来定义方法,传入多个同一类型变量
public static void test(int a, String[] books);
- JDK5.0:采用可变个数形参来定义方法,传入多个同一类型变量
public static void test(int a, String… books);
- 注意点:
- 调用方法时,可传入的参数个数可以是0个,1个或多个
- 可变参数的方法可与其他方法构成重载,调用时,优先调用匹配度高的
- 用
...
和[]
作为方法参数的同名方法视为同一个类里视为重复定义(编译报错),父子类内视为重写。
- 可变参数方法的使用与方法参数部分使用数组是一致的,方法体内使用for循环
- 方法的参数部分有可变形参时,需要放在形参声明的最后
- 一个方法最多只能声明一个可变个数形参
4.3 封装、继承和多态
2.3.1 封装与隐藏
- 含义:将类中的属性(数据)私有化,并提供外部可访问的类进行属性操作。
- 好处:
- 对(变量)数据进行验证,保证数据安全
- 隐藏实现细节
- 便于修改,增强代码可维护性
- 体现:
- 将属性使用private修饰,进行私有化,并提供公共的set和get方法获取和设置此属性的值
- 提供public修饰的setter,用于判断并修改属性,无返回值
- 提供public修饰的getter,用于判断并读取属性,有返回值
- 不对外暴露私有方法
- 单例模式(将构造器私有化)
- 如果不希望类在包外调用,可以将类设置为缺省的
- 权限修饰符:
修饰符 |
类内部 |
同一个包 |
不同包的子类 (其父类是protected) |
同一个工程 |
private |
√ |
|||
缺省 |
√ |
√ |
||
protected |
√ |
√ |
√ |
|
public |
√ |
√ |
√ |
√ |
- 类的权限修饰符只能用public或缺省
- 使用public时,本工程下使用
- 需要导入全类名
- 使用缺省时,只可以被同一个包内部的类访问
- 局部变量(方法内、方法形参等)的修饰符只能缺省。
- protected修饰的属性、方法需要在不同包内访问时,一是需要父子继承关系,且protected修饰的内容是父类,二是需要在子类中导入包
4.3.2 继承(inheritance)
- 作用:
- 减少代码的冗余,提高代码的复用性,提高开发效率
- 有利于功能扩展
- 使类与类之间产生联系,为多态提供了前提
- 格式:
class A extends B{}
A
:子类、派生类、subclassB
:父类、超类、superclas
- 体现:
- 子类A继承父类B以后,子类A中就自动获取了父类B中声明的所有的属性和方法。
- 直接父类、间接父类、自身都有同名属性的情况下,访问时执行就近原则。
- 方法按重写规定执行。
- 父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。只有因为封装性的影响,使得子类不能直接调用父类的结构而已。需要通过setter和getter调用属性。
- 通过
super.属性
的方法也访问不到。
- 子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。
- 子类必须调用父类的构造器,完成父类的初始化。
- 实例化过程中会一直调用至Object对象。
- 注意点:子类A继承自父类B后,如果父类B中声明了带参的构造器,则必须在子类A中声明A的构造函数,且必须在首行通过
super(形参列表)
调用B的构造函数。否则会编译报错。
- 原因为如果子类A中不写构造器,其默认构造器为无参构造器,无参构造器中默认会调用B的无参构造器
super()
。由于父类B中显式地声明了构造器,导致默认的无参构造器不可用,从而会报错。而如果不在首行写super(形参列表)
语句,则表明调用的是super()
,同样会报错。
- 解决方案一:父类B中显式声明无参构造器。
- 解决方案二:子类A中调用父类B中指定的构造器。声明A的构造器方法体内使用
super(形参列表)
- 规定:
- 一个父类可以被多个子类继承。
- 子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类。
- 子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法。
- 如果我们没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类。
- 所有的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类
- 所有的java类具有java.lang.Object类声明的功能。
- 子类对象的实例化过程
- 从结果上来看:(继承性)
- 子类继承父类以后,就获取了父类中声明的属性或方法。
- 创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。
- 从过程上来看:
- 通过子类的构造器创建子类对象时,一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,直到调用了java.lang.Object类中空参的构造器为止。
- 正因为加载过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用。
- 虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。
4.3.3 多态性(Polymorphism)
- 理解:方法和对象的多种形态。实现代码的通用性。
- 体现:
- 方法多态:
- 重载多态(参数不同,方法体现出多重形态)。
- 重写多态。
- 对象多态:
- 对象的编译类型和运行类型可以不一致,编译类型在定义时确定,不能变化。
- 对象的运行类型可以是变化的,可以通过
getClass()
查看运行类型。
- 常将父类对象作为方法形参,执行方法时根据具体实例化的父/子类对象进行传入。
- 使用:虚拟方法调用
- 子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
- 编译期只能调用父类中声明的方法,但在运行期,实际执行的是子类重写父类的方法。
- 不能调用子类中特有的成员。
- 需要调用子类特有成员时,需要使用向下转型。
- 总结:编译,看左边;运行,看右边。
- 使用前提:
- 类的继承关系
- 方法的重写
- 对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)。
- 使用向下转型声明的父类对象,调用父子类同名的属性时,实际调用的是父类的属性。
- 属性不具有多态性。
- 由于父类的属性通常设置为private,所以需要getter方法才能获取到。
instance of
操作符:
x instanceof A
:检验x的运行类是否为类A或其子类的对象,返回值为boolean型。
- 如果x是类B的对象,而B是A的子类,则仍返回true。
AAaa=newBB(); System.out.println(aainstanceofAA); System.out.println(aainstanceofBB); //设BB继承自AA
- 强制转型后的类型判断:这里均返回true,因为运行的是BB类
x instanceof Object
:总是返回true。- 检验的A类必须与x的类有父子类关系,否则编译会报错。
- 造型:对java类型的强制类型转换
- 子类到父类可以自动类型转换——多态、向上转型
- 父类到子类必须通过造型
()
实现——向下转型。 - 无继承关系的引用类型造型非法,运行报错:
ClassCastException
- 通常首先使用
instanceof
操作符进行判断后进行造型操作,避免报错。
- java的动态绑定机制:
- 当调用对象的方法时,该方法会和对象的内存地址/运行类型绑定。
- 当调用对象的属性时,没有动态绑定机制,哪里声明,哪里使用。
4.4 关键字
4.4.1 this
- 作用:
- 方法内部使用时,代表方法所属对象的引用
- 构造器内部使用时,代表该构造器正在初始化的对象
- 使用范围:属性、方法、构造器
- 时机:方法内需要调用该方法的对象时,可以使用this。
- 在任意方法或构造器内,如果使用当前类的成员变量或成员方法可以在其前面添加this,增强程序的阅读性。不过,通常我们都习惯省略this。
- 当形参与成员变量同名时,如果在方法内或构造器内需要使用成员变量,必须添加this来表明该变量是类的成员变量
- 使用this访问属性和方法时,如果在本类中未找到,会从父类中查找
- this调用构造器
- 可以在类的构造器中使用
this(形参列表)
的方式,调用本类中重载的其他的构造器! - 明确:构造器中不能通过
this(形参列表)
的方式调用自身构造器 - 如果一个类中声明了n个构造器,则最多有n-1个构造器中使用了
this(形参列表)
this(形参列表)
必须声明在类的构造器的首行!- 在类的一个构造器中,最多只能声明一个
this(形参列表)
- 有参的构造器如果没有显式地调用this(),则不会调用无参的构造器。因为如果没写this(),也没写super(0,默认调用的是super()。
4.4.2 package
- 作用:package语句作为Java源文件的第一条语句,指明该文件中定义的类所在的包。(若缺省该语句,则指定为无名包)。
- 格式:
package 顶层包名.子包名
- 一般:
com.公司名.项目名.业务模块名
- 使用规范:
- 包对应于文件系统的目录,package语句中,用
.
来指明包(目录)的层次 - 包通常用小写单词标识。通常使用所在公司域名的倒置
- 作用:
- 包帮助管理大型软件系统:将功能相近的类划分到同一个包中。比如:MVC的设计模式
- 包可以包含类和子包,划分项目层次,便于管理
- 解决类命名冲突的问题
- 控制访问权限
- 注意点:
- 同一个包下,不能命名同名的接口、类。
- 不同的包下,可以命名同名的接口、类。
- 常见包:
- java.lang----包含一些Java语言的核心类,如String、Math、Integer、 System和Thread,提供常用功能
- java.net----包含执行与网络相关的操作的类和接口。
- java.io ----包含能提供多种输入/输出功能的类。
- java.util----包含一些实用工具类,如定义系统特性、接口的集合框架类、使用与日期日历相关的函数。
- java.text----包含了一些java格式化相关的类
- java.sql----包含了java进行JDBC数据库编程的相关类/接口
- java.awt----包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。 B/S C/S
4.4.3 import
- 作用:为使用定义在不同包中的Java类,需用import语句来引入指定包层次下所需要的类或全部类(.*)。
- 语法格式:
import 包名.类名;
- 使用细节:
- 在源文件中使用import显式的导入指定包下的类或接口
- 声明在包的声明和类的声明之间。
- 如果需要导入多个类或接口,那么就并列显式多个import语句即可
- 可以使用java.util.*的方式,一次性导入util包下所有的类或接口。
- 如果导入的类或接口是java.lang包下的,或者是当前包下的,则可以省略此import语句。
- 如果在代码中使用不同包下的同名的类。那么就需要使用类的全类名的方式指明调用的是哪个类。
- 如果已经导入java.a包下的类。那么如果需要使用a包的子包下的类的话,仍然需要导入。
- import static组合的使用:调用指定类或接口下的静态的属性或方法
4.4.4 super
- 用途:子类调用父类的属性、方法、构造器
- 解决子父子类属性、方法冲突的问题。
- 在合适的位置调用父类的属性、方法、构造器。
- 操作对象:属性、方法、构造器
- 调用属性和方法:
- 子类的方法和构造器中,使用父类的属性和方法时,默认使用了
super.
的方式调用属性、方法。编程中习惯不写super.
。
- super不能调用父类的私有属性。
- 当子类和父类中定义了同名的属性时,使用
super.属性
调用的是父类的属性;如果属性没有重名,使用super.属性
同this.属性
作用一样。 - 在子类中重写的方法中调用父类被重写的方法时,必须使用
super.方法
的方式。
- super不能调用父类的私有方法。
- super调用父类时,不局限于直接父类,有多个上级类有重名方法、属性时,遵循就近原则。
- 调用构造器:
- 可以在子类的构造器中,显式地使用
super(形参列表)
的方式调用父类的构造器。 super(形参列表)
必须声明在首行。- 由于
this(形参列表)
也必须出现在首行,所以一个构造器中this(形参列表)
或super(形参列表)
只能二选一,不能同时出现。 - 在构造器的首行,没有显式的声明
this(形参列表)
或super(形参列表)
,则默认调用的是父类中空参的构造器:super()
。 - 在类的多个构造器中,至少有一个类的构造器中使用了
super(形参列表)
,调用父类中的构造器。
- 因为所有的对象都是继承来的,都必然具有父类的特征。
- 默认的无参构造器默认调用的
super()
- 无论哪个构造器创建子类对象,需要先保证初始化父类,当子类继承父类以后,继承了父类中的属性和方法,因此子类有必要知道父类如何对对象初始化。
4.4.5 static
- 设计思想:
- 属性:在各个对象间共享,不因对象的不同而改变。
- 方法:方法的调用与对象无关,不需要创建对象就可调用方法。
- 修饰范围:
- 可修饰:属性、方法、代码块、内部类
- 被static修饰的属性叫:静态属性、静态变量、类变量。
- 没有被static修饰的属性叫非静态属性、实例变量。
- 被static修饰的方法叫:静态方法。
- 不能修饰:局部变量、形参、构造器、外部类
- 不能修饰构造器的原因:static随着类的加载而加载,根据构造器的加载时机区分static和非static,先于构造器加载的为static,后于构造器加载的为非static
- 特点:
- 属性和方法随着类的加载而加载,与是否创建对象无关。
- 由于类只会在JVM的内存里加载一次,所以属性会只有一份。
- 存在于方法区的静态域中。
- 无论创建多少次对象,使用多少次类,都只有一份
- 属性和方法优先于对象而存在。
- 被static修饰的成员,被所有对象所共享。
- 访问权限允许时,可不创建对象,直接被类调用。
被static修饰的内部类实例化的特点呢?以及与多线程的关系?
- 注意点:
- 静态属性和静态方法可以通过
类名.静态属性
、类名、静态方法名()
的方式直接调用,也可以通过对象名.静态属性
、对象名、静态方法名()
的方式调用。 - 静态方法中,只能调用静态的方法或属性;
- 同类中的静态方法调用静态方法,可以直接使用
方法名();
或类名、静态方法名()
,不能使用this
- 实例化当前类后,可以通过
对象名.静态属性
、对象名、静态方法名()
的方式调用非静态的属性和方法。
- 非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性。
- 在静态的方法内,不能使用this关键字、super关键字。
- 也不能通过
this.
、super.
的方式调用静态属性、静态方法,只能通过类名.
的方式调用。
- static修饰的方法不能被重写,但可以被继承。
- 不能被重写:父类、子类的同名、同参静态方法都会被加载。
- 可以被继承:子类可以直接通过
类名.方法名()
的方式调用。
- static修饰内部类,类中的属性、方法、代码块可以是非静态的
- static可与四种权限修饰符搭配使用,控制可见范围。
- 设置为private时,只能被
类名.静态属性
、类名.静态方法名()
调用,不能被对象的引用调用。
- 使用时机:
- 属性:
- 属性需要被多个对象共享
- 常量
- 方法:
- 操作静态属性
- 工具类中的方法。如Math、Collection、Arrays
4.4.6 main
- main()说明:
- main方法的权限修饰符必须是public
- main方法的修饰符必须是static,因为main方法也是一个内中的方法,需要加载对象的时候执行,而不是创建main方法所在类的对象时候执行。
- 由于main方法使用static修饰了,所有不能直接访问本类中的非static属性和方法,必须通过实例化创建本类对象的方式进行调用。
- main方法可以作为与控制台交互的方式
- 在命令行中执行(运行时,先要生成字节码文件)
java 文件名 参数
- 参数可以带引号,也可以不带,多个参数使用空格分开
- 在eclipse工具中执行:
- main方法中访问直接访问本类中的静态方法和静态静态属性可以不需要
类名.
的方式调用。
4.4.7 final
- 用途:修饰类、变量(成员变量及局部变量)、方法、内部类
- 变量分为局部变量和成员变量
- final修饰类:不能被继承,提高安全性、可读性。
- 通常final修饰类后,内部的方法没必要在用final修饰
- String类、StringBuffer类、System类
- final修饰方法:不能被子类重写
- Object的getClass()
- 不能修饰构造器
- final修饰属性(成员变量):表示常量,且必须在定义时初始化。
- 赋值的位置可以是类中显式初始化、代码块、构造器内。
- 搭配static使用时,初始化位置只能是显示初始化和静态代码块内
- final修饰局部变量:。
- final修饰形参时,表示给常量赋值,方法体内只能使用该常量,不能修改该形参。
- 修饰方法体内局部变量时,称为局部常量。
- final可以和static搭配使用,
static final
:只能用于修饰属性、普通方法,修饰属性时,表示全局常量。
4.4.8 abstract(抽象类与抽象方法)
- 修饰结构:类、方法、内部类
- 不能修饰属性、私有(private)方法、静态(static)方法、final方法、final类
- 通常用于处理父类方法不缺定时的需求。
- 先有抽象方法,后有抽象类。
- 不可修饰结构:属性、构造器等。
- 修饰类:
- 此类不能实例化
- 开发中一般提供抽象类的子类,该子类使用多态的方式实例化父类。否则匿名化。
- 修饰方法:
- 抽象方法只有方法名,没有方法体,用
;
结束 - 包含抽象方法的类,一定是抽象类;但抽象类不一定包含抽象方法。
- 抽象类不能被private修饰(因为要对外可见,需要被重写)
- 若子类重写了父类所有的抽象方法,则该子类可实例化;
- 若子类没有重写或部分重写了父类的所有抽象方法,则该子类也是一个抽象类,需要使用abstract修饰。
- 抽象类的应用:模板方法的设计模式。(见2.6.2)
- 匿名类(不一定为抽象类)与匿名对象:共有四种格式
- 非匿名类非匿名对象:
Worker worker = new Worker();
- 非匿名类匿名对象:
new Worker();
- 匿名类非匿名对象:
Person p = new Person(){重写Person的虚拟方法};
- 注意分号不能省略。
- Person是一个虚拟类,按理不能实例化,实际上也正是通过方法体中的重写,将Person类变成了其他类,正常来说应该单独定义一个非虚拟具名的类继承自Person,再将方法体写入其中,但正由于没有这样做,不知道这个子类叫什么名字,所以就是匿名类。
- 在匿名类非匿名对象的方法体中,如果声明了属性a,通过
对象引用.p.a
的方式访问属性a的值,如果Person类中没有定义过a,则报错,如果Person中定义了a,则返回的是Person类中的a的值。即,匿名类非匿名对象中声明的自有属性访问不到。 - 如果要能访问到,可采用
int p = new Person(){int a = 1;重写Person的虚拟方法}.a;
int p = new Person(){int a = 1;重写Person的虚拟方法}.a;
改变返回值类型、.a
为.方法名()
可以获取.方法名()
的返回值。但是仅限于有返回值类型的方法(含自有方法)。
- 匿名类匿名对象:
new Person(){重写Person的虚拟方法}
- 注意分号不能省略。
4.5 面向对象补充知识
4.5.1 JavaBean
- JavaBean是一种Java语言编写的可重用组件。
- 特征:
- 类是公共的
- 有一个无参的公共构造器
- 有属性
- 属性一般定义为private,有对应的getter、setter
- 可以使用JavaBean将功能、处理、值、数据库访问和其他任何可以用Java代码创造的对象进行打包,并且其他的开发者可以通过内部的JSP页面、Servlet、其他JavaBean、applet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变。
4.5.2 UML类图
- Accout:类名
+
表示public,-
表示private,#
表示protected- 属性:
:
前表示属性名,:
后表示属性类型 - 方法:方法的
()
外面有:
表示有返回值,:
后面为返回值类型 - 方法有下划线表示构造器
4.5.3 MVC设计模式
- 内容:常用的设计模式之一,将整个程序分为三个层次:视图模型层,控制器层,与数据模型层。
- 优点:这种将程序输入输出、数据处理,以及数据的展示分离开来的设计模式使程序结构变的灵活而且清晰,同时也描述了程序各个对象间的通信方式,降低了程序的耦合性。
- 模型层(model):主要处理数据
- 数据对象封装:model.bean/domain
- 数据库操作类:model.dao
- 数据库:model.db
- 控制层(controller):处理业务逻辑
- 应用界面相关:controller.activity
- 存放fragment:controller.fragment
- 显示列表的适配器:controller.adapter
- 服务相关的:controller.service
- 抽取的基类:controller.base
- 视图层(view ):显示数据
- 相关工具类:view.utils
- 自定义view:view.ui
4.5.4 Object类的使用
- Object类是所有Java类的根父类。
- 如果在类的声明中未使用extends关键字指明其父类,则默认直接父类为java.lang.Object类。
- Object类中的属性:无
- Object类中的方法:
equals()
:
- 只能比较引用数据类型,作用与
==
相同,比较是否指向同一对象。 - 类
File、String、Date及包装类
由于重写了equals()
,所以比较的时内容是否相同。 - 重写
equals()
原则:
- 对称性:如果x.equals(y)返回是true,那么y.equals(x)也应该返回是true。
- 自反性:x.equals(x)必须返回是true。
- 传递性:如果x.equals(y)返回是true,而且y.equals(z)返回是true,那么z.equals(x)也应该返回是true。
- 一致性:如果x.equals(y)返回是true,只要x和y内容一直不变,不管重复x.equals(y)多少次,返回都是true。
- 任何情况下,x.equals(null),永远返回是false,null。equals(x)会空指针异常。
- x.equals(和x不同类型的对象)永远返回是false。
toString()
:
- 返回值为String,返回类名和它的内存地址——全类名+@+哈希值的十六进制。
- 全类名:包名+类名
- 类
File、String、Date及包装类
由于重写了toString()
,返回"实体内容"信息。 - 使用String类型的数据与
+
进行连接操作时,自动调用toString()
getClass()
:获取当前对象所处的类hashCode()
:返回该对象的哈希值,用于提高哈希表的性能。
- 两个引用指向同一对象,哈希码值一定一样。
- 两个引用指向不同对象,哈希码值一般不一样,(极少数情况一样)。
- 哈希值主要根据地址值生成,但与地址值不同。
clone()
finalize()
:
- 当对象被回收时,系统会自动调用该方法。
- 当某个对象没有任何引用,则jvm认为该对象是一个垃圾对象。在销毁该对象前,会先调用该方法。
- 可以通过System.gc()主动出发垃圾回收机制。
wait()
notify()
notifyAll()
- Object类只声明了一个空参的构造器
- final、finally、finalize的区别?
4.5.5 JUnit单元测试
- 步骤:
- 选中当前工程 - 右键选择:build path - add libraries - JUnit 5.4 - 下一步
- 创建Java类,进行单元测试。
- 简便方式:在测试方法上一行写上
@Test
,然后Ctrl+1修复。
- 此时的Java类要求:① 此类是public的 ②此类提供公共的无参的构造器
- 此类中声明单元测试方法:方法的权限是public,没有返回值,没有形参。
- 此单元测试方法上需要声明注解:@Test,并在单元测试类中导入:import org.junit.Test;
- 声明好单元测试方法以后,就可以在方法体内测试相关的代码。
- 写完代码以后,左键双击单元测试方法名,右键:run as - JUnit Test
- 说明:
- 如果执行结果没有任何异常:绿条
- 如果执行结果出现异常:红条
4.6 设计模式
- 概念:开发过程中经过大量的实践中总结和理论化之后优选的代码结构、编程风格、 以及解决问题的思考方式。
- 分类:
- 创建型(5种):
- 结构型(7种):
- 行为型(11种):
4.6.1 单例模式(Singleton)
- 定义:采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
- 实现原理:
- 构造器私有化,防止new
- 类的内部创建静态实例对象
- 向外暴露一个静态的公共方法。
- 实现方式:
- 饿汉式:
classSingleton { // 1.私有化构造器privateSingleton() {} // 2.内部提供一个当前类的实例// 4.此实例也必须静态化privatestaticSingletonsingle=newSingleton(); // 3.提供公共的静态的方法,返回当前类的对象publicstaticSingletongetInstance() { returnsingle; } }
- 懒汉式:
classSingleton { // 1.私有化构造器privateSingleton() {} // 2.内部提供一个当前类的实例// 4.此实例也必须静态化privatestaticSingletonsingle; // 3.提供公共的静态的方法,返回当前类的对象publicstaticSingletongetInstance() { if(single==null) { single=newSingleton(); } returnsingle; } }
- 优缺点:
- 饿汉式:
- 优点:线程安全。
- 缺点:对象加载时间过长。全生命周期。
- 懒汉式:
- 优点:延迟对象的创建。
- 缺点线程不安全。
- 应用场景:
- 网站计数器:
- 应用程序的日志程序:
- 数据库的连接池
- 读取配置文件的类:
- Application:
- windows系统的任务管理器
- windows系统的回收站
4.6.2 模板方法设计模式(TemplateMethod)
- 体现:就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
- 也是多态行的一种体现。
- 解决的问题:
- 当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
- 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。
- 应用:模板方法设计模式是编程中经常用得到的模式。各个框架、类库中都有他的影子,比如常见的有:
- 数据库访问的封装
- Junit单元测试
- JavaWeb的Servlet中关于doGet/doPost方法调用
- Hibernate中模板程序
- Spring中JDBCTemlate、HibernateTemplate等
- 实现过程:
- 定义抽象类
- 抽象类中写明确定的流程,定义在一个方法(模板方法)内,模板方法调用各个流程
- 将不确定的内容插入到方法中的合适位置调用
- 将不确定的内容定义成抽象方法。
- 子类继承抽象类,重写抽象方法,在方法中完成需要工作的代码。
- 实例化子类,子类由于继承了父类的方法,通过子类对象的引用调用父类的模板方法的那个方法。
4.6.3 代理模式(Proxy)
- 体现:类A实现了接口,实际需要类A去调用(操作)接口时,表面上通过实例化类B去操作,而将A作为一个参数调用。
- 步骤:
- 类A实现接口C
- 类B实现接口C
- 类B构造函数传入接口C
- 在类B内定义检查、校验等方法,并在实现的接口C的方法内调用
- 创建
C 变量名 = new B(new A())
变量名.C的虚拟方法
- 应用场景
- 安全代理
- 远程代理
- 延迟加载
- 分类:
- 静态代理(上述描述)
- 动态代理(JDK自带的静态代理,需要反射实现)
4.6.4 工厂模式(略)
4.7 Interface(接口)
- 概述:接口是一组规则的集成,是抽象方法和常量值的集合。
- 修饰符:public、缺省。
- public修饰的接口需要单独建一个文件
- 级别:接口是和类并列的一类结构
- 语法:
interface接口名{ 属性; 抽象方法; } class类名extends父类implements接口1, 接口2{ 类的自有属性;类的自有方法;必须实现的接口的抽象方法; }
- 属性为全局常量,
public static final
修饰,不写时仍然为public static final
,一般不写。
- 可以省略任意多个修饰符。
- 声明时必须初始化,因为为final修饰。
- 接口中的抽象方法可以不用
public abstract
修饰,不写时仍然为public abstract
,一般不写(不写时注意不是缺省,override方法时注意权限修饰符范围,即实现类的重写方法权限修饰符必须为public,重写方法不能写abstract)。
- 可以省略任意多个修饰符。
- 不能new
- 接口名有没有abstract修饰不影响
- JDK7之前:只能定义全局常量和抽象方法:没有方法体
- JDK8及之后:还可以定义静态方法、默认方法:有方法体
- 静态方法:static修饰
- 可以通过接口直接调用并执行方法体。
- 实现类可以不用重写接口的静态方法
- 默认方法:default修饰
- 可以通过类对象来调用
- 实现类可以不用重写接口的默认方法
- 注意点:
- 接口不能被实例化(不能声明构造器),匿名接口的方式可以new
- 普通类实现接口,必须实现接口中的所有方法(重写接口中的所有抽象方法)
- 抽象类实现接口,可以不用实现(重写)接口中的方法
- 接口可以相互继承,子类接口含有了直接父类、间接父类的所有属性和方法,其实现接口的类必须实现所有方法
- 接口中属性的访问形式:
接口名.属性名
或实现接口的类名.属性名
、对象名.属性名
(同虚拟类访问静态属性)
类名.属性名
、对象名.属性名
的方式不能和继承的父类中的属性名冲突,否则会报错,原因为不明确
- 继承+实现情况下属性名重复会报错
- 实现+实现情况下属性名重复会报错
- 接口也具有多态性
接口名 变量名 = new 实现接口的类名()
- 类优先原则:
class C extends A implements B
中,如果A、B、C中均定义了同名的属性,实例化C后访问该属性时,返回C中的,如果C中没有,则会报错。
- 如果A中实现了B中的抽象方法,而C中什么也没写,这时根据继承原则,默认C中有了A的方法,从而重写了B中的方法。
- 两个接口同时定义了同名同参的默认方法:
- 如果返回值不一致(权限修饰符均一致), 一个类都实现了接口时,会发生接口冲突(报错),无法通过重写两个同名同参不同返回值类型的方法,因为两个接口不知道谁是它的重写。
- 如果返回值一致,则一个实现类可只写一个重写方法,表示对两个接口方法的重写。
- 一个接口定义了默认方法,一个父类定义了同名同参数的非抽象方法,不会发生冲突,且类优先,接口中定义的同名同参方法会被忽略。
- 调用指定接口的方法:
接口名.super.默认方法名
4.8 一个小结
类 |
内部类 |
属性 |
局部变量 |
方法 |
构造器 |
代码块 |
接口 |
|
public |
是 |
是 |
是 |
否 |
是 |
是 |
否 |
是 |
protected |
否 |
是 |
是 |
否 |
是 |
是 |
否 |
否 |
缺省 |
是 |
是 |
是 |
否 |
是 |
是 |
是 |
是 |
private |
否 |
是 |
是 |
否 |
是 |
是 |
否 |
否 |
- public修饰类和接口时,必须单独设置一个文件。
类 |
内部类 |
属性 |
局部变量 |
方法 |
构造器 |
代码块 |
接口 |
|
static |
否 |
是 |
是 |
否 |
是 |
否 |
是 |
否 |
final |
是 |
是 |
是 |
是 |
是 |
否 |
否 |
否 |
abstract |
是 |
是 |
否 |
否 |
非private的方法 |
否 |
否 |
是 |
- static、final、abstract要写在语句的开头,但与权限修饰符的先后顺序没关系。
- final和abstract修饰符永远二选一(类、内部类、方法)。
- final和static永远可搭配(内部类、属性、方法)
- static和abstract可同时修饰内部类,不可同时修饰方法。