Java面向对象高级
抽象类
概念
有时候我们需要这样一个类,它不需要被实例化,也不需要实现完整的方法,它只是用来被继承的,用来限制子类的一个“规范”。这样的类我们就可以将它定义为抽象类,抽象类内的方法可以定义为抽象方法。
1 | 抽象类必须使用abstract class声明 |
注意:
在抽象类的使用中有几个原则:
- 抽象类本身是不能直接进行实例化操作的,即:不能直接使用关键字new完成。
- 一个抽象类必须被子类所继承,被继承的子类(如果不是抽象类)则**必须覆写(重写)**抽象类中的全部抽象方法。
常见的问题:
- 抽象类不能使用final声明,因为final修饰的类是不能有子类的 ,而抽象类必须有子类才有意义。
- 抽象类能有构造方法,而且子类对象实例化的时候的流程与普通类的继承是一样的,都是要先调用父类中的构造方法(默 认是无参的),之后再调用子类自己的构造方法。
抽象类与普通类的区别
- 抽象类必须用public或protected修饰(如果为private修饰,那么子类则无法继承,也就无法实现其抽象方法)。默认缺省为 public
- 抽象类不可以使用new关键字创建对象,但是在子类创建对象时,抽象父类也会被JVM实例化。
- 如果一个子类继承抽象类,那么必须实现其所有的抽象方法。如果有未实现的抽象方法,那么子类也必须定义为abstract类
1 | //Person.java 抽象类 |
1 | 以上代码的运行结果为: |
接口
接口的概念及定义
如果一个类中的全部方法都是抽象方法,全部属性都是全局常量,那么此时就可以将这个类定义成一个接 口。 定义格式:
1 | interface 接口名称{ |
面向接口编程思想
接口是定义(规范,约束)与实现(名实分离的原则)的分离的思想。
优点:
- 降低程序的耦合性
- 易于程序的扩展
- 有利于程序的维护
1 | 因为接口本身都是由全局常量和抽象方法组成,所以接口中的成员定义可以简写: |
接口的实现 implements
接口可以多实现,格式:
1 | class 子类 implements 父接口1,父接口2...{ |
以上的代码称为接口的实现。那么如果一个类即要实现接口,又要继承抽象类的话,则按照以下的格式编写 即可:
1 | class 子类 extends 父类 implements 父接口1,父接口2...{ |
如果一个接口要想使用,必须依靠子类。 子类(如果不是抽象类的话)要实现接口中的所有抽象方法。
1 | //Person.java 接口 |
接口的继承
接口因为都是抽象部分, 不存在具体的实现, 所以允许多继承,例如:
1 | interface C extends A,B{ |
继承相当于扩展了接口的方法
default方法
JDK1.8之后规定,在接口中,可以定义default方法。例如,把Person中的say()方法改为default方法
1 | //Person.java 接口 |
实现类可以不必覆写default方法。default方法的目的是,当我们需要给接口新增一个方法时,会涉及到修改全部子类。如果新增的是default方法,那么子类就不必全部修改,只需要在需要覆写的地方去覆写新增方法。
接口与抽象类的区别
- 抽象类要被子类继承,接口要被类实现。
- 接口只能声明抽象方法,抽象类中可以声明抽象方法,也可以写非抽象方法。
- 接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
- 抽象类使用继承来使用, 无法多继承。 接口使用实现来使用, 可以多实现
- 抽象类中可以包含static方法 ,但是接口中不允许(静态方法不能被子类重写,因此接口中不能声明 静态方法)
- 接口不能有构造方法,但是抽象类可以有
Object 类
概念
Object类是所有类的父类(基类),如果一个类没有明确的继承某一个具体的类,则将默认继承Object类。例如我们定义一个类:
1 | public class Person{ } |
其实它被使用时 是这样的:
1 | public class Person extends Object{ } |
使用Object可以接收任意的引用数据类型
Object类中常用的方法
toString
建议重写Object中的toString方法。 此方法的作用:返回对象的字符串表示形式。
Object的toString方法, 返回对象的内存地址
类Object
的toString
方法返回一个字符串,该字符串由对象为实例的类的名称,字符“ @
”以及对象的哈希码的无符号十六进制表示形式组成。 换句话说,此方法返回一个等于值的字符串:
1 | getClass().getName() + '@' + Integer.toHexString(hashCode()) |
1 | //Person.java |
可以看出,如果不重写toString(),那打印的结果明显不能体现出我们所创建的对象的特征。所以建议在类中去重写toString()方法。
我们可以在Person中重写toString()方法。
1 |
|
equals
先来看一段代码
1 | //Person.java 看上面toString中的Person.java |
我们可以看出p1和p2的内容信息是完全一样的,但我们用 ==
比较时,会返回false。其实这也很好理解,每当我们new一个对象时就会在内存中开辟一块空间,也就是说p1和p2指向的是不同的内存地址,程序当然会判定他们不等。
但我们总需要对对象的信息进行比较,这时我们可以重写Object中的equals()方法来解决。不能直接用,直接用相当于还是在用==
比较。可以看Object中equals方法的源码如下:
1 | public boolean equals(Object obj) { |
建议重写Object中的equals(Object obj)方法,此方法的作用:指示某个其他对象是否“等于”此对象。
Object的equals方法:实现了对象上最具区别的可能等价关系; 也就是说,对于任何非空引用值x和y,当且仅当 x和y引用同一对象( x == y具有值true )时,此方法返回true 。
equals方法重写时的五个特性:
- 自反性 :对于任何非空的参考值x , x.equals(x)应该返回true 。
- 对称性 :对于任何非空引用值x和y,x.equals(y)应该返回true当且仅当y.equals(x)回报true 。
- 传递性 :对于任何非空引用值x ,y和z ,如果x.equals(y)返回true且y.equals(z)返回true ,那么 x.equals(z)应该返回true 。
- 一致性 :对于任何非空引用值x和y ,多次调用x.equals(y)始终返回true或始终返回false ,前提是未修改对象上的equals比较中使用的信息。
- 非空性 :对于任何非空的参考值x , x.equals(null)应该返回false 。
equals一般根据我们的业务进行重写,举个例子,我们可以在Person类中重写equals方法,当唯一标识符id相等时,我们就认为这两个对象相等
1 |
|
以上代码可以进行简化,如下:
1 |
|
IDEA 快捷键
Alt+Inster
可以自动重写equals方法和toString方法
内部类
在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。
广泛意义上的内部类一般来说包括这四种:
- 成员内部类
- 局部内部类
- 匿名内部类
- 静态内部类
###成员内部类
定义:成员内部类是最普通的内部类,它的定义为位于另一个类的内部,形如下面的形式:
1 | class Outer { |
特点:
成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。不过要注意的是,当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:
1 | 外部类.this.成员变量 |
成员内部类的使用:
以上面定义的Outer类为例,来看如何让使用它里面的内部类
1 | public class TestInner { |
###局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限
于方法内或者该作用域内。例如:
1 | class Person{ |
**注意:**局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及
static修饰符的。
下面我们使用系统的某个API,来演示局部内部类的使用。
1 | import java.awt.Frame; |
匿名内部类
匿名内部类由于没有名字,只能使用一次。创建格式如下:
1 | new 父类构造器(参数列表)|实现接口() |
在这里我们看到使用匿名内部类我们必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一个接口。同时它也是没有class关键字,这是因为匿名内部类是直接使用new来生成一个对象的引用。这个引用是隐式的。
在使用匿名内部类的过程中,我们需要注意如下几点:
- 使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能
继承一个类或者实现一个接口。- 匿名内部类中是不能定义构造函数的。
- 匿名内部类中不能存在任何的静态成员变量和静态方法。
- 匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。
- 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
- 和局部内部类一样,只能访问final型的局部变量,因为内部类会被单独编译成一个字节码文件,为了保障这个单独的文件中用到的内部类外部的变量与内部类外部的变量的值绝对一致,系统从规则上限制这个值不可以被更改。
1 | //Person接口 |
静态内部类
静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。
静态内部类是不需要依赖于外部类对象的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非
static成员变量或者方法.
1 | public class Test { |
包装类
在Java中有一个设计的原则“一切皆对象”,那么这样一来Java中的一些基本的数据类型,就完全不符合于这种设计思想,因为Java中的八种基本数据类型并不是引用数据类型,所以Java中为了解决这样的问题,引入了八种基本数据类型的包装类。
基本数据类型 | 包装类 |
---|---|
int | Integer |
char | Character |
float | Float |
double | Double |
boolean | Boolean |
byte | Byte |
short | Short |
long | Long |
以上的八种包装类,可以将基本数据类型按照类的形式进行操作。
以上的八种包装类也分为两种大的类型:
- Number:Integer、Short、Long、Double、Float、Byte都是Number的子类表示是一个
数字。 - Object:Character、Boolean都是Object的直接子类。
装箱和拆箱操作
以下以Integer
和Float
为例进行操作
将一个基本数据类型变为包装类,那么这样的操作称为装箱操作。
将一个包装类变为一个基本数据类型,这样的操作称为拆箱操作,
因为所有的数值型的包装类都是Number的子类,Number的类中定义了如下的操作方法,以下的全部方法都
是进行拆箱的操作。
方法 | 描述 |
---|---|
public byte byteValue() | 用于Byte->byte |
public abstract double doubleValue() | 用于Double->double |
public abstract float floatValue() | 用于Float->float |
public abstract int intValue() | 用于Integer->int |
public abstract long longValue() | 用于Long->long |
public short shortValue() | 用于Short->short |
装箱操作:
在JDK1.4之前 ,如果要想装箱,直接使用各个包装类的构造方法即可,例如:
1 | int temp = 10 ; // 基本数据类型 |
在JDK1.5,Java新增了自动装箱和自动拆箱,而且可以直接通过包装类进行四则运算和自增自减操作。例
如:
1 | Float f = 10.3f ; // 自动装箱 |
字符串转换
使用包装类还有一个很优秀的地方在于:可以将一个字符串变为指定的基本数据类型,此点一般在接收输入
数据上使用较多。
在Integer
类中提供了以下的操作方法:
public static int parseInt(String s)
:将String变为int型数据
在Float
类中提供了以下的操作方法:
public static float parseFloat(String s)
:将String变为Float
在Boolean
类中提供了以下操作方法:
public static boolean parseBoolean(String s)
:将String变为boolean
……
1 | public class demo11 { |