7. 抽象类和接口的比较
1、什么是抽象类? 就是对类更高的抽象。抽象类作为多个子类的共同父类。它所体现的是一种模版设计,抽象类作为多个子类的父类,可以把它理解为系统实现过程中的中间产品,这个中间产品已经实现了系统的部分功能,但是不能当成最终产品,还需要进一步的完善。
当父类的一些方法不能确定时,可以用abstract关键字来修饰该方法【抽象方法】,用abstract来修饰该类【抽象类】。
1)、抽象类不能被实例化。
2)、用abstract关键字来修饰一个方法时,这个方法就是抽象方法,抽象方法不能有主体【即不能实现】;用abstract关键字来修饰一个类时,这个类就叫抽象类。
3)、抽象类不一定要包含abstract方法。也就是说抽象类可以没有abstract方法;但是一旦类包含了abstract方法,则这个类必须声明为abstract。
2、什么是接口? 接口是一种规范,就像现实中生产主板和内存条或者网卡的不是同一家产商,但是为何内存或者网卡插入到主板上就能用呢,因为他们都遵守了某种规范。然后就可以使用。虽然他们的内部实现可能完全不同。就好比在java语言中的方法内部实现你不需要关心,只需要知道这个接口是怎样的干嘛的就行了,直接用。既然是一种规范,那他在编程语言中就能在架构中起到非常之大的作用,在一个应用程序之间的时候,接口体现的是一耦合标准。特别是在多个应用程序需要对接的时候。接口是多个应用程序的通信标准。接口就是给出一些没有内容的方法,封装在一起,到某个类要使用的时候,再根据具体情况把这些方法写出来。
接口是更加抽象的抽象的类,抽象类里的方法(非抽象方法,抽象类可以有非抽象方法)可以有方法体,接口里的所有方法都没有方法体。接口体现了程序设计的多态和高内聚低偶和的设计思想。
1)、接口中的所有方法都不能有主体,不能被实例化。
2)、一个类可以实现多个接口。
3)、接口中可以有变量【但变量不能用private和protected修饰】。a、Java接口中的变量是公共的(public),静态的(static),最终的常量(final),相当于全局常量,所以定义接口变量时必须初始化。 b、接口中的变量,本质上都是static的,不管你加不加static修饰。c、在java开发中,我们经常用的变量,定义在接口中,作为全局变量使用。访问形式:接口名.变量名。
4)、一个接口不能继承其它的类,但是可以继承别的接口。
5)、接口没有方法就可以作为一个标志,比如可序列化的接口Serializable,没有方法的接口称为空接口。
7.1. 抽象类与接口的比较
接口和抽象类都不能实例化,他们都位于继承树的顶端,用于被其他类实现和继承。接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。
他们的区别:
1、属性:接口没有普通属性,只有静态属性,并且只能用public final static 修饰(并且是默认的,就算你在接口中定义Int i = 0 它也会被隐式的加上public final static);而抽象类可以有普通属性,可以有静态属性(类属性)。
2、方法:接口中的方法都没有方法体并且都是默认使用public abstracrt 修饰,不能定义静态方法。 而抽象类可以有普通方法,也可以没有抽象方法,也可以定义静态方法。
3、构造函数:接口中没有构造器,抽象类中可以有构造器, 但是它不能用于new 对象 而是用于子类调用来初始化抽象类的操作。
4、初始化块:接口中不能包含初始化块,而抽象方法中可以包含初始化块。
5、一个类只能有一个直接父类,包括抽象类,而类可以实现多个接口,弥补了java不能多继承的不足。
7.2.新版本中的修改
1、JDK1.8以前,抽象类中的方法默认访问权限protected,JDK1.8时默认访问权限default。
2、JDK1.8接口,增加了default和static方法,这2个都可以有方法体的,default方法属于实例,static方法属于类(接口),接口的静态方法不会被继承,静态变量会被继承。
3、JDK1.8,如果接口只有一个抽象方法自动变成函数式接口,可以用使用@FunctionInterface注解,接口有FunctionInterface注解只能有一个抽象方法。
4、从java8开始接口里可以有静态方式,用static修饰,但是接口里的静态方法的修饰符只能是public,且默认是public。调用时(接口名.方法名)。
5、java8里,除了可以在接口里写静态方法,还可以写非静态方法,但是必须用default修饰,且只能是public,默认也是public。
8. 构造方法,构造方法重载,什么是复制构造方法?
构造方法是类的一种特殊方法,它的主要作用是完成对新对象的初始化。
1、方法名和类名相同,没有返回值。
2、在创建(new)一个类的新对象时,系统会自动的调用该类的构造方法完成对新对象的初始化。
3、普通方法可以和类名相同,和构造方法唯一的区别在于,构造方法没有返回值(注意是没有不是void)。
4、定义类的时候,若没有自定义构造方法,则会有一个默认的无参构造方法,若自定义了构造方法,则没有无参构造方法。
5、构造方法不能被继承,构造方法只能被显式或者隐式地调用。
6、子类的构造方法总是先调用父类的构造方法,如果子类的构造方法没有显式地指出使用父类的哪个构造方法,子类则默认调用父类的无参构造方法(此时若父类自定义了构造方法,而子类又没有用super则会报错)。
7、Java不支持像C++中那样的复制构造方法(没有这个概念),但是在Object类中有一个clone()方法。
Protected Object clone() throws CloneNotSupportedException:创建并返回此对象的一个副本。“副本”的准确含义可能依赖于对象的类。
这样做的目的是,对于任何对象 x,表达式:x.clone() != x为true,表达式:x.clone().getClass() == x.getClass()也为true,但这些并非必须要满足的要求。一般情况下:x.clone().equals(x)为true,但这并非必须要满足的要求。
首先,使用这个方法的类必须实现java.lang.Cloneable接口,否则会抛出CloneNotSupportedException异常。Cloneable接口中不包含任何方法,所以实现它时只要在类声明中加上implements语句即可。
第二个比较特殊的地方在于这个方法是protected修饰的,覆写clone()方法的时候需要写成public,才能让类外部的代码调用。
按照惯例,返回的对象应该通过调用super.clone获得。如果一个类及其所有的超类(Object除外)都遵守此约定,则 x.clone().getClass() == x.getClass()。
按照惯例,此方法返回的对象应该独立于该对象(正被复制的对象)。要获得此独立性,在super.clone返回对象之前,有必要对该对象的一个或多个字段进行修改。这通常意味着要复制包含正在被复制对象的内部“深层结构”的所有可变对象,并使用对副本的引用替换对这些对象的引用。如果一个类只包含基本字段或对不变对象的引用,那么通常不需要修改 super.clone 返回的对象中的字段。
也就是clone的浅拷贝和深拷贝问题,在具体使用的时候需要特别注意:
浅拷贝:如果一个对象内部还有一个引用类型的基本变量,那么在拷贝该对象的时候,只是在通过clone方法新产生的新对象中拷贝一个该基本类型的引用。换句话说,也就是新对象和原对象他们内部都含有一个指向同一对象的引用。
深拷贝:拷贝对象的时候,如果对象内部含有一个引用类型的变量,那么就会再将该引用类型的变量指向的对象复制一份,然后引用该新对象。
8、实际上,特别需要注意的一点是,Java对象并不是由构造器创建的,而是由new运算符创建的,在程序运行时,是new运算符在堆上开辟一块空间,然后执行对象的初始化(其中包括调用构造器),当对象创建成功,也是new运算符将对象的起始地址返回给应用程序的(并非构造器)。实际上构造器的作用是在对象创建的时候进行类中成员变量的初始化,而绝非创建对象,构造器也没有返回值。因此程序执行的顺序是,先创建对象,然后求解构造器所有形参表达式的值(若形参表达式的计算出现异常,则不会调用构造器方法),最后调用构造器对对象进行初始化。
9. Java内部类
内部类(inner class)是定义在另一个类内部的类。
使用内部类的原因有三个:
1)、内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据。
2)、内部类能够隐藏起来,不被同一个包中的其他的类所见。
3)、想要定义一个回调函数,且不想编写大量代码时,使用匿名内部类比较便捷。
4)、内部类有四种:成员内部类、局部内部类、静态内部类、匿名内部类
9.1、成员内部类
成员内部类也是最普通的内部类,它是外围类的一个成员,所以他是可以无限制的访问外围类的所有成员属性和方法,尽管是private的,但是外围类要访问内部类的成员属性和方法则需要通过内部类实例来访问。如果在内部类中定义有和外部类同名的实例变量,访问:OuterClass.this.outerMember;
在成员内部类中要注意两点,第一:成员内部类中不能存在任何static的变量和方法(因为需要先创建外部类,才能创建自己,可以声明static的常量);第二:成员内部类是依附于外围类的,所以只有先创建了外围类才能够创建内部类。
9.2、局部内部类
局部内部类,它是嵌套在方法和作用域内的,对于这个类的使用主要是应用与解决比较复杂的问题,想创建一个类来辅助我们的解决方案,但又不希望这个类是公共可用的,所以就产生了局部内部类,局部内部类和成员内部类一样被编译,只是它的作用域发生了改变,它只能在该方法和属性中被使用,出了该方法和属性就会失效。
局部内部类可以访问的外部类的成员根据所在方法体不同。如果在静态方法中:可以访问外部类中所有静态成员,包含私有;如果在实例方法中:可以访问外部类中所有的成员,包含私有。局部内部类可以访问所在方法中定义的局部变量,但是要求局部变量必须使用final修饰。
9.3、匿名内部类
1、匿名内部类是没有访问修饰符的,也是唯一一种没有构造方法的类。
2、new 匿名内部类,这个类首先是要存在的。
3、注意当所在方法的形参需要被匿名内部类使用,那么这个形参就必须为final。
9.4、静态内部类(静态嵌套类)
使用static修饰的内部类我们称之为静态内部类,或者称之为嵌套内部类。静态内部类与非静态内部类之间存在一个最大的区别,我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:
1、它的创建是不需要依赖于外围类的。
2、它不能使用任何外围类的非static成员变量和方法。
10. 枚举类
(1)、定义一个枚举类,使用的是enum关键字而不是class。
(2)、枚举类的实例一定是有限多个(可枚举的),所有的enum变量必须定义在枚举类的第一行,用逗号隔开,定义完所有的变量后,以分号结束,如果只有枚举变量,而没有自定义变量,分号可以省略。枚举变量最好大写,在其他类中使用enum变量的时候,只需要【类名.变量名】就可以了,和使用静态变量一样。
Enum变量默认添加public static final修饰。
(3)enum类可以把它看成一个普通类,可以有构造器,成员方法,成员变量。当然和普通类也有一定的区别:
枚举类的构造方法默认使用private修饰,且只能使用private修饰;
枚举类默认继承自Enum类,所以不能继承其他类,Enum类实现了Serializable、Comparable接口;
枚举类默认使用final修饰,因此不能派生子类。如果需要扩展enum中的元素,在一个接口的内部,创建实现该接口的枚举,以此将元素进行分组。达到将枚举元素进行分组的目的。
(4)、switch()参数可以使用enum
(5)、enum允许程序员为eunm实例编写方法。所以可以为每个enum实例赋予各自不同的行为。
(6)、常用方法
10.1、枚举类方法:
valueOf()方法:它的作用是传来一个字符串,然后将它转变为对应的枚举变量。前提是你传的字符串和定义枚举变量的字符串一模一样,区分大小写。如果你传了一个不存在的字符串,那么会抛出异常。
values()方法:这个方法会返回包括所有枚举变量的数组,可以方便的用来做循环。
name()方法:它和toString()方法的返回值一样,这两个方法的默认实现是一样的,唯一的区别是,你可以重写toString方法。name变量就是枚举变量的字符串形式。
10.2、枚举变量方法:
toString()方法:该方法直接返回枚举定义枚举变量的字符串。
ordinal()方法:默认情况下,枚举类会给所有的枚举变量一个默认的次序,该次序从0开始,类似于数组的下标。而.ordinal()方法就是获取这个次序(或者说下标)。枚举类中枚举变量的次序可以自定义。
compareTo()方法(枚举类实现了Comparable接口):该方法用来比较两个枚举变量的”大小”,实际上比较的是两个枚举变量的次序,返回两个次序相减后的结果,如果为负数,就证明变量1”小于”变量2 (变量1.compareTo(变量2),返回【变量1.ordinal()- 变量2.ordinal()】)。