《Java核心技术 卷Ⅰ》 第5章 继承前端
关键字extend
表示继承。java
public class Manager extends Employee { // 添加方法和域 }
extend
代表正在构造的新类派生于一个已存在的类。git
已存在的类称为超类(superclass)、基类(base class)或父类(parent class);
新类称为子类(subclass)、派生类(derived class)或孩子类(child class)。程序员
子类有超类没有的功能,子类封装了更多的数据,拥有更多的功能。github
因此在扩展超类定义子类时,仅须要指出子类与超类的不一样之处。数组
有时候,超类的有些方法并不必定适用于子类,为此要提供一个新的方法来覆盖(override)超类中的这个方法:安全
public class Manager extends Employee { private double bonus; ... public double getSalary() { double baseSalary = super.getSalary(); return baseSalary + bonus; } ... }
这里因为Manager
类的getSalary
方法并不能直接访问超类的私有域。app
这是由于尽管子类拥有超类的全部域,可是子类无法直接获取到超类的私有部分,由于超类的私有部分只有超类本身才可以访问。而子类想要获取到私有域的内容,只能经过超类共有的接口。ide
而这里Employee
的公有方法getSalary
正是这样的一个接口,而且在调用超类方法使,要使用super
关键字。工具
那你可能会好奇:
super
关键字不行么?Employee
类的getSalary
方法不该该是被Manager
类所继承了么?这里若是不使用super
关键字,那么在getSalary
方法中调用一个getSalary
方法,势必会引发无限次的调用本身。
关于super
和this
须要注意的是:他们并不相似,由于super
不是一个对象的引用,不能将super
赋给另外一个对象变量,它只是一个指示编译器调用超类方法的特殊关键字。
public Manager(String name, double salary, int year, int month, int day) { super(name, salary, year, month, day); bonus = 0; }
语句super(...)
是调用超类中含有对应参数的构造器。
Q1:为何要这么作?
A1:因为子类的构造器不能访问超类的私有域,因此必须利用超类的构造器对这部分私有域进行初始化。可是注意,使用super
调用构造器的语句必须是子类构造器的第一条语句。
Q2:必定要使用么?
A2:若是子类的构造器没有显示地调用超类构造器,则将自动地调用超类默认(没有参数)的构造器;若是超类并无不带参数构造器,而且子类构造器中也没有显示调用,则Java编译器将报告错误。
Employee[] staff = new Employee[3]; staff[0] = manager; staff[1] = new Employee(...); staff[2] = new Employee(...); for(Employee e : staff) { System.out.println(e.getName() + "" + e.getSalary()); }
这里将e
声明为Emplyee
对象,可是实际上e
既能够引用Employee
对象,也能够引用Manager
对象。
Employee
对象时,e.getSalary()
调用的是Employee
的getSalary
方法Manager
对象时,e.getSalary()
调用的是Manager
的getSalary
方法虚拟机知道e
实际引用的对象类型,因此可以正确地调用相应的方法。
一个对象变量能够指示多种实际类型的现象被称为多态(polymorphism)。在运行时可以自动地选择调用哪一个方法的现象称为自动绑定(dynamic binding)。
集成并不只限于一个层次。
由一个公共超类派生出来的全部类的集合被称为继承层次(inheritance hierarchy),在继承层次中,从某个特定的类到其祖先的路径被称为该类的继承链(inheritance chain)。
Java不支持多继承,有关Java中多继承功能的实现方式,见下一章有关接口的部分。
"is-a"
规则的另外一种表述法是置换法则,它代表程序中出现超类对象的任何地方均可以用子类对象置换。
Employee e; e = new Employee(...); e = new Manager(...);
在Java程序设计语言中,对象变量是多态的。
Manager boss = new Manager(...); Employee[] staff = new Employee[3]; staff[0] = boss;
这个例子中,虽然staff[0]
与boss
引用同一个对象,可是编译器将staff[0]
当作Employee
对象,这意味着这样调用是没有问题的:
boss.setBonus(5000); // OK
可是不能这样调用:
staff[0].setBonus(5000); // Error
这里由于staff[0]
声明的类型是Employee
,而setBonus
不是Employee
类的方法。
尽管把子类赋值给超类引用变量是没有问题的,但这并不意味着反过来也能够:
Manager m = staff[2]; // Error
若是这样赋值成功了,那么编译器将m
当作是一个Manager
类,在调用setBonus
因为所引用的Employee
类并无该方法,从而会发生运行时错误。
弄清楚如何在对象上应用方法调用很是重要。
好比有一个C
类对象x
,C
有一个方法f(args)
。
如今以调用x.f(args)
为例,说明调用过程的详细描述:
C
类中可能有多个同名的方法,编译器将列举全部C
类中名为f
的方法和其超类中属性为public
且名为f
的方法(超类私有没法访问)。f
,就选择这个方法,这个过程被称为重载解析(overloading resoluton)。这个过程容许类型转换(int转double,Manager转Employee等等)。若是编译器没有找到,或者发现类型转换后,有多个方法与之匹配,就会报告一个错误。private
方法,static
方法、final
方法或者构造器,编译器将能够准确知道应该调用哪一个方法,这种称为静态绑定(static binding)。若是不是这些,那调用的方法依赖于隐式参数的实际类型,而且在运行时动态绑定,好比x.f(args)
这个例子。x
所引用对象的实际类型最合适的那个类的方法。好比x
实际是C
类,它是D
类的子类,若是C
类定义了方法f(String)
,就直接调用;不然将在C
类的超类中寻找,以此类推。简单说就是顺着继承层次从下到上的寻找方法。若是每次调用方法都要深度/广度遍历搜索继承链,时间开销很是大。
所以虚拟机预先为每一个类建立一个方法表(method table),其中列出了全部方法的签名和实际调用的方法,这样一来在真正调用时,只须要查表便可。
若是调用super.f(args)
,编译器将对隐式参数超类的方法表进行搜索。
以前的Employee
类和Manager
类的方法表:
Employee: getName() -> Employee.getName() getSalary() -> Employee.getSalary() getHireDay() -> Employee.getHireDay() raiseSalary(double) -> Employee.raiseSalary(double) Manager: getName() -> Employee.getName() getSalary() -> Manager.getSalary() getHireDay() -> Employee.getHireDay() raiseSalary(double) -> Employee.raiseSalary(double) setBonus(double) -> Manager.setBonus(double)
在运行时,调用e.getSalary()
的解析过程:
Employee e
能够引用全部Employee
类的子类,因此要肯定实际引用的类型)。getSalary
签名的类,虚拟机肯定调用哪一个方法。注:在覆盖一个方法时,子类方法不能低于超类方法的可见性,特别是超类方法是public
,子类覆盖方法时必定声明为public
,由于常常会发生这样的错误:在声明子类方法时,由于遗漏public
而使编译器把它解释为更严格的访问权限。
有时候,可能但愿阻止人们利用某个类定义子类。
不容许扩展的类被称为
final
类。
若是在定义类时使用了final
修饰符就代表这个类是final
类。
public final class Executive extends Manager { ... }
方法也能够被声明为final
,这样子类就不能覆盖这个方法,final
类中的全部方法自动地称为final
方法。
public class Employee { ... public final String getName() { return name; } ... }
这里注意final
域的区别,final
域指的是构造对象后就再也不运行改变他们的值了,不过若是一个类声明为final
,只有其中的方法自动地成为final
,而不包括域。
将方法或类声明为
final
主要目的是:确保不会在子类中改变语义。
有时候就像将浮点数转换为整数同样,也可能须要将某个类的对象引用转换成另外一个类的对象引用。
对象引用的转换语法与数值表达式的类型转换相似,仅须要一对圆括号将目标类名括起来,并放置在须要转换的对象引用以前就能够了。
Manager boss = (Manager) staff[0]; // 由于以前把boss这个Manager类对象也存在了Employee数组中 // 如今经过强制类型转换回复成Manager类
进行类型转换的惟一缘由:在暂时忽略对象的实际类型以后,使用对象的所有功能。
在Java中,每一个对象变量都属于一个类型,类型描述了这个变量所引用的以及可以引用的对象类型。
将一个值存入变量时,编译器将检查是否容许该操做:
若是试图在继承链上进行向下的类型转换,并谎报有关对象包含的内容(好比硬要把一个Employee
类对象转换成Manager
类对象):
Manager boss = (Manager) staff[1]; // Error
运行时,Java运行时系统将报告这个错误(不是在编译阶段),并产生一个ClassCastException
异常,若是没有捕获异常,程序将会终止。
因此应该养成一个良好习惯:在进行类型强转以前,先查看一下是否能成功转换,使用instanceof
操做符便可:
if(staff[1] instanceof Manager) { boss = (Manager) staff[1]; ... }
注:若是x
为null
,则它对任何一个类进行instanceof
返回值都是false
,它由于没有引用任何对象。
位于上层的类一般更具备通用性,甚至可能更加抽象,对于祖先类,咱们一般只把它做为派生其余类的基类,而不做为想使用的特定的实例类,好比Person
类对于Employee
和Student
类而言。
因为Person
对子类一无所知,可是又想规范他们,一种作法是提供一个方法,而后返回空的值,另外一种就是使用abstract
关键字,这样Person
就彻底不用实现这个方法了。
public abstract String getDescription(); // no implementation required
为了提供程序的清晰度,包含一个或多个抽象方法的类自己必须被声明为抽象的。
public abstract class Person { private String name; ... public abstract String getDescription(); ... public String getName() { return name; } }
除了抽象方法外,抽象类还能够包含具体数据和具体方法。
尽管许多人认为,在抽象类中不能包含具体方法,可是仍是建议尽可能把通用的域和方法(无论是否抽象)都放在超类(无论是否抽象)中。
虽然你能够声明一个抽象类的引用变量,可是只能引用非抽象子类的对象,由于抽象类不能被实例化。
在非抽象子类中定义抽象类的方法:
public class Student extends Person { private String major; ... public String getDescription() { return "a student majoring in " + major; } }
尽管Person
类中没有具体定义getDescription
的具体内容,可是当一个Person
类型引用变量p
使用p.getDescription()
也是没有问题的,由于根据前面的方法调用过程,在运行时,方法的实际寻找是从实际类型开始寻找的,而实际类型都是定义了这个方法的具体内容。
那你可能会问,我能够只在Student
类中定义getDescription
不就好了么?为何还要在Person
去声明?由于若是这样的话,就不能经过p调用getDescription
方法了,由于编译器只容许调用在类中声明的方法。
有些时候,人们但愿超类中的某些方法容许被子类访问,或容许子类的方法访问超类中的某个域,而不让其余类访问到。
为此,须要将这些方法或域声明为protected
。
例如,若是Employee
中的hireDay
声明为protected
,而不是private
,则Manager
中的方法就能够直接访问它。
不过,Manager
中的方法只可以访问Manager
对象中的hireDay
,而不能访问其余Employee
对象中的这个域,这样使得子类只能得到访问受保护域的权利。
对于受保护的域来讲,可是这在必定程度上违背了OOP提倡的数据封装原则,由于若是当一个超类进行了一些修改,就必须通知全部使用这个类的程序员(而不像普通的private
域,只能经过开放的方法去访问)。
相比较,受保护的方法更具备实际意义。若是须要限制一个方法的使用,就能够声明为protected
,这代表子类获得信任,能够正确地使用这个方法,而其余类(非子类)不行。
这种方法的一个最好示例就是Object
类中的clone
方法。
概括总结Java控制可见性的4个访问修饰符:
public
:对全部类可见protected
:对本包和全部子类可见private
:仅对本类可见
Obejct
类是Java中全部类的始祖,Java中每一个类都是它扩展而来。
若是没有明确指出超类,Object就被认为是这个类的超类。
天然地,可使用Object
类型的变量引用任何类型的对象:
Obejct obj = new Employee("Harry Hacker", 35000);
Object
类型的变量只能用于各类值的通用持有者。若是想要对其中的内容进行具体操做,还须要清楚对象的原始类型,并进行相应的类型转换:
Employee e = (Employee) obj;
Object
类中的equals
方法用于检测一个对象 是否等于另一个对象。
这里的等于指的是判断两个对象是否具备相同的引用。
可是在判断两个不肯定是否为null
的对象是否相等时,须要使用Objects.equals
方法,若是两个都是null
,将返回true
;若是其中一个为null
,另外一个不是,则返回false
;若是两个都不为null
,则调用a.equals(b)
。
固然大多数时候Object.equals
并不能知足,通常来讲咱们须要比较两个对象的状态是否相等,这个时候须要重写这个方法:
public class Manager extends Employee { ... public boolean equals(Object otherObject) { // 首先要调用超类的equals if(!super.equals(otherObejct)) return false; Manager other = (Manager) otherObject; return bonus == other.bonus; } }
在阅读后面的书籍笔记内容以前,首先补充一下getClass
和instanceof
究竟是什么:
object
的运行时类Class
(Java中有一个类叫Class)。好比一个Person
变量p
,则p.getClass()
返回的就是Person
这个类的Class
对象,Class
类提供了不少方法来获取这个类的相关信息manager instanceof Employee
是返回true
的好了,让咱们回到原书吧。
若是隐式和显示的参数不属于同一个类,equals
方法如何处理呢?
有许多程序员喜欢使用instanceof
来进行检测:
if(!otherObject instanceof Employee) return false;
这样作不但没有解决otherObject
是子类的状况,而且还可能招致一些麻烦。
Java语言规范要求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)
应该返回一样结果x
,x.equals(null)
应该返回false
从两个不一样的状况看一下这个问题:
getClass
进行检测instanceof
进行检测,这样能够在不一样子类的对象之间进行相等的比较给出一个编写完美equals
方法的建议:
otherObejct
,稍后将它转换成另外一个叫作other
的变量检测this
与otherObject
是否因用同一个对象:
if(this == otherObject) return true;
检测otherObject
是否为null
:
if(otherObject == null) return false;
比较this
和otherObject
是否属于同一个类
// 若是equals语义在每一个子类中有改变,就用getClass if(getClass() != otherObject.getClass()) return false; // 若是子类拥有统一的语义,就用instanceof检测 if(!(otherObejct instanceof ClassName)) return false;
将otherObejct
转换为相应的类类型变量:
ClassName other = (ClassName) otherObejct;
开始进行域的比较,使用==
比较基本类型域,使用Objects.equals
比较对象域
return field1 == other.field1 && Objects.equals(field2, other.field2) && ...;
若是在子类中从新定义equals
,还要在其中包含调用super.equals(other)
。
另外,对于数组类型的域,可使用静态的Array.equals
方法检测相应的数组元素是否相等。
散列码(hash code)是由对象导出的一个整数值。
散列码是没有规律的,若是x
和y
是两个不一样的对象,x.hashCode()
与y.hashCode()
基本上不会相同。
对于String
类而言,字符串的散列码是由内容导出的。
因为hashCode方法定义在Object
类中,所以每一个对象都有一个默认的散列码,其值为对象的存储地址。
若是从新定义equals
方法,就必须从新定义hashCode
方法,以便用户能够将对象插入到散列表中。
hashCode
方法应该返回一个整数数值(也能够是负数),并合理地组合实例域的散列码,以便让各个不一样的对象产生的散列码更加均匀。
例如,Employee
类的hashCode
方法:
public class Employee { public int hashCode() { return 7 * name.hashCde() + 11 * new Double(salary).hashCode() + 13* hireDay.hashCode(); } }
不过若是使用null
安全的方法Objects.hashCode(...)
就更好了,若是参数为null
,这个方法返回0。
另外,使用静态方法Double.hashCode(salary)
来避免建立Double
对象。
还有更好的作法,须要组合多个散列值时,能够调用Objects.hash
并提供多个参数。
public int hashCode() { return Obejcts.hash(name, salary, hireDay); }
equals
与hashCode
的定义必须一致:若是x.equals(y)
返回true
,那么x.hashCode()
就必须与y.hashCode()
具备相同的值。
Object
中还有一个重要的方法,就是toString
方法,它用于返回表示对象值的字符串。
绝大多数(但不是所有)的toString
方法都遵循这样的格式:类的名字,随后是一对方括号括起来的域值。
public String toString() { return getClass().getName() + "[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay + "]"; }
toString
方法也能够供子类调用。
固然,设计子类的程序员也应该定义本身的toString
方法,并将子类域的描述添加进去。
若是超类使用了getClass().getName()
,子类只须要调用super.toString()
便可。
public class Manager extends Employee { ... public String toString() { return super.toString() + "[bonus=" + bonus + "]"; } }
如今,Manager
对象将打印输出以下所示内容:
Manager[name=...,salary=...,hireDay=...][bonus=...]
注意这里在子类中调用的super.toString()
,不是在超类Employee
中调用的么?为何打印出来的是Manager
?
由于getClass
正如前面所说,获取的是这个对象运行时的类,与在哪一个类中调用无关。
若是任何一个对象x
,调用System.out.println(x)
时,println
方法就会直接调用x.toString()
,并打印输出获得的字符串。
Object
类定义了toString
方法,用来打印输出对象所属类名和散列码:
System.out.println(System.out) // 输出 java.io.PrintStream@2f6684
这样的结果是PrintStream
类设计者没有覆盖toString
方法。
对于一个数组而言,它继承了object
类的toString
方法,数组类型按照旧的格式打印:
int[] luckyNumbers = { 2, 3, 5, 7, 11, 13 }; String s = "" + luckyNumbers; // s [I@1a46e30
前缀[I
代表是一个整形数组,若是想要获得里面内容的字符串,应该使用Arrays.toString
:
String s = Arrays.toString(luckyNumbers); // s [2,3,5,7,11,13]
若是想要打印多维数组,应该使用Arrays.deepToString
方法。
强烈建议为自定义的每个类增长
toString
方法。
在许多程序设计语言中,必须在编译时就肯定整个数组大小。
在Java中,容许运行时肯定数组的大小:
int actualSize = ...; Employee[] staff = new Employee[actualSize];
固然,这段代码并无彻底解决运行时动态更改数组的问题。一旦肯定了大小,想要改变就不容易了。
在Java中,最简单的解决方法是使用Java中另外一个被称为ArrayList
的类,它使用起来有点像数组,但在添加或删除元素时,具备自动调节数组容量的功能,而不须要为此编写任何代码。
ArrayList
是一个采用类型参数(type paraneter)的泛型类(generic class)。为了指定数组列表保存的元素对象类型,须要用一对尖括号将类名括起来加在后面,例如ArrayList<Employee>
。
ArrayList<Employee> staff = new ArrayList<Employee>(); // 两边都是用参数有些繁琐,在Java SE 7中,能够省去右边的类型参数 ArrayList<Employee> staff = new ArrayList<>();
这通常叫作“菱形语法”(<>
),能够结合new
操做符使用。
若是赋值给一个变量,或传递到某个方法,或者从某个方法返回,编译器会检查这个变量、参数或方法的泛型类型,而后将这个类型放在<>
中。
在这个例子中,new ArrayList<>()
将赋值给一个类型为ArrayList<Employee>
的变量,因此泛型类型为Employee
。
使用add
方法能够将元素添加到数组列表中。
staff.add(new Employee(...));
数组列表管理着对象引用的一个内部数组,最终数组空间有可能被用尽,这时数组列表将会自动建立一个更大的数组,并将全部的对象从较小数组中拷贝到较大数组中。
也能够肯定存储的元素数量,在填充数组前调用ensureCapacity
方法:
// 分配一个包含100个对象的内部数组 // 在100次调用add时不用再每次都从新分配空间 staff.ensureCapacity(100); // 固然也能够经过把初始容量传递给构造器实现 ArrayList<Employee> staff = new ArrayList<>(100);
size方法返回数组列表包含的实际元素数目:
staff.size()
一旦可以确认数组列表大小再也不发生变化,能够调用trimToSize
方法。这个方法将存储区域的大小调整为当前元素数量所须要的存储空间数目,垃圾回收器将回收多余的存储空间。
数组列表自动扩展容量的便利增长了访问元素语法的复杂程度。
须要使用get
和set
方法实现或改变数组元素的操做,而不是[index]
语法格式。
staff.set(i, harry); Employee e = staff.get(i);
当没有泛型类时,原始的ArrayList
类提供的get
方法别无选择只能返回Object
,所以,get
方法的调用者必须对返回值进行类型转换:
Employee e = (Employee) staff.get(i);
固然仍是有一个比较方便的方法来灵活扩展又方便访问:
ArrayList<X> list = new ArrayList<>(); while(...) { x = ...; list.add(x); } X[] a = new X[list.size()]; // 使用toArray方法把数组元素拷贝到一个数组中 list.toArray(a);
还能够在数组列表的中间插入元素:
int n = staff.size()/2; staff.add(n, e);
固然也能够删除一个元素:
Employee e = staff.remove(n);
可使用for each
循环遍历数组列表:
for(Employee e : staff) do sth with e
有时须要将int
这样的基本类型转换为对象,全部基本类型都有一个与之对应的类。
例如,Integer
类对应基本类型int
,一般这些类称为包装器(wrapper)。
这些对象包装器有很明显的名字:Integer
、Long
、Float
、Double
、Short
、Byte
、Character
、Void
和Boolean
(前6个类派生于公共超类Number
)。
对象包装器类是不可变的,即一旦构造了包装器,就不容许更改包装在其中的值。
同时,对象包装器类仍是final
,所以不能定义它们的子类。
有一个颇有用的特性,便于添加int
类型的元素到ArrayList<Integer>
中。
ArrayList<Integer> list = new ArrayList<>(); list.add(3); // 这里将自动地变为 list.add(Integer.valueOf(3));
这种变换被称为自动装箱(autoboxing)。
相反地,将一个Integer
对象赋给一个int
值时,将会自动地拆箱。
int n = list.get(i); // 将会被翻译成 int n = list.get(i).intValue();
在算术表达式中也能自动地装箱和拆箱,例如自增操做符应用于一个包装器引用:
Integer n = 3; n++;
编译器自动地插入一条对象拆箱指令,而后自增,而后再结果装箱。
==
虽然也能够用于对象包装器对象,但通常检测是对象是否指向同一个存储区域。
Integer a = 1000; Integer b = 1000; if(a == b) ...;
然而Java中上面的判断是有可能(may)成立的(这也太玄学了),因此解决办法通常是使用equals
方法。
还有一些须要强调的:
null
,因此自动装箱可能会抛出NullPointerException
异常Integer
和Double
类型,Integer
值就会拆箱,提高为double
,再装箱为Double
使用数值对象包装器还有另一个好处,能够将某些基本方法放置在包装器中,好比,将一个数字字符串转换成数值。
int x = Integer.parseInt(s);
Java SE 5之前的版本中,每一个Java方法都有固定数量的参数,然而如今的版本提供了可变的参数数量调用的方法。
好比printf
方法的定义:
public class PrintStream { public PrintStream printf(String fmt, Object... args) { return format(fmt, args); } }
这里的省略号...
是Java代码的一部分,代表这个方法能够接收任意数量的对象(除fmt参数外)。
实际上,printf
方法接收两个参数,一个是格式字符串,另外一个是Object[]
数组,其中保存着全部的参数。
编译器须要对printf
的每次调用进行转换,以便将参数绑定到数组上,并在必要的时候进行自动装箱:
System.out.printf("%d %s", new Object[]{ new Integer(n), "widgets" });
用户也能够自定义可变参数的方法,并将参数指定为任意类型,甚至基本类型。
// 找出最大值 public static double max(double... values) { double largest = Double.NEGATIVE_INFINITY; for(double v : values) if(v > largest) largest = v; return largest; }
public enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE };
实际上这个声明定义的类型是一个类,它恰好有4个实例。
所以比较两个枚举类型值时,不须要调用equals
,直接使用==
就能够了。
若是须要的话,能够在枚举类型中添加一些构造器、方法和域,构造器只在构造枚举常量的时候被调用。
public enum Size { SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL"); private String abbreviation; private Size(String abbreviation) { this.abbreviation = abbreviation; } public String getAbbreviation() { return abbreviation; } }
全部的枚举类型都是Enum
类的子类,他们集成了这个类的许多方法,最有用的一个是toString
,这个方法能返回枚举常量名,例如Size.SMALL.toString()
返回"SMALL"
。
toString
的逆方法是静态方法valueOf
。
Size s = Enum.valueOf(Size.class, "SMALL");
将s
设置成Size.SMALL
。
每一个枚举类型都有一个静态的values
方法,返回一个包含所有枚举值的数组。
Sizep[] values = Size.values();
ordinal
方法返回enum
声明中枚举常量的位置,位置从0开始技术。
反射是一种功能强大且复杂的机制,使用它的主要人员是工具构造者,而不是应用程序员。
因此这部分先跳过,将会在之后一个专题单独来讲明。
"is-a"
关系protected
受保护访问Object
全部类的超类equals
方法hashCode
方法toString
方法我的静态博客: