Java基础知识 -- 类的复用和多态

一. 类的复用

1. Java语言的一个重要特点:可重用性

  • 从类定义的角度:类的定义封装了类的成员变量和成员方法,类的不同对象实例也就起到了代码复用的效果
  • 从类间关系的角度:关联、依赖、聚合、合成(组合)、继承(纵向关系)、接口实现
依赖:一个类用到另一个类,但这种使用关系是偶然的、临时的。
	代码体现:局部变量、方法参数
关联:一个类用到另一个类,但这种使用关系是经常的。
	代码体现:成员变量
聚合:整体与局部的关系,是关联的一种,但是整体与部分可以独立存在
合成(组合):整体与局部的关系,是关联的一种,但是整体与局部不可独立存在

2. 继承

  • 声明形式:public subClassName extends superClassName { //body }
  • 超类(基类、父类):被继承的类
  • 子类:继承超类的类
  • 子类包括超类的所有成员,但不能访问超类中被声明成private的成员
  • 继承的特征:
    • 继承关系是传递的
    • 继承提供软件复用功能,称为“白盒复用”
    • Java仅支持单重继承,但可以使用接口机制来实现多重继承

注:(1)构造方法的执行顺序按先超类后子类的层次顺序
(2)如果调用超类有参数的构造方法,必须要在子类的构造方法中用super方法明确指出调用超类的哪个构造方法

3. 重写与重载

(1)重写:子类修改超类已有的方法
  • 超类方法的参数列表与被子类重写的方法的参数列表和返回类型必须相同
  • 重写方法不能比超类中被重写方法有更严格的访问权限
    • 访问权限大小关系:private < 默认 < protected < public
  • 重写方法一定不会出现新的受检异常,或者比被重写方法声明更加宽泛的受检异常
(2)重载:方法名相同,但参数列表不同
  • 不能通过访问权限、返回类型、抛出的异常进行重载
  • 方法的异常类型和数目不会对重载造成影响
区别点 重写(覆写) 重载
英文 Overriding Overloading
定义 方法名称、参数类型、返回值类型全部相同 方法名称相同,参数的类型或个数不同
被重写的方法不能拥有更严格的权限 对权限没有要求
范围 发生在继承类中 发生在同一个类中

4. abstract

  • abstract修饰符表示所修饰的部分没有完全实现,无法实例化
  • 如果一个类包含抽象方法,那这个类必须是抽象类,即用abstract修饰
  • 接口缺省为abstract
  • 构造方法、static方法、final方法不能使用abstract修饰符

5. final

(1)final类:
  • final类不能被继承,所以final类的成员方法没有机会被覆盖,默认都是final的
  • 类不能同时被声明为abstract和final,因为abstract类中的abstract方法永远不会被实现
  • 当一个类不需要子类,即该类已完全实现时,可设置该类为final类
(2) final方法:
  • final方法不允许被子类覆盖
  • 将方法设置为final方法的目的:
    • 是为方法“上锁”,防止任何继承类改变它的本来含义
    • 提高程序执行效率。将一个方法设成final后,编译器就可以把对那个方法的所有调用都置入“嵌入”调用里,即用函数代码对调用进行替换(内嵌机制)
(3)final变量(常量)
  • 用final修饰的成员变量表示常量,并且不会被子类覆盖
  • final空白(Black final):final变量定义时,可以先声明,而不给初值,但是在使用前必须要被赋值

6. this

  • 用法:
    • this(有参数 / 无参数):用于调用本类相应的构造方法
    • this.后跟变量或方法:使用本类的变量或方法

7. super:

  • 用法:
    • super(有参数 / 无参数):用于调用父类相应的构造方法
    • super.后跟变量或方法(父类中指明为public或protected的):使用超类的变量或方法

二. 多态(polymorphism)

1. 向上转型:子类转型成超类

  • 定义一个超类类型的引用指向一个子类类型对象
    • 超类类型的引用可以调用超类中定义的所有变量和方法,而无法调用在子类中定义而超类中没有定义的方法
    • 动态绑定:对于超类中定义的方法,如果子类重写了该方法,那么超类类型引用会调用子类中的这个方法
  • 不能把超类对象引用赋给子类类型引用变量
  • 一个类型引用只能引用其自身含有的方法和变量

2. 多态性

  • 多态:一个程序中同名的不同方法共存的情况下,发送消息给某个对象,让该对象自行决定响应何种行为方法
  • 当超类变量引用子类对象时,由被引用对象的类型而不是超类引用对象的类型,来决定调用谁的成员方法,但被调用的方法必须是超类中定义过的
  • 每一个实例对象都自带一个虚拟函数表(virtualtable),存储指向虚函数的指针
  • Java的虚拟函数表中包含了Java类所定义的所有成员方法

3. 运行时绑定(动态绑定)

  • 绑定:将一个方法调用和一个方法主体连接到一起
  • 早期绑定(静态绑定、编译时绑定):再程序运行之前进行绑定(由编译器和链接程序完成)
  • 后期绑定(动态绑定、运行时绑定):在程序运行期间进行绑定

4. Java的方法调用过程

  • 编译器查看对象变量的声明类型和方法名
  • 通过声明类型找到虚拟函数表
  • 编译器查看调用方法时提供的权限类型
    • 如果方法是private、static、final或者构造函数,编译器就可以确定调用哪个方法(静态绑定)
    • 如果不属于上述情况,就采用运行时绑定
静态绑定成员变量:在处理Java类中的成员变量时,并不是采用运行时绑定,而是一般意义上的静态绑定
可以将成员变量封装成get形式,通过动态绑定来实现调用子类的成员变量

public Father {
	protected String name = "Father";
	public String getName() {
		return name;
	}
}
public Son extends Father {
	protected String name = "Son";
	public String getName() { //子类必须写getName方法,否则调用sample.getName()结果还是Father
		return name;
	}
}
Father sample = new Son();
System.out.println(sample.name); //Father
System.out.println(sample.getName()); //Son

5. 多态的实现

  • 三种形式:继承、抽象类、接口

三. 接口

1. 面向对象设计模式的五大原则

  • 单一职责原则(SRP)
  • 开放-封闭原则(OCP)
  • 依赖倒转原则(DIP)
  • 接口隔离原则
  • Liskov(里氏)替换原则(LSP)

2. 接口的定义

  • 接口:抽象方法与常量值的集合
  • 声明形式:
[public] interface 接口名 [extends 父接口列表] {
	[public] [static] [final] 常量;
	[public] [abstract] 方法;
}

3. 接口的特征

  • 成员变量默认都是public、staic、final类型的(都可省略),但必须被显示初始化
  • 方法默认都是public、abstract类型的(都可省略),没有方法体,在接口中不能被实例化
  • 接口中没有构造方法,不能被实例化
  • 一个接口不能实现(implements)另一个接口,但可以继承多个其他接口
  • 一个类只能继承一个直接的超类,但可以实现多个接口,间接实现多重继承
  • 当类实现了某个接口时,它必须实现接口中所有的抽象方法,否则这个类必须声明为抽象的
  • 不允许创建接口的实例,但允许定义接口类型的引用变量,该引用变量引用实现了这个接口的实例
public class B implements A {}
A a = new B(); //正确
A a = new A(); //错误

4. 接口与抽象类的对比

(1)相同点:
  • 代表系统的抽象层,当一个系统使用一棵继承树上的类时,应该尽量把引用变量声明为继承树的上层抽象类型
  • 都不能被实例化
  • 都包含抽象方法
(2)不同点:
  • 抽象类可以提供某些方法的部分实现,而接口不能
  • 抽象类的实现只能由子类给出,而任意一个实现了接口中的方法的类都具有该接口的类型
  • 接口可以多继承,而抽象类不可以

四. 内部类

1. 内部类的定义

  • 一个类的定义放在另一个类的内部,这个放在内部的类就叫做内部类
  • 两种形式
    • 内部类:非静态
    • 嵌套类:静态的内部类
创建内部类:
创建非静态内部类对象时,一定要先创建相应的外部类对象
	outerClass outerObject = new outerClass(Constructor Parameters);
	outerClass.innerClass innerObject = outerObject.new innerClass(Constructor Parameters);

2. 内部类

  • 静态内部类(嵌套类):将内部类声明为static

    • 创建嵌套类的对象时,不需要创建其外部类的对象

    outerClass.innerClass innerObject = new outerClass.innerClass(Constructor Parameters)

    • 静态内部类不能访问外部类的非静态成员
  • 局部内部类:类被定义在一个方法或是一个代码块中

    • 局部内部类可以访问外部类的成员变量
    • 局部内部类只能访问外部类的final类型局部变量
  • 匿名内部类

    • 声明形式:
声明形式:
	new interfaceName(){ //body } 
	new superClassName(){ //body }
	
实例:
	interface Contents { int vaule(); }
	public class Goods {
		public Contents cont() {
			return new Contents() {
				private int i = 11;
				public int value() {
					return i;
				}
			};
		}
	}

3. 内部类与外部类

  • 内部类生成的class文件
    • 内部类与外部类在编译时,将生成各自的class文件
    • 外部类的class文件名称与类的生成规则相同
    • 内部类的class文件名是由外部类的类名与内部类的类名用“ ” 连 起 来 形 成 的 , 如 o u t e r C l a s s N a m e ”连起来形成的,如outerClassName outerClassNameinnerClassName.class
    • 匿名内部类的类名为outerClassName$#.class,其中的“#”是数字,从1开始,每生成一个匿名内部类,数字加一
  • 内部类访问外部类
    • 内部类可以在不需任何特殊条件下访问外部类的所有成员
    • 使用关键字.this和.new
	public class Outer {
		public Outer() {}
		
		private class Inner {
			public Outer getOuter() {
				return Outer.this;
			}
			public Outer newOuter() {
				return new Outer();
			}
		}
	}
	
	Outer out = new Outer();
	Outer.Inner inner = out.new Inner();
	Outer o1 = inner.getOuter(); //得到创建该内部类时使用的外部类对象的引用
	Outer o2 = inner.newOuter(); //创建了一个新引用
  • 内部类与向上转型
	interface Shape {
		public void paint();
	}
	public class Painter {
		private class Inner Shape implements Shape {
			public void paint() {
				System.out.println("painter");
			}
		}
		
		public Shape getShape() {
			return new InnerShape();
		}
		
		public static void main(String[] args) {
			Painter painter = new Painter();
			Shape shape = painter.getShape();
			shape.paint(); //painter
		}
	}
  • 内部类的继承
	class WithInner {
		class Inner {
			Inner() {
				System.out.println("WithInner.Inner");
			};
		}
	}
	
	public class InheritInner extends WithInner.Inner {
		InheritInner(WithInner wi) {
			wi.super();
			System.out.println("InheritInner");
		}
		
		public static void main(String[] args) {
			WithInner wi = new WithInner();
			InheritInner ii = new InheritInner(wi);
		}
	}
  • 内部类的重载
重载的两个内部类是完全独立的,各自在自己的命名空间内
	class Egg {
		privated Yolk y;
		protected class Yolk {
			public Yolk() {
				System.out.println("Egg.Yolk()");
			}
		}
		
		public Egg() {
			System.out.println("new Egg");
			y = new Yolk();
		}
	}
	
	public class BigEgg extends Egg {
		public class Yolk {
			public Yolk() {
				System.out.println("BigEgg.Yolk()");
			}
		}
		
		public static void main(String[] args) {
			BigEgg b = new BigEgg(); //new Egg; Egg.Yolk()
		}
	}

你可能感兴趣的:(Java,java)