面向对象
Posted daidai66
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面向对象相关的知识,希望对你有一定的参考价值。
面向对象编程思想
面向对象的编程特点:
以类/对象为最基本单位
类与对象
类与对象的概念
类:一类具有相同特性的事物的“抽象”描述。
对象:是类的一个具体的实例,个体。
类是创建对象的模板,设计图。根据某个类创建(new)的对象都具有这个类中声明的特征。
例如:Student是类,张三这个学生是Student的一个具体的对象,实例。
如何声明类
语法格式
【修饰符】 class 类名{ //成员列表 }
如何创建对象
语法格式
//(1)有名字的对象 类名 对象名/变量名 = new 类名(); //(2)匿名对象 例如: System.out.println(new 类名()); new 类名().方法();
看到new,就说明在创建对象
类的成员之一:成员变量
如何声明成员变量
【修饰符】 class 类名{
【修饰符】 数据类型 成员变量名;
}
成员变量声明的位置:类中方法外
成员变量的分类
根据是不是有static修饰来分:
1、静态变量:有static修饰的成员变量,有的时候也称为类变量
2、实例变量:没有static修饰的成员变量
特点
1、成员变量有默认值
byte,short,int,long:默认值是0
float,doube:默认值是0.0
char:默认值是u0000
boolean:默认值是false
其他的引用数据类型:null
2、静态变量的值是该类所有对象共享的;实例变量的值是每一个对象独立的
如何在类外面访问成员变量
1、静态的类变量
(1)类名.静态变量:推荐
(2)对象.静态变量
2、实例变量
只能使用 对象.实例变量
成员变量的内存分析
1、静态的类变量:方法区
2、非静态的实例变量:堆
类的成员之二:成员方法
方法的概念
方法代表一个独立的可复用的功能。
例如:Math.sqrt(x)每一次调用都可以获取某个数的平方根,而我们调用者不需要知道内部是如何实现的。
方法在一个类中,特别标准的Javabean等,方法又代表了事物的行为能力,行为特征。
成员方法分类
1、静态方法:又称为类方法
2、非静态方法:又称为实例方法
如何声明/定义成员方法
1、语法格式
【修饰符】 class 类名{
【修饰符】 返回值类型 方法名(形参列表){
方法体
}
}
一个完整的方法 = 方法头 + 方法体
方法头:【修饰符】 返回值类型 方法名(形参列表),方法头又称为方法签名
我们调用者一般只需要关注“方法签名",内部的方法体实现可以不清楚。
我们设计者/声明者,既需要设计“方法签名”,还得编写”方法体“的实现。
2、说明
(1)【修饰符】:目前只用了static
有static修饰的就是静态方法
没有static修饰的就是非静态实例方法
(2)返回值类型
void:表示无返回结果
基本数据类型:8种
引用数据类型:类、数组、接口、枚举。。。
(3)方法名
见名知意:尽量能够代表方法的功能
从第二个单词开始首字母大写
(4)形参列表
():无参
(数据类型 形参名, 数据类型 形参名 。。。):有参
形参的数据类型,可以是基本数据类型,也可以是引用数据类型。
如何调用
1、是否静态的规则
1、静态类方法
(1)在其他类中
A:类名.方法 推荐
B:对象.方法
(2)在本类中
直接被使用就可以
2、非静态实例方法
(1)在其他类中
只能用 对象.方法
(2)在本类中
除了在静态方法中不能直接使用它,其他地方也是直接使用
2、是否有参
调用时,是否需要传“实参”,看被调用的方法是否有“形参”,
要求实参的个数、类型、顺序要与形参列表一一对应。
3、是否有返回值
void:不能接收,也不能打印调用结果
基本数据类型/引用数据类型:可以接收,也可以不接收。如果要接收,必须使用相同/兼容的数据类型的变量来接收
return
1、形式一:return;
用于返回值类型是void的方法中,
作用是,提前结束当前方法的执行
return;语句是【可选】
2、形式二:return 返回值;
用于返回值类型不是void的方法中,
作用是,结束当前方法的执行,并且返回结果
return 返回值;是【必选】
方法的调用过程的内存分析
1、调用时,“入栈”
表示在栈中开辟一块独立的内存空间,用来存储这个方法的局部变量等。
2、调用结束后,“出栈”
表示释放该方法的栈空间
3、先调用的先入栈,后调用的后入栈
方法的参数传递机制
1、形参是基本数据类型
形参的修改和实参无关
2、形参是引用数据类型
通过形参修改“堆/方法区”的成员变量/元素的值的时候,会影响实参。
需要警惕:形参如果重新指向了新的对象,就和实参无关。
3、实参给形参赋值
形参是基本数据类型:copy了数据值
形参是引用数据类型:copy了对象的地址值,形参就可以通过这个地址值,操作实参在堆中的对象
局部变量与成员变量的区别
1、声明的位置
成员变量:在方法外
局部变量:方法内,包括在方法的(形参)和方法体{变量}
2、在内存中存储位置
成员变量:
静态变量:方法区
实例变量:堆
局部变量:栈
3、作用域
成员变量:没有作用域
在本类中:在静态方法中不能使用非静态成员变量
在其他类中:静态的建议使用“类名.",非静态的只能用"对象."
局部变量:有作用域
4、生命周期
静态变量: 和类一样,很久
实例变量:每一个对象是独立的,随着new在堆中分配,随着GC回收消亡
局部变量:方法调用时,到作用域开始,出了作用域结束
5、修饰符
成员变量:可以有修饰符
局部变量:除了final,不能有其他修饰符
可变参数
1、形式
(数据类型... 形参名) (数据类型 形参名, 数据类型... 形参名)
2、在声明可变参数的方法体中如何可变参数:当数组使用
3、在调用包含可变参数的方法时,可变参数对应的实参:
(1)可以是0~n个对应类型的元素
(2)可以是传入对应的数组
4、可变参数声明的要求
(1)一个方法最多只能有一个可变参数
(2)可变参数必须是形参列表最后一个
方法重载
在一个类,出现了两个或以上的 方法名相同, 形参列表不同(个数、数据类型)的方法。和返回值类型无关。
命令行参数
给main的形参赋值的实参是命令行参数:
java 类名 参数1 参数2 参数3...
public static void main(String[] args){ //args数组就可以接受命令行参数 }
递归
当一个方法直接或间接的调用自己,就称为递归。
注意:
所有递归都必须有终止条件,否则就死循环,出现“栈内存溢出”。
同时递归就算有终止条件,递归的层次不宜太深,容易造成内存溢出,或者运行效率非常低。
对象数组
元素是引用数据类型时,这个数组就是对象数组。
例如:String[],Student[],Circle[]等
注意:
(1)第一步,创建数组对象本身
元素类型[] 数组名 = new 元素类型[长度];
(2)第二步,创建元素对象
数组名[下标] = new 元素类型();
(3)我们后面可以通过元素对象,调用成员变量、成员方法等
System.out.println(数组名[下标].成员变量); 数组名[下标].方法(实参列表)
(4)如果要交换两个对象数组的元素
元素的类型 temp = 数组名[下标1]; 数组名[下标1] = 数组名[下标2]; 数组名[下标2] = temp;
这段代码没有产生新对象,只是借着temp这个变量,交换了两个元素中存储的对象地址。
面向对象的基本特征:封装
封装的好处?
1、调用者:方便使用/简化使用
2、设计者:安全,可控
如何实现封装,控制不同等级的封装
使用权限修饰符
修饰符 | 本类 | 本包 | 其他包的子类 | 其他包的非子类 |
private | √ | × | × | × |
缺省 | √ | √ | × | × |
protected | √ | √ | √ | × |
public | √ | √ | √ | √ |
权限修饰符可以修饰什么?
所有的权限修饰符都可以修饰:成员变量、成员方法、构造器、成员内部类
可以修饰外部类的权限修饰符:缺省和public
属性私有化
1、在属性前面加private
2、根据需求提供get/set方法
public class Student{ private String name; private int age; private boolean marry; public void setName(String name){ this.name = name; } public String getName(){ return name; } public void setAge(int age){ this.age = age; } public int getAge(){ return age; } public void setMarry(boolean marry){ this.marry = marry; } public boolean isMarry(){//boolean类型的属性,它的标准的get方法,用is代替get return marry; } }
包
1、包的作用
(1)控制可见性范围
如果在这个包中的某个类或成员,它的权限修饰符缺省了,就表示这个类或成员仅限于本包使用。
(2)避免类的重名
(3)按照不同的主题来分门别类管理类
因为包在编译后是对用文件夹。
常见的包:
java.lang:核心语言类库,例如:String。System,Math,Thread。。。
java.io:输入输出相关
java.util:各种工具
java.net:和网络编程相关
java.sql:和数据库相关
...
2、如何声明包
package 包名;
位置必须在.java文件的首行,一个.java文件只能有一句
包名的命名规范:(1)所有单词都小写,单词之间使用.分割(2)习惯上用公司域名倒置+模块名
3、如何编译带包的源文件
javac -d . 源文件名.java
-d:创建包目录结构
.:表示在当前目录
4、如何运行带包的类
java 包.类名
5、如何使用其他包的类
(1)使用类的全名称
java.util.Scanner input = new java.util.Scanner(System.in);
(2)使用import语句导包,在代码中使用简名称
import 包名.类名; import 包名.*; //*只能省略最后一级的类名 //静态导入 import static 包名.类名.静态成员; import static 包名.类名.*;
当使用两个不同包,但是类名相同的两个类时,只能一个使用导包,一个使用全名称,不能两个都使用导包。
被使用的其他包的类或成员,它的权限修饰符必须是>缺省
初始化
初始化是干什么的?
目的:为成员变量初始化,赋值。
初始化的方式有四种
1、默认值
2、显式初始化
【修饰符】 class 类名{
【修饰符】 数据类型 成员变量 = 值;
}
3、代码块
【修饰符】 class 类名{ static{ //...静态代码块,给静态变量初始化 } { //...非静态代码块,给实例变量初始化 } }
有的文章中人家把非静态代码块也叫构造块
4、构造器
注意:构造器为实例变量初始化,一般不用它来为静态变量初始化。
有的文章中人家把构造器也称为构造方法
【修饰符】 class 类名{ 【修饰符】 类名(){ //无参构造 } 【修饰符】 类名(形参列表){ //有参构造 } }
特点:
(1)所有类都有构造器
(2)如果一个类没有手动声明构造器,编译器会自动添加一个默认的无参构造;
如果我们手动声明了构造器,那么编译就不会自动添加无参构造了,如果需要,必须手动添加。
(3)构造器的名称必须和类名完全一致。
(4)构造器没有返回值类型
(5)构造器的修饰符:只能有权限修饰符(public,protected,缺省,private),不能有final,abstract,static等
初始化分为两类
1、类初始化
作用:给静态变量初始化
类初始化本质上是在执行一个<clinit>()方法,这个方法是由编译自己组装而成的,编译会把:
(1)静态变量的显式赋值
(2)静态代码块中代码
按照代码编写的顺序进行组装。
每一个类的类初始化方法<clinit>()只会执行一次,第一次使用这个类的时候。
public class Demo{ private static int a = 1; static{ System.out.println("静态代码块1"); } private static int b = 1; static{ System.out.println("静态代码块2"); } }
//组装后 public class Demo{ private static int a = 1; private static int b; <clinit>(){ a = 1; System.out.println("静态代码块1"); b = 1; System.out.println("静态代码块2"); } }
2、实例初始化
作用:创建实例对象时,为实例变量初始化
本质上:实例初始化过程是在执行<init>(【...】)的实例初始化方法。
什么时候执行?每次new对象时执行
执行哪个?看你调用哪个构造器,看new后面。
一个类有几个实例初始化方法?看你这个类有几个构造器。
实例初始化方法<init>(【...】),由编译自己组装而成:
(1)实例变量的显式赋值
(2)非静态代码块
(3)构造器
其中(1)和(2)是按照顺序组装,(3)无论如何都在最后。而且(1)和(2)是在每一个实例初始化方法中都有,而(3)只找对应构造器的
public class Demo{ private int a = 1; { System.out.println("非静态代码块1"); } private int b = 1; public Demo(){ System.out.println("无参构造"); } public Demo(int a, int b){ this.a = a; this.b = b; System.out.println("有参构造"); } { System.out.println("非静态代码块2"); } }
public class Demo{ private int a; private int b; <init>(){ a = 1; System.out.println("非静态代码块1"); b = 1; System.out.println("非静态代码块2"); System.out.println("无参构造"); } <init>(int a, int b){ a = 1; System.out.println("非静态代码块1"); b = 1; System.out.println("非静态代码块2"); this.a = a; this.b = b; System.out.println("有参构造"); } }
3、类初始化与实例初始化的顺序
一定是先完成类初始化,然后再实例初始化
继承
为什么要继承?
目的:
(1)代码复用:
延续父类的特征
(2)代码的扩展:
扩展父类没有的特征
(3)表示is-a的关系
子类 is a 父类的派别。
例如:学生 is a 人。
如何继承
语法格式:
【修饰符】 class 父类{ } 【修饰符】 class 子类 extends 父类{ }
继承的特点
1、Java只支持单继承,每一个子类只有一个直接父类
2、Java支持多层继承,父类还可以有父类
3、子类会继承父类所有特征,包括成员变量、成员方法,哪怕是私有的,私有的在子类中无法直接使用。
4、子类不会继承父类的代码块、构造器等。
5、但是子类一定会调用父类的实例初始化方法。
继承时成员变量问题
1、子类会继承父类的所有的成员变量
子类创建对象时,在堆内存中是要为从父类继承的成员变量分配内存空间。
2、如果子类声明了与父类同名的成员变量
此时,如果想要访问父类的同名的成员变量,那么可以使用super.,但是前提条件,这个成员变量没有私有化。
如果私有化了,只能通过父类中的get/set来操作它。
继承时成员方法问题
1、子类会继承父类的所有的成员方法
2、如果父类的某个方法的方法实现不适用于子类,那么子类可以选择重写
重写的要求:
(1)方法名必须相同
(2)形参列表必须相同
(3)返回值类型
void和基本数据类型:必须相同
引用数据类型:<=
(4)权限修饰符:>=
(5)其他修饰符:不能重写子类中不可见的private,如果跨包的话,缺省的也不能被重写,
final,static
final
final:最终的
1、修饰类:不能被继承
2、修饰方法:不能被重写
3、修饰变量:值不能修改,即常量
this和super
this
1、this表示当前对象
2、它可以出现在:
(1)非静态代码块和构造器:this表示正在new的那个对象
(2)非静态方法:this表示调用这个方法的对象
3、用法:
(1)this.成员变量
当成员变量与局部变量同名时,在成员变量的前面加"this."
这个成员变量可以是本类声明的,也可以从父类继承的,但是要求是在子类中可见的成员变量并且是不和子类的成员变量同名
(2)this.成员方法
这个成员方法可以是本类声明的,也可以从父类继承的,但是要求是在子类中可见的成员方法,并且是没有重写。
(3)this()和this(实参列表)
只能访问本类的其他构造器
而且必须在构造器首行
super
1、super:表示当前对象中从父类继承的
2、通过super访问的成员变量、成员方法、构造器的,都必须在子类中可见
3、用法
(1)super.成员变量
当子类的成员变量与从父类继承的在子类中依然可见的成员变量同名时,可以使用"super.成员变量"来区别
当然,就算不同名,通过"super.成员变量"也可访问到从父类继承的在子类中依然可见的成员变量
(2)super.成员方法
当子类重写了父类的成员方法,又想要调用父类被重写的成员方法时,可以使用“super.成员方法"
当然,不重写的话,也可以通过“super.成员方法"来访问从父类中继承的在子类中依然可以见的成员方法
(3)super()或super(实参列表)
表示访问父类的实例初始化方法
而且必须在子类构造器的首行
this()和this(实参列表) 与 super()或super(实参列表) 不能同时出现
就近原则
super一定是从当前对象类型的直接父类开始找
this一定是从当前对象类型的本类的成员开始找
即没有this又没有super,如果是变量一定是从局部变量开始找,如果是方法就从当前对象类型的本类的方法开始找
有继承情况下的初始化
类初始化
类初始化是执行<clinit>()方法,它的代码由两部分组成:
(1)静态变量的显式赋值
(2)静态代码块
它俩是按照编写的顺序组装而成。
每一个类的类初始化方法只会执行一次。
子类初始化时会先检查父类,如果父类还没有初始化,会先完成父类的初始化,即先执行父类的<clinit>()方法
实例初始化
一个类可能会有1~n个的<init>方法,有几个看你声明了几个构造器;
实例初始化是执行对应的<init>方法,具体执行哪个,看你new后面调用的是哪个构造器;
实例初始化由以下四个部分组成:
①super()或super(实参列表) ==> 调用父类的对应的实例初始化方法,说明创建子类对象时,也会导致父类的实例初始化方法执行的
其中super()或super(实参列表)是原先写在构造器首行的
②实例变量的显式赋值
③非静态代码块
④构造器中的代码
其中②和③是按代码中编写的顺序组装,①和④是一头一尾
实例初始化,每次new对象时执行,new一个执行一个
类初始化和实例初始化
如果第一次使用某个类时就是在创建对象,那么要先完成类初始化,然后再执行实例初始化。
抽象类
什么情况下会需要用到抽象类
1、在编写某个父类时,发现某个方法的方法体无法给出具体的实现,但是父类又需要声明这个方法以代表事物的特征,那么此时就只能把这个方法声明为抽象方法,一旦某个类包含抽象方法,那么这个类就必须是抽象类
2、在编写某个父类时,可能没有包含抽象方法,但是不希望用户创建这个父类的对象,希望用户创建它的子类对象时,那么也可以把这个父类声明为抽象类。
如何声明抽象类和抽象方法
抽象类的语法格式
【其他修饰符】 abstract class 抽象类名{ }
抽象方法的语法格式:
【其他修饰符】 abstract 返回值类型 方法名(【形参列表】);
抽象方法没有方法体
抽象类的特点
1、抽象类不能直接创建对象
2、抽象类就是用来被继承的,子类继承它时,必须重写/实现抽象类的所有抽象方法,否则子类也得时抽象类
3、抽象类也有构造器,给子类调用的
4、如果类中有抽象方法,那么必须是抽象类,但是反过来,抽象类中可以没有抽象方法。
多态
多态的概念
从逻辑意义上说,多态就是同一类事物的某一种行为,在不同的子类中表现出不同的形态。例如:求面积,不同的图形,求面积的方式不同。
从语法角度来说,多态就是在运行期间才能确定调用哪个类的方法,即执行子类重写的方法。
多态的前提
1、继承
2、重写
静态方法
final方法
private方法
属性
这些不能重写的就不可能有多态(即静态绑定)。只有能重写的方法才有多态(即动态绑定)。
3、多态引用/向上转型
父类的变量/形参 指向了子类的对象
多态的现象
编译时看父类,即通过父类的变量只能引用父类中有的方法
运行时看子类对象,即运行时一定执行子类“重写”的方法
多态的应用
1、多态数组
元素的类型是父类的类型,存进去的元素对象是子类的对象
2、多态参数
形参是父类的类型,传入的实参是子类的对象
类型转换
1、向上转型upcasting
当把子类的对象赋值给父类的变量/形参时,就会发生向上转型。
向上转型的后果,在编译期间只能调用父类中有的方法、成员变量等。
2、向下转型downcasting
当想要调用子类特有的方法时,需要把这个父类的变量再向下转型为子类的类型。
向下转型有风险,可能会发生ClassCastException异常,如果想要避免这个异常,最好在向下转型之前,用instanceof进行判断。
class Person{ //... } class Man extends Person{ //... } class Woman extends Person{ //... } class ChineseMan extends Man{ /// }
Person p1 = new Person(); Person p2 = new Man(); Person p3 = new Woman(); Person p4 = new ChineseMan(); Man m5 = new ChineseMan(); //把以上上面的5个对象转为ChineseMan类型,哪些向下转型会成功? ChineseMan c1 = (ChineseMan) p1;//ClassCastException ChineseMan c2 = (ChineseMan) p2;//ClassCastException ChineseMan c3 = (ChineseMan) p3;//ClassCastException ChineseMan c4 = (ChineseMan) p4;//可以 ChineseMan c5 = (ChineseMan) m5;//可以 if(p1 instanceof Person) true if(p1 instanceof Man) false if(p1 instanceof Woman) false if(p2 instanceof Person) true if(p2 instanceof Man) true if(p2 instanceof Woman) false if(p4 instanceof Person) true if(p4 instanceof Man) true if(p4 instanceof Woman) false if(p4 instanceof ChineseMan) true
无论是向下转型还是instanceof,父类的变量中一定执行的对象的实际类型是<=向下转型或判断的类型。
例如:p4指向的是ChineseMan的对象,它是<=Person,<=Man,<=ChineseMan
关键字:native
native:原生的,本地的
native:用来修饰方法。表示这个方法的方法体不是用Java语言实现的,而是用C/C++语言实现的,编译为.dll文件,由Java代码进行调用。
在Java层面使用native的方法和使用普通的Java方法一样。如果是非静态的,那么使用“对象”调用,如果是静态的,那么使用“类名”调用,如果子类继承后想要重写,也可以使用Java代码进行重写。
在运行时native方法的入栈是在本地方法栈,Java实现的方法的入栈是在虚拟机栈。
java.lang.Object类
根父类
Object类是所有类型的超类,即根父类。包括数组类型在内的所有引用数据类型。
如果一个类没有显式的声明它的直接父类,那么它的父类就是Object。
所有的对象(包括数组对象)都可以调用从Object类继承的方法(11个)。
Object类型的变量/形参可以赋值为任意类型的对象。
Object类的几个方法
1、toString()
public String toString():把一个对象的信息用一个字符串表示,尽量要能够简单易懂,建议子类重写。
如果没有重写,默认 “类型名@对象hashCode的十六进制值”。
eclipse中Alt +Shift +S,选择toString。
注意:当我们打印一个对象,或者一个对象与String数据进行拼接是,都会自动调用这个对象的toString。
2、getClass()
public Class<?> getClass():获取对象的“运行时”类型。
3、finalize()
public void finalize():
什么时候调用?谁调用?调用特定是什么?
当一个对象被确定为垃圾时,由GC垃圾回收器来进行调用,每一个对象的finalize()方法只会被调用一次。
一般是与系统有关的资源对象需要重写这个方法。
4、hashCode()
public int hashCode():返回一个对象的hashCode值。
常规协定:
(1)如果一个对象的参与计算hashCode值的成员变量没有修改,那么在程序运行期间,每次获取的hashCode值不变。
(2)如果两个对象hashCode不同,那么这两个一定不“相等”
(3)如果两个的hashCode值相同,那么这两个对象不一定“相等”
(4)如果两个相等,那么他们的hashCode值一定要相同。
5、equals()
public boolean equals(Object obj):用于判断当前对象与指定对象是否相等。
默认的实现,等价于“==”,比较对象的内存地址。
我们子类可以选择重写,重写有一些要求:
(1)重写equals时,一定要重写hashCode方法
(2)还需要遵循几个原则:
A:一致性:如果参与equals比较的成员变量没有修改,在程序运行期间比较的结果不变。
B:对称性:x.equals(y)与y.equals(x)的结果一致
C:自反性:x.equals(x)一定返回true
D:传递性:x.equals(y)如果是true,y.equals(z)如果是true,那么x.equals(z)也是true
E:非空对象与null的equals一定是false
eclipse中Alt +Shift +S,选择generate hashCode和equals....
枚举
枚举是一种特殊的类
特殊:它的对象是有限固定的几个常量对象。
如何声明枚举
JDK1.5之前没有枚举的概念,但是可以实现这种效果:
(1)把构造器私有化
(2)在类型的内部使用几个静态的常量来保存它的对象
public Season{ public static final Season SPRING = new Season(); public static final Season SUMMER = new Season(); public static final Season FALL = new Season(); public static final Season WINTER = new Season(); private Season(){ } }
JDK1.5之后开始有枚举的概念,实现比较简单:
【修饰符】 enum 枚举类型{ 常量对象列表; 其他成员列表 }
要求:
(1)常量对象列表; 必须在首行
(2)如果枚举的常量对象列表后面没有其他成员,那么结尾的;可以省略,但是如果后面还有其他成员,那么;不能省略。
(3)枚举类型的构造器,只能使用private修饰,那么你省略private也是私有的。
public enum Week{
MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY,SUNDAY
}
public enum Week{ MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY,SUNDAY; private String description; //... }
首行:
(1)super()或super(实参列表):调用父类的构造器,本质上是调用父类的实例初始化方法<init>
(2)this()或this(实参列表):调用本类的其他构造器
(3)package 语句,在源文件的首行。
(4)枚举类型的常量对象在枚举类的首行
枚举类型的其他特点
枚举类型有一个公共的基类:java.lang.Enum类,即枚举类型不能再继承别的类型。
Enum类只有一个唯一的构造器:
protected Enum(String name, int ordinal)
name代表的是常量对象名,ordinal代表的是常量对象的序号。
它的toString()默认返回的是常量对象名,如果要重写,只能手动重写,不能使用eclipse的toString生成。
还有两个API中没有的方法:
枚举类型[] values():
枚举类型 valueOf("常量对象名")
switch在JDK1.5之后,对枚举开始支持。switch(表达式)其中的表达式可以是枚举类型的变量,case后面写枚举的常量对象名。
包装类
为什么要有包装类
Java中的很多特性和API不支持基本数据类型,因为基本数据类型不是对象。所以我们必须把基本数据类型的数据使用它们的包装类进行包装。
包装类有哪些
基本数据类型 | java.lang包装类 |
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
void | Void |
类型转换
1、装箱与拆箱
装箱:把基本数据类型-->包装类对象
手动装箱:例如:new Integer(int值) 或 Integer.valueOf(int值)
自动装箱:Integer i = int值;
拆箱:把包装类的对象-->基本数据类型
手动拆箱:例如:Integer的对象.intValue()
自动拆箱:int i = Integer的对象;
2、基本数据类型与字符串之间转换
基本数据类型-->字符串: 直接+""
字符串-->基本数据类型:
Integer.parseInt(字符串)
Double.parseDouble(字符串)
....
包装类有缓存对象
Byte | -128~127 |
Short | -128~127 |
Integer | -128~127 |
Long | -128~127 |
Float | 没有 |
Double | 没有 |
Character | 0~127 |
Boolean | true,false |
包装类对象不可变
一旦修改就是新对象。
容易出现在面试题的“方法的参数传递”类型的题目中。
public static void change(Integer value){ value = xxx; } public static void main(String[] args){ Integer i = 10; change(i); System.out.println("i=" + i);//10 }
接口
概念
接口是一种标准,行为规范。比抽象类还要抽象的类型。
最早接口中都是抽象方法,没有具体方法,从JDK1.8之后,开始支持默认方法,静态方法,从JDK1.9之后又增加私有方法。
如何声明接口
语法格式:
【修饰符】 interface 接口名{ //... }
接口的成员
1、公共的静态的常量:public static final
2、公共的抽象的方法:public abstract
非抽象的实现类必须重写
3、公共的默认方法:public default,JDK1.8之后
使用“实现类的对象."进行调用
实现类可以选择重写
4、公共的静态方法:public static, JDK1.8之后
只能使用”接口名.“进行调用
实现类不能重写
5、私有的方法:private(private不能省略)JDK1.9之后
关于默认方法可能有冲突问题:
(1)一个实现类实现了多个接口,而多个接口中出现了方法签名相同的默认方法时:
实现类必须做出选择:
A:保留其中一个:接口名.super.方法名
B:也可以完全重写
(2)一个实现类既继承父类,又实现接口,当父类中出现与接口的默认方法的方法签名相同的方法:
A:默认遵循亲爹原则,即保留父类的
B:也可以完全重写
接口的其他特点
1、接口不能直接创建对象
2、一个类能同时实现多个接口
【修饰符】 class 实现类 [extends 父类] implements 接口们{ }//继承在前,实现在后
实现类实现接口时,必须重写接口的所有抽象方法,否则实现类就必须是抽象类。
3、接口与接口之间支持多继承
【修饰符】 interface 接口 extends 接口们{ }
常见的接口
1、java.lang.Comparable接口:自然排序
它的抽象方法:
int compareTo(Object obj)
规则:
当前对象this 大于 指定对象obj 返回正整数
当前对象this 小于 指定对象obj 返回负整数
当前对象this 等于 指定对象obj 返回0
哪个类的对象需要比较大小,排序,哪个类就实现这个接口即可。
class Student implements Comparable{ private int id; private String name; private int score; //.... public int compareTo(Object obj){ Student other = (Student)obj; //例如:按照学号比较 return this.id - other.id; } }
Student s1 = new Student(....); Student s2 = new Student(....); if(s1.compareTo(s2)>0){ //... }else if(s1.compareTo(s2)<0){ //.. }else{ //... }
Student[] arr = new Student[5]; //.... Arrays.sort(arr);//按照Student的compareTo方法实现的排序规则进行排序
2、java.util.Comparator接口:定制排序、挽救的排序
抽象方法:
int compare(Object o1, Object o2)
用 比较器对象.compare(要比较大小的对象1, 要比较大小的对象2)
当某个类的对象不支持自然排序或者是自然排序规则不符合当前的需求时,可以单独使用定制比较器进行排序。
//它的自然排序是按照学号比较大小 //新的需求是要求按照分数比较大小,如果分数相等,再按照学号比较,这个时候就可以单独再编写一个Commparator接口的实现类 class StudentScoreComparator implements Commparator{ public int compare(Object o1, Object o2){ Student s1 = (Student)o1; Student s2 = (Student)o2; if(s1.getScore() != s2.getScore()){ return s1.getScore() - s2.getScore(); } return s1.compareTo(s2); } }
Student s1 = new Student(....); Student s2 = new Student(....); StudentScoreComparator ssc = new StudentScoreComparator(); if(ssc.compare(s1,s2)>0){ //... }else if(ssc.compare(s1,s2)<0){ //.. }else{ //... }
Student[] arr = new Student[5]; //.... StudentScoreComparator ssc = new StudentScoreComparator(); Arrays.sort(arr,ssc);//按照StudentScoreComparator的compare方法实现的排序规则进行排序
3、java.lang.Cloneable接口
如果某个类想要重写Object类的clone()来支持对象的克隆功能,那么要求这个类型必须实现java.lang.Cloneable接口,这个接口没有抽象方法,只是一个标识型接口,如果不实现这个接口,调用clone()时,会报CloneNotSupportedException。
内部类
什么情况下需要用内部类
当在描述一个比较复杂的事物时,发现它的内部需要另一个完整的结构来辅助它的描述。这个内部的结构只为外部类服务,并且需要直接访问外部类的私有的数据等的时候,这样的情况就可以把这个内部的结构声明为内部类。
内部类的分类
1、成员的内部类:声明在外部类中方法外
有static修饰的:静态成员内部类,简称静态内部类
没有static修饰的:非静态成员内部类,一般说的成员内部类就是它
2、局部的内部类:声明在外部类的方法体中
分为有名字的局部内部类和匿名的局部内部类
静态内部类
1、语法结构:
【修饰符】 class 外部类 【extends 父类】 【implements 接口们】{ 【其他修饰符】 static class 静态内部类 【extends 父类】 【implements 接口们】{ } }
外部类的权限修饰符:public或缺省,其他修饰符:abstract或final
静态内部类的权限修饰符:四种,其他修饰符:abstract或final + static
2、静态内部类也是类
(1)可以有自己的父类或父接口
(2)有自己的字节码文件:外部类名$静态内部类名.class
(3)有自己的成员:没有限制
只要在类中可以写的成员它都可以有,只有静态内部类允许有静态成员。
3、静态内部类的使用
(1)静态内部类使用外部类的成员时:
有限制:静态内部类不能使用外部类的非静态成员
(2)在外部类中使用静态内部类
使用静态内部类的静态成员:直接使用“静态内部类名."
使用静态内部类的非静态成员:使用“静态内部类的对象."
(3)在外部类的外面使用静态内部类
前提:这个静态内部类在外面是可见,否则只能通过这个静态内部类的父类或父接口来引用它的对象。
如果可见,使用静态内部类的静态成员:直接使用“外部类名.静态内部类名."
使用静态内部类的非静态成员:使用“静态内部类的对象."
外部类.静态内部类 对象名 = new 外部类.静态内部类(【...】);
非静态内部类
1、语法结构:
【修饰符】 class 外部类 【extends 父类】 【implements 接口们】{ 【其他修饰符】 class 非静态内部类 【extends 父类】 【implements 接口们】{ } }
外部类的权限修饰符:public或缺省,其他修饰符:abstract或final
非静态内部类的权限修饰符:四种,其他修饰符:abstract或final
2、非静态内部类也是类
(1)可以有自己的父类或父接口
(2)有自己的字节码文件:外部类名$非静态内部类名.class
(3)有自己的成员:有限制
不能有静态成员,除非静态常量。
3、非静态内部类的使用
(1)非静态内部类使用外部类的成员时:没有限制
(2)在外部类中使用非静态内部类
在外部类的静态成员中使用无法使用非静态内部类的。
在外部类的非静态成员中使用非静态内部类,使用“非静态内部类的对象."
(3)在外部类的外面使用非静态内部类
前提:这个非静态内部类在外面是可见,否则只能通过这个非静态内部类的父类或父接口来引用它的对象。
如果可见,使用非静态内部类的非静态成员:要求必须有外部类的对象的引用
//原始写法 外部类 out = new 外部类(【...】); 外部类.非静态内部类 对象名 = out.new 非静态内部类(【...】); class Outer{ class Inner{ //... } } Outer out = new Outer(); Outer.Inner in = out.new Inner();
//在实际开发中,在外部类中编写一个方法来返回非静态内部类的对象,然后调用这个方法来获取非静态内部类对象 class Outer{ class Inner{ //... } public Inner getInner(){ return new Inner(); } } Outer out = new Outer(); Outer.Inner in = out.getInner();
有名字的局部内部类
1、语法结构:
【修饰符】 class 外部类 【extends 父类】 【implements 接口们】{ 【修饰符】 返回值类型 方法名(【形参列表】){ 【abstract或final或没有】 class 局部内部类 【extends 父类】 【implements 接口们】{ } } }
外部类的权限修饰符:public或缺省,其他修饰符:abstract或final
局部内部类没有权限修饰符,其他修饰符:abstract或final
2、局部内部类也是类
(1)可以有自己的父类或父接口
(2)有自己的字节码文件:外部类名$编号局部内部类名.class
(3)有自己的成员:有限制
不能有静态成员,除非静态常量。
3、局部内部类的使用
(1)局部内部类使用外部类的成员时:有限制
能否使用外部类的非静态成员,看所在的方法是否是静态的,如果是静态就不能用。
(2)在外部类中使用局部内部类
有作用域,即只能在这个方法体中,并且在声明之后。
(3)在外部类的外面使用局部内部类:不能
(4)在外部类的外面可以得到局部内部类的对象
但是需要通过它的父类或父接口的变量来引用,用方法的返回值返回。
(5)在局部内部类中如果要使用外部类的局部变量,需要有final。
匿名内部类
1、语法格式
new 父类(){ //可以写方法.... } new 父类(实参列表){ //可以写方法.... } new 父接口(){ //可以写方法.... }
2、匿名内部类的对象一般会三种形式使用它
父类/父接口 变量 = new 父类/父接口(【实参列表】){ //可以写方法.... };
new 父类/父接口(【实参列表】){ //可以写方法.... }.方法(【实参列表】);
方法名(new 父类/父接口(【实参列表】){ //可以写方法.... });
interface A{ void a(); } A obj = new A(){ public void a(){ //.... } }; obj.a();
new Object(){ public void method(){ //... } }.method();
interface A{ void a(); } class Test{ public static void main(String[] args){ method(new A(){ public void a(){ //.... } }); } public static void method(A a){ a.a(); } }
注解
注解也是一种注释
(1)和普通注释相同:它本身不会改变程序的逻辑,只是对程序添加一些注释性的内容
(2)和普通注释不同:注解的注释内容是可以被另一段程序读取的
一个完整的注解应该有三个部分:
(1)声明: @interface
(2)使用: @注解名 或 @注解名(....)
(3)读取:
SOURCE:只能由编译器读取
CLASS:可以由类加载器读取
RUNTIME:可以通过反射的代码在运行时读取
系统的三个最基本的注解
1、@Override
(1)用在重写的方法上
(2)让编译器帮我们检查这个方法是否遵循重写的要求
方法名:相同
形参列表:相同
返回值类型:
基本数据类型和void:相同
引用数据类型:<=
权限修饰符:>=
其他修饰符:不能是static,final,private
throws:
如果父类被重写的方法没有抛出编译时异常,那么重写的方法也不能抛
如果父类被重写的方法抛出了编译时异常,那么重写的方法只能抛出<=它的异常
2、@Deprecated
(1)用在已过时的xx上面
3、@SuppressWarnings
(1)用于抑制警告
(2)格式
@SuppressWarnings("all") @SuppressWarnings("uncheck") @SuppressWarnings({"unused","uncheck"}) ...
JUnit的几个注解
标记在公共的类,方法需要是公共的、无参、无返回值的。
1、@Test:表示单元测试方法
2、@Before:在每一个单元测试方法之前运行
3、@After:在每一个单元测试方法之后运行
4、@BeforeClass:在所有测试方法之前运行,只运行一次,方法必须是static
5、@AfterClass:在所有测试方法之后运行,只运行一次,方法必须是static
四个元注解
元注解:给注解加注解信息的注解
1、@Target:指定注解的使用位置
@Target的位置由ElementType枚举类型的10个常量对象来指定
@Target(ElementType.METHOD) @Target({ElementType.METHOD,ElementType.FIELD, 。。。。})
2、@Retention:指定注解的滞留阶段
@Retention的阶段由RetentionPolicy枚举类型的3个常量对象来指定
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.CLASS) @Retention(RetentionPolicy.RUNTIME)
3、@Documented:标记这个注解是否要被记录到javadoc文档中
4、@Inherited:标记这个注解是否可以被子类继承
自定义注解
1、语法格式
@元注解 【修饰符】 @interface 注解名{ } 或 @元注解 【修饰符】 @interface 注解名{ 数据类型 参数名1(); 数据类型 参数名2() default 默认值; }
2、配置参数的要求
(1)声明时,类型有要求:
8种基本数据类型、String、枚举、Class、注解,以及他们的数组
(2)使用时,
只要声明了参数,使用这个注解时,就要为它赋值,赋值的格式:(参数名1=参数值,参数名2=参数值)
当参数名是value时,可以省略“value=”
当参数有默认值,也可以不赋值
以上是关于面向对象的主要内容,如果未能解决你的问题,请参考以下文章