标签:强制 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个访问控制修饰符:
privatepublicprotectedObject类:所有类的超类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