面向对象的两个要素
- 类(Class):是对一类事物的描述,是抽象的、概念上的定义
- 对象(Object):是实际存在的该类事物的每个个体,因而也称为实例(instance)
面向对象程序设计的重点是类的设计
设计类,就是设计类的成员
目录
文章目录
Java类及类的成员
属性:应对类中的成员变量 属性=成员变量=Field=域、字段
行为:应对类中的成员方法 (成员)方法=函数=Method
创建类的对象=类的实例化=实例化类
class Person{
//属性,或成员变量
String name;
Boolean isMarried;
//构造器
public Person(){}
public Person(String n,boolean im){
name=n;isMarried=im;
}
//方法,或函数
public void walk(){
System.out.prinln("人走路……");
}
public String display(){
return "名字是:"+name+",Married:"+isMarried;
}
//代码块
{
name="SunJianFeng";
age=19;
isMarried=false;
}
//内部类
class pet{
String name;
float weight;
}
}
p1.name=Tom;
p1.age=1;
person p3=p1;
//将p1的变量保存的对象地址值赋给p3,导致p1和p3指向了堆空间中的同一个对象实体。
System.out.println(p3.name);//Tom
p3.age=10;
System.out.println(p1.age);//10
对象的内存解析
内存结构
栈(stack)
局部变量
堆(heap)
new出来的结构:对象、数组
方法区(Method Area)
常量池
静态域
Person p1=new Person();
p1.name="Tom";
p1.isMale=true;
Person p2=new Person();//独立生成,使用互不影响
System.out.println(p2.name);
Person p3=p1;//引用地址,
p3.age=10;
类和对象的使用(面向对象思想落地的实现)
- 创建类,设计类的成员
- 创建类的对象
- 通多”对象.属性“或”对象.方法“调用对象的结构
public class Test04 {
public static void main(String[]args){
//创建Person类的对象
Person p1=new Person();
//Scanner sc=new Scanner(System.in);
//调用对象的结构:属性、方法
//调用属性:"对象.属性"
p1.name="Tom";
p1.isMale=true;
System.out.println(p1.name);
//调用方法:"对象.方法"
p1.eat();
p1.sleep();
p1.talk("Chinese");
}
}
class Person {
//属性
String name;
int age = 1;
boolean isMale;
//方法
public void eat() {
System.out.println("人可以吃饭");
}
public void sleep() {
System.out.println("人可以睡觉");
}
public void talk(String language) {
System.out.println("人可以说话,使用的是" + language);
}
}
- 如果创建了一个类的多个对象,则每个对象都独立的拥有一套类的属性。(非static的)
类中属性的使用
属性(成员变量)和局部变量
相同点
- 定义变量的格式:数据类型 变量名=变量值
- 先声明,后使用
- 变量都有其对应的作用域
不同点
-
在类中声明的位置不同
-
属性:直接定义在类的一对{}内
-
局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量
class user{//默认为default String name; int age; boolean isMale; //属性(成员变量) public void talk(String language){//language为形参(局部变量) System.out.println("我们使用"+langrage+"进行交流"); } public void eat(){ String food="披萨";//方法内,局部变量 System.out.println("北方人喜欢吃:"+food); } }
-
-
关于权限修饰符的不同
- 属性:可以在声明属性时,指明其权限,使用权限修饰符
- 常用的权限修饰符:public、protected、default(默认、缺省)、private
- 目前,声明属性时,都使用缺省就可以了
- 局部变量:不可以使用权限修饰符
- 属性:可以在声明属性时,指明其权限,使用权限修饰符
-
默认初始化值不同
-
属性:根据其类型都有初始化值
类型 举例 初始化值 整型 byte、short、int、long 0 浮点型 float、double 0.0 字符型 char 0(或’\u0000’) 布尔型 boolean false 引用的数据类型 类、数组、接口 null -
局部变量:没有默认初始化值
- 意味着,我们在调用局部变量之前,需要赋值
- 形参在调用时,我们赋值即可
-
内存中加载的位置不同
- 属性:加载到堆空间中(非static)
- 局部变量:加载到栈空间中
-
类中方法的声明和使用
-
方法:描述类应该具有的功能
- Math类:sqrt(开方) random(随机数)……
- Scanner类:nextXxx()……
- Arrays类:sort(排序) binarySearch(查找) toString(输出) equals(比较数组)
-
方法的分类
无返回值 有返回值 无形参 public void eat(){} public String getName(){} 有形参 public void sleep(int hour){} public String getNation(String nation){}
class Customer{
//属性
String name;
int age;
boolean isMale;
//方法
public void eat(){
System.out.println("客户吃饭");
}
public void sleep(int hour){
System.out.println("休息了"+hour+"个小时")
}
public String getName(){
return name;
}
public String getNation(String nation){
String info="我的国籍时:"+nation;
return info;
}
}
方法的声明
权限修饰符 返回值类型 方法名(形参列表){
方法体
}
修饰符static、final、abstract除外
权限修饰符
public、protected、default(默认、缺省)、private
public | protected | default | private | |
---|---|---|---|---|
同一个类 | ✓ | ✓ | ✓ | ✓ |
同一个包 | ✓ | ✓ | ✓ | ✕ |
子父类 | ✓ | ✓ | ✕ | ✕ |
不同包 | ✓ | ✕ | ✕ | ✕ |
返回值类型
若有方法有返回值,则必须在方法声明时指定返回值类型.
同时,方法中需要使用return关键字来返回指定类型的变量或常量.
若无返回值,使用void来表示.通常没有返回值的方法中就不需要使用return,
但是,可以用"return;"来结束方法
方法名
属于标识符,遵循标识符的规则和规范,
- “见名知意”
- 小驼峰
形参列表
方法可以声明0个/1个/或多个形参
数据类型1 形参1,数据类型2 形参2,....
(int a,double b,...)
方法体
方法功能的体现
return关键字的使用
- 作用范围:使用在方法体中
- 作用
- 结束方法
- 正对于有返回值类型的方法,使用"return 数据"方法返回所要的数据.
- 注意点:return关键字后面不可以声明执行语句.
this关键字的使用
- 可以用来修饰、调用:属性、方法、构造器
- this修饰属性和方法
- this理解为:当前对象
- 在类的方法中,我们可以使用‘this.属性”或“this.属性”的方式,调用当前对象属性或方法。但是通常情况下,我们都选择省略“this.”。特殊情况下,如果方法的形参和类的属性同名时,我们必须显式的使用“this.变量”的方式,表名此变量时属性,而非形参。
- this调用构造器
- 我们在类中的构造器,可以显式的使用“this(形参列表)”方式,调用本类中指定的其他构造器。
- 构造器不能通过“this(形参列表)”方式调用自己。
- 如果类中有n个构造器,则最多有n-1个构造器中使用了“this(形参列表)”
- 规定:“this(形参列表)”必须声明在当前构造器的首行
- 构造器内部,最多只能声明一个“this(形参列表)”,用来调用其他的构造器
package关键字
包
为了更好的实现项目中类的管理,提供包的概念
- 使用package声明类或者接口所属的包,声明在源文件的首行
- 属于标识符,遵循标识符的命名规则、规范(xxxyyyzzz)、见名知意
- 每“.”一次,就是代表一层文件目录
补充
同一个包下,不能命名同盟的接口、类
JDK中主要的包
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
import关键字
导入
- 在源文件中显式的使用import结构导入指定包下的类、结构
- 声明在包的声明和类的声明之间
- 如果需要导入多个结构,则并列写出即可(或使用xxx.*;的方式,表示导入xxx包下所有结构)
- 如果使用的类或接口时java.lang包下(System String……)或本包下的类或接口,则可以省略
/*
*如果在源文件中,使用了不同包下的同名的类,
*则必须至少有一个类需要以全名的方式显示
*/
import java.util.Date;
Date date=new Date();
//全类名的方式显示
java.sql.Date date=new java.sql.Date();
-
xxx.*方式可以调用xxx包下的所有结构,并不包括xxx子包下的结构
-
import.static:导入指定类或接口中的静态结构:属性、方法。
import static java.lang.System.*; import static java.lang.Math.*; main{ out.println("正常输出"); lang num=round(123.434);//=Math.round(); }
匿名对象的使用
- 理解:我们创建的对象,没有显式的赋给一个变量名。即为匿名对象。
- 特征:匿名对象只能调用一次。
- 栈空间不记名,但堆有,
phone c=new pohone();
new phone();
万事万物皆对象
- 在Java语言范畴中,我们都将功能、结构等封装到类中,通过类的实例化,来调用具体的功能结构。
- Scanner,String等
- 文件:File
- 网络资源:URL
- 涉及到Java语言与前端HTML、后端的数据库交互时,前后端在Java层面交互时,都体现为类、对象。
MVC的设计模式
MVC是常用的设计模式之一,将整个程序分为三个层次:视图模型层(V),控制器层(C),与数据模型层(M)。这种将程序输入输出、数据处理,以及数据的展示分离开来的设计模式是程序结构变的灵活而且清晰,同时也描述了程序各个对象间的通信方式,降低了程序的耦合性。
模型层 model 主要书处理数据
- 数据对象封装 model.bean/domain
- 数据库操作类 model.dao
- 数据库 model.db
控制层 controller 处理业务逻辑
- 应用界面相关 controller.activity
- 存放fragment controller.fragemt
- 显示列表的设配器 controller.adapter
- 服务相关的 controller.service
- 抽取的基类 controller.base
视图层 view 显示数据
- 相关工具类 view.utils
- 自定义类 view.ui
构造器
构造方法,Constructor
用来创建对象
初始化信息
-
如果没有显式的定义类的构造器的话,则系统默认提供一个空参的构造器
-
定义构造器的格式:
权限修饰符 类名(形参){} class person{ public person(){ } public person(String n,int m){ name=n; age=m; } }
-
一个类中定义多个构造器,彼此构成重载。
-
一旦我们显式的定义了类的构造器之后,系统就不再提供默认的空参构造器。
-
一个类中,至少会有一个构造器
属性赋值的先后顺序
先>>后
- 默认初始化值(null、0)
- 显式初始化-方法的属性
- 构造器中初始化-构造器的属性
- 通过”对象.方法“或”对象.属性“
JavaBean
JavaBean是一种Java语言写成的可重用组件
- 符合如下标准的Java类
- 类是公共的
- 有一个二无参的公共的构造器
- 有属性,且有对应的get、set方法
- 用户可以使用JavaBean将功能、处理、值数据库访问和其他任何可以用Java代码创造的对象进行打包,并且其他的开发者可以通过内部JSP页面、Serblet、其他JavaBean、applet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变。
UML类图
类名 |
---|
-属性名:属性类型 |
方法类型(+、-)方法名(参数名;参数类型):返回值类型 有下划线的表示构造器 |
- +表示public;-表示private;#表示protected
Debug
操作 | 作用 |
---|---|
step into | 进入当前所调用的方法中 |
step over | 执行完当前的语句,进入下一行 |
step return | 执行完当前所在的方法,进入下一行 |
drop to frame | 回到当前行所在方法的第一行 |
resume | 执行完当前行所在断点的所有代码,进入下一个断点,如果没有就结束 |
Terminate | 停止JVM,后面的程序不会再执行 |
封装和隐藏
当我们创建一个类的对象以后,我们可以通过”对象.属性“的方式,对对象的属性进行赋值。这里,赋值操作属性收到属性的类型和存储范围的制约。除此之外,没有其他制约条件。但是,在实际问题中,我们往往需要给属性赋值加入额外的限制条件。这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加。(比如:setXxx、getXxx)
同时,我们需要避免用户再使用”对象.属性“的方式对属性赋值。则需要将属性声明为私有的(private)
此时体现了封装性。
-
高内聚,低耦合
-
高内聚:类的内部数据操作细节自己完成,不允许外部干涉;
-
低耦合:仅对外暴露少量的方法用于使用。
-
-
隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。
public class AnimalTest{
Animal a=new Animal();
}
class Animal{
String name;
int age;
int legs;
public void eat(){
System.out.println()
}
}
封装性的体现
我们将类的属性XXX私有化(private),同时提供公共的(public)方法来获取(getXXX)和设置(setXXX)此属性值。
权限修饰符
Java规定的4种(由小到大):private、缺省、protected、public
修饰符 | 类内部 | 同一个包 | 不同包的子类 | 同一个工程 |
---|---|---|---|---|
private | Y | |||
缺省(default) | Y | Y | ||
protected | Y | Y | Y | |
public | Y | Y | Y | Y |
- 可以用来修饰类及类的内部结构:属性、方法、构造器、内部类
- 修饰类的话只能用public和(缺省)
继承
- 减少了代码的冗余,提高了代码复用性
- 便于功能的扩展
- 为之后多态性的使用,提供了前提
继承性的格式
extends 延申、继承、扩充
class A extends B{
}
// A:子类、派生类、subclass
// B:父类、超类、基类、superclass
//一旦子类A继承父类B以后,子类A中就获取父类B中声明的所有属性和方法
-
父类中声明的private的属性或方法,子类继承父类以后,仍认为
获取了父类中私有的结构
。- 因为
封装性的影响
,所以子类并不能直接调用
父类的结构而已。
- 因为
-
子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。
继承的规定
- Java只支持单继承和多层继承,不允许多继承
- 一个子类只能有一个父类,一个父类可以派生出多个子类(类的单继承性)
- 子类继承父类以后,就获取了直接父类 以及所有间接父类 中声明的属性和方法(多层继承)
- 如果我们没有显式的声明一个类的父类的话,则此类继承与java.lang.Objext类
- 所有的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类
- 意味着,所有的java类都具有java.lang.Object类声明的功能
方法的重写
override/overwrite
子类继承父类以后,对父类中同名同参数的方法,进行
重置、覆盖
操作重写以后,当创建子类对象以后,通过子类对象调用子父类中同名同参数的方法时,实际执行的是子类重写父类的方法。
-
权限修饰符 返回值类型 方法名 (形参列表) throws 异常的类型{ //方法体 }
-
子类重写的方法的方法名和形参列表与父类被重写的方法方法名和形参相同
-
子类重写的方法的权限修饰符不小于父类被重写的方法权限修饰符
- 但子类不能重写父类中private的方法
-
返回值类型:
- 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
- 父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
- 父类为Object,则子类可为Object或object的子类(如Stirng)
- 父类被重写的返回值类型是基本的数据类型,则子类重写的方法的返回值类型必须是相同的基本数据类型
- 父类为double,则子类必须也是double
-
子类重写的方法抛出的异常类型不大于被重写的方法抛出的异常类型(具体在异常)
子类和父类中同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写、static无法重写)
区分方法的重载和重写?
-
概念
-
重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变
-
重载是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
-
-
具体规则
- 参数列表与被重写方法的参数列表必须完全相同。
- 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
- 父类的成员方法只能被它的子类重写。
- 声明为 final 的方法不能被重写。
- 声明为 static 的方法不能被重写,但是能够被再次声明。
- 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
- 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
- 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
- 构造方法不能被重写。
- 如果不能继承一个类,则不能重写该类的方法
- 被重载的方法必须改变参数列表(参数个数或类型不一样);
- 被重载的方法可以改变返回类型;
- 被重载的方法可以改变访问修饰符;
- 被重载的方法可以声明新的或更广的检查异常;
- 方法能够在同一个类中或者在一个子类中被重载。
- 无法以返回值类型作为重载函数的区分标准。
-
重载:不表现为多态性
重写:表现为多态性
重载是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法,它们调用的地址在编译期就绑定了
。 Java重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。
所以,对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法。 这称为早绑定或静态绑定
;
而对于多态直等到对方调用的那一刻。解释运行器才会确定所要调用的具体方法,可称为晚绑定或动态绑定
。
区别点 | 重载方法 | 重写方法 |
---|---|---|
参数列表 | 必须修改 | 一定不能修改 |
返回类型 | 可以修改 | 一定不能修改 |
异常 | 可以修改 | 可以减少或删除,一定不能抛出新的或者更广的异常 |
访问 | 可以修改 | 一定不能做更严格的限制(可以降低限制) |
super关键字
可以用来调用父类的:属性、方法、构造器
- 在子类的方法或构造器中。通过使用
super.属性
或super.方法
的方式,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略"super." - 当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的使用
super.属性
的方法,表明调用的是父类中声明的属性 - 当子类重写了父类的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的使用
super.方法
的方式,表明调用的是父类中被重写的方法。
调用父类构造器
super(形参列表)
必须声明在子类构造器的首行
- 在类的构造器中,针对于
this(形参列表)
或super(形参列表)
只能二选一,不能同时出现 - 在构造器的首行,没有显式的声明
this(形参列表)
或super(形参列表)
,则默认调用的是父类中空参的构造器 - 在类的多个构造器中,至少有一个类的构造器中使用了
super(形参列表)
,调用了父类中的构造器
子类对象实例化过程
虽然创建子类对象时,调用了父类的构造器,但是
自始至终就创建过一个对象,即为new的子类对象
结果上看
- 子类继承父类以后就获取了父类中声明的属性或方法。创建子类的对象,在堆空间中,就
会加载所有父类中声明的属性
过程上看
- 当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器…,
直到调用了java.lang.Object类中空参的构造器为止
。正因为加载过所有的父类的构造器,所以才看到内存中有父类中的结构,子类对象才可以考虑进行调用
多态
Polymorphism
一个事物的多种形态
对象的多态性:父类的引用指向子类的对象
Person test=new man; //man为people的子类
Java引用变量有两个类型:编译时类型
和运行时类型
。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。
- 若编译时类型和运行时类型不一致,就出现了对象的多态性
- 多态情况下,
- 编译时:看的是父类的引用(父类中不具备子类特有的方法)
- 运行时:看的是子类的对象(实际运行的是子类重写父类的方法)
对多态的理解
1.实现代码的通用性。
Object类中定义的public boolean equals(Object obj){}
多态的使用
编译看左,运行看右
当调用子父类同名同参数的方法时,实际执行时子类重写父类的方法
——虚拟方法调用(Virtual Method Invocation)
有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。
总结:编译,看左边;执行,看右边
对象能执行哪些方法,主要看对象左边的类型,和右边的关系不大!
父类型,可以指向子类,但是不能调用子类独有的方法
public class AnimalTest{
public static void main (String[] args){
Animal test=new Animal();
test.func(new Dog());//Animal animal=new Dog();
test.func(new Cat());//Animal animal=new Cat();
}
public void func(Animal animal){
animal.eat();
animal.shout();
}
}
class Animal{
public void eat(){
System.out.println("动物:进食");
}
public void shout(){
System.out.println("动物:叫");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("狗吃骨头");
}
public void shout(){
System.out.println("汪!汪!汪!");
}
}
class Cat extends Animal{
public void eat(){
System.out.println("猫吃鱼");
}
public void shout(){
System.out.println("喵!喵!喵!");
}
}
结果为
狗吃骨头
汪!汪!汪!
猫吃鱼
喵!喵!喵!
若无多态性,Java实现以上功能需要
public void func(Dog dog){
dog.eat();
dog.shout();
}
public void func(Cat cat){
cat.eat();
cat.shout();
}
使用前提
- 类的继承
- 方法的重写
对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
向下转型:用强制类型转换符号
有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用
Person p=new Man;
Man m=(Man) p;
使用强转时,可能出现ClassCastException
的异常
Man m=(Woman) w;
//此时就可能出现ClassCastException的异常
instanceof关键字
保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回 boolean 的数据类型
a instanceof A
:判断对象a是否是类A的实例。如果是,返回true;如果不是,返回false。
为了避免在向下转型时出现ClassCastException的异常
,我们现在向下转型之前,先进行instanceof的判断,一旦返回true,就向下转型。如果返回false,不进行向下转型。
Person p=new Man;
//false
if(p instanceof woman){
woman w=(woman) p;
w.workHard();
System.out.println("Woman")
}
//true
if(p instanceof Man){
Man m=(Man) p;
m.workHard();
System.out.println("Man")
}
当a instanceof A返回true,且B为A的父类。则此时a instanceof B也返回true
向下转型的几种问题
-
编译时通过,运行时报错
Person p=new Woman(); Man m=(man) p; //运行时出现ClassCastException的异常 Person p2=new Person(); Man m2=(Man) p2; //new的对象中没有Man的某些属性,所以不能强转
-
编译不通过
Man m=new Woman(); //Woman不是Man的子类
-
编译通过,运行也通过
Object o=new Woman(); Person p=(Person) o; //可行,因为Woman继承了父类的所有属性
Object类
java.lang.Object类
- 作为所有Java类的根父类
- 如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类
- Object类中的方法就具有通用性(Object中没有属性)
- Object类只声明了一个空参构造器
修饰符和类型 | 方法和描述 | 描述 |
---|---|---|
protected Object | Clone() | 创建并返回此对象的副本 |
boolean | equals(Object obj) | 比较两个对象是否相等,可在自定义类中重写 |
Class<?> | getClass() | 返回此对象的运行时类 |
int | hashCode() | 返回该对象的哈希代码值 |
void | notify() | 唤醒一个正在等待这个对象的监视器的单线程 |
void | notifyAll() | 唤醒所有在此对象的监视器上等待的线程 |
String | toString() | 返回对象的字符串表示形式,可在自定义类中重写 |
void | wait() | 导致当前线程等待,直到它被唤醒,通常是通多被通知或被打断 |
void | wait(long timeoutMillis) | 导致当前线程等待,直到它被唤醒,通常是通过被通知或被打断,或直到一定量的实际时间过去 |
void | wait(long timeoutMillis, int nanos) | 导致当前线程等待,直到它被唤醒,通常是通过被通知或被打断,或直到一定量的实际时间过去 |
equals与==的区别
-
==
-
可以使用在基本数据类型变量和引用数据类型变量中
-
如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不同数据类型会转换)
如果比较的是引用数据变量:比较两个对象的地址值是否相同。
-
补充:必须保证符号左右两边的变量类型一致。
-
-
equals()
-
是一个方法,而非运算符。所以仅可用于引用数据类型
-
Object类中equals()的定义
public boolean equals(Object obj){ return (this == obj); } //Object类中定义的equals()和==的作用相同:比较两个对对象的地址值是否相同,即两个引用是否指向同一个对象实体
-
像String、Date、File、包装类都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的"实体内容"是否相同。
-
-
总结
- ==既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型就是比较内存地址
- equals的话,它是属于java.lang.Object类里边的方法,如果该方法没有被重写过默认也是==;我们可以看到String等类的equals方法是被重写过的,而且String类在日常开发中用的比较多,久而久之,形成了equals是比较值的错误观点。
- 具体要看自定义类里有没有重写Object的equals方法来判断
- 通常情况下,重写equals方法,会比较类中的相应属性是否都相等
重写equals
通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的"实体内容"是否相同。那么,我们就需要对Object类中equals()进行重写
重写equals的原则
对称性
:如果x.equals(y)返回是"true",那么y.equals(x)也应该返回时"true"自反性
:x.equals(x)必须是"true"传递性
:如果x.equals(y)&&y.equals(z)返回是"true",那么z.equals(x)也应该返回是"true"一致性
:如果x.equals(y)返回是"true",只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是"true"。- 任何情况下,x.equals(null),永远返回"false";
x.equals(和x不同类型类型的对象)永远返回是"false"。
重写equals的两种方式
-
手动实现
public boolean equals(Object obj){ if(this == obj){ return true; } if(obj instanceof /*自定义类名*/){ //对比对象类型 /*自定义类名*/ /*别名*/=(/*类名*/) obj;//强制可以访问子类属性 if(/*比较各个属性*/)return true; //实际开发时再详写 } return false; }
-
自动生成
实际开发中还是自动生成比较实用,且省时省力
Eclipse:
Source-Generate hashCode() and equals()...
Idea:
Code-Generate-equals() and hashCode()
toSring()的使用
-
当我们输出一个对象的引用时,实际上就是调用当前对象的toString()
-
Object类中toString()的定义
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
返回的是地址值
-
像String、Date、File、包装类都重写了Object类中的toString()方法。使得在调用对象的toString()时,返回"实体内容"信息。
-
自定义类也可以重写tiString()方法,当调用此方法时,返回对象的"实体内容"
也分为手动实现和自动实现两种,自动实现可为:
Eclipse:
Source-Generate toString()
Idea:
Code-Generate-toString()
单元测试
JUnit测试
步骤:@Test自动导入,或按一下步骤手动导入
- 选中当前工程-右键选择:build path -add libraries -JUnit 4-下一步
- 创建一个Java类,进行单元测试
此类是public的
此类提供公共的无参构造器
- 此类中声明单元测试方法
方法的权限是public,没有返回值,没有形参
- 此单元测试方法上需要声明注解:
@Test
,并在单元测试类中导入import org.junit.Test;
- 声明好单元测试方法以后,就可以在方法体内测试相关代码的代码。
- 写完代码以后,右键方法体即可执行。若不指定单个test方法,则会依次执行类中每个可执行方法
import org.junit.Test;
public class JUniTest{
@Test
public void testXxxYyy(){
//方法体
}
}
包装类
Wrapper
- 针对8中基本数据类型定义相应的引用类型—包装类(封装类)
- 使得基本数据类型有了类的特征,就可以调用类中的方法,Java才是真正的面向对象
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
boolean | Boolean |
char | Character |
Byte、Short、Integer、Long、Float、Double等数值类型都有一个共同的父类Number
基本数据类型、包装类、String类型的相互转换
基本数据类型–>包装类
-
调用包装类型给你的构造器
@Test public void testNum(){ int num=10;//基本数据类型 Integer int1=new Interger(num1); Integer int2=new Interger("123");//包装类 System.out.println(int1.eauals(int2));//true }
包装类–>基本数据类型
-
调用包装类的xxxValue()
@Test public void testNum(){ Integer int1 = new Integer(12); System.out.println(int1.equals(12));//true int in1 = int1.intValue(); System.out.println(in1);//此时无法调用equals() }
自动装箱、自动拆箱
自动装箱
@Test
public void Test1(){
int num=10;
Integer int1=num;
boolean b=true;
Boolean b1=b;
}
自动拆箱
@Test
public void Test1(){
Integer int1=new Integer(1);
int num=int1;
}
基本数据类型、包装类–>String类型
-
隐式转换
@Test public void test1(){ int num=10; String str1=num+""; }
-
调用String重载到valueOf(Xxx xxx)
@Test public void test1(){ float f=12.3f; String str1=String.vlaueOf(f); }
String类型–>基本数据类型、包装类
-
调用包装类的parseXxx()
@Test public void test1(){ String str1="123"; int num=Integer.parseInt(str1); System.out.println(num+1); }//可能会报NumberFormatException
static
静态的
用来修饰类的内部结构:属性、方法、代码块、内部类
随着类的加载而加载:早于对象的创建;只要权限允许,可以通过“对象.static属性”的方法进行调用;存在于方法区的静态域
当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有new关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份
。
修饰属性
静态变量(或类变量)
属性,按是否使用static修饰,又分为:静态属性、非静态属性(实例变量)
-
实例变量
我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象非静态属性时,不会导致其它对象中同样的属性值的修改。
-
静态变量
我们创建了类的多个对象,多个对象共享一个静态变量。当通过某一个对象修改静态变量时,会导致其它对象调用此静态变量时,是修改过了的
-
其它属性
-
静态变量随着类的加载而加载。可以通过“类.静态变量”的方式进行调用
-
静态变量的加载要早于对象的创建
-
由于类只会加载一次,则静态变量在内存中也只会存在一份,存在于方法区的静态域中
。 -
类变量 实例变量 类 可以调用 不可调用 对象 可以调用 可以调用
-
-
例
Math.PI //可随处调用,无需创建对象
修饰方法
随着类的加载而加载,可以通过“类.静态方法”的方式进行调用
静态方法 | 非静态方法 | |
---|---|---|
类 | 可以调用 | 不可调用 |
对象 | 可以调用 | 可以调用 |
- 静态方法中,只能调用静态的方法或属性
- 非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性
所以,在静态方法内,不能使用this关键字、super关键字
是否使用static
开发中,如何确定一个属性
是否要声明为static?
- 属性是可以被多个对象所共享的,不会随着对象的不同而不同
- 类中的常量也常常声明为static
开发中,如何确定一个方法
是否要声明为static?
- 操作静态属性的方法,通常设置为static
- 工具类中的方法,习惯上声明为static。比如:Math、Arrays、Collections
单例(Singleton)设计模式
设计模式:在大量的事件中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式
单例实际模式:采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例
,并且该类只提供一个取得其对象实例的方法,如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private
,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法
以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以指向内部产生的该类对象的变量也必须定义成静态的
。
应用场景
由于只生成了一个实例,
减少了系统性能开销
,当一个对象的产生需要比较多的系统资源时,如读取配置、产生其他依赖对象时,则可以通过在应用其同时直接产生一个单例对象,然后永久驻留内存的方式来解决。例如java.lang.Runtime
public class Runtime { private static final Runtime currentRuntime = new Runtime(); private static Version version; /** * Returns the runtime object associated with the current Java application. * Most of the methods of class {@code Runtime} are instance * methods and must be invoked with respect to the current runtime object. * * @return the {@code Runtime} object associated with the current * Java application. */ public static Runtime getRuntime() { return currentRuntime; } /** Don't let anyone else instantiate this class */ private Runtime() {} ..... }
- 网站计数器
- 应用程序的日志应用
- 数据库连接池
- 读取配置文件的类
- Application
- windows的Task Mannager(任务管理器)
- Windows的Recycle Bin(回收站)
饿汉式实现
public class SingletonTest {
public static void main(String[] args) {
Bank bank1 = Bank.getInstance();
Bank bank2 = Bank.getInstance();
System.out.println(bank1 == bank2);//true,因为地址值相同
}
}
class Bank {
//私有化类的构造器
private Bank() {
}
//内部创建的对象(声明为静态是因后面静态的公共方法)
private static Bank instance = new Bank();
//提供公共的静态方法,返回类的对象
public static Bank getInstance() {
return instance;
}
}
懒汉式实现
public class SingletonTest {
public static void main(String[] args) {
Order order1 = Order.getInstance();
Order order2 = Order.getInstance();
System.out.println(order1 == order2);//true
}
}
class Order {
// 私有化类的构造器
private Order() {
}
//声明当前类的对象,没有初始化
//此对象也需声明为static
private static Order instance = null;
//声明public、static的返回当前类对象的方法
public static Order getInstance() {
if (instance == null) {
instance = new Order();
}
return instance;
}
}
区分饿汉式和懒汉式
饿汉 | 懒汉 | |
---|---|---|
对象的创建 | 对象加载时间过长 | 延迟对象的创建(优) |
线程安全 | 线程是安全的(优) | 目前的写法不安全,可以改善 改善见线程篇章 |
main()方法
-
最为程序的入口
-
也是一个普通的静态方法(可被其它main方法调用)
class MainTest{ public static void main (String[]args){ //因为main是静态属性,所以可以用Main.maind Main.main(new String[10]);//传入一个10个长度的String类型的数组 } } class Main{ public static void main(String[]args){ for(int i=0;i<args.length;i++){//遍历数组args System.out.println(i); } } }
-
main()方法也可以作为与控制台交互方式,(之前,Scanner)(了结即可)
public class MainDemo{ public static void main(String[]args){ for(int i=0;i<args.length;i++){ System.out.println(args[i]); } } }
Windows—Dos
D:\system\Desktop>javac MainDemo.java D:\system\Desktop>java MainDemo 66 77 88 你好 66 77 88 你好 D:\system\Desktop>
代码块
- 初始化当前类或者对象(初始化块)
- 代码块如果有修饰的话,仅能由static修饰
静态代码块
- 内部可以有输出语句
随着类的加载而执行
,只执行一次(不管该类被创建多少个对象)- 作用:
初始化类的信息
- 如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
- 静态代码块内
只能调用静态
属性、方法
static{
System.out.println(Hello);
}
非静态代码块
- 内部可以有输出语句
随着对象的创建而执行
,创建一个对象就执行一次- 作用:
可以在创建对象时,对对象的属性等进行初始化
- 如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
- 可以调用静态与非静态的属性和方法
{
System.out.println("Hello");
}
属性赋值的先后顺序
- 默认初始化 [默认值]
- 显示初始化 [int i=0]
- 代码块中赋值
- 构造器初始化
- 有了对象之后,通过“对象.属性”或“对象.方法”的方式,进行赋值
final
最终的
修饰:类、方法、变量
-
修饰类:不能被其它类继承
比如:String类、System类、StringBuffer类
-
修饰方法:此方法不能被重写
比如:Object中的getclass();
-
修饰变量:此时的“变量”不可改变(常量)
-
属性:可以在显示初始化时、代码块中、构造器中使用
class FinalTest{ final int width=0; final int left; final int right; final int buttom; { left=1; } public FinalTest(int n){ right=n } }
-
局部变量
尤其是使用final修饰形参时,表明此形参时一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值
-
-
static final用来修饰属性:全局变量
抽象类与抽象方法
abstract
抽象的
可以用来修饰
类、方法
不能修饰:属性、构造器
不能用来修饰私有方法、静态方法、final的方法、final的类
修饰类:抽象类
抽象类可以继承非抽象类
- 此类
不能实例化
- 抽象类中一定要有构造器,便于
子类实例化
时调用 - 开发中,都会提供抽象类的子类,让子类实现实例化,完成相关操作
修饰方法
抽象方法,只定义了一种功能的标准。具体的执行,需要子类去实现。
- 只用方法的声明,没有方法体
- 包含抽象方法的类,一定是一个抽象类。但抽象类里可以没有抽象类
- 子类重写了父类中的所有的抽象方法后才可实例化(没有重写的话就继承父类的抽象类[参考上一条])
abstract class F{
public abstract void L();
}
应用
Java允许设计者指定:超类声明一个方法但不提供实现,该类的实现由子类提供。这样的方法称为抽象方法
。有一个或多个抽象方法的类称为抽象类
。
抽象类是用来模型化那些父类无法确定全部实现,而是由其子类提供具体实现的对象的类。
IO流中设计到的抽象类:InputStream/OutputStream/Reader/Writer。在其内部定义了抽象的read()、write()方法。
在航运公司系统中,Vegicle类需要定义两个方法分别计算运输工具的燃料效率和行驶距离。
卡车(Truck)和驳船(RiverBarge)的燃料效率和行驶距离的计算方法王权不同。Vehicle类不能提供计算方法,但子类可以
public abstract class Vehicle{
public abstract double calcFuelEfficiency();//计算燃料效率的抽象方法
public abstract double calcTripDistance();//计算行驶距离的抽象方法
}
public class Truck extends Vegicle{
public double calcFuelEfficiency(){//计算卡车的燃料效率的具体方法
}
public double calcTripDistance(){//写出计算卡车行驶距离的具体方法
}
}
public class RiverBarge extends Vegicle{
public double calcFuelEfficiency(){
//写出计算驳船的燃料效率的具体方法
}
public double calcTripDistance(){
//写出计算驳船行驶距离的具体方法
}
}
注意
- abstract不能用来修饰:属性、构造器
- abstract不能用来修饰私有方法(不能被重写)、静态方法(不能被覆盖)、final的类和方法。
匿名类
抽象类的匿名子类
public static void main(String[]args){
method(new Student());//匿名对象
Worker worker=new Worker();
method1(worker);//非匿名的类非匿名的对象
method1(new Worker());//非匿名的类
//创建一个匿名子类的对象,P
Person p = new Person(){
@Override
public void eat(){
System.out.print("eat ");
}
@Voveride
Public void breath(){
System.out.println("breath");
}
};
method1(p);
//eat breath
}
//匿名子类的匿名对象
method1(new Person(){
@Override
public void eat(){
System.out.print("eat ");
}
@Voveride
Public void breath(){
System.out.println("breath");
}
});
public static void method1(Person p){
p.eat();
p.breath();
}
public static void method(Student s){
}
Worker extends Person{
}
多态的应用:模板方法设计模式(TemplateMethod)
抽象类体现的就是已汇总模板模式的,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上回保留抽象类的行为模式
- 当功能内部一部分实现是确定的,一部分是现实是不确定的。这时可以把不确定的部分暴露出去让子类去实现。
- 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同的子类是心啊。这就是一种模板模式
public class TemplateTest {
//调用
public static void main(String[] args) {
Template t = new SubTemplate();
t.spendTime();
}
}
abstract class Template {
//计算某段代码执行所需要花费的时间
public void spendTime() {
long start = System.currentTimeMillis();
this.code();//不确定的部分、易变部分
long end = System.currentTimeMillis();
System.out.println("花费的时间为" + (end - start) + "毫秒");
}
public abstract void code();
}
class SubTemplate extends Template {
//输出1000以内的素数
@Override
public void code() {
for (int i = 2; i <= 1000; i++) {
boolean Flag = true;
for (int j = 2; j <= Math.sqrt(i); j++) {
if (i % j == 0) {
Flag = false;
break;
}
}
if (Flag) {
System.out.println(i);
}
}
}
}
public class TeamplateMethodTest {
public static void main(String[] args) {
BankTemplateMethod btm = new DrawMoney();
btm.process();
BankTemplateMethod btm2 = new ManageMoney();
btm2.process();
}
}
abstract class BankTemplateMethod {
public void takeNumber() {
System.out.println("取号排队");
}
public abstract void transact();//办理具体的业务//钩子方法
public void evaluate() {
System.out.println("反馈评分");
}
//模板方法,把基本操作组合到一起,子类一般不能重写
public final void process() {
this.takeNumber();
this.transact();//像个钩子,具体执行时,挂哪个子类,就执行哪个子类的代码
this.evaluate();
}
}
class DrawMoney extends BankTemplateMethod {
public void transact() {
System.out.println("取款");
}
}
class ManageMoney extends BankTemplateMethod {
public void transact() {
System.out.println("理财");
}
}
模板方法设计模式是编程中共经常用得到的模式。各个框架、类库中都有他的影子,比如说常见的有:
- 数据库访问的封装
- Junit单元测试
- JavaWeb的Servlet中关于doGet/doPost方法调用
- Hibernate中模板程序
- Spring中JDBCTemlate、HibernateTemplate等
例题
编写工资系统,实现不同类型员工(多态)的按月发放工资。如果当月某个Employee对象的生日,则将改员工的工资增加100元。
定义一个Employee类,该类包含:
private成员变量name,number,birthday,其中birthday为MyDate类的对象;
abstract方法earnings();
toString()方法输出对象的name,number和birthday
MyDate类包含:
private成员变量year,month,day
toDateString()方法返回日期对应的字符串:xxxx年xx月xx日
定义SalaredEmployee类继承Employee类,实现按月计算工资的员工处理。该类包括:private成员变量monthlySalary;
实现父类的抽象方法earnings(),该方法返回monthlySalary值;toString()方法输出弗雷的抽象方法eaninngs,该方法返回monthlySalary值;toString()方法输出员工类型信息及员工的name,number,birthday。
参照SalariedEmployee类定义HourlyEmoloyee类,实现按小时计算工资的员工处理。该类包括private成员变量wage和hour;实现父类的抽象方法earnings(),该方法返回wage*hour值;toString()方法输出员工类型信息及员工的name,number,birthday。
定义PayrollSystem类,创建employee变量数组并初始化,该数组存放各类雇员对象的引用。利用循环结构比遍历数组元素,输出各个对象的类型,name,number,birthday,以及该对象生日。当键盘输入本月月份值时,如果本月时某个Employee对象的生日,还要输出增加工资信息。
public abstract class Employee {
private String name;
private int number;
private MyDate birthday;
public Employee(String name, int number, MyDate birthday) {
this.name = name;
this.number = number;
this.birthday = birthday;
}
public abstract double earnings();
public MyDate getBirthday() {
return birthday;
};
@Override
public String toString() {
return "name=" + name + ",number=" + number + ",birthday=" + birthday.toDateString();
}
}
public class MyDate {
private int year;
private int month;
private int day;
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int month) {
this.day = day;
}
public int getyear() {
return year;
}
public String toDateString() {
return year + "年" + month + "月" + day + "日";
}
}
public class SalariedEmployee extends Employee {
private double monthlySalary;//月工资
public SalariedEmployee(String name, int number, MyDate birthday, double monthlySalary) {
super(name, number, birthday);
this.monthlySalary = monthlySalary;
}
public void setMonthlySalary(double monthlySalary) {
this.monthlySalary = monthlySalary;
}
public double getMonthlySalary() {
return monthlySalary;
}
@Override
public double earnings() {
return monthlySalary;
}
public String toString() {
return "SalariedEmployee[" + super.toString() + "]";
}
}
public class HourlyEmloyee extends Employee {
private int wage;//每小时工资
private int hour;//月工作的小时数
public HourlyEmloyee(String name, int number, MyDate birthday, int wage, int hour) {
super(name, number, birthday);
this.wage = wage;
this.hour = hour;
}
@Override
public double earnings() {
return wage * hour;
}
public String toString() {
return "HourlyEmployee[" + super.toString() + "]";
}
}
import java.util.Calendar;//该包能获取当前时间
public class PayrollSystem {
public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
int month = calendar.get(Calendar.MONTH)+1;//获取当前月份,月份需要+1
Employee[] emps = new Employee[2];
emps[0] = new SalariedEmployee("mary", 1002, new MyDate(1992, 9, 28), 10000);
emps[1] = new HourlyEmloyee("Tom", 1003, new MyDate(1993, 3, 23), 60, 240);
for (int i = 0; i < emps.length; i++) {
System.out.println(emps[i]);
double salary = emps[i].earnings();
if (month == emps[i].getBirthday().getMonth()) {
System.out.println("生日快乐!奖励100元");
salary += 100;
}
System.out.println("月工资为:" + salary);
}
}
}
接口
-
接口使用上也满足多态性
-
接口,实际上就是定义了一种规范
-
开发中,体会面向接口编程[doge]
我们在应用程序中,调用的结构都是JDBC中的定义的接口,不会出现具体某一个数据库厂商的API。
- 一方面,有时必须从几个类中派生除一个子类,继承它们所有的属性和方法。但是,Java不支持多继承。有了接口,就可以得到多重继承的效果。
- 另一方面,又是必须从几个类中抽取除一些共同的行为特征,而它们之间又没有is-a的关系,仅仅时具有相同的行为特征而已。
- 接口就是规范,定义的时一组规则,体现了现实世界中“如果你是/要……则必须能……“的思想。继承是一个”是不是“的关系,而接口实现则是“能不能”的关系
- 接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守
-
使用interface来定义
-
在Java中,接口和类是并列的结构
-
如何定义接口:定义接口中的成员
JDK7及以前
只能定全局常量
和抽象方法
interface Flyable {
//全局变量
public static final int MAX_SPEED = 7900;
//书写时可以不写public static final,接口内常量默认有
int MIN_SPEEN = 1;
//抽象方法
public abstract void fly();
//书写时可以省略public abstract
void stop();
}
interface Attackable {
void attack();
}
//用类实现接口
class Plane implements Flyable {
@Override
public void fly() {
System.out.println("起飞");
}
@Override
public void stop() {
System.out.println("减速停止");
}
}
class Bullit extends Object implements Flyable, Attackable {
@Override
public void fly() {
}
@Override
public void stop() {
}
@Override
public void attack() {
}
}
JDK8以后
除了定义全局常量
和抽象方法
之外,还可以定义静态方方法、默认方法
-
接口中不能定义构造器!意味着接口不可以实例化
-
Java开发中,可以用类实现(
implements
)接口,此类可以实例化如果实现类覆盖率接口中的所有抽象方法,则此实现类就可以实例化
如果实现类没有覆盖接口中所有的抽象方法,则此是实现类仍为一个抽象类
-
Java类可以实现多个接口 --》弥补了Java单继承的局限性
格式:
class AA extends BB implements CC,DD,EE
-
接口与接口之间,可以继承,而且可以多继承
格式:
interface AA exends BB,CC,DD
同样得覆盖率接口中的所有抽象方法后才能实例化
- 接口的具体的使用,体现多态性
- 接口,实际上可以看作是一种规范
使用
- 接口使用上也满足多态性
- 接口,实际上就是定义了一种规范
- 在开发中,就是面向接口编程
/*
以遵守USB协议的设备为例
Flash和Printer都遵守USB协议,在使用时,通过多态调用实现其内部方法
*/
public class T {
public static void main(String[] args) {
Computer com = new Computer();
//创建了接口的非匿名实现类的非匿名对象
Flash flash = new Flash();
com.transferData(flash);
}
}
class Computer {
public void transferData(USB usb) {//USB usb=new Flash
usb.start();
System.out.println("传输数据");
usb.stop();
}
}
interface USB {
void start();
void stop();
}
class Flash implements USB {
@Override
public void start() {
System.out.println("U盘开始工作");
}
@Override
public void stop() {
System.out.println("U盘结束工作");
}
}
class Printer implements USB {
@Override
public void start() {
System.out.println("打印机开始工作");
}
@Override
public void stop() {
System.out.println("打印机结束工作");
}
}
匿名对象
Computer com = new Computer();
//创建了接口的非匿名实现类的非匿名对象
Flash flash = new Flash();
com.transferData(flash);
//创建了接口的非匿名实现类的匿名对象
com.transferData(new Printer());
//创建了接口的匿名实现类非匿名对象
USB phone = new USB() {
@Override
public void start() {
System.out.println("phone_start");
}
@Override
public void stop() {
System.out.println("phone_stop");
}
};
com.transferData(phone);
//创建了接口的匿名实现类的匿名对象
com.transferData(new USB() {
@Override
public void start() {
System.out.println("start");
}
@Override
public void stop() {
System.out.println("stop");
}
});
代理模式
代理模式是Java开发中使用较多的一种设计模式。代理模式就是为其他对象提供一种代理以控制对这个对象的访问
//代理模式
public class NetWorkTest {
public static void main(String[] args) {
Server server = new Server();
ProxyServer proxyServer = new ProxyServer(server);
proxyServer.browse();
}
}
interface NetWork {
void browse();
}
//被代理类
class Server implements NetWork {
@Override
public void browse() {
System.out.println("真实");
}
}
//代理类
class ProxyServer implements NetWork {
private NetWork work;
public ProxyServer(NetWork work) {
this.work = work;//NetWork work = new Server;
}
public void check() {
System.out.println("联网之前的检查工作");
}
@Override
public void browse() {
check();
work.browse();
}
}
应用场景
- 安全代理:屏蔽对真实角色的直接访问。
- 原创代理:通过代理类处理远程方法调用(RMI)
- 延迟加载:先加载轻量级的代理对象,真正需要再加载真实的对象
比如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有100M,在打开文件时,不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用proxy来进行大图片的打开
分类
-
静态代理(静态定义代理类)
-
动态代理(动态生成代理类)
JDK自带的动态代理,需要反射等知识
工厂模式
实现了创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的
。其实设计模式和面向对象色痕迹原则都是为了使得开发项目更加容易扩展和维护,解决凡是就是一个"分工"
分类
- 简单工厂模式;用来生产同一等级结构中的任意产品。(对于增加新的产品,需要修改已有代码)
- 工厂方法模式:用来生产同一等级结构中的固定产品。(支持增加任意产品)
- 抽象工厂模式:用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)
public class Client{
public static void main(String[] args) {
Car a = new Audi();
Car b = new BYD();
a.run();
b.run();
}
}
//无工厂
interface Car {
void run();
}
class Audi implements Car {
@Override
public void run() {
System.out.println("Audi to run");
}
}
class BYD implements Car {
@Override
public void run() {
System.out.println("BYD to run");
}
}
public class Client {
public static void main(String[] args) {
Car a = CarFactory.getCar("audi");
a.run();
Car b = CarFactory.getCar("BYD");
b.run();
}
}
//简单工厂
interface Car {
void run();
}
class Audi implements Car {
public void run() {
System.out.println("Audi is run");
}
}
class BYD implements Car {
public void run() {
System.out.println("BYD is run");
}
}
class CarFactory {
public static Car getCar(String type) {
if ("audi".equals(type)) {
return new Audi();
} else if ("BYD".equals(type)) {
return new BYD();
} else {
return null;
}
}
}
public class Client {
public static void main(String[] args) {
Car a = new AudiFactory().getCar();
Car b = new BYDFactory().getCar();
a.run();
}
}
//工厂方法模式
interface Car {
void run();
}
//工厂接口
interface Factory {
Car getCar();
}
class Audi implements Car {
public void run() {
System.out.println("audi is run");
}
}
class BYD implements Car {
public void run() {
System.out.println("BYD is run");
}
}
class AudiFactory implements Factory {
public Audi getCar() {
return new Audi();
}
}
class BYDFactory implements Factory {
public BYD getCar() {
return new BYD();
}
}
/*简单工厂模式与工厂模式正真的避免了代码的改动?
没有。在简单工厂模式中,新产品的加入要修改工厂角色中的判断语句;而在工厂模式中,要么将判断逻辑留在抽象工厂角色中,要么在客户程序中将具体工厂角色写死(就像上面的例子一样)。而且产品对象创建条件的改变必然会引起工厂角色的修改。
面对这种情况,Java的反射机制与配置文件的巧妙结合突破了限制——这在Spring中完美的体现了出来。
*/
抽象模式
抽象工厂模式和工厂方法模式的区别就在于需要创建对象的复杂程度上。
而且抽象工厂模式是三个里面最为抽象、最具一般性的
抽象模式的用以为:为客户端提供一个接口,可以创建多个产品族中的产品
而且使用抽象工厂模式还要满足一下条件:
- 系统中有多个铲平族,而系统一次只可能消费其中一族产品
- 同属于同一个产品族的的产品以其使用
看过了前两个模式,对这个模式各个角色之间的协调情况应该心里有个数了
练习
定义一个接口用来实现两个对象的比较。
interface CompareObject{
public int compareTo(Object 0);//若返回值是0,代表相等,代表当前对象大;附属代表当前对象小
}
定义一个Circle类,声明radius属性,提供getter和setter方法
定义一个ComparableCircle类,继承Circle累了并且实现CompareObject接口。在ComparableCircle类中给出接口中方法compareTo的实现体,用来比较两个圆的半径大小。
定义一个测试类Interface Test,创建两个ComparableCircle对象,调用compareTo方法比较两个类的半径大小。
/**
* 定义一个Circle类,声明radius属性,提供getter和setter方法
*/
public class Cricle {
private double radius;
public Cricle() {
super();
}
public Cricle(double radius) {
super();
this.radius = radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
public double getRadius() {
return radius;
}
}
/**
* 定义一个测试类Interface Test,创建两个ComparableCircle对象,调用compareTo方法比较两个类的半径大小。
*/
public class ComparableCircleTest {
public static void main(String[] args) {
ComparableCircle c1 = new ComparableCircle(3.4);
ComparableCircle c2 = new ComparableCircle(3.6);
int compareValue = c1.compareTo(c2);
if (compareValue > 0) {
System.out.println("c1对象大");
} else if (compareValue < 0) {
System.out.println("c2对象大");
} else {
System.out.println("两个对象一样大");
}
int compareValue1 = c1.compareTo("AA");
if (compareValue1 > 0) {
System.out.println("c1对象大");
} else if (compareValue1 < 0) {
System.out.println("c2对象大");
} else {
System.out.println("两个对象一样大");
}
}
}
/**
* 定义一个ComparableCircle类,继承Circle累了并且实现CompareObject接口。
* 在ComparableCircle类中给出接口中方法compareTo的实现体,用来比较两个圆的半径大小。
*/
public class ComparableCircle extends Cricle implements CompareObject {
public ComparableCircle(double radius) {
super(radius);
}
@Override
public int compareTo(Object o) {
if (this == o) {
return 0;
}
if (o instanceof ComparableCircle) {
ComparableCircle c = (ComparableCircle) o;
// return (int)(this.getRadius()-c.getRadius());//错误的
if (this.getRadius() > c.getRadius()) {
return 1;
} else if (this.getRadius() < c.getRadius()) {
return -1;
} else {
return 0;
}
//当属性radius声明为double类型时,可以调用包装类的方法
// this.getRadius().compareTo(c.getRadius());
} else {
// return 0;
throw new RuntimeException("传入的数据类型不匹配");
}
}
}
/**
* - interface CompareObject{
* public int compareTo(Object 0);//若返回值是0,代表相等,代表当前对象大;附属代表当前对象小
* }
*/
public interface CompareObject {
int compareTo(Object o);
}
JDK8以后
除了定义全局常量
和抽象方法
之外,还可以定义静态方方法、默认方法
在Java8中,你可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念
静态方法:使用使用staitc
关键字修饰。可以通过接口直接调用静态方法
,并执行其方法体。我们经常在互相一起使用的类中使用静态方法。你可以在标准库中找到想Collection/Collectioins或者Path/Paths这样成对的接口和类。
- 接口中定义的静态方法只能通过接口来调用
默认方法:默认方法使用default
关键字修饰。可以通过实现类对象来调用。我们在已有的接口中提供新方法的同时,,还保持了与旧版本代码的兼容性。比如Java8 API中共对Collection、List、Comparator等接口提供了丰富的默认方法。
- 通过实现类的对象,可以调用接口中的默认方法
- 如果实现类重写了接口中的默认方法,调用时,调用的是被重写后的方法
- 若一个接口中定义了一个默认方法,而另一个接口中也定义了一个同名同参数的方法(不管此方法是否是默认方法),在实现类同时实现了这两个接口时,会出现:
接口冲突
。- 实现类必须覆盖接口中同名同参数的方法,来解决冲突
- 若一个接口中定义了一个默认方法,而父类中也定义了一个同名同参数的非抽象方法,则不会出现冲突问题。因为此时遵守:
类优先原则
。接口中具有相同名称和参数的默认方法会被忽略。
interface CompareA {
//静态方法
//接口中定义的静态方法只能通过接口来调用
static void method1() {
System.out.println("CompareA.method1()");
}
//默认方法
//通过实现类的对象,可以调用接口中的默认方法
//默认方法可以不被重写
default void method2() {
System.out.println("CompareA.method2()");
}
//权限还是public,可省略不写
default void method3() {
System.out.println("CompareA.method3()");
}
// //在子类(实现类)的方法中调用父类、接口中被重写的方法
// public void myMethod() {
// method3();//调用自己定义的重写方法
// super.method3();//调用的是父类中声明的
// //接口中的默认方法
// CompareA.super.method3();
// compareB.super.method3();
// }
}
public class SubClassTest {
public static void main(String[] args) {
SubClass s = new SubClass();
//s.method1();//无法调用
CompareA.method1();//调用CompareA.method1()
//接口中定义的静态方法只能通过”接口名.方法"来调用
s.method2();//SubClass.method2()
s.method3();//SuperClass.method3()
/*
通过实现类的对象,可以调用接口中的默认方法
如果实现类重写了接口中的默认方法,调用时,调用的是被重写后的方法
如果子类(实现类)继承的父类和接口中声明了同名同参数的方法,那么子类在没有重写此方法的情况下,默认调用的是父类中同名同参数的方法——》类优先原则
如果实现类中实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,报错。——》接口冲突(需要重写方法)
*/
s.myMethod();
}
}
class SubClass extends SuperClass implements CompareA, CompareB {
@Override
public void method2() {
System.out.println("SubClass.method2()");
}
@Override
public void method3() {
System.out.println("SubClass.method3()");
}
//如何在子类(或实现类)的方法中调用弗雷、接口中被重写的方法
public void myMethod() {
System.out.print("\n");
method2();//调用自己定义的重写方法
super.method3();//调用的父类中声明的方法
//调用接口中的默认方法
CompareA.super.method3();
CompareA.super.method3();
}
}
public interface CompareB {
default void method3() {
System.out.println("CompareB.method3()");
}
}
public class SuperClass {
public void method3() {
System.out.println("SuperClass.method3()");
}
}
内部类
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个內部的完整结构又只为外部事物提供服务,那么整个内部的完整结构最阿红使用内部类
在Java中,允许一个类的定义为与另一个类的内部,前者称为内部类,后者成为外部类
Inner class一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称
Inner class的名字不能与包含它的外部类类名相同;
分类:
- 成员内部类(static成员内部类和非static成员内部类)
- 局部类(不谈修饰符)、匿名内部类
当类A声明在另一个类B中,则类A就是内部类,类B称为外部类
内部类分类
-
成员内部类
- 分为静态与非静态
- 一方面,作为外部类的成员
- 可以调用外部类的结构
- 可以被static修饰
- 可以被四种不同的权限修饰
- 另一方面,作为一个类
- 类内可以定义属性、方法、构造器等
- 可被fianl修饰,表不可被继承
- 可被abstract修饰,表不能被实例化
-
局部内部类
-
方法内、代码块内,构造器内
-
在局部内部类的方法中(比如:show)如果调用局部内部类所声明的方法(比如:method)中的局部变量的话,要求此局部变量声明为final的。
public void method(){ //局部变量 int num=10;//jdk7及以前版本,要求局部变量显式的声明为final的;jdk8及以后的版本,可以省略final的声明,但默认存在fianl; class AA{ public void show(){ System.out.println(num);//输出10 } } }
-
开发中局部内部类的使用?
public class InnerClassTest1 {
//开发中很少见
public void method() {
//局部内部类
class A {
}
}
//常用
public Comparable getComparable() {//调用getComparable()方法时返回Comparable类型
//标准方式一:实现接口的有名的实现类的匿名对象
//创建一个实现了Comparable接口的类:局部内部类
class MyComparable implements Comparable {
@Override
public int compareTo(Object o) {
return 0;
}
}
return new MyComparable();//返回一个实现了Comparable接口的类的对象
/*
标准方式二:实现接口的匿名实现类的匿名对象
return new Comparable(){
@Override
public int compareTo(Object o){
return 0;
}
};
*/
}
}
声明抽象类,并包含抽象方法。测试类中创建一个继承抽象类的匿名子类的对象
abstract AA{
public abstract void m();
}
main(){
AA a=new AA(){//匿名子类的非匿名对象
public void m(){
}
};
a.m();
}
抽象类和接口的异同点?
同:
- 不能实例化
- 都可以被继承
不同
- 抽象类有构造器;接口不能声明构造器
- 抽象类单继承;接口可多继承
异常处理
概述
异常:在Java语法中,将程序执行中发生的不正常情况称为”异常“。(开发过程中的语法错误和逻辑错误不是异常)
Java程序在执行过程中发生的异常时间可分为两类:
Error
: Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError
和OOM
。一般不编写针对性的代码进行处理。
-
java.lang.StackOverflowError(栈溢出)
class ErrorTest{ public static void main(String[]args){ main(args); } }
-
java.lang.OutOfMemoryError(OOM:堆溢出)
class ErrorTest{ public static void main(String[]args){ Integer[]arr=new Integer[1024*1024*1024]; } }
Exception
: 其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对的代码进行处理。例如:
- 空指针访问
- 试图读取不存在的文件
- 网络连接中断
- 数组角标越界
分类
-
对于这些错误,一般有
两种解决办法
:意识遇到错误就终止程序的运行。另一种方法时由程序员在编写程序时,就考虑到错误的检测、错误消息的提示,以及错误的处理。 -
捕获错误最理想的实在
编译期间
,但是有的错误只有在运行时才会发生。比如:除以0,数组下标越界等
分类:
编译时异常
和运行时异常
异常的体系结构
从程序执行过程,看编译时异常和运行时异常
编译时异常:执行javac.exe时,可能会出现的异常
运行时异常:执行java.exe时,可能会出现的异常
java.lang.Throwable
|-----java.lang.Error;一般不编写针对性代码进行处理
|-----java.lang.Excetption:可以进行异常的处理
|-----编译时异常(checked)
|-----IOException
|-----FileNotFoundException
|-----ClassNotFoundException
|-----运行时异常(uncheked)
|-----NullPointterException
|-----ArrayIndexOutOfBaoundsExcException
|-----ClassCastException
|-----NumberFormatExcception
|-----InputMismatchException
|-----ArithmeticException
常见异常
运行时异常
//NullPointerException 空指针异常
@Test
public void test1() {
int[] arr = null;
//int[]arr=new int[1];//若声明是此句则正常
System.out.println(arr[0]);
}
@Test
public void test2(){
String str=null;
//str="a";//加上此句后正常
System.out.println(str.charAt(0));
//charAt(n)可以查询字符串中第n个数字(从0开始)
}
//ArrayIndexOutOfBoundsException 数组角标越界
@Test
public void Test2() {
String str = "abc";
System.out.println(str.charAt(3));//StringIndexOutOfBoundsException
}
//StringIndexOutOfBoundsException 字符串越界
@Test
public void Test1() {
int[] arr = new int[10];
System.out.println(arr[10]);//ArrayIndexOutOfBoundsException
}
//ClassCastException 类型转换异常
@Test
public void test1() {
Object obj = new Date();
String str = (String) obj;
}
//NumberFormatException 数字格式异常
@Test
public void test1() {
String str = "abc";
//str="123";//加上此句后无错
int num = Integer.parseInt(str);//NumberFormatException
}
//InputMismatchException 输入格式异常
@Test
public void test2(){
int score=new Scanner(System.in).nextInt();
System.out.println(score);
}
//ArithmeticException 算术异常
@Test
public void test1(){
System.out.println(10/0);//ArithmeticException
}
编译时异常
@Test
public void test(){
File file=new File("Hlle.txt");
FileInputStream fis=new FileInputStream(file);//FileNotFoundException
int data=fis.read();//IOException
while(data!=-1){
System.out.println((char)date);
data=fis.read();//IOException
}
fis.close();//IOException
}
异常处理方式概述
在编写程序时,经常要在可能出现错误的地方加上检测的代码,如进行x/y运算时,要
检测分母为0,数据为空,输入数据类型错误
等。过多的if-else分支会导致程序的代码加长、臃肿,可读性差。因此采用异常处理机制。
Java异常处理
Java采用的异常处理机制,是将异常处理的程序代码集中在一起,与正常的代码分开,使得程序简洁、优雅,并易于维护
-
Java提供的异常处理的
抓抛模型
-
Java程序的执行过程中如出现异常,会生成一个
异常类对象
,该异常对象将被提交给Java运行时系统,这个过程称为抛出(throw)异常
-
异常对象的生成
- 由虚拟机
自动生成
:程序运行过程中,虚拟机检测到程序发生了问题,如果在当前代码中没有找到相应的处理程序,就会在后台自动创建一个对应异常类的实例对象并抛出——自动抛出 - 由开发人员手动创建:Exception exception=new ClassCastException();——创建好的异常对象不抛出对程序没有任何影响,和创建一个普通对象一样。
- 由虚拟机
抓抛模型
-
过程一:抛:程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象。并将此对象抛出。
一旦抛出对象以后,其后的代码就不再执行。
-
过程二:抓:可以理解为异常的处理方式:
- try-catch-finally
- throws
异常处理机制:try-catch-finally
try{
//可能出现异常的代码
}catch(异常类型1 变量名1){
//处理异常的方式1
}catch(异常类型2 变量名2){
//处理异常的方式2
}catch(异常类型3 变量名3){
//处理异常的方式3
}...
finally{
//一定会执行的代码
}
@Test
public void test1() {
String str = "abc";
try {
//NumberFormatException
int num = Integer.parseInt(str);
System.out.println("若没有异常,则会正常输出此句");
} catch (NullPointerException e) {
System.out.println("出现了空指针异常");
} catch (NumberFormatException e) {
System.out.println("出现了数据转换异常");
} catch (Exception e) {
System.out.println("出现异常了,不要着急");
}
System.out.print("异常处理完之后,继续执行");
}
-
finally是可选的。
-
使用try将可能出现异常的代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配
-
一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理。一旦处理完成,就跳出当前的try-catch结构(没写fanally的情况)。继续执行其后的代码
-
catch中的异常类型,若无子父类关系,则谁声明在上,谁声明在下无所谓。
catch中的异常类型,满足字符类关系,则要求子类一定声明在父类上面。否则,报错
-
常用的异常对象处理的方式
getMessage()
printStackTrace()
(包含getMessage),常用
try{ }catch(NumberFormatException e){ System.out.println(e.getMessage()); } try{ }catch(NumberFormatException e){ e.printStackTrace();//包含getMessage()方法 }
-
在try结构中声明的变量,再出了try结构以后,就不能再被调用
-
try-catch-finally结构可以相互嵌套
体会1:使用try-catch-finally处理编译时异常,使得程序在编译时就不再报错,但运行时人可能报错。相当于我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现。
@Test
public void test(){
try{
File file=new File("hello.txt");
FileInputStream fis=new FileInputStream(file);
int data=fis.read();
while(data !=-1){
System.out.println((char)data);
date=fis.read();
}
fis.close();
}catch(FileNuotfoundException e){
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}
}
finally的使用
-
finally是可选的
-
finally中声明的是一定会被执行的代码。即使catch中出现异常了,try中有return语句,catch中有return语句等情况
public int method(){ try{ int[]arr=new int[10]; System.out.println(arr[10]); return 1; }catch(ArrayIndexOutOfBoundsException e){ e.printStackTrace(); return -1; }finally{ System.out.println("一定会执行"); return 3;//不管try中有没有异常都会执行 } }
-
像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动回收的,我们需要自己手动的进行资源的释放。此时的资源释放就需要声明在finally中。
import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; class{ @Test public void test1() { FileInputStream fis = null; try { File file = new File("hello.txt"); fis = new FileInputStream(file); int data = fis.read(); while (data != -1) { System.out.println((char) data); data = fis.read(); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (fis != null) fis.close(); } catch (IOException e) { e.printStackTrace(); } } } }
体会2:开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了,
针对与编译时异常,我们一定要考虑异常的处理
异常处理机制二:throws
throws+异常类型
-
写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。一旦方法体执行时,出现异常,仍会在异常代码处生产一个异常类的对象。此对象满足throws后异常类型时,就会被抛出。异常代码后续的代码,就不再执行!
-
try-catch-finally:真正的将异常处理掉了
throws的方式只是将异常抛给了方法的调用者。并没有真正的将异常处理掉。
public static void main(String[] args) {
try {
method2();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
method3();
}
public static void method3() {
try {
method2();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void method2() throws IOException {
method1();
}
public static void method1() throws FileNotFoundException, IOException {
File file = new File("hello.txt");
FileInputStream fis = new FileInputStream(file);
int data = fis.read();
while (data != -1) {
System.out.println((char) data);
data = fis.read();
}
fis.close();
}
方法重写的规则之一
- 子类重写的方法异常类型不大于父类被重写的方法抛出的异常类型
开发中如何选择
- 如果父类中被重写的方法没有throws方法处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中有异常,必须是员工try-catch-finally方式处理。
- 执行的方法a中,先后又调用了另外几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throws的方式进行处理。而执行的方法a可以考虑使用try-catch-finally方式进行处理。
手动抛出异常:throw
关于异常对象的产生
- 系统自动生产的异常
- 手动的生成一个异常,并抛出throw
public class Test {
public static void main(String[] args) {
try {
Student s = new Student();
s.regist(-1001);
System.out.println(s);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
class Student {
private int id;
public void regist(int id) throws Exception {
if (id > 0) {
this.id = id;
} else {
// System.out.println("您输入的数据非法");
// throw new RuntimeException("您输入的数据非法!");
throw new RuntimeException("您输入的数据非法!");//因为Exception包含编译时异常,所以需要显式的进行处理
}
}
}
throws与throw的不同
throws:生成一个异常对象,并抛出。使用再方法内部<->自动抛出异常对象
throw:处理异常的方式。使用在方法声明处的末尾<->try-catch-finally
“上游排污,下游治污”
用户自定义异常类
- 继承与现有的异常结构:RuntimeException、Exception
- 提供全局常量:serialVersionUID
- 提供重载的构造器
public class MyException extends RuntimeException{
static final lang serialVersionUID = -123456784681733L;
public MyException(){
}
public MyException(String msg){
super(msg);
}
}
练习
- 编写应用程序EcmDef.java.接受命令行的两个参数,要求不能输入附属,计算两束相除
- 对数据类型不一致(NumberFormatException)、缺少命令行参数(ArrayIndexOutOfBoundsException)、除0(ArithmeticException)及输入负数(EcDef自定义的异常)进行异常处理。
public class EcmDef {
public static void main(String[] args) {
try {
int i = Integer.parseInt(args[0]);
int j = Integer.parseInt(args[1]);
int result = ecm(i, j);
System.out.println(result);
} catch (NumberFormatException e) {
System.out.print("参数类型不一致");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("缺少命令行参数");
} catch (ArithmeticException e) {
System.out.println("除0");
} catch (EcDef e) {
System.out.println(e.getMessage());
}
}
public static int ecm(int i, int j) throws EcDef {
if (i < 0 || j < 0) {
throw new EcDef("分子分母都不能为0");
}
return i / j;
}
}
public class EcDef {
public static void main(String[] args) {
try {
int i = Integer.parseInt(args[0]);
int j = Integer.parseInt(args[1]);
int result = ecm(i, j);
System.out.println(result);
} catch (NumberFormatException e) {
System.out.print("参数类型不一致");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("缺少命令行参数");
} catch (ArithmeticException e) {
System.out.println("除0");
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
public static int ecm(int i, int j) throws Exception {
if (i < 0 || j < 0) {
throw new RuntimeException("分子分母都不能为0");
}
return i / j;
}
}
总结
-
捕获异常
try:执行可能产生异常的代码
catch:捕获异常
finally:无论是否发生异常,代码总被执行
-
抛出异常
throw:异常的生成阶段:手动抛出异常对象
-
声明异常
throws:异常的处理方式:声明方法可能要抛出的各种异常类