继承
继承是Java的第二大特性,是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
在实际应用中,继承为程序开发节省了大量的重复性工作,提高开发效率。例如你想创建一个新的类,这个类中的所有成员变量和成员方法在一个已经存在的类中都有,这时这个新的类可以直接继承自父类,然后再添加子类中特有的变量和方法。值得注意的是,一个子类只能继承自一个父类,毕竟一个儿子不能有多个爹。下面就用一个实列来了解继承。
package demo.package1;
/*
创建一个父类Animal
*/
public class Animal {
/*
对变量进行私有化封装
*/
private String name;
private int age;
/*
提供set、get方法
*/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
/*
创建三个方法
*/
public void run(){
System.out.println(name + "is running");
}
public void eat(){
System.out.println(name + "is eating");
}
public void query(){
System.out.println(name + "'s age is" + age);
}
}
package demo.package1;
/*
创建一个Dog类继承自Animakl
*/
public class Dog extends Animal{
}
package demo;
import demo.package1.Dog;
/*
创建一个测试类
*/
public class Test {
public static void main(String[] args) {
/*
创建一个狗对象,并初始化
*/
Dog d = new Dog();
d.setName("Jack");
d.setAge(3);
/*
直接调用父类的方法
*/
d.eat();
d.run();
d.query();
}
}
可见即便在子类中没有定义变量和方法,子类对象仍可以调用父类的变量和方法。当然有时候父类的方法不适合子类直接使用,这时就可以对方法进行重写。
package demo.package1;
/*
创建一个Dog类继承自Animakl
*/
public class Dog extends Animal{
/*
重写run()方法
*/
@Override
public void run() {
System.out.println("狗跑的很快");
}
}
这时再次运行,对run()方法的调用就会调用子类中重写的方法
接口
抽象类是从多个类中抽象出来的模板,如果将这种抽象进行的更彻底,则可以提炼出一种更加特殊的“抽象类”——接口(Interface)。接口是Java中最重要的概念之一,它可以被理解为一种特殊的类,不同的是接口的成员没有执行体,是由全局常量和公共的抽象方法所组成。
interface与class类似。主要用来定义类中所需包含的函数,接口也可以继承其他接口,一个类可以实现多个接口。使用接口可以规范不同类实现相同的行为。
package demo.package1;
/*
创建一个接口Animal
*/
public interface Animal {
public void run();
public void eat();
public void query();
}
使用接口实现Dog类
package demo.package1;
/*
创建一个Dog类接自Animal
*/
public class Dog implements Animal{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public void run() {
System.out.println(name + " is running");
}
@Override
public void eat() {
System.out.println(name + " is eating");
}
@Override
public void query() {
System.out.println(name + "'age is " + age);
}
}
再次运行测试类
接口是支持多继承的,也就是说一个接口可以继承自多个接口。同时每个类也可以实现多个接口。
下面用代码分别演示这两种情况。
多继承
package demo.package1;
public interface SmallAnimal {
public void small();
}
package demo.package1;
/*
创建一个接口Animal
*/
public interface Animal extends Life,SmallAnimal {
public void run();
public void eat();
public void query();
}
package demo.package1;
/*
创建一个Dog类接自Animal
*/
public class Dog implements Animal,Life{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public void run() {
System.out.println(name + " is running");
}
@Override
public void eat() {
System.out.println(name + " is eating");
}
@Override
public void query() {
System.out.println(name + "'age is " + age);
}
@Override
public void live() {
System.out.println(name + "居住在这里");
}
@Override
public void small() {
System.out.println(name + "很小");
}
}
实现多个接口
package demo.package1;
public interface Life {
public void live();
}
package demo.package1;
/*
创建一个Dog类接自Animal
*/
public class Dog implements Animal,Life{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public void run() {
System.out.println(name + " is running");
}
@Override
public void eat() {
System.out.println(name + " is eating");
}
@Override
public void query() {
System.out.println(name + "'age is " + age);
}
@Override
public void live() {
System.out.println(name + "居住在这里");
}
}
多态
多态就是事物的多种形态,一个对象在不同条件下所表现的不同形式。
多态存在的三个必要条件
-
继承或实现:在多态中必须存在有继承或实现关系的子类和父类
-
方法的重写:子类对父类中的某些方法进行重新定义(重写,使用@Override注解进行重写)
-
基类引用指向派生类对象,即父类引用指向子类对象,父类类型:指子类对象继承的父类类型,或、实现的父接口类型
多态在形式上表现为使用父类的类名创建一个子类对象。由于多态是指成员方法的多态,所以使用多态时成员变量的编译和运行都是看以左边类型,而对成员方法的编译时,则是编译看左边,运行看右边。
现在继续使用父类Animal和子类Dog演示多态的具体操作。
package demo.package1; /* 创建一个Dog类接自Animal */ public class Dog extends Animal{ public int age = 25; @Override public int getAge() { return age; } @Override public void setAge(int age) { this.age = age; } }
package demo.package1; /* 创建一个接口Animal */ public class Animal { public int age = 50; public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
package demo; import demo.package1.Animal; import demo.package1.Dog; /* 创建一个测试类 */ public class Test { public static void main(String[] args) { /* 创建一个狗对象,并初始化 */ Animal a = new Dog(); System.out.println(a.getAge()); System.out.println(a.age); } }
可见在分别使用成员变量输出对象的age和使用成员方法输出对象的age时,成员方法使用子类方法,成员变量使用父类变量。即符合多态时成员变量的编译和运行都是看以左边类型,而对成员方法的编译时,则是编译看左边,运行看右边的原则。
当然,多态也存在他的弊端。由于其特殊的编译运行规则,使用多态时无法使用子类的特有成员方法。例如我在狗类在添加一个query方法,在调用该方法时会爆红。
package demo.package1; /* 创建一个Dog类接自Animal */ public class Dog extends Animal{ public int age = 25; @Override public int getAge() { return age; } @Override public void setAge(int age) { this.age = age; } public void query(){ System.out.println("狗的年龄是" + age); } }
package demo; import demo.package1.Animal; import demo.package1.Dog; /* 创建一个测试类 */ public class Test { public static void main(String[] args) { /* 创建一个狗对象,并初始化 */ Animal a = new Dog(); a.query(); } }
异常
异常处理可以允许我们在程序运行时进行诊断和补救。异常(Exception)不等于错误(Error)
Error是程序无法处理的错误,比如OutOfMemoryError、ThreadDeath等。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。此类异常是程序的致命异常,是无法捕获处理的。
Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。 程序中应当尽可能去处理这些异常。
下图是Error类和Exception类的继承关系。
其中异常分为两个子类:运行时异常(RuntimeException)和非运行时异常(除RuntimeException外的所有异常,如IOException、SQLException等以及用户自定义的Exception异常)
RuntimeException都是非检查型异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,例如数组越界、算数异常等,程序应该从逻辑角度尽可能避免这类异常的发生。下表是RuntimeException及其描述。
非运行时异常类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。下表是 非运行时异常及其描述。
当然,在了解了异常之后,我们要捕获异常并进行一定的处理。
捕获异常常用的方法是try-catch语句。try后是需要检查的语句,catch后是对异常的处理,一般还会在最后加上finally语句,表示最终的处理,无论如何finally语句一定会被执行。
举个例子,现在有一个数组array = {0,1,2,3,4},输入两个数字k和x,将下标为k的数字除以x,最后输出新的数组,代码如下:
package demo;
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
int[] array = new int[5];
for (int i = 0; i < 5; i ++ ) {
array[i] = i;
}
Scanner sc = new Scanner(System.in);
int k = sc.nextInt();
int x = sc.nextInt();
try {
array[k] /= x;
} catch (ArithmeticException e) {
System.out.println("除零错误!");
e.printStackTrace();
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组越界!");
e.printStackTrace();
} finally {
for (int i = 0; i < 5; i ++ ) {
System.out.print(array[i] + " ");
}
}
}
}
显而易见,当k > 4 时,数组会越界,而当x = 0 时,会出现算数异常,现在运行一些分别模拟上列情况。
在运行中可见对异常捕获的具体方式,同时也印证了无论是否存在异常,finally语句都会执行。
在捕获完异常后,我们会对异常进行处理。对于非检查型异常的处理方式是修改完善代码逻辑,消除异常,而对于检查性异常,我们会选择抛出异常。
抛出异常的方式有两种:第一种是使用try-catch语句配合throw在函数内抛出,第二种是直接在函数名后添加异常签名抛出。在IDEA中,可以使用Alt + Enter快速选择想要使用的抛出异常的方式。