目录
一、继承
1、定义:
2、用法:
3、使用从父类继承的成员
(1)、子类使用从父类继承的成员变量
(2)、子类使用从父类继承的成员方法
4、super
(1)、定义:
5、子类构造方法
(1)、定义
6、执行顺序
(1)、没有继承关系时
(2)、有继承关系时
7、继承方式
(1)、单继承
(2)、多继承
(3)、不同类继承同一个类
8、final
(1)、final修饰变量或字段
(2)、修饰方法
(3)、修饰类
9、继承与组合
(1)、定义
二、多态
1、定义
2、使用
3、重写
(1)、定义
(2)、规则
(3)、绑定
4、向上转型和向下转型
(1)、向上转型
(2)、向下转型
5、多态优缺点
(1)、优点
(2)、缺点
6、避免在构造方法中调用重写的方法
在Java语言中,两个不同类可能会有部分重叠的属性和方法,因此为了减少代码的重复率,Java提出了继承的概念以提高代码的复用率。
继承:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特 性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。
Java语言中,类的继承关系,需要使用关键字extends
public class 子类 extends 父类 {
}
例:
class person{
public String name;
public int age;
public void eat(){
System.out.println("吃饭!!!");
}
public void sleep(){
System.out.println("休息!!!");
}
}
class student extends person{
public void learning{
System.out.println("上课!!!")
}
}
class teacher extends person{
public void teaching{
System.out.println("教学!!!")
}
}
public class test {
public static void main(String[] args) {
student s1 = new student();
// student类中并没有定义任何成员变量,name和age属性是从父类person继承下来的
System.out.println(s1.name);
System.out.println(s1.age);
// s1访问的eat()和sleep()方法也是从person中继承下来的
s1.eat();
s1.sleep();
}
}
注:
在继承体系中,子类将父类中的方法和字段继承下来了,那在子类中能否直接访问父类中继承下来的成员呢?
1. 子类和父类不存在同名成员变量
public class A {
int a;
int b;
}
public class B extends A{
int c;
public void func(){
a = 10; // 访问从父类中继承下来的a
b = 20; // 访问从父类中继承下来的b
c = 30; // 父类没有c,访问子类的c
}
}
2. 子类和父类存在同名成员变量
public class A {
int a;
int b;
}
public class B extends A{
int a; // 与父类中成员a同名,且类型相同
public void method(){
a = 1; // 访问子类的a!!!
b = 2; // 子类没有b,访问的是从父类继承下来的b
//c = 3; // 编译失败,因为父类和子类都没有成员变量c
}
}
在子类方法中或通过子类对象访问成员时:
1. 子类和父类不存在同名成员方法
public class A {
public void func1(){
System.out.println("A中的func1方法");
}
}
public class B extends A{
public void func2(){
System.out.println("B中的func2方法");
}
public void func3(){
func2(); // 访问子类的方法
func1(); // 访问父类的方法
// func4(); // 父类和子类都没有,编译失败
}
}
2. 子类和父类存在同名的成员方法
public class A {
public void func1(){
System.out.println("A中的func1()");
}
public void func2(){
System.out.println("A中的func2()");
}
}
public class B extends A{
public void func1(int a) {
System.out.println("B中的func1(int)");
}
public void func2(){
System.out.println("B中的func2()");
}
public void func3(){
func1(); // 没有传参,访问父类中的func1()
func1(20); // 传递int参数,访问子类中的func1(int)
//方法重载,注意不是重写!!!
func2(); // 访问的是子类中的func2()
}
}
注:
在上述的代码中,子类和父类中存在同名的成员变量和方法,调用同名的变量和方法时只能使用子类中的成员,但是想要明确调用父类中的成员就需要使用super关键字。
public class A {
int a;
int b;
}
public class B extends A{
int a; // 与父类中成员变量同名且类型相同
char b; // 与父类中成员变量同名但类型不同
public void func1(){
// 对于同名的成员变量,直接访问时,访问的都是子类的
this.a = 100;
this.b = 101;
// 注意使用this是引用当前类的对象,而想要访问父类的成员变量时,需要使用super关键字
super.a = 200;
super.b = 201;
}
}
注:super只能在非静态方法中使用
构造子类对象时,先要调用父类的构造方法,将从父类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整 。
public class A {
public A(){
System.out.println("A()");
}
}
public class B extends A{
public B(){
super();
// 用户没有写构造方法时,编译器会自动添加无参构造方法,而且super()必须是子类构造方法中第一
条语句。
System.out.println("B()");
}
}
public class Test {
public static void main(String[] args) {
B b = new B();
}
}
结果:
// A()
// B()
注:
class A {
public int m;
public int n;
public A(String m, int n) {
this.m = m;
this.n = n;
System.out.println("构造方法执行");
}
{
System.out.println("实例代码块执行");
}
static {
System.out.println("静态代码块执行");
}
}
public class test {
public static void main(String[] args) {
A a1 = new A(1,2);
System.out.println("------------------------");
A a2 = new A(3,4);
}
}
执行结果:
静态代码块
实例代码块
构造方法
------------------------
实例代码块
构造方法
注:
class A {
public int m;
public int n;
public A(int m, int n) {
this.m = m;
this.n = n;
System.out.println("A的构造方法执行");
}
{
System.out.println("A的实例代码块执行");
}
static {
System.out.println("A的静态代码块执行");
}
}
class B extends A{
public B(int m,int n) {
super(m,n);
System.out.println("B的构造方法执行");
}
{
System.out.println("B的实例代码块执行");
}
static {
System.out.println("B的静态代码块执行");
}
}
public class test {
public static void main(String[] args) {
B b1 = new B(1,2);
System.out.println("--------------------");
B b2 = new B(3,4);
}
}
执行结果:
A的静态代码块执行
B的静态代码块执行
A的实例代码块执行
A的构造方法执行
B的实例代码块执行
B的构造方法执行
---------------------
A的实例代码块执行
A的构造方法执行
B的实例代码块执行
B的构造方法执行
注:
注:Java不支持多继承,并且一般不出现超过三层的继承关系。继承层次太多, 就需要考虑对代码进行重构了。如果想从语法上进行限制继承, 可以使用 final 关键字。
final关键可以用来修饰变量、成员方法以及类。
此处表示常量即不能修改
final int a = 10;
a = 20; // 编译出错
此方法不能被重写
此类不能被继承
final public class Animal {
}
public class Bird extends Animal {
}
// 编译出错Error:(3, 27) java: 无法从最终com.bit.Animal进行继
组合和继承类似,既是一种表达类之间关系的方式,也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如 extends 这样的关键字),仅仅是将一个类的实例作为另外一个类的字段。继承表示对象之间是is-a的关系,组合表示对象之间是has-a的关系
例:
// 轮胎类
class Tire{
}
// 发动机类
class Engine{
}
// 车载系统类
class VehicleSystem{
}
class Car{
private Tire tire; // 可以复用轮胎中的属性和方法
private Engine engine; // 可以复用发动机中的属性和方法
private VehicleSystem vs; // 可以复用车载系统中的属性和方法
}
class Benz extend Car{
// 将汽车中包含的:轮胎、发送机、车载系统全部继承下来
}
多态为不同的对象去完成同一个行为时,会产生出不同的状态。总的来说就是同一件事情,发生在不同对象身上,就会产生不同的结果。
在Java中要实现多态,必须要满足如下几个条件,缺一不可:
在代码运行时,当传递不同类对象时,会调用对应类中的方法
例:
public class A {
int m;
public Animal(int m){
this.m = m;
}
public void func(){
System.out.println("呵呵呵!!!");
}
}
public class B extends A{
public B(int m){
super(m);
}
public void func(){
System.out.println("哈哈哈!!");
}
}
public class C extends A {
public C(int m){
super(m);
}
public void func(){
System.out.println("嘿嘿嘿!");
}
}
public class test {
public static void func1(A a){
a.func);
}
public static void main(String[] args) {
B b = new B(1);
C c = new C(2);
func1(b);
func1(c);
}
}
运行结果:
哈哈哈!!
嘿嘿嘿!
重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。
动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。
定义:实际就是创建一个子类对象,将其当成父类对象来使用,从小范围向大范围的转换。
父类类型 对象名 = new 子类类型()
优点:让代码实现更简单灵活。
缺点:不能调用到子类特有的方法。
定义:将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转换。
注:向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入了 instanceof ,如果该表达式为true,则可以安全转换。
1. 能够降低代码的 "圈复杂度", 避免使用大量的 if - else
圈复杂度是一种描述一段代码复杂程度的方式。一段代码如果平铺直叙,那么就比较简单容易理解。而如果有很多的条件分支或者循环语句,就认为理解起来更复杂。因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数,这个个数就称为 "圈复杂度"。如果一个方法的圈复杂度太高,就需要考虑重构。不同公司对于代码的圈复杂度的规范不一样. 一般不会超过 10 .
例:
public static void drawShapes() {
Rect rect = new Rect();
Cycle cycle = new Cycle();
Flower flower = new Flower();
String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"};
for (String shape : shapes) {
if (shape.equals("cycle")) {
cycle.draw();
} else if (shape.equals("rect")) {
rect.draw();
} else if (shape.equals("flower")) {
flower.draw();
}
}
}
使用使用多态, 则不必写这么多的 if - else 分支语句, 代码更简单
public static void drawShapes() {
Shape[] shapes = {new Cycle(), new Rect(), new Cycle(),
new Rect(), new Flower()};
for (Shape shape : shapes) {
shape.draw();
}
}
2. 可扩展能力更强
如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低
class Triangle extends Shape {
@Override
public void draw() {
System.out.println("△");
}
}
对于类的调用者来说(drawShapes方法),只要创建一个新类的实例就可以了,改动成本很低。
而对于不用多态的情况,就要把 drawShapes 中的 if - else 进行一定的修改,改动成本更高。
用尽量简单的方式使对象进入可工作状态,不要在构造器中调用方法(如果这个方法被子类重写,就会触发动态绑定,但是此时子类对象还没构造完成),可能会出现一些隐藏的但是又极难发现的问题。
例:
class B {
public B() {
func();
}
public void func() {
System.out.println("B.func()");
}
}
class D extends B {
private int num = 1;
public void func() {
System.out.println("D.func() " + num);
}
}
public class test {
public static void main(String[] args) {
D d = new D();
}
}
// 执行结果:D.func() 0
构造 D 对象的同时,会调用 B 的构造方法。B 的构造方法中调用了 func 方法,此时会触发动态绑定,会调用到 D 中的 func。此时 D 对象自身还没有构造,此时 num 处在未初始化的状态,值为 0。如果具备多态性,num的值应该是1。所以在构造函数内,尽量避免使用实例方法,除了final和private方法。