本章主要知识结构如下:
this与super构造方法的多态抽象类与接口引用内部类代码与对象初始化的过程标注如下(注释里面的数字表示对象初始化过程中该句代码的执行顺序):
class AddClass { public int x = 0, y = 0, z = 0; // 5 当执行到构造函数的定义以后,便执行此句代码 AddClass(int x) { // 4 执行到了AddClass类的第一个构造函数,接下来要执行非静态代码块,注意此时并未执行(this.x=x)这一句 this.x = x; // 6 执行完非静态代码以后再执行构造函数内的实现 } AddClass(int x, int y) { this(x); // 3 this.y = y; // 7 步骤3已结束,执行此步 } AddClass(int x, int y, int z) { this(x, y); // 2 this.z = z; // 8 步骤2已结束,执行此步 } public int add() { return x + y + z; } } public class SonAddClass extends AddClass { int a = 0, b = 0, c = 0; // 9 已经执行完父类的构造函数,接下来需要先执行本类的非静态代码,再执行本类构造函数内部的代码 SonAddClass(int x) { super(x); a = x + 7; } SonAddClass(int x, int y) { super(x, y); a = x + 5; b = y + 5; } SonAddClass(int x, int y, int z) { super(x, y, z); // 1 首先执行此构造函数,然后调用父类的构造函数 a = x + 4; // 10 执行本类构造函数内的代码,此时已经执行完本类的非静态代码 b = y + 4; // 11 c = z + 4; // 12 } public int add() { System.out.println("super:x+y+z=" + super.add()); return a + b + c; } public static void main(String[] args) { SonAddClass p1 = new SonAddClass(2, 3, 5); // 0 对此句对象初始化进行debug,下面以数字记录初始化过程 SonAddClass p2 = new SonAddClass(10, 20); // 13 上面的new语句共执行了12个步骤,接下来便执行此句初始化过程 SonAddClass p3 = new SonAddClass(1); System.out.println("a+b+c=" + p1.add()); System.out.println("a+b=" + p2.add()); System.out.println("a=" + p3.add()); } } 结果输出为: super:x+y+z=10 a+b+c=22 super:x+y+z=30 a+b=40 super:x+y+z=1 a=8对例6.4中对象初始化过程进行总结:
对象初始化时首先执行构造函数的声明处(不执行构造函数内部的定义代码),然后执行类内部的非静态代码,当执行完非静态代码定义以后再执行构造函数内的代码。子类继承了父类的情况下,先执行子类构造函数,若子类构造函数中通过spuer关键字调用了父类的构造函数则执行对应的父类的构造函数,若子类构造函数中没有使用super显式调用父类的构造函数,则会默认调用父类的无参构造函数。 以下代码可以证明这一点:class New{ public New(){ System.out.println("无参"); } public New(String s){ System.out.println("参数是"+s); } } class SubNew extends New{ public SubNew(){ System.out.println("子类的构造函数"); // 子类中没有显示调用父类构造函数,则会默认调用父类的无参构造函数 } } public class Test{ public static void main(String[] args){ SubNew sn = new SubNew(); } } 输出为: 无参 子类的构造函数对例6.6进行探索的代码如下(注释里面的数字表示代码的执行顺序):
class Pare { int i = 3; // 5 Pare() { // 4 System.out.println("super"); // 6 } } public class Construct extends Pare { int i = 8; // 7 Construct() { // 3 System.out.println("this"); // 8 } Construct(int num) { this(); // 2 } // 9 int getSuper() { return super.i; }; public static void main(String[] args) { Construct ct = new Construct(9); // 1 对此句进行探索 System.out.println(ct.i); // 10 System.out.println(ct.getSuper()); } } 输出为: super this 8 3对例6.6进行总结:
第一点与例6.4的总结的第一条相同:对象初始化时先执行到构造函数的声明处,再执行非静态代码,最后执行构造函数里面的代码;当子类中的属性与父类同名时,若直接使用属性名调用属性则默认调用的是子类的属性,等同于this.属性名,若需要调用父类的属性则可以使用super.属性名;可以通过类的this关键字来实现对象之间互发消息。
举例来说要创建两个类(A, B),A类中含有B类的对象引用,B类中含有A类的对象引用,这样A类的对象便可以调用B类的对象引用,从而像B类对象发消息。
组合:是指在一个类中拥有另一个类的对象引用,这样便可以通过对象引用调用另一个类的公有属性和方法。
继承:子类继承父类时,便继承了父类所有的属性和方法(也包括private修饰的属性和方法,只是子类看不到private修饰的属性和方法)。
二者的使用场景: 继承通常是用来对父类的属性和方法进行完善,来创建出很具体的类; 在仅仅需要使用某个类的某些方法时,而不对该类的这些方法进行修改的情况下使用组合最方便。
Java中多态有两种,编译时多态和运行时多态。
编译时多态:是指在编译时就可以确定要执行哪个方法,方法重载就属于编译时多态,此外对象引用指向了本类的对象(与父类的对象引用指向子类对象相对)时也属于编译时多态。
运行时多态:在运行时才可以确定执行哪个方法的多态即为运行时多态。
请看以下例子:
class Person{ String type = "P"; public String getName() { String name = "Person"; return name; } } class Man extends Person{ String type = "M"; public String getName(){ String name = "Man"; return name; } public String newName(){ return "newName"; } } public class Test { public static void main(String[] args) { Person p = new Man(); // 父类的对象引用指向了子类对象 System.out.println(p.type); //返回结果为P System.out.println(p.getName()); //返回结果为Man System.out.println(((Man)p).newName()); // } }当父类的对象引用指向了子类对象时(即上述代码Person p = new Man();),若子类对象对父类对象的某个方法进行了覆盖,则通过父类对象引用调用被子类覆盖的方法时会调用子类的方法,若子类没有对该方法进行覆盖则会调用父类的方法。
若子类中具有新的父类中没有的方法,则父类的对象引用无法调用子类中新的方法,若父类对象引用想要调用子类中新的方法(即上述main函数中的最后一句代码),需要进行强制类型转换,将父类对象引用转换为子类对象引用,即必须将Person对象引用p转为Man对象引用类型,因为newName方法是子类中特有的,父类没有这个方法。注意此处的转换格式!需要使用((Man)p)整体来调用newName方法
参考链接:Java 编译时多态和运行时多态
例6.8(未改写,使用了抽象类)代码如下:
abstract class Shapes { protected int x, y, k; protected double m; public Shapes(int x, int y, int k, double m) { this.x = x; this.y = y; this.k = k; this.m = m; } abstract public double getArea(); abstract public double getPerimeter(); } class Rect extends Shapes { public double getArea() { return (k * m); } public double getPerimeter() { return (2 * k + 2 * m); } public Rect(int x, int y, int width, int height) { super(x, y, width, height); } } class Triangle extends Shapes { public double getArea() { return (Math.sqrt(m * (m - k) * (m - x) * (m - y))); } public double getPerimeter() { return (k + x + y); } public Triangle(int baseA, int baseB, int baseC) { super(baseA, baseB, baseC, 0); // m充当了周长的一半 m = (baseA + baseB + baseC) / 2.0; } } class Circle extends Shapes { public double getArea() { return (m * m * Math.PI); // Math是java.lang包中的类,PI是静态其属性,其值为Π } public double getPerimeter() { return (2 * Math.PI * m); } public Circle(int x, int y, int width) { // k充当了直径,m充当了半径的角色 super(x, y, width, width / 2.0); } } public class RunShape{ public static void show(Shapes s){ System.out.println(s.getClass().getName()+" Area: "+s.getArea()); System.out.println(s.getClass().getName()+" Perimeter: "+s.getPerimeter()); } public static void main(String[] args){ Shapes s1 = new Rect(5,15,25,25); Shapes s2 = new Triangle(5,5,8); Shapes s3 =new Circle(13,90,25); show(s1); show(s2); show(s3); } }输出结果为:
Rect Area: 625.0 Rect Perimeter: 100.0 Triangle Area: 12.0 Triangle Perimeter: 18.0 Circle Area: 490.8738521234052 Circle Perimeter: 78.53981633974483上述代码中涉及到了父类引用指向子类对象(即代码Shapes s1 = new Rect(5,15,25,25); Shapes s2 = new Triangle(5,5,8); Shapes s3 =new Circle(13,90,25);),这种引用是非常方便的,即所有父类引用可以引用子类对象,所有接口可以引用接口的实现类。
使用接口改写以后的代码如下:
首先在Shapes.java文件中写入以下代码:package myinterface; public interface Shapes { abstract public double getArea(int x, int y, int k, double m); abstract public double getPerimeter(int x, int y, int k, double m); } class Rect implements Shapes { public double getArea(int x, int y, int k, double m) { return (k * m); } public double getPerimeter(int x, int y, int k, double m) { return (2 * k + 2 * m); } } class Triangle implements Shapes { public double getArea(int x, int y, int k, double m) { m = (x + y + k) / 2.0; return (Math.sqrt(m * (m - k) * (m - x) * (m - y))); } public double getPerimeter(int x, int y, int k, double m) { return (k + x + y); } } class Circle implements Shapes { public double getArea(int x, int y, int k, double m) { m = k / 2.0; return (m * m * Math.PI); // Math是java.lang包中的类,PI是静态其属性,其值为Π } public double getPerimeter(int x, int y, int k, double m) { m = k / 2.0; return (2 * Math.PI * m); } } 然后在RunShape.java文件中写入如下代码:package myinterface; public class RunShapeIn{ public static void show(Shapes s, int x, int y, int k, double m){ System.out.println(s.getClass().getName()+" Area: "+s.getArea(x,y,k,m)); System.out.println(s.getClass().getName()+" Perimeter: "+s.getPerimeter(x,y,k,m)); } public static void main(String[] args){ Shapes s1 = new Rect(); Shapes s2 = new Triangle(); Shapes s3 =new Circle(); show(s1, 5,15,25,25); show(s2, 5,5,8,0); show(s3, 13,90,25,0); } }改写以后输出结果为:
myinterface.Rect Area: 625.0 myinterface.Rect Perimeter: 100.0 myinterface.Triangle Area: 12.0 myinterface.Triangle Perimeter: 18.0 myinterface.Circle Area: 490.8738521234052 myinterface.Circle Perimeter: 78.53981633974483可以看到,结果和改写之前是一样的,不同的地方在于类名前面加了包的名字。
先说下instanceof的用法 instanceof是二元运算符,使用格式为A instanceof B,目的是判断对象A是不是类B的实例,A和B的关系不同时一般有以下三种情况:
A是本类对象,B是本类或父类:此时A instanceof B返回布尔值true;A是父类对象,B是子类:此时A instanceof B返回布尔值false;A是对象,B是与A类无关(没有继承关系)的类:A instanceof B不会编译通过,会如下错误:Incompatible conditional operand types A and B使用场景: 当同一个父类(一般是抽象类)被多个子类继承时,需要定义函数来对不同的子类进行不同的操作,此时需要判断该函数所接收的对象是哪个子类的实例,便可以对不同子类的实例进行不同的操作,防止报错,也是多态的体现。