avatar

目录
JAVA核心技术卷I(继承)

JAVA核心技术卷I(继承)

一、类、超类、子类

1.1 覆盖方法

派生类继承了父类的方法,但是,如果觉得父类的方法不好用,可以自己重新设计一个方法名和参数与父类方法一样的方法,这叫做覆盖。如果覆盖中需要调用父类的方法可以使用 super 关键字,如:

java
void getSomething() {
super.getSomething();
}

当然对于构造器方法,需要在第一行用super给父类字段赋值:

java
public class Cat extend Animal {
public Cat(String name, int age) {
super(name, age);
}
// other method ...
}

1.2 多态性

父类引用变量可以赋值子类对象,但是编译器只允许调用在类中声明的对象。

java
public class ClassA {
public void method(){}
}
public class ClassB extends ClassA {
public void newMethod(){}
}

ClassA a = new ClassB(); // 允许
a.method(); // 允许
a.newMethod(); // 错误

1.3 final 阻止继承

阻止利用某个类定义子类,可以用:

java
public final class Person{}		// Person类将无法被继承

也可以使用在方法上,那么就无法被覆盖了。一个类被声明为final,那么其方法自动变为final,而不包括字段。

1.4 强制类型转换

java
if (obj1 instanceof obj2) {
Obj2 o = (Obj2)obj1;
}

1.5 抽象类

包含一个或多个抽象方法的类本身必须被声明为抽象类abstract

抽象类可以有具体的方法和字段。抽象方法充当占位方法的角色,在子类中被部分实现,或全部实现。如果部分实现,那么该子类依旧是抽象类,需要声明 abstarct

抽象类不能被实例化。可以创建具体子类的对象。但是可以定义抽象类的对象变量,即多态特性。

1.6 访问控制修饰符

  • private:仅对本类可见
  • public:对外部完全可见
  • protected:对本包和所有子类可见
  • 默认(很遗憾),不需要修饰符:对本包可见

二、Object 所有类的超类

2.1 equals

Object类中实现的 equals 方法将确定两个对象引用是否相等。但是有时候我们需要比较的是两个对象的状态(字段值)是否全部相等。

完美 equals 方法的建议

  1. 显式参数命名为otherObject,稍后需要将它强制转换成另一个名为other的变量

  2. 检查this与otherObject是否相等:

    java
    if (this == otherObject) return true;

    这条语句只是一个优化。

  3. 检测otherObject是否为null,如果为null,返回false。这项检测很有必要。【是吗?】

  4. 比较this与otherObject的类。如果equals的语义可以在子类中改变,就使用getClass检测:

    java
    if (getClass() != otherObject.getClass()) return false;

    如果所有的子类都有相同的相等性语义,可以使用instanceof检测:

    java
    if (!(otherObject instanceof ClassName)) return false;
  5. 将otherObject强制转换为相应类型的变量:

    java
    ClassName other = (ClassName)otherObject;
  6. 现在根据相等性概念的要求来比较字段。使用==比较基本类型字段,使用Objects.equals比较对象字段。

    java
    // JAVA7 java.util.Objects
    public static boolean equals(Object a, Object b) {
    return (a == b) || (a != null && a.equals(b));
    }

如果所有的字段都匹配,返回true,否则返回false。

如果在子类中重新定义equals,就要在其中包含一个super.equals(other)调用。

提示:对于数组可以用Arrays.equals(xxx[] a, xxx[] b)

2.2 hashCode

需要组合多个散列值时,可以调用Objects.hash并提供所有这些参数。

java
// java.util.Objects.hash
public static int hash(Object... values) {
return Arrays.hashCode(values);
}

// Arrays.hashCode
public static int hashCode(Object a[]) {
if (a == null)
return 0;

int result = 1;

for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());

return result;
}

equals与hashCode的定义必须相容:如果x.equals(y)返回true,那么x.hashCode() 必须与y.hashCode()返回相同的值。

2.3 toString

输出类名

尽量使用 getClass().getName() 来获得类名的字符串。

数组的toString

java
int[] a = {1, 2, 3, 4, 5};
System.out.println(a); // 打印 [I@14ae5a5
System.out.println(Arrays.toString(a)); // 打印 [1, 2, 3, 4, 5]

三、泛型数组列表

3.1 ArrayList 数组列表

