coreJava 继承-抽象多态-反射

继承(inheritance)

有一个用来判断是否应该设计为继承关系的简单规则,这就是“is-a”规则,它表明子类的每个对象也是超类的对象。“is-a“规则的另一种表述方式是置换法则。它表明程序中出现超类对象的任何地方都可以用子类对象置换。例如,可以将一个子类的对象赋给超类变量。

之类不能够直接地访问超类的私有域。(关键字super调用get方法获取)

super不是一个对象的引用,不能将super赋给另一个对象变量,它只是一个指示编译器调用超类方法的特殊关键字
关键字this有两个用途:一是引用隐式参数,二是调用该类其他的构造器,同样,super关键字也有两个用途:一是调用超类的方法,二是调用超类的构造器。在调用构造器的时候,这两个关键字的使用方式很相似。调用构造器的语句只能作为另一个构造器的第一条语句出现。构造参数既可以传递给本类(this)的其他构造器,也可以传递给超类(super)的构造器

一个对象变量(例如,变量e)可以指示多种实际类型的现象被称为多态(参考文章最后部分)(polymorphism)。在运行时能够自动地选择调用哪个方法的现象称为动态绑定(dynamicbinding)
1:如果方法是 private方法,static方法,final方法,或者构造器 编译器就可以准确知道应该调用那个方法,我们将这种调用方式成为静态绑定(staticbinding)  =》因为statci和final方法不能被继承,private声明的方法和成员变量不能被子类继承,所有的private方法都被隐式的指定为final的(由此我们也可以知道:将方法声明为final类型的一是为了防止方法被覆盖,二是为了有效的关闭java中的动态绑定)
2:如果方法不是上面的这4种情况,编译器会进行动态绑定 public.当程序运行时,并且是采用动态绑定调用方法时,JVM会调用与X所引用对象的实际类型最合适的那个类方法,假设X类型是D类型,它是C的子类如果D类中定义了f(string)方法,则会直接调用,不会去C中查找  =》为了避免每次调用方法都进行查找,虚拟机会预先为每个类生成对应的方法表,方法表中记录了所有方法的签名和实际调用的方法

public class Person{
    int age=11;
    public void method(){
        System.out.println("父类方法,对象类型:"+this.getClass());//父类方法,对象类型:Man
    }
} 

public class Man extends Person{
    int age=22;
    public static void main(String[]args){
        Person sample=new Man();  //向上转型
        sample.method();
        System.out.println(sample.age);//11,还是Person父类的属性,属相没有虚拟调用
    }
}//声明的是父类的引用(句柄),但准确的调用了子类的对象,调用method,在子类中没有该方法,所以去父类中寻找到并调用之。

在处理Java类中的成员变量时,并不是采用运行时绑定,而是一般意义上的静态绑定。所以在向上转型的情况下,对象的方法可以“找到”子类,而对象的属性还是父类的属性(age)。注意class与age

动态绑定有一个非常重要的特性:无需对现存的代码进行修改,就可以对程序进行扩展。

方法的名字和参数列表称为方法的签名.例如,f(int)和f(String)是两个具有相同名字,不同签名的方法。(返回类型不是签名的一部分从JavaSE5.0开始,允许子类将覆盖方法的返回类型定义为原返回类型的子类型)

如果一个方法没有被覆盖并且很短,编译器就能够对它进行优化处理,这个过程为称为内联(inlining)。内联调用e.getName()将被替换为访问e.name域。这是一项很有意义的改进,这是由于CPU在处理调用方法的指令时,使用的分支转移会扰乱预取指令的策略,所以,这被视为不受欢迎的。然而,如果getName在另外一个类中被覆盖,那么编译器就无法知道覆盖的代码将会做什么操作,因此也就不能对它进行内联处理了。

将一个子类的引用赋给一个超类变量,编译器是允许的。但将一个超类的引用赋给一个子类变量,必须进行类型转换if(obj instanceof Class ) 当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false(obj 是引用类型)
•只能在继承层次内进行类型转换。
•在将超类转换成子类之前,应该使用instanceof进行检查。
如果x为null,进行下列测试 x instanceof C,不会产生异常,只是返回false。之所以这样处理是因为null没有引用任何对象,当然也不会引用C类型的对象

