文章目录
- 1.动态绑定
- 2.对象转换和 instanceof 操作符
- 稍作总结
- 3.Object 类的 equals 方法
- 4.ArrayList 类
- 5.继承体系中的权限问题
- 6.final
1.动态绑定
多态意味着父类型的变量可以引用子类型的对象。
方法可以在沿着继承链的多个类中实现。JVM 决定运行时调用哪个方法。
一个变量必须被声明为某种类型。变量的这个类型称为它的声明类型 ( declared type)。这里,o 的声明类型是A。
变量的实际类型 ( actualtype) 是被变量引用的对象的实际类。这里,o 的实际类型是 B, 因为 o 引用使用 B创建的对象。o 调用哪个print() 方法由 o 的实际类型决定。这称为动态绑定 (dynamicbinding)。
即父类变量指向子类对象时,调用同名方法时,调用的是子类的,除非子类没有才会调用父类的。
方法可以在沿着继承链的多个类中实现。调用同名方法时,调用的是子类的,如果子类没有重写,就一直挨个挨个往"上辈"找。
public class DynamicBindingDemo {
public static void main(String[] args) {
m(new Graduatestudent() ) ;
m(new Student() ) ;
m(new Person() ) ;
m(new Object() ) ;
}
public static void m(Object x) {
System.out.println(x.toString() ) ;
}
}
class Graduatestudent extends Student {
}
class Student extends Person {
©Override
public String toString() {
return "Student";
}
}
class Person extends Object {
©Override
public String toString() {
return "Person";
}
}
/*Student
Student
Person
java.1ang.Object@130c19b*/
2.对象转换和 instanceof 操作符
一个对象的引用可以类型转换为对另 外一个对象的引用,这称为对象转换。
m(new Student());
// 等价于
Object o = new Student(); // 隐式转换 (implicit casting)
m(o);
总是可以将一个子类的实例转换为一个父类的变量,称为向上转换 ( upcasting), 因为子类的实例总是它的父类的实例。当把一个父类的实例转换为它的子类变量 (称为向下转换(downcasting)) 时,必须使用转换标记 “(子类名)”进行显式转换。
Student b = (Student)o;
如果一个对象不是 Student 的实例,它就不能转换成 Student 类型的变量。因此,一个好的做法是,在尝试转换之前确保该对象是另一个对象的实例。这可以利用操作符 instanceof 来实现。
instanceof看的是实际类型。
void f(Object obj){
if(obj instanceof Student){
obj.printId(); // 假设printId是Student类的一个对象函数。}
}
变量 obj被声明为object类,声明类型决定了在编译时匹配哪个方法。使用obj.printId()会引起一个编译错误,因为Object 类没有 printId()方法。所以,有必要将obj转换成Student类型,以告诉编译器obj也是 Student的一个实例。
为什么不在一开始就把 myObject 定义为 Circle 类型呢?为了能够进行通用程序设计,一个好的做法是把变量定义为父类型,这样,它就可以接收任何子类型的对象
public class Test{
public static void main(Stringp[] args){
Object obj1 = new Circle(1);
Object obj2 = new Rectangle(1,1);
display(obj1);
display(obj2);
}
public static void display(Object obj){
if(obj instanceof Circle)
((Circle)obj).showradius();
else if(obj instanceof Rectangle)
((Rectangle)obj).showarea();
}
}
对基本类型值进行转换不同于对对象引用进行转换。转换一个基本类型值返回一个新的值。转换一个对象引用不会创建一个新的对象。
// a和b是不同的东西
double a = 4.5;
int b = (int)a; // 4
// o和a指向同一个对象
Object o = new A(); // 声明为Object,实际为A
A a = (A)o; // 声明为A,实际为A
稍作总结
本文第一点指的是子类重写父类方法的现象。
父类变量可以指向子类对象,而且用父类变量调用父类和子类同名的方法时,调用的会是子类中重写后的方法。
当父类被多个子类继承时,假设不同的子类对父类中的同一个函数进行不同版本的重写。那么之后当父类变量指向不同的子类对象时,调用这个同名函数,就可以根据不同的子类对象而调用不同的重写后的版本
本文第二点指的是子类相比父类有新的方法的现象。
用父类变量指向子类对象时,声明类型是父类,实际类型是子类。由于声明类型是父类,所以这个父类变量不能调用子类新定义的方法。
只能先通过instanceof关键词判断实际类型是哪个子类,然后通过类型转换转化为对应的子类,然后调用对应的方法
总而言之,能不能调用某个方法,取决于声明类型(比如第2点中,声明类型为父类的对象不能调用只在子类中存在的方法)。
具体调用哪个方法,取决于实际类型(1.父类和子类存在同名方法,父类变量指父类对象,用的自然是父类的。子类变量是子类对象,要的自然是子类的。父类变量指子类对象,用的是子类的。子类变量指不了父类对象2.父类有而子类无的对象,无论什么情况都是调用父类的)
能不能(编译器同不同意)调用某个方法,取决于声明类型
具体调用哪个方法,取决于实际类型
3.Object 类的 equals 方法
比较操作符 ==用来比较两个基本数据类型的值是否相等,或者判断两个对象是否具有相同的引用。
如果想让 equals 方法能够判断两个对象是否具有相同的内容,可以在定义这些对象的类时,重写equals 方法。
4.ArrayList 类
ArrayList即数组列表
ArrayList 对象可以用于存储一个对象列表。
可以创建一个数组存储对象,但是这个数组一旦创建,它的大小就固定了。Java 提供了 ArrayList 类,可以用来存储不限定个数的对象。
ArrayList 是一种泛型类,具有一个泛型类型E创建一个 ArrayList 时,可以指定一个具体的类型来替换E。
储在 ArrayList 中的元素必须是一种对象。不能使用诸如 int 的基本数据类型来代替一个泛型类型。然而,可以创建一个存储工Integer 对象的ArrayList
ArrayList<AConcreteType> list = new ArrayList<AConcreteType>();
ArrayList<AConcreteType> list = new ArrayList<>(); //类型推导 (type inference)
ArrayList 的大小是灵活的,所以无须提前给定它的大小。而当创建一个数组时,它的大小必须给定。可以使用 foreach 循环来遍历数组中的元素。数组列表ArrayList中的元素也可以使用 foreach 循环来进行遍历。
5.继承体系中的权限问题
修饰符 protected 和 public 都称为可见性修饰符( visibility )或可访问性修饰符(accessibility) 因为它们指定如何访问类和类的成员。
类可以以两种方式使用:一种是用于创建该类的实例;另一种是通过继承该类创建它的子类。如果不想从类的外部使用类的成员,就把成员声明成private如果想让该类的用户都能使用类的成员,就把成员声明成 public,如果想让该类的继承类使用数据和方法,而不想让该类的用户使用,则把成员声明成protected。
修饰符 private 和 protected 只能用于类的成员。public 修饰符和默认修饰符(也就是没有修饰符)既可以用于类的成员,也可以用于类。一个没有修饰符的类(即非公共类)是不能被其他包中的类访问的。
里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
原文:https://blog.csdn.net/Liebers/article/details/82697949
另外还有重写的原则:
覆盖方法必须满足的十大约束
一:子类方法的名称、参数签名和返回类型必须与父类方法的名称、参数签名和返回类型一致
二:子类方法不能缩小父类方法的访问权限
三:子类方法不能抛出比父类方法更多的异常,子类方法抛出的异常必须和父类方法抛出的异常相同,或者子类方法抛出的异常类是父类方法抛出的异常类的子类
四:方法覆盖只存在于子类和父类(包括直接父类和间接父类)之间。在同一个类中方法只能被重载,不能被覆盖。
五:父类的静态方法不能被子类覆盖为非静态方法。
六:子类可以定义与父类的静态方法同名的静态方法,以便在子类中隐藏父类的静态方法。子类隐藏父类的静态方法和子类覆盖父类的实例方法,这两者的区别在于:运行时,Java虚拟机把静态方法和所属的类绑定,而把实例方法和所属的实例绑定。
七:父类的非静态方法不能被子类覆盖为静态方法。
八:父类的私有方法不能被子类覆盖。
九:父类的抽象方法可以被子类通过两种途径覆盖:一是子类实现父类的抽象方法;二是子类重新声明父类抽象方法。
十:父类的非抽象方法可以被覆盖为抽象方法。
原文链接:https://blog.csdn.net/pickpocket/article/details/83923446
最后说一下public修饰类。
如果一个类声明的时候使用了public class进行了声明,则类名称必须与文件名称完全一致。被public修饰的类可以被其他包访问。
如果一个类声明的时候使用了class进行了声明,则作为启动类的名称可以与文件名称不一致,但是执行的时候肯定执行的是生成后的名称。没有public修饰的类,该类就拥有了包访问权限,即该类只可以用于该包之中。
6.final
一个被 final 修饰的类和方法都不能被继承。被 final 修饰的数据域是一个常数。
final的用法:
1.修饰类:当用final修饰一个类时,表明这个类不能被继承。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。
2.修饰方法:把方法锁定,以防任何继承类重写。
3.修饰变量:对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。