“核心技术:继承”的版本间差异

来自Wikioe
跳到导航 跳到搜索
第197行: 第197行:


=== Class类 ===
=== Class类 ===
<pre>
在程序运行期间,Java运行时系统始终为所有的对象维护一个被称作“运行时”的类型标识。
这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。


然而,可以通过专门的Java类访问这些信息。保存这些新欻的类被称为“Class”。
如同一个Employee对象表示某个的雇员一样,一个Class对象将表示某个类的属性。
</pre>
==== 获取“Class类”对象 ====
# Java对象的“getClass()”方法:【普通对象的方法】
#: <syntaxhighlight lang="java" highlight="2">
Employee e = new Employee();
Class c1 = e.getClass();
System.out.println(c1.getName()); // 结果:Employee
</syntaxhighlight>
#: 如果类在包里,则包名也作为类名的一部分,如“java.util.Random”
# “Class”类的静态方法“forName()”:【Class类的静态方法】
#: <syntaxhighlight lang="java" highlight="2">
String className = "java.util.Random";
Class c2 = Class.forName(className);
System.out.println(c2.getName()); // 结果:Random
</syntaxhighlight>
#: 只有在className时类名或接口名时才能执行,否则抛出“checked exception”
#: 无论何时使用此方法,都应该提供一个异常处理器(exception handler)
# “Java类名.class”【类字面量】
#: <syntaxhighlight lang="java" highlight="1,5,9">
Class c3 = Employee.class;
System.out.println(c3.getName()); // 结果:Employee
// 运用.calss的方式获取Class实例(基本类型)
Class c4 = Random.class;
System.out.println(c4.getName()); // 结果:int
// 运用.class的方式获取Class实例(基本数据类型的封装类)
Class c5 = Integer.TYPE;
System.out.println(c5.getName()); // 结果:int
</syntaxhighlight>
* 一个“Class”对象实际上表示的时一个类型,而这个类型未必一定是一种类;
*: 如:int不是一个类,但int.Class是一个Class类型的对象;
* Class类 实际上是一个泛型类,如:“Employee.class”的类型是“Class<Employee>”
* 虚拟机为每个类型管理一个Class对象,因此可以利用“==”实现两个类对象的比较:
*: <syntaxhighlight lang="java">
if(e.getClass() == Employee.class)
</syntaxhighlight>
==== 创建类的实例 ====
“newInstance()”方法用于动态地创建一个类的实例:
<syntaxhighlight lang="java">
e.getClass().newInstance(); // e 是一个Employee对象
</syntaxhighlight>
* 将 forName 与 newInstance 配合使用,可以根据存储在字符串中的类名创建一个对象:
*: <syntaxhighlight lang="java">
String s = "java.util.Random";
Object m = Class.forName(s).newInstance();
</syntaxhighlight>


=== 捕获异常 ===
=== 捕获异常 ===

2020年10月17日 (六) 00:33的版本


类、超类和子类

定义子类

关键字 extends 表示正在构造的新类(subclass子类;derived class派生类;child class孩子类)派生于一个已存在的类(superclass超类、base class基类、parent class父类)

覆盖方法

父类中的某些方法对子类并不一定适应,需要子类对方法覆盖(override);

  • 子类不能之间访问父类的私有域,必须使用父类私有域对应的公有访问接口;
  • 子类可以增加域、增加方法、覆盖方法,但不能删除继承的域和方法;
  • 覆盖一个方法的时候,子类方法不能低于超类方法的可见性;

“super”关键字用于只是编译器调用父类方法。

  • super与this不同,并非一个对象的引用,不能将super赋给另一个对象变量;

子类构造器

子类构造器中:

  • 如果使用super显示调用父类构造器,则该语句必须是子类构造器的第一条语句出现;
  • 如果没有显示调用父类构造器,则将自动地调用父类默认(没有参数)的构造器;
  • 如果没有显示调用父类构造器,且父类没有无参构造器,则Java编译器将报告错误;

多态

