7.1 继 承

179 阅读8分钟

1. 类 超类 子类

1.1 超类和子类

子类是父类的扩展,功能比超类更多,超类定义通用的属性。

1.2 覆盖方法

父类中的方法不一定适用于子类,子类可以重写父类方法。

class FuLei{
     private String str="父类";//私有 
         public String getStr(){ return this.str; } 
     }
     public class ZiLei extends FuLei{ 
         public String str2="子类"; 
         
         public String getStr(){ 
             //return getStr()+this.str2;//错误,java.lang.StackOverflowError 
             return super.getStr()+this.str2; 
         } 
         
         public static void main(String[] args) { 
             ZiLei ziLei=new ZiLei(); System.out.println(ziLei.getStr()); 
     }
}

当子类实现了父类的方法,但是需要使用父类的方法,需要super关键字,调用父类的方法,super不是对象或者类引用,只是一个指示编译器调用对应类的关键字。

1.3 子类构造器

子类调用父类构造器 super(),不声明默认调用无参super()。

一个对象变量(例如, 变量 e ) 可以指示多种实际类型的现象被称为多态。

在运行时能够自动地选择调用哪个方法的现象称为动态绑定。

1.4 继承层次

继承不仅限于一个层次,可以层层继承。

1.5 多态

一个对象变量(例如, 变量 e ) 可以指示多种实际类型的现象被称为多态。

对象是多态的,一个子类引用可以升级成为一个父类引用。

但是数组中不建议这么使用

image.png

数组把子类当成一个父类来识别,所以没有子类的方法。

数组声明的时候不修改类型和引用

ZiLei[] arr=new ZiLei[5];//猫 
FuLei[] arr2=new FuLei[5];//动物 
arr2=arr;//父类=子类引用,相当于arr2现在是猫 
arr2[0]=new FuLei();//猫数组加了个动物,不对 //Exception in thread "main" java.lang.ArrayStoreException: 5行

1.6 方法的调用

修饰符 返回值 隐式参数 (显式参数)

方法调用时,先根据隐式参数 找到对应的类,然后根据方法名寻找,最后根据参数

1.7 抽象类

类自上而下继承,位于上层的类应该更具有通用性,子类在通用属性之上进行扩展,

1.8 受保护的访问

部分字段有保护private 如果是protected或是public 只要继承该类,修改会影响所有使用这个类的用户, 不符合OOP提倡的数据封装原则.

1 ) 仅对本类可见 private

2 ) 对所有类可见 public

3 ) 对本包和所有子类可见 protected

4 ) 对本包可见 不需要修饰符。

2 Object

2.1 基类

在 Java 中,只有基本类型 ( primitive types) 不是对象, 例如,数值、 字符和布尔类型的值都不是对象。

2.2 equals

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

1 ) 自反性:对于任何非空引用 x, x.equals(0) 应该返回 true

2 ) 对称性: 对于任何引用 x 和 y, 当且仅当 y.equals(x) 返回 true , x.equals(y) 也应该返回 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

如果正确的使用equals?

下面给出编写一个完美的 equals 方法的建议:

1 ) 显式参数命名为 otherObject, 稍后需要将它转换成另一个叫做 other 的变量。

2 ) 检测 this 与 otherObject 是否引用同一个对象:if (this = otherObject) return true;这条语句只是一个优化。实际上,这是一种经常采用的形式。因为计算这个等式要比一个一个地比较类中的域所付出的代价小得多。

3 ) 检测 otherObject 是否为 null, 如 果 为 null, 返 回 false。这项检测是很必要的。if (otherObject = null) return false;

4 ) 比较 this 与 otherObject 是否属于同一个类。如果 equals 的语义在每个子类中有所改变,就使用 getClass 检测:if (getClass() != otherObject.getCIassO) return false;如果所有的子类都拥有统一的语义,就使用 instanceof 检测:if (!(otherObject instanceof ClassName)) return false;

5 ) 将 otherObject 转换为相应的类类型变量:ClassName other = (ClassName) otherObject

