为了账号安全,请及时绑定邮箱和手机立即绑定

Java中容易被你忽略的细节(二)

标签:
Java

14.Java方法重载:方法名相同,方法的参数类型,个数,顺序不同。
不能够用来区分重载方法的元素有:
a.形式参数的名称;b.方法的返回类型;c.方法的异常列表;d.参数化类型。
重载方法可以根据参数列表对应的类型与参数的个数来区分。
两个方法不能通过参数化类型的不同而构成重载。如:
void m(List list){}
void m(List<String> list){}
void m(List<Number> list){}
对于引用类型来说,重载方法的行为与基本数据类型是相同的,那就是会挑选实参类型
可赋值给形参类型,并且对形参类型最明确的方法进行调用。
关于究竟选择哪个重载方法来调用,是根据引用的静态类型(编译时类型)。
也就是在编译期间,就已经确定了要调用的方法,而不是等到运行的时候,根据
引用所指向对象的具体类型来决定调用哪个方法,这点与方法重写不同。
方法重载不同于方法重写。调用哪个重载方法是根据实参的静态类型(编译时类型)决
定的,与运行时实参的具体类型无关。

15.当子类继承父类,子类就继承了父类的public与protected成员,如果子类与父类在
同一个包中,也会继承父类中包访问权限的成员。如果一个父类中的方法在子类中重新
定义并且符合一定的条件时,我们就说子类重写了父类的方法。利用方法重写,就可以
在运行时实现多态。如果子类重写了父类的方法,则在运行时就会调用子类的方法。
方法的签名就是指方法的名称与方法的参数类型。如果两个方法具有相同的名称与参数
类型,则两个方法具有相同的签名。如:
void m(int x,float y)
void m(int a,float b)
可以在子类中使用非泛型化的方法重写父类中泛型化的方法,但反过来是不允许的,即
不能在子类中使用泛型化的方法重写父类中的非泛型化的方法。

访问权限由低到高为:private,包访问权限(无访问修饰符),protected,public.
子类方法的访问权限不允许低于父类方法的访问权限。
在重写方法时,要求子类方法的访问权限不允许低于父类方法的访问权限。
子类方法的异常列表(只考虑受检异常)必须是父类方法异常列表的子集或者与父类相同。
子类重写父类方法的前提是继承了该方法,也就是父类的方法在子类中是可访问的,
如果父类的方法在子类中不可访问,子类也就没有继承父类的方法,则即使满足上述的
所有条件,也并没有重写父类的方法,只能算是声明了一个新的方法而已。
通过对象引用来调用实例方法时,在运行时会根据引用所指向的实际类型来决定调用
哪个方法。如果子类重写了父类的方法,则调用子类的方法,否则调用父类的方法。
从JDK1.5起,引入了@Override注解,使用该注解可以用来判断子类的方法是否重写
了父类的方法。从JDK1.6起,@Override注解也可以用来判断一个类是否实现了接口
中的抽象方法(类实现了接口)。如果标注的方法没有重写父类的方法,或没有实现类
所实现的接口中的抽象方法,就会产生编译错误。

方法重写不同于方法重载,方法重载是根据实参的静态类型来决定调用哪个方法,而
方法重写是根据运行时引用所指向对象的实际类型来决定调用哪个方法。
在方法是静态还是实例方面,方法重写要求父类与子类的方法都是实例方法,如果其中
有一个方法是静态方法,则会产生编译错误,如果两个方法都是静态方法,没有编译
错误,但这种情况是方法隐藏,不是方法重写。
在方法签名方面,方法重写要求子类方法签名是父类方法签名的子签名。
在方法的返回类型方面,方法重写要求子类方法返回类型是父类方法返回类型的可替换类型。
在方法的继承方面,方法重写要求子类继承了父类的方法,即父类的方法在子类中必须是
可访问的。如果子类没有继承父类的方法,则父类的方法在子类中不可访问,自然也就不
可能重写父类的方法。
方法重写要求父类与子类的方法是实例方法。
方法隐藏要求父类与子类的方法是静态方法。

