本节主要介绍Java面向对象三大特性:继承 封装 多态,以及其中的原理。java
本文会结合虚拟机对引用和对象的不一样处理来介绍三大特性的原理。数组
Java中的继承只能单继承,可是能够经过内部类继承其余类来实现多继承。安全
<pre>jvm
public class Son extends Father{
public void go () {
System.out.println("son go");
}
public void eat () {
System.out.println("son eat");
}
public void sleep() {
System.out.println("zzzzzz");
}
public void cook() {
//匿名内部类实现的多继承
new Mother().cook();
//内部类继承第二个父类来实现多继承
Mom mom = new Mom();
mom.cook();
}
private class Mom extends Mother {
@Override
public void cook() {
System.out.println("mom cook");
}
}
}
封装主要是由于Java有访问权限的控制。public > protected > package = default > private。封装能够保护类中的信息,只提供想要被外界访问的信息。ide
类的访问范围spa
A、public 包内、包外,全部类中可见
B、protected 包内全部类可见,包外有继承关系的子类可见
(子类对象可调用)
C、(default)表示默认,不只本类访问,并且是同包可。
D、private 仅在同一类中可见
</pre>code
多态通常能够分为两种,一个是重写,一个是重载。对象
重写是因为继承关系中的子类有一个和父类同名同参数的方法,会覆盖掉父类的方法。重载是由于一个同名方法能够传入多个参数组合。
注意,同名方法若是参数相同,即便返回值不一样也是不能同时存在的,编译会出错。
从jvm实现的角度来看,重写又叫运行时多态,编译时看不出子类调用的是哪一个方法,可是运行时操做数栈会先根据子类的引用去子类的类信息中查找方法,找不到的话再到父类的类信息中查找方法。
而重载则是编译时多态,由于编译期就能够肯定传入的参数组合,决定调用的具体方法是哪个了。
public static void main(String[] args) {
Son son = new Son();
//首先先明确一点,转型指的是左侧引用的改变。
//father引用类型是Father,指向Son实例,就是向上转型,既能够使用子类的方法,也能够使用父类的方法。
//向上转型,此时运行father的方法
Father father = son;
father.smoke();
//不能使用子类独有的方法。
// father.play();编译会报错
father.drive();
//Son类型的引用指向Father的实例,因此是向下转型,不能使用子类非重写的方法,能够使用父类的方法。
//向下转型,此时运行了son的方法
Son son1 = (Son) father;
//转型后就是一个正常的Son实例
son1.play();
son1.drive();
son1.smoke();
//由于向下转型以前必须先经历向上转型。
//在向下转型过程当中,分为两种状况:
//状况一:若是父类引用的对象若是引用的是指向的子类对象,
//那么在向下转型的过程当中是安全的。也就是编译是不会出错误的。
//由于运行期Son实例确实有这些方法
Father f1 = new Son();
Son s1 = (Son) f1;
s1.smoke();
s1.drive();
s1.play();
//状况二:若是父类引用的对象是父类自己,那么在向下转型的过程当中是不安全的,编译不会出错,
//可是运行时会出现java.lang.ClassCastException错误。它能够使用instanceof来避免出错此类错误。
//由于运行期Father实例并无这些方法。
Father f2 = new Father();
Son s2 = (Son) f2;
s2.drive();
s2.smoke();
s2.play();
//向下转型和向上转型的应用,有些人以为这个操做没意义,何须先向上转型再向下转型呢,不是画蛇添足么。其实能够用于方法参数中的类型聚合,而后具体操做再进行分解。
//好比add方法用List引用类型做为参数传入,传入具体类时经历了向下转型
add(new LinkedList());
add(new ArrayList());
//总结
//向上转型和向下转型都是针对引用的转型,是编译期进行的转型,根据引用类型来判断使用哪一个方法
//而且在传入方法时会自动进行转型(有须要的话)。运行期将引用指向实例,若是是不安全的转型则会报错。
//若安全则继续执行方法。
}
public static void add(List list) {
System.out.println(list);
//在操做具体集合时又经历了向上转型
// ArrayList arr = (ArrayList) list;
// LinkedList link = (LinkedList) list;
}
总结: 向上转型和向下转型都是针对引用的转型,是编译期进行的转型,根据引用类型来判断使用哪一个方法。而且在传入方法时会自动进行转型(有须要的话)。运行期将引用指向实例,若是是不安全的转型则会报错,若安全则继续执行方法。blog
其实就是根据引用类型来调用对应方法。继承
<pre>
public static void main(String[] args) {
Father father= new Son();
静态分派 a= new 静态分派();
//编译期肯定引用类型为Father。
//因此调用的是第一个方法。
a.play(father);
//向下转型后,引用类型为Son,此时调用第二个方法。
//因此,编译期只肯定了引用,运行期再进行实例化。
a.play((Son)father);
//当没有Son引用类型的方法时,会自动向上转型调用第一个方法。
a.smoke(father);
//
}
public void smoke(Father father) {
System.out.println("father smoke");
}
public void play (Father father) {
System.out.println("father");
//father.drive();
}
public void play (Son son) {
System.out.println("son");
//son.drive();
}
</pre>
<pre>
public static void main(String[] args) {
方法重载优先级匹配 a = new 方法重载优先级匹配();
//普通的重载通常就是同名方法不一样参数。
//这里咱们来讨论当同名方法只有一个参数时的状况。
//此时会调用char参数的方法。
//当没有char参数的方法。会调用int类型的方法,若是没有int就调用long
//即存在一个调用顺序char -> int -> long ->double -> ..。
//当没有基本类型对应的方法时,先自动装箱,调用包装类方法。
//若是没有包装类方法,则调用包装类实现的接口的方法。
//最后再调用持有多个参数的char...方法。
a.eat('a');
a.eat('a','c','b');
}
public void eat(short i) {
System.out.println("short");
}
public void eat(int i) {
System.out.println("int");
}
public void eat(double i) {
System.out.println("double");
}
public void eat(long i) {
System.out.println("long");
}
public void eat(Character c) {
System.out.println("Character");
}
public void eat(Comparable c) {
System.out.println("Comparable");
}
public void eat(char ... c) {
System.out.println(Arrays.toString(c));
System.out.println("...");
}
// public void eat(char i) {
// System.out.println("char");
// }