@@@@@@@@@类和对象@@@@@@@@@@@@@@@@1 由类构造(construct)对象的过程称为创建类的实例(instance)
对象中的数据称为实例域(instance field),操纵数据的过程称为方法(method) 每个对象有一组特定是实例域值,这些值的集合就是这个对象的状态(state) 如果类的每个方法都不会改变其对象,这种类就是不可变的类。String类就是不可变不可变的类。 类名首字母大写;变量名和方法名首字母小写 -- 所有的java对象都是在堆中构造的;
-- 字符串常量,还有类的静态变量都是存储在data seg; -- 对象的引用存在stack(栈空间); -- 对象存在于heap(堆内存)。 2 类之间最常见的关系是
依赖dependence(use-a):Order订单类使用了Account账户类,因为Order对象需要
访问Account对象查看信用状态。所以一个类的方法操纵另一个类的对象,就是一个类依赖另一个类。 应该尽可能减少依赖,A不知道B的存在,那它就不用担心B的任何改变。这就是让类之间的耦合度最小。 聚合aggregation(has-a):一个A对象包含一些B对象
继承inheritance(is-a):A类拓展类B,类A不但包含从类B继承的方法,还会拥有一些额外的功能。
3 构造器 P110
构造器与类同名;每个类可以有一个以上的构造器;构造器可以有0个,1个或多个参数; 构造器没有返回值;构造器总是伴随着new操作仪器调用4 隐式参数 显式参数 this
public void raiseSalary(double byPercent) //定义Employee里面的一个方法raiseSalary {
double raise = salary * byPercent / 100; salary += raise; }
number007. raiseSalary(5); //调用这个方法 这个调用将执行下列指令
double raise = number007. salary * 5 / 100; number007. salary += raise;
raiseSalary方法有两个参数,第一个是是隐式参数,是出现在方法名前面的Employee类的对象 第一个是显式参数,位于方法名后面的括号中的数值。
在每一个方法中,关键字this表示隐式参数。比如可以这么写: public void raiseSalary(double byPercent) {
double raise = this. salary * byPercent / 100; this.salary += raise; }
5 克隆clone的需要 P113
不要编写返回引用可变对象的访问器方法。比如 class Employee
{ private Date hireDay; public Date getHireDay(){ return hireDay; } }
这样子的写法会破坏封装性,如 Employee harry = . . .;
Date d = harry.getHireDay();
double tenYearsInMilliSeconds = 10 * 365.25 * 24 * 60 * 60 * 1000; d.setTime(d.getTime() - (long) tenYearsInMilliSeconds); d和harry.hireDay 都引用同一个对象,对都引用同一个对象,对d调用更改器方法就可以改变harry的私有状态
如果需要返回一个可变数据域的拷贝,应该使用clone class Employee {
public Date getHireDay(){ return hireDay.clone();} }
6 基于类的访问权限
一个方法可以访问所属类的所有对象(不仅仅是调用这个方法的对象而已)的私有数据所属类的所有对象(不仅仅是调用这个方法的对象而已)的私有数据 class Employee{
public boolean equals(Employee other){ return name.equals(other.name); } }
典型的调用方式如下
if (harry.equals(boss)) . . .
这个equals方法能访问了harry的私有域(return 后面的那个name),它还能访问它还能访问boss的
私有域(other.name),它并没有写成other.getName()。因为boss也是Employee类的对象, 而Employee类的方法可以访问Employee类的任何一个对象的私有域。类的任何一个对象的私有域7 final实例域 P116
必须确保在每一个构造器执行后,这个域的值被设置了,而且之后的操作不能再修改它了。
final修饰符大都应用于基本类型域,或不可变类的域,比如String。如果用于可变的类的话,会造成
一些理解上的混乱。比如:private final Date hiredate;这里面仅仅是存储在hiredate变量中的对象引用对象引用 在对象构造之后不能改变,并不意味着hiredate对象是一个常量,任何方法都可以对hiredate引用的对象 调用setTime更改器。8 静态域 static
静态域不属于任何的类的实例(对象),它属于类。它是在类被加载的时候就被加载了的,并不是之后new 对象才生成的。
9 静态常量 static final
public class Math //通过Math.PI这种形式获得常量
{public static final double PI = 3.14159265358979323846;. . .}
如果上面的语句省略了static,那PI就变成了一个实例域,final实例域,需要通过Math类的对象才能访问PI。 System.out其实也是一个静态常量。 public class System { . . .
public static final PrintStream out = . . .; . . . }
由于每个类对象都可以对公有域进行修改,所以最好不要将域设计为public。但是公有常量(即final域) 却没有问题。因为out被声明为final,所以不允许再将其他打印流赋给它了。 System.out = new PrintStream(...);//错误的10 静态方法 P117
用static声明的方法为静态方法,在调用该方法时,不会将对象的引用传递给它,所以在static方 法中不可访问非static的成员。(静态方法不再是针对于某个对象的调用,所以不能访问非静态的成员
成员);这里还要注意一点静态方法不能访问非静态的域,不能想当然反过来认为非静态的方法不能 访问静态域,别把逻辑搞乱了,想当然了。
可以通过对象引用或类名(不需要实例化)访问静态域,对象引用或类名(不需要实例化)访问静态域,建议用类名来访问 在下面两种情况下使用静态方法:
-- 一个方法不需要访问对象状态,其所需参数都是通过显式参数提供(如 Math.pow); -- 一个方法只需要访问类的静态域
11 java采用是的按值调用,方法得到的是所有参数值的一个拷贝而已。方法不能修改传递给它的任何 参数变量的内容,方法调用完,这些拷贝就不再使用了。
方法参数有两种类型:一种是基本数据类型,一种是对象引用。方法不能修改前者,也不能改变后者, 但是能改变它所引用的对象的状态。所以还是理解好 对象引用 和 对象。对象引用不过是指示了 它所引用的对象所存储的地址。
12 如果在构造器中没有显式的给域赋初值,那将会被自动赋值,数值为0,布尔值为false,对象引用为null
但是不建议这么做,尽量明确对域进行初始化,提高代码可读性。上面这点也是域与局部变量的主要不同点。域与局部变量的主要不同点。 必须明确地初始化方法中的局部变量。13 显式的给域初始化 class Employee
{ private String name = \"\"; . . .
} //在执行构造器前,会先执行赋值操作。当一个类的所有构造器都希望把相同的值赋予某个特定的实例域时,这种 方式特别有用(我个人觉得还是不要显式的给域初始化,虽然会让构造器的代码看起来很累赘重复,但是不容易看漏)14 用this调用另一个构造器 见前面第4条 P129
this引用方法的隐式参数,如果构造器的第一个语句如 this(。。。),那这个构造器将调用同一个类的另一个构造器。15 初始化块
前面提到了两种初始化数据域的方法: 在构造器中设置值;在声明中赋值;实际上还有第三种办法。叫初始化块 在一个类中可以包含多个代码块,只要构造类的对象,这些块就会被执行。如 class Employee
{ private static int nextId; private int id; private String name;
{ // object initialization block 初始化块 id = nextId; nextId++; }
public Employee(String n, double s) { name = n; salary = s; } public Employee() { name = \"\"; salary = 0; } }
调用构造器的具体处理步骤:
--所有数据域被初始化为默认值(0,false,null);
--按照在类声明中出现的次序,一次执行所有的域初始化语句域初始化语句和初始化块初始化块; --如果构造器第一行调用了第二个构造器,则执行第二个构造器主体; --执行这个构造器的主体。16 静态域的初始化
两种办法:用静态域初始化语句(private static int nextId = 1;)或者静态初始化块。静态初始化块。 static{ //功能是将雇员ID的起始值赋予一个小于10000的随机整数。
Random generator = new Random(); //构造一个新的随机数生成器。
nextId = generator.nextInt(10000); // int nextInt(int n) 返回一个0~n-1之间的随机数。 //前面提到的Math.random()方法返回一个0到1(包含0,不包含1)的随机浮点数。
}
在类第一次加载类第一次加载的时候,将会进行静态域的初始化。与实例域一样,除非将它们显式地设置成其他值, 否则默认的初始值是0,false,null。所有的静态初始化语句以及静态初始化块都将依照定义的顺序执行。17 import不仅可以导入类,还能导入静态方法和静态域。 import static java.lang.System.*;
out.println(\"Goodbye, World!\"); // i.e., System.out exit(0); // i.e., System.exit
另外还可以导入特定的方法或域 import static java.lang.System.out;
虽然 sqrt(pow(x, 2) + pow(y, 2)) 看起来比Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))更清晰,但是本人不建议这么做。18 类路径 jar P140
19文档注释 P143 内容比较多,看pdf吧
javadoc工具,用它可以由源文件生成一个html文档。20 类的设计技巧
-- 一定要保证数据私有
--一定要对数据初始化,不要依赖系统默认值 --不要在类中使用过多的基本类型
--不是所有的域都需要独立的域访问器和域更改器 --讲职责过多的类进行分解为更简单独立的类 --类名和方法名的命名要体现它们的职责
@@@@@@@@继承@@@@@@@@@@@@21 子类的构造器 P153
--子类的构造的过程中必须调用其超类的构造方法。因为子类的构造器不能访问超类的私有域因为子类的构造器不能访问超类的私有域,所以必须利用超类的 构造器对超类的私有域进行初始化。
--子类可以在自己的构造方法中使用super(argument_list)调用超类的构造方法。可以使用this(argument_list) 调用本类的另外的构造方法。如果调用如果调用super,必须写在子类构造方法的第一行;构造参数既可以传递给超类,必须写在子类构造方法的第一行(super) 的构造器,也可以传递给本类(this)的其他构造器。
--如果子类的构造方法中没有显示地调用超类构造方法,则系统默认调用超类无参数的构造方法;--如果子类构造方法中没有显式调用基类构造方法,超类中又没有无参的构造方法,则编译出错。
22 重写方法必须和被重写方法具有相同的方法名称,参数列表,返回类型必须与被重写方法的相同或是其子类(有点问题,见P159)。 重写的方法不能使用比被重写的方法更严格的访问权限。
重写方法不能抛出新的异常,或者超过父类范围的异常,但是可以抛出更少,更有限的异常,或者不抛出异常。
超类的private方法不能被重写,因为它的访问权限仅限在本类。方法不能被重写(事实上private,stataic,final类型的方法都不能被重写! 对这些方法的调用叫静态绑定静态绑定!)。另外,default, protected方法是可以被重写public class Test {
public static void main (String[] args) { //Animal h = new Horse(); Horse h = new Horse(); h.eat(); } }
class Animal { private void eat(){ System.out.println (\"Animal is eating.\"); } }
class Horse extends Animal{ public void eat(){ System.out.println (\"Horse is eating.\"); } }
这段代码是能通过编译的。Animal类的eat()方法不能被继承,因此Horse类中的eat()方法是一个全新的方法,不是重写也不是重载,只是一个只属于Horse类的全新全新的方法!这点让很多人迷惑了,但是也不是那么难以理解。
main()方法如果是这样: Animal h = new Horse(); //Horse h = new Horse(); h.eat();
编译器会报错,Horse类的eat()方法是public的啊!应该可以调用啊?!请牢记,多态只看父类引用的方法,而不看子类对象的方法!这里没有重写方法,就不构成多态和动态绑定,对象引用h去子类中找被重写的eat方法,但是找不到,编译出错。
23 super this
--this 的 两个用途:引用隐式参数;调用该类的其他的构造器--super的两个用途:调用超类的方法调用超类的方法;调用超类的构造器class Manager extends Employee
{private double bonus; //超类有 name,salary,hireDate三个私有域;子类另外再定义一个私有域bonuspublic void setBonus(double b){bonus = b;}}
然后Manager的getSalary()方法需要覆盖Employee的。普通工人的工资只有单纯工资,经理的还要加上奖金bonus。如果写成:public double getSalary(){return salary + bonus; // won't work }
这个方法不能运行,因为Manager类的getSalary方法不能够直接访问超类的私有域,不能够直接访问超类的私有域,想想前面21条那张修饰符的权限图吧,子类不能访问超类中private修饰的域(私有域)。这个时候这个时候super和域的访问器的作用就体现出来啦
public double getSalary(){
double baseSalary = super. getSalary();return baseSalary + bonus;}
另外思考一下,如果去掉super会是什么后果:会发现它依然不能运行,因为超类有getSalary()方法,子类自己也有一个getSalary()方法(就是正在实现的这个方法),这将会导致无限次的调用自己!!!
24 \"is-a\" 多态 动态绑定
在考虑是否使用继承时,最简单的判断规则是“is-a\",它表明子类的每个对象也是超类的对象,每个经理都是雇员,反过来并不是每一个雇员都是经理。\"is-a\"规则的另外一种表述(称呼)是置换法则置换法则,它表明出现超类对象的任何地方都可以用子类对象置换。
一个对象变量可以指示多种实际类型的现象称为多态(polymorphism)。在运行时能够自动地选择调用哪个方法的现象称为动态绑定(dynamic binding)
--一个超类 的引用类型变量可以“指向”其子类的对象。
--一个超类的引用不可以访问其子类对象新增加的成员(属性和方法)。
--可以用 引用变量 instanceof 类名 来判断该引用变量所“指向”的对象是否属于该类或者该类的子类。
--多态存在的三个必要条件:要有继承,要有重写,超类多态存在的三个必要条件:要有继承,要有重写,超类引用指向子类对象。引用指向子类对象。--注意有一个原则:编译器只允许调用在编译器只允许调用在类中类中声明声明的方法。的方法。使用了什么引用,编译器就会只调用引用类所拥有使用了什么引用,编译器就会只调用引用类所拥有的方法。如果超类引用调用子类特有的方法,编译器会报错。也就是说,调用方法时,编译器只看引用类型,的方法。调用方法时,编译器只看引用类型,而不是对象类型。先查看该引用类型的是否拥有该方法,如果没有的话,那就报错,有的话,再结合它实际引用而不是对象类型。的对象类型,子类的重写方法等去调用正确的方法。例子: Manager boss = new Manager(\"Carl Cracker\boss.setBonus(5000);Employee[] staff = new Employee[3];staff[0] = boss; //变量staff[0]与boss引用了同一个对象,但是编译器将引用了同一个对象但是编译器将staff[0]看成Employee对象。对象 //意味着可以这样调用 boss.setBonus(5000);,但是不能这样调用staff[0].setBonus(5000); // 调用方法时,编译器只看引用类型,而不是对象类型。staff[1] = new Employee(\"Harry Hacker\staff[2] = new Employee(\"Tony Tester\for (Employee e : staff) System.out.println(e.getName() + \" \" + e.getSalary());输出如下:Carl Cracker 9000.0 //4000+5000 这个例子就是置换法则置换法则优点的提现Harry Hacker 4000.0Tommy Tester 4000.0http://blog.sina.com.cn/s/blog_7540bf5f0100t1lv.html25 调用对象方法的详细过程。 P15826 关于超类数组的一些问题。
这个问题再联系前面24条的那个例子思考一下。有些乱,但是应该跟数组的存储方式有关,staff和managers这两个变量引用的是同一个数组,但是其本质上只是一个地址,那个数组存储位置开始的那个地方的地址。数组存储位置开始的那个地方的地址。
27 阻止继承 final类 final方法
--定义类时使用final修饰符表明是final类,其不允许扩展,阻止被继承,不能成为超类
--final类中的所有方法自动地成为final方法,当然只是方法会这样,域不会自动变成final域的啦。 --如果超类中的方法被声明为final,那它的子类就不能覆盖这个方法。
比如String就是一个final类,它的声明如下:
public final class String extends Object implements Serializable, Comparable --只能在继承层次内进行类型转换,在将超类转换成子类前,应该用instanceof检查 接着看第24条的例子,将一个超类的引用赋给一个子类变量,必须进行类型转换。但是如果试图在继承链上进行 向下的类型转换,并且“谎报”有关对象包含的内容,会出错。 Manager boss2 = (Manager)staff[0];// 可以,因为staff[0]引用的对象本质上时Manager类 Maneger boss3 = (Manager)staff[1];// ERROR,会报ClasssCastException异常。 为了防止出错报异常,使用instanceof运算符查看是否能转换成功。 if (staff[1] instanceof Manager) { boss2 = (Manager) staff[1]; } 可以看到instanceOf方法( x instanceOf y)是很符合继承的 “is-a” 原则的。被检验的引用类型的x的真正对象类型真正对象类型 只要是y或者y的子类,该方法就返回的子类true。 一般来说,应该尽量少用类型转换和instanceof运算符,在示例中,由于有动态绑定机制,Maneger对象和 Employee对象都能正确的调用getSalary方法。只有需要在使用Manager特有的方法(setBonus)才需要进行类型转换, 出现这种情况,就要思考重新设计超类了,考虑Employee类中添加setBonus方法才是正经事。29 抽象类 --用abstract关键字来修饰一个类时,这个类叫做抽象类;用sbstract来修饰一个方法时,该方法叫做抽象方法。 --含有抽象方法的类必须被声明为抽象类,但是类即使不含抽象方法也可以把类声明为抽象类 --抽象类还可以包含具体的数据和具体的方法,当然许多程序员认为不应该这么做。 --抽象方法充当占位的角色,只需声明声明,而不需要定义定义(实现)。它们的具体实现在子类中。而继承扩展抽象类可以有两种选择: 一种是在子类中实现部分抽象方法或抽象方法也不实现,这样就必须将子类也标记为抽象类;另一种是定义全部的抽 象方法,这样一来子类就可以不是抽象的了 (毕竟类即使不含抽象方法,也可以将类声明为抽象类,蛋疼的规则)。 --抽象类不能被实例化。比如Person是个abstract类, 表达式 new Person(\"yangxz\") 是错误的。但是注意:可以定义是错误的可以定义 一个抽象类的对象变量(引用),但是它只能引用非抽象子类对象。如:,但是它只能引用非抽象子类对象 person p = new Student(\"yangxz\计算机系\");说 到底 java设计者还是想在抽象类身上使用像非抽象的超类那样使用多态啊,动态绑定啊这些超级技能。 例子: P166 抽象类Person声明了抽象方法 getDescription,具体子类Employee,Student分别各自定义(实现)了getDescription方法 Person[] people = new Person[2]; people[0] = new Employee(. . .); people[1] = new Student(. . .); for (Person p : people) System.out.println(p.getName() + \ 可能会对 p.getDescription()有疑问,会认为这是个只声明没定义(实现)的方法。但是要记得由于不能构造抽象类Person 的对象,所以变量p永远不会引用Person对象,而是引用Employee,Student这些具体的子类对象,这些对象都定义了 getDescription方法。在这里面Person包括数组Person[],都只是定义了Person类的对象变量而已,并没有实例化Person类 包括那句 new Person[2];(看看前面写的 表达式 new Person(\"yangxz\") 是错误的。),这个数组只是准备要存2个抽象 类的对象变量(引用)而已。 进一步思考是否可以省略Person超类中的抽象方法,而仅在Employee,Student中定义getDescription呢。如果这样的话 就不能通过变量p调用 getDescription方法了,编译器只允许调用在编译器只允许调用在类中类中声明声明的方法(注意是声明,而不是实现或者说定义)的方法(注意是声明,而不是实现或者说定义)。 这应该是涉及到一些编译器的工作原理,总之它还是遵循了多态存在的三个必要条件:要有继承,要有重写(父类没要有重写(父类没 有getDescription方法的话,哪里来的重写)),超类引用指向子类对象。感觉这一切的设计都是围绕着多态和动态绑定 这些超级技能而存在。30 权限 P168 前面20条的图提到了java用于控制可见性的\"4\"个访问修饰符,当然实际只有3个啦,default是默认,不需要修饰符。 关于protected,受保护的访问。设置受保护的域,是违背了OOP提倡的数据封装原则,相对来说受保 护的方法更有实际意义,表明子类(可能很熟悉祖先类)得到信任,可以正确的使用这个方法,而其他类则不行。31 Object是所有类的超类,java中只有基本类型不是对象。 P169 所有的数组类型,不管是对象数组还是基本类型的数组都扩展于Object。 Object类型的变量可以引用任何类型的变量,这个很变态!!! class A{ private String n; public void setN(String n){this.n = n;} public String getN(){return this.n;} public static void main(String[] args){ A[] a =new A[10]; a[0] = new A(); a[0].setN(\"nn\"); Object b = a; //这个语句是完全不会报错的。神奇的是,b并不是被声明一个数组变量(并不是被声明一个数组变量Object[] b = a;) //如果写成 A b =a;是会报错的, // Object[] c= (Object[])b; A[] d = (A[])c; //这样子写跟下面的语句写效果是一样的 A[] d = (A[])b; System.out.print(Arrays.toString(d));//输出 [Aa@c17164, null, null, null, null, null, null, null, null, null] //这里的输出打印只是数组里面各个A对象引用的值而已。。。并没有用getN()的方法去输出。。。。 } } 32 equals 方法 这个比较复杂,建议看pdf P170 Object 类中的 equals 方法对于任何非空非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,此方法才返引用同一个对象 回 true(x == y 具有值 true)。 但是对多数类来说这个判断意义不大,我们更加愿意认为只要两个对象两个对象的状态状态相等 就可以认为这两个对象是相等的。所以新建类时,一般需要重写equals方法。而当equals方法被重写时,通常有必要 重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。 完美的equals方法:方法 ClassName.equals(otherObject) 1)显式参数命名为otherObject,稍后需要将它转换成另外一个叫做other的变量 2)检测this与otherObject是否引用同一个对象 if (this == otherObject) return true; 3)检测otherObject是否为null。 if (otherObject == null) return false; 4)比较this与otherObject 是否属于同一个类。如果equals的语义在每个子类中有所修改,就用getClass检测 if (getClass() != otherObject.getClass()) return false; 如果所有的子类都拥有统一的语义,就用instanceof检测 if (!(otherObject instanceof ClassName)) return false; 5)将otherObject 转换为相应的类的类型变量。 ClassName other = (ClassName) otherObject 6)比较域,使用 == 比较基本类型,使用equals比较对象域。 return field1 == other.field1 && Objects.equals(field2, other.field2) && . . .; 如果在子类中重新定义,就要在其中包含调用super.equals(otherObject). 注意:上面第4点 getClass和instanceof选择的问题(看由超类还是子类相等的判定): 子类决定就用getClass,超类决定就用instanceof 可以看P177的例子代码 注意上面图片最后一句话 上面第6点,见core java 第九版的书:有一个第九版API说明 java.util.Objects : static boolean equals(Object a, Object b) 这是一个java7才有的静态静态方法 如果a和b都为null,返回true;如果其中一个为null,则返回false;否则返回a.equals(b)。 在core java第八版的书中,上面第第八版6点是这么写的: return field1 == other.field1 && field2.equals(other.field2) && . . .; java7中新定义的这个静态方法主要是考虑到了对象域为null的情况,特别是两个都为null的时候! 在java6中的Object类的equals方法(非静态方法)是用于非空非空对象引用的比较的,java6中API是这么写的 ------------------------ equals 方法在非空对象引用上实现相等关系: 自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。 对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。 传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。 一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象 上 equals 比较中所用的信息没有被修改。 对于任何非空引用值 x,x.equals(null) 都应返回 false。 ------------------------- 上面的文字没有提到x为null这种情况,比如在比较两个这种情况Employee对象时,如果两个对象的 name域都为null时,注意name域是String 类型的,比如: String a = \"11\"; String b = null; System.out.print(a.equals(b)); // 输出false a = null; System.out.print(a.equals(b)); //报错,抛异常 NullPointerException,空指针异常。而且这样的代码是能通过编译的, 只是有个警告 所以java7新增的这个静态方法还是有道理的。虽然现实中这种对象域,比如String类型的对象域,可以赋个空字符串,不至于傻得赋 个null。 注意:注意public boolean equals(Employee other) 这样子声明是有问题的。这并不会重写equals方法,而是另外定义了一个方 法。 应该写成 public boolean equals(Object other) 这样子才能重写超类Object的equals方法。 33 hashCode方法 由于hashCode方法定义在Object类中,因此每个对象都有一个默认的散列码,其值为对象的存储地址其值为对象的存储地址。 String类重写了hashCode方法,字符串的散列码是由内容导出的字符串的散列码是由内容导出的,它使用下列算法计算散列码: int hash = 0; for (int i = 0; i < length(); i++) hash = 31 * hash + charAt(i); 注意,StringBuffer类中没有定义hashCode方法(也就是没有重写它啦),所以它的散列码是由Object类默认 的hashCode方法导出的对象存储地址。 hashCode方法应该返回一个整形数值(可以是负数),并合理组合实例域的散列码,以便让各个不同的对象产生的 散列码更加均匀(虽然概率很小,但是不同对象的散列码还是有可能相等的,没办法就是刚好碰上了)。例如: class Employee { public int hashCode() { return 7 * name.hashCode() + 11 * new Double(salary).hashCode() //salary是基础类型呀,木有基础类型呀,木有hashCode方法, //这是类对象才有的呀,所以进行封装成对象呀 + 13 * hireDay.hashCode(); } 在java7中做了改进,首先最好使用首先最好使用null安全的静态方法Object.hashCode(毕竟引用值如果为null的话,执行 hashCode方法是会报空指针异常的,就像前面equals那样,所以java7这些修改还是很不错的)。 如果其参数为null,这个方法会返回0,否则返回对参数调用hashCode的结果。 java7还新定义了一个方法Object.hash,很厉害的。这个方法会对各个参数调用Object.hashCode,并 组合这些散列值。 public int hashCode() { return Objects.hash(name, salary, hireDay); } //注意上面的salary,它竟然木有先封装,可能再hash方法里面会进行转换吧,看看源代码 如果存在数组类型的域,可以使用静态方法Array.hashCode方法计算一个散列码。 34 toString 方法 P176 Object类定义了toString方法,用来打印输出对象所属的类名和散列码 System.out.println(System.out);//输出 java.io.PrintStream@2f6684 //因为PrintStream类设计者没覆盖重写toString方法,所以直接调用Object的。 建议自定义的类,要覆盖重写toString方法,日志时会用到的啦。大多数的toString方法的格式:类的名字, 随后是大括号括起来的域值。 public String toString() {return getClass().getName() + \"[name=\" + name + \ + \ + \"]\"; } 子类设计toString方法时,将子类域的描述添加进去就行。 class Manager extends Employee {public String toString() {return super.toString() + \"[bonus=\" + bonus + \"]\"; } 随处可见toString方法的原因是:只要对象引用与一个字符串(空字符串啥的也是好好的)通过操作符\"+\"连接起来,java编译就会自动 调用toString方法。所以 \"\"+x 是很不错的,前面提到的x是基本类型也是ok的,虽然基本类型的话不是调用toString 当然甚至可以省略掉这个字符串,如果用 System.out.println(x); 警告,:悲催的是,数组没有重写toString方法。比如 int[] luckyNumbers = { 2, 3, 5, 7, 11, 13 }; String s = \"\" + luckyNumbers; //输出 [I@1a46e30 其中 [I 表明是一个整形数组 前面提到还有一种办法就是静态方法 Arrays.toString String s = Arrays.toString(luckyNumbers); //输出 [2, 3, 5, 7, 11, 13] 多维数组的话就用 Arrays.deepToString(x). -------关于 equals hashCode 最后可以看看 P177的程序例子public class EqualsTest{ public static void main(String[] args) { Employee alice1 = new Employee(\"Alice Adams\ Employee alice2 = alice1; Employee alice3 = new Employee(\"Alice Adams\ Employee bob = new Employee(\"Bob Brandson\ System.out.println(\"alice1 == alice2: \" + (alice1 == alice2)); System.out.println(\"alice1 == alice3: \" + (alice1 == alice3)); System.out.println(\"alice1.equals(alice3): \" + alice1.equals(alice3)); System.out.println(\"alice1.equals(bob): \" + alice1.equals(bob)); System.out.println(\"bob.toString(): \" + bob); Manager carl = new Manager(\"Carl Cracker\ Manager boss = new Manager(\"Carl Cracker\ boss.setBonus(5000); System.out.println(\"boss.toString(): \" + boss); System.out.println(\"carl.equals(boss): \" + carl.equals(boss)); System.out.println(\"alice1.hashCode(): \" + alice1.hashCode()); System.out.println(\"alice3.hashCode(): \" + alice3.hashCode()); System.out.println(\"bob.hashCode(): \" + bob.hashCode()); System.out.println(\"carl.hashCode(): \" + carl.hashCode());/* alice1 == alice2: true //引用了同一个对象alice1 == alice3: false //引用变量不相等 alice1.equals(alice3): true //两个对象,引用变量不相等alice1.equals(bob): false bob.toString(): Employee[name=Bob Brandson,salary=50000.0,hireDay=Sun Oct 01 00:00:00 CST 1989]boss.toString(): Manager[name=Carl Cracker,salary=80000.0,hireDay=Tue Dec 15 00:00:00 CST 1987][bonus=5000.0]carl.equals(boss): falsealice1.hashCode(): 377780067 alice3.hashCode(): 377780067 //两个对象,hashcode相同,因为字符串的散列码是由内容导出的,内容相同,hashcode当然相 同 bob.hashCode(): 955285015carl.hashCode(): 386513600*/ } } ----------------import java.util.Date; import java.util.GregorianCalendar;import java.util.Objects;public class Employee{ private String name; private double salary; private Date hireDay; public Employee(String n, double s, int year, int month, int day) { name = n; salary = s; GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day); hireDay = calendar.getTime(); } public String getName() { return name; } public double getSalary() { return salary; } public Date getHireDay() { return hireDay; } public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; } public boolean equals(Object otherObject) { if (this == otherObject) return true; if (otherObject == null) return false; if (getClass() != otherObject.getClass()) return false; Employee other = (Employee) otherObject; return Objects.equals(name, other.name) && salary == other.salary && Objects.equals(hireDay, other.hireDay); } public int hashCode() { return Objects.hash(name, salary, hireDay); } public String toString() { return getClass().getName() + \"[name=\" + name + \ + \"]\"; }} ------------------------------public class Manager extends Employee{ private double bonus; public Manager(String n, double s, int year, int month, int day) { super(n, s, year, month, day); bonus = 0; } public double getSalary() { double baseSalary = super.getSalary(); return baseSalary + bonus; } public void setBonus(double b) { bonus = b; } public boolean equals(Object otherObject) { if (!super.equals(otherObject)) return false; Manager other = (Manager) otherObject; // super.equals checked that this and other belong to the same class return bonus == other.bonus; } public int hashCode() { return super.hashCode() + 17 * new Double(bonus).hashCode(); } public String toString() { return super.toString() + \"[bonus=\" + bonus + \"]\"; }} 35 对象包装器与自动装箱和拆箱(也称自动打包和解包) Integer, Long, Float, Double,Short, Byte, Character, Void, Boolean. (前六个类派生与公共的超类Number),这些类称为 包装器。 对象包装器是不可变的,一旦构造了包装器,就不允许更改包装其中的值,这一点像这一点像String一样,而且对象包装器类一样, 还是final,所以不能有子类。 泛型数组列表的类型参数不允许是基本类型,又想在里面存储基本类型的值,包装类这时就能用上啦。 ArrayList list.add(3);//这个语句不会报错,因为编译器会把它自动变成 list.add(Integer.valueOf(3)); 自动装箱(也称自动打包) int n = list.get(i);// /这个语句不会报错,因为编译器会把它自动变成 int n = list.get(i).intValue(); 自动拆箱(也称自动解包) 上面这种机制叫自动装箱和拆箱(也称自动打包和解包),主要是为了代码简洁而已啦主要是为了代码简洁而已啦 甚至这种机制能在算术表达式中使用: Integer x = 3; //不用写成Integer x =Integer.valueOf(3),方便好写多啦 x++; 装箱和拆箱是编译器认可处理的,跟虚拟机木有关系的呀。 包装类还有些特殊用处,比如想把字符串转为整形的话,用Integer的静态方法静态方法parseInt int x = Integer.parseInt(s); //s为字符串 36 参数数量可变的方法(变参方法) P190 Y(Type...values){} //变参方法可以为参数指定为任意类型 Type可以是基本类型,也可以是类,参数的个可以是基本类型,也可以是类, 数可以为0~n(包括0的呀),其实编译器对这些参数会进行处理,把它们先变成其实编译器对这些参数会进行处理,把它们先变成相应类型的数组形参相应类型的数组形参,并在编 译出的class文件里作上一个记号,表明这是个实参个数可变的方法。 public static double max(double... values) { double largest = Double.MIN_VALUE; for (double v : values) if (v > largest) largest = v; //用for each 来循环,其实完全是把values看成是数组的 return largest; //事实上,用普通for循环一样可以的。for(int i = 0; i < values.length; i++) {...values[i]...} } double m = max(3.1, 40.4, -5); // 编译器将 new double[] { 3.1, 40.4, -5 } 传递给max方法 可变参数是兼容数组类参数的,但是数组类参数却无法兼容可变参数。 比如上面可以直接写 double m = max( new double[]{3.1,40.4,-5} );//直接传递了一个数组作为参数。 但如果是数组为形参的方法,是不能给它传递多个参数(相当于找不到相应的方法来调用啦)。 一个方法只能有一个可变参数,可变参数类型必须作为参数列表的最后一项 能匹配定长的方法,那么优先匹配该方法。含有不定参数的那个重载方法是最后被选中的。能匹配定长的方法,那么优先匹配该方法。含有不定参数的那个重载方法是最后被选中的 重载方法的时候,如果调用的方法可以和两个可变参数的匹配,则编译是不会通过的啦。尽量少在可变参数的方法使用重载啦。 我们常用的格式化输出方法就是典型的可变参数方法。 public class PrintStream { public PrintStream printf(String fmt, Object... args) { return format(fmt, args); } } 调用的时候如 System.out.printf(\"%d\ System.out.printf(\"%d %s\ 特别是这里还使用到了自动装箱,把传递进来的基本类型转换成对象,这里对n会处理: new Integer(n)37枚举类 枚举类其实是为了弥补静态常量的不足而设计的,主要是安全性和可读性安全性和可读性方面。它的使用方式很像静态常量 枚举类更多是用在那些固定数量的常量时,比如一年只有四个季节。 所有的枚举类型都是Enum类的子类,所以枚举类型不能再继承其它类了。 常规定义: public enum Season { SPRING, SUMMER, AUTUMN, WINER; } 枚举类型跟类的定义不一样,它使用关键字enum,枚举类可以单独定义在一个文件中,也可以嵌套在其它类中 SPRING, SUMMER, AUTUMN, WINER这四个值叫做枚举值(枚举常量)。 还有一个隐含的叫做枚举常量的位置的值。它从0开始计数。这里SPRING这个枚举值的位置为0 。 复杂一些的定义:枚举类型还可以添加构造器,域和方法 public enum Season { //跟类的定义很像,只是关键字换成了enum SPRING(\"11春天\"), SUMMER(\"22夏天\"), AUTUMN(\"33秋天\"), WINER(\"44冬天\"); //调用了下面的私有类型的构造 器!!! private String seasons; privateSeason(String seasons) { this.seasons= seasons; } //私有类型的构造器私有的构造器 public String getSeasons() { return seasons; } } 它不能有public的构造函数,没办法新建一个enum的实例(这样子更加符合静态常量的特征,直接跟由类支配,不依托于对象)。 当然这个private的构造器能够在枚举类的内部使用内部使用,在定义的时候在定义的时候(SPRING(\"11春天\"), SUMMER(\"22夏天\"),...),这个私有构造 器就被使用了被使用了。 常用的方法:toString , Enum.valueOf , ordinal ,compareTo ,values toString : String s = Season.SPRING.toString(); //这方法会返回字符串 \"SPRING\看起来就是静态常量调用的方式,类 似Math.PI Enum.valueOf: toString 方法的逆方法是Enum的静态方法valueOf :(所有的枚举类型都是Enum类的子类) Season a = Enum.valueOf (Season.class,\"SPRING\"); //将a设置成Season.SPRING (Season.class这个写法参见 反射) ordinal : ordinal 方法返回enum声明中枚举常量的位置,int c = Season.SPRING.ordinal();// 3 get取值器: 方法getSeasons ,这个方法不是从Enum继承来的,是子类自己新添加的,是返回调用private构造器时的那个形参的值。 String d = Season.SPRING.getSeasons ();// 返回字符串 \"11春天\" compareTo: int compareTo(E other) 如果枚举常量出现在other之前,则返回负值;如果this==other,返回0;否则返回正值。 枚举常量的出现次序在enum声明中给出了 values 每个枚举类型都有一个静态静态方法 values ,它将返回一个包含全部枚举值的数组: Season[] values = Season.values(); 这个数组包含了元素Season.SPRING Season.SUMMER Season.AUTUMN Season.WINER 最后,比较两个枚举类型的值的时候不要使用equals,直接用 == 按照静态常量去理解啊。import java.util.*;public class Enumtest{ public static void main(String[] args) { String input = \"WINER\"; Season aa = Enum.valueOf(Season.class, input); System.out.println(\"Season=\" + aa.toString()); // Season=WINER int c = aa.ordinal(); System.out.println(\"Season=\" + c); // Season=3 System.out.println(\"Season=\" + aa.getSeasons()); //Season=44冬天 System.out.println(\"Season=\" + Season.WINER.getSeasons()); //Season=44冬天 if (aa == Season.WINER) System.out.println(\"相等\"); //能够输出 相等 // 比较两个枚举类型的值的时候不要使用equals,直接用 == System.out.println(aa.compareTo(Season.SUMMER)); // 2 Season[] ss = Season.values(); for(Season s :ss) System.out.println(s); //换行输出 SPRING SUMMER AUTUMN WINER System.out.println(ss[1]); //SUMMER }} enum Season { SPRING(\"11春天\"), SUMMER(\"22夏天\"), AUTUMN(\"33秋天\"), WINER(\"44冬天\"); //调用了下面的私有类型的构造器 private String seasons; private Season(String seasons) { this.seasons= seasons; } //私有类型的构造器 public String getSeasons() { return seasons; } } 38 继承设计的技巧 P212 --将公共操作和域放在超类 --不要使用受保护的域。 子类数量是没限制的,子类直接访问protected的实例域破坏封装性,而且同 一个包中的所有类,不管是否是子类都能访问protected实例域。 --使用继承来实现 “is-a”,比如钟点工与雇员之间就不属于“is-a”关系,钟点工是按小时计算薪水的,不适合使用继承。 --除非所有继承的方法都是有意义的,否则不要使用继承。 --在覆盖方法时,不要改变预期的行为。 --使用多态,而非类型信息。 无论什么时候,类似下面这样的代码: if (x is of type 1) action1(x); else if (x is of type 2) action2(x); 都应该考虑使用多态。如果 action1和 action2是相同的概念,就应该为这个概念定义一个方法,将其放置在两个类 的超类或接口中,然后就可以调用 x.action(),以便多态的动态绑定去执行相应的方法。 --不要过多的使用反射39 反射 直接看pdf吧 http://www.cnblogs.com/dolphin0520/p/3811445.html 因篇幅问题不能全部显示,请点此查看更多更全内容