Java—抽象类与接口

本文对Java中抽象类与接口相关知识做解析与总结。

抽象类概念:如果一个类中没有包含足够的信息用来描述一个具体的对象,这样的类就是抽象类。

1.使用abstract修饰的方法叫做抽象方法(抽象方法没有方法体),abstract修饰的类叫做抽象类。

2.包含抽象方法的类必须用abstract修饰为抽象类。

3.抽象类不能进行实例化。

4.抽象类中可以有与普通类一样的普通方法,普通成员。抽象类最为特殊的地方在于抽象类不能实例化。

5.虽然抽象类不能实例化,但是能被继承(抽象类存在的最大意义就是被继承)。

6.当一个普通类继承抽象类后,这个普通类必须重写抽象父类中所有的抽象方法,否则不能通过编译。

7.如果抽象类A继承抽象类B,则A可以重写也可以不重写B中的抽象方法。

8.抽象方法不能用private,static,final等修饰符修饰。

拓展:抽象类可以作为父类参与多态的应用。

抽象类的构造方法的作用为供子类实例化对象时初始化父类的成员变量。

抽象类可以有静态代码块,实例代码块与构造方法。

接口概念:接口就是公共的行为规范标准,在实现时只要符合规范标准就可以使用,接口可以看成多个类的公共规范,是一种引用数据类型。

接口修饰符—interface

1.使用interface修饰接口

2.接口中可以有成员变量,成员变量默认被public static final修饰。

3.接口中的成员方法默认为抽象方法,有public abstract修饰

4.接口中的普通成员方法不能有具体实现,若非要有具体实现,则普通方法需要用default修饰。

5.接口中可以有静态的成员方法,静态方法与default普通方法都默认public修饰。

6.接口不能实例化,只能被实现。

7.类与接口通过implements关联。 类绑定接口格式:class 类名 implements 接口名{}

8.一个接口可以引用具体实现类(子类)的对象,相当于向上转型,所以接口也可以作为父类参与多态应用。

定义接口: public interface 接口名{}

创建接口时接口名一般以大写字母I开头

9.接口中不能有静态代码块,实例代码块与构造方法。

接口虽然不是类,但接口经编译环节后生成的字节码文件后缀格式也是.class

拓展:接口可以被public与abstract修饰,但是在同一文件中若已有被public修饰的类或接口,那么该接口就不应该使用public。

10.如果类没有实现接口中所有的抽象方法,则该类必须设置为抽象类。

接口应用举例:父类Animal(是抽象类)有两个子类Dog与Bird,此时我想写一个Run方法来表示各个动物跑动的情况,这个Run不应该写在父类Animal中,因为不是每个动物都会Run,我也不能单独给Run写成一个类,因为Java不支持多继承,所以只能将Run写成接口,再使用合适的动物类如猫狗类实现这个接口。

类不支持多继承,但是支持多实现。

格式:class 类名 implement 接口1名,接口2名{}

接口多态举例:

 这两段代码写在public类中,IRunning为接口,而Dog,Bird,Duck为实现了这个接口的类,可以看出,形参虽然为接口的“对象”,但依旧可以接收实现类传过来的对象,并可以在方法中通过子类调用实现过的抽象方法,这里发生了向上转型,即父类引用引用了子类对象,也是多态的表现。

定义类的时候若继承实现都有,则先写继承再写实现。

接口之间允许多继承(extends),用来扩展功能。比如我定义了接口A,B(A,B中各自有一个抽象方法),再使用接口C(有单独的抽象方法)继承了接口A,B则类T在实现接口C时需要实现三个方法(即A,B,C三个接口中所有的抽象方法)。

自定义类型的比较(接口使用举例):

在Java中,自定义类型对象的比较不能直接使用比较大小操作符比较,直接比较的含义也不是比较地址。自定义类型做比较时需要程序员自行编写比较程序,而不是直接使用操作符。

自定义类型需要比较大小时需要指定比较的成员,并且需要告诉编译器比较的方式。

比较做法1:使用对象的cmopareTo方法比较。

让比较大小的两个对象的类实现一个接口Comparable,接口Comparable需要在其实现类中实现Comparable中的compareTo抽象方法。

 Comparable接口后包括一个泛型参数,此参数一般与当前类(即比较对象的类)相同。

 因为需要比较的是两个本类对象,所以形参类型就是本类的类型,而compareTo的返回类型为int,直接使用本类对象this与形参对象o,若当前对象成员大于参数对象成员返回1,等于返回0,小于返回-1,其实返回值不一定取-1,0,1,只要在main中的值对应处理合理即可。

下图为在main中的调用格式:

 使用Arrays.sort()对数组进行排序时,若数组类型为引用数据类型(即自定义类型),由于类型中有很多成员变量,因此无法确定排序方式,程序报错。

我创建了student类型的数组,若有student实现Comparable与实现抽象方法compareTo的代码,则编译器在使用sort方法时自动调用student类中的compareTo方法根据返回值完成从小到大或者从大到小的排序,注意:Arrays.sort()方法在运行时底层会将参数数组中所有元素强转为comparable接口的对象,然后使用对象调用compareTo方法从小往后比较并根据返回值排序。