抽象

包含一个或多个抽象方法的类本身必须被声明为抽象的,抽象类还可以包含具体数据和具体方法.类即使不含抽象方法,也可以将类声明为抽象类.抽象类不能被实例化.需要注意,可以定义一个抽象类的对象变量,但是它只能引用非抽象子类的对象。例如:Personp=newStudent("VineeVu","Economics")

1)仅对本类可见private。
2)对所有类可见public:
3)对本包和所有子类可见protected。
4)对本包可见—默认(很遗憾,)不需要修饰符。

Object

Object类是Java中所有类的始祖,在Java中,只有基本类型(primitivetypes)不是对象,例如,数值、字符和布尔类型的值都不是对象.

Object类中的equals方法用于检测一个对象是否等于另外一个对象。在Object类中,这个方法将判断两个对象是否具有相同的引用.使用Objects.equals方法。如果两个参数都为null,Objects.equals(a,b)调用将返回true;如果其中一个参数为null,则返回false;否则,如果两个参数都不为null,则调用a.equals(b).

在子类中定义equals方法时,首先调用超类的equals。如果检测失败,对象就不可能相等。如果超类中的域都相等,就需要比较子类中的实例域

Java语言规范要求equals方法具有下面的特性:
1)自反性:对于任何非空引用x,x.equals(x)应该返回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,为空时是true

== 运算符用于比较基本类型的值是否相同,或者比较两个对象的引用是否相等
在 Object 类中,== 运算符和 equals 方法是等价的,都是比较两个对象的引用是否相等(多数情况下还是需要重写equals方法)

1.如果子类能够拥有自己的相等概念,则对称性需求将父类强制采用 getClass 进行检测
2.如果由超类决定相等的概念,那么就可以使用 imtanceof进行检测, 这样可以在不同子类的对象之间进行相等的比较。原因如下:https://cloud.tencent.com/developer/article/1079384 

Person p = new Person("Tom",22);
Man m = new Man("Tom",22,"男");
System.out.println(p.equals(m));//true
System.out.println(m.equals(p));//false

person.equals(man)得到的结果是 true,而man.equals(person)得到的结果却是false,这显然是不正确的。问题出现在 instanceof 关键字上Man 是 Person 的子类,person instanceof Man 结果当然是false。这违反了我们上面说的对称性。实际上用 instanceof 关键字是做不到对称性的要求的。这里推荐做法是用 getClass()方法取代 instanceof 运算符。getClass() 关键字也是 Object 类中的一个方法,作用是返回一个对象的运行时类.

打印结果 person.equals(man)得到的结果是 false,man.equals(person)得到的结果也是false,满足对称性

如果使用 getClass 实现equals方法的重写,那么就不能在两个不同子类的对象进行相等的比较,所以什么时候使用 instanceof 运算符,什么时候使用 getClass() 有如下建议:
  ①、如果子类能够拥有自己的相等概念,则对称性需求将强制采用 getClass 进行检测。
  ②、如果有超类决定相等的概念,那么就可以使用 instanceof 进行检测,这样可以在不同的子类的对象之间进行相等的比较。

下面给出一个完美的equals方法的建议:
  1、显示参数命名为 otherObject,稍后会将它转换成另一个叫做 other 的变量。
  2、判断比较的两个对象引用是否相等,如果引用相等那么表示是同一个对象,那么当然相等
  3、如果 otherObject 为 null,直接返回false,表示不相等
  4、比较 this 和 otherObject 是否是同一个类:如果 equals 的语义在每个子类中有所改变,就使用 getClass 检测;如果所有的子类都有统一的定义,那么使用 instanceof 检测
  5、将 otherObject 转换成对应的类类型变量:ClassName other = (ClassName) otherObject
  6、最后对对象的属性(实例域)进行比较。使用 == 比较基本类型,使用 equals 比较对象。如果都相等则返回true,否则返回false。
注意如果是在子类中定义equals,则要包含 super.equals(other)

