l 内容提要:
*多态
*重载
*覆盖
Object类的描述
*构造函数及初始化操作
*使用protected及friendly访问修饰符
*异类收集
instanceof 运算符
*对象的类型转换
=运算符与equals()方法
toString方法的描述
Class类的介绍
包装类
继承(子类)
l 在编程中,常常要创建某件事的模型(如:一个动物),我们要给几种动物创建类的模型。
l 当我们写一些相关类的时候,没必要进行重复定义(比如:属性,方法)。看看下面的例子:
l class Cat { //猫类
l
l double weight;
l String name;
l
l public Cat(String aName,double aWeight) {//初始化猫的名字和体重
l name=aName;
l weight=aWeight;
l }
l
l public void setName(String aName) {//从新设置猫的名字
l name=aName;
l }
l
l public String getName() { //得到猫的名字
l return name;
l }
l
l public double getWeight() {//得到猫的体重
l return weight;
l }
l
l public void eat(double something) { //猫吃东西
l weight=weight+something;
l }
l };
l
l class Dog //狗类
l {
l double weight;
l String name;
l
l public Dog(String aName,dobule aWeight) {//初始化狗的名字和体重
l name=aName;
l weight=aWeight;
l }
l
l public void setName(String aName) {//从新设置狗的名字
l name=aName;
l }
l
l public String getName() { //得到狗的名字
l return name;
l }
l
l public double getWeight() {//得到狗的体重
l return weight;
l }
l
l public void eat(double something){ //狗吃东西
l weight=weight+something;
l }
l };
l 在猫和狗类之间有许多相同的数据,而且有同时适用于猫和狗两者的方法。假如我们需要描述所有的动物,每个动物用一个类描述,那就不太科学了。在面向对象的语言中,允许程序员用现有的类来定义一个新类,这个新类就叫做原由类的子类。那么我们分析一下,所有的动物都有体重和名字,还有吃饭等行为。如果我们构造一个动物类来描述所有动物都具有的属性和行为,然后在创建一个具体动物类的时候,只要继承动物类就可以了。那么我们可以先构造一个动物类。然后让一个具体的动物类继承这个动物类。如下所示,可用关键字extends来实现:
l class Animal {//动物类
l
l public double weight;
l public String name;
l
l public Animal(){}
l
l public Animal(String aName,double aWeight){//初始化动//物的名字和体重
l name=aName;
l weight=aWeight;
l }
l
l public void setName(String aName) {//从新设置动物的名//字
l name=aName;
l }
l
l public String getName(){//得到动物的名字
l return name;
l }
l
l public double getWeight(){//得到动物的体重
l return weight;
l }
l
l public void eat(double something) {
l weight=weight+something;
l }
l };
l
l class Cat extends Animal { //猫就自动把动物类的基本特性都继承过来了。
l
l public Cat(){}
l
l public Cat(String name,double w){
l super(name,w); //调用父类的构造器。
l }
l
l public void catchMouse(){ //猫特有的功能,子类扩展父类的功能
l System.out.println(“cat can catch mouse!”):
l }
l }
l 通过继承的方法,Cat类被定义,具有 Animal 所拥有的全部变量及方法。所有这些变量和方法都是从父类的定义中继承来的。所有的程序员需要做的是定义额外特征或规定将适用的变化。(比如猫捉老鼠)
l
l 注意:这种方法是在维护和可靠性方面的一个伟大进步。如果在Animal类中进行修改,那么, Cat类就会自动修改,而不需要程序员做任何工作,除了对它进行编译。
l 单继承性
Java编程语言允许一个类仅能扩展成一个其它类。这个限制被称做单继承性。Java编程语言加强了单继承性限制而使代码更为可靠。
使用单继承性的子类的一个例子如图6-1所示:
构造函数不能被继承
l 尽管一个子类从父类继承所有的方法和变量,但它不继承构造函数,掌握这一点很重要。
l 一个类能得到构造函数,只有两个办法。或者写构造函数,或者根本没有写构造函数,类有一个缺省构造函数。
l 子类只能且必须调用父类的构造函数。这也是为什么子类能够继承到父类属性和方法的原因。因为要调用父类的构造函数,调用父类构造函数的时候,会把父类的方法和属性构造给子类。并不是只靠一个关键字extends就可以继承到父类的东西。默认情况下子类缺省构造函数会去调用父类的缺省构造函数。例如:
l class Cat extends Animal{
l
l public Cat() { //却省构造函数的完整写法
l super(); //super关键字是来调用父类的构造函数
l }
l }
l 在子类构造函数里面,如果没有明确用super这个关键字调用父类的那个构造函数,那么缺省状态下会去调用父类默认的构造函数。如果父类没有默认的构造函数(该类定义了自己的构造函数,系统不会再给提供默认的构造函数),并且子类又没有去调用父类具体的构造函数的时候,编译器在编译的时候就会报错。例如:t.java
l class Person{
l
l String name;
l
l public Person(String aName){
l name=aName;
l }
l
l }
l
l class Manager extends Person{
l /*
l 由于该子类没有定义构造函数,那么系统会为该类提供一个
l 默认的构造函数:
l public Manager(){
l super(); //由于父类并没有不带参数的构造函数,
l 所以编译器一定会报错
l }
l */
l };
l 在编译的时候会报出以下错误:
l t.java:9: Person(java.lang.String) in Person cannot be applied to ()
l class Manager extends Person
l ^
l 1 error
l 改后的代码如下:
l class Person{
l
l String name;
l
l public Person(String aName){
l name=aName;
l }
l }
l
l class Manager extends Person{
l /*
l 由于该子类没有定义构造函数,那么系统会为该类提供一个
l 默认的构造函数:
l public Manager(){
l super(); //由于父类并没有不带参数的构造函数,
l 所以编译器一定会报错
l }
l */
l public Manager(){
l this("andy"); //this是调用本类的其它构造函数
l }
l public Manager(String s){
l super(s); //调用父类的构造函数
l }
l };
l 当用Manager的默认的构造函数来构造对象的时候,执行顺序如下:
l new Magager() 通过this(“andy”) 调用 Manager(String s)这个构造函数,该构造函数会通过super(s),来调用父类的Person(String aName)构造函数。
l 注意:this和super关键字都可以来调用构造函数,this是调用本类的构造函数,super是调用父类的构造函数。而且this或者super语句必须写在构造函数里的第一行,并且这两个语句不能同时出现在一个构造函数里面。如果在一个构造器里既没有this,也没有super语句来调用其它的构造器,那么系统会默认的加上super()这句话。这是他们的第一个作用。
l 例如:
l class Person{
l
l String name;
l
l public Person(String aName){
l name=aName;
l }
l }
l
l class Manager extends Person{
l /*
l 由于该子类没有定义构造函数,那么系统会为该类提供一个
l 默认的构造函数:
l public Manager(){
l super(); //由于父类并没有不带参数的构造函数,
l 所以编译器一定会报错
l }
l */
l
l public Manager(){
l super("dd"); //两条语句同时出现在该构造函数里面,所以会出错
l this("andy"); //this是调用本类的其它构造函数
l }
l
l public Manager(String s) {
l int k=5; //出错,super或者this语句必须出现在第一行
l super(s); //调用父类的构造函数
l }
l };
l this和super的第二个作用就是做为隐含参数的调用。this代表本类的当前对象,super是代表父类的对象
l
l 多 态
l 在说多态这个概念之前,我们先说一下,C/C++语言中的指针,定义一个指针,该指针可以指向任何类型的值。
l 也就说一个指针变量可以指向多种不同类型状态,这种现象就是多态,这是广义上的多态。在JAVA里面,删除了指针的概念。而引入真正的数组(上一章已经介绍过)。在JAVA里面可以定义一个类型的变量,该类型的变量可以指向多种类型的现象叫做多态。有了继承,才有了多态。具体地说用父类定义的变量可以指向其所有子类类型的对象,就是多态。
l 还有一种就是用接口声明的变量,可以指向实现该接口的任何类产生的对象。(下一章讲到接口)。
l
l 一个对象只有一个类型(是在构造时给它的)。在Java编程语言中,有一个类,它是其它所有类的父类。这就是java.lang.object类。因此,实际上,以前的定义被简略为:
public class Animal extends Object
public class Cat extends Animal
也就是说当你定义一个类,没有明确指出该类继承哪个类,那么该类会自动的继承java.lang.Object类。
Object类定义许多有用的方法,包括toString(),它就是为什么Java软件中每样东西都能转换成字符串表示法的原因。象大多数面向对象语言一样,Java允许父类的变量引用子类的对象。因此,下面代码是合法的:
Animal a = new Cat();
使用变量a是因为,你能访问的对象部分只是Cat的一个部分;Cat的特殊部分是隐藏的。这是因为编译者应意识到,a 是一个Animal,而不是一个Cat。因而,下述情况是不允许的:
l a.catchMouse(); // illegal
l 分析原因:a是类 Animal声明的,并且该变量确实指向了子类Cat产生的对象。当你用变量调用Cat类里的catchMouse()方法的时候,就会报错。主要是编译器会检查这些语法错误,变量a调用catchMouse()方法,编译器会检查类Animal有没有这个方法,如果类Animal没有catchMouse()方法,那么编译器就会报错。这是编译器的安全机制造成的。
l
l 异类收集
可以创建具有共同类的对象的收集(如数组)。这种收集被称作同类收集。
Java编程语言有一个Object类,因此,由于多态性,它能收集所有种类的元素。这种收集被称作异类收集。也就是说用Object类声明一个数组,该数组可以存放(引用)任何类型的对象(包括数组类型,数组是Object类派生出来的类类型)。创建一个Cat并慎重地将其引用赋到类型Animal的变量中似乎是不现实的。但这是可能的,而且有很多为什么要取得这种效果的理由。
用这种方法,可以写出一个能接受通用对象的方法,在这种情况下,就是类Animal,并在它的任何子类的对象上正确地运作。然后可以在应用类中产生一个方法,该应用类抽取一个动物,并将给该动物喂什么食物?
// In the Animal class
if(e.getName().equals(“Cat”))
e.eat(1.4);//如果是猫的话,喂1.4斤的鱼
}
// Meanwhile, elsewhere in the application class
Cat c = new Cat();
findAnimal(c);
这是合法的,因为一只猫就是一只动物。
异类收集就是不相同的对象的收集。在面向对象语言中,可以创建许多东西的收集。所有的都有一 个共同的祖先类-Object类。如:
Animal [] animal = new Animal[1024];
animal[0] = new Cat(“Cat1”); //初始化时给定一个名字
animal[1] = new Dog(“Dog1”);
animal[2] = new Fish(“Fish1”);
for(){
animal[i].getName()
Instanceof 运算符
我们可以通过异类收集将一些不同对象收集到它们的父类中。有时你可能想知道某个对象到底属于哪个类。这就是instanceof运算符的目的。假设类层次按照下列方法被扩展:
public class Animal extends Object
public class Cat extends Animal
public class Dog extends Animal
如果你通过Animal类型的引用接受一个对象,它是否能够变换成Cat或Dog,可以象这样用instanceof 来测试:
if (a instanceof Cat) {
//可以捉老鼠
}else if (a instanceof Dog) {
// 可以咬
}else {
//普通的操作
}
对象的类型转换
l 在你接收父类的一个引用时,你可以通过使用Instanceof运算符判定该对象实际上是你所要的子类,并可以用类型转换该引用的办法来恢复对象的全部功能:
if (a instanceof Manager) {
Cat c = (Cat)a;
c.catchMouse();
}
}
l 如果不用强制类型转换,那么引用c.catchMouse()的尝试就会失败,因为编译器不能将被称做catchMouse()的成员方法定位在Animal类中。
l 如果不用instanceof做测试,就会有类型转换失败的危险。通常情况下,类型转换一个对象引用的尝试是要经过几种检查的:
l 向上强制类型转换类层次总是允许的,而且事实上不需要强制类型转换运算符。可由简单的赋值实现。
l 对于向下类型转换,编译器必须满足类型转换至少是可能的这样的条件。比如,任何将Cat引用类型转换成Dog引用的尝试是肯定不允许的,因为Dog不是一个Cat。类型转换发生的类必须是当前引用类型的子类。
l 如果编译器允许类型转换,那么,该引用类型就会在运行时被检查。比如,如果instanceof检查从源程序中被省略,而被类型转换的对象实际上不是它应被类型转换进去的类型,那么,就会发生一个运行时错误(exception)。异常是运行时错误的一种形式,而且是后面模块中的主题。
l 请看下面综合的例子:
l class TestInherit {
l public static void main(String[] args) {
l
l Animal[] k=new Animal[3]; //定义动物类型的数组,来保存各种类型的动物
l k[0]=new Cat("Cat");
l k[1] =new Dog("Dog");
l k[2]=new Animal("Animal");
l for(int i=0;i<k.length ;i++){
l if(k[i] instanceof Cat)
l ((Cat)k[i]).catchMouse();
l else if(k[i] instanceof Dog)
l ((Dog)k[i]).bitPerson();
l else
l k[i].says();
l }
l }
l }
l
l class Animal{
l
l public String name;
l
l public Animal(String aName){
l name=aName;
l }
l
l public void says(){
l System.out.println("My name is"+name);
l }
l
l };
l
l class Cat extends Animal {
l
l public Cat(String s){
l super(s);
l }
l
l public void catchMouse(){
l System.out.println("Cat can catch Mouse");
l }
l };
l
l class Dog extends Animal{
l
l public Dog(String s){
l super(s);
l }
l
l public void bitPerson(){
l System.out.println("Be carefull ,I could bit you!");
l }
l
l 访问控制
l 变量和方法可以处于四个访问级别的一个中;公共,受保护,缺省或私有。类可以在公共或缺省级别。
l 变量、方法或类有缺省访问性,如果它没有显式受保护修饰符作为它的声明的一部分的话。这种访问性意味着,访问可以来自任何方法,当然这些方法只能在作为目标的同一个包中的成员类当中。
l 以修饰符protected标记的变量或方法实际上比以缺省访问控制标记的更易访问。一个protected方法或变量可以从类当中的任何方法进行访问,这个类可以是同一个包中的成员,也可以是从任何子类中的任何方法进行访问。当它适合于一个类的子类但不是不相关的类时,就可以使用这种受保护访问来访问成员。
表6-1 访问性标准
修饰符 同类 同包 子类 通用性
public(公共) 是 是 是 是
protected(保护) 是 是 是
friendly (缺省) 是 是
private (私有) 是
受保护访问甚至被提供给子类,该子类驻留在与拥有受保护特征的类的不同包中。
重载方法名称
l 在某些情况下,可能要在同一个类中写几个方法做基本相同工作,区别仅仅在于参数不同。例如,输出文本参数的方法, println(String s)。
l 很有可能,你还需要打印int,float,double等类型数据的打印方法。这是合情合理的,因为各种数据类型要求不同的格式,而且可能要求不同的处理。你当然可以分别创建三个方法:printInt(),printFloat()和printDouble ()。但是Java为你提供了更好的办法。
l Java允许多个方法使用相同的名称,而通过参数表来加以区分,这就叫重载。注意:返回类型不做要求。
l 对于上述三种打印方法,可以使用相同的函数名,在参数的数量和类型上对此进行区分:
public void println(int i)
public void println(float f)
public void println(double d)
l 当写代码来调用这些方法中的一种方法时,根据提供的参数的类型选择合适的一种方法。
l 有两个规则适用于重载方法:
l 调用语句的参数表必须有足够的不同,以便区分出正确的方法被调用。正常的拓展晋升(如,单精度类型float到双精度类型double)可以被应用,但是这样会导致在某些条件下的混淆。
l 重载方法的参数表必须不同。方法的返回类型可以各不相同,但它不足以使返回类型变成唯一的差异。
参见事例:
class TestCZ{
public static void main(String[] args) {
TestCZ tt=new TestCZ();
tt.print(2.0f);
}
//如果不注释掉,会调用该方法。原因是参数是float类型的
/*public void print(float f) {
System.out.println("float");
}*/
public int print(double d) {//虽然返回类型不一样,也叫方法重载。
System.out.println("double");
return 1;
}
}
在方法重载调用中。虚拟机会根据参数来找到最匹配的方法拿来调用。
重载构造函数
如果有一个类带有几个构造函数,那么也许会想复制其中一个构造函数的某些功能到另一个构造函数中。可以通过使用关键字this作为一个方法调用来达到这个目的。
l class Person{
l
l String name;
l
l public Person(String aName){
l name=aName;
l }
l }
l
l class Manager extends Person{
l
l public Manager(){
l this("andy"); //调用本类的构造函数
l }
l
l public Manager(String s) { //构造函数的重载
l super(s); //调用父类的构造函数
l }
l };
前面已经介绍过调用的过程。下面再重复一遍:
l 当用Manager的默认的构造函数来构造对象的时候,执行顺序如下:
new Magager() 通过this(“andy”) 调用 Manager(String s)这个构造函数,该构造函数会通过super(s),来调用父类的Person(String aName)构造函数。
构造并初始化对象
l 对象初始化是一个相当复杂的过程。简单来讲,有以下主要过程:
l 首先,系统为整个对象分配存储空间,并为实例变量分配默认值。第二步,调用顶层构造函数。递归调用父类的构造函数。第三步,在执行该类构造器之前进行属性值的初始化。
l 例如,我们用下面的代码初始化Manager和Employee类。
l public class Object{
l …
l public Object( ){}
l …
l }
l
l public class Employee extends Object{
l
l private String name;
l private double salary = 3800.00;
l private Date birthDate;
l
l public Employee(String n, Date DoB){
l //默认的会加上该句 super( );
l name =n;
l birthDate = DoB;
l }
l
l public Employee(String n){
l this(n, null);
l }
l }
l
l public class Manager extends Employee{
l
l private String department;
l
l public Manager(String n, String d){
l super(n);
l department = d;
l }
l }
l 覆盖
l 在继承的时候,有时候父类的方法并不能满足子类的要求。而子类如果再定义新的方法,就满足不 了多太性的优点。
l 在方法覆盖的时候要注意以下三点:
1, 子类的方法名,参数,返回类型必须与父类的相同。
2, 访问权限不能比父类的弱!
3, 抛出的异常不能比父类多(这个多字 含义就是子类覆盖方法抛出的异常必须为父类方法抛出的异常所识别的。也就是多态,在后面会介绍异常的操作。)
l 例如:
l class TestMO {//methord override
l
l public static void main(String[] args) {
l Animal a[]=new Animal[3];
l a[0]=new Cat();
l a[1]=new Animal();
l a[2]=new Dog();
l for(int i=0;i<a.length;i++)
l a[i].say(); //第十句
l }
l }
l
l class Animal{
l
l public void say(){
l System.out.println("animal say");
l }
l };
l
l class Cat extends Animal{
l
l public void say() { //覆盖父类方法,如果把public改成其他的访问权限,编译就通不过了
l System.out.println("cat say");
l }
l };
l
l class Dog extends Animal{
l
l public void say(){//覆盖父类方法
l System.out.println("dog say");
l }
l };
l 第十句用到了动态绑定机制。下面简单描述动态绑定:
1) 编译器检查对象的声明类型和方法名。假设我们调用a.m(args),并且隐式变量a被声明为Animal类的一个对象。要注意的是,可能会有多个方法都叫m,只是其参数类型各不相同。比如:可能有方法m(int),还有方法m(String)。编译器会列举Animal类中名为m的所有方法和Animal的超类中所有名为m的公有方法。
l 现在,编译器就知道了被调用方法的所有可能候选者。
2) 接下来,编译器检查方法调用中提供的参数类型。如果在所有名为m的方法中有一个的参数类型同调用提供的参数类型最为匹配,那么就选折调用这个方法。这个过程称为“重载解析”。例如:对调用a.m(5),编译器会挑选m(int),而不是m(String)。由于存在类型转换(int转成double,Cat转成Animal等等),情况会变得复杂。如果编译器无法找到任何同参数类型匹配的方法,或者找到了多个参数类型在转换后能够匹配的方法,那么编译器会报告错误。
l 现在,编译器就知道需要调用的方法的名字和参数类型。
3) 如果方法是private ,static,final的,或者是一个构造器,那么编译器能准确地判断应该调用哪个方法(static,final修饰符将在后面介绍)。这称为静态绑定。而对于其他的方法,要调用哪个方法只有根据隐式参数的实际类型来决定,并且在运行时使用动态绑定。
4) 当程序运行并且使用动态绑定调用方法时,虚拟机必须调用同a所指向的对象的实际类型相匹配的方法版本。假设实际类型为Dog(Animal类的子类),如果Dog类定义了方法m(int),那么该方法会被调用。否则,就在Dog的超类中搜寻方法m(int),以此类推。
l
l Object类
l Object类是Java语言中所有类的根。如果声明一个类的时候没有使用extends子句,编译器会自动加上“extends Object” 子句。也就是说,下面两个声明是等价的:
l public class Employee{
l …
l }
l
l public class Employee extends Object{
l …
l }
l
l 这样的技术,允许你从Object类中继承并覆盖几个很有用的方法。后面一一介绍Object类的所有方法的作用。
l
l =运算符与equals()方法
l ==只能判断基本类型的值和引用类型的地址相等不相等。
l equals()方法可以判断两个引用类型的值相等不相等。
Java.lang包中的Object类有public boolean equals(Object obj)方法。它也比较两个对象是否相等。仅当被比较的两个引用指向同一对象时,对象的equals()方法返回true。由于Object类是所有类的超类,所以每个类都有该方法,即使你不覆盖equals()方法,你的类也有该方法(从Object类里继承过来的)。对于你自己写的类,如果你要比较这个类产生的多个对象相等不相等,那么你必须的覆盖equals()方法。并且你这个类必须得有实例变量(属性)存在。因为实例变量是区分每个实例的唯一标志。
Object类的equals( )方法很少被使用,因为,多数情况下我们希望比较两个对象的内容,而不是判断两个引用是否指向同一对象。String类已经覆盖了equals()方法。所以两个String类型的对象可以用equals方法来比较两个String对象相等不相等。
例如:
String s1=new String(“hello”);
String s2=new String(“hello”);
l 方法s1.equals(s2)返回真,尽管s1和s2指向两个不同的对象。注意:上面两句代码其实是产生了三个对象,s1,s2分别引用了两个对象,还有一个对象是“hello”,虽然没变量引用,也是先在堆里面生成的。用完之后,由于没有变量来指向该“hello”对象,所以会被垃圾回收机制收掉。
l 再看一个例子:
l String s1=”person”;
l String s2=”person”;
l 那么 s1==s2返回true or false??
l 结果为true。s1和s2指向了同一个对象。当然s1.equals(s2)的结果也是为true的。 String这个类在jdk里面是唯一一个特殊类。它直接可以用两个双引号来构造它的一个对象。并且只要对象的值改变,会在堆里面寻找新的空间来存放改变后的值。如果原来的值没有变量再引用,那么也会被垃圾回收机制收掉。
l
l 下面的例子使用equals方法测试雇员的名字和生日:
l public Employee{
l
l private String name;
l private Mydate birthDate;
l private float salary;
l
l public Employee(String name, MyDate DoB, float salary){
l this.name = name;
l this.birthDate = Dob;
l this.salary = salary;
l }
l
l public boolean equals(Object o){
l boolean result = false;
l if( (o !=null) && (o instanceof Employee) ){
l Employee e = (Employee) o;
l if(name.equals(e.name) && birthDate.equals(e. birthDate) ){
l result = true;
l }
l }
l return result;
l }
l
l public int hashCode( ){
l return (name.hashCode( ) ^ birthDate.hashCode( ) );
l }
l }
l 我们覆盖了hashCode方法。这样做保证了相同的雇员对象有相同的hashCode。
l
l 下面的程序判断两个雇员对象引用是否相同:
l public TestEquals{
l
l public static void main(String[ ] args){
l Employee emp1 = new Employee(“Fred Smith”, new Mydate(14,3,1976), 25000.0F);
l Employee emp2 = new Employee(“Fred Smith”, new Mydate(14,3,1976), 25000.0F);
l
l if(emp1 ==emp2){
l System.out.println(“emp1 is identical to emp2”)’
l }else{
l System.out.println(“emp1 is not identical to emp2”)’
l }
l
l if(emp1.equals(emp2) ){
l System.out.println(“emp1 is equals to emp2”)’
l }else{
l System.out.println(“emp1 is not equals to emp2”)’
l }
l
l emp2 = emp1;
l System.out.println(“set emp2 = emp1”);
l if(emp1 ==emp2){
l System.out.println(“emp1 is identical to emp2”)’
l }else{
l System.out.println(“emp1 is not identical to emp2”)’
l }
l }
l }
l 执行结果为:
l emp1 is not identical to emp2
l emp1 is equals to emp2
l set emp2 = emp1
l toString( )方法
l toString方法被用来将一个对象转换成String表达式。当自动字符串转换发生时,它被用作编译程序的参照。例如:
Date now = new Date()
System.out.println(now)
l 将被翻译成:
System.out.println(now.toString());
l 对象类定义缺省的toString()方法,它返回类名称加“@”加它的散列码(通常情况下不是很有用)。
l 下面是Object类里toString()方法的定义:
l public String toString()
l {
l return tt.getClass().getName()+"@"+Integer.toHexString(tt.hashCode());
l //用16进制数表示散列码
l }
l 许多类覆盖toString()以提供更有用的信息。例如,所有的包装类覆盖toString()以提供它们所代表的值的字符串格式。甚至没有字符串格式的类为了调试目的常常实现toString()来返回对象状态信息。所以,如果你要想打印你的类产生的每个对象的信息,你的类必须覆盖toString()方法。
l
l Class类
程序运行时,Java运行时系统一直对所有的对象进行所谓的运行时类型识别。这项信息记录了每个对象所属的类。虚拟机通常使用运行时类型信息选折正确的方法去执行。不过,通过专门的Java类,我们也可以访问到这些信息。用来保存这些信息的类是Class类,它的名字也很容易让人混淆。Object类中的getClass()方法返回的就是Class类型的实例。
第一种获得Class类型的对象:
Cat c=new Cat();
Class class=c.getClass();
第二种获得Class类型的对象:
String str=”Cat”;
Class class=Class.forName(str);
如果类名保存在字符串中,而且能在运行时改变,那么我们可以使用该方法。当然str要是类或接口的名字才行,否则,forName()方法会抛出一个已检查异常。无论何时使用该方法,都应提供一个异常处理器。
第三种获得Class类型的对象:如果T是一个Java类型,那么T.class就代表了匹配的类对象。例如:
Class c1=Animal.class.
Class c2=int.class;
注意Class对象实际上描述的只是类型,而这类型未必是类。例如:int不是类,但是int.class是一个Class类型的对象。
虚拟机为每种类型管理一个独一无二的Class对象。因此,可以使用= =操作符来比较类对象。例如:
if(c.getClass()= =Cat.class) 返回为true。
包装类
在Java 中,除了八种基本类型之外,一切皆对象。由于这八种基本类型又对应有相应的类类型,所以说Java是纯面向对象的。Java编程语言不把基本数据类型看作对象。例如,在基本格式本身当中,数字、布尔及字符型数据都被看作是为了提高效率。Java编程语言提供包装类来将基本数据元素看作对象。这样的数据元素被包裹在创建于它们周围的对象中,每个Java基本数据类型在Java.lang包中都有一个相应的wrapper class。每个包装类对象封装一个基本值。
表6-2 包装类
基本数据类型 | 包装类 |
boolean | Boolean |
byte | Byte |
char | Character |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
可以通过将被包裹的值传递到合适的构造函数中来构造包装类对象。例如:
int pInt = 500;
Integer wInt = new Integer(pInt);
String myInt = “235”;
int theInt = new Integer(myInt).intValue();
下面的例子是对本章的总结。用到了继承,覆盖,对象的初始化等内容
public class TestInherite {
public static void main(String[] args) {
new LiSi();
}
}
class ZhangSan{
int weight=30; //体重
public ZhangSan(){
System.out.println(weight);
System.out.println(getWeight());
}
public int getWeight(){
return weight;
}
};
class LiSi extends ZhangSan{
int weight=20; //体重
public LiSi(){
}
public int getWeight(){
return weight;
}
};
请问下结果是多少?
结果为
30
0
分析: 该例子里面用到了继承,覆盖。在main()方法里只构造了子类的对象。按照对象初始化规则:
先对属性进行空间的分配并初始化为默认值。所以在构造LiSi这个对象的时候会进行两个weight属性的空间分配并初始化为默认值0。由于LiSi是子类所以先调用父类的构造器,那么在调用父类构造器之前先对ZhangSan的weight初始化为30。然后执行ZhangSan()里的代码。打印30,由于在第一个打印语句里面调用了getWeight()方法,这里用到了方法覆盖。会去调用子类的getWeight()方法来打印子类的weight属性值。由于这时候该值还没进行初始化,所以打印的是0
练习
写出一个公司里面员工发工资的简单系统:要求有总经理,经理,员工对象。有基本工资和奖金操作,最后打印出总经理,经理,员工的工资。