大三学生一枚,有一定基础,这篇可能会有些杂,因为是梳理,把一些之前没有注意的点在此总结一下。
对象与类
类路径:
引入目的 :为了使类 能够被多个程序共享,需要以下几点
(1)把类放到一个目录中,例如/home/user/calssdir.需要注意,这个目录是包树状结构的基目录。如果希望将com.user.corejava.Person类添加到其中,这个Person 类文件就必须位于 com.user.corejava中。
(2)将JAR文件放在一个目录中,例如:/home/user/archives
(3)设置**类路径** (class path)。类路径是所有包含类文件的路径的集合。
类设计技巧 :
(1)一定要保证数据私有。
这是最重要的,绝对不要破坏封装性。
(2)一定要对数据初始化。
Java不对局部变量进行初始化但是会对对象的实例域进行初始化,最好不要依赖于系统的默认值。
(3)不要在类中使用过多的基本类型。
用其他的类代替多个相关的基本类型的使用。
(4)不是所有的域都需要独立的域访问器和域更改器。
(5)将职责过多的类进行分解
单一职责原则
(6)类名和方法名要能够体现他们的职责。
见名知意
继承:
super:有些人认为super与this引用是类似的概念,实际上,这样比较并不太恰当,这是因为super不是一个对象的引用,不能将super赋给另一个对象变量,它只是一个指示编译器调用超类方法的特殊关键字。
使用super调用构造器的语句必须是子类构造器的第一条语句。
如果子类的构造器没有显示地调用超类的构造器,则将自动调用超类默认(没有参数)的构造器。如果超类没有不带参数的构造器,并且在子类的构造器中又没有调用超类的其他构造器,则Java编译器将报告错误。
this与super:
this有两个用途,一是引用隐式参数,二是调用该类其他的构造器。
super有两个用途,一是调用超类的方法,二是调用超类的构造器。
在调用构造器的时候这两个关键字的使用发誓很相似。调用构造器的语句只能作为另一个构造器的第一条语句出现,构造参数既可以传递给本类(this)的其他构造器,也可以传递给超类(super)的构造器。
多态:
在Java中,子类数组的引用可以转换成超类数组的引用,而不需要采用强制类型转换。
Manager [] managers = new Manager[10];
Employee [] staff = managers ;//OK
//注意!
staff[0] = new Employee("cht",...);
//编译器会接纳这个赋值操作。
//但是staff[0] 和 manager[0]引用的是同一个对象
manager[0].setBonus(1000);
//会导致一个不存在的实例域。
为了确保不发生这类错误,所有数组都要牢记创建他们的元素类型,并负责监督仅将类型兼容的引用存储到数组中。
动态绑定:
(1)编译器查看对象的声明类型和方法名
(2)编译器查看调用方法时提供的参数类型
(3)如果是private方法,static方法,final方法或者构造器,那么编译器将可以准确地知道调用哪个方法,这种调用方式称为静态绑定。
(4)每次调用方法都有时间开销,因此虚拟机预先为每一个类创建了一个方法表,包含本类与超类中的方法,选择最合适的方法运行。
Object:所有类的超类
在Java中只有基本类型不是对象,例如,数值,字符和布尔类型的值都不是对象。所有的数组类型,不管是对象数组还是基本类型的数组都扩展于Object类。
Employee [] staff = new Employee[10];
obj = staff;//ok
obj = new int[10];//ok
equals 方法
Object类中的equals方法用于检测一个对象是否等于另外一个对象。在Object类中,这个方法将判断两个对象是否具有相同的引用,如果两个对象具有相同的引用,他们一定是相等的。从这点上看,将其作为默认操作也是合乎情理的。然而对于多数类来说,这种判断并没有什么意义。例如,采用这种方式比较PrintStream对象是否相等就完全没有意义,然而经常需要检测两个对象状态相等性,如果两个对象的状态相等,就认为这两个对象是相等的。
Java语言规范要求equals方法具有以下特性:
(1)自反性:对于任何非空引用x,x.equals(x)返回true.
(2)对称性:对于任何引用x和y,当且仅当y.equals(x)返回true,x.equals(y)也应该返回true。
(3)传递性:对于任何引用x,y,z,如果x.equals(y)返回true,y.equals(z),返回true,x.equals(z)也应该返回true。
(4)一致性:如果x和y引用的对象没有发生变化,反复调用x.equals(y),应该返回一样的结果。
(5)对于任意非空引用x,x.equals(null)应该返回false。
对称性会对子类对象.equals(父类对象)中的子类有一定的束缚。这个类的equals方法必须能够用自己与任何一个父类对象进行比较,从而不考虑子类拥有的那部分特有信息!其实instanceof测试并不是完美无暇。
I 如果子类能够拥有自己的相等概念,则对称性需求将强制采用getClass进行检测
II如果由超类决定相等的概念,那么就可以使用instanceof进行检测,这样在不同子类对象之间进行相等的比较。
一个完美编写equals方法的建议:
1)显式参数命名为otherObject,稍后需要将它转换成另一个叫做other的变量。
2)检测this与otherObject是否引用同一个对象。
if(this == otherObject) return true ;
这条语句只是一个优化,实际上,这是一种经常采用的形式,因为计算这个等式要比一个一个地比较类中的域所付出的代价小得多。
3)检测otherObject是否为null,如果为null,返回false。这项检测是很必要的
if(otherObject == null) return false ;
4)比较this与otherObject是否属于同一个类。如果equals的语意在每个子类中有所改变,就用getClass检测:
if(this.getClass() != otherObject.getClass()) return false ;
如果所有的子类都拥有统一的语意,就使用instanceof检测:
if(!(otherObject instanceof ClassName)) return false ;
5)将otherObject转换为相应的类类型变量:
ClassName other = (ClassName) otherObject
6)现在开始对所有需要比较的域进行比较了。使用 == 比较基本类型域,使用equals比较对象域。如果所有的域都匹配,就返回true;否则返回false。
return field1 == other.field1
&&Objects.equals(field2,other.field2)
&&…
如果在子类中重新定义equals,就要在其中包含调用
super.equals(other).
hashCode方法:
散列码是由对象导出的一个整型值。散列码是没有规律的。如果x和y是两个不同的对象,x.hashCode()与y.hashCode()基本上不会相同。
字符串的散列码是由内容导出的。而字符串缓冲却有着不同的散列码,这是因为在StringBuffer类中没有定义hashCode方法。他的散列码是由Object默认hashCode方法到处对象存储地址。
如存在数组类型的域,那么可以使用静态Arrays.hashCode方法计算一个散列码,这个散列码是由数组元素的散列码组成。
在自定义类中应覆盖equals方法,equals与hashCode的定义必须一致。如果x.equals(y)返回true,那么x.hashCode()就必须与y.hashCode()具有相同的值。
toString方法
用于返回表示对象值的字符串。在调用x.toString()的地方可以用”“+x替代。这里与toString不同的是,如果x是基本类型,这条语句照样能够执行。
new ArrayList<>(100)//capacity is 100
new Employee<>[100]//size is 100
数组列表的容量与数组大小有个非常重要的区别。如果为数组分配100个元素的存储空间,数组就有100个空位可以使用。而容量为100元素的数组列表只是拥有保存100个元素的潜力(实际上,如果重新分配空间的话,将会超过100),但是在最初,甚至完成初始化构造之后,数组列表根本就不含有任何元素。
size方法将返回数组列表中包含的实际元素数目。eg: list.size();
一旦能够确认数组列表的大小不再发生变化就可以调用trimToSize方法。这个方法将存储区的大小调整为当前元素量所需要的存储空间数目。GC将回收多余的存储空间。
一旦整理的数组列表的大小,添加新元素就需要花时间再次移动存储块,所以应该在确认不会添加任何元素时,再调用trimToSize。
ArrayList<Employee> list = new ArrayList<>(100);
//capacity is 100 , size is 0
list.se(0,x);// no element 0 yet
//上面这段代码是错误的 使用add方法为数组添加新元素,而不要使用set方法,他只
能替换数组中存在的元素内容。
toArray():用于实现将一个数组元素拷贝到另一个数组中
X [] a = new [list.size()];
list.toArray();
除了在数组列表的尾部追加元素之外,还可以在数组列表中间插入元素,使用带索引参数的add方法:
int n = staff.size()
staff.add(n,x);
为了插入一个新元素,位于n之后的所有元素都要向后移动一个位置,如果插入新元素后,数组列表的大小超过了容量,数组列表就会重新分配存储空间。
同样地,也可以在数组列表中间删除一个元素:
Employee e = staff.remove(n);
位于这个位置之后的所有元素都向前移动一个位置,并且数组的大小减1.
对象包装器与自动装箱
自动装箱
list.add(3);将自动变换成list.add(Integer.valueOf(3));
自动拆箱
int n = list.get(i)将自动变换成int n = list.get(i).intValue();
装箱和拆箱是编译器认可的,而不是虚拟机。编译器在生成类的字节码时,插入必要的方法调用。虚拟机只是执行这些字节码。
java方法都是值传递
枚举类 :
在比较两个枚举类型的值时,永远不需要调用equals,而直接使用“==”就可以了。
所有的枚举类型都是Enum类的子类。他们继承了这个类的许多方法。其中最有用的一个是toString,这个方法能够返回枚举常量名。例如,Size.SMALL.toString()将返回字符串”SMALL”,
toString的逆方法是静态方法valueOf。
Size s = Enum.valueOf(Size.class,”SAMLL”);
将s设置成Size.SAMLL.
每个枚举类型都有一个静态的Values方法。它将返回一个包含全部枚举值的数组。
Size [] values = Size.values();
ordinal 方法返回enum声明中枚举常量的位置。位置从0开始计数。