hashCode方法

散列码(hashcode)是由对象导出的一个整型值。hashCode方法定义在Object类中,因此每个对象都有一个默认的散列码,其值为对象的存储地址。两个相等的对象要求返回相等的散列码。

字符串s与t拥有相同的散列码,这是因为字符串的散列码是由内容导出的。而字符串缓冲sb与tb却有着不同的散列码,这是因为在StringBuffer类中没有定义hashCode方法,它的散列码是由Object类的默认hashCode方法导出的对象存储地址.
public int hashCoded
{
return Objects.hash(name, salary, hireDay);
}
Equals 与 hashCode 的定义必须一致:如果 x.equals(y) 返回 true, 那么 x.hashCode( ) 就必须与 y.hashCode( ) 具有相同的值
hashCode方法应该返回一个整型数值(也可以是负数,)并合理地组合实例域的散列码,以便能够让各个不同的对象产生的散列码更加均匀.请注意,无论何时重写equals方法,通常都必须重写hashCode方法,以维护hashCode方法的一般约定,该方法声明相等对象必须具有相同的哈希代码。以便用户可以将对象插人到散列表中

两个对象相等,其 hashCode 一定相同;
两个对象不相等,其 hashCode 有可能相同;
hashCode 相同的两个对象,不一定相等;
hashCode 不相同的两个对象,一定不相等;

toString方法
它用于返回表示对象值的字符串。
绝大多数(但不是全部)的toString方法都遵循这样的格式:类的名字,随后是一对方括号括起来的域值.Object类定义了toString方法,用来打印输出对象所属的类名和散列码。
ps:一个nativeMethod就是一个Java调用非Java代码的接口

ArrayList<Employee>staff=newArrayList<Eniployee>0;两边都使用类型参数Employee,这有些繁琐。JavaSE7中,可以省去右边的类型参数:ArrayList<Employee>staff=newArrayListo();

staff.ensureCapacity(lOO);.一旦能够确认数组列表的大小不再发生变化,就可以调用trimToSize方法(将数组列表的存储容量削减到当前尺寸)。这个方法将存储区域的大小调整为当前元素数量所需要的存储空间数目。垃圾回收器将回收多余的存储空间

数组列表自动扩展容量的便利增加了访问元素语法的复杂程度

使用toArray方法将数组元素拷贝到一个数组中。
X[] a=new X[list.size()]; list.toArray(a);

对数组实施插人和删除元素的操作效率比较低。

对象包装器与自动装箱

Integer、Long、Float、Double、Short、Byte、Character、Void和Boolean(前6个类派生于公共的超类Number)。对象包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值。同时,对象包装器类还是final,因此不能定义它们的子类

枚举类
enum和class、interface的地位一样,使用enum定义的枚举类默认继承了java.lang.Enum,而不是继承Object类。枚举类可以实现一个或多个接口。枚举类的所有实例都必须放在第一行展示,不需使用new关键字,不需显式调用构造器。自动添加publicstaticfinal修饰。
使用enum定义、非抽象的枚举类默认使用final修饰,不可以被继承。枚举类的构造器只能是私有的

Throwable是Exception类的超类

反射

能够分析类能力的程序称为反射(reflective)。
•在运行时分析类的能力。
•在运行时查看对象,例如,编写一个toString方法供所有类使用。
•实现通用的数组操作代码。
•利用Method对象,这个对象很C++像中的函数指针。

Strings="java.util.Random";//调用静态方法forName获得类名对应的Class对象。
Objectm=Class.forName(s).newlnstance();

一个Class对象实际上表示的是一个类型,而这个类型未必一定是一种类。例如,int不是类,但int.class是一个Class类型的对象。

虚拟机为每个类型管理一个Class对象。因此,可以利用==运算符实现两个类对象比较的操作

在java.lang.reflect包中有三个类Field、Method和Constructor分别用于描述类的域、方法和构造器。
Class类中的getFields、getMethods和getConstructors方法将分别返回类提供的public域、方法和构造器数组,其中包括超类的公有成员。
Class类的getDeclareFields、getDeclareMethods和getDeclaredConstructors方法将分别返回类中声明的全部域、方法和构造器,其中包括私有和受保护成员,但不包括超类的成员

