java(五)继承和多态,抽象类与接口,异常(javaSE完)

八、继承和多态(重要)

对于java来说,最重要的就是面对对象,而如何体现这个,在其中三个概念极为重要,封装、继承、多态而无论考试还是面试通常都会考察这几个概念及其原理用法。

1.继承

面向对象思想中提出了继承的概念,专门用来进行共性抽取,实现代码复用。在实际使用类和对象的过程中,会出现的几个问题,比如我们定义了一个猫和狗的类。它们有着共同的一些性质,或者说是行为。比如他们都是动物,都有名字,也都会吃饭。我们在描述它们时,就会将这些行为或者属性通过方法或者成员进行描述。这样我们就发现了他们,在某些情况下,代码是重复的。为了避免代码复写,就将一个类的共有属性进行封装。在出现这个类的子类时(什么是子类?比如“动物”他就父类,而“动物”下面会细分其他“动物”比如“狗”,那么“动物”就是“狗”的父类,“狗”是“动物”的子类),我们只需要描述,这个子类特有的特点就好。而他们的共同特点,可以通过继承他的父类的公共方法进行使用。

1.1 定义

继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。

父类:又叫基类,也叫超类
子类:又叫派生类

在这里插入图片描述
语法:

修饰符 class 子类 extends 父类 {
		// ... 
}

父类

// Animal.java

public class Animal{
	String name;
	int age;
	public void eat(){
		System.out.println(name + "正在吃饭");
	}
	public void sleep(){
		System.out.println(name + "正在睡觉");
		}
}

子类

// Dog.java
	//子类
public class Dog extends Animal{
		void bark(){
			System.out.println(name + "汪汪汪~~~");
		}
}

测试类

// TestExtend.java
public class TestExtend {
	public static void main(String[] args) {
		Dog dog = new Dog();
		// dog类中并没有定义任何成员变量,name和age属性肯定是从父类Animal中继承下来的
		System.out.println(dog.name);
		System.out.println(dog.age);
		// dog访问的eat()和sleep()方法也是从Animal中继承下来的
		dog.eat();
		dog.sleep();
		dog.bark();
		}
}

在子类方法中 或者 通过子类对象访问成员时:

成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。

  1. 如果访问的成员变量子类中有优先访问自己的成员变量
  2. 如果访问的成员变量子类中无则访问父类继承下来的,如果父类也没有定义,则编译报错。
  3. 如果访问的成员变量与父类中成员变量同名,则优先访问自己的
  4. 成员方法没有同名时,在子类方法中或者通过子类对象访问方法时,则优先访问自己的,自己没有时再到父类中找,如果父类中也没有则报错。
  5. 通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。
  6. 通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法适传递的参数选择合适的方法访问,如果没有则报错;

1.2 关键字super

问题:如果子类中存在与父类中相同的成员时,那如何在子类中访问父类相同名称的成员呢?没错,通过super关键字。与this关键字相似,不过前者是声明调用的是父类中的方法。

父类

public class Base {
	int a;
	int b;
	public void methodA(){
		System.out.println("Base中的methodA()");
	}
	public void methodB(){
		System.out.println("Base中的methodB()");
	}
}

子类

public class Derived extends Base{
	int a; // 与父类中成员变量同名且类型相同
	char b; // 与父类中成员变量同名但类型不同
	// 与父类中methodA()构成重载
	public void methodA(int a) {
		System.out.println("Derived中的method()方法");
	}
	// 与基类中methodB()构成重写(即原型一致,重写后序详细介绍)
	public void methodB(){
		System.out.println("Derived中的methodB()方法");
	}
	public void methodC(){
		// 对于同名的成员变量,直接访问时,访问的都是子类的
		a = 100; // 等价于: this.a = 100;
		b = 101; // 等价于: this.b = 101;
		
		// 注意:this是当前对象的引用
		// 访问父类的成员变量时,需要借助super关键字
		// super是获取到子类对象中从父类继承下来的部分
		super.a = 200;
		super.b = 201;
		
		// 父类和子类中构成重载的方法,直接可以通过参数列表区分清访问父类还是子类方法
		methodA(); // 没有传参,访问父类中的methodA()
		methodA(20); // 传递int参数,访问子类中的methodA(int)
		
		// 如果在子类中要访问重写的基类方法,则需要借助super关键字
		methodB(); // 直接访问,则永远访问到的都是子类中的methodA(),父类的无法访问到
		super.methodB(); // 访问父类的methodB()
	}
}

构造方法

子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法

父类构造

public class Base {
	public Base(){
		System.out.println("Base()");
	}
}

子类构造

public class Derived extends Base{
		public Derived(){
		 super();
		 // 注意子类构造方法中默认会调用基类的无参构造方法:super(),
		// 用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语句,
		// 并且只能出现一次
		System.out.println("Derived()");
		}
}
public class Test {
		public static void main(String[] args) {
		Derived d = new Derived();
	
		}
}
	
		结果打印:
		Base()
		Derived()	

super与this的区别
相同点:

  1. 都是Java中的关键字
  2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段
  3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在

构造方法

