面向对象(下)
类的继承
类的继承是指在一个现有类的基础上去构建一个新的类,构建出来的新类称为子类,现有类称为父类,子类会自动拥有父类所有可继承的属性和方法。(用extends关键字)
//定义Animal类
class Animal{
String name; //定义name属性
//定义动物叫的方法
void shout(){
System.out.println("动物发出叫声。");
}
}
//定义Dog类继承Animal类
class Dog extends Animal{
//定义一个打印name的方法
public void printName(){
System.out.println("name = "+name); //父类的name
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();//创建一个Dog类的实例对象
dog.name = "tony";//为Dog类的name属性赋值
dog.printName();//调用Dog类的printName()方法
dog.shout();//调用Dog类继承来的shout()方法
}
}
运行结果
name = tony
动物发出叫声。
注:①只支持单继承,不允许多重继承。②多个类可以继承一个父类。③多层继承是可以的,如A继承B,B继承C。
重写父类方法
在子类重写的方法要和父类被重写的方法具有相同的方法名、参数列表以及返回值类型。
//定义Animal类
class Animal{
//定义动物叫的方法
void shout(){
System.out.println("动物发出叫声。");
}
}
//定义Dog类继承Animal类
class Dog extends Animal{
//这里重写父类,定义狗叫的方法
void shout(){
System.out.println("汪汪...");
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();//创建一个Dog类的实例对象
dog.shout();//调用Dog类继承来的shout()方法
}
}
运行结果
汪汪...
注:子类重写父类方法时,不能使用比父类中重写的方法更严格的访问权限,如父类中的方法是public的,子类方法就不能是private。
super关键字
super关键字用于访问父类成员,例如访问父类的成员变量、成员方法和构造方法。
①使用super关键字调用父类的成员变量和成员方法。格式为:
super.成员变量
super.成员方法([参数1,参数2...])
例:
class Animal{
String name = "动物";
void shout(){
System.out.println("动物发出叫声");
}
}
class Dog extends Animal{
//重写成员变量name
String name = "犬类";
//重写父类shout()方法
void shout(){
super.shout(); //访问父类的成员方法
}
//打印name的方法
void printName(){
System.out.println("name = "+super.name); //访问父类的成员变量
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.shout();
dog.printName();
}
}
运行结果
动物发出叫声
name = 动物
②使用super关键字调用父类的构造方法。格式为:
super([参数1,参数2...])
例:
class Animal{
//定义Animal类有参的构造方法
public Animal(String name){
System.out.println("我是一只"+name);
}
}
class Dog extends Animal{
public Dog(){
super("二哈"); //调用父类有参的构造方法
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
}
}
运行结果
我是一只二哈
注:通过super调用父类构造方法的代码必须位于子类构造方法的第一行,并且只能出现一次。还有,在子类的构造方法中一定会调用父类的某个构造方法,可以通过super指定调用父类的某个构造方法,如果没有指定,实例化子类对象时,会自动调用父类无参的构造方法(这时如果父类中没有无参的构造方法,会报错)。
定义一个类时,如果没有特殊要求,尽量在一个类中定义一个无参的构造方法,避免被继承时出现错误。
class Animal{
//定义Animal类无参的构造方法
public Animal(){
System.out.println("我只是一只动物");
}
//定义Animal类有参的构造方法
public Animal(String name){
System.out.println("我是一只"+name);
}
}
class Dog extends Animal{
//定义Dog类无参的构造方法
public Dog(){
//方法中无代码
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
}
}
运行结果
我只是一只动物
final关键字
final关键字可用于修饰类、变量和方法,它有“这是无法改变的”或“最终”的含义。有以下特性:
①final修饰的类不能被继承
②final修饰的方法不能被子类重写
当在父类中定义某个方法时,如果不希望被子类重写,就可以使用final关键字修饰该方法。
③final修饰的变量(成员变量和局部变量)是常量,只能赋值一次。
注:使用final关键字修饰成员变量时,虚拟机不会对其进行初始化,因此使用final修饰成员变量时,需要在定义变量的同时赋予一个初始值。
抽象类
定义方法时不写方法体,该方法称为抽象方法,必须使用abstract关键字修饰。当类中包含了抽象方法,该类也必须使用abstract修饰,该类称为抽象类。
抽象类不可以被实例化,因为抽象类中的抽象方法没有方法体,不能被调用,如果想调用抽象类中的方法,需要创建子类,在子类中将抽象类中的抽象方法进行实现。
//定义抽象类Animal
abstract class Animal{
//定义抽象方法shout()
abstract void shout();
}
//定义Dog类继承抽象类Animal
class Dog extends Animal{
//实现抽象方法shout()
void shout(){
System.out.println("汪汪...");
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.shout();
}
}
运行结果
汪汪...
接口
如果一个抽象类中所有的方法都是抽象的,则可以将这个类用另一种方式来定义,即接口。定义接口时,需要使用interface关键字来声明。
interface Animal{
int ID = 1; //定义全局常量
void breathe(); //定义抽象方法
}
//接口中的变量默认使用“public static final”来修饰,即全局常量
//接口中定义的方法默认使用“public abstract”来修饰,即抽象方法
不能通过实例化调用接口中的方法,需要定义一个类,并使用implements关键字实现接口中的所有方法。
下面是类与接口之间实现的关系
//定义Animal接口
interface Animal{
int ID = 1; //定义全局常量
void breathe(); //定义抽象方法breathe()
void run(); //定义抽象方法run()
}
//Dog类实现了Animal接口
class Dog implements Animal{
//实现breathe()方法
public void breathe(){
System.out.println("狗在呼吸");
}
//实现run()方法
public void run(){
System.out.println("狗在跑");
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.breathe();
dog.run();
}
}
运行结果
狗在呼吸
狗在跑
还可以定义一个接口使用extends关键字去继承另一个接口
//定义Animal接口
interface Animal{
int ID = 1; //定义全局常量
void breathe(); //定义抽象方法breathe()
void run(); //定义抽象方法run()
}
//定义了LandAnimal接口,并继承了Animal接口
interface LandAnimal extends Animal{
void liveOnload(); //定义C抽象方法liveOnload()
}
//Dog类实现了LandAnimal接口
class Dog implements LandAnimal{
//实现breathe()方法
public void breathe(){
System.out.println("狗在呼吸");
}
//实现run()方法
public void run(){
System.out.println("狗在跑");
}
//实现liveOnload()方法
public void liveOnload(){
System.out.println("狗生活在陆地上");
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.breathe();
dog.run();
dog.liveOnload();
}
}
运行结果
狗在呼吸
狗在跑
狗生活在陆地上
注:①接口中的方法都是抽象的,不能实例化对象。②当一个类实现接口时,如果这个类是抽象类,则实现接口中的部分方法即可,否则需要实现接口中的所有方法(“class A implements B{}”A中的方法必须全在B中写出来,如果是“abstract class A implements B{}”则不需要全写)。③一个类通过implements关键字实现接口时,可以实现多个接口用逗号连接(class A implements B,C{})。④一个接口可以通过extends关键字继承多个接口用逗号连接(interface A extends B,C{})。⑤一个类在继承另一个类的同时还可以实现接口,此时,extends关键字必须位于implements关键字之前(class A extends B implements C{} 先继承,再实现)。
多态
在同一个方法中,由于参数类型不同而导致执行效果各异的现象就是多态。
为了实现多态,允许使用一个父类类型的变量来引用一个子类类型的对象,根据被引用子类对象特征的不同,得到不同的运行结果。
//定义接口Animal
interface Animal{
void shout();
}
//定义Cat类实现Animal接口
class Cat implements Animal{
//实现shout()方法
public void shout(){
System.out.println("喵喵");
}
}
//定义Dog类实现Animal接口
class Dog implements Animal{
//实现shout()方法
public void shout(){
System.out.println("汪汪");
}
}
public class Test {
//定义静态的animalShout()方法,接收一个Animal类型的参数
public static void animalShout(Animal an){
an.shout(); //调用实际参数的shout()方法
}
public static void main(String[] args) {
Animal an1 = new Cat(); //创建Cat对象,使用Animal类型的变量an1引用
Animal an2 = new Dog(); //创建Dog对象,使用Animal类型的变量an2引用
animalShout(an1); //调用animalShout()方法,将an1作为参数传入
animalShout(an2); //调用animalShout()方法,将an2作为参数传入
}
}
运行结果
喵喵
汪汪
所以多态不仅解决了方法同名的问题,而且还使程序变得更加灵活,从而有效地提高程序的可扩展性和可维护性。
对象的类型转换
多态中,涉及到将子类对象当作父类类型使用的情况,如上面代码所写到的这两行:
Animal an1 = new Cat(); //将Cat对象当作Animal类型来使用
Animal an2 = new Dog(); //将Dog对象当作Animal类型来使用
注:此时不能通过父类变量去调用子类中的某些方法(这里的某些方法指的是子类中有的方法而父类中没有)。
//定义接口Animal
interface Animal{
void shout();
}
//定义Cat类实现Animal接口
class Cat implements Animal{
//实现抽象shout()方法
public void shout(){
System.out.println("喵喵");
}
//定义sleep()方法
void sleep(){
System.out.println("猫睡觉");
}
}
public class Test {
//定义静态的animalShout()方法,接收一个Animal类型的参数
public static void animalShout(Animal animal){
animal.shout(); //调用传入参数Animal的shout()方法
//编译器检查到下面这行时,发现Animal类中没有定义sleep()方法,会报错
animal.sleep(); //调用传入参数Animal的sleep()方法
}
public static void main(String[] args) {
Cat cat = new Cat(); //创建Cat实例对象
animalShout(cat); //调用animalShout()方法,将cat作为参数传入
}
}
以上代码会报错,所以需要进行强制类型转换,将animalShout()方法改为:
public static void animalShout(Animal animal){
Cat cat = (Cat) animal; //将animal对象强制转换为Cat类型
cat.shout(); //调用传入参数Animal的shout()方法
cat.sleep(); //调用传入参数Animal的sleep()方法
}
修改后运行结果
喵喵
猫睡觉
需要注意的是,在类型转换时也可能出现错误,当animalShout()方法传入Dog类型的对象时,会报错,例:
//定义接口Animal
interface Animal{
void shout();
}
//定义Cat类实现Animal接口
class Cat implements Animal{
//实现抽象shout()
public void shout(){
System.out.println("喵喵");
}
//定义sleep()方法
void sleep(){
System.out.println("猫睡觉");
}
}
//定义Dog类实现Animal接口
class Dog implements Animal{
public void shout(){
System.out.println("汪汪");
}
}
public class Test {
//定义静态的animalShout()方法,接收一个Animal类型的参数
public static void animalShout(Animal animal){
Cat cat = (Cat) animal; //将animal对象强制转换为Cat类型
cat.shout(); //调用传入参数Animal的shout()方法
cat.sleep(); //调用传入参数Animal的sleep()方法
}
public static void main(String[] args) {
Dog dog = Dog(); //创建Cat实例对象
animalShnew out(dog); //调用animalShout()方法,将cat作为参数传入
}
}
程序运行会出错,原因是在调用animalShout()方法时,传入一个Dog对象,在强制类型转换时,Animal类型的变量无法强转为Cat类型。
针对这种情况,Java提供了关键字instanceof,它能判断一个对象是否为某个类(或接口)的实例或者子类实例,如b instanceof A ,b为子类对象或者实现类对象,
语法:
对象(或者对象引用变量) instanceof 类(或接口)
对animalShout()方法进行修改后
public static void animalShout(Animal animal){
if(animal instanceof Cat){ //判断animal是否是Cat类的实例对象
Cat cat = (Cat) animal; //将animal对象强制转换为Cat类型
cat.shout(); //调用传入参数Animal的shout()方法
cat.sleep(); //调用传入参数Animal的sleep()方法
}
else
System.out.println("这个动物不是猫");
}
运行结果
这个动物不是猫
使用instanceof关键字判断animalShout()方法中传入的对象是否为Cat类型,如果是就进行类型转换,否则输出“这个动物不是猫”。
Object类
Object类是所有类的父类。比如toString()方法,在该方法中输出了对象的基本信息,但也可以在其它类中用对其重写。
class Animal{
//重写Object类的toString()方法
public String toString(){
return "I am an Animal";
}
}
public class Test {
public static void main(String[] args) {
Animal an = new Animal();
System.out.println(an.toString());
}
}
运行结果
I am an Animal
如果不对其重写会输出“类名+16进制表示的该对象哈希值”,如“Animal@b4c966a”
匿名内部类
之前都是用子类来实现接口,并根据该类进行实例化。除此之外,还可以使用匿名内部类来实现接口。
匿名内部类格式:
new 父类(参数列表)或父接口(){//匿名内部类实现部分}
//定义动物类接口
interface Animal{
void shout();
}
public class Test {
//定义静态方法animalShout()
public static void animalShout(Animal an){
an.shout();
}
public static void main(String[] args) {
//定义匿名内部类作为参数传递给animalShout()方法
animalShout(new Animal(){
//实现shout()方法
public void shout(){
System.out.println("喵喵");
}
});
}
}
运行结果
喵喵
接下来分两步来编写匿名内部类(以便容易理解):
①在new Animal()后面又一对大括号,表示创建的对象是Animal的子类实例,该子类是匿名的。
animalShout(new Animal(){});
②在大括号中编写匿名子类的实现代码。
animalShout(new Animal(){
//实现shout()方法
public void shout(){
System.out.println("喵喵");
}
});
异常
举一个ArithmeticExceotion异常:
public class Test {
public static void main(String[] args) {
int result = divide(4,0);
System.out.println("结果: "+result);
}
public static int divide(int x,int y){
int result = x/y;
return result;
}
}
运行结果
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Test.divide(Test.java:7)
at Test.main(Test.java:3)
Throwable类中包含两大类Error类和Exception类。
Error类称为错误类,表示Java运行时产生的系统内部错误或资源耗尽的错误,比较严重,仅靠修改程序本身是不能恢复执行的;
Exception类称为异常类,它表示程序本身可以处理的错误。
Throwable常用方法
|
|
方法声明
|
功能描述
|
String getMessage()
|
返回此throwable的详细消息字符串
|
void printStackTrace()
|
将此throwable及其追踪输出至标准错误流
|
void printStackTrace(PrintStream s)
|
将此throwable及其追踪输出到指定的输出流
|
try...catch和finally
由于发生异常,程序立即终止,无法继续执行下去,所以提供了异常的处理方式--异常捕获。异常捕获通常使用try...catch语句,格式:
try{
//程序代码块
}catch(ExceptionType(Exception类及其子类) e){
//对ExceptionType的处理
}
需要注意的是,在try代码块中,发生异常语句后面的代码不会被执行,如下面的“System.out.println(result);”没有执行。
public class Test {
public static void main(String[] args) {
//下面的代码定义了一个try...catch语句用于捕获异常
try{
int result = divide(4,0);
//发生异常,下面这条语句没被执行
System.out.println("结果: "+result);
}catch (Exception e){ //对异常进行处理
System.out.println("捕获的异常信息为:"+e.getMessage());
}
}
public static int divide(int x,int y){
int result = x/y;
return result;
}
}
运行结果
捕获的异常信息为:/ by zero
还有finally代码块,无论是否发生异常后面代码都会被执行。由于这种特性,在程序设计时,经常会在try...catch后使用finally代码块来完成必须做的事情,例如释放系统资源。
public class Test {
public static void main(String[] args) {
//下面的代码定义了一个try...catch语句用于捕获异常
try{
int result = divide(4,0);
System.out.println("结果: "+result);
}catch (Exception e){ //对异常进行处理
System.out.println("捕获的异常信息为:"+e.getMessage());
return; //用于结束当前语句
}finally{
System.out.println("进入finally代码块");
}
System.out.println("程序继续向下执行...");
}
public static int divide(int x,int y){
int result = x/y;
return result;
}
}
运行结果
捕获的异常信息为:/ by zero
进入finally代码块
return语句用于结束当前语句,所以“System.out.println("程序继续向下执行...");”就不会执行了,而finally中的代码仍然会执行。总的来说就是不会被return语句和异常影响。
注:当在try...catch中执行了System.exit(0)语句finally中的代码就不会执行了,System.exit(0)语句表示退出当前的Java虚拟机,任何代码都不会被执行了。还有一种是try代码块中没有代码运行finally也不会被运行。
throws关键字
在方法的后面使用throws关键字对外声明该方法有可能发生异常。格式:
修饰符 返回值类型 方法名([参数1,参数2...])throws ExceptionType1[,ExceptionType2...]{}
如果在某方法上声明抛出异常,就必须使用try...catch对该方法进行处理,否则会报错。
public class Test {
public static void main(String[] args) {
//下面的代码定义了一个try...catch语句用于捕获异常
try{
int result = divide(4,2);
System.out.println("结果: "+result);
}catch (Exception e){ //对异常进行处理
e.printStackTrace(); //打印捕获的异常信息
}
}
//下面的方法实现了两个数相除,并使用throws关键字声明抛出异常
public static int divide(int x,int y)throws Exception{
int result = x/y;
return result;
}
}
运行结果
结果: 2
使用throws关键字声明抛出异常后,即使不会出现被0除的现象,但如果不用try...catch编译程序依然会报错。
自定义异常
在程序开发时,有可能会用到一些自己描述的特殊的异常情况,Java中允许用户自定义异常,但自定义的异常类必须继承自Exception或其子类。
自定义了异常,就需要用到throw关键字,throw关键字用于在方法中声明抛出异常的实例对象。格式:
throw Exception异常对象
下面是一个当被除数是负数时抛出自定义异常的例子。
//下面自定义一个异常类继承自Exception
class DivideByMinusExpetion extends Exception{
public DivideByMinusExpetion(){
super(); //调用Exception无参的构造方法
}
public DivideByMinusExpetion(String message){
super(message); //调用Exception有参的构造方法
}
}
public class Test {
public static void main(String[] args) {
//下面的代码定义了一个try...catch语句用于捕获异常
try{
int result = divide(4,-2);
System.out.println("结果: "+result);
}catch (DivideByMinusExpetion e){ //对异常进行处理
System.out.println("异常信息:"+e.getMessage()); //打印捕获的异常信息
}
}
//下面的方法实现了两个数相除,并使用throws关键字声明抛出异常
public static int divide(int x,int y)throws DivideByMinusExpetion{
if(y<0){
//使用throw关键字声明异常对象
throw new DivideByMinusExpetion("被除数是负数");
}
int result = x/y;
return result;
}
}
运行结果
异常信息:被除数是负数
下面是直接用Exception类,输出结果同上
public class Test {
public static void main(String[] args) {
try{
System.out.println(divide(4,-2));
}catch (Exception e){
System.out.println("异常信息:"+e.getMessage());
}
}
public static int divide(int x,int y)throws Exception{
if(y<0){
throw new Exception("被除数是负数");
}
return x/y;
}
}
包
Java中的包是专门用来存类的,通常功能相同的类存放在相同的包中。在声明包时,用package语句。
package cn.itcast;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
运行结果
Hello World!
import语句
使用import语句导包。
如果两个类不在同一目录下,引用某个类时需要用import导包,格式:
import 包名.类名;
例如:import cn.itcast.Student;然后在实例化对象的时候就可以直接用Student s = new Student(); ,但如果没有导包,实例化对象时需指明位置,就得写成cn.itcast.Student s = new cn.itcast.Student(); 。
访问控制
Java中,针对类、成员方法和属性提供了四种访问级别,访问控制级别由小到大:private,default,protected,public。
- private(类访问级别):如果类成员被其修饰,该成员只能被该类的其他成员访问。
- default(包访问级别):如果一个类或者类的成员不使用任何访问控制修饰符修饰,则称则称它为默认访问控制级别,这个类或者类的成员只能被本包中的其他类访问。
- protected(子类访问级别):如果类成员被其修饰,该成员既能被同一包下的其他类访问,也能被不同包下的子类访问。
- public(公共访问级别):如果一个类或者类成员被其修饰,不管访问类与被访问类在不在同一个包中,都能被访问。
注:private和protected不能修饰类,只能修饰类成员。