面向对象程序设计(一)
一、基础:
一个Java文件中至多有一个public类,且文件名要和public类名相同。若没有public类,则文件名可以和任一个类名相同。
垃圾回收机制:若一个实例对象没有任何引用指向,那么它所占的内存就会成为垃圾,但JVM一般不会立即回收,会在适合的时机回收垃圾,不保证先申请就先回收,也不保证先成为垃圾就先回收。若要想手动回收可以点用System.gc()方法,这样会调用类的finalize()方法,而系统在程序执行完回收则不会调用。
二、继承:
三大特性之一,一个类最多继承一个直接父类。
示例父类:
class shape { int Area; //面积域 public shape() //无参构造函数 { System.out.println("father construction"); } public int getArea() //求面积 { System.out.println("father area"); return Area; } }
示例子类:
class Square extends shape { int Area; //暂时不必要的 public Square(int a) { this.Area=a; System.out.println("son construction"); } public int getArea() { System.out.println("son area"); return Area*Area; } }
super()和this()方法:super()在子类的构造函数中调用,用于初始化类成员,必须写在构造函数第一句,若不含任何参数,则可以省略不写。值得注意,若父类没有构造函数,会创建一个默认的无参构造函数;若父类只要有构造函数,则默认构造函数不会产生;
隐式调用/super()无参 | super()有参 | |
有无参构造函数/没有构造函数 | 正确 | 错误 |
有有参构造函数 | 错误 | 正确 |
都有 | 正确 | 正确 |
而this()方法也是在构造函数中出现,必须写在第一行,所以与显式调用super()无法同时使用,用于调用其他重载构造方法。
例如子类新增加一个无参构造函数:
public Square() { this(0); System.out.println("son construction0"); }
注意构造函数绝对不能递归调用!因为会报错Recursive constructor invocation。
public static void main(String[] args) { Square d=new Square(); System.out.println(d.getArea());
Square d=new Square(3);
System.out.println(d.getArea());
}
测试结果输出:
father construction //先调用父类无参构造函数。没有写属于隐式调用
son construction //接着调用子类无参构造函数
son construction0 //无参构造函数调用子类有参构造函数
son area //子类方法
0
father construction
son construction
son area
9 //没有调用无参,因为没有this语句
上转型隐式调用:将子类对象复制给父类对象时成为上转型(upcasting),例如子类Square继承父类Shape,可以将父类的引用指向子类的对象,例如:Shape sh=new Square();
下转型显式调用:接上面语句,Square sq=(Square)sh;称为下转型(dwoncasting),但不能直接使用这句,因为只有sh在指向子类对象时才能下转,否则编译不会出错,但运行会出错。
instanceof:判断对象是否是某类的实例,对于没有转型来说,子类是所有超类及自身的实例,返回true,例如Square sq=new Square();则sq instanceof Square,sq instanceof Shape乃至sq instanceof Object都返回true;而父类不会是子类的实例,例如Shape sh=new Shape();sh instanceof Shape 返回true,但sh instanceof Square返回false;
对于上转型来说,父类便是子类的实例,例如 Shape sh=new Square();那么sh instanceof Square返回true;或者 Square sq=new Square(); Shape sh=sq;则sh instanceof Square,sh instanceof Shape,sq instanceof Square,sq instanceof Shape全都返回true;
对于下转型来说,由于父类肯定是上转型,所以不再讨论,子类与没有转型来说是相同的。
三、多态:
面向对象三大特征之一,分为静态多态,动态多态(即C++中的静态联编和动态联编)
静态多态:即重载(overload),方法的名称相同但参数不同,例如各个构造函数。注意参数类型相同但参数名不同不算重载,仍是相同的函数。
动态多态:即覆盖(override),指父类和子类方法参数类型和方法名完全相同,例如上面的getArae()父类和子类都有,但JVM会识别出是哪一个调用。
先对子类父类分别构造对象,调用同名成员方法:
Square d=new Square(2); shape sh=new shape(); System.out.println(d.getArea()); System.out.println(sh3.getArea());
输出:
father construction
son construction //子类日常调用父类构造方法和自己的
father construction //父类不调用子类的构造
son area
4
father area
0 //并没有给父类参数赋值,int型自觉变0
再来上转型和下转型:
shape sh2=new Square(5); //upcasting Square d3=(Square)sh2; //dwoncasting System.out.println(sh2.getArea()); System.out.println(d3.getArea());
结果:
father construction
son construction //不想再重复一遍了
son area
25
son area
25
由以上结果可以结论,对于同名成员方法,子类对象则调用子类方法,父类对象调用父类方法,上转型的父类调用子类方法,下转型的子类也调用子类方法。
super和this关键字:注意和前面super(),this()方法不相同,super可以使用在子类中非静态成员方法中用于访问父类型的成员,this用法与super相同,都是super.a,this.b,super.A(),this.B()形式,调用super主要原因是本类有和父类同名的成员域或在非静态成员方法中有同名的局部变量。但是要this,主要用于区分局部变量和成员域(同名时),例如在很多set方法中都会有this.a=a类似的语句。
调整子类构造方法如下:
public Square(int a) { super.Area=a+super.Area; this.Area=a+super.Area; //子类的Area终于派上用场 System.out.println("son construction"); }
例如我们执行shape sh2=new Square(5);上转型语句会执行完父类的构造方法后执行子类构造函数,所以在执行上述语句时,该子类对象的父类的成员域被初始化为0,之后变为5(不影响其他实例对象,也即成员域是实例对象私有的),子类成员域变为10,语句System.out.println(sh2.getArea());会调用子类方法(在上面说过)输出结果100.
然后我们再来看看调用成员方法的情况,在子类方法中进行修改:
public int getArea() { System.out.println(super.getArea()); //新增一句,输出结果为5 System.out.println("son area"); return Area*Area; }
输出结果为5的原因是构造函数修改了父类的值,且会输出“father area”表示执行了父类的方法。
此外静态方法不具有动态多态性,且当父类有某静态方法时,子类不能拥有同名方法;父类拥有某方法,子类不能拥有同名静态方法;当两者均为静态方法且同名时编译可通过。但同名静态域则不会有这种烦恼,以上三种情况均可。
什么类型对象和类就会调用相应的静态方法,上转型调用父类不会被覆盖,下转型调用子类方法或域(父调父,子调子)。
2018.4.24补:发现一个新的多态性质:
父类和子类具有同名成员域和同名成员方法时,使用上转型申请父类对象时,对象不能访问子类特有的成员域或成员方法,但期间会执行子类构造函数;
对象访问同名成员域时会调用父类的,但访问同名成员方法时则会调用子类重写方法。例如Shape类和triangle类具有父子关系,且具有同名成员域A和同名成员方法getArea();
2018.04.27再补:
子类覆盖父类函数时,上转型调用子类方法时且子类成员方法有调用同名成员域时,会调用子类例成员域的值,而引用直接访问同同名成员域时则会调用父类成员域。
class shape { int A; public shape() { A=88; } public int getarea() { System.out.println("father"); return A; } } class triangle extends shape2 { int a; int b; int A; public triangle(int a,int b) { System.out.println("son construct"); this.a=a; this.b=b; this.A=a; } public int getarea() { System.out.println("son"); return a*b/2; } public void f() {} } public static void main(String []args) { shape2 ab=new triangle(10,5); //上转型声明 triangle rt=new triangle(10,5); //正常声明子类 System.out.println(ab.getarea()); //子类方法 ;25 System.out.println(ab.A); // 父类成员域 ;88 // ab.a; ab.f(); //错误,父类对象和上转型对象无法调用子类特有成员 System.out.println(rt.getarea()); //子类方法 ;25 System.out.println(rt.A); //子类成员域;10 rt.f(); }
至于下转型和直接实例化子类对象一样(是否完全一样有待深入学习),这也是为什么下转需要建立在上转的基础上,不在赘述。
四、包:
包关键字package,必须是文件的第一句;
导入包语句有三种分别是:
import wym.*;将整个wym包里的接口、类、枚举都导入。不会导入子包的类型。
import wym.Eat;将wym包中的Eat类型(接口、类、枚举其中之一)导入。
import static wym.Eat.main;将main静态方法导入。
第一种会增加程序开销,降低编译效率,尽量使用后两种。
五、封装:
三大特性之一
类的访问控制方式:pulic和默认(即什么修饰词也不加),public的类可以被所有包使用(不同包需要用import导入,在同一包中可见),而默认类只能被包内使用(在同一包中可见)。
类成员访问控制方式:四种类型public,protected,默认,private。
对于public的类成员,所有能够访问该类的类型定义体都能访问访问该成员。
对于protected的类成员,若所在类是默认模式,则其他包的成员不能访问,若所在类是public,比default多了其他包的子类方法能够访问。
对于默认的类成员,同一软件包能够访问,但是不同包一定不能访问。
对于private的类成员,只有本类成员方法才能访问。
所在类为public时的权限:
访问控制模式 | 在同一类内 | 在同一包内 | 子类 | 所有类 |
公共 | 允许 | 允许 | 允许 | 允许 |
保护 | 允许 | 允许 | 允许 | |
默认 | 允许 | 允许 | ||
私有 | 允许 |
所在类为默认时的权限:
访问控制模式 | 在同一类内 | 在同一包内 | 子类 | 所有类 |
公共 | 允许 | 允许 | ||
保护 | 允许 | 允许 | ||
默认 | 允许 | 允许 | ||
私有 | 允许 |