在java.lang.reflect包中有三个类Field、Method和Constructor分别用于描述类的域、方法和构造器。
这三个类都有一个叫做getName的方法,用来返回项目的名称。Field类有一个getType方法,用来返回描述域所属类型的Class对象。
Method和Constructor类有能够报告参数类型的方法,Method类还有一个可以报告返回类型的方法。
这3个类还有一个叫做getModifiers的方法,它将返回一个整型数值,用不同的位开关描述public和static这样的修饰符使用状况。另外,还可以利用java.lang.refleCt包中的Modifier类的静态方法分析getModifiers返回的整型数值。例如,可以使用Modifier类中的isPublic、isPrivate或isFinal判断方法或构造器是否是public、private或final。我们需要做的全部工作就是调用Modifier类的相应方法,并对返回的整型数值进行分析,另外,还可以利用Modifier.toString方法将修饰符打印出来。Classcl=Class.forName(name);Stringmodifiers=Modifier.toString(cl.getModifiers());//public**1

查看对象域的关键方法是Field类中的get方法。如果f是一个Field类型的对象(例如,通过getDeclaredFields得到的对象)obj是某个包含f域的类的对象,f.get(obj)将返回一个对象,其值为obj域的当前值。
Employeeharry=newEmployee("HarryHacker",35000,10,1,1989);
Classcl=harry.getClass0;
//theclassobjectrepresentingEmployee
Fieldf=cl.getDeclaredFieldC'name"):
//thenamefieldoftheEmployeeclass
Objectv=f.get(harry);
//thevalueofthenamefieldoftheharryobject,i.e.,theStringobject"HarryHacker"
实际上,这段代码存在一个问题。由于name是一个私有域,所以get方法将会抛出一个IllegalAccessException。只有利用get方法才能得到可访问域的值。除非拥有访问权限,否则Java安全机制只允许査看任意对象有哪些域,而不允许读取它们的值。
反射机制的默认行为受限于Java的访问控制。然而,如果一个Java程序没有受到安全管理器的控制,就可以覆盖访问控制。为了达到这个目的,需要调用Field、Method或Constructor对象的setAccessiblef.setAtcessible(true);//nowOKtocallf.get(harry);setAccessible方法是AccessibleObject类中的一个方法,它是Field、Method和Constructor类的公共超类。这个特性是为调试、持久存储和相似机制提供的。

调用f.set(obj,value)可以将obj对象的f域设置成新值

Java数组会记住每个元素的类型,即创建数组时new表达式中使用的元素类型。将一个Employee[]临时地转换成Object[]数组,然后再把它转换回来是可以的,但一从开始就是Objectt的数组却永远不能转换成Employe数组。

在Method类中有一个invoke方法,它允许调用包装在当前Method对象中的方法
Object invoke(Object obj,Object...args)第一个参数是隐式参数,其余的对象提供了显式参数(在JavaSE5.0以前的版本中,必须传递一个对象数组,如果没有显式参数就传递一个null)。对于静态方法,第一个参数可以被忽略,即可以将它设置为null.假设用ml代表Employee类的getName方法,下面这条语句显示了如何调用这个方法:String n=(String)ml.invoke(harry);

invoke的参数和返回值必须是Object类型的public Object invoke(Object implicitParameter,Object[] explicitParamenters)调用这个对象所描述的方法,传递给定参数,并返回方法的返回值。对于静态方法,把null作为隐式参数传递。在使用包装器传递基本类型的值时,基本类型的返回值必须是未包装的

多态

强烈建议文章:https://www.cnblogs.com/chenssy/p/3372798.html
父类类型的引用可以调用父类中定义的所有属性和方法,对于只存在与子类中的方法和属性它就望尘莫及了(因为向上转型后会丢失该方法)

多态总结如下:
指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载该方法。若子类重写了父类中的某些方法,在调用该些方法的时候,必定是使用子类中定义的这些方法(动态连接、动态调用)

在继承链中对象方法的调用存在一个优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值