类的继承
通过继承可以简化类的定义,java只支持单继承,不允许多重继承,即一个类只能有一个父类。但可以有多层继承,即一个类可以继承某一个类的子类。如类B继承了类A,类C有继承了类B,那么类C也间接继承了类A。
子类继承父类所有的(非private的)成员变量和成员方法,但不继承父类的构造方法。在子类的构造方法中可使用语句super(参数列表)调用父类的构造方法。如果子类的构造方法中没有显示地调用父类的构造方法,也没有使用this(参数列表)调用重载的其他构造方法,则在产生子类的实例对象时,系统默认调用父类无参数的构造方法,因此在编写一个类时,应保证有一个无参数的构造方法。
子类对象的实例化过程
1、分配成员变量的存储空间并进行默认的初始化,就是用new关键字产生对象后,对类的成员变量进行初始化赋值。
2、绑定构造方法参数,就是将实际参数列表中所传递进的参数赋值给构造方法中的形式参数变量。
3、如有this调用,则调用相应的重载构造方法(被调用的重载构造方法又重步骤2开始执行),被调用的重载构造方法的执行流程结束后,回到当前构造方法,当前构造方法直接跳转到步骤6执行。this(…)调用,与super(…)调用不可同时出现,并且它们必须作为构造函数方法体的第一句。
4、显示或隐式追溯调用父类的构造方法,一直到Object类为止,父类的构造方法又从步骤2开始对父类执行这些流程,父类的构造方法的执行流程结束后,回到当前构造方法,当前构造方法继续往下执行。
5、进行实例变量的显示初始化操作,也就是执行在定义成员变量时就对其进行赋值的语句。
6、执行当前构造方法的方法体重的语句,至完成。
覆盖父类的方法
覆盖父类的方法和被覆盖方法具有相同的方法名称、参数列表和返回值类型。
如果在子类中想调用父类的那个被覆盖的方法,我们可以用super.方法的格式。
覆盖方法时,不能使用比父类中被覆盖的方法风严格的访问权限。
final关键字
final标记的类不能被继承。
final标记的方法不能被子类重写。
final标记的变量(成员变量或局部变量)即成为常量,只能赋值一次。要么在变量刚开始定义时对其进行赋值,要么在构造方法中进行赋值,但必须保证在所有的构造方法中对其赋值。
方法中定义的内置类只能访问该方法的final类型的局部变量,用final定义的局部变量相当于一个常量,它的生命周期超出方法的运行的生命周期,将一个形参定义成final也是可以的,就先定了我们在方法中修改形式参数的值。
public static final共同标记常量时,这个常量就成了全局的常量,必须在定义时就进行初始化。
抽象类
java中可以一些不含方法体的方法,它的方法体的实现交给该类的子类根据自己的情况去实现,这样的方法就是抽象方法,包含抽象方法的类就叫抽象类。抽象类中不一定所有的方法是抽象方法。
抽象类必须用abstract关键字修饰;抽象方法也必须用abstract来修饰。
抽象类不能被实例化,也就是不能用new关键字去产生对象。
抽象方法只需声明,而不需实现,即没用大括号。
含有抽象方法的类必须被声明为抽象类,抽象类的子类必须覆盖所有的抽象方法后才能被实例化,否则这个子类还是个抽象类。
接口(interfacce)
如果一个抽象类中的所有方法都是抽象的(不用加abstract关键字),我们就可以将这个类用另外一种方式来定义,也就是接口。接口是抽象方法和常量的定义的集合,从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义,而没有变量和方法的实现。
接口中的成员都是public访问类型的,public关键字可省略。接口里的变量默认是用publicstatic final标识的,一般可以将public static省略。
我们可以定义一个新的接口用extends关键字去继承一个已有的接口。
我们也可以定义一个类用implements关键字去实现一个接口中所有的方法,这些方法必须是public的。我们还可以去定义一个抽象类用implements关键字去实现一个接口中定义的部分方法。
一个类可以继承一个父类的同时,实现一个或多个接口,extends关键字必须位于implements关键字之前。
对象的类型转换
子类对象可以自动转换成父类, 即父类类型的引用变量可以指向子类的对象。
class A
{
public void func1()
{
System.out.println("A func1 is calling");
}
public void func2()
{
func1();
}
}
class Bextends A
{
public void func3()
{
System.out.println("B func3 is calling");
}
}
class c
{
public static void main(String[] args)
{
B b=new B();
callA(b); //b可以传递给a
}
public static void callA(A a)
{
a.func1();
a.func2();
}
}
若将类c的callA方法稍作修改:
publicstatic void callA(A a)
{
a.func1();
a.func2();
a.func3();
}
尽管传过来的对象b中有func3方法,但编译器是在运行之前,按语法进行检查的,只认参数a,而对象a中是没有func3方法的。
父类转换成子类必须使用强制转换。
若将类c的callA方法改为:
publicstatic void callA(A a)
{
B b=(B)a; //需要强制转换。
b.func1();
b.func2();
b.func3();
}
方法func3()便可执行,编译没有错误。使用强制转换时一定要注意,传过来的对象一定要有func3()方法,若没有,即使进行强制类型转换,但是传过来的对象本身没有发生改变,只是当做对象b来使用,此时编译时没有错误,但运行时会出错。
为防出现上面的错误,我们可以用instanceof操作符可以用它来判断一个实例对象是否属于一个类。如可将类c的callA方法改为:
public static void callA(A a)
{
if(a instanceof B)
{
B b=(B)a;
b.func1();
b.func2();
b.func3();
}
else
{
a.func1();
a.func2();
}
}
Object类的equals方法
若我么要比较两个类的对象是否相等,就必须重写Object类的equals方法,否则总是返回不相等。比较两个对象是否相等,主要是比较它们的成员变量是否相同。如:
publicclass Student
{
private String name;
private int age;
public Student(String name,int age)
{
this.name=name;
this.age=age;
}
public boolean equals(Object obj)//重写Object类的equals方法
{
Student stu=null;
if(obj instanceof Student)
{
stu=(Student)obj;
if(stu.name==name&&stu.age==age)
return true;
else
return false;
}
else
return false;
}
}
面向对象的多态性
a、应用程序不必为每一个派生类(子类)编写动能语句,只需对抽象基类进行处理即可。这样可大大提高程序的可复用性。
b、派生类的功能可以被基类的引用变量引用,这叫向后兼容,可以提高程序的可扩充性。以前写的程序可以被后来程序调用不足为奇,现在写的程序能调用以后写的程序就了不起了。
异常
异常定义了程序中遇到的非致命的错误,而不是编译时的错误,如果程序要打开一个不存在的文件、网络连接中断、操作数越界、装载一个不存在的类等。出现异常后,程序便不能再向下运行。
用try,catch语句,处理异常:
class Test
{
public int devide(int x,int y)
{
int result=x/y;
return result;
}
}
class TestException {
public static void main(String[] args)
{
try
//将可能产生异常的语句包起来,从产生异常的语句处跳转到catch,后面的不再执行
{
new Test().devide(3, 0);
}
catch(Exception e) //捕获异常,处理异常
{
System.out.println(e.getMessage());
}
}
}
如果程序的编写者知道某个方法可能会产生异常,可用throws关键字来声明,而不用处理异常。这样该方法的使用者就必须用用try,catch语句,处理可能出现的异常。如:
publicint devide(int x,int y)throws Exception
{
int result=x/y;
return result;
}
有时方法的使用者也不想去处理这些异常,也可以用throws关键字抛出,传递给上级程序,如同踢皮球一样传递下去,直到main方法,但这些异常最终需要被某个使用者处理,否则会出现不必要的麻烦。
自定义异常与throw关键字
如将上程序改为:
class Test
{
public int devide(int x,int y)throws Exception //自定义异常需要继承Exeception类
{
if(y<0)
throw new DevideByMinusException("devisor is "+y);
//用throw产生新定义的异常
int result=x/y;
return result;
}
}
class DevideByMinusExceptionextends Exception
{
public DevideByMinusException(String msg)
{
super(msg);
}
}
class TestException {
public static void main(String[] args)
{
try
{
new Test().devide(3, -1);
}
catch(Exception e)
{
System.out.println(e.getMessage());
}
}
}
运行结果:devisor is -1
对多个异常作出处理
一个方法可以抛出多个异常,如:
publicint devide(int x,int y)throws ArithmeticException,DevideByMinusException,Exception
{
if(y<=0)
throw new DevideByMinusException("devisor is "+y);
int result=x/y;
return result;
}
}
此时,相应的try,catch语句变为:
try
{
new Test().devide(3, 0);
}
catch(ArithmeticException e)
{
//异常处理语句
}
catch(DevideByMinusException e)
{
//异常处理语句
}
catch(Exception e)
{
System.out.println(e.getMessage());
}
对于有多个catch语句的情况,程序会根据产生的异常,选择最符合的catch块,而后面的catch代码块就不再执行。这就要求我们将最底层的,描述的最详细的异常类放在前面。
我们可以在一个方法中使用throw,try…catch语句来实现程序的跳转,而不一定真正要处理某个异常。我们可以故意用throw产生某异常,然后用catch语句捕获,以实现跳转:
void fun()
{
try
{
if(x==0)
throw new XxxException("xxx");
//在try语句中故意产生某异常,实现跳转
else
throw new YyyException("yyy");//
}
catch(XxxException e)
{
//要跳转到的语句
}
catch(YyyException e)
{
//要跳转到的语句
}
}
finally语句
通常将finally语句放在catch后面:
try
{
new Test().devide(3, 0);
}
catch(Exception e)
{
System.out.println(e.getMessage());
}
finally
{
//相应的语句
}
finally中的语句是总会被执行的,无论有没有异常,也不管前面是否有return或break等语句,除非前面有System.exit(0),否则finally中的语句是总会被执行。
另外,一个父类的方法被覆盖时,覆盖它的方法必须扔出相同的异常或异常的子类。
如果父类扔出多个异常,那么重写(覆盖)方法必须扔出那些异常的一个子集,也就是说不能扔出新的异常。
包
包使得类存放更有层次,也提供了多个命名空间,在计算机中,必须有与包名相对应的目录结构,此时就需要新建相应的文件夹,将类放在里面。此时在命令行中运行(注意不是编辑时)这个类时,类的名称变成了包名加原类名,如:原来是java Xxx,现在必须是:java包名.Xxx,否则编译器找不到所要编译的类,并且编译时所指的目录只需到达包所在的目录就可以了,编译器会自动根据此时的类名,找相应的目录结构。
其实编译器可以帮我们创建这个目录,在编译时,在命令行中键入:javac –d . Xxx.java(注意空格),其中的点代表当前目录,也就是说将编译的结果放在当前目录下,这时候,在当前目录下便会创建相应的目录。从中也可以看出目录结构体现包名,同一个包中的类可以放在不同的地方,只要它们有相同的体现包名的相对目录结构就可以了。
package语句作为java源文件的第一条语句,结构是:package包名,这就告诉编译器,该类在一个包中。如果没有package语句,则为缺省无名包。而同一个包中的类相互调用是不用指定包名的。父包与子包之间的相互调用与其他的两个包之间的调用是没有区别的,import了父包并不等于import了子包。
访问控制
类成员的访问控制如图:
类本身也有访问控制,即在定义类的class关键字前加上访问控制符,但类只有两种访问控制即public和默认,public修饰符的类能被所有的类访问,默认修饰(即class关键字前没有访问控制符)的类,只能被同一包中的类访问。
使用jar文件
java的jar文件其实是一种压缩格式的文件,当我们将jar文件交给其他人使用时,使用者只要将classpath目录中包含jar文件,使用时java编译程序就自动在内存中解压jar文件,将jar文件当成一个目录,然后在这个目录中查找我们需要的类以及类的包名所对应的目录结构。
java中提供了jar.exe来生成和查看jar文件。jar命令常用的使用格式为:
jar –选项输出文件名.jar文件名/目录
生成jar文件:
jar –cf文件名.jar要压缩的文件名/目录或jar –cvf文件名.jar要压缩的文件名/目录
查看jar文件:
jar –tf文件名.jar或 jar –tvf文件名.jar(查看更详细的内容)或jar –tvf 文件名.jar >文件名(将要查看的内容转放至该文件,注意要有扩展名)
解压jar文件
jar –xf文件名.jar或jar –xvf文件名.jar
使用jar命令压缩文件时,生成的jar文件中会包含出现在jar命令中的所有目录,所以一定要注意生成的jar文件的目录。