Java面向对象(下)

来源:互联网 发布:实用 双肩包 知乎 编辑:程序博客网 时间:2024/05/22 13:31

Java中最容易让人迷惑的几个知识点,总结如下:

一:引用变量的强制类型转换

在Java中,人们常常提到引用类型的变量,其实质引用变量只能调用它在编译时类型的方法,而不能调用它运行时类型的方法,即使它实际所引用对象确实包括该方法。如果需要让这个引用变量来调用它运行时类型的方法,则必须把它强制类型转换成运行时类型。

格式:类型转换运算符是小括号,如(type)variable    将variable变量转换为一个type类型的变量。

eg   Object obj="java" ;    String objStr=(String)obj;

强制类型转换时需要注意一下几点:

(1)基本类型之间的转换只能在数值类型(整数类型,字符型和浮点型)之间进行,注意,数值型不能和布尔类型之间进行转换。

(2)引用类型之间的转换只能把一个父类型变量转换成子类型(简单的说就是父类引用指向子类对象,被称为向上转型)。

instanceof运算符:instanceof运算符的前一个操作数是一个引用类型的变量,后一个操作数通常是一个类,也可以是一个接口,它用于判断前面的对象是否是后面对象的类,或者其子类,实现类的实例。如果是,则返回true,否则返回false。

经典实例:

