Java核心技术I——基础点拾掇(第五章)

来源:互联网 发布:淘宝上的客服怎么联系 编辑:程序博客网 时间:2024/04/29 15:20

第五章:继承
动态绑定:对象方法的执行过程:
(1)编译器查看对象的声明类型和方法名。
(2)接下来,编译器将查看调用方法时提供的参数类型。(重载解析overloading)
(3)如果是private方法、static方法、final方法或者构造器,那么编译器将可以准确地知道应该调用哪个方法,我们将这种调用方式称为静态绑定(static binding)。
(4)当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与x所引用对象的实际类型最合适的那个类的方法。(是当前类,还是当前类的超类)每次调用方法都要进行搜索,时间开销相当大。因此,虚拟机预先为每个类创建一个方法表(method table),其中列出了所有方法的签名和实际调用的方法。这样一来,在真正调用方法的时候,虚拟机仅查找这个表就行了。

阻止继承:final类和方法
有时候,可能希望阻止人们利用某个类定义子类。不允许扩展的类称为final类。(如果将一个类声明为final,只有其中的方法自动地成为final,而不包括域)
类中的特定方法也可以被声明为final。如果这样做,子类就不能覆盖这个方法(final类中的所有方法自动地成为final方法)。String类也是final类,这意味着不允许任何人定义String的子类。换而言之,如果有一个String的引用,它引用的一定是一个String对象,而不可能是其他类的对象。
如果一个方法没有被覆盖并且很短,编辑器就能够对它进行优化处理,这个过程称为内联(inlining)。例如:内联调用e.getName()将被替换为访问e.name域。

强制类型转换:
在Java中,每个对象变量都属于一个类型。对象变量的类型不等于所引用对象的类型。
将一个值存入变量时,编译器将检查是否允许该操作。将一个子类的引用赋给一个超类变量,编译器是允许的。但是将一个超类的引用赋给一个子类变量,必须进行类型转换,这样才能够通过运行时的检查。
一个良好的程序设计习惯:在进行类型转换之前,先检查一下是否能够成功地转换。这个过程简单地使用instanceof运算符就可以实现。

if (staff instanceof Manager) { }

(1)只能在继承层次内进行类型转换
(2)在将超类转换成子类之前,应该使用instanceof进行检查。
(一般情况下:应该尽量少用类型转换和instanceof运算符。)

受保护访问:
(1)仅对本类可见——private.
(2)对所有类可见——public.
(3)对本包和所有子类可见——protected.
(4)对本包可见——默认,不需要修饰符。

Object:所有类的超类
在Object中有几个只在处理线程时才会被调用的方法。
当然,Object类型的变量只能用于作为各种值得通用持有者。要想对其中的内容进行具体的操作,还需要清楚对象的原始类型,并进行相应的类型转换。
在Java中,只有基本类型(primitive types)不是对象。例如:数值、字符和布尔类型的值都不是对象。所有的数组类型,不管是对象数组还是基本类型的数组都扩展都扩展于Object类。(为什么不是Object[]数组?)
http://blog.csdn.net/lingzhou1/article/details/8526346

编写一个完美的equals方法的建议!P173

Arrays工具类:
一维数组:Arrays.toString(). 多维数组:Arrays.deepToString()
数组转换为List:Arrays.asList() (不能对这个list进行维护操作)

类型化与原始数组列表的兼容性:
使用类型转换并不能避免出现警告!
ArrayList result = (ArrayList) employeeDB.find(quey); // 将会得到另外一个警告信息,被告知类型转换有误。
这就是Java中不尽如人意的参数化类型的限制所带来的结果。鉴于兼容性的考虑,编译器对类型转换进行检查之后,如果没有发现违反规则的现象,就将所有的类型化数组列表转换成原始ArrayList对象。在程序运行时,所有的数据列表都是一样的,即没有虚拟机中的类型参数。因此,类型转换(ArrayList)和(ArrayList)将执行相同的运行时检查。
在这种情况下,不必做什么。只要在与遗留的代码进行交叉操作时,研究一下编译器的警告性提示,并确保这些警告不会造成太严重的后果就行了。
一旦能确保不会造成严重的后果,就可以用@SuppressWarnings(“unchecked”)标注来标记这个变量能够接受类型转换。

对象包装器与自动装箱:
int(基本类型) ——> Integer(包装器)
对象包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值。同时,对象包装器类还是final,因此不能定义它们的子类。
假设想定义一个整型数组列表。而尖括号中的类型参数不允许为基本类型。

ArrayList<int> list = new ArrayList<>();        // Wrong!ArrayList<Integer> list = new ArrayList<>();    // Right!