16.在Java中,静态方法不能重写,只可以隐藏。
成员变量也不能重写,只可以隐藏。相对于方法的隐藏,成员变量的隐藏只要求父类与
子类的成员变量名称相同,并且父类的成员变量在子类中可见即可。与成员变量的访问
权限,类型,实例变量还是静态变量无关。
重写与隐藏的本质区别是:重写是动态绑定的,根据运行时引用所指向对象的实际类型
来决定调用相关类的成员。而隐藏是静态绑定的,根据编译时引用的静态类型来决定
调用相关类的成员。换句话说,如果子类重写了父类的方法,当父类的引用指向子类
对象时,通过父类的引用调用的是子类的方法。如果子类隐藏了父类的方法(成员变量),
通过父类的引用调用的任然是父类的方法(成员变量).

17.构造器,也称构造方法,用来初始化类的实例成员变量,在使用new关键字对象的
时候,由系统自动调用。构造器必须与类名相同,并且没有返回值,在外观上与类中
声明的方法相似,也可以具备形式参数,类型变量,异常列表等。然而,构造器不是
方法,也不是类的成员。
构造器不是方法。方法既可以在其声明的类中调用,也可以在类外调用,而构造器通常
在使用new关键字创建对象的时候,由系统自动调用,如果显示调用构造器,则必须在
同类或子类的另一个构造器中,使用this或super关键字调用,方法则是类或对象的引用
(静态方法或实例方法),通过方法名称调用。
如果显示地使用this或super调用构造器,则this或super语句必须是构造器中的第1条语句
,否则就会产生编译错误。可知,this或super只能有一个出现在同一个构造器中,两者
不可共存。
构造器也不是类的成员。类的成员可以由子类继承(如果访问权限满足的话),但是构造器
不能由子类继承,即使构造器声明为public也是如此。构造器就是在创建对象的时候,
用来初始化实例成员变量的,子类可以有自己的成员变量,也有自己的初始化方法,继承
父类的构造器没有意义。
构造器不会由子类继承,但是在子类的构造器中,如果第1条语句没有使用this来调用另外
一个构造器,则会使用super();语句隐式调用父类无参的构造器,此时如果父类中没有
无参数的构造器,就会发生编译错误。
在Java语言中,如果构造器(或方法)的参数列表不为空,则在调用之前一定要先计算实际
参数的值,然后才能调用构造器。调用构造器时,需要将实际参数赋值给形式参数
构造器是在对象创建之后才调用的。
当创建对象失败时,还没有对构造器的实际参数表达式求值,因此也就没有调用构造器。
从顺序上来看,是先创建对象,然后才调用构造器的。因此并非构造器创建了对象。
对象是new运算符创建的。在程序运行时,是new运算符在堆上开辟一定的空间,然后
执行对象的初始化(其中包括调用构造器),当对象创建成功时,也是new运算符将对象的
起始地址返回给应用程序(并非是构造器返回的)。
构造器用于在对象创建时进行类中实例成员的初始化,而非是创建对象,构造器也没有返
回值。对象是先创建,然后才调用构造方法进行初始化的。
如果我们在类中没有显示地声明构造器,则编译器会自动生成一个默认的构造器,该
构造器的参数列表为空,访问权限与类的访问权限相同。可以使用javap反编译类或使用
javadoc生成文档,来查看默认生成的构造器。
如果一个构造器内没有使用this来调用同类中其他的构造器,则构造器的第1条语句就会
使用super来调用父类的构造器,即使我们没有显示地使用super调用,编译器也会为我
们补上这条语句。构造器递归的调用父类构造器,直到Object类为止。
如果类中没有声明构造器,编译器就会生成一个默认的构造器,该构造器并不是什么
也不做,而是会首先调用父类的构造器,相当于语句:super();
如果构造器声明为public,则在任何位置都是可见的,在任何位置都可以创建该类的对象
如果声明为private,则可以在本包内的任何类中创建该类的对象。关键是声明为
protected的构造器,protected与包访问权限相比,就是除了在包内可见外,对包外的
子类也是可见的。
能否有权限调用父类的构造器,就在于父类构造器的访问权限,如果访问权限不足,就会
产生编译错误。
构造器中使用this来访问由局部变量所遮蔽的成员变量。this也可以在实例方法中使用,
用来指代当前的对象,只是我们一般会省略this,而是直接通过实例变量的名称去访问。
一个类可以创建无数个对象,并且每个对象都会在堆上分配独立的空间,每个对象也
都有自己的实例变量。在构造器或者实例方法中,都含有一个隐藏的参数,这个参数
就是类的对象,当调用构造器或者实例方法时,就会将这个参数作为第1个参数传递
过去。
对于静态方法,在方法调用的时候是没有隐式参数(当前对象this)传递的,因为静态
方法与对象无关,是与类相关联的,所以,在静态方法中没有this.