有一个用来判断是否应该设计为继承关系的规则:“is-a”,即“置换法则”,程序中出现超类对象的任何地方都可以用子类对象置换

  • 一个对象可以指示多种实际类型的现象,被称为多态(polymorphism);
    如,Employee既可以引用一个Employee对象,也可以引用Employee的子类对象(如,Manager)
  • 在运行时能自动地选择调用哪个方法的现象,被称为动态绑定(dynamic binding);
  • 如果方法是private方法、static方法、final方法,那么编译器可以准确地知道应该调用的方法,这种调用方式被称为静态调用(static bingding);

阻止继承:final类和方法

final使用:

  • final域:域不能被修改
  • final方法:方法不能被覆盖
  • final类:类不能被扩展(即不能被继承)
    类中的所有方法自动成为final
    类中的域并不为final

强制类型转换

对象的强制类型转换:

  1. 只能在继承层次内进行类型转换;
  2. 在将父类转换为子类之前,应该用instanceof检查
    • “instanceof”用于判断对象是否为某个类型

抽象类

  • 包含一个或多个抽象方法的类,必须被声明为抽象类;
  • 除了抽象方法,抽象类还可以包含具体数据和具体方法;
  • 抽象类不能被实例化;
  • 可以定义抽象类对象,该对象只能引用非抽象子类的对象;

受保护访问

  1. public:公有,对其他类可见
  2. private:私有,对其他类不可见(即使子类也不可见)
  3. protect:受保护,对所有子类 及同一个包中的其他类可见,包外不可见
  • 对本包可见是默认的,不需要修饰符;

Object:所有类的超类

Object 类Java中所有类的始祖,每个类都由它扩展而来

  • 只有基本类型不是对象
  • 所有的数组类型(无论基本类型数组,对象数组),都扩展了Object类

equals

用于判断一个对象是否等于另一个对象。(需要重写)

  • 在Object中,equals将判断两个对象的是否有相同的引用(与“==”一样);
  • 为了判断值相等,应该重写equals方法,进行内容判断(先比较hashcode,再比较值);

相等测试与继承

Java语言规范要求equals方法具有以下特性:

  1. 自反性:对于任何非空引用x,均有x.equals(x)=true;
  2. 对称性:对于任何引用x、y,当且仅当x.equals(y)=true,则应y.equals(x)=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;

hashcode

散列码(hashcode)是由对象导出的一个整型值,是没有规律的:

  1. 两个不同对象的hashcode基本不会相同;
  2. 两个对象相同,其hashcode必定相同(所以在equals前先比较hashcode可以提高效率);
  • “hashcode()”应该返回一个整型数值(可以负数),并合理地组合实例域的散列码,以便能让各个不同的对象产生的散列码更加均匀;
  • 最好使“hashcode()”对null安全:如果参数为null,这个方法返回0,否则返回对参数调用hashcode的结果;

toString

该方法用于返回表示对象值的字符串。(需要重写)

泛型数组列表

ArrayList使用:

ArrayList<E>()
// 构造一个空数组列表

ArrayList<E>(int initialCapacity)
//用指定容量构造一个空数组列表(initialCapacity:初始容量)

boolena add(E obj)
// 在数组列表的尾端添加一个元素

int size()
// 返回存储在数组列表中的当前元素数量

void ensureCapacity(int capacity)
// 确保数组列表在不重新分配存储空间的情况下,就能保存给定数量的元素

void trimToSize()
// 将数组列表的存储容量削减到当前(使用的)尺寸
  • 一旦整理了数组列表的大小,或者空间被完全使用,添加新元素时就需要再次移动存储块

访问数组列表元素

void set(int index, E obj)
// 设置元素,将会覆盖原有内容

E get(int index)
// 

void add(int index, E obj)
// 

E remove(int index)
//

类型化与原始数组列表的兼容性

对象包装器与自动装箱

所有的基本类型都有一个对应的类,即包装器(wrapper):

  1. “Integer”、“Long”、“Float”、“Double”、“Short”、“Byte”、“Character”、“Void”、“Boolean”:
  2. 前六个派生于公共超类“Number”;


关于 wrapper:

  • 对象包装器类是不可变的,即:一旦构造了包装器,就不允许更改包装在其中的值;
  • 同时,对象包装器类还是 final,因此不允许定义它们的子类;