6 ) 现在开始对所有需要比较的域进行比较了。使用 =比较基本类型域,使用 equals 比较对象域。如果所有的域都匹配, 就返回 true; 否 则 返 回 false。return fieldl == other.field&& Objects.equa1s(fie1d2, other.field2)如果在子类中重新定义 equals, 就要在其中包含调用 super.equals(other。

equals 比较两个对象的引用是否相等

基本数据类型可以==引用数据类型用equals,getClass 方法将返回一个对象所属的类

public boolean equals (Object otherObject){ 
    //引用一致 
    if (this == otherObject) return true; 
    if (otherObject == null) return false; //类型一致 
    if (getClass() != otherObject.getClass()) return false; 
    Employee other = (Employee) otherObject; //属性一致 
    return name.equals(other.name) && salary = other.salary && hireDay.equals(other,hireDay); 
}

2.3 父子类的equals

如果隐式和显式的参数不属于同一个类, equals 方法将如何处理呢?

不能用 instanceof 检测,父.eq(子) 和子.eq(父) 结果不一致,不满足对称性

子 instanceof 父 恒等于true

public class Test { 
    public static void main(String[] args) {
        A a = new A(); B b = new B(); 
        System.out.println(b instanceof A);//true 
        System.out.println(a instanceof B);//true 
        System.out.println(a.getClass());//class bai.main.A 
        System.out.println(b.getClass());// class bai.main.B 
        System.out.println(a.getClass()==b.getClass());//false 
    } 
} 
class A { 
    public String a; 
} 
class B extends A { 
public String b; public int b1; 
public String[] arr; 
@Override public boolean equals(Object o) {
if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; B b2 = (B) o; if (b1 != b2.b1) return false; if (!b.equals(b2.b)) return false; // Probably incorrect - comparing Object[] arrays with Arrays.equals return Arrays.equals(arr, b2.arr); } }

2.4 hashCode

散列码( hash code ) 是由对象导出的一个整型值。散列码是没有规律的.

每个对象都有一个默认的哈希码.其值为对象的存储地址

String str1 = "s"; String str2 = "s"; System.out.println(str1.hashCode());//115 System.out.println(str2.hashCode());//115 StringBuffer sb = new StringBuffer("s"); StringBuffer sb2 = new StringBuffer("s"); System.out.println(sb.hashCode());//1705736037 System.out.println(sb2.hashCode());//455659002 StringBuilder sb3=new StringBuilder("s");//250421012 StringBuilder sb4=new StringBuilder("s");//1915318863 System.out.println(sb3.hashCode());//250421012 System.out.println(sb4.hashCode());//1915318863

string重写了hashCode方法 根据内容导出字符串的散列码

sb没有重写,所以即使是相同的 值 hashCode也是不同的

2.5 重写hashCode

根据自己的设定的值 计算散列值,需要组合多个散列值时,可以调用 ObjeCtS.hash 并提供多个参数。这个方法会对各个参数调用 Objects.hashCode, 并组合这些散列值.

Equals 与 hashCode 的定义必须一致:如果 x.equals(y) 返回 true, 那么 x.hashCode( ) 就必须与 y.hashCode( ) 具有相同的值。例如, 如果用定义的 Employee.equals 比较雇员的 ID,那么 hashCode 方法就需要散列 ID,而不是雇员的姓名或存储地址

@Override public int hashCode() { return Objects.hash(b); }

2.6 toString方法

Object中一个重要的方法就是 toString方法, 它用于返回表示对象值的字符串。默认是返回 十六进制(对象的哈希码)和类名

public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }

StringBuffer和StringBuilder的toString不会把字符串添加到常量池,只是new了一个新的String对象,并把char[]数组放进去

//StringBuilder //可变字符串 @Override public String toString() { // Create a copy, don't share the array return new String(value, 0, count); } //StringBuffer 字符串缓冲 @Override public synchronized String toString() { if (toStringCache == null) { toStringCache = Arrays.copyOfRange(value, 0, count); } return new String(toStringCache, true); }

3. 泛型数组列表

泛型的使用, 数组编译的时候已经确定了大小,长度为100的数组的存储数据,当101时候,就必须要在创建一个 长度100的数组,如果仅有101条数据, 会浪费99个位置. 所以需要一个动态的数组.ArrayList

3.1 ArrayList<>

ArrayList<>是一个采用 类型参数 的泛型类 类型参数写<>中,

ensureCapacity() ArrayList<>是一个动态数组 底层是一个Object数组,初始化的数组的长度默认是10,也可以用ensureCapacity() 方法可以设置

trimToSize() 一旦能够确认数组列表的大小不再发生变化,就可以调用 trimToSize()方法。这个方法将存储区域的大小调整为当前元素数量所需要的存储空间数目。垃圾回收器将回收多余的存储空间。一旦整理了数组列表的大小,添加新元素就需要花时间再次移动存储块,所以应该在确认不会添加任何元素时, 再调用

4 装箱拆箱

如果一个表达式混合使用Integer和Double Integer, Integer会拆箱成int 参与运算升级成double,再装箱成Double

包装类用 equals比较,注意空指针异常

装箱和拆箱是编译器认可,并不是虚拟机,相当于语法糖,字节码文件里面是转化类型了的

5 可变参数列表

public void a(String a,Object... args);

6 继承的技巧

  1. 将公共操作和变量放在超类

  2. 不要使用受保护的域
    尽量不要使用protected, 因为子类是可以不断继承,任何子类都可以修改该属性,破坏封装性. 同一个包内类都可以访问protected属性

  3. 使用继承实现“ is-a” 关系

  4. 除非所有继承的方法都有意义,否则不要使用继承

  5. 在覆盖方法时,不要改变预期的行为

  6. 使用多态, 而非类型信息

  7. 不要过多地使用反射