比较做法2:创建比较类,该类实现了比较器接口Comparator,使用比较类对象方法进行比较。

虽然Comparator接口中有两个抽象方法,但是其实现类只需要实现int compare(T o1, T o2); 其中T代表泛型类型

 

从上图中AgeCmoparator类实现比较器接口方法声明行可知为泛型指定类型确认类型的语句,即将接口中方法的T确定为Student类型。由于比较器接口一般用于比较目标类的两个对象,所以泛型中的类型为目标类的类型。

在使用时实例化一个当前类对象,然后调用当前类的compare对象方法,传入参数为两个需要比较的对象,临时变量承接返回值根据返回值做对应处理。

需要使用哪种比较方式就定义哪种比较类(承接比较器的类),并调用该类方法对传入的多个对象进行比较。

Arrays.sort()的另一种用法:

使用Arrays.sort(students,ageComparator); 就能将students数组进行年龄排序

Arrays.sort方法中传入的是需要排序的对象数组,编译器自动调用后面参数类对象的compare方法。

需要怎么排序,就在Arrays.sort中第二个参数传入什么比较类的对象即可。

Students类中还有一个String的成员变量name,而String类型的变量是不能直接比较的。

写法:

 从底层代码中得知String也实现了Comparable接口,自然有对应的compareTo方法,此时需要在姓名比较类中实现接口compare方法并在compare方法中调用String类的compareTo方法得出返回值。

Clonable接口与深拷贝

Cloneable为一个空接口/标记接口(没有抽象方法),实现了该接口的类说明该类能被克隆。但是如果需要真正克隆需要实现一个克隆方法clone(),该克隆方法为Ojbect类的,而Object类为所有类的父类所以需要重写克隆方法。

为什么需要重写clone:因为clone()方法被protected修饰,而Object与当前类不在同一个包, 经protected修饰的方法不能在不同包的子类中直接使用,而是要在子类方法使用super.clone() 使用。

但如果在普通方法中使用,虽然可以运行,但是会产生不支持异常,保险起见还是在子类中重写clone() 比较好。

重写格式:

重写后使用该类对象就能调用clone()方法,但克隆的返回值类型为父类Object,所以需要将返回的对象强制类型转换为当前目标对象再进行赋值。

原理:现有一个对象,调用clone()在堆区中生成另一块空间,此空间内容与原克隆对象内容相同,此空间地址作为返回值(类型为父类),在强转之后可被其他变量承接。

深拷贝:两个同类对象指向不同的空间,这两块空间虽然内容相同但互不相干。

使用clone()时,当需要拷贝的类的对象中有一个实例成员,这个实例成员是另一个类的对象,那么经过拷贝,目标对象中也有这个成员,并且这两个对象中的两个成员(另一个类的对象)指向堆区中同一块空间,使用拷贝前或拷贝后对象任意一个访问该实例成员的值都会影响共同空间的数据。不算深拷贝。

正确做法:类实现克隆接口,并重写父类Object的克隆方法。具体思路:在需要克隆的大类的类重写clone()时先创建临时变量承接父类克隆并强转之后的该类对象地址值(新的对象空间),再将该临时变量中是其他类的类型的成员克隆一份并赋给自己的对应成员(此处实现了内成员再克隆一份的工作,是深拷贝的核心),最后返回该临时变量。注意:其他类也应该实现Cloneable接口并重写父类的克隆方法。

类的其他自定义类成员:

 类重写clone()的写法:

 这样一来使用Person.clone()克隆出来的地址指向的空间中即使有其他自定义类的成员,这个成员与克隆样本的其他类成员也不指向同一块空间。完成了深拷贝。

总结:

对于自定义类型,有些方法需要重写,如:比较,克隆,equals,hashcode等等

深拷贝与浅拷贝不是哪个方法来决定的,是自己代码的实现决定的。

Object类:

Object类是java默认提供的一个类,java里面除了Object类,所有的类都是操作继承关系的即默认继承Object父类,所有类的对象都可以使用Object类的引用接收(向上转型,多态使用)。

Object的equals方法:

public boolean equals(Object obj){

return (this == obj);//比较的是传入对象与当前对象的地址值。

}

一般来说自定义类型都需要重写equals方法便于比较对象,通过equals方法实现对当前对象与传入对象的某成员的比较。

自定义的compareTo比较大小(int),equals比较相同或不同(boolean)。

注意:String类中也重写了equals方法。

Object的hashCode方法:hashCode()是根据转换规则来计算对象的在堆区地址,可以用任意对象使用该方法得出数据如int add = dog.hashCode();

hashCode();的底层实现是c/c++代码,我们只能知道该方法的返回值为int(一个地址值)。

我们可以在子类中重写hashCode方法,使其根据不同的数据来生成数字地址.如:

 id为当前类的成员变量(前提),此处调用Objects.hash(); 它是另一工具类Objects的方法,根据参数产生地址值。

自定义的类尽量重写hashcode方法,可使用此方法对不同对象进行比较。

END。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值