继承
定义: 子类从它的父类中继承 可访问 的数据域和方法,还可以添加新的数据域和方法,即对父类进行扩展。
关键字: extends
class SuperA{
..........
}
class A1 extends SuperA{
..........
}
class A2 extends SuperA{
..........
}
- java不允许多继承(eg:A extends SuperA1 && A extends SuperA2)
定义中说到 可访问的 ,那么子类到底继承了父类的哪些属性和方法呢?
答案是所有的,除了 一部分没有权限的(例如,子类没有继承父类的构造方法,私有属性和私有方法)
访问修饰符 :
public------整个工程 protected-------同包下与缺省一样,不同包的子类也可以 缺省------整个包 private------整个类中
static关键字常见用法: (唯一性)
1.static类 :
static只能用来修饰内部类
class Outer{
static class Inner{
public Inner(){
System.out.println("Inner");
}
}
}
public class Test{
public static void main(String[] args){
Outer.Inner inner = new Outer.Inner();
}
}
- 对于内部类,只有用static声明的内部类中才可以定义静态成员变量和静态成员方法
class Outer{
/*static*/ class Inner{
public Inner(){
System.out.println("Inner");
}
public static void test(){
System.out.println("静态方法");
}
}
}
public class Test{
public static void main(String[] args){
Outer.Inner inner = new Outer.Inner();
Outer.Inner.test();
}
}
===================================================================
报错!!!
2.static变量 :
只可以修饰类成员变量,static修饰的类成员变量叫做类变量或静态变量,被该类的所有对象共享,并且只在类初次加载时进行初始化,在内存中只有一个副本;没有static修饰符的类成员变量叫做实例变量,实例变量在创建对象时进行初始化,在内存中存在多个副本。
3.static方法:
调用方式:类名.方法名();
- 静态方法中不可以使用super和this关键字,因为静态方法是独立于所有实例而存在的,而super和this关键字相当于指向某一实例的“指针”
- 静态方法中不能使用非静态成员变量,也不能调用非静态成员方法,原因同上
- 静态方法不能为抽象方法(不能用abstract修饰)
4.静态代码块:
静态代码块只在类第一次实例化时执行,而非静态代码块在每次实例化时都执行
class A{
static{
System.out.println("静态代码块");
}
{
System.out.println("非静态代码块");
}
public A(){
System.out.println("构造方法");
}
}
public class Test{
public static void main(String[] args){
new A();
new A();
}
}
==================================================================
执行结果:
静态代码块
非静态代码块
构造方法
非静态代码块
构造方法
5.静态导入:
静态导入可以直接导入方法
import static java.lang.Math.abs;
public class Test{
public static void main(String[] args){
System.out.println(abs(-10));
}
}
final关键字常用方法: (不可变性)
final有不可改变之意,可用来修饰非抽象类、非抽象成员变量和方法
1.final类 :
final类不能被继承,所以其中的成员变量和方法默认也是为final的。一般来说,只有在确信该类不需要再有子类,并且不会被扩展。所以一般只有在定义一个保存一些常量信息的时候才会将类声明为final类
2.final变量:
final修饰的变量将无法再被改变,即为常量。final变量在定义时可以不赋值,此时意味着该变量为null;在使用这个变量之前则必须为其赋值,否则会出错,赋值之后这个值将不会再被改变。
3.final方法:
final修饰的方法不能被覆盖,所以方法被声明为final之后,该类的子类不能重写覆盖此方法,但是可以被继承。
final和static的结合使用:(既有唯一性又有不可变性)
可以同时使用static和final修饰成员变量和方法,此时的成员变量和方法可以直接用“类.名称”调用,成员变量的值不能改变,方法不能被重写。
调用方式:
class A{
public final static int a = 0;
public final static void test(){
System.out.println("被final和static同时修饰的方法" );
}
}
public class Test{
public static void main(String[] args){
System.out.println(A.a);
A.test();
}
}
=============================================================
运行结果:
0
被final和static同时修饰的方法
多态:
使用父类对象的地方都可以使用子类对象
Object o = new String();
!!!!!注意这里的对象o不能调用String类独有的方法!!!!!
即在这种情况下,不能调用子类独有的方法
那么如果调用对象o的toString方法,究竟是按Object类的toString执行还是按String类的 toString执行呢?
public class Test{
public static void main(String[] args){
Object o = new String("余娜瑞");
System.out.println(o.toString());
}
}
=================================================================
执行结果
余娜瑞
结论:子类对象优先调用重写之后的方法
动态绑定:
声明类型:一个变量被声明为某种类型,这种类型就称为变量的声明类型
实际类型:变量的实际类型是指被变量引用的对象的实际类
class SSA{
public void test(){
System.out.println("A的父类的父类");
}
}
class SA extends SSA{
@Override
public void test(){
System.out.println("A的父类");
}
}
class A extends SA{
@Override
public void test(){
System.out.println("A类");
}
}
public class Test{
public static void main(String[] args){
SSA x = new SA();
x.test();
}
}
====================================================================
执行结果
A的父类
====================================================================
将SA类中的test方法注释
class SSA{
public void test(){
System.out.println("A的父类的父类");
}
}
class SA extends SSA{
/*@Override
public void test(){
System.out.println("A的父类");
}*/
}
class A extends SA{
@Override
public void test(){
System.out.println("A类");
}
}
public class Test{
public static void main(String[] args){
SSA x = new SA();
x.test();
}
}
====================================================================
执行结果
A的父类的父类
结论:动态绑定是指若对象的实际类型中重写了某方法,则执行实际类型中的该方法,否则沿着继承链向上寻找父类中的该方法,直到 第一次 找到为止。(实质上就是从对象的实际类型开始沿继承链寻找某个方法最终被重写覆盖的状态)
SSA x = new SA(); 只要SA是SSA的子类便可以通过编译,但是在执行的过程中是将对象的实际类型中的方法列表加载入内存的,若执行x.test(),便会在SA独有的方法和SA继承父类的方法中寻找test方法,又由于重写会覆盖父类方法,所以沿继承链向上寻找时,第一次找到的方法便是重写覆盖后的最终形态,所以第一次找到后便停止寻找
对象转换和instanceof运算符
public class Test{
public static void main(String[] args){
Object o = new String(); (隐式的对象转换,由于String类的实例也是Object类的实例,所以是合法的)
String s = o;(不合法,编译器无法识别)
}
}
========================================================================
编译错误
java: 不兼容的类型: java.lang.Object无法转换为java.lang.String
改正
需要通过对象的显式转换来告诉编译器o是一个String对象
!!!!String s = (String)o;!!!! √
========================================================================
结论:子类对象向父类对象转换时总是正确的(向上转换),但是父类对象向子类对象转换时(向下转换)需要通过显式转换对编译器表明意图。显示转换的过程中一定要确保被转换的对象时子类的一个实例。
========================================================================
范式:
public class Test{
public static void main(String[] args){
Object o = new String("余娜瑞");
if(o instanceof String){
System.out.println(((String)o).toString());
}
}
}
注意:对象成员访问运算符(.)优先于类型转换运算符,使用圆括号保证在访问成员方法之前成功转换
构造方法在继承中的情况
构造方法在继承的过程中不被继承,但是在使用子类的构造方法进行实例化时,子类的构造方法会在所有的语句执行之前隐式地调用父类的无参构造方法,这个过程持续到沿着这个继承链的最后一个无参构造方法被调用为止。
注意虽然子类构造方法隐式地调用了父类构造方法,但是并没有对父类进行实例化
class SA{
public SA(){
System.out.println("父类无参构造方法");
}
}
class A extends SA{
public A(){
//super();
System.out.println("子类构造方法");
}
}
public class Test {
public static void main(String[] args) {
new A();
}
}
====================================================================
执行结果
父类无参构造方法
子类构造方法
因此如果想对某个类进行扩展,最好提供一个无参构造方法,否则编译器报错
显式调用父类构造方法:
super(); || super(parameters);
这两个语句必须出现在子类构造方法的第一行,这是显式调用父类构造方法的唯一途径