【Java】继承
类、超类、子类
Java中继承使用extends关键字,并且Java中所有的继承都是公有继承,而没有C++的私有继承和保护继承。
超类:就是父类。
重载:方法的方法名相同,参数列表不同,返回值无所谓。类的构造方法可以重载。
覆盖(重写):在子类中写父类的同名同参同返覆盖方法。允许子类将返回值定义为原方法返回类型的子类型(细品)。类的构造方法没有返回值,方法名也不同所以不会形成覆盖。
如果想要在子类中调用父类的方法可以使用super关键字。
super还可以用来调用父类的构造函数。super(...)。并且使用super调用父类的构造函数的语句必须是子类构造函数的第一条语句。
Java中的多态不需要使用virtual。直接将子类对象给父类的引用即可。
Java不支持多继承。
动态绑定
如果采用了动态绑定,那么虚拟机会调用与所引用对象的实际类型最合适的那个类的方法。
例子:x的实际类型为B,B是A的子类。如果B定义了方法fun(),那么就直接调用它,如果没定义那就会去找A中的fun()方法。
注意上面的例子,发现如果每次都找,那么开销较大。所以虚拟机给每一个类创建了一个方法表(对应的就是C++中的虚基类表)
final类
- 被final修饰的类不能有子类。
- 被final修饰的方法在子类中也不能被覆盖。
- 被final修饰的成员变量在构造对象后就不能改变其值了。
- 对于final修饰的类,其中的方法也是final方法,但成员变量还是普通的成员变量。
final类早期是为了避免动态绑定给系统带来较大的开销,毕竟没有覆盖的方法,编译器可以对其直接优化处理,这个过程就是内联。但后来由于虚拟机中的计时编译器很厉害,它会自动的将方法很短,被频繁调用且没有覆盖的放法进行内联处理。
强制类型转化
将子类的引用给父类变量,是可以的。但将父类的引用给子类变量,就需要强制类型转换。
抽象类
用abstract声明。
-
包含一个或多个抽象方法的类必须声明为抽象类。当然不包含抽象方法的类也可以是抽象类。
-
抽象是可以包含具体数据和具体方法的。
-
抽象方法不需要实现。
-
抽象类不能实例化。
Object:所有类的父类
可以使用Object类型的变量引用任何类型的对象。
Java中只有基本数据类型不是对象。其他包括数组(基本类型的数组或对象数组)都扩展了Object类。
equals方法
Object中的equals方法用于检测一个对象是否等于另外一个对象。将判断两个对象是否具有相同的引用。
在子类中定义class方法时,首先调用父类的equals,如果失败对象就不可能相等。如果成功再比较子类中的成员变量即可。
hashCode方法
散列码(hash值)是由对象导出的一个整型值。
在Object类中有hashCode方法可以直接使用,但也可以通过重写的方法自己写该方法。
区别:原本的方法只要对象不一样,生成的hash值就不同。但自己写的就会根据写的函数体生成固定的hash值,导致虽然对象不同,也会生成相同的hash值。
注:
在java.util.Objects;包下Objects类(多了一个s)有自己的hash函数和hashCode函数,使用方法与Object略有不同。
- Objects下的hashCode()更安全因为如果传的是null指针就会返回0。
- Object下的hashCode()会报错。
- Objects下的hash()函数可以接收多个参数,生成一个hash值
- 同时要求Equals与hashCode定义必须一致。俨然就是如果equals返回为true,生成的hash值就需要一样。
toString方法
Object中的toString方法会返回表示对象值得字符串。
也可以在自己的类中重写toString方法。
一个常见的格式为:类名[成员变量 = 值,...]。类名可以使用getClass().getName()获得。
其实在使用System.out.println(x)得时候们就会直接调用x.toString方法。
注:
比较不方便的一点是,数组也继承了Object的toString方法,但它会用旧的格式打印生成的字符串是看不懂的。这时我们需要使用Arrays.toString(数组名)的方法进行打印。多维数组需要用Arrays.deepToString()方法。
泛型数组列表
在C++STL中有vector它可以自动扩容。
在Java中有ArrayList,也是具有自动调节数组容量的功能。
ArrayList<Integer> arr = new ArrayList<Integer>();//<>里不能出现int等基本数据类型
arr.add(harry);//添加元素
arr.remove(i);//删除元素
arr.ensureCapacity(100);//固定容量,就不需要重复扩容了
arr.size();//返回包含的实际元素数量
arr.set(i,harry);//相当于C++的arr[i] = harry,但是它只能替换不能添加
arr.get(i);//就是arr[i]
对象包装器与自动装箱
对象包装器类:Intager、Long、Float、Double、Short、Byte、Character、Void、Boolean前6个类派生于公共的父类Number。
对象包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值。同时,对象包装器类还是final,因此不能定义它们的子类。
在<>中的类型不能是基本类型。
Integer n = 1;//自动装箱
n++;//自动拆箱
如果在一个条件表达式中混合使用Integer和Double类型,Integer值就会拆箱,提升为double,再装箱为Double:
装箱和拆箱是编译器认可的,而不是虚拟机。编译器在生成类的字节码时,插入必要的方法调用。虚拟机只是执行这些字节码。
继承的设计技巧
- 将公共操作和域放在父类
- 一般不要用protected
- 使用继承实现“is-a”的关系
- 除非所有的继承的方法都有意义否则不要用继承
- 在覆盖方法时,不要改变预期的行为
- 使用多态,而非类型信息
- 不要过多使用反射
Annotation
准确的覆盖:@Override
如果在写代码是没有写extends,或者覆盖的时候单词写错了,所以为了避免以上问题对的出现,追加@Override,明确标识该方法是覆盖的方法。
在编译期就能报错。
过期操作:@Deprecated
在开发过程中可能有某方法或某个类存在问题,导致新版本引用存在问题(老版本没事),这时当然不能直接删掉该操作,就可以采用过期声明。告诉新的用户不要在用了,老的用户可以用。
编译期会警告,但不影响运行。
压制警告:@SuppressWarings
我知道有编译器会给警告信息,但我不想看见,就可以用。