继承《Java核心技术 SE8》
类、超类、子类
继承
关键字extends表明正在构造的新类派生于一个已存在的类。已存在的类称为超类、基类、父类;新类称为子类、派生类或孩子类。
Java单继承,但继承层次不限。
在设计类的时候,将通用的方法放在超类中,将具有特殊用途的方法放在子类中。
子类使用@Override注解来重写父类方法;
使用super关键字可调用超类被覆写的方法,子类构造器首行super(...)可调用超类构造器,当子类的构造器没有显式地调用超类地构造器,则默认自动调用超类无参构造器。
多态
置换法则:程序中出现超类对象的任何地方都可以使用子类对象替换。
理解方法的调用:
- 获取所有可能被调用的候选方法,编译器一一列举子类中同名方法和超类中public的同名方法;
- 根据参数类型重载解析查询匹配的方法;
- 如果是private、static、final、构造器方法,编译器直接在子类中静态绑定;
- 与之对应的依赖隐式参数的实际类型,在运行时动态绑定,子类没有再从超类的方法表中找(虚拟机预先会为每个类创建一个方法表,其中列出所有方法签名和实际调用的方法);
- 虚拟机调用方法
阻止继承:final类和方法
将类或方法声明为final的主要目的:确保它们不会在子类中改变语义。
强制类型转换
需要使用子类特有方法时,需要进行类型转换,类型转换后就能使用对象的全部功能;
将一个值存入变量时,编译器将检测是否允许该操作,将一个子类的引用赋给一个超类变量,编译器是允许的。但将一个超类的引用赋给一个子类变量,必须进行类型转换,这样才能够通过运行时检查。
// 只能在继承层次内进行类型转换,超类转子类前应进行instanceof检查
if(staff[0] instanceof Manager){
Manager boss = (Manager) staff[0];
}
抽象类
抽象类不能实例化,包含一个或多个抽象方法的类本身必须被声明为抽象的,不包含抽象方法的类也可以声明为抽象类。
抽象类除了包含抽象方法之外,抽象类还可以包含具体的数据域和方法。
抽象方法充当着占位的角色,具体实现在子类中,扩展抽象类可以有两种选择:
- 在抽象类中定义部分抽象类方法或不定义抽象类方法,这样就必须将子类也标记为抽象类;
- 定义全部的抽象类,子类就不是抽象类了。
受保护访问
谨慎使用,对受保护的域进行修改,就必须通知那些其它包继承本类的程序员,违反了数据封装原则。
private:仅对本类可见;
public:所有类可见;
protected:对本包和所有子类可见,最好的示例:Object类中的clone方法;
默认,不需要修饰符:对本包可见;
Object所有类的超类
Java中的每个类都是由Object拓展而来的,如果没有明确指出,就认为Object是这个类的超类,所有可以使用Object类型的变量引用任何类型的对象,当然Object类型的变量只能用作各种值得通用持有者,要相对其中得内容进行具体的操作,还需要清楚对象的原始类型并进行相应的类型转换。
在Java中,只有基本类型不是对象,但不管基本数据类型还是对象都可以使用Object对象进行引用。
equals
重写equals就必须重写hashCode,以便用户可以把对象插入到散列表中。
Object类equals方法是判断两个变量是否指向一个对象。
// 推荐使用,能避免参数为null比较失败的情形
Objects.equals(Object a, Object b);
// 比较两个数组的长度和对应位置的元素是否一致
Arrays.equals(type[] a, type[] b);
Java语言规范要求equals方法具有下面的特性:
自反性,对称性、传递性、一致性、非空引用equals(null)返回fasle。
重写equals的建议:
- 显式参数命名为otherObject,稍后需要将它转换为other变量;
- 检测this和otherObject是否引用同一个对象;
- 检测otherObject是否为null
- 比较this和otherObject是否属于同一个类
- 如果子类重写了equals,就使用getClass检测
- 如果使用超类的equals方法,就使用instanceof检测
- 将otherObject转换为相应的类类型变量other
- 对域进行比较,基本类型域使用==,对象域使用equals
hashCode
重写equals就必须重写hashCode,以便用户可以把对象插入到散列表中。
散列码hash code是由对象导出的一个整型值,散列码是没有规律的。
Object类提供的hashCode值是对象的存储地址。
// null安全,null返回0
Objects.hashCode();
// 组合对个散列值
Objects.hash(Object ... values);
//避免创建Double对象
Double.hashCode(double d);
//计算数组的散列码
Arrays.hashCode(...);
equals与hashCode的定义必须一致,a.equals(b)为true,则a、b的hashCode必须一致。
toString
返回对象值得字符串,大多数toString方法都遵循格式:类的名字,随后是一对括号括起来的值。
Object类的toString,打印的是hashCode
Student@be2c6
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
lombok的@ToString
Student(id=100, name=张三)
idea的generate toString
Student{id=100, name='张三'}
随处可见toString方法的主要原因是:只要对象与一个字符串通过操作符“+”连接起来,Java编译就会自动地调用toString方法,以便获取这个对象地字符串描述。
数组虽然继承了Object类的toString方法,但数组类型还是按照旧格式打印的。
int[] ints = {1,2,3};
//[I@3f3afe78
System.out.println(ints);
//[1, 2, 3]
System.out.println(Arrays.toString(ints));
强烈建议为自定义的实体类增加toStirng方法,来提高日志的可读性。
getClass
返回对象信息的类对象,内容封装在Class类中。
java.lang.Class
// 返回类的名字
String getClass();
// 返回超类信息
Class getSuperClass();
泛型数组列表
数组在初始化时必须指定数组大小。
ArrayList的类,使用起来像数组,但在添加、删除元素时,具有自动调节数组容量的功能,而不需要为此编写任何代码。
ArrayList是一个采用类型参数的泛型类。
ArrayList<Integer> list = new ArrayList();
list.add(1);
list.zise();
list.get(0);
list.remove(1);
// 调整存储区域大小为当前实际存储大小
list.trimToSize();
类型化与原始数组列表的兼容性
由类型参数和没有类型参数的数组列表交互操作会提示警告性文字信息,鉴于兼容性的考虑,编译器在对类型转换进行检查后,如果没有违法规则的现象,会将所有的类型化数组列表转换为原始的ArrayList对象。
研究警告性提示,确保不会造成严重的后果,使用@SuppressWarnings("unchecked")标注告诉编译器忽略指定的警告。
// Unchecked assignment: 'java.util.ArrayList' to 'java.util.ArrayList<java.lang.Integer>
ArrayList<Integer> arrayList = (ArrayList<Integer>) listQuery();
// Unchecked cast: 'java.util.ArrayList' to 'java.util.ArrayList<java.lang.Integer>'
ArrayList<Integer> arrayList = (ArrayList<Integer>) listQuery();
@SuppressWarnings("unchecked")
ArrayList<Integer> arrayList = listQuery();
public static ArrayList listQuery(){
return new ArrayList();
}
对象包装器与自动装箱
所有的基本类型都有一个与之对应的类,这些类称为包装器,包装器类和String类一样,被final修饰,是不可变的类。
对应泛型数组列表,不允许使用基本类型,就可以使用包装器类来替换。
ArrayList
list.add(31);
// 转换过程称之为自动装箱
list.add(Integet.valueOf(31));
int a = list.get(0);
// 转换过程称之为自动拆箱
int a = list.get(0).intValue();
两个包装器对象进行比较时调用equals方法,因为自动装箱规范要求boolen、byte、char ≤127,介于-128~127之间的整型值都会被固定到固定的对象中,浮点值不会。
装箱拆箱是编译器认可的,而不是虚拟机。
java.lang.Integer
static int parseInt(String);
static Integer valueOf(String);
int intValue();
参数数量可变方法
省略号...表示这个方法可以接收任意数量的参数,实际上Object...和Object[]完全一样。
hello("say","hello");
public static void hello(Object... values) {
}
hello(new Object[]{"say","hello"});
public static void hi(Object[] values) {
}
枚举类
public enum Size { SMALL , MEDIUM, LARGE, EXTRAJARGE };
所有的枚举类型都是Enum类的子类。
在比较两个枚举值时,请直接使用“==”。
在枚举类中可以添加一些域、构造器、方法,构造器是构造枚举常量使用。
//每个枚举类型默认都有一个静态的valuse方法,返回包含全部枚举值的数组
Size[] values = Size.values();
String small = Size.SMALL.toString();
Size large = Size.valueOf("LARGE");
java.lang.Enum
//根据枚举常量名找到枚举常量
static Enum valueOf(Class enumClass, String name);
// 返回枚举常量名
String toString();
// 返回声明顺序,从0开始
int ordinal();
// 声明顺序相减
int compareTo(E other);
反射
反射是指根据对象获取类信息的机制,Java提供了java.lang.reflect反射库,可以用来:
- 反射+注解,导出excel。可以继续尝试导入excel
- 编写一个toString方法供所有类使用
- 实现通用的数组操作代码
Class类
Java程序运行时系统始终为所有的对象维护一个被称为运行时的类型标识,这个信息跟踪着每个对象所属的类。Java虚拟机利用运行时类型信息选择相应的方法执行,保存类信息的类是Class类。
java.lang.Class
// 返回类和超类的公有域对象
Field[] getFields();
// 返回类的所有域对象
Field[] getDeclaredFields();
//返回类公开的构造器
Constructor[] getConstructors();
// 返回类所有的构造器
Constructor[] getDeclaredConstructors();
// 返回类和超类的所有公开方法,子类重写就不再显示超类的方法
Method[] getMethods();
// 返回类所有方法
Method[] getDeclaredMethods();
// 判断当前类是否为数组
boolean isArray();
// 判断当前类是否是基本数据包装类型
Boolean isPrimitive();
// 返回数组元素类型
Class<?> getComponentType();
虚拟机为每个类型管理一个Class对象,可以使用==比对两个class对象。
//class com.example.demo.entity.Student
Class<? extends Student> aClass = student.getClass();
Class<Student> bClass = Student.class;
Class<?> cClass = Class.forName("com.example.demo.entity.Student");
// int.class是一个Class类型对象,一个Class对象表示的一个类型,但这个类型不一定是类。
Class i = int.class;
// 历史原因,数组返回的名字比较奇怪:class [I
class intsClass = new int[]{1,2}.getClass();
// 历史原因,数组返回的名字比较奇怪:class [Ljava.lang.Double;
class doublesClass = new Double[]{1.0,2.0}.getClass();
// 调用无参构造器创建实例,@Deprecated(since="9")
Student student = aClass.newInstance();
// 建议通过构造器创建实例
Student student1 = aClass.getConstructor().newInstance();
利用反射分析类的能力
反射机制最重要的内容:检测类的结构
java.lang.reflect.Field:描述类的域;
java.lang.reflect.Constructor:描述类的构造器
java.lang.reflect.Method:描述类的方法
//[Field,Constructor,Method]返冋所属的Class对象。
Class getDeclaringClass( )
//[Constructor和Method]返回抛出的异常类型的Class对象数组。
Class[] getExceptionTypes()
//[Field,Constructor,Method]返回修饰符的整型数值,Modifier类静态方法解析
int getModifiers( )
//[Field,Constructor,Method],返回当前构造器、方法或域名称的字符串。
String getName( )
//[Constructor和Method]返回参数类型的Class对象数组。
Class[] getParameterTypes ( )
//[Method] 返回返回值类型的Class对象
Class getReturnType( )
java.lang.reflect.Modifiei:描述修饰符
// 1 public,2 private,4 protected,8 static
static Stirng toString(int modifiers);
static boolean isAbstract(int modifiers )
static boolean isFinal (int modifiers )
static boolean islnterface(int modifiers )
static boolean isNative(int modifiers )
static boolean isPrivate(int modifiers )
static boolean isProtected(int modifiers )
static boolean isPublic(int modifiers )
static boolean isStatic(int modifiers )
// 是否是严格的浮点计算修饰符
static boolean isStrict(int modifiers )
static boolean isSynchronized(int modifiers )
static boolean isVolati1e(int modifiers )
在运行时使用反射分析对象
查看对象的属性值
反射机制的默认行为受到Java的访问控制,只允许查看对象有哪些域,而不允许读它们的值。
Student student = new Student(1L,25,"张三","法外狂徒");
Class<? extends Student> aClass = student.getClass();
Field[] fields = aClass.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
Object o = field.get(student);
/*
// 基本数据类型可直接生成
double aDouble = field.getDouble(student);
// 能查看也能修改
field.setLong(student,100L);
*/
...
}
在Field、Constructor、Method类的公共超类AccessibleObject类中有个为调试、持久存储和相似机制提供的setAccsessible方法,启用后能够屏蔽Java语言检查、覆盖访问权限。
java.lang.reflect.AccessibleObject
//IllegalAccessException: cannot access a member of class with modifiers "private"
// 设置Java安全检查开关
void setAccessible(boolean flag);
// 检查Java安全检查开关
boolean isAccessible();
// 批量设置安全检查设置
static void setAssessible(AccessibleObject[] array, boolean flag);
java.lang.reflect.Array
// 获取array的长度
int Array.getLength(Object obj);
// 根据索引获取array中的值
Object Array.get(Object obj, int index);
使用反射编写泛型数组代码
两种扩容数组的方式
// [1, 2, 3, 4, 5]
int[] ints = new int[]{1,2,3,4,5,6,7,8,9,0};
int[] copy = Arrays.copyOf(ints, 5);
System.out.println(Arrays.toString(copy));
// [0, 0, 0, 6, 7, 8, 9, 0, 0, 0]
int[] ints = new int[]{1,2,3,4,5,6,7,8,9,0};
int[] copy = new int[10];
System.arraycopy(ints,5,copy,3,4);
java.lang.reflect.Array类允许动态地创建数组。
java.lang.reflect.Array
// 根据索引查值
static Object get(Object array, int index);
static xxx getXxx(Object array, int index);
// 更新指定索引的值
static void set(Object array, int index, Object newValue);
static void setXxx(Object array, int index, Object newValue);
// 返回数组长度
static int getLength(Object array);
// 生成指定类型、长度、维数的新数组
static Object newInstance(Class compoentType, int length);
static Object newInstance(Class compoentType,int[] length)
// 自动装拆箱不适应于:int[]——> [0,0] 和Integer[]——>[null,null]
int[] intArray = (int[]) Array.newInstance(int.class, 10);
Integer[] integerArray = (Integer[]) Array.newInstance(Integer.class, 10);
调用任意方法
反射机制允许你调用任意方法。
Method类中有个invoke方法,它允许你调用当前对象的方法。
// obj是当前对象,args是方法参数
Object invoke(object obj, Object... args);
Method method = Math.class.getMethod("pow", double.class, double.class);
// 64.0
Object invoke1 = method.invoke(null, 2, 6);
继承的设计技巧
- 将公共操作和域放在超类;
- 不要使用受保护的域;
- 避免滥用,实现了"is-a"关系的类再考虑继承;
- 除非所有继承的方法都有意义,否则不要使用继承
- 在覆盖方法时,不要改变预期的行为
- 使用多态,而非类型信息,使用多态方法或接口编写的代码比对使用多种类型检测的代码更加易于维护和拓展。
- 不要过多使用反射
- 反射机制可以在运行时查看域或方法,编写出更通用性的程序,但编译器很难发现其中的错误,只有才运行时才能发现错误。

浙公网安备 33010602011771号