3、面向对象高级
3.1 继承
继承是Java面向对象编程技术的一块基石,因为他允许创建分等级层次的类。继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有相同的行为
PS:Java语言中只有单继承和多重继承(类似与爷爷-父亲-儿子关系)没有多继承(与c\c++\c#等语言不同)。
3.2 super 关键字
- 通过super,可以访问父类的构造方法
- 通过super调用父类的构造方法时,必须写在子类的构造方法第一行
- 通过super,可以访问父类的属性
- 通过super,可以访问父类的方法
3.3 重写与重载的区别
重写:子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
重载: 在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
最常用的地方就是构造器的重载。
重写(Override)规则:
- 参数列表与被重写方法的参数列表必须完全相同。
- 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
- 父类的成员方法只能被它的子类重写。
- 声明为 final 的方法不能被重写。
- 声明为 static 的方法不能被重写,但是能够被再次声明。
- 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
- 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
- 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
- 构造方法不能被重写。
- 如果不能继承一个类,则不能重写该类的方法。
重载(Overload)规则:
- 被重载的方法必须改变参数列表(参数个数或类型不一样);
- 被重载的方法可以改变返回类型;
- 被重载的方法可以改变访问修饰符;
- 被重载的方法可以声明新的或更广的检查异常;
- 方法能够在同一个类中或者在一个子类中被重载。
- 无法以返回值类型作为重载函数的区分标准。
面试题:
Java中重写与重载的区别
-
发生的位置
重载:一个类中
重写:子父类中
-
参数列表限制
重载:必须不一致
重写:必须一致
-
异常
重载:可以修改
重写:可以减少或删除,但不能抛出新的或者更广的异常
-
访问
重载:可以修改
重写:一定不能做更加严格的限制(可以降低限制,提高权限)
-
返回类型
重载:可以修改
重写:一定不能修改
3.4 final 关键字
final关键字用于修饰属性、变量、类、方法。
全局常量(public static final
)
被final修饰后的变量将变为一个常量,无法对其进行操作。
PS:
final修饰的局部变量,只能赋值一次(可以先声明后赋值)
final修饰的成员属性,必须在声明时赋值
final修饰的类,不可以被继承
final修饰的方法,不可以被子类重写
3.5 抽象类
3.5.1 概念
抽象类必须使用abstract class 声明
一个抽象类中可以没有抽象方法,抽象方法必须写在抽象类中或接口中。
格式:
abstract class 类名{}
3.5.2 抽象方法
只声明而未实现(未实现指的是没有{}方法体)的方法被称为抽象方法,抽象方法必须使用abstract关键字声明
格式:
abstract class 类名{
public abstract 返回值类型 方法名();
}
3.5.3 抽象类使用原则:
- 抽象类本身不能直接进行实例化,即:不能使用new关键字。
一个抽象类必须被一个子类继承,被继承的子类要实现(重写)抽象类的所有抽象方法
PS:
- 抽象类不能使用final关键字修饰,因为final关键字使用后不能被子类继承。抽象类如果没有子类继承则毫无意义。
- 抽象类能有构造方法,,与普通类实例化继承是一样的(1、调用父类的构造方法,2、调用子类的构造方法)
3.5.4 抽象类与普通类的区别
- 抽象类必须用public或protected修饰。默认缺省为public
- 抽象类不可以直接被实例化,但是子类继承了抽象类再实例化子类后,JVM会将抽象类实例化(也就是说程序员不能手动实例化抽象类,只能通过JVM来对抽象类实例化)
- 如果一个非抽象子类继承抽象类,那么必须实现所有的抽象方法。如果一个抽象子类继承抽象类,那么可以不实现抽象方法。
3.6 接口
3.6.1概念
如果一个类中的全部方法都是抽象方法,全部属性都是全局常量,那么此时就可以将这个类定义成一个接口。
定义格式:
interface 接口名称{
全局常量 ;
抽象方法 ;
}
3.6.2 面向接口编程思想
这种思想是接口是定义(规范,约束)与实现(名实分离的原则)的分离。
优点:
-
降低程序的耦合性
-
易于程序的扩展
-
有利于程序的维护
3.6.3 全局常量和抽象方法的简写
因为接口本身都是由全局常量和抽象方法组成 , 所以接口中的成员定义可以简写:
1、全局常量编写时, 可以省略public static final 关键字,例如:
public static final String INFO = "内容" ;
简写后:
String INFO = "内容" ;
2、抽象方法编写时, 可以省略 public abstract 关键字, 例如:
public abstract void print() ;
简写后:
void print() ;
3.6.4 implements 关键字
格式:
class 子类 extends 父类 implements 父接口1,父接口2...{
}
3.6.5 接口的继承
接口因为都是抽象部分, 不存在具体的实现, 所以允许多继承
interface C extends A,B{}
PS:如果一个接口要想使用,必须依靠子类。 子类(如果不是抽象类的话)要实现接口中的所有抽象方法。
3.6.6 接口和抽象类的区别
1、抽象类要被子类继承,接口要被类实现。
2、接口只能声明抽象方法,抽象类中可以声明抽象方法,也可以写非抽象方法。
3、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
4、抽象类使用继承来使用, 无法多继承。 接口使用实现来使用, 可以多实现
5、抽象类中可以包含static方法 ,但是接口中不允许(静态方法不能被子类重写,因此接口中不能声明静态方法)
6、接口不能有构造方法,但是抽象类可以有
3.7 多态
3.7.1 概念
是对象的多种表现形式,(多种体现形态)
3.7.2 多态的使用:对象的类型转换
向上转型:将子类实例变为父类实例
格式:
父类 父类对象 = 子类实例 ;
向下转型:将父类实例变为子类实例
格式:
子类 子类对象 = (子类)父类实例 ;PS(向下转型可能导致溢出)
3.7.3 instanceof 关键字
作用:判断某个对象是否是指定类的实例,则可以使用instanceof关键字
格式: 实例化对象 instanceof 指定类 //此操作返回boolean类型的数据
3.8 Object类
3.8.1 概念
Object类是所有类的父类(基类),如果一个类没有明确的继承某一个具体的类,则将默认继承Object类。
3.8.2 toString 方法
建议重写Object中的toString方法。 此方法的作用:返回对象的字符串表示形式。
Object的toString方法, 返回对象的内存地址
3.8.3 equals方法
建议重写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 。
3.9 内部类
3.9.1 概念
在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。
广泛意义上的内部类一般来说包括这四种:
1、成员内部类
2、局部内部类
3、匿名内部类
4、静态内部类
3.9.2 成员内部类
成员内部类是最普通的内部类,它的定义为位于另一个类的内部
class Outer {
private double x = 0;
public Outer(double x) {
this.x = x;
}
class Inner { //内部类
public void say() {
System.out.println("x="+x);
}
}
}
特点: 成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员不过要注意的是,当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:
外部类.this.成员变量
外部类.this.成员方法
外部使用成员内部类:
Outter outter = new Outter();
Outter.Inner inner = outter.new Inner();
3.9.3 局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
class Person{
public Person() {}
}
class Man{
public Man(){}
public People getPerson(){
class Student extends People{ //局部内部类
int age =0;
}
return new Student();
}
}
PS:局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及
static修饰符的。
3.9.4 匿名内部类
匿名内部类由于没有名字,所以它的创建方式有点儿奇怪。创建格式如下:
new 父类构造器(参数列表)|实现接口()
{
//匿名内部类的类体部分
}
在这里我们看到使用匿名内部类我们必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一个接口。同时它也是没有class关键字,这是因为匿名内部类是直接使用new来生成一个对象的引用。当然这个引用是隐式的。
PS:在使用匿名内部类的过程中,我们需要注意如下几点:
1、使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能
继承一个类或者实现一个接口。
2、匿名内部类中是不能定义构造函数的。
3、匿名内部类中不能存在任何的静态成员变量和静态方法。
4、匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。
5、匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
6、只能访问final型的局部变量
3.9.5 静态内部类
静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。
静态内部类是不需要依赖于外部类对象的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法.
public class Test {
public static void main(String[] args) {
Outter.Inner inner = new Outter.Inner();
}
}
class Outter {
public Outter() {}
static class Inner {
public Inner() {}
}
}
3.10 包装类
3.10.1 概述
序列 | 基本数据类型 | 包装类 |
---|---|---|
1 | char | Character |
2 | short | Short |
3 | long | Long |
4 | double | Double |
5 | float | Float |
6 | boolean | Boolean |
7 | byte | Byte |
8 | int | Integer |
以上的八种包装类也是分为两种大的类型的:
- Number:Integer、Short、Long、Double、Float、Byte都是Number的子类表示是一个数字。
- Object:Character、Boolean都是Object的直接子类。
3.10.2 字符串转换
使用包装类还有一个很优秀的地方在于:可以将一个字符串变为指定的基本数据类型,此点一般在接收输入数据上使用较多。
在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
3.11 可变参数
一个方法中定义完了参数,则在调用的时候必须传入与其一一对应的参数,但是在JDK 1.5之后提供了新的功能,可以根据需要自动传入任意个数的参数。
返回值类型 方法名称(数据类型…参数名称){
//参数在方法内部 , 以数组的形式来接收
}
PS:可变参数只能出现在参数列表的最后。
3.12 递归
递归,在数学与计算机科学中,是指在方法的定义中使用方法自身。也就是说,递归算法是一种直接或者间接调用自身方法的算法。
public static void main(String[] args) {
//使用递归获得5!的值
int sum=fact(5);
System.out.println(sum);
}
public static int fact(int n){
if(n==1){
return 1;
}
else {
return n*fact(n-1);
}
}