类的继承
继承在面向对象中的一个重要思想,它可以使整个程序框架具有一定的弹性。在程序中复用一些已经定义完善的类不仅可以减少软件开发周期,也可以提高软件的可维护性和可扩展性
继承,其基本思想是基于父类进行扩展,得到一个新的子类。子类可以继承父类的属性和方法。
创建一个Test类,同时创建一个继承Test类的Test2子类,其中包括重写父类的成员方法和新增的成员方法
public class Test {
public Test(){
//构造方法
}
public void dosome(){
//成员方法
}
protected Test doIt(){
return new Test();
}
}
public class Test2 extends Test{
public Test2(){ //构造方法
super(); //调用父类构造方法
super.dosome(); //调用父类成员方法
}
public void dosomenew(){
//新增成员方法
}
public void dosome(){
//重写父类方法
}
protected Test2 doIt(){ //重写父类方法
return new Test2();
}
}
上述Test2类的构造方法中使用super关键字调用父类的构造方法和成员方法
Test2类继承Test类。在子类中可以连同初始化父类构造方法来完成子类初始化操作,既可以在子类的构造方法中使用super()语句调用父类的构造方法,也可以在子类中使用super关键字调用父类的成员方法等。但是子类没有权限调用父类中被修饰为private的成员方法,只可以调用public或protected的方法。
重写: 继承不只是扩展父类的功能,还可以重写父类的成员方法。重写(还可以称为覆盖)就是在子类中将父类的成员方法名字保留,重写成员方法的实现内容,更改成员方法的储存权限,或是修改成员方法的返回值类型。继承中还有一种特殊的重写方法,子类与父类的成员方法返回值,方法名称,类型及个数完全相同,唯一不同的是方法内容,这种特殊方法称为重构。
当重写父类方法时,修改方法的修饰权限只能从小范围到大范围改变,例如:父类中的dosome()方法的修饰权限为protected,继承后子类中的方法dosome()只能修改为public ,不能修改为private
子类重写的方法还可以修改方法的返回值类型。(只是在J2SE 5.0以上版本中支持) 例如:父类的doIt()方法的返回值类型为Test,而子类中的doIt()方法返回值类型为Test2。重写的返回值类型必须是父类中同一方法返回值类型的子类
class Test {
Test(){
System.out.println("调用父类Test的构造方法");
}
}
class Test2 extends Test{
Test2(){ //构造方法
System.out.println("调用子类Test2的构造方法");
}
}
public class Test3 extends Test2 {
public Test3(){
System.out.println("调用子类Test3的构造方法");
}
public static void main(String[] args) {
Test3 t=new Test3();
}
}
输出结果:
调用父类Test的构造方法
调用子类Test2的构造方法
调用子类Test3的构造方法
可以看出,我们并没有在子类Test3的构造方法中调用任何父类的构造方法,但是在实例化Test3的时候,却调用了父类的构造方法并且调用构造方法的顺序是,先是顶级父类,然后是上一级父类,最后是子类。也就是说实例化子类对象时,要先实例化父类对象,然后再实例化子类对象。所以在子类构造方法访问父类的构造方法之前,父类已完成实例化操作。
在实例化子类的对象时,父类无参构造方法将被自动调用。有参构造方法不呢个被自动调用,用户只能使用super关键字显示地调用父类的构造方法。
如果使用finalize()方法对对象进行清理,需要确保子类finalize()方法的最后一个动作是调用父类的finalize()方法。以保证当垃圾回收对象占用内存时,对象的所有部分都能被正常终止。
Object类
Object类是一种特殊的类,它是所有类的父类,是java类层中最高层的类。用户创建一个类时除非已经指定要从其他类继承,否则它就是从Object类继承过来的。java中的每个类都源于Object类,如String,Integer类都是继承与Object类。除此以外,自定义的类也都继承与Object类。由于所有子类都是Object的子类,所以不需要加extends Object
在Object类中包括clone(),finalize(),equals(),toString()方法。由于所有类都是Object类的子类,所以任何类都可以重写Object类中的方法(一些方法除外)。
Object类中的getClass(),notify(),notifyAll(),wait()等方法不能被重写,因为他们被定义为final类型
(1).getClass()方法
getClass()方法是Object类定义的方法,它会返回对象执行时的Class实例,然后使用此实例调用getName()方法可以取得类的名称
getClass().getName();
可以将getClass()与toString()方法联合使用
(2).toString()方法
toString()方法是将一个对象返回为字符串形式,它会返回一个String实例。实际应用中,通常重写toString方法,为对象提供一个特定的输出模式。当这个类转换为字符串或与字符串连接时,会自动调用重写的toString方法
public class Demo2 {
public String toString(){
return getClass().getName()+"类中的重写toString方法";
}
public static void main(String[] args) {
Demo2 d=new Demo2();
System.out.println(d);
System.out.println(d.toString()); //打印本类对象
}
}
输出结果:
com.code.Demo2类中的重写toString方法
com.code.Demo2类中的重写toString方法
(3).equals()方法
"==“与equals()方法的区别,”=="比较的是两个对象的引用是否相同,而equals()方法比较的是两个对象的实际内容。
class Test {
}
class Test2{
public static void main(String[] args) {
String s1="123";
String s2="123";
System.out.println(s1.equals(s2));
Test t1=new Test();
Test t2=new Test();
System.out.println(t1.equals(t2));
}
}
输出结果:
true
false
可以看出,在自定义类中使用equals()方法进行比较时,返回false,这是因为equals()方法的默认实现是使用"=="运算符比较两个对象引用地址,而不是比较对象的内容,所以要想真正做到比较两个对象内容的比较,需要在自定义类中重写equals()方法。String类返回的是true是因为String类重写了equals()方法
三·对象类型的转换
(1).向上转型
比如平行四边形是特殊的四边形,平行四边形是四边形的一种,那么就可以将平行四边形对象看作是一个四边形对象。
public class Quadrangle { //四边形类
public static void draw(Quadrangle q){
System.out.println("这是父类");
}
}
public class Parallelogram extends Quadrangle { //平行四边形类
public static void main(String[] args) {
Parallelogram p=new Parallelogram();
draw(p); //draw方法是static,所以它的子类可以直接调用
}
}
输出结果:
这是父类
平行四边形类继承了四边形的类,四边形类存在一个draw()方法,它的参数是Quadrangle类型,但是在子类中调用时却给予的参数类型却是Parallelogram类。Parallelogram是Quadrangle的一个子类,所以,Parallelogram对象可以看作是Quadrangle的一个对象。相当于Quadrangle q=new Parallelogram();就是把子类对象赋值给父类类型的变量,这称为向上转型
比如其他四边形类型的对象(四边形类的子类,比如你定义正方形类,梯形类都继承与四边形类)可以作为四边形类的draw()的参数,如果在draw()方法中根据不同的对象设置不同的处理,就可以做到在父类中定义一个方法完成各个子类的功能,这样可以使同一份代码毫无差别地运用到不同类型之上,这就是多态机制的基本思想。
由于向上转型是从一个较具体的类到抽象的类的转换,所以它总是安全的。比如,我们可以说平行四边形是特殊的四边形,但不能说四边形是平行四边形。
(2).向下转型
向下转型是将抽象的类转换为较具体的类。这样的转型通常会出现问题,比如不能说四边形是平行四边形的一种,所有鸟都是大雁。子类对象总是父类的一个实例,但父类对象不一定是子类的一个实例。
public class Quadrangle {
public static void draw(Quadrangle q){
System.out.println("这是父类");
}
}
public class Parallelogram extends Quadrangle {
public static void main(String[] args) {
draw(new Parallelogram()); //向上转型
Quadrangle q=new Parallelogram(); //向上转型
//Parallelogram p=q; 父类对象赋值给子类对象会报错
Parallelogram p=(Parallelogram)q; //父类对象赋值给子类对象,并强制转换为子类对象是对的
}
}
将父类对象强制转换为某个子类对象,称为显式类型转换
在程序中使用向下转型时,必须使用显示类型转换,指明要将父类对象转换为哪一种类型的子类对象
四·instanceof操作符判断对象类型
当在程序中执行向下转型操作时,如果父类对象不是子类对象的实例,就会发生ClassCastException异常,所以在执行向下转型时,可以使用instanceof操作符判断父类对象是否为子类对象的实例。可以使用instanceof操作符判断是否一个类实现了某个接口,也可以判断一个实例对象是否属于一个类
instanceof语法
某类对象的引用 instanceof 某个类
使用instanceof操作符的表达式返回值为布尔值。如果返回值为true,说明某个类的对象是某一个类的实例对象。如果返回值为false,说明某个类对象不是某个类的实例
public class Father {
}
public class son extends Father {
}
class Test {
}
public class son2 extends Father {
public static void main(String[] args) {
Father f=new Father(); //实力化一个父类对象
if(f instanceof son){ //判断父类是不是son子类的一个实例
son s=(son)f; //强制转换为son类
}
if(f instanceof son2) { //判断父类是不是son子类的一个实例
son2 s = (son2) f; //强制转换为son2类
}
//System.out.println(f instanceof Test); 因为f不是Test类的对象,所以这句会编译报错
}
}
五·方法的重载
构造方法的名称由类名决定,所以构造方法只有一个名称。如果需要以不同方式来实例化对象,就需要使用多个构造方法来完成。这些构造方法都需要根据类名进行命名,为了使方法名相同而使形参不同的构造方法同时存在,必须用到方法重载。
方法的重载就是在同一个类中允许存在一个以上同名的方法,只要这些方法的参数个数不同或类型不同或参数的顺序不同(需要参数类型不同然后顺序不同)。
只有返回值不同不足以区分两个方法的重载
参数个数不同时重载,可以定义不定长参数方法
public static int add(int...a){
int s=0;
for(int i=0;i<a.length;i++)
s+=a[i];
return s;
}
在参数列表中使用"…"形式定义不定参数,其实这个不定参数a就是一个数组,编译器会将(int…a)这种形式看作是(int[] a)
六·多态
利用多态可以使程序具有良好的扩展性,并可以对所有类对象进行通用的处理。
例如,在向上转型中子类对象可以作为父类对象的实例使用。如果需要在一个子类中定义一个方法,然后另一个子类定义一个方法,但是这两个方法实现的功能是相同的。在调用时就会出现冗余,需要分别定义着两个子类。但是我们可以利用向上转型,定义一个父类,在这个父类上定义这个方法。这样可以解决代码冗余,同时也易于维护
public class Square extends Quadrangle{
public Square(){
System.out.println("正方形类");
}
}
public class Parallelogram extends Quadrangle {
public Parallelogram(){
System.out.println("平行四边形");
}
}
public class Quadrangle {
private Quadrangle[] qList=new Quadrangle[6];
private int nextindex=0;
public void draw(Quadrangle q){
if(nextindex < qList.length){
qList[nextindex]=q;
System.out.println(nextindex);
nextindex++;
}
}
public static void main(String[] args) {
Quadrangle q=new Quadrangle();
q.draw(new Square());
q.draw(new Parallelogram());
}
}
输出结果:
正方形类
0
平行四边形
1
以不同的类对象调用draw()方法,可以处理不同的图形问题。使用多态节省了开发和维护时间。只需要实例化一个继承父类的子类对象,就可调用。
七·抽象类和接口
(1)抽象类
在类与对象中说过抽象类,一般父类定义为抽象类,需要使用这个父类进行继承和多态处理。越是在上方的类越抽象。在多态机制中,并不需要将父类初始化对象,我们需要的是子类对象,所以在java中设置抽象类不可以实例化对象。
public abstract class Test(){ /定义抽象类
abstract void method(); //定义抽象方法
}
abstract关键字定义的类称为抽象类,而使用这个关键字定义的方法称为抽象方法,抽象方法没有方法体,这个方法本身没有任何意义,除非它被重写,而承载这个抽象方法的抽象类必须被继承,抽象类除了被继承之外没有任何意义。
声明一个抽象的方法,就必须将承载这个抽象方法的类定义为抽象类,不可能在非抽象类中获取抽象方法。只要类中有一个抽象方法,此类就被标记为抽象类。
抽象类被继承后需要实现其中所以的抽象方法,也就是保证相同的方法名称,参数列表和相同返回值类型创建非抽象方法,也可以是抽象方法。
继承抽象类的所有子类需要将抽象类中的抽象方法进行覆盖。但这样就会出现冗余代码,同时还会出现一个问题,比如某一个子类不需要这个方法但它又不得不去重写这个抽象方法。如果将这个抽象方法单独拿出来放在一个类中,让那些需要这个方法的子类继承这个类,不需要这个方法的类继承本来的父类,但这又会出现一个问题,需要那个抽象方法的子类又不继承本来的父类了。所有的子类都要继承父类,但只有一些子类需要抽象方法,而java中又不允许继承多个类,所以这就需要接口
(2)接口
接口是抽象类的延伸,可以将它看作是存粹的抽象类,接口中的所有方法没有方法体,可以将一个方法封装到一个接口中,需要这个方法的类实现这个接口。同时不管需不需要的这个方法的类都可以去继承父类。
public interface text{ //interface : 接口关键字
void dosome(); //接口内的方法,省略abstarct关键字
}
一个类实现一个接口可使用implements关键字
public class Son extends Father implements text{
//......
}
在接口中,方法必须被定义为public或abstract形式,即使不将该方法定义为public形式,它也是public
在接口中定义的任何字段都自动是static和final的
public interface drawInterface {
public void draw();
}
public class Square extends Quadrangle implements drawInterface{
public void draw(){
System.out.println("正方形类.draw()");
}
}
public class Parallelogram extends Quadrangle implements drawInterface{
public void draw(){
System.out.println("平行四边形.draw()");
}
}
public class Quadrangle {
public static void main(String[] args) {
drawInterface[] d={new Square(),new Parallelogram()}; //接口也可以实现向上转型操作 对象数组
for(int i=0;i<d.length;i++){
d[i].draw();
}
}
}
输出结果:
正方形类.draw()
平行四边形.draw()
(3)接口与继承
一个类可以实现多个接口,使用逗号隔开,但这可能会在一个类中产生庞大的代码量,因为实现一个接口时需要实现接口中的所有方法
class 类名 implements 接口1,接口2......接口n
在定义一个接口时也可以继承另一个接口
interface inf1{
}
interface inf2 extends intf1{
}