不同点:

  1. this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用
  2. 在非静态成员方法中,this用来访问本类的方法和属性super用来访问父类继承下来的方法和属性
  3. 在构造方法中:this()用于调用本类构造方法,super(…)用于调用父类构造方法,两种调用不能同时在构造方法中出现
  4. 构造方法中一定会存在super(…)的调用,用户没有写编译器也会增加,但是this(…)用户不写则没有

执行优先分析(重要)

class Person {
	public String name;
	public int age;
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
		System.out.println("Person:构造方法执行");
	}
	
		{
			System.out.println("Person:实例代码块执行");
		}
		
		static {
			System.out.println("Person:静态代码块执行");
		}
	}
class Student extends Person{
	public Student(String name,int age) {
		super(name,age);
		System.out.println("Student:构造方法执行");
	}
	
	{
		System.out.println("Student:实例代码块执行");
	}
	static {
		System.out.println("Student:静态代码块执行");
	}
}
/*
Person:静态代码块执行
Student:静态代码块执行
Person:实例代码块执行
Person:构造方法执行
Student:实例代码块执行
Student:构造方法执行
*/
  1. 父类静态代码块优先于子类静态代码块执行,且是最早执行
  2. 父类实例代码块和父类构造方法紧接着执行
  3. 子类的实例代码块和子类构造方法紧接着再执行
  4. 第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行

继承的方式种类
这个图是我从其他地方摘抄来的。以问过作者了,主要是这个图确实好用。
在这里插入图片描述

1.3 关键字 final

final关键可以用来修饰变量、成员方法以及类。

1.修饰变量
修饰变量或字段,表示常量(即不能修改)

final int a = 10;
a = 20; // 编译出错

2.修饰类
表示此类不能被继承

final public class Animal {

}
public class Bird extends Animal {

}

3.修饰方法
表示该方法不能被重写

2.多态

**通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同 的状态。**比如动物,它有猫这个子类,也有狗这个子类,但是他们吃的东西不同,猫吃猫粮,狗吃狗粮,在父类中出现吃这个动作。而子类将这些具象化了,呈现了不同类型不同状态的形式。(多态)

2.1定义

  1. 必须在继承体系下
  2. 子类必须要对父类中方法进行重写(及重写就是对父类方法的重新构写)
  3. 通过父类的引用调用重写的方法
public class Animal {
		String name;
		int age;
		public Animal(String name, int age){
			this.name = name;
			this.age = age;
		}
		public void eat(){
			System.out.println(name + "吃饭");
		}
		}
public class Cat extends Animal{
		public Cat(String name, int age){
			super(name, age);
		}
		@Override
		public void eat(){
			System.out.println(name+"吃鱼~~~");
			}
		}

}

2.2 重写

重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。也就是说子类能够根据需要实现父类的方法。
规则:

  1. 子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名 (参数列表) 要完全一致
  2. 被重写的方法返回值类型可以不同,但是必须是具有父子关系的
  3. 访问权限不能比父类中被重写的方法的访问权限更低。如果父类方法被public修饰,则子类中重写该方法就不能声明为 protected
  4. 父类被static、private修饰的方法、构造方法都不能被重写。
  5. 重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验.
区别 重写(override) 重载(override)
参数列表 一定不能修改 必须修改
返回类型 一定不能修改【除非可以构成父子类关系】 可以修改
访问限定符 一定不能做更严格的限制 可以修改

重写:方法名和参数必须一致

重载:方法名相同,参数不同

重载

2.3 向上/向下转型

向上转型:
向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。

父类类型 对象名 = new 子类类型()

Animal animal = new Cat("将军");

向下转型:
将父类引用再还原为子类对象,即向下转换。

public class TestAnimal {
	public static void main(String[] args) {
			Cat cat = new Cat("将军");
			Dog dog = new Dog("虞姬");
			// 向上转型
			Animal animal = cat;
			animal.eat();
			animal = dog;
			animal.eat();
		if(animal instanceof Cat){
		cat = (Cat)animal;
		cat.mew();
		}
	if(animal instanceof Dog){
		dog = (Dog)animal;
		dog.bark();
	}
}

注意:
向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入了 instanceof ,如果该表达式为true,则可以安全转换。

继承和多态的小总结

1.多态能够降低代码的 “圈复杂度”(形容代码的复杂程度,一般if-else不超过10), 避免使用大量的 if - else
2.多态可扩展能力更强
3.向上转型用的更多也更安全,向下转型,会横跨两个子类,可能会出现跨类调用

九、抽象类与接口(重要)

如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类,他没有具体的实现方法,只提供了一个方法或成员

1.抽象类

abstract 修饰称为抽象类,抽象类中被 abstract 修饰的方法称为抽象方法,抽象方法不用给出具体的实现体。

// 抽象类:被abstract修饰的类
public abstract class Shape {
// 抽象方法:被abstract修饰的方法,没有方法体
	abstract public void draw();
	abstract void calcArea();
// 抽象类也是类,也可以增加普通方法和属性
	public double getArea(){
		return area;
	}
	protected double area; // 面积
}

抽象类

特点:

