网站建设文翻译工作,手机网站关键词快速排名,儿童 网站 设计,怎样网站备案表文章目录对象内存解析方法的参数传递机制关键字#xff1a;package、importpackage(包)JDK中主要的包介绍import(导入)JavaBeanUML类图继承的一些细节封装性中的4种权限修饰关键字#xff1a;supersuper的理解super的使用场景子类中调用父类被重写的方法子类中调用父类中同名…
文章目录对象内存解析方法的参数传递机制关键字package、importpackage(包)JDK中主要的包介绍import(导入)JavaBeanUML类图继承的一些细节封装性中的4种权限修饰关键字supersuper的理解super的使用场景子类中调用父类被重写的方法子类中调用父类中同名的成员变量子类构造器中调用父类构造器this与super子类对象实例化全过程对于多态的再理解为什么需要多态多态的好处和弊端虚方法调用(Virtual Method Invocation)成员变量没有多态性向上转型与向下转型为什么要类型转换如何向上或向下转型instanceof关键字对象内存解析
HotSpot Java虚拟机的架构图如下。其中我们主要关心的是运行时数据区部分Runtime Data Area。 其中
堆Heap此内存区域的唯一目的就是存放对象实例几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是所有的对象实例以及数组都要在堆上分配。
栈Stack是指虚拟机栈。虚拟机栈用于存储局部变量等。局部变量表存放了编译期可知长度的各种基本数据类型boolean、byte、char、short、int、float、long、double、对象引用reference类型它不等同于对象本身是对象在堆内存的首地址。 方法执行完自动释放。
方法区Method Area用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
举例
class Person { //类人String name;int age;boolean isMale;
}public class PersonTest { //测试类public static void main(String[] args) {Person p1 new Person();p1.name 赵同学;p1.age 20;p1.isMale true;Person p2 new Person();p2.age 10;Person p3 p1;p3.name 郭同学;}
}内存解析图 说明 堆凡是new出来的结构(对象、数组)都放在堆空间中。对象的属性存放在堆空间中。创建一个类的多个对象比如p1、p2则每个对象都拥有当前类的一套副本即属性。当通过一个对象修改其属性时不会影响其它对象此属性的值。当声明一个新的变量使用现有的对象进行赋值时比如p3 p1此时并没有在堆空间中创建新的对象。而是两个变量共同指向了堆空间中同一个对象。当通过一个对象修改属性时会影响另外一个对象对此属性的调用。 对象名中存储的是什么呢
答对象地址
public class StudentTest{public static void main(String[] args){System.out.println(new Student());//Student7852e922Student stu new Student();System.out.println(stu);//Student4e25154fint[] arr new int[5];System.out.println(arr);//[I70dea4e}
}直接打印对象名和数组名都是显示“类型对象的hashCode值所以说类、数组都是引用数据类型引用数据类型的变量中存储的是对象的地址或者说指向堆中对象的首地址。 方法的参数传递机制
Java里方法的参数传递方式只有一种值传递。 即将实际参数值的副本复制品传入方法内而参数本身不受影响。 形参是基本数据类型将实参基本数据类型变量的“数据值”传递给形参 形参是引用数据类型将实参引用数据类型变量的“地址值”传递给形参 如果这个环节比较薄弱可以看我的另一篇文章详解了java的参数传递机制 你真的搞懂了参数传递方式吗(多图超详细) 关键字package、import 这个也是容易忽略的点因为相关工作IDE已经为我们做好了所以在开发中我们基本上不会去动手写这些东西我们这里简单的过一下。 package(包)
package称为包用于指明该文件中定义的类、接口等结构所在的包。
语法格式
package 顶层包名.子包名 ;举例pack1\pack2\PackageTest.java
package pack1.pack2; //指定类PackageTest属于包pack1.pack2public class PackageTest{public void display(){System.out.println(in method display());}
}说明
一个源文件只能有一个声明包的package语句package语句作为Java源文件的第一条语句出现。若缺省该语句则指定为无名包。包名属于标识符满足标识符命名的规则和规范全部小写、见名知意 包通常使用所在公司域名的倒置com.nefu.xxx。大家取包名时不要使用java.xx包 包对应于文件系统的目录package语句中用 “.” 来指明包(目录)的层次每.一次就表示一层文件目录。同一个包下可以声明多个结构类、接口但是不能定义同名的结构类、接口。不同的包下可以定义同名的结构类、接口
包的作用
包可以包含类和子包划分项目层次便于管理帮助管理大型软件系统将功能相近的类划分到同一个包中。比如MVC的设计模式解决类命名冲突的问题控制访问权限
JDK中主要的包介绍
java.lang----包含一些Java语言的核心类如String、Math、Integer、 System和Thread提供常用功能 java.net----包含执行与网络相关的操作的类和接口。 java.io ----包含能提供多种输入/输出功能的类。 java.util----包含一些实用工具类如定义系统特性、接口的集合框架类、使用与日期日历相关的函数。 java.text----包含了一些java格式化相关的类 java.sql----包含了java进行JDBC数据库编程的相关类/接口 java.awt----包含了构成抽象窗口工具集abstract window toolkits的多个类这些类被用来构建和管理应用程序的图形用户界面(GUI)。
import(导入)
为了使用定义在其它包中的Java类需用import语句来显式引入指定包下所需要的类。相当于import语句告诉编译器到哪里去寻找这个类。
语法格式
import 包名.类名;应用举例
import pack1.pack2.Test; //import pack1.pack2.*;表示引入pack1.pack2包中的所有结构public class PackTest{public static void main(String args[]){Test t new Test(); //Test类在pack1.pack2包中定义t.display();}
}注意事项 import语句声明在包的声明和类的声明之间。 如果需要导入多个类或接口那么就并列显式多个import语句即可 如果使用a.*导入结构表示可以导入a包下的所有的结构。举例可以使用java.util.*的方式一次性导入util包下所有的类或接口。 如果导入的类或接口是java.lang包下的或者是当前包下的则可以省略此import语句。 如果已经导入java.a包下的类那么如果需要使用a包的子包下的类的话仍然需要导入。 如果在代码中使用不同包下的同名的类那么就需要使用类的全类名的方式指明调用的是哪个类。 了解import static组合的使用调用指定类或接口下的静态的属性或方法
JavaBean JavaBean是一种Java语言写成的可重用组件。 好比你做了一个扳手这个扳手会在很多地方被拿去用。这个扳手也提供多种功能(你可以拿这个扳手扳、锤、撬等等)而这个扳手就是一个组件。 所谓JavaBean是指符合如下标准的Java类 类是公共的有一个无参的公共的构造器有属性且有对应的get、set方法 用户可以使用JavaBean将功能、处理、值、数据库访问和其他任何可以用Java代码创造的对象进行打包并且其他的开发者可以通过内部的JSP页面、Servlet、其他JavaBean、applet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能而不用关心任何改变。 《Think in Java》中提到JavaBean最初是为Java GUI的可视化编程实现的。你拖动IDE构建工具创建一个GUI 组件如多选框其实是工具给你创建Java类并提供将类的属性暴露出来给你修改调整将事件监听器暴露出来。 示例 public class JavaBean {private String name; // 属性一般定义为privateprivate int age;public JavaBean() {}public int getAge() {return age;}public void setAge(int a) {age a;}public String getName() {return name;}public void setName(String n) {name n;}
}
UML类图 UMLUnified Modeling Language统一建模语言用来描述软件模型和架构的图形化语言。 常用的UML工具软件有PowerDesinger、Rose和Enterprise Architect。 UML工具软件不仅可以绘制软件开发中所需的各种图表还可以生成对应的源代码。 在软件开发中使用UML类图可以更加直观地描述类内部结构类的属性和操作以及类之间的关系如关联、依赖、聚合等。 表示 public 类型 - 表示 private 类型#表示protected类型方法的写法: 方法的类型(、-) 方法名(参数名 参数类型)返回值类型斜体表示抽象方法或类。 继承的一些细节
1、子类会继承父类所有的实例变量和实例方法
从类的定义来看类是一类具有相同特性的事物的抽象描述。父类是所有子类共同特征的抽象描述。而实例变量和实例方法就是事物的特征那么父类中声明的实例变量和实例方法代表子类事物也有这个特征。
当子类对象被创建时在堆中给对象申请内存时就要看子类和父类都声明了什么实例变量这些实例变量都要分配内存。当子类对象调用方法时编译器会先在子类模板中看该类是否有这个方法如果没找到会看它的父类甚至父类的父类是否声明了这个方法遵循从下往上找的顺序找到了就停止一直到根父类都没有找到就会报编译错误。
所以继承意味着子类的对象除了看子类的类模板还要看父类的类模板。 2、子类不能直接访问父类中私有的(private)的成员变量和方法
子类虽会继承父类私有(private)的成员变量但子类不能对继承的私有成员变量直接进行访问可通过继承的get/set方法进行访问。如图所示 3、在Java 中继承的关键字用的是“extends”即子类不是父类的子集而是对父类的“扩展”
子类在继承父类以后还可以定义自己特有的方法这就可以看做是对父类功能上的扩展。
4、Java支持多层继承(继承体系) class A{}
class B extends A{}
class C extends B{}说明 子类和父类是一种相对的概念 顶层父类是Object类。所有的类默认继承Object作为父类。 5、一个父类可以同时拥有多个子类
class A{}
class B extends A{}
class D extends A{}
class E extends A{}6、Java只支持单继承不支持多重继承 public class A{}
class B extends A{}//一个类只能有一个父类不可以有多个直接父类。
class C extends B{} //ok
class C extends A,B... //error封装性中的4种权限修饰
权限修饰符public,protected,缺省,private
修饰符本类本包其他包子类其他包非子类private√×××缺省√√本包子类非子类都可见××protected√√本包子类非子类都可见√其他包仅限于子类中可见×public√√√√
外部类public和缺省
成员变量、成员方法等public,protected,缺省,private
1、外部类要跨包使用必须是public否则仅限于本包使用
1外部类的权限修饰符如果缺省本包使用没问题 2外部类的权限修饰符如果缺省跨包使用有问题 2、成员的权限修饰符问题
1本包下使用成员的权限修饰符可以是public、protected、缺省 2跨包下使用要求严格 3跨包使用时如果类的权限修饰符缺省成员权限修饰符类的权限修饰符也没有意义 关键字super
super的理解
在Java类中使用super来调用父类中的指定操作
super可用于访问父类中定义的属性super可用于调用父类中定义的成员方法super可用于在子类构造器中调用父类的构造器
注意
尤其当子父类出现同名成员时可以用super表明调用的是父类中的成员super的追溯不仅限于直接父类super和this的用法相像this代表本类对象的引用super代表父类的内存空间的标识
super的使用场景
子类中调用父类被重写的方法
如果子类没有重写父类的方法只要权限修饰符允许在子类中完全可以直接调用父类的方法如果子类重写了父类的方法在子类中需要通过super.才能调用父类被重写的方法否则默认调用的子类重写的方法
举例
package com.atguigu.inherited.method;public class Phone {public void sendMessage(){System.out.println(发短信);}public void call(){System.out.println(打电话);}public void showNum(){System.out.println(来电显示号码);}
}//smartphone智能手机
public class SmartPhone extends Phone{//重写父类的来电显示功能的方法public void showNum(){//来电显示姓名和图片功能System.out.println(显示来电姓名);System.out.println(显示头像);//保留父类来电显示号码的功能super.showNum();//此处必须加super.否则就是无限递归那么就会栈内存溢出}
}总结 方法前面没有super.和this. 先从子类找匹配方法如果没有再从直接父类找再没有继续往上追溯 方法前面有this. 先从子类找匹配方法如果没有再从直接父类找再没有继续往上追溯 方法前面有super. 从当前子类的直接父类找如果没有继续往上追溯
子类中调用父类中同名的成员变量
如果实例变量与局部变量重名可以在实例变量前面加this.进行区别如果子类实例变量和父类实例变量重名并且父类的该实例变量在子类仍然可见在子类中要访问父类声明的实例变量需要在父类实例变量前加super.否则默认访问的是子类自己声明的实例变量如果父子类实例变量没有重名只要权限修饰符允许在子类中完全可以直接访问父类中声明的实例变量也可以用this.实例访问也可以用super.实例变量访问
举例
class Father{int a 10;int b 11;
}
class Son extends Father{int a 20;public void test(){//子类与父类的属性同名子类对象中就有两个aSystem.out.println(子类的a a);//20 先找局部变量找没有再从本类成员变量找System.out.println(子类的a this.a);//20 先从本类成员变量找System.out.println(父类的a super.a);//10 直接从父类成员变量找//子类与父类的属性不同名是同一个bSystem.out.println(b b);//11 先找局部变量找没有再从本类成员变量找没有再从父类找System.out.println(b this.b);//11 先从本类成员变量找没有再从父类找System.out.println(b super.b);//11 直接从父类局部变量找}public void method(int a, int b){//子类与父类的属性同名子类对象中就有两个成员变量a此时方法中还有一个局部变量a System.out.println(局部变量的a a);//30 先找局部变量System.out.println(子类的a this.a);//20 先从本类成员变量找System.out.println(父类的a super.a);//10 直接从父类成员变量找System.out.println(b b);//13 先找局部变量System.out.println(b this.b);//11 先从本类成员变量找System.out.println(b super.b);//11 直接从父类局部变量找}
}
class Test{public static void main(String[] args){Son son new Son();son.test();son.method(30,13); }
}总结起点不同就近原则 变量前面没有super.和this. 在构造器、代码块、方法中如果出现使用某个变量先查看是否是当前块声明的局部变量如果不是局部变量先从当前执行代码的本类去找成员变量如果从当前执行代码的本类中没有找到会往上找父类声明的成员变量权限修饰符允许在子类中访问的 变量前面有this. 通过this找成员变量时先从当前执行代码的本类去找成员变量如果从当前执行代码的本类中没有找到会往上找父类声明的成员变量权限修饰符允许在子类中访问的 变量前面super. 通过super找成员变量直接从当前执行代码的直接父类去找成员变量权限修饰符允许在子类中访问的如果直接父类没有就去父类的父类中找权限修饰符允许在子类中访问的
特别说明应该避免子类声明和父类重名的成员变量
在阿里的开发规范等文档中都做出明确说明 子类构造器中调用父类构造器
① 子类继承父类时不会继承父类的构造器。只能通过“super(形参列表)”的方式调用父类指定的构造器。
② 规定“super(形参列表)”必须声明在构造器的首行。
③ 我们前面讲过在构造器的首行可以使用this(形参列表)调用本类中重载的构造器 结合②结论在构造器的首行“this(形参列表)” 和 super(形参列表)只能二选一。
④ 如果在子类构造器的首行既没有显示调用this(形参列表)“也没有显式调用super(形参列表)” 则子类此构造器默认调用super()即调用父类中空参的构造器。
⑤ 由③和④得到结论子类的任何一个构造器中要么会调用本类中重载的构造器要么会调用父类的构造器。 只能是这两种情况之一。
⑥ 由⑤得到一个类中声明有n个构造器最多有n-1个构造器中使用了this(形参列表)“则剩下的那个一定使用super(形参列表)”。 开发中常见错误 如果子类构造器中既未显式调用父类或本类的构造器且父类中又没有空参的构造器则编译出错。 情景举例1
class A{}
class B extends A{}class Test{public static void main(String[] args){B b new B();//A类和B类都是默认有一个无参构造B类的默认无参构造中还会默认调用A类的默认无参构造//但是因为都是默认的没有打印语句看不出来}
}情景举例2
class A{A(){System.out.println(A类无参构造器);}
}
class B extends A{}
class Test{public static void main(String[] args){B b new B();//A类显示声明一个无参构造//B类默认有一个无参构造//B类的默认无参构造中会默认调用A类的无参构造//可以看到会输出“A类无参构造器}
}情景举例3
class A{A(){System.out.println(A类无参构造器);}
}
class B extends A{B(){System.out.println(B类无参构造器);}
}
class Test{public static void main(String[] args){B b new B();//A类显示声明一个无参构造//B类显示声明一个无参构造 //B类的无参构造中虽然没有写super()但是仍然会默认调用A类的无参构造//可以看到会输出“A类无参构造器和B类无参构造器)}
}情景举例4
class A{A(){System.out.println(A类无参构造器);}
}
class B extends A{B(){super();System.out.println(B类无参构造器);}
}
class Test{public static void main(String[] args){B b new B();//A类显示声明一个无参构造//B类显示声明一个无参构造 //B类的无参构造中明确写了super()表示调用A类的无参构造//可以看到会输出“A类无参构造器和B类无参构造器)}
}情景举例5
class A{A(int a){System.out.println(A类有参构造器);}
}
class B extends A{B(){System.out.println(B类无参构造器);}
}
class Test05{public static void main(String[] args){B b new B();//A类显示声明一个有参构造没有写无参构造那么A类就没有无参构造了//B类显示声明一个无参构造 //B类的无参构造没有写super(...)表示默认调用A类的无参构造//编译报错因为A类没有无参构造}
}情景举例6
class A{A(int a){System.out.println(A类有参构造器);}
}
class B extends A{B(){super();System.out.println(B类无参构造器);}
}
class Test06{public static void main(String[] args){B b new B();//A类显示声明一个有参构造没有写无参构造那么A类就没有无参构造了//B类显示声明一个无参构造 //B类的无参构造明确写super()表示调用A类的无参构造//编译报错因为A类没有无参构造}
}情景举例7
class A{A(int a){System.out.println(A类有参构造器);}
}
class B extends A{B(int a){super(a);System.out.println(B类有参构造器);}
}
class Test07{public static void main(String[] args){B b new B(10);//A类显示声明一个有参构造没有写无参构造那么A类就没有无参构造了//B类显示声明一个有参构造 //B类的有参构造明确写super(a)表示调用A类的有参构造//会打印“A类有参构造器和B类有参构造器}
}情景举例8
class A{A(){System.out.println(A类无参构造器);}A(int a){System.out.println(A类有参构造器);}
}
class B extends A{B(){super();//可以省略调用父类的无参构造System.out.println(B类无参构造器);}B(int a){super(a);//调用父类有参构造System.out.println(B类有参构造器);}
}
class Test8{public static void main(String[] args){B b1 new B();B b2 new B(10);}
}this与super
1、this和super的意义
this当前对象
在构造器和非静态代码块中表示正在new的对象在实例方法中表示调用当前方法的对象
super引用父类声明的成员
2、this和super的使用格式
this this.成员变量表示当前对象的某个成员变量而不是局部变量this.成员方法表示当前对象的某个成员方法完全可以省略this.this()或this(实参列表)调用另一个构造器协助当前对象的实例化只能在构造器首行只会找本类的构造器找不到就报错 super super.成员变量表示当前对象的某个成员变量该成员变量在父类中声明的super.成员方法表示当前对象的某个成员方法该成员方法在父类中声明的super()或super(实参列表)调用父类的构造器协助当前对象的实例化只能在构造器首行只会找直接父类的对应构造器找不到就报错
子类对象实例化全过程
class Creature{ //生物类//声明属性、方法、构造器
}class Animal extends Creature{ //动物类}class Dog extends Animal{ //狗类}class DogTest{public static void main(String[] args){Dog dog new Dog();dog.xxx();dog.yyy ...;}
}从结果的角度来看体现为类的继承性 当我们创建子类对象后子类对象就获取了其父类中声明的所有的属性和方法在权限允许的情况下可以直接调用。 从过程的角度来看 当我们通过子类的构造器创建对象时子类的构造器一定会直接或间接的调用到其父类的构造器而其父类的构造器同样会直接或间接的调用到其父类的父类的构造器…直到调用了Object类中的构造器为止。正因为我们调用过子类所有的父类的构造器所以我们就会将父类中声明的属性、方法加载到内存中供子类的对象使用。 问题在创建子类对象的过程中一定会调用父类中的构造器吗 yes! 问题创建子类的对象时内存中到底有几个对象 就只有一个对象即为当前new后面构造器对应的类的对象。分配内存是new的事情构造方法我们其实可以把它当作一种初始化的手段 Dog dog new Dog(小花,小红);举例
class Creature {public Creature() {System.out.println(Creature无参数的构造器);}
}
class Animal extends Creature {public Animal(String name) {System.out.println(Animal带一个参数的构造器该动物的name为 name);}public Animal(String name, int age) {this(name);System.out.println(Animal带两个参数的构造器其age为 age);}
}
public class Dog extends Animal {public Dog() {super(汪汪队阿奇, 3);System.out.println(Dog无参数的构造器);}public static void main(String[] args) {new Dog();}
}
结果
Creature无参数的构造器
Animal带一个参数的构造器该动物的name为汪汪队阿奇
Animal带两个参数的构造器其age为3
Dog无参数的构造器我们可以看出创建一个子类是从上往下依次构建的最后才构造出来他自己。
对于多态的再理解
多态性是面向对象中最重要的概念在Java中的体现对象的多态性父类的引用指向子类的对象
格式父类类型指子类继承的父类类型或者实现的接口类型
父类类型 变量名 子类对象对象的多态在Java中子类的对象可以替代父类的对象使用。所以一个引用类型变量可能指向(引用)多种不同类型的对象
Java引用变量有两个类型编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定运行时类型由实际赋给该变量的对象决定。简称编译时看左边运行时看右边。
若编译时类型和运行时类型不一致就出现了对象的多态性(Polymorphism)多态情况下“看左边”看的是父类的引用父类中不具备子类特有的方法 “看右边”看的是子类的对象实际运行的是子类重写父类的方法
多态的使用前提① 类的继承关系 ② 方法的重写
为什么需要多态
多态机制使得具有不同内部结构的对象可以共享相同的外部接口。利用多态可以得到良好的设计开发中有时我们在设计一个数组、或一个成员变量、或一个方法的形参、返回值类型时无法确定它具体的类型只能确定它是某个系列的类型。
说的官方一点
可替换性多态对已存在的代码具有可替换性可扩充性增加新的子类并不影响已存在类的多态性、继承性以及其他特性的运行和操作接口性多态是父类超类通过方法签名向子类提供了共同的接口子类可以通过覆写完善或者覆盖这个接口。灵活性在应用中体现了灵活多样的操作提高了使用效率。
多态的好处和弊端
好处变量引用的子类对象不同执行的方法就不同实现动态绑定。代码编写更灵活、功能更强大可维护性和扩展性更好了。
弊端一个引用类型变量如果声明为父类的类型但实际引用的是子类对象那么该变量就不能再访问子类中添加的属性和方法。
Student m new Student();
m.school pku; //合法,Student类有school成员变量
Person e new Student();
e.school pku; //非法,Person类没有school成员变量// 属性是在编译时确定的编译时e为Person类型没有school成员变量因而编译错误。开发中 使用父类做方法的形参是多态使用最多的场合。即使增加了新的子类方法也无需改变提高了扩展性符合开闭原则。 【开闭原则OCP】 对扩展开放对修改关闭通俗解释软件系统中的各种组件如模块Modules、类Classes以及功能Functions等应该在不修改现有代码的基础上引入新功能 虚方法调用(Virtual Method Invocation)
在Java中虚方法是指在编译阶段不能确定方法的调用入口地址在运行阶段才能确定的方法即可能被重写的方法。
Person e new Student();
e.getInfo(); //调用Student类的getInfo()方法子类中定义了与父类同名同参数的方法在多态情况下将此时父类的方法称为虚方法父类根据赋给它的不同子类对象动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
举例 前提Person类中定义了welcome()方法各个子类重写了welcome()。
执行多态的情况下调用对象的welcome()方法实际执行的是子类重写的方法。 拓展 静态链接或早起绑定当一个字节码文件被装载进JVM内部时如果被调用的目标方法在编译期可知且运行期保持不变时。这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接(编译期发生)。那么调用这样的方法就称为非虚方法调用。比如调用静态方法、私有方法、final方法、父类构造器、本类重载构造器等。 动态链接或晚期绑定如果被调用的方法在编译期无法被确定下来也就是说只能够在程序运行期将调用方法的符号引用转换为直接引用由于这种引用转换过程具备动态性因此也就被称之为动态链接(运行期发生)。调用这样的方法就称为虚方法调用。比如调用重写的方法针对父类、实现的方法针对接口。 成员变量没有多态性 若子类重写了父类方法就意味着子类里定义的方法彻底覆盖了父类里的同名方法系统将不可能把父类里的方法转移到子类中。 对于实例变量则不存在这样的现象即使子类里定义了与父类完全相同的实例变量这个实例变量依然不可能覆盖父类中定义的实例变量
package com.atguigu.polymorphism.grammar;public class TestVariable {public static void main(String[] args) {Base b new Sub();System.out.println(b.a);System.out.println(((Sub)b).a);Sub s new Sub();System.out.println(s.a);System.out.println(((Base)s).a);}
}
class Base{int a 1;
}
class Sub extends Base{int a 2;
}向上转型与向下转型
首先一个对象在new的时候创建是哪个类型的对象它从头至尾都不会变。即这个对象的运行时类型本质的类型永远不会变。但是把这个对象赋值给不同类型的变量时这些变量的编译时类型却不同。
为什么要类型转换
因为多态就一定会有把子类对象赋值给父类变量的时候这个时候在编译期间就会出现类型转换的现象。
但是使用父类变量接收了子类对象之后我们就不能调用子类拥有而父类没有的方法了。这也是多态给我们带来的一点小麻烦。所以想要调用子类特有的方法必须做类型转换使得编译通过。 向上转型当左边的变量的类型父类 右边对象/变量的类型子类我们就称为向上转型 此时编译时按照左边变量的类型处理就只能调用父类中有的变量和方法不能调用子类特有的变量和方法了但是运行时仍然是对象本身的类型所以执行的方法是子类重写的方法体。此时一定是安全的而且也是自动完成的 向下转型当左边的变量的类型子类右边对象/变量的编译时类型父类我们就称为向下转型 此时编译时按照左边变量的类型处理就可以调用子类特有的变量和方法了但是运行时仍然是对象本身的类型不是所有通过编译的向下转型都是正确的可能会发生ClassCastException为了安全可以通过isInstanceof关键字进行判断
如何向上或向下转型
向上转型自动完成
向下转型子类类型父类变量
package com.atguigu.polymorphism.grammar;public class ClassCastTest {public static void main(String[] args) {//没有类型转换Dog dog new Dog();//dog的编译时类型和运行时类型都是Dog//向上转型Pet pet new Dog();//pet的编译时类型是Pet运行时类型是Dogpet.setNickname(小白);pet.eat();//可以调用父类Pet有声明的方法eat但执行的是子类重写的eat方法体
// pet.watchHouse();//不能调用父类没有的方法watchHouseDog d (Dog) pet;System.out.println(d.nickname d.getNickname());d.eat();//可以调用eat方法d.watchHouse();//可以调用子类扩展的方法watchHouseCat c (Cat) pet;//编译通过因为从语法检查来说pet的编译时类型是PetCat是Pet的子类所以向下转型语法正确//这句代码运行报错ClassCastException因为pet变量的运行时类型是DogDog和Cat之间是没有继承关系的}
}instanceof关键字
为了避免ClassCastException的发生Java提供了 instanceof 关键字给引用变量做类型的校验。如下代码格式
//检验对象a是否是数据类型A的对象返回值为boolean型
对象a instanceof 数据类型A 说明 只要用instanceof判断返回true的那么强转为该类型就一定是安全的不会报ClassCastException异常。如果对象a属于类A的子类Ba instanceof A值也为true。要求对象a所属的类与类A必须是子类和父类的关系否则编译错误。
代码
package com.atguigu.polymorphism.grammar;public class TestInstanceof {public static void main(String[] args) {Pet[] pets new Pet[2];pets[0] new Dog();//多态引用pets[0].setNickname(小白);pets[1] new Cat();//多态引用pets[1].setNickname(雪球);for (int i 0; i pets.length; i) {pets[i].eat();if(pets[i] instanceof Dog){Dog dog (Dog) pets[i];dog.watchHouse();}else if(pets[i] instanceof Cat){Cat cat (Cat) pets[i];cat.catchMouse();}}}
}