JAVA的object、多态、抽象类、接口的用法

Object

我们上一篇文章说了继承,那么每一个类都是有自己的父类,就算你没有使用继承的关键字,那也是有默认的隐性继承类,它就是超父类Object,那在编写代码的时候为什么不在创建的类后面extends Object呢?
如果没有显式继承自其它非Object类,编译器默认会让这个类继承成Object类。这一点可以通过反编译.class文件确认。
那Object类有哪些属性和方法呢?
那属性真的是一个都没有,全都是方法,今天就简单说一些我们经常用的方法

getClass

getClass()方法用于获取对象的运行时类,在反射的时候用的比较多。
注意到这个方法被final和native修饰,那说明

  • 不能覆写
  • 实现在C/C++层

返回的Class对象就是表示类静态同步方法锁定的对象。

hashCode

源码如下:

public native int hashCode();

hashCod()方法用户获取对象的hash值。 在强烈依赖hashCode的地方是必须的。比如HashMap中对key进行hash计算,其实是利用key的hashCode。
这个方法的实现依然在native层,具体实现暂时不得而知。

equals

源码如下:

public boolean equals(Object obj) {
	return (this == obj);
}

equals方法用于比较两个对象是否相等。
默认的实现就是通过比较引用来判断是不是相等,但是经过重写后比较的就是值,比如String方法里面源码如下:

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

在String中重写了Object中的equals,那么比较的是值,首先第一个if判断的是当前对象(谁调用equals方法就是谁)的地址和后面值(equals(这里的值))的地址是否一样,如果一样,既然地址都一样,那么值就一定相等。
如果不一样就要判断这个对象是不是和后面的对象是同一种类型,通过instanceof这个关键字来判断,比较的是前面的类型是否和后面的类型一样。如果一样就去比值,如果不一样就返回false了。
注意:地址相同,值一定相同;值相同的,地址不一定相同。
注意:重写equals方法之后,一定要重写hashCode!!!
那么什么一定要重写hashCode呢?
首先这个是Object规范中要求的,两个相等的实例必须有相同的hashCode。那我们探究一下根本原因。
以HashMap为例,上面提到对key进行hash,其实是你用了key的hashCode。HashMap中判断两个key是否相同,除了equals还要判断hashCode是否一致。如果没有覆写,本来我们本意认为是同一个key的,现在变成了两个,这个影响可能很小但也可能是灾难性的。

toString

源码如下:

public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

toString方法就是把对象用字符串的形式表示,这个在日志中使用较多,当然也可以是类型转换,比如Integer类。
默认的实现其实没有什么用,“类名@hashCode”,直接打印对象输出就是这样的格式。
官方文档建议最好覆写这个方法,返回一些有价值的、可读性强的信息。
还有几个方法:

  • notify
  • notifyAll
  • wait

都是多线程里面的知识点了,等到讲多线程的时候,在详细讲解。
还有一个方法是:finalize,这个方法是GC准备回收对象的时候调用来执行清理工作的。很遗憾的是,Java语言不能保证finalize及时执行,也不能保证它会执行。如何能保证的话,大家可以去百度一下,这里就不进行过多的讲解了

多态

多态有一个前提,就是两个类之间产生继承关系才能够产生多态,继承在上篇文章就讲解到。
那什么叫做多态呢?
就是一个对象有多种表现形式或形态的能力叫做多态。

多态的优点

  1. 消除类型之间的耦合关系
  2. 可替换性
  3. 可扩充性
  4. 接口性
  5. 灵活性
  6. 简化性

多态存在的三个必要条件

  • 继承
  • 重写
  • 父类引用指向子类对象

比如:

Parent p = new Son();

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。
多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。举例说明一下:

public class Test {
    public static void main(String[] args) {
      show(new Cat());  // 以 Cat 对象调用 show 方法
      show(new Dog());  // 以 Dog 对象调用 show 方法
                
      Animal a = new Cat();  // 向上转型  
      a.eat();               // 调用的是 Cat 的 eat
      Cat c = (Cat)a;        // 向下转型  
      c.work();        // 调用的是 Cat 的 work
  }  
            
    public static void show(Animal a)  {
      a.eat();  
        // 类型判断
        if (a instanceof Cat)  {  // 猫做的事情 
            Cat c = (Cat)a;  
            c.work();  
        } else if (a instanceof Dog) { // 狗做的事情 
            Dog c = (Dog)a;  
            c.work();  
        }  
    }  
}
 
