继承关系
首先,复习一下访问控制权限
-
private:仅对本类可见
子类不能直接访问private字段,但可以拥有
-
public:外部完全可见
-
protected:本包和所有子类可见
-
缺省:本包可见
使用继承
使用extends
关键字继承类
public Manager extends Employee{
...
}
在子类中调用超类的方法:super
子类构造器:调用子类构造器前会先调用父类构造器(必须
多态
静态绑定:private方法、static方法、final方法不会有二义性
父类变量可以指向子类变量,但只能调用父类中存在的方法和字段。
如果覆写了,则调用的子类方法;否则调用父类方法
理解:一个对象变量可以引用该类型的变量,也可以引用任何一个子类的对象
方法的重写
签名:方法名+参数列表
子类中签名相同的会覆写父类的方法
返回类型不是签名的一部分,运行返回原返回类型的子类
可见性:子类方法可见性不能低于父类
final类
final
关键字修饰的类不可继承
final
方法:子类可以继承获得,但不能重写
final
字段:一旦赋值就不能再被修改
强制类型转换
有两种情况:
-
子类引用赋值给超类变量,这显然是可以的,属于多态的一部分
-
超类引用赋值给子类变量呢?答案是不一定
明确:超类引用不能凭空的强制转换为子类
什么叫凭空?就是超类变量一开始就引用的超类引用,相比于子类引用,超类引用肯定缺少了一些内容,此时强制转换会失败。
如果这个超类变量是一开始引用的子类引用,现如这个子类引用包含了所有内容,只不过超类变量只能访问超类中定义的部分;这时候可以强制转换为子类
可以使用
instanceof
来检查是否能够进行强制转换
public class test02 {
public static void main(String[] args){
//这种强制转换会失败
//Employee e1 = new Employee("sjs","1",1700);
//Manager m1 = (Manager) e1;
//System.out.println(m1.getMoney());
//这种转换会成功
Manager m1 = new Manager("sjs","1",1700,1899);
Employee e1 = m1;
//强制转换前可以用instanceof进行检查
System.out.println(e1 instanceof Manager);
Manager m2 = (Manager) e1;
System.out.println(m2.getMoney());
}
}
抽象类
使用abstract
关键字定义抽象类
特点:和普通类一样有字段也有方法,但是方法不一定实现。
没有实现所有方法的子类也是抽象类
可以有抽象变量,但只能引用非抽象子类的实例,因此调用的方法必然是非抽象子类的完整方法
不实现的方法要用abstract
标识,且abstract
只能用在抽象类里
public abstract class AbstractEmploy {
public abstract float getMoney();
}
public class test03 {
public static void main(String[] args){
AbstractEmploy e1 = new Employee("123","456",1700);
AbstractEmploy m1 = new Manager("789","1011",899,200);
System.out.println(e1.getMoney()); //得到1700
System.out.println(m1.getMoney()); //得到1099
}
}
Object类一些常用方法
Object的一个典型用法是可以用作容器引用任何类型,但具体操作其中内容时一般要强制转换
equals与==
==:如果是基本类型,则比较是值;如果是引用类型,比较的是地址
equals:可以自定义equals来比较
数组类型一般调用Arrays.equal
//这是Object的原生equals方法
public boolean equals(Object obj) {
return (this == obj);
}
//这是String的equals方法
//可见这个equals方法比较的是字符串的值是不是相等
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
如何编写一个equlas方法
equals方法要满足自反性、对称性、传递性等性质
- 形参类型定义为Object
- 和this进行比较
- 进行类层面的比较,也就是Class对象
- 强转后进行字段的比较
@Override
public boolean equals(Object object){
//1.equals(null)必须为false
if(object == null){
return false;
}
//2.如果是同一引用,直接返回true,就不用做后面的比较了
if(this == object){
return true;
}
//3.比较Class类
if(this.getClass() != object.getClass()){
return false;
}
//4.类结构一样,开始比较字段
Manager objManager = (Manager) object;
if(this.getName().equals(objManager.getName()) && this.getWork().equals(objManager.getWork()) &&
this.getSalary() == objManager.getSalary() && this.bonus == objManager.bonus){
return true;
}
return false;
}
值为null的引用不能调用equals方法,否则会报NullPointerException
如果一定要和null进行可能的比较,则可以调用Objects的静态方法equals
hashCode方法
默认的hashCode由对象的存储地址得到
如果重写了equlas,就必须重写hashCode
原因:如果a.equals(b)为true,那么a和b的hashCode也应该一样
简单来说,重新定义的equlas比较的是什么,就应该重新散列什么。
注意:hashCode相等,对象不一定相等
//接上文,比较的字段拿来计算hashCode
public int hashCode(){
return 7*this.getName().hashCode()+5*this.getWork().hashCode()+3*Double.hashCode(this.getSalary())+
6*Double.hashCode(this.bonus);
}
hashCode有什么作用呢?
当你把对象加入 HashSet
时,HashSet
会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,HashSet
会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()
方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet
就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的 Java 启蒙书《Head First Java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
toSrting方法
通常会把类中字段的所有值打印出来,方便调试
通过+
连接字符串和对象时,会自动调用toString方法
注意:数组自带的toString有历史遗留问题,不得劲,建议使用Arrays.toString
方法
对象包装器
基本类型都有对应的包装器
自动装箱:需要用到包装类型时基本类型变量会自动包装
自动拆箱:需要用到基本类型时包装类型变量会自动拆除
常量池
Java 基本类型的包装类的大部分都实现了常量池技术。Byte
,Short
,Integer
,Long
这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character
创建了数值在[0,127]范围的缓存数据,Boolean
直接返回 True
Or False
。
Integer i1 = 33;
Integer i2 = 33;
System.out.println(i1 == i2);// 输出 true
可变参数列表
使用...
可以表示不限参数数量,相当于传入一个该类型的数组
本质就是传入该类型的数组
public float bePromoted(float ...values){
float res = 0;
for(float v : values){
res += v;
}
return res;
}
枚举类
枚举类除了包含枚举常量,还可以像普通类一样有字段、方法、构造器
可以在枚举常量定义传入构造器的参数
所有枚举类型都是Enum
的子类,其中的toString会返回枚举常量名
通过Enum
的valueOf可以构造实体类
public enum Color {
RED(""),BLUE(""),YELLOW("");
private String desc;
Color(String desc){
this.desc = desc;
}
public String getDesc() {
return desc;
}
}
枚举类的构造器是private的:无法被new实例化,只能在类内部调用构造方法
如何在其他地方实例化枚举类型:调用Enum.valueOf
方法
public class test06 {
public static void main(String[] args){
Color color = Enum.valueOf(Color.class,"YELLOW"); //实例化
Color[] values = Color.values(); //获取全部枚举常量
System.out.println(color); //toString
System.out.println(Arrays.toString(values));
}
}