构造器是递归调用的,子类的构造器会调用父类的构造器,直到调用到Object类
的构造器为止。
protected构造器与包访问权限构造器是不同的,前者可以在子类的构造器中使用super
来调用,而后者不能。

18.成员变量,也称域或字段,就是在类的内部声明,表示类具备的,区别在于其他类的
某种特征(属性)。成员变量可以分为静态成员变量和实例成员变量。成员变量在创建时,
系统会为其分配一个默认值。不同类型的变量,默认值也不相同。
byte,short,char,int,long类型的实例变量与静态成员变量的默认值都为0
boolean类类型的实例变量与静态成员变量的默认值都为false
float,double类型的实例变量与静态成员变量的默认值都为0.0
String,string[]类型的实例变量与静态成员变量的默认值都为null.
数组类型与引用类型的默认值相同,都为null.
相同类型的实例变量与静态变量默认值是相同的。
相对于成员变量,局部变量没有默认值(不管是什么类型),如果试图使用一个局部变量
的值,而这个局部变量尚未初始化,就会产生编译错误。
对于数组而言,如果使用new在堆上分配了空间,则数组就会获得默认值,即使数组
变量为局部变量也是如此。
初始化实例变量的三种方法:
1.在声明处初始化;2.在实例初始化块中初始化;3.在构造器中初始化
初始化静态变量的方法:1.在声明处初始化;2.在静态初始化块中初始化
如果子类继承了父类的某些成员变量,则子类就可以访问这些变量,就像这些变量是在
子类中声明一样。
局部变量不管是什么类型的,都无默认值,在使用局部变量的值时一定要先对局部变量
进行初始化。
当子类继承父类的实例变量x,如果子类没有隐藏变量x,则对于同一个对象,只存在一个
变量x,即通过this.x与super.x访问的是同一个变量。如果子类隐藏变量x,则通过this.x
或super.x访问的将不再是同一个变量。
当子类继承父类的静态变量x,如果子类没有隐藏变量x,则x由父类以及所有子类所共享,
无论是通过类名(父类或子类)还是对象名(父类对象或子类对象)访问的x,都是同一个变量
,如果子类隐藏变量x,则通过父类(父类名或父类对象)访问的x与通过子类(子类名或
子类对象)访问的x将不再是同一个变量。

19.静态初始化(静态变量与静态初始化块)虽然没有在类的最前面声明,但还是先于实例
初始化(实例变量,实例初始化块与构造器)而执行,并且只执行一次。而每次创建对象的
时候都会执行实例初始化。
当类在初始化时,就是执行类内声明的静态初始化语句,包括静态变量声明处初始化与
静态初始化块。与构造器的调用规则相似,类的初始化也是按顺序进行的,当初始化子类
时,如果父类尚未初始化,就会首先初始化父类(可能需要加载并链接父类),这也是一个
递归的过程,直到递归到Object为止。如果父类已经初始化,则初始化将不再执行,因为
类的初始化只执行一次。父类的初始化会在子类的初始化之前完成。父类的实例初始化会
在子类的实例初始化之前完成。 静态初始化先于实例初始化而执行。在静态初始化中
不能引用实例变量。如果在变量尚未初始化就使用这个变量的值,那就是向前引用。
不要在构造器中调用可让子类重写的方法,以免发生向前引用,正常情况下,在构造器
中应该只调用声明为private或者final的方法(final方法不能由子类重写)。
在执行静态初始化时,会按照静态变量声明处初始化与静态初始化块在类中出现的顺序
执行。在执行实例初始化时,会按照实例变量声明处初始化与实例初始化块在类中出现的
顺序执行,然后执行构造器。
如果某个变量x满足以下条件:
1.x使用final修饰
2.x使用在声明处初始化
3.初始化x的表达式(值)是常量表达式(编译时常量)
则可以认为x会最先得到初始化。
初始化的顺序可以简单总结为先静态,后实例,先父类,后子类。
在构造器中不要调用可由子类重写的方法,调用private与final的方法才是安全的。

点击查看更多内容
21人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
JAVA开发工程师
手记
粉丝
1.5万
获赞与收藏
8507

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消