目录
1.继承
1.1 继承的概念
继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生的新的类称为派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。
继承主要解决的问题是:共性的抽取,实现代码复用;
1.2 继承语法
在java中,表示类的继承关系需要使用到exends关键字:
修饰限定符 子类 extends 父类{
//...
}
class Animal{
public String name;
public int age;
public void fun1(){
System.out.println("It is an animal.");
}
}
class Dog extends Animal{
public void Bark(){
System.out.println("The dog named " +name+" is barking.");
}
}
class Cat extends Animal{
public void Meow(){
System.out.println("The cat named "+ name+" is meowing.");
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.fun1();
dog.name = "Mike";
dog.Bark();
Cat cat = new Cat();
cat.fun1();
cat.name= "Mary";
cat.Meow();
}
}
输出结果为:
注:(1)子类会将父类中的成员变量或成员方法继承到子类中;
(2)子类继承父类之后,必须要新增自己特有的成员以体现与父类的不同,否则不需要继承;
1.3 父类成员的访问
1.3.1 子类中访问父类的成员变量
1. 子类和父类不存在同名成员变量
class Base{
public int a = 10;
public int b = 20;
}
class Derived extends Base{
public int c = 30;
public void fun(){
System.out.println("a = "+a);
System.out.println("b = "+b);
System.out.println("c = "+c);
}
}
public class Main {
public static void main(String[] args) {
Derived derived = new Derived();
derived.fun();
}
}
输出结果为:
结论1:当子类当中不存在与父类同名的属性,在子类内读写属性时,如果子类中存在该属性则直接读取,如果子类当中不存在该属性就去父类当中寻找;
2. 子类和父类成员变量同名
class Base{
public int a = 10;
public int b = 20;
}
class Derived extends Base{
public int a = 100;
public int b = 200;
public int c = 30;
public void fun(){
System.out.println("a = "+a);
System.out.println("b = "+b);
System.out.println("c = "+c);
}
}
public class Main {
public static void main(String[] args) {
Derived derived = new Derived();
derived.fun();
}
}
输出结果为:
结论2:当子类当中存在与父类同名的属性,当在子类内读写属性时,会优先读取子类的属性;
如果需要指定访问父类中的属性,则需使用super关键字:
class Base{
public int a = 10;
public int b = 20;
}
class Derived extends Base{
public int a = 100;
public int b = 200;
public int c = 30;
public void fun(){
System.out.println("a = "+ super.a);
System.out.println("b = "+ super.b);
System.out.println("c = "+c);
}
}
public class Main {
public static void main(String[] args) {
Derived derived = new Derived();
derived.fun();
}
}
输出结果为:
注:结论2:当在子类内读写属性时,如果依次在子类、父类中都查询无果,则会编译报错;
1.3.2 子类中访问父类的成员方法
1. 成员方法名不同
class Base{
public int a = 10;
public int b = 20;
public void methodBase(){
System.out.println("Base function.");
}
}
class Derived extends Base{
public int a = 100;
public int b = 200;
public int c = 30;
public void methodDerived(){
System.out.println("Derived function.");
}
public void fun1(){
methodBase();
methodDerived();
}
}
public class Main {
public static void main(String[] args) {
Derived derived = new Derived();
derived.fun1();
}
}
输出结果为:
2. 成员方法名相同
class Base{
public int a = 10;
public int b = 20;
public void methodBase(){
System.out.println("Base function(methodBase).");
}
}
class Derived extends Base{
public int a = 100;
public int b = 200;
public int c = 30;
public void methodDerived(){
System.out.println("Derived function.");
}
public void methodBase(){
System.out.println("Derived function(method Base).");
}
public void fun1(){
methodBase();
}
}
public class Main {
public static void main(String[] args) {
Derived derived = new Derived();
derived.fun1();
}
}
输出结果为:
在子类中访问父类方法与在子类中访问父类属性的规则相同,就近访问子类的方法,如果该方法在子类中不存在就去父类中查询,如果两个类都查询无果则报错;同样,在子类访问与父类同名的方法时如需指定访问父类方法则需使用super关键字:
class Base{
public int a = 10;
public int b = 20;
public void methodBase(){
System.out.println("Base function(methodBase).");
}
}
class Derived extends Base{
public int a = 100;
public int b = 200;
public int c = 30;
public void methodDerived(){
System.out.println("Derived function.");
}
public void methodBase(){
System.out.println("Derived function(method Base).");
}
public void fun1(){
super.methodBase();
}
}
public class Main {
public static void main(String[] args) {
Derived derived = new Derived();
derived.fun1();
}
}
输出结果为:
注:
class Base{
public void methodBase(){
System.out.println("Base function(methodBase).");
}
}
class Derived extends Base{
public void methodBase(int a){
System.out.println("Derived function(method Base).");
}
public void fun1(){
methodBase();
}
}
在上文代码中:
① 分别位于父类和子类中的methodBase方法构成重载;
② 由于子类中的该方法需要一个int型参数,而在调用时并未传参,故而无需使用super.methodBase,也调用的是父类的该方法:
在java官方文档中关于重载的定义如下:
详见: https://docs.oracle.com/javase/specs/index.html
1.4 super关键字
在上文已介绍,super关键字的主要功能是:在子类中指定访问父类的属性或方法:
super.data:在子类当中访问父类的成员属性;
super.func():在子类当中访问父类的成员方法;
注:① 只能在非静态方法内使用;
② 在子类方法中访问父类的属性或方法;
1.5 子类构造方法
子类对象构造时需要先调用其父类的构造方法,然后再执行子类的构造方法;
class Animal{
public String name;
public int age;
public void fun1(){
System.out.println("It is an animal.");
}
public Animal(){
}
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
}
class Dog extends Animal{
public String breed;
public Dog(String name,int age,String breed){
super(name,age);
//显式调用父类构造方法对父类成员进行初始化
this.breed = breed;
}
public void Bark(){
System.out.println("The dog named " +name+" is barking.");
}
@Override
public String toString() {
return "Dog{" +
"breed='" + breed + '\'' +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
class Cat extends Animal{
public String color;
public Cat(String name,int age,String color){
//super();
//当父类拥有无参构造方法时,编译器会默认给子类增加一个父类的构造方法
// 此时可以不再显式书写super();
super(name,age); //为传参完整,此处以带参父类构造方法为例
this.color = color;
}
public void Meow(){
System.out.println("The cat named "+ name+" is meowing.");
}
@Override
public String toString() {
return "Cat{" +
"color='" + color + '\'' +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog("Mike",5,"Labrador");
System.out.println(dog);
Cat cat = new Cat("Mary",5,"grey");
System.out.println(cat);
}
}
输出结果为:
注:(1)子类调用父类构造方法时必须将super语句放在子类构造函数的第一行,否则就会报错;
(2)super(...)只能在子类构造方法中出现一次且与this不能同时出现;
(3)当没有在父类中显式书写带参构造方法时,编译器会默认增加一个无参的构造方法,故而建议在编写类尤其是有可能发生继承拥有子类时,重载一个无参的构造方法与一个带参的构造方法;
1.6 再谈初始化
class Animal{
public String name;
public int age;
//静态代码块
static{
System.out.println("Animal static{}");
}
//实例代码块
{
System.out.println("Animal{}");
}
//无参构造方法
public Animal(){
System.out.println("Animal(){}");
}
}
class Dog extends Animal{
public String breed;
//无参构造方法
public Dog(){
System.out.println("Dog(){}");
}
//静态代码块
static{
System.out.println("Dog static{}");
}
//实例代码块
{
System.out.println("Dog {}");
}
}
public class Main {
public static void main(String[] args) {
Dog dog1 = new Dog();
System.out.println("------------");
Dog dog2 = new Dog();
}
}
输出结果为:
1.7 protected 关键字
创建如下包与类:
(1)同一包中的不同类:
试运行以下代码:
以上代码可以正常运行,说明protected修饰的属性在同一包中的不同类中可以进行访问;
(2)不同包中的子类:
注:(1)上文代码的前提是TestDemo1类是public修饰的;
(2)private修饰的成员不是没有被继承,而是继承了无法访问;
1.8 继承方式
(1)在Java中只支持以下几种继承方式:
单继承(B继承A),多继承(C继承B,B继承A),不同类继承同一个类(B和C都继承A);
(2)C++支持多继承,但Java不支持多继承(C继承A和B);
(3)一般我们不希望有超过三层的继承关系,如果想通过语法进行限制,则需使用final关键字;
1.9 final 关键字
1.9.1 修饰变量或字段
final int A= 10;
A=20;
报错如下:
final修饰变量或字段表示常量,代表不能被修改,并建议名称改为大写;
1.9.2 修饰类
运行如下代码:
final class Animal{
public String name;
public int age;
public Animal(){
System.out.println("Animal(){}");
}
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
}
class Dog extends Animal{
public String breed;
public Dog(String name,int age,String breed){
super(name,age);
//显式调用父类构造方法对父类成员进行初始化
this.breed = breed;
}
public Dog(){
System.out.println("Dog(){}");
}
public void Bark(){
System.out.println("The dog named " +name+" is barking.");
}
}
class Cat extends Animal{
public String color;
public Cat(String name,int age,String color){
super(name,age);
this.color = color;
}
public void Meow(){
System.out.println("The cat named "+ name+" is meowing.");
}
}
public class Main {
public static void main(String[] args) {
Dog dog1 = new Dog();
Dog dog2 = new Dog();
}
}
报错如下:
final修饰类表示该类不能再被继承;
注:① 如果需要将成员变量定义为final,语法规定必须同时给定一个初始值;
② 运行如下代码:
public static void main(String[] args) {
final int[] array = {1,2,3};
array = new int[10];
}
报错如下:
因为此时final修饰的是引用array,即array保存的值(指向的对象)是不可以更改的,但是array引用指向对象的内容是可以改变的,即以下代码可以正常运行:
public static void main(String[] args) {
final int[] array = {1,2,3};
array[0]=2;
}
1.10 继承与组合
与继承类似,组合也是一种表示类与类之间关系的方式(a part of);
以教师、学生、学校以及学院类为例:
class Students{ //学生类
}
class Teachers{ //教师类
}
class Campus{
public Students[] students = new Students[3];
public Teachers[] teachers = new Teachers[3];
//将另外一个类作为当前类的一个属性称为组合
}
class academy extends Campus{ //学院类继承学校类
}
public class Main {
public static void main(String[] args) {
Campus campus = new Campus();
}
}
内存分布:
2.多态
2.1 多态的概念
多态即多种形态,具体就是去完成某个行为,当不同的对象去完成时会有不同的状态;
2.2 多态实现的条件
(1)必须在继承体系下;
(2)子类必须要对父类中方法进行重写;
(3)通过父类的引用调用重写的方法(向上转型);
多态的体现:在代码运行时,当传递不同类的对象时会调用对应类中的方法;
2.3 重写
2.3.1 方法重写的规则:
(1)方法名、参数列表等相同;
(2)被重写的方法可以返回值可以不同,但必须具有父子关系;
(3)子类的访问修饰限定权限必须大于等于父类的权限;
(4)父类被private、static以及final修饰的方法都不能被重写;
(final修饰的方法称为密封方法)
(5)重写方法可以使用@override显式注解,此时如果方法名与参数列表等与父类不同,编译器就会帮助检查并报错。
也可由编译器自动生成:Alt+Insert选中Override Methods...选择需要重写的方法即可;
2.3.2 重写与重载
区别点 | 重写 | 重载 |
参数列表 | 一定不能修改 | 必须修改 |
返回类型 | 一定不能修改 | 可以修改 |
访问限定符 | 一定不能作更严格地限定 | 可以修改 |
2.3.3 重写设计的原则
对于已经投入使用的类尽量不要修改,最好的方式是重新定义一个类,来重复利用其中共性的内容,并且添加或者改动新的内容;
2.4 向上下转型与动静态绑定
2.4.1 向上转型
形式1:直接赋值:
class Animal{
public String name;
public int age;
public void fun(){
System.out.println("It is an animal.");
}
}
class Dog extends Animal{
public void Bark(){
System.out.println("The dog named "+name+" is barking.");
}
}
public class Main {
public static void main(String[] args) {
//1.
Dog dog = new Dog();
dog.name = "Mike";
dog.age = 5;
dog.Bark();
Animal animal1 = dog; //子类给父类:向上转型
System.out.println("--------------");
//2.
Animal animal2 = new Dog();
animal2.name = "Mary";
animal2.fun();
}
}
输出结果为:
还有两种向上转型的形式:(基于以上Animal、Dog、Bird类)
形式2:方法传参:
public static void fun1(Animal animal){
}
public static void main(String[] args) {
Dog dog = new Dog();
fun1(dog);
}
形式3:方法返回值:
public static Animal fun2(){
return new Dog();
}
public static void main(String[] args) {
Dog dog = new Dog();
fun2();
}
注:(1)向上转型时,不能通过父类对象调用子类的成员,只能通过父类的引用访问父类的成员;
(2)向上转型的优点:让代码实现更加灵活
(3)向上转型的缺点:不能调用到子类特有的方法,除非实现了重写;
2.4.2 动态绑定
动态绑定也称为后期绑定,即在编译时不能确定方法的行为,需要在程序运行时才能够确定具体运行哪个类的方法;
代码示例1:
class Animal{
public String name;
public int age;
public void Species(){
System.out.println(name+" is an animal.");
}
}
class Dog extends Animal{
public void Bark(){
System.out.println("The dog named "+name+" is barking.");
}
@Override //注解
public void Species() {
System.out.println(name + " is a dog.");
}
}
class Bird extends Animal{
public String wing;
public void Fly(){
System.out.println("The bird named "+name+" is flying.");
}
@Override //注解
public void Species(){
System.out.println(name+" is a bird.");
}
}
public class Main {
public static void main(String[] args) {
Animal animal1 = new Dog();
animal1.name = "Mike";
animal1.age = 5;
animal1.Species();
System.out.println("--------------");
Animal animal2 = new Bird();
animal2.name = "Mary";
animal2.age = 4;
animal2.Species();
}
}
输出结果为:
此时会发现:形式上虽是Animal的对象调用Species()方法,但输出的却是其new类的同名方法,是因为发生了动态绑定:
动态绑定需要三个前提条件:① 向上转型;② 构成重写;③ 通过父类引用调用重写的方法;
动态绑定是多态的基础;
代码示例2:
class Animal{
public String name;
public int age;
public void Species(){
System.out.println(name+" is an animal.");
}
}
class Dog extends Animal{
public void Bark(){
System.out.println("The dog named "+name+" is barking.");
}
@Override //注解
public void Species() {
System.out.println(name + " is a dog.");
}
}
class Bird extends Animal{
public String wing;
public void Fly(){
System.out.println("The bird named "+name+" is flying.");
}
@Override
public void Species() {
System.out.println(name + " is a bird.");
}
}
public class Main {
public static void fun2(Animal animal) {
animal.Species();
}
public static void main(String[] args) {
Animal animal1 = new Dog();
animal1.name = "Mike";
animal1.age = 5;
fun2(animal1);
System.out.println("--------------");
Animal animal2 = new Bird();
animal2.name = "Mary";
animal2.age = 4;
fun2(animal2);
}
}
使用方法传参方式更能体现多态,在未传递引用调用fun2方法时,在fun2方法内部是无法确定待调方法的,只有当对象的引用作为参数传递给fun2方法时,fun2方法内部才确定调用的是同名方法中的哪一个;
即:当父类引用 引用的对象不一样时,调用的方法是不一样的,这种现象就称为多态;
2.4.3 静态绑定
静态绑定也称为前期绑定,在编译时就确定即将调用的方法称为静态绑定,如java中调用重载的方法就是静态绑定;
2.4.4 向下转型
class Animal{
public String name;
public int age;
public void Species(){
System.out.println(name+" is an animal.");
}
}
class Dog extends Animal{
public void Bark(){
System.out.println("The dog named "+name+" is barking.");
}
@Override //注解
public void Species() {
System.out.println(name + " is a dog.");
}
}
class Bird extends Animal{
public String wing;
public void Fly(){
System.out.println("The bird named "+name+" is flying.");
}
@Override
public void Species() {
System.out.println(name + " is a bird.");
}
}
public class Main {
public static void main(String[] args) {
Animal animal1 = new Dog();
//向下转型
Dog dog = (Dog)animal1; //强转
dog.name = "Jack";
dog.Bark();
}
}
输出结果为:
但向下转型是非常不安全的,基于以上Animal、Dog、Bird类,运行如下代码:
public static void main(String[] args) {
Animal animal1 = new Dog();
//向下转型
Bird bird = (Bird)animal1;
bird.name = "Alice";
bird.Fly();
}
在编译时编译器并不会报错,而当运行时就会出现错误:
当new语句的赋值类范围大于接受类时,如上文示例,所有的动物都是猫,这显然是错误的;
可以增加判断程序:
if(animal1 instanceof Bird) {
Bird bird = (Bird) animal1;
bird.name = "Alice";
bird.Fly();
}
该语句用于判断animal1是否引用了一个Bird对象,如果为假则不执行一系列语句,故程序不再报错,也不打印任何内容;
2.5 多态的优缺点
2.5.1 多态的优点
(1)可以降低代码的“圈复杂度”,避免使用大量的“if-else”:
class Shape{
public void draw(){
System.out.println("Drawing.");
}
}
class Rectangle extends Shape{
@Override
public void draw() {
System.out.println("Drawing a rectangle.");
}
}
class Circle extends Shape{
@Override
public void draw() {
System.out.println("Drawing a circle.");
}
}
class Flower extends Shape{
@Override
public void draw() {
System.out.println("Drawing a flower.");
}
}
public class Main {
public static void drawMap(Shape shape){
shape.draw();
}
public static void main(String[] args) {
Rectangle rectangle1 = new Rectangle();
drawMap(rectangle1);
Circle circle1 = new Circle();
drawMap(circle1);
Flower flower1 = new Flower();
drawMap(flower1);
}
}
或写为:
class Shape{
public void draw(){
System.out.println("Drawing.");
}
}
class Rectangle extends Shape{
@Override
public void draw() {
System.out.println("Drawing a rectangle.");
}
}
class Circle extends Shape{
@Override
public void draw() {
System.out.println("Drawing a circle.");
}
}
class Flower extends Shape{
@Override
public void draw() {
System.out.println("Drawing a flower.");
}
}
public class Main {
public static void drawMap1(){
Rectangle rectangle = new Rectangle();
Circle circle = new Circle();
Flower flower = new Flower();
Shape[] shapes = {rectangle,circle,rectangle,circle,flower};//向上转型
for(Shape shape:shapes){
shape.draw();
}
}
public static void main(String[] args) {
drawMap1();
}
}
(2)可扩展能力强:若要新增一种新的形状, 使用多态的方式代码改动成本也较低:
class Triangle extends Shape{
@Override
public void draw() {
System.out.println("Drawing a triangle.");
}
}
2.6 避免在构造方法中调用重写的方法
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 Main {
public static void main(String[] args) {
D d = new D();
}
}
输出结果为:
即当在父类构造方法中调用与子类的同名的方法时,会执行子类的该方法;
并且由于d对象本身还未完成构造,num还处于未初始化的状态,故而值为0;
在实际编写程序时应该避免这样的编写方法;