使用:

  1. 自动装箱:将基本类型赋给一个包装器对象时;
  2. 自动拆箱:将一个包装器对象赋给基本类型时;
  • 拆箱装箱时编译器认可的,而不是虚拟机;
  • 自动装箱要求boolean、byte、char<=127、介于-128-127之间的short和int被包装到固定的对象中;
  • 如果在一个表达式中,混合使用Integer和Double:Integer会被拆箱为,提升为double,再装箱为Double;


另:

int x = Integer.parseInt(s);

可以将s强制转换为int类型(parse为静态方法)

参数数量可变的方法

在 Java SE 5 之前的版本中,每个方法都有固定数量的参数。


public class PrintStream
{
    public PrintStream printf(String fmt, Object... args)
    {
        return format(fmt, args);
    }
}

其中“Object... args”的省略号也是代码的一部分,表示这个方法可以接受任意数量的对象(除 fmt 参数外)。

  1. 对于printf的实现来说:“Object... args”与“Object[] args”完全一样;
  2. 对于printf的调用来时:将以前args[]数组的元素,单个依次传递为为方法参数;


  • 允许将一个数组传递给可变参数方法的最后一个参数。

枚举类

[1]

public enum Size{SMALL, MEDIUM, LARGE, EXTRA_LARGE};

声明了一个枚举类(这是一个类!)

  1. 所有枚举类都是“Enum”类的子类;
  2. 比较两个枚举类型的值时,应该用“==”而非“equals”;
  3. “toString”方法用于返回枚举常量名;
  4. “toString”的逆方法为静态方法“valueOf”:用于获取枚举常量;

反射

反射库(reflection library)提供了一个非常丰富且精心设计的工具集,以便能够动态操纵Java代码的程序。

能够分析类能力的程序成为反射(reflective),可以用作:

  1. 在运行时分析类的能力;
  2. 在运行时查看对象;
  3. 实现通用的数组操作代码;
  4. 利用“Method”对象,这个对象很像 C++ 中的函数指针;
  • 反射主要用在工具框架的构造过程中;

Class类

在程序运行期间,Java运行时系统始终为所有的对象维护一个被称作“运行时”的类型标识。
这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。

然而,可以通过专门的Java类访问这些信息。保存这些新欻的类被称为“Class”。

如同一个Employee对象表示某个的雇员一样,一个Class对象将表示某个类的属性。

获取“Class类”对象

  1. Java对象的“getClass()”方法:【普通对象的方法】
    	Employee e = new Employee(); 
    	Class c1 = e.getClass(); 
    	System.out.println(c1.getName());	// 结果:Employee
    
    如果类在包里,则包名也作为类名的一部分,如“java.util.Random”
  2. “Class”类的静态方法“forName()”:【Class类的静态方法】
    	String className = "java.util.Random";
    	Class c2 = Class.forName(className); 
    	System.out.println(c2.getName()); 	// 结果:Random
    
    只有在className时类名或接口名时才能执行,否则抛出“checked exception”
    无论何时使用此方法,都应该提供一个异常处理器(exception handler)
  3. “Java类名.class”【类字面量】
    	Class c3 = Employee.class; 
    	System.out.println(c3.getName()); // 结果:Employee 
     
    	// 运用.calss的方式获取Class实例(基本类型) 
    	Class c4 = Random.class; 
    	System.out.println(c4.getName()); // 结果:int 
     
    	// 运用.class的方式获取Class实例(基本数据类型的封装类) 
    	Class c5 = Integer.TYPE; 
    	System.out.println(c5.getName()); // 结果:int
    


  • 一个“Class”对象实际上表示的时一个类型,而这个类型未必一定是一种类;
    如:int不是一个类,但int.Class是一个Class类型的对象;
  • Class类 实际上是一个泛型类,如:“Employee.class”的类型是“Class<Employee>”
  • 虚拟机为每个类型管理一个Class对象,因此可以利用“==”实现两个类对象的比较:
    	if(e.getClass() == Employee.class)
    

创建类的实例

“newInstance()”方法用于动态地创建一个类的实例:

	e.getClass().newInstance();		// e 是一个Employee对象
  • 将 forName 与 newInstance 配合使用,可以根据存储在字符串中的类名创建一个对象:
    	String s = "java.util.Random";
    	Object m = Class.forName(s).newInstance();
    

捕获异常

利用反射分析类的能力

在运行时使用反射分析对象

使用反射编写泛型数组代码

调用任意方法

继承的设计技巧