  1. 抽象类不能直接实例化对象
  2. 抽象方法不能是 private 的
  3. 抽象方法不能被final和static修饰,因为抽象方法要被子类重写
  4. 抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用 abstract 修饰
  5. 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
  6. 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量

2.接口

接口可以看成是:多个类的公共规范,是一种引用数据类型。接口你可理解成父类也就是父类,接口,可以理解为一类东西,只是运用的范围不同而已。

抽象类---->接口==(约等于)父类

2.1定义

接口的定义格式与定义类的格式基本相同,将class关键字换成 interface 关键字,就定义了一个接口。

public interface 接口名称{
// 抽象方法
		// public abstract 是固定搭配,可以不写
		public abstract void method1(); 
		
		public void method2();
		
		abstract void method3();
		// 注意:在接口中上述写法都是抽象方法,跟推荐方式4,代码更简洁
		void method4();

}

2.2 实现

接口不能直接使用,必须要有一个"实现类"来"实现"该接口,实现接口中的所有抽象方法。

子类和父类之间是extends 继承关系,类与接口之间是 implements 实现关系。

// USB接口
public interface USB {
	void openDevice();
	void closeDevice();
}
// 鼠标类,实现USB接口
public class Mouse implements USB {
	@Override
	public void openDevice() {
		System.out.println("打开鼠标");
	}
}

接口

抽象类与接口的小总结

  1. 接口类型是一种引用类型,但是不能直接new接口的对象
  2. 接口中每一个方法都是public的抽象方法, 即接口中的方法会被隐式的指定为 public abstract(只能是public abstract,其他修饰符都会报错)
  3. 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现
  4. 重写接口中方法时,不能使用默认的访问权限
  5. 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量
  6. 接口中不能有静态代码块和构造方法
  7. 接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class
  8. 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类
  9. jdk8中:接口中还可以包含default方法。
  10. 接口可以继承一个接口, 达到复用的效果. 使用 extends 关键字.(增加了扩展性)

其实对于接口和类,java自带的库中有很多好用的方法。这里先不赘述,在文件末尾,我会对这些进行总结(用链接的方式)(就复习而言这些足以)

十、异常(重要)

在Java中,将程序执行过程中发生的不正常行为称为异常。

1.常见类型

1.算术异常

System.out.println(10 / 0);

java.lang.ArithmeticException

2.数组越界异常

int[] arr = {1, 2, 3};
System.out.println(arr[100]);

java.lang.ArrayIndexOutOfBoundsException

3.空指针异常

int[] arr = null;
System.out.println(arr.length);

java.lang.NullPointerException

在这里插入图片描述

  1. Throwable:是异常体系的顶层类,其派生出两个重要的子类, Error 和 Exception
  2. Error:指的是Java虚拟机无法解决的严重问题,比如:JVM的内部错误、资源耗尽等,典型代表:StackOverflowError和OutOfMemoryError,一旦发生回力乏术。
  3. Exception:异常产生后程序员可以通过代码进行处理,使程序继续执行。

2.实现

抛出异常

public static int getElement(int[] array, int index){
	if(null == array){
	//抛出异常
		throw new NullPointerException("传递的数组为null");
	}
	if(index < 0 || index >= array.length){
	//抛出异常
		throw new ArrayIndexOutOfBoundsException("传递的数组下标越界");
	}
	return array[index];
}
public static void main(String[] args) {
		int[] array = {1,2,3};
		getElement(array, 3);
}

捕获异常:

语法格式:
修饰符 返回值类型 方法名(参数列表) throws 异常类型1,异常类型2…{
}

try-catch捕获并处理

 语法格式:
try{
		// 将可能出现异常的代码放在这里
}catch(要捕获的异常类型 e){
		// 如果try中的代码抛出异常了,此处catch捕获时异常类型与try中抛出的异常类型一致时,或者是try中抛出异常的父类时,就会被捕获到
		// 对异常就可以正常处理,处理完成后,跳出try-catch结构,继续执行后序代码
}[catch(异常类型 e){
		// 对异常进行处理
}finally{
		// 此处代码一定会被执行到
}]
		// 后序代码
		// 当异常被捕获到时,异常就被处理了,这里的后序代码一定会执行
		// 如果捕获了,由于捕获时类型不对,那就没有捕获到,这里的代码就不会被执行

异常的小总结

  1. throws必须跟在方法的参数列表之后
  2. 声明的异常必须是 Exception 或者 Exception 的子类
  3. 方法内部如果抛出了多个异常,throws之后必须跟多个异常类型,之间用逗号隔开,如果抛出多个异常类型具有父子关系,直接声明父类即可。
  4. try块内抛出异常位置之后的代码将不会被执行
  5. 如果抛出异常类型与catch时异常类型不匹配,即异常不会被成功捕获,也就不会被处理,继续往外抛,直到JVM收到后中断程序----异常是按照类型来捕获的
  6. 写程序时,有些特定的代码,不论程序是否发生异常,都需要执行,比如程序中打开的资源:网络连接、数据库连接、IO流等,在程序正常或者异常退出时,必须要对资源进进行回收。另外,因为异常会引发程序的跳转,可能导致有些语句执行不到,finally就是用来解决这个问题的。

你可能感兴趣的:(java,java,开发语言)