常用API:

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

  • ArrayList<E>(int initialCapacity):构造一个指定容量的数组列表

  • void add(E obj):末尾追加元素

  • int size():返回个数

  • void ensureCapacity(int capacity):确保数组列表在不重新分配内部存储数组的情况下,有足够的容量存储给定数量的元素(可以理解为变为普通的数组的定长)

  • void trimToSize():将存储容量减为当前大小(存储容量>=当前大小)

  • E set(int index, E obj):修改指定下标的类型,并返回之前的值

  • E get(int index):返回下标的元素

  • void add(int index, E obj)

  • E remove(int index)

    指定位置添加/删除,不过需要移动后面的所有元素,效率低(如果元素少不影响)

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

比如在老版本中有一个方法:

java
public void update(ArrayList list){}

你可以直接将对象传递给update方法:

java
ArrayList<Employee> staff = ...;
employeeDB.update(staff);

出于兼容性的考虑,编译器检查到没有发现违反规则的现象后,就将所有的类型化数组列表转换成原始ArrayLsit对象(即不管有没有添加泛型,最后都会变成AarrayList,泛型只是用来编译时候的检查)。在程序运行时,所有数组列表都是一样的,即虚拟机中没有类型参数。

四、对象包装器与自动装箱

4.1 包装器

  • Integer

  • Long

  • Float

  • Double

  • Short

  • Byte

    以上6种,派生于公共超类Number

  • Character:char

  • Boolean

4.2 自动装箱

java
var list = new ArrayList<Integer>();	// 这里不能用int
list.add(3);
// 将自动变成:
list.add(Integer.valueOf(3));

4.3 自动拆箱

java
int n = list.get(i);
// 转换成:
int n = list.get(i).intValue();

4.4 比较

如果直接用 == 是比较内存地址,但是有时候也许会成功。因此推荐用equals方法来比较。

java
Integer a = 100;
Integer b = 100;
System.out.println(a == b); // 输出true
Integer a = 128;
Integer b = 128;
System.out.println(a == b); // 输出false

注意:自动装箱规范要求boolean、byte、char <= 127,介于 -128和127之间的short和int被包装到固定的对象中。例如,如果将a和b(都是Integer类型)初始化为100,那么他们的比较结果(即,==)一定成功

4.5 valueOf

尽量使用valueOf构建包装类,而不是用new。