list.add(3); 将自动地变换成 list.add(Integer.valueOf(3));
**这种变换被称为自动装箱(autoboxing).
相反地,当将一个Integer对象赋给一个int值时,将会自动地拆箱。**
int n = lisg.get(i); 被编译器翻译成 int n = list.get(i).intValue();
在很多情况下,容易有一种假象,即基本类型与它们的对象包装器是一样的,只是他们的相等性不同。大家知道,==运算符也可以应用于对象包装器对象,只不过检测的是对象是否指向同一个存储区域。因此,下面的比较通常不会成立:

Integer a = 1000;Integer b = 1000;if( a == b) ...

然后,Java实现却有可能让它成立。如果将经常出现的值包装到同一个对象中,这种比较就有可能成立。这种不确定的结果并不是我们所希望的。解决这个问题的办法就是在两个包装器对象比较时调用equals方法。
注释:自动装箱规范要求boolean、byte、char <= 127,介于-128 ~127之间的short和int被包装到固定的对象中。例如,前面的例子中将a和b初始化为100,==对它们进行比较的结果一定成立。
装箱和拆箱是编译器认可的,而不是虚拟机。编辑器在生成累的字节码时,插入必要的方法调用。虚拟机只是执行这些字节码。

Integer a = 100;Integer b = 100;System.out.println(a == b);     // trueInteger c = 1000;Integer d = 1000;System.out.println(c == d);     // false

警告:有些人认为包装器类可以用来实现修改数值参数的方法,然后这是错误的。

public static void triple(int x)    // won’t work{ x = 3 * x; }public static void triple(Integer x) // won’t work{……}

问题是Integer对象是不可变的:包含在包装器中的内容能够不会改变。不能使用这些包装器类创建修改数值参数的方法。
如果想编写一个修改数值参数值得方法,就需使用在org.omg.CORBA包中定义的持有者(holder)类型:包括IntHolder、BooleanHolder等。
public static void triple(IntHolder x) // It will work
{……}

参数数量可变的方法:即“变参”方法。底层原理即是将多个参数自动装箱为对象类型的数组。(Object…参数类型与Object[]完全一样。)

枚举类:

public enum Size {SMALL, MEDIUM, LARGE };

实际上,这个声明定义的类型是一个类,它刚好有4个实例,在此尽量不要构造新对象。因此,在比较两个枚举类型的值时,永远不要调用equals,而直接使用“==”就可以了。

反射机制(分析类能力):
作用:(1)在运行中分析类的能力。
(2)在运行中查看对象。
(3)实现通用的数组操作。
(4)利用Method对象,这个对象很想C++中的函数指针。
Class类:
在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。然而,可以通过专门的Java类访问这些信息,保存这些信息的类被称为Class。

依据对象获取对应的类名:

Date d = new Date();Class cl = d.getClass();            // cl: 对象对应的Class对象String name = cl.getName();     // "java.util.Date"

依据类名获取对应的Class对象:

String className = "java.util.Date";Class cl = Class.forName(className);

获取Class类对象的三种方式:

Class cl1 = Date.class;Class cl2 = int.class;Class cl3 = Double[].class;

注意:一个Class对象实际表示的事一个类型,而这个类型未必一定是一种类。例如:int不是类,但int.class是一个Class类型的对象。
虚拟机为每个类型管理一个Class对象。因此,可以利用==运算符实现两个类对象的比较操作。例如 if (A.getClass() == B.getClass()) {}
利用newInstance()来快速地创建一个类的实例。例如:
e.getClass().newInstance(); // 调用默认构造器,如不存在,则抛出异常

反射机制最重要的内容——检查类的机构!(java.lang.reflect)
最重要的三个类:Field、Method和Constructor。还有Array、Modifier等类!

// 反射需深入了解!!!

继承设计的技巧:
(1)将公共操作和域放在超类。
(2)不要使用手保护的域。(建议用私有域)
因为protected机制并不能够带来更好的保护。第一:子类集合是无限制的,任何一个人都能够由某个类派生一个子类,并以编写代码直接访问protected的实例域。第二:在Java程序设计语言中,在同一个包中的所有类都可以访问protected域,而不管它是否为这个类的子类。
(3)使用继承实现“is-a”关系。
(4)除非所有继承的方法都有意义,否则不要使用继承
(5)在覆盖方法时,不要改变预期的行为。
(6)使用多态,而非类型信息。(两个同样行为的方法,只是因为参数类型不同。则可以使用多态来合并)
(7)不要过多的使用反射。
反射是很脆弱的,即编译器很难帮助人们发现程序中错误,因此只有在运行时才发现错误并导致异常。

0 0