public static void main(String[] args) {//定义一个Object类的变量Object obj="heima";//返回trueSystem.out.println("字符串是否是Object类的实例:"+(obj instanceof Object));//String是Object类的子类,所以返回trueSystem.out.println("字符串是否是String类的实例:"+(obj instanceof String));//Math是Object类的子类,所以能正常编译。所以返回falseSystem.out.println("字符串是否是Math类的实例:"+(obj instanceof Math));//String实现了Comparable接口,所以返回trueSystem.out.println("字符串是否是Comparable接口的实例:"+(obj instanceof Comparable));String str="java";//String类不是Math类,也不是Math类的父类,所以下面的代码不能进行编译System.out.println("字符串是否是Math类的实例:"+(str instanceof Math));}
Instanceof和(type)是Java提供的两个相关的运算符,一般先用instanceof判断一个对象是否可以强制类型转换,然后再使用(type)运算符进行强制类型转换,从而保证程序不会出现错误。


二:初始化块

初始化块分为:静态初始化块(在大括号前使用static修饰)和普通初始化块。

初始化块的作用和构造器非常类型,主要用于初始化。

初始化块格式:

[修饰符]{

初始化块的可执行代码[包含任何可执行性语句,包括定义局部变量,调用其他对象的方法,使用分支语句和循环语句等等]

虽然Java中允许在类中定义多个初始化块,但这没太多的意义,因为初始化块是在创建Java对象时隐式执行的,而且总是全部执行,因此当我们完全可以把多个普通初始化块合并成一个初始化块。这样可以挺高程序的可读性。

注意:初始化块,声明实例属性指定默认值都可认为是对象的初始化代码,他们的执行顺序与源代码中的顺序相同。

经典实例:

public class Test {//先执行初始化块,给变量num赋值为10{num=10;}//在执行将变量a的值赋值为6int num=6;public static void main(String[] args) {//下面打印的结果是:变量num=6System.out.println("变量num="+new Test().num);}}
上面的实例充分说明他们的执行顺序与源代码中的顺序相同。

当Java创建一个对象时,系统先为该对象的所有实例属性分配内存(前提是该类已经被加载过了),接着程序 开始对这个实例属性执行初始化,其顺序是:先执行初始化块或者声明属性时指定的初始值,再执行构造器里指定的初始值。通过其他命令可以解析Java执行的源码,其实质是,程序在执行初始化时,会将初始化块中的代码和声明属性的代码都移植到构造函数中,这些代码都位于构造函数自身代码之前。所以他们总是比构造函数自身代码先执行。

静态初始化块:

定义初始化块时使用static修饰符,叫做静态初始化块。静态初始化块与类相关,所以系统将在类初始化阶段执行静态初始化块,因此静态初始化块总是比普通初始化块先执行。静态初始化块属于类的静态成员,同样需要遵循静态成员不能访问非静态成员的规则,因此静态初始化块不能访问非静态成员,包括不能访问实例属性和方法。

初始化块与构造函数类似,创建一个Java对象时,不仅会执行该类的普通初始化和构造器,系统会一直追溯到java.lang.Object类,先执行java.lang.Object类的初始化块,开始执行Object的构造函数,依次向下执行其父类的初始化块,和构造函数.......。最后才能执行自身的初始化块和构造函数。

经典实例:

public class Test extends Root {static{System.out.println("我是Test的静态初始化块");}{System.out.println("我是Test的普通初始化块");}public Test() {System.out.println("我是Test的构造函数");}public Test(String msg){System.out.println(msg);}}class Test2 extends Root{static{System.out.println("我是Test2的静态初始化块");}{System.out.println("我是Test2的普通初始化块");}public Test2() {System.out.println("我是Test2的构造函数");}public static void main(String[] args) {new Test2();new Test2();}}class Root{static{System.out.println("我是Root的静态初始化块");}{System.out.println("我是Root的普通初始化块");}public Root() {System.out.println("我是Root的构造函数");}}

打印结果:

我是Root的静态初始化块
我是Test2的静态初始化块
我是Root的普通初始化块
我是Root的构造函数
我是Test2的普通初始化块
我是Test2的构造函数
我是Root的普通初始化块
我是Root的构造函数
我是Test2的普通初始化块
我是Test2的构造函数


三:"=="和equals比较运算符

Java程序中判断两个变量是否相等有两种方式:一种是利用==运算符,一种是利用equals方法。

当使用==来判断两个变量是否相等时,如果2个变量是基本类型的变量,且都是数值型(不一定要求数据类型严格相同),则只要两个变量的值相等,这返回true。但对于两个引用类型的变量,必须他们指向同一个对象时,==判断才会返回true。

equals方法是Object类提供的一个实例方法,因此所有引用变量都可以调用该方法来判断是否与其他引用变量相等。但这个判断是两个对象相等的标准与==符号没有区别,同样要求两个应用变量来指向同一个对象才会返回true。因此这个Object提供的equals方法没有太大的实际意义,如果希望采用自定义的相等标准,可以采用重写equals方法来实现【实际上,重新equals方法就是提供自定义相等的标准,你认为怎么样相等,就怎么样相等。一切都是你做主】。

经典实例一:

public static void main(String[] args) {int num=65;float fnum=65.0f;char ch='A';System.out.println("65和65.0f是否相等:"+(num==fnum));//trueSystem.out.println("65和'A'是否相等:"+(num==ch));//trueString str1=new String("ABC");String str2=new String("ABC");System.out.println("str1和str2是否相等="+(str1==str2));//false//String类已经重新了equals方法,String的equals方法判断两个字符串是否相等的//标准是:只要两个字符串包含的字符序列相同,就返回true,否则返回false。System.out.println("str1和str2是否相等="+(str1.equals(str2)));//true}
经典实例二:

public class Test1 {private String idCard;public String getIdCard() {return idCard;}public void setIdCard(String idCard) {this.idCard = idCard;}//重写equals方法public boolean equals(Object obj) {//比较的两个对象是否是同一对象if (this == obj) {return true;}//只有当obj是Test1对象if (obj != null && obj.getClass() == Test1.class) {Test1 test = (Test1) obj;if (this.getIdCard().equals(test.getIdCard())) {return true;}}return false;}}


四:final修饰符

final关键字可用于修饰类,变量和方法,被final修饰的类,方法和变量是不可变的。

final修饰变量,被修饰的变量被赋初始值之后,不能对它重新赋值。final修饰方法,被final修饰的方法不能被重写。final修饰类,被final修饰的类不能派生子类。

final修饰实例变量可以在三个位置指定初始值:

(1)在定义final实例变量时指定初始值。

(2)在非静态初始化块中为final实例变量指定初始值。

(3)在构造器中为final实例变量指定初始值。

对于普通的实例变量,Java程序可以对它执行默认的初始化,也就是将实例变量的值指定为默认的初始值0或null;但对于final实例变量,则必须由程序员显示指定初始值。

final修饰类变量可以在两个地方指定初始值

(1)定义final类变量时指定初始值。

(2)在静态初始化块中为final类变量指定初始值。

除上面两个地方可以为final类变量指定初始值外,final类变量不能再次赋值。

final修饰局部变量:

Java本来就要求局部变量必须被显式的赋初始值,final修饰的局部变量一样需要被显式的赋初始值。与普通初始值变量不同的是,final修饰的局部变量被赋初始值之后,就不能再对final局部变量重新赋值。

对于一个使用final修饰的变量(类变量,实例变量,局部变量)而言,如果定义该final变量时就指定初始值或确定的表达式(如果被赋的表达式只是基本的算术运算表达式或字符串连接运算,没有访问普通变量,调用方法),而且这个初始值可以在编译时就确定下来,那么这个final变量将不再是一个变量,系统会将其当成"宏变量"处理。也就是说,所有出现该变量的地方,系统将直接把它当成对应的值处理。

经典实例:

public static void main(String[] args) {//下面定义五个宏变量。final int a=5;final int b=5+3;final  double c=1.2/3;final String str1="Java"+" Android";final String str2="Java"+99;//str3变量的值因为调用了方法,所以在编译时不能确定值,因此str3不是宏变量。final String str3="Java"+String.valueOf(99);System.out.println(str2=="Java99");//返回trueSystem.out.println(str3=="Java99");//返回false}

Java要求所有被内部类访问的局部变量都使用final修饰,对应普通局部变量而言,它的作用域就是停留在该方法内,当方法执行结束,该局部变量也随之消失;但内部类则可能产生隐式的“闭包”,闭包将使得局部变量脱离它所在的方法继续存在【此处说的内部类指的是局部内部类,因为只有局部内部类(包括匿名内部类)才可以访问局部变量,普通静态内部类,非静态内部类不可访问方法体的局部变量】。


五:抽象类和抽象方法

在Java中被abstract修饰的类,叫抽象类,被abstract修饰的方法,叫抽象方法。

抽象类和抽象方法原则:

(1)抽象类必须使用abstract修饰符修饰,抽象方法必须使用abstract修饰符修饰,抽象方法不能有方法体。

(2)抽象类不能被实例化,即使抽象类中不包含抽象方法,同样也不能创建实例。

(3)抽象类可以包含属性,方法(普通方法和抽象方法都可以),构造器,初始化块,内部类,枚举类六种成分。抽象类的构造器不能用于创建实例,主要是用于被子类调用。

(4)含有抽象方法的类,只能被定义为抽象类。

注意:

(1)抽象方法和空方法体的方法是不同概念,例如public abstract void test();是一个抽象方法,它根本没有方法体,方法后面没有大括号,但public void test(){}方法是一个普通的方法,他已经定义了方法体,只是方法体是空,因此该方法不能用abstract修饰。

(2)abstract不能用于修饰属性,不能用于修饰局部变量,即使没有抽象变量,没有抽象属性等说法;abstract也不能修饰构造器,没有抽象构造器。抽象类中定义的构造器只能是普通的构造器。

(3)当使用static修饰一个方法时,该方法属于类,如果该方法被定义成抽象方法,则将导致通过该类来调用该方法时出现错误,因此static和abstract不能同时修饰某个方法。

(4)abstract关键字修饰的方法必须被子类重写才有意义,因此abstract方法不能定义为private访问权限。

抽象类的作用:

抽象类是从多个具体类中抽象出来的父类,它具有更高层次的抽象。从多个具有相同特征的类中抽象出一个抽象类,以这个抽象类作为其子类的模版,从而避免子类设计的随意性。抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展,改造,但子类总体上会大致保留抽象类的行为方式。

六:接口

接口是从多个相似类中抽象出来的规范,接口不提供任何实现。可以看成是抽象类的一种特殊。

由于接口定义的是一种规范,因此接口里不能包含构造器和初始化块定义,接口里可以包含属性(只能是常量,默认是public static final,只能是public static final)、方法(只能是抽象方法,默认修饰符是public,也只能是public)、内部类(包括内部接口)和枚举定义。

接口支持多继承,一个接口可以有多个直接的父接口。和类的继承相似,子接口可以扩展某个父接口,将会获得父接口里所有的抽象方法,常量属性,内部类和枚举类的定义。

格式: interface D extends A,B,C{ }

注意:

(1)接口不能创建实例,但接口可以用于声明引用类型的变量。当使用接口来声明引用类型的变量时,这个引用类型的变量必须引用到其实现类的对象。

(2)一个类可以实现一个和多个接口,多个接口之间使用逗号隔开。


总结接口和抽象类:

相同特点:

(1)接口和抽象类都不能实例化,它们都位于继承树的顶端,用于被其他类实现和继承。

(2)接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通之类都必须实现这些抽象方法。

不同特点:

(1)二者的设计目的不同,接口作为系统与外界交换的窗口,接口体现的是一种规范。从某种程度上看,接口类似于整个系统的"总纲",他制定了系统各模块应该遵循的标准,因此一个系统中的接口不应该经常修改,一定接口被修改,对整个系统甚至其他系统的影响都非常大(导致大部分类都需要修改)。抽象类就不一样,抽象类作为系统中多个子类的共同父类,它所体现的是一种模板式的设计,抽象类作为多个子类的抽象父类,可以被当成系统实现过程中的中间产品,这个中间产品已经实现了系统的大部分功能,但这个产品不是最终产品,必须进一步完善。

(2)接口里只能包括抽象方法,不能包含已经提供实现的方法 ;抽象类则可以包含已经实现的方法。

(3)接口里不能定义静态方法;抽象类可以定义静态方法。

(4)接口里只能定义静态常量属性,不能定义普通属性;抽象类里既可以定义普通属性,也可以定义静态常量属性。

(5)接口不包含构造器;抽象类可以包含构造器,抽象类里的构造器并不是用于创建对象,而是让子类调用这个构造器来完成属与抽象类的初始化操作。

(6)接口里不能包含初始化块,但抽象类则完全可以包含初始化块。

(7)一个类最多只能有一个直接父类,包括抽象类;但一个类可以直接实现多个接口,通过实现多个接口可以弥补Java中的单继承。