java
public static Integer valueOf(int i) {
// 最小值为-128,最大值默认是127,也可以自由配置
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

可以看到,如果用valueOf方法来创建一个对象,那么会判断是否是在范围里,如果是,就从缓存种直接返回对象的引用,如果不是才去new一个新的对象。

4.6 常用API

Integer -> int

  • int intValue():返回int类型的值

int -> String

  • static String toString(int i):返回10进制数值
  • static String toString(int i, int radix):返回数值i的指定radix进制

String -> int

  • static int parseInt(String s)

  • static int parseInt(String s, int radix)

    返回字符串s表示的整数,第一个方法10进制,第二个指定进制。

String -> Integer

  • static Integer valueOf(String s)

  • static Integer valueOf(String s, int radix)

    返回Integer对象,字符串必须是10进制(第一个方法),或者指定进制(第二个方法)

五、枚举

5.1 定义

java
public enum Size {
// 这里相当于利用私有构造函数创建了枚举实例
SMALL("S"), MEDIUM("M"),LARGE("L"),EXTRA_LARGE("XL");

// 私有构造函数,必须是私有的
private Size(String abbreviatioin) {
this.abbreviatioin = abbreviatioin;
}

public String getAbbreviatioin() {
return abbreviatioin;
}

// 枚举类型的字段
private String abbreviatioin;
}

5.2 常用API

  • static Enum valueOf(Class enumClass, String name):返回给定类中有指定名字的枚举常量
  • String toString():返回枚举常量名
  • int ordinal():返回枚举常量在enum声明中的位置,位置从0开始计数
  • int compareTo(E other):如果枚举常量出现在other之前,返回一个负整数;

六、反射

6.1 Class 类

在启动时,包含main方法的类被加载。它会加载所有需要的类。这些被加载的类又要加载它们需要的类,以此类推。

6.1.1 获取Class类的三种方式

java
static Class forName(String className)
// 根据类名返回
java
Class cl = Employee.class;
// 直接通过 类名.class 获取
java
Class cl = employree.getClass();
// 通过对象的 getClass 方法获取

6.1.2 创建实例

java
// 获取构造器
Constructor getConstructor(Class... ParameterTypes)
// 创建实例
Object newInstance(Object... params)

6.2 资源

java.lang.Class

  • URL getResource(String name)

  • InputStream getResourceAsStream(String name)

    找到与类位于同一个位置的资源,返回一个可以用来加载资源的URL或者输入流。如果没有找到资源,返回null,所以不会抛出IO异常。

6.3 利用反射分析类

6.3.1 java.lang.Class

字段

  • Field[] getFields()

  • Field[] getDeclaredFields()

    getFields返回一个包含Field对象的数组,这些对象对应这个类或其超类的公共字段。而getDeclaredFields返回所有字段

方法

  • Method[] getMethods()

  • Method[] getDeclaredMethods()

    getMethods返回所有公共方法,包括超类继承来的。getDeclaredMethods返回这个类或接口的全部方法

构造器

  • Constructor[] getConstructors()

  • Constructor[] getDeclaredConstructors()

    返回Class对象的所有公共构造器(getConstructors),和全部构造器(getDeclaredConstructors)

6.3.2 java.lang.reflect.Field/Method/Constructor

  • Class getDeclaringClass()

    返回一个Class对象,表示定义了这个构造器、方法或字段的。(即哪个类定义了这个方法、构造器或字段)

  • Class[] getExceptionTypes():在Constructor和Method classes类中

    返回一个Class对象数组,其中各个对象表示这个方法所抛出的异常的类型

  • int getModifiers()

    返回一个整数,描述这个构造器、方法或字段的修饰符。使用Modifier类中的方法来分析这个返回值。(下面介绍)

  • String getName()

    返回一个构造器、字段或方法的名字的字符串

  • Class[] getParameterTypes():在Constructor和Method classes类中

    返回一个Class对象数组,里面各个对象表示参数的类型

  • Class getReturnType():在Method类中

    返回一个用于表示返回类型的Class对象

6.3.3 java.lang.reflect.Modifier

  • static String toString(int modifiers)

    返回一个字符串,包含对应modifiers中设置的修饰符

  • static boolean isAbstract(int modifiers)

  • static boolean isFinal(int modifiers)

  • static boolean isInterface(int modifiers)

  • static boolean isNative(int modifiers)

  • static boolean isPrivate(int modifiers)

  • static boolean isPublic(int modifiers)

  • static boolean isStatic(int modifiers)

  • static boolean isStrict(int modifiers)

  • static boolean isSynchronized(int modifiers)

  • static boolean isVolatile(int modifiers)

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

6.4.1 获取字段

java.lang.Class

  • Field getField(String name):根据名字获取公共字段
  • Field[] getFields()
  • Field getDeclaredField(String name):根据名字获取字段
  • Field[] getDeclaredFields()

6.4.2 获取/设置 字段值

java.lang.reflect.Field

  • Object get(Object obj):返回obj对象中,用这个Field对象描述的字段的值
  • void set(Object obj, Object newValue):更新这个obj对象的Feild描述的字段的值

6.4.3 取消访问标志

如果是私有字段,在调用上述get/set方法时,会有异常。这时候就要修改。‘

java.lang.reflect.AccessibleObject

  • void setAccessible(boolean flag):设置或取消这个可访问对象的可访问标志,如果拒接访问会抛出一个IllegaAccessExceptioin异常。【true为允许访问】
  • boolean isAccessible()
  • static void setAccessible(AccessibleObject array, boolean flag):便利方法,用于设置一个对象数组的可访问标志

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

java
// 其中a传入数组
public static Object goodCopyOf(Object a, int newLength) {
Class cl = a.getClass();
if (!cl.isArray()) {
return null;
}
Class componentType = cl.getConponentType();
int length = Array.getLength(a); // 获取数组a的长度,用静态方法
Object newArray = Array.newInstance(componentType, new Length);
System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));
return newArray;
}

6.6 调用任意方法和构造器

java.lang.reflect.Method

public Object invoke(Object implicitParameter, Object[] explicitParameters)

调用这个对象描述的方法,传入给定参数,并返回方法的返回值。对于静态方法,传入null作为隐式参数。

七、继承设计技巧

  1. 将公共操作和字段放在超类中
  2. 不要使用受保护的字段
  3. 使用继承实现“is-a”关系
  4. 除非所有继承的方法都有意义,否则不要使用继承
  5. 在覆盖方法时,不要改变预期的行为
  6. 使用多态,而不要使用类型信息
  7. 不要滥用反射
文章作者: IT小王
文章链接: https://wangbowen.cn/2020/05/14/JAVA%E6%A0%B8%E5%BF%83%E6%8A%80%E6%9C%AF%E5%8D%B7I%EF%BC%88%E7%BB%A7%E6%89%BF%EF%BC%89/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 IT小王

评论