目录
继承
继承的概念:
为什么要用到继承?
我们来看一下以下代码:
如果我们要描述一个小猫类和一个小狗类,那么我们可以发现它们的很多属性是相同的,那么这时候代码就会比较冗余。
那么要怎么把这些代码进行复用呢?这时候就可以用到继承了~~
这时候我们可以将Cat类和Dog类相同的部分写在Animal类里
我们借助了extends关键字来继承这个Animal类,Dog类和Cat类都继承这个Animal类,那么这两个类就拥有了自己类里的方法和属性以及Animal里的方法和属性。
我们将形如Animal类称为父类(超类/基类),将形如Cat类和Dog类称为子类(派生类)。
继承的好处:
1.继承可以实现共性的抽取,代码的复用。
父类成员的访问:
如果再代码中父类的变量名、方法名与子类的变量名、方法名相同,那我们会访问或者调用那个类的变量、方法呢?
class Base {
public int a = 1;
public int b = 2;//相同变量名
public void menthod1() {//相同方法名
System.out.println("Base类里的menthod1方法");
}
}
class Derived extends Base {
public int b = 3;//相同变量名
public int c = 4;
public void menthod1() {//相同方法名
System.out.println("Derived类里的menthod1方法");
}
public void run() {
menthod1();
System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("c = " + c);
}
}
public class Main1 {
public static void main(String[] args) {
Derived derived = new Derived();
derived.run();
}
}
那么以上的程序运行结果调用的会是子类的还是父类的呢?
我们可以发现运行结果是调用子类的变量和方法。
那如果我们要调用父类的变量和方法呢?
这时候就要用到super关键字了。
通过以上的例子不难发现如果在命名相同的情况下(其实命名不同也可以用)我们想要调用父类的变量或者方法,可以用使用super关键字,super关键字的作用就是再子类访问父类成员。
注意:super关键字只能在非静态方法中使用
子类构造方法:
俗话说先有夫再有子,我们在构建子类的时候会先执行父类的构造函数。
import java.util.SortedMap;
class Base {
public Base() {
System.out.println("父类Base的构造函数");
}
}
class Derived extends Base{
public Derived() {
System.out.println("子类Derived的构造函数");
}
}
public class Main2 {
public static void main(String[] args) {
Derived derived = new Derived();
}
}
为什么会先执行父类的构造函数再执行子类的构造函数呢?原因是我们的子类是由两部分组成的,第一部分是从父类继承下来的,第二部分是自己新增的,所以在构建子类的时候肯定要先执行父类的构造函数,使自己第一部分完整,然后再调用子类自己的构造方法,是自己新增的成员初始化完整。
super与this:
super与this的相同点:
1.都只能存在于非静态方法,但是可以访问静态成员变量和方法。(但是不推荐这样做,静态变量和方法最好直接使用类名调用)
class Base {
public static int b = 1;
public Base() {
System.out.println("父类Base的构造函数");
}
public static void menthod() {
}
}
class Derived extends Base{
public static int c = 2;
public Derived() {
super.b = 3;
this.c = 4;
super.menthod();
this.menthod();
System.out.println("子类Derived的构造函数");
}
public static void menthod() {
}
}
super与this的不同点:
1.this关键字实际上就是对当前对象的引用,可以取到当前对象的方法和属性,super是在子类中从父类继承下来的方法和属性的引用。
2.this(...)用来调用子类构造函数,super(...)用来调用父类构造函数,两个不能在同一个构造函数出现。
3.在子类的构造方法中一定会有super(...)的调用,this(...)不调用则没有。
再谈构造方法:
当创建一个子类对象时,它里面构造的顺序是什么样的,我们通过代码和执行结果来看一下吧~~
import java.util.SortedMap;
class Base {
public Base() {//父类构造函数
System.out.println("父类Base的构造函数");
}
static {
System.out.println("父类的静态代码块");
}
{
System.out.println("父类的实例代码块");
}
}
class Derived extends Base{
public Derived() {//子类构造函数
System.out.println("子类Derived的构造函数");
}
static {
System.out.println("子类的静态代码块");
}
{
System.out.println("子类的实例代码块");
}
}
public class Main2 {
public static void main(String[] args) {
Derived derived = new Derived();
System.out.println("----------");
System.out.println("再实例化一个对象: ");
Derived derived1 = new Derived();
}
}
执行结果:
从执行结果不难看出在创建一个子类对象的时候,它的构造执行顺序如下:
父类静态代码块->子类静态代码块->父类实例代码块->父类构造函数->子类实例代码块->子类构造函数
如果再创建一个子类,那静态代码块不再被执行,因为静态代码块只会执行一次。
Java中的多种继承方式:
1.单继承:
class A {
}
class B extends A {
}
2.多层继承:
class A {
}
class B extends A {
}
class C extends B {
}
class D extends C {
}
3.不同类继承同一个类
class A {
}
class B extends A {
}
class C extends A {
}
class D extends A {
}
注意:Java不支持一个类继承多个类!!!
final关键字:
1.final关键字修饰的字段表示常量,不得修改。
2.final修饰类表示不能被继承。
3.final修饰的方法不得被重写,被final修饰的方法也叫密封方法(稍后介绍)
类的组合:
现在我们要组装一台汽车,那我们就可以定义多个类来组装这台汽车:
// 轮胎类
class Tire{
}
// 发动机类
class Engine{
}
// 车载系统类
class VehicleSystem{
}
class Car{
private Tire tire; // 可以复用轮胎中的属性和方法
private Engine engine; // 可以复用发动机中的属性和方法
private VehicleSystem vs; // 可以复用车载系统中的属性和方法
}
类的组合和继承都可以提高代码的复用性,具体选择要根据实际情况~~
多态
概念:
多态通俗来讲就是多种状态,就是去完成某个任务时,不同对象实现时会产生不同的状态。
就拿吃东西这个任务来讲,如果是猫猫来执行就是吃小鱼,如果是狗狗来执行就是吃骨头~~
实现条件:
多态的三个实现条件(缺一不可!!!)
1.必须是在继承体系之下。
2.子类必须对父类的方法进行重写。
3.通过父类的引用调用重写的方法。
多态的体现:在传递不同类的对象时,会调用该对应类的方法。
class Animal {
public String name;
public int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println("小动物吃东西");
}
}
class Cat extends Animal {
public Cat(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(this.name + "吃小鱼");
}
}
class Dog extends Animal {
public Dog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(this.name + "吃骨头");
}
}
public class Main7 {
public static void testEat(Animal animal) {
animal.eat()//在编译阶段并不知道调用的是Cat类还是Dog类,
//要等程序运行起来了才能知道传的是什么类~~
}
public static void main(String[] args) {
Cat cat = new Cat("猫猫", 5);
Dog dog = new Dog("狗狗", 6);
testEat(cat);//Cat类执行eat
testEat(dog);//Dog类执行eat
}
}
执行结果:
重写:
重写也称为覆盖,是对父类无private修饰、无final修饰的方法的重新编写。
重写的规则:
1.类名要相同。
2.返回类型相同(除非具有父子关系)。
3.参数完全相同。
4.访问权限必须要大于等于父类(比如父类的某个方法用protect修饰,那子类要用protect或者public修饰~~)
5.被static、private、final修饰的方法都不能被重写
6.在重写的时候最好加@Override修饰,可以帮助我们检查重写的合理性。
应该避免在构造函数里调用重写的方法:
class Animal {
public String name;
public int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
eat();//在父类构造函数中调用被Cat重写的方法
}
public void method() {
eat();//在父类普通函数中调用被Cat重写的方法
}
public void eat() {
System.out.println("小动物吃东西");
}
}
class Cat extends Animal {
int weight = 20;//初始化weight的值
public Cat(String name, int age) {
super(name, age);
eat();//在子类构造函数中调用重写的方法
}
@Override
public void eat() {
System.out.println(this.name + "吃小鱼" + ", 它" + weight + "斤重~~");
}
}
public class Main7 {
public static void main(String[] args) {
Animal cat = new Cat("猫猫", 5);
System.out.println("我是分割线:----------");
System.out.println("调用Animal类里的普通函数");
cat.method();
}
}
上述代码执行结果:
分割线前:
所以我们尽量不要在构造方法中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题~~
分割线后:
我们在如果父类的普通函数里调用有子类重写的eat方法,那么在调用父类对象的这个普通函数执行的会是子类重写的eat方法,而不是父类的eat方法。
一个小例题:
答案为D
大家课后好好思考以下哦~~
向上转型:
向上转型就是创建一个子类对象,把它当作父类对象来引用:
应用场景:
1.直接赋值:子类对象赋给父类对象
2.方法传参:形参为父类对象引用,可以接收任意子类对象
3.返回值:返回任意子类参数
向上转型的优点:使代码实现更加灵活。
向上转型的缺点:不得使用子类特有的方法。
向下转型:
向上转型就是创建一个父类对象,把它当作子类对象来引用。
向下转型可以解决向上转型无法使用子类特有的方法的缺陷:
这样就可以执行子类特有的方法了~~
向下转型用得比较少,且不安全,如果强转失败就会抛异常:
那么在Java中为了提高转换的正确性,提供了instanceof,如果表达式为true,则可以安全转换:
多态的优缺点:
多态的优点:
1.能够降低代码的“圈复杂度”,避免大量执行if-else语句。
2.可扩展能力强。
多态的缺点:
1.代码的运行效率低。
小总结:继承与多态是Java非常重要的特性,大家一定要写代码,多领悟~~