继承也是面向对象的又一重要特性,继承是类于类的一种关系,通俗来说狗属于动物类,那么狗这个类就继承了动物类
java中的继承是单继承的,一个类只能继承与一个父类
子类继承父类之后,子类就拥有了父类的所有属性和方法,private的除外,优点就是可以提高代码的复用性,简单的继承实例如下:
public class Dog extends Animal {
//Dog类继承了Animal类中的所有非私有的属性和方法,可以直接使用了
}
使用继承还是挺简单的
方法重写
如果子类对继承父类的方法不满意,那么可以重写父类继承的方法,当调用方法时会优先调用子类的方法,就比如上面实例,比如Animal有一个吃的方法,但是狗也有自己吃东西的方法,对动物类定义的方法不太满意,那么可以重写动物类的吃这个方法
重写的语法规则是:返回值类型、方法名、参数类型、参数个数及顺序都要与父类中继承的方法一致,才可以实现方法的重写
继承的初始化顺序
继承是有初始化顺序的,当我们实例化一个子类对象时,那么具体类是怎么加载的呢?
因为子类中需要用到父类的属性,所以顺序是先初始化父类,然后再初始化子类;
并且初始化的时候先初始化对象中的属性,然后再执行类中的构造方法,这样的顺序依次来进行初始化;
所以最终初始化顺序应该是:初始化父类属性->执行父类构造方法->初始化子类属性->执行子类构造方法
final关键字的使用
final的字面意思是最后的,最终的,不可修改的,所以final修改修饰一个不能被改变的量
实际上,final关键字可以修饰类、方法、属性、局部变量
1、当final修饰一个类时,该类不允许被继承
此时final修饰类中的所有成员方法也被隐式的设置为final类型,但是成员属性没有做改变;
当用final修饰类时一定要谨慎选择,除非该类以后确定不会被继承,或者确保安全,否则一般不设置为final
写法一般是:public final class 类名{}
2、当final修饰一个方法时,该方法不允许被重写
private修饰的方法会被隐式的设置为final
写法比如:public final float 方法名(参数){}
3、当final修饰属性的时候
默认情况下,成员属性会被系统隐式的赋初值为0,但是当final关键字修饰的时候,系统不会进行初始化赋值,所以我们必须在初始化的时候给变量赋值或者在构造方法中给该属性赋值,两种选择必须只能选其一,否则编译器会报错,一旦属性被初始化赋值该属性的值(或指向的对象)不能被改变,即成员属性变成了一个常量
写法比如:private final int a = 3;
或者:final Object obj = new Object();
4、当final修饰局部变量的时候
final修饰变量,该变量只能赋一次值,而且必须在声明变量的时候赋值,赋值后变为常量
写法比如:final float b = 3.1f;
super关键字的使用
super关键字一般在子类内部使用,可以代表相对应的父类的对象,作用有以下两点:
1、访问父类对象属性
一般子类中如果定义了与父类重名的属性,那么访问时会优先访问子类的属性,可以使用super.父类属性的方式来访问父类中的属性
2、访问父类对象的方法
如果父类对象的方法被子类重写,那么子类对象默认调用的是子类中的重写过的方法,此时如果需要调用父类的该方法可以使用super.方法名()来访问父类中被重写过的方法
另外,我们知道子类在实例化对象时,会先调用父类的构造方法,那么此时就相当于隐式的使用了super关键字,看个简单的例子:
父类Animal:
public class Animal {
//父类构造方法
public Animal(){
System.out.println("Animal构造方法被调用");
}
}
子类Dog:
public class Dog extends Animal {
//子类构造方法
public Dog(){
super(); //编译器自动添加super关键字,调用父类的构造方法
System.out.println("Dog类构造方法被调用");
}
}
这个程序输出结果肯定都知道,是先执行父类的构造方法后执行子类的构造方法,并且编译器自动在子类构造方法第一行也就是例子中Dog类的第4行自动添加了super();这样来调用父类的构造方法,所以才执行了父类的构造方法,如果我们手动添加了super()那么系统就不会再次添加了;
根据以上情况,我们知道一个类中可以定义多个构造方法包括有参的、无参的,如果父类中既定义了有参的构造方法又定义了无参的构造方法,而我们又没有在子类构造方法中显示的指明调用父类中哪个构造方法,那么系统会默认执行父类中无参的构造方法,就是通过super()方法,我们如果显示调用,必须放在子类的构造方法第一行来调用,并且可以在super()中传入参数;
如果我们在父类中只定义了一个有参的构造方法,前面介绍封装性的时候说过此时系统就不会为我们初始化一个无参的构造方法,而我们子类中又没有显示的使用super()来调用我们在父类中定义的,编译器此时隐式调用无参构造方法失败,所以就会报出错误;
Java中的Object类
在java世界中,Object类是所有类的父类,也是所有类的老祖宗,就好比我们生活中的宇宙,如果一个类没有用extends关键字明确表名继承于那个类,那么他就一定直接继承于Object类
Object提供了一些直接可以使用的方法
1、toString()
toString方法是java为了方便输出而定义的一个方法,是为了我们操作字符串而自动调用的一个方法,通常我们不需要干预,在很多方法中toString方法都会被重写;
使用System.out.println();输出时也会自动使用toString方法,如果我们直接输出一个对象,而不是一个字符串,那么我们会看到编译器输出了:
net.zengzhiying.Tel@60eb2ea8
这种情况,很显然@前面是包名,那么@后面我们称为对象的哈希码,哈希码是通过哈希算法生成的一个字符串,标识对象在内存中存放的地址,哈希码可以唯一区分一个对象;
如果我们希望toString方法可以输出对象的属性值,那么我们可以通过重写toString方法实现,eclipse可以很方便的重写toString方法,通过单击工具栏:Source->Generate toString()...可以根据我们的需要快速重写toString方法,toString方法就可以返回字符串了,默认代码如下:
@Override
public String toString() {
return "Animal [age=" + age + "]";
}
其中Animal是类名,age是类中的一个属性,这样就能输出对象中的某个属性了,当然这只是一个例子我们完全可以用对象.属性的方式输出
2、equals()
equals方法的作用是比较对象的引用是否指向同一块内存地址,返回值是布尔值
比如我们前面创建过一个类Dog,我们可以创建一个Dog类的一个对象:Dog dog = new Dog();这样就创建了一个dog对象,那么严格来说,这个dog本身并不是一个对象,而是把dog指向了内存中一个对象的地址,所以dog只是一个对象的引用,我们通过引用来间接的操作各种属性和方法;
所以equals的本质是比较两个对象的引用是否是同一块内存区域
但是我们常用的并不是比较内存区域,我们认为两个对象内的所有属性和方法相同那么两个对象就是我们所认为的"相等",而并不在乎是否指向同一块内存区域,这个时候Object提供的方法不适合我们的需要,现在就需要重写equals方法了
在默认情况下,我们利用Dog类的两个对象,来测试一下:
public class Initail {
public static void main(String[] args){
Dog dog1 = new Dog();
Dog dog2 = new Dog();
//利用默认的equals方法比较两个对象是否相同
if(dog1.equals(dog2)){
System.out.println("两个对象是相同的");
}else{
System.out.println("两个对象是不相同的");
}
}
}
这个结果显然输出:两个对象是不相同的,就算dog1和dog2中所有的属性值完全相同,因为系统为dog1和dog2的引用在内存中开辟了两个不同的内存区域
如果把equals方法换成==来比较会怎么样的,我们知道对于java基本数据类型,==判断的是两个基本属性的值是否相等,而不考虑内存地址,但是当用==判断两个引用数据类型时,比如字符串,他也是比较两个引用指向的内存区域是否是同一个区域,所以用==和上面的输出结果是一样的
所以想实现只比较两个对象引用的值是否相等而不考虑是否指向同一内存地址,那么就要重写equals方法来实现,同样eclipse可以直接快速重写equals方法,单击菜单:Source->Generate hashCode() and equals()...就可以快速重写hashCode生成算法和equals方法,生成之后我们可以删除hashCode方法,只保留equals方法即可,具体方法如下:
public class Dog extends Animal {
public int age = 20 ; @Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Dog other = (Dog) obj;
if (age != other.age)
return false;
return true;
}
}
现在我们Dog对象中只定义了一个属性age,所以重写equals方法相对简单一些,因为调用方法就是dog1.equals(dog2),所以里面的this就是代表了dog1对象,而传入的参数dog2就是方法中的参数,所以把equals写到Dog类中是比较合理的
首先比较this==obj,就是说明如果dog1和dog2指向同一个内存区域,那么显然两个对象是相等的,所以直接返回true;
否则,如果obj为null就是没有指向任何内存地址,那么肯定是不相等的,直接返回false;
否则,通过getClass方法判断类对象是否相等,getClass方法的得到的是某个对象的代码信息,属性类型和分布,通俗来说呢,我们就是比较两个对象里面的属性和方法类型,修饰等信息是否一致,而我们普通使用的对象属于类的对象,我们一般是操作属性值,然后计算得到我们所需要的结果,关注的是属性的数据信息,所以类对象和类的实例化对象要区分清楚
然后,我们对传进来的dog2对象,也就是obj参数对象进行类型转换转换为Dog类型,这样转换是安全的,然后比较属性值是否相同,如果属性值不相同,返回false
如果经过了以上几步的验证,两个对象值肯定是相等的,所以返回true值,这样就实现了equals的重写,达到了比较对象值的目的
以上就是继承的简单运用和对继承稍微深入的理解,如果有新的体会继续再补充