abstract class Animal {  
    abstract void eat();  
}  
  
class Cat extends Animal {  
    public void eat() {  
        System.out.println("吃鱼");  
    }  
    public void work() {  
        System.out.println("抓老鼠");  
    }  
}  
  
class Dog extends Animal {  
    public void eat() {  
        System.out.println("吃骨头");  
    }  
    public void work() {  
        System.out.println("看家");  
    }  
}

注意:当一个父类有两个子类的是,在我们多态状态下一个子类转换成父类的对象时,在通过变量名转换成另外一个子类的时候,编译是通过的,但是运行会报错。ClassCastException强制类型转换的时候产生的异常叫做类型不匹配。代码如下:

Peson p = new Son();
Son1 s = (son1)p;

多态的实现方式

  1. 重写
  2. 接口
  3. 抽象类和抽象方法

抽象类

在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。
父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。
在Java中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。
抽象类的关键字是:abstract

public abstract class Develop {
	abstract void work();
	abstract void sleep();
	void test(){
		System.out.println("哈哈哈,马上可以吃饭了...");
	}
}
/*
 * 具体子类
 */
public class Java extends Develop{

	@Override
	void work() {
		System.out.println("边敲代码边睡觉");
	}
	
	void haha(){
		System.out.println("聊天止于微笑");
	}

	@Override
	void sleep() {
		// TODO Auto-generated method stub
		
	}

}

//抽象子类
abstract class Web extends Develop{

	@Override
	void work() {
		System.out.println("网页开发");
	}

	//abstract void sleep();
	
	void hehe(){
		System.out.println("聊天止于呵呵");
	}
}


//具体子类:
class Son extends Web{

	@Override
	void sleep() {
		System.out.println("上课睡觉可真香!!!");
	}

}

当一个类是抽象类的时候,如果子类继承了抽象类,就要把抽象父类中的抽象方法进行具体实现,如果没有把所有的方法具体实现, 编译时报错的,除非把子类也变成一个抽象类,指导所有的方法都被子类具体实现后才可以。
注意:抽象类是单继承的。
总结:

  • 抽象类可以直接继承抽象类
  • 抽象类也可以直接继承具体类
  • 具体类不能直接继承抽象类,除非把抽象类中的抽象方法都具体实现才可以。
  • 抽象类不能被重写
  • abstract与static,private,final,native不能一起使用,因为一个是静态的只加载一次,一个是私有的不能被访问,一个是最终的不能被重写,一个是原生函数,是C++/C语言实现的,是访问不了的。

接口

接口与类相似点

  • 一个接口可以有多个方法。
  • 接口文件保存在 .java 结尾的文件中,文件名使用接口名。
  • 接口的字节码文件保存在 .class 结尾的文件中。
  • 接口相应的字节码文件必须在与包名称相匹配的目录结构中。

接口与类的区别

  • 接口不能用于实例化对象。
  • 接口没有构造方法。
  • 接口中所有的方法必须是抽象方法。
  • 接口不能包含成员变量,除了 static 和 final 变量。
  • 接口不是被类继承了,而是要被类实现。
  • 接口支持多继承。

接口特性

  • 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
  • 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
  • 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。

抽象类和接口的区别

  • 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
  • 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
  • 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
  • 一个类只能继承一个抽象类,而一个类却可以实现多个接口。

JDK 1.8 以后,接口里可以有静态方法和方法体了。代码如下:

interface InterfaceDemo{
	
	//默认方法
	 public default void test(){
		 System.out.println("default方法");
	 }
	 //静态方法
	 public static void testStatic(){
		 System.out.println("静态方法");
	 }
	 
}

接口的声明

语法如下:

public interface 接口名称{}

接口是多继承的,代码如下:

public interface A{}
public interface B{}
public interface 接口名称 extends A,B{}

接口有以下特性:

  • 接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
  • 接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。
  • 接口中的方法都是公有的。

重写接口中声明的方法时,需要注意以下规则:

  • 类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常。
  • 类在重写方法时要保持一致的方法名,并且应该保持相同或者相兼容的返回值类型。
  • 如果实现接口的类是抽象类,那么就没必要实现该接口的方法。

在实现接口的时候,也要注意一些规则:

  • 一个类可以同时实现多个接口。
  • 一个类只能继承一个类,但是能实现多个接口。
  • 一个接口能继承另一个接口,这和类之间的继承比较相似。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值