标签:强制 abs ons 限制 print bst 枚举 java 管理
继承(inheritance)
的基本思想是,可以基于已有的类创建新的类。继承已存在的类就是复用(继承)这些类的方法,而且可以增加一些新的方法和字段,使新类能够适应新的情况。
反射(reflection)
是指在程序运行期间更多地了解类及其属性的能力。
Java
中,使用关键字extends
表示继承。
public class Manager extends Employee {
// added methods and fields
}
这个已存在的类称为超类(superclass)
、基类(base class)
或父类(parent class)
,新类称为子类(subclass)
、派生类(derived class)
或孩子类(child clsaa)
。
应该将最一般的方法放在超类中,将更特殊的方法放在子类中。在子类中可以增加字段、增加方法或覆盖超类的方法,不过继承绝对不会删除任何字段或者方法。
子类不能直接访问超类的私有字段,必须使用关键字super
来调用超类的公共接口。
子类可以提供一个新的方法来覆盖超类中同名的方法:
public double getSalary () {
double baseSalary = super.getSalary();
return baseSalary + bonus;
}
子类构造器:
public Manager (String name, double salary, int year, int month, int day) {
super(name, salary, year, month, day);
bonus = 0;
}
使用super
调用构造器的语句必须是子类构造器的第一条语句。
如果子类的构造器没有显式的调用超类的构造器,将自动的调用超类的无参数构造器。
关键字this
的两种含义:
关键字super
的两种含义:
一个对象变量(例如,变量e
)可以指示多种实际类型的现象称为多态(polymorphism)
。在运行时能够自动地选择适当的方法,称为动态绑定(dynamic binding)
。
Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
boss.setBonus(5000);
var staff = new Employee[3];
staff[0] = boss;
staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
staff[2] = new Employee("Tony Tester", 40000, 1990, 3, 15);
for (Employee e : staff)
System.out.println(e.getName + " " + e.getSalary);
继承并不仅限于一个层次。由一个公共超类派生出来的所有类的集合称为继承层次(inheritance hierarchy)
。在继承层次中,从某个特定的类到其祖先的路径称为该类的继承链(inheritance chain)
。
"is-a"
规则的另一种表述是替换原则,它指出程序中出现超类对象的任何地方都可以使用子类对象替换。在Java
程序设计语言中,对象变量是多态的。不过,不能将超类的引用赋给子类变量。
假设要调用x.f(args)
,隐式参数x
声明为类C
的一个对象。方法调用的过程如下:
private
方法、static
方法、final
方法或者构造器,那么编译器将可以准确的知道应该调用哪个方法。这称为静态绑定(static binding)
。x
所引用对象的实际类型对应的那个方法。动态绑定有一个非常重要的特性:无须对现有的代码进行修改就可以对程序进行扩展。
运行子类将覆盖方法的返回类型修改为原返回类型的子类型。
在覆盖一个方法的时候,子类方法不能低于超类方法的可见性。
不允许被扩展的类被称为final
类。
public final class Executive extends Manager {
...
}
如果类中的某个特定方法声明为final
,那么子类就不能覆盖这个方法。
如果将一个类声明为final
,其中的方法会自动地成为final
,字段不会。
进行强制类型转换地唯一原因是:要在暂时忽视对象的实际类型之后使用对象的全部功能。
将一个子类的引用赋给一个超类变量,编译器是允许的。但是将一个超类的引用赋给一个子类变量时,就承诺过多了,必须进行强制类型转换。
强制类型转换的原则:
instanceof
进行检查if (staff[1] instanceof Manager) {
boss = (Manager) staff[1];
...
}
包含一个或多个抽象方法的类本身必须被声明为抽象的。
public abstract class Person {
...
public abstract String getDescription() {};
}
除了抽象方法之外,抽象类还可以包含字段和具体方法。
即使不含抽象方法,也可以将类声明为抽象类。
抽象类不能实例化,即不能创建这个类的对象。
可以定义一个抽象类的对象变量,但是这个变量只能引用非抽象子类的对象。
抽象方法充当占位方法的角色,它们在子类中具体实现。
扩展抽象类有两种选择:
Java
中的4个访问控制修饰符:
private
public
protected
Object
类:所有类的超类Object
类是Java
中所有类的始祖,在Java
中每个类都扩展了Object
。
可以使用Object
类型的变量引用任何类型的对象。在Java
中,只有基本类型不是对象,例如,数值、字符和布尔类型的值。
所有的数组类型,不管是对象数组还是基本类型的数组都扩展了Object
类。
Object obj = new Employee("Harry Hacker", 50000);
Employee[] staff = new Employee[10];
obj = staff; // OK
obj = new int[10]; // OK
Object
类中实现的equals
方法可以确定两个对象引用是否相等。如果两个对象引用是相等的,这两个对象就相等。如果对象可能为null
,可以使用Objects.equals
方法进行判断。
在子类中定义equals
方法时,可以首先调用超类的equals
,然后再比较子类中新增的实例字段。
编写一个完美的equals
方法的建议:
otherObject
,稍后需要将它强制转换成另一个名为other
的变量。this
与otherObject
是否相等:if (this == otherObject) return false;
otherObject
是否为null
,如果是null
,则返回false
。if (otherObject == null) return false;
this
与otherObject
的类。如果equals
的语义可以在子类中改变,就使用getClass
检测:if (getClass() != otherObject.getClass()) return false;
如果所有的子类都有相同的相等性语义,可以使用instanceof
检测:if (!(otherObject instanceof ClassName)) return false;
otherObject
强制转换为相应类类型的变量:ClassName other = (ClassName) otherObject;
==
比较基本类型字段,使用Objects.equals
比较对象字段。如果所有的字段都匹配,就返回true
;否则返回false
。return field1 == other.field1
&& Objects.equals(field2, other.field2)
&& ...;
散列码(hash code)
是由对象导出的一个整型值。散列码是没有规律的。
由于hashCode
方法定义在Object
类中,因此每个对象都有一个默认的散列码,其值由对象的存储地址得出。
字符串的散列码是由内容导出的。
Employee
类的hashCode
方法:
public class Empoyee {
public int hashCode() {
return 7 * name.hashCode()
+ 11 * new Double(salary).hashCode()
+ 13 * hireDay.hashCode();
}
...
}
进阶版:
null
安全的方法Objects.hashCode
,如果参数为null
,这个方法会返回0
。Double.hashCode
来避免创建Double
对象。public int hashCode() {
return 7 * Objects.hashCode(name)
+ 11 * Double.hashCode(salary)
+ 13 * Objects.hashCode(hireDay);
}
再次进阶版:
Objects.hash
方法public int hashCode() {
return Objects.hash(name, salary, hireDay);
}
两个相等的对象要求返回相等的散列码。
Object
类有toString
方法,绝大多数(但不是全部)的toString
方法都遵循这样的格式:类的名字,随后是一对方括号括起来的字段值。
设计的子类应该定义自己的toString
方法,并加入子类的字段。
随处可见toString
方法的主要原因是:只要对象与一个字符串通过操作符"+"
连接起来,Java
编译器就会自动地调用toString
方法来获得这个对象的字符串描述。
如果x
是一个任意对象,并调用:
System.out.println(x);
println
方法就会简单地调用x.toString()
,并打印输出得到的字符串。
打印数组可以调用静态方法:Arrays.toString
。打印多维数组,需要调用Arrays.deepToString
静态方法。
ArrayList
是一个有类型参数的泛型类。ArrayList
类类似于数组,但在添加或删除元素时,可以自动调整大小。
声明数组列表:
ArrayList<Employee> staff = new ArrayList<Employee>();
在Java 10
中,可以使用var
关键字,以避免重复写类名:
var staff = new ArrayList<Employee>();
如果没有使用var
关键字,可以省去右边的类型参数:
ArrayList<Employee> staff = new ArrayList<>();
这称为“菱形”语法。
add
方法可以将元素添加到数组列表中,如果内部数组已经满了,数组列表就会自动创建一个更大更大的数组,并将所有的对象从较小的数组中拷贝到较大的数组中。
如果已经知道或能够估计除数组可能存储的元素数量,就可以在填充数组之前调用ensureCapacity
方法。
size
方法将返回数组列表中包含的实际元素个数。
trimToSize
方法将存储块的大小调整为保存当前元素数量所需要的存储空间,垃圾回收器将回收多余的存储空间。
add
方法为数组列表增加新元素,get
方法获取第i个元素,set
方法可以替换数组中已经加入的元素,remove
方法从数组列表中删除一个元素。使用toArray
方法可以将数组元素拷贝到一个数组中。
可以使用for each
循环遍历数组列表的内容。
出于兼容性的考虑,编译器检查到没有发现违反规则的现象之后,就将所有的类型化数组列表转换成原始ArrayList
对象。
所有的基本类型都有一个与之对应的类:IntegerL、Long、Float、Double、Short、Byte、Character、Boolean
(前6个类派生于公共的超类Number
),这些类称为包装器。包装器类是不可变的,一旦构造了包装器,就不允许更改包装在其中的值。同时,包装器类还是final
,因此不能派生它们的子类。
加入定义一个整型数组列表,尖括号中的类型参数不允许是基本类型,但是可以使用包装器类。
var list = new ArrayList<Integer>();
自动装箱:
list.add(3);
会自动地变换成:
list.add(Integer.valueOf(3));
当将一个Integer
对象赋给一个int
值时,将会自动地拆箱:
int n = list.get(i);
会转换成:
int n = list.get(i).intValue();
自动装箱和拆箱也适用于算数表达式。
Integer n = 3;
n++;
比较两个包装器对象时要调用equals
方法。
在一个条件表达式中混合使用Integer
和Double
类型,Integer
值就会拆箱,提升为double
,再装箱成Double
。
装箱和拆箱是编译器要做的工作,而不是虚拟机。
使用静态方法parseInt
可以将字符串转换成整型。
int x = Integer.parseInt(s);
printf
方法的定义:
public class PrintStream {
public PrintfStream printf(String fmt, Object... args) {return format(fmt, args)};
}
printf
方法接收两个参数,一个是格式字符串,另一个是Object[]
数组,其中保存着所有其他参数。
如果一个已有方法的最后一个参数是数组,可以把它重新定义为有可变参数的方法,而不会破坏任何已有的代码。
一个典型的例子:
public enum Size {SMALL, MEDIUM, LARGE, EXTRA_LARGE};
这个声明定义的类型是一个类,它刚好有4个实例,不可能构造新的对象。
枚举的构造器总是私有的。可以省略private
修饰符。如果声明一个enum
构造器为public
或protected
,会出现语法错误。
能够分析类能力的程序称为反射(reflective)
,反射可以用来:
Method
对象在程序运行期间,Java
运行时系统始终为所有对象维护一个运行时类型标识。这个信息会跟踪每个对象所属的类。保存这些信息的类名为Class
。
获得Class
类对象的三种方法:
Object
类中的getClass()
方法将会返回一个Class
类型的实例。Employee e;
...
Class cl = e.getClass();
forName
获得类名对应的Class
对象。String className = "java.util.Random";
Class cl = Class.forName(className);
无论何时使用forName
方法,都应该提供一个异常处理器。T
是任意类型的Java
类型(或void
关键字),T.class
将代表匹配的类对象。Class cl1 = Random.class;
Class cl2 = int.class;
Class cl3 = Double[].class;
一个Class
对象实际上表示的是一个类型,这可能是类,也可能不是类。Class
类实际上是一个泛型类。例如,Employee.class
的类型是Class<Employee>
。虚拟机为每个类型管理一个唯一的Class
对象。因此,可以利用==
运算符实现两个类对象的比较。
if (e.getClass == Employee.class) ...
如果有一个Class
类型的对象,可以用它构造类的实例。调用getConstructor
方法将得到一个Constructor
类型的对象,然后使用newInstance
方法来构造一个实例。
String className = "java.util.Random"; // or any other name of a class with
// a no-arg constructor
Class cl = Class.forName(className);
Object obj = cl.getConstructor().newInstance();
如果这个类没有无参数的构造器,getConstructor
方法会抛出一个异常。
异常有两种类型:
(checked)
异常:编译器将会检查程序员是否知道这个异常并做好准备来处理后果。(unchecked)
异常:越界错误或访问null
引用等。编译器并不期望程序员为这些异常提供处理器,而是应该集中精力避免这些错误的发生。异常处理最简单的一个策略:
如果一个方法包含一条可能抛出检查型异常的语句,则在方法名上增加一个throws
子句。调用这个方法的任何方法也都需要一个throws
声明。这也包括main
方法。如果一个异常确实出现,main
方法将终止并提供一个堆栈轨迹。
public static void doSomethingWithClass(String name) throws ReflectiveOperationException {
Class cl = Class.forName(name); // might throw exception
do something with cl
}
在Java
中,与类有关联的文件被称为资源。Class
类提供了一个很有用的服务可以查找资源文件:
Class
对象,例如,ResourceTest.class
。ImageIcon
类的getImage
方法,接受描述资源位置的URL
。则要调用URL url = cl.getResource("about.gif");
getResourceAsStream
方法得到一个输入流来读取文件中的数据。利用反射分析类的能力
在java.lang.reflect
包中有三个类Field
、Method
和Constructor
分别用于描述类的字段、方法和构造器。
Field
类的常用方法:getName、getType、getModifiers
。Constructor
类的常用方法:getName、getModifiers、getParameterTypes
。Method
类的常用方法:getName、getModifiers、getReturnType、getParameterTypes
。可以利用java.lang.reflect
包中的Modifier
类的静态方法分析getModifiers
返回的整数。如isPublic、isPrivate、isFinal
,也可以使用Modifier.toString
方法。
Class
类中的getFields、getConstructors、getMethods
方法将分别返回这个类支持的公共字段、构造器和方法的数组,其中包括超类的公共成员。
Class
类的getDeclareFields、getDeclareConstructors、getDeclareMethods
方法将分别返回类中声明的全部字段、构造器和方法的数组,其中包括私有成员、包成员和受保护的成员,但不包括超类的成员。
使用反射在运行时分析对象
如果f
是一个Field
类型的对象,obj
是某个包含f
字段的类的对象,f.get(obj)
将返回一个对象,其值为obj
的当前字段值。
var harry = new Employee("Harry Hacker", 50000, 10, 1, 1989);
Class cl = harry.getClass(); // the class object representing Employee
Field f = cl.getDeclaredField("name"); // the name field of the Employee class
Object v = f.get(harry); // the value of the name field of the harry object, i.e., the String object "Harry Hacker"
调用f.set(obj, value)
将把对象obj
的f
表示的字段设置为新值。
只能对可以访问的字段使用get
和set
方法。反射机制的默认行为受限于Java
的访问控制。
可以调用Field
、Method
或Constructor
对象的setAccessible
方法覆盖Java
的访问控制。setAccessible
方式是AccessibleObject
类中的一个方法,它是Field
、Method
和Constructor
类的公共超类。
使用反射编写泛型数组代码
java.lang.reflect
包中Array
类的静态方法newInstance
,能够构造一个新数组,调用此方法时必须提供的参数,一是数组的元素类型,二是数组的长度。
数组的长度可以通过Array
类的静态方法getLength
方法获得。
获得新数组元素类型的步骤:
a
数组的类对象Class
类的getComponentType
方法(只为表示数组的类对象定义了这个方法)确定数组的正确类型public static Object goodCopyOf(Object a, int newLength) {
Class cl = a.getClass();
if (!cl.isArray()) return null;
Class componentType = cl.getComponentType();
int length = Array.getLength(a);
Object newArray = Array.newInstance(componentType, length);
System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));
}
这个方法可以用来扩展任意类型的数组,而不仅是对象数组。
该方法的参数应该声明为Object
类型,而不要声明为对象型数组(Object[])
。整型数组类型int[]
可以转换成Object
,但是不能转换成对象数组。
调用任意方法和构造器
Method
类有一个invoke
方法,允许调用包装在当前Method
对象中的方法。invoke
方法的签名为:
Object invoke(Object obj, Object... args)
第一个参数是隐式参数,其余的对象提供了显式参数。对于静态方法,第一个参数可以忽略,即可以将它设置为null
。
invoke
方法的返回类型是Object
对象,必须相应地完成强制类型转换再使用。如果返回类型是基本类型,invoke
方法会返回其包装器类型。
获得Method
对象的方法:
getDeclareMethods
方法Class
类的getMethod
方法。由于有可能存在若干个同名的方法,调用该方法时必须提供想要的方法的参数类型。getMethod
方法的签名为:Method getMethod(String name, Class... parameterTypes)
可以使用类似的方法调用任意的构造器。将构造器的参数类型提供给Class.getConstructor
方法,并把参数值提供给Constructor.newInstance
方法。
Class cl = Random.class; // or any other class with a constructor that
// accepts a long parameter
Constructor cons = cl.getConstructor(long.class);
Object obj = cons.newInstance(42L);
将公共操作和字段放在超类中。
不要使用受保护的字段。
protected
字段,从而破坏了封装性。protected
字段,不管它们是否为这个类的子类。使用继承实现"is-a"
关系。
除非所有继承的方法都有意义,否则不要使用继承。
在覆盖方法时,不要改变预期的行为。
使用多态,而不要使用类型信息。
不要滥用反射。
标签:强制 abs ons 限制 print bst 枚举 java 管理
原文地址:https://www.cnblogs.com/harr/p/12563281.html