java 基础&继承(上)
继承: extends
Java继承(Java inheritance)
Java继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。这种技术使得复用以前的代码非常容易,能够大大缩短开发周期,降低开发费用。比如可以先定义一个类叫车,车有以下属性:车体大小,颜色,方向盘,轮胎,而又由车这个类派生出轿车和卡车两个类,为轿车添加一个小后备箱,而为卡车添加一个大货箱。 JAVA不支持多重继承,单继承使JAVA的继承关系很简单,一个类只能有一个父类,易于管理程序,同时一个类可以实现多个接口,从而克服单继承的缺点。 在中运用继承原则,就是在每个由一般类和特殊类形成的一般—特殊结构中,把一般类的对象实例和所有特殊类的对象实例都共同具有的属性和操作一次性地在一般类中进行显式的定义,在特殊类中不再重复地定义一般类中已经定义的东西,但是在语义上,特殊类却自动地、隐含地拥有它的一般类(以及所有更上层的一般类)中定义的属性和操作。特殊类的对象拥有其一般类的全部或部分属性与方法,称作特殊类对一般类的继承。 继承所表达的就是一种对象类之间的相交关系,它使得某类对象可以继承另外一类对象的数据成员和成员方法。若类B继承类A,则属于B的对象便具有类A的全部或部分性质(数据属性)和功能(操作),我们称被继承的类A为基类、父类或超类,而称继承类B为A的或子类。 继承避免了对一般类和特殊类之间共同特征进行的重复描述。同时,通过继承可以清晰地表达每一项共同特征所适应的概念范围——在一般类中定义的属性和操作适应于这个类本身以及它以下的每一层特殊类的全部对象。运用继承原则使得比较简练也比较清晰。
class Person
{
String name;
int age;
}
class Student extends/*继承*/ Person
{
void study()
{
System.out.println(name+”…study run…”+age);
}
}
class Worker extends Person
{
void work()
{
System.out.println(“work run”);
}
}
class ExtendsDemo
{
public static void main(String[] args)
{
Student s = new Student();
s.name = “lisi”;
s.age = 14;
s.study();
Student s1 = new Student();
}
}
1、提高了代码的复用性
2、让类与类之间发生了关系(正是因为这种关系的存在,才让类出现了多态)
java只支持单继承。对于多继承,java保留这种机制,并进行了改良,避免了多继承调用时的不确定性。
java支持多层继承
对于使用一个继承体系:如果java中已经建立一个继承体系,那么当我们想用这个体系中的功能,那么这个时候我们就应该先查阅最顶层类中的共性内容了解该体系的基本功能。然后创建该体系的最子类对象。
什么时候使用继承?当事物之间存在着所属关系(is a)时,就可以使用继承。所属: xxx是yyy中的一种 xxx extends yyy
当子类继承了父类之后,那么子类就拥有了父类的所有功能,但是当父类有10个功能,而子类只具备其中某些功能,那么这个时候子类是不能继承父类的,一定要记住,如果让子类继承了父类,那么子类就会自动继承了父类中不属于自己的功能的方法。坚决不能这么做。如果真的要继承,那么就把这两个类中的共性抽取出来,从新定义一个类,让这两个类来继承抽取出来的这个类。
子父类中的成员变化
1、成员变量
当子父类中出现同名的成员变量时,可以使用super关键字来区分父类的成员变量。super关键字和this关键字的用法很相似。this 代表的是本类一个对象(引用)。super 代表的是父类的所属的那个空间。父类中的私有成员,子类可以继承过来,只是访问权限有限制。就相当于子类中存在父类中这个私有成员变量,但这个成员变量前面有标识,子类没有权限使用,而当通过父类的提供的方法的时候就可以操作父类中私有的成员变量。但是这种情况非常少见。
注意面试的时候会出现子父类中同名的成员变量。注意他们的细节。
2、成员函数:如果成员函数私有,别说子类没有访问权限,就父类自己也无法在本类之外的其他类中访问。
当子父类中出现一模一样的函数时,就会出现调用时运行子类的方法的情况。这种情况,形象的称为覆盖或者重写或者叫做覆写。
什么时候使用覆盖?
为了对phone类中的来电显示功能进行改变。
定义了一个子类,保留了父类中功能的声明,但是重新定义了功能的内容。这就是覆盖的应用。
当对一个类中的功能的内容进行重新定义的时候,可以定义该类的子类,并保留该类中的功能声明重新定义功能内容,并建立子类对象调用该功能。
覆盖的小细节:1)子类的权限必须大于等于父类权限;2)静态只能覆盖静态,或者被静态覆盖
3、 构造函数 :构造函数不可能覆盖的,函数最起码不一样。
子父类中的构造函数的特点:
在运行时,发现父类的构造函数方法先运行了,然后才是子类构造函数。
原因:是子类的所有的构造函数中,都有一句默认的隐式语句super(),会默认调用父类中的空参数的构造函数。
为什么要有super默认语句呢?因为子类对象在初始化的时候,必须要到父类先去初始化。
为什么非要到父类中去初始化呢?那是因为,子类继承了父类,可以获取到父类中的数据。所以在使用父类数据之前,必须要明确父类是如何对自己的成员进行初始化的,如果父类中的没有空参数的构造函数,那么子类的构造函数就必须手动的通过super语句或者this语句指定要访问的父类中的构造函数或者子类的其他构造函数。如果在构造函数出现了this()那么就没有了super(),不管this调用本类那个构造函数,最后那个构造函数也会去父类的构造函数。注意:super语句必须要定义在构造函数的第一行。因为子类继承了父类,子类要初始化,就必须明确父类是如何初始化的。
class Test extends Object
{Test()
{super();
//显示初始化。
//构造代码块
show();
}
{
System.out.println(“test code…”);
}
void show()
{
}
}
class Demo
{
int num = 4;
{
System.out.println(“code…”+num);
}
Demo()
{
super();
//显示初始化。
//构造代码块。
System.out.println(“demo run…”+num);
}
void show()
{
System.out.println(” demo show=”+num);
}
}
class ObjectInitDemo
{
public static void main(String[] args)
{
Demo d = new Demo();
d.show();
}
}
对象的初始化:
1、加载对象所属的字节码文件。
2、通过new操作符在堆内存中开辟空间。明确首地址值。
3、对该对象空间中的属性进行默认初始化
4、调用对应本类构造函数进行初始化。
5、本类构造函数中有隐式super,必须到父类中进行初始化
6、当父类中的内容初始化完毕后,在进行属性的显示初始化
7、在进行构造代码块的初始化
8、在执行本类对应的构造函数中的自定义
当在查阅API文档的时候:(小技巧)
1、当一个类没有构造函数说明的时候,说明这个类不能创造对象。
2、此时说明这个类中的所有内容全部静态的。
3、如果这个类中没有构造函数,还有非静态成员,这个类肯定会有静态方法返回该类对象。
final 关键字
继承的弊端:打破了封装性。
class Fu
{
void method(){ // code 访问底层代码 }
}
class Zi extends Fu
{
void method() { sop(“haha”); } 覆写了父类的method方法 这样做是很危险的,可能导致程序无法与windows进行交互
}
当我们写了一个类之后,不想让别人随随便便就覆写掉,那么这个时候我们可以将这个类声明为最终类,这个时候就需要用final来修饰这个类。
final关键字的特点:
1、final是一个修饰符,可以修饰类,方法,变量(成员变量, 局部变量 )。
2、final修饰的类不可以被继承。
3、final修饰的方法不可以被覆盖。当在一个类中有部分功能不能让覆盖时,就用final修饰。
4、final修饰的局部变量是一个常量,只能被赋值一次。不能再修改。
通常把用final修饰的变量名的所有字母都大写,目的真假阅读性。当变量名由多个单词组成时需要用下划线隔开 PERSON_AGE。为了增加阅读性,只要数据是固定的,就将这个数据用一个名称来表示,并用final来修饰。
5、被final 修饰的成员变量必须手动初始化值。不能默认初始化。
原因是成员变量在堆内存中,他们有默认初始化值,但是我们定义成员变量,一般都不是想使用他们的默认初始化值,所以必须给他赋值。final 修饰的变量是常量值,那么这个时候可以再加上静态来修饰,反正这个值是固定不变的,不管哪个对象使用,值都不会变,没有必要让每个对象都拥有这个值,这个时候可以把这个值静态修饰,让所有对象共享。但是需不需要用public修饰根据具体情况来确定。 在写单例的时候一定要注意,在写饿汉式的时候需要用final修饰 private static final Single s = new Single();