1 封装
面向对象编程语言,需要对现实世界中的事物进行抽象、模拟。现实世界中的对象属性,都是隐藏在对象内部的,外界无法直接操作和修改。
在类中定义属性的时候,一般需要把属性隐藏起来。
如果外界需要访问这个属性,那么就提供公共方法对其访问
例如,
public class Student{
//使用private关键字来修饰属性,不运行外部直接访问该属性
private String name;
//提供公共的setName方法,可以让外部调用该方法给name属性赋值
public void setName(String name){
this.name = name;
}
//提供公共的getName方法,可以让外部调用该方法获取name属性的值
public String getName(){
return name;
}
}
setXxx方法,是设置指定的xxx属性值用的
getXxx方法,是获取指定的xxx属性值用的
代码中,private
修饰符的特点:
- private是四种权限修饰符中的一种,并且是权限最小的一种
- private可以修饰成员变量和成员方法
- private修饰成的属性和方法,就只在当前类中才能访问,在当前类以外的其他地方,都不能访问
- private修饰的属性,称为类中的私有属性,private修饰的方法,称为类中的私有方法
例如:
public class Student{
//私有属性,只允许在当前类中访问,出了这个类的范围,就不能访问
private String name;
//私有方法,只允许在当前类中调用,出了这个类的范围,就不能调用
private void test(){
}
}
public class Test{
public static void main(String[] args){
Student stu = new Student();
stu.name = "tom";//编译报错,不允许访问
stu.test();//编译报错,不允许访问
}
}
封装的优点:
-
提高代码的安全性,重要信息可以私有化,不对外暴露
-
提高代码的复用性,常用的代码或者功能封装到方法中,可以在其他地方反复调用
-
封装代码的实现细节,便于修改内部代码,提高可维护性
-
简化外部的调用,便于调用者使用
其实,我们在定义一个类的时候,就是在完成封装的过程。
2 方法重载
类中有多个方法,具有相同的方法名,但是方法的参数各不相同,这种情况被称为方法的重载(overload)
方法的重载,必须是在同一个类中,并且要求如下:
- 方法的名字必须相同
- 方法的参数列表必须不同
- 参数的类型不同
- 参数的个数不同
- 参数的顺序不同
- 方法的修饰符、返回类型、抛出异常这些地方没有限制(可以相同,也可以不同,但一般都是相同的)
例如,
public class Test{
//参数的个数不同
public void test(){}
public void test(int a){}
public void test(String s1,String s2){}
//参数的类型不同
public void test(boolean flag){}
public void test(double salary){}
//参数的顺序不同
public void test(int a,String s){}
public void test(String s,int a){}
//方法的参数可以相同,也可以不同
public int test(int a,int b){
return 0;
}
}
特殊情况:
public class Test{
//方法重载
public void test(int a,long b){}
public void test(long b,int a){}
public static void main(String[] args){
Test t = new Test();
t.test(1,1L);//调用到第一个方法
t.test(1L,1);//调用到第二个方法
t.test(1,1);//问题:这个会调用到哪一个test方法?
}
}
注意,这时候,
t.test(1,1)
代码是会编译报错的,因为类中定义的俩个方法都匹配注意,当参数无法完全精确匹配到方法的时候,参数会尝试做自动转换,然后再去尝试匹配方法
例如,
public class Test {
//重载方法1
public void test(int a){}
//重载方法2
public void test(short a){}
public static void main(String[] args){
Test t = new Test();
byte a = 1;
t.test(a);//这里会调用第二个方法,也就是short类型参数的test方法
}
}
虽然byte类型数据,可以自动转换为short,也可以转换为int,但是short离byte“更近”
3 创建和初始化对象
new Student(),该代码完成了俩个过程,对象的创建和初始化
例如,以下面代码为例进行说明
public static void main(String[] args){
new Student();
}
-
new关键字,给对象申请/分配内存空间,并将对象中的属性进行默认值的初始化,根据属性类型的不同,其默认值分别为:0 0.0 false ‘\u0000’ null
-
如果在代码中,还给这些属性进行了显示赋值,那么就会用这个显示赋的值,把之前的默认值给覆盖掉。
显示赋值:
private String name = "tom";
最开始name的值是null,之后做了显示赋值,name的值就变为tom
-
调用类中的构造器,在构造器中也可以对属性进行赋值,这个赋值会把之前默认值或者显示赋值给覆盖掉
注意,这时候对象已经创建出来了,并且属性也有了值(可能是默认值,也可以是显示赋值,也可能构造器中赋的值),如果我们想再设置其他的属性值,可以使用对象访问属性,或者调用方法。
如果是public属性,那么就可以直接使用对象进行访问并赋值。(不推荐)
如果是private属性,那么就需要使用对应的setXxx方法进行属性值的的设置。(推荐)
成员变量和局部变量的区别?
成员变量直接出现在类中,局部变量出现在方法和代码块中,
成员变量随对象创建而初始化,对象被回收时被销毁(ps:生命周期与对象一致),局部变量随方法入栈而初始化,随着方法出栈而销毁。(ps:生命周期与方法一致)。成员变量会默认初始化,而局部变量不会。
4 构造器
类中的构造器也称为构造方法、构造函数,是在创建对象的时候必须要调用的
构造器有以下俩个特点:
- 必须和类的名字保持一致
- 必须没有返回类型,也不能写void
public class Student{
//构造器
public Student(){
}
}
构造器的作用:
- 使用new关键字来创建对象的时候,后面跟的必须是类中存在的构造器
- 构造器中的代码,在对象创建后会被调用,从而可以完成对象的初始化工作
public class Student{
private String name;
private int age;
//构造器
public Student(String name,int age){
this.name = name;
this.age = age;
}
public static void main(String[] args){
//创建对象的时候使用构造器
//并且传参到构造器中,构造器中可以使用这些参数给属性进行初始化
Student stu = new Student("tom",20);
}
}
构造器的重载:
除了默认的无参构造器之外,在类中还可以对构造器进行重载,让构造器可以接收一些参数,然后使用这些参数进行对象的初始化工作。
public class Student{
private String name;
private int age;
//无参构造器
public Student(){}
//有参数构造器,接收参数,进行对象属性的初始化
public Student(String name){
this.name = name;
}
public Student(int age){
this.age = age;
}
public Student(String name,int age){
this.name = name;
this.age = age;
}
}
构造器之间的调用:
使用this关键字,可以在构造器中,调用另一个构造器
public class Student{
private String name;
private int age;
//无参构造器
public Student(){
//调用俩参的构造器
this("tom",20);
}
public Student(String name){
//调用俩参的构造器
this(name,0)
}
public Student(int age){
//调用俩参的构造器
this(null,age);
}
public Student(String name,int age){
this.name = name;
this.age = age;
}
}
默认构造器:
即使在类中没有定义构造器,那么在编译之后,也会自动的生成一个无参构造器,并且构造器中不执行任何代码。这个无参构造器就被称为默认的构造器。
我们也可以主动在类中定义出这个无参的默认构造器:
public class Student{
private String name;
private int age;
//这个构造器即使没有写出来,编译后也会自动生成的
public Student(){
}
}
需要注意的时候,如果我们在类中定义了一个构造器,那么系统就不在为该类生成无参的默认构造器了。
5 继承
类和类之间的关系有很多中,继承就是其中一种关系,除了之外还有依赖、组合、聚合等
继承描述的是事物之间的所属关系,这种关系是: is-a 的关系。
子类继承父类,子类就可以继承父类中定义的属性和方法。
例如,
蛇属于爬行动物,爬行动物属于动物。
爬行动物继承了动物的基本属性和方法,蛇又继承了爬行动态的基本属性和方法。
由此可见,父类更通用,子类更具体。
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么这些类(子类)就不需要再定义这些属性和行为,只要继承同一个类(父类),它们就可以直接访问父类中的非私有的属性和方法。
继承的好处:
- 提高代码的复用性。
- 类与类之间产生了关系(is a),这是使用多态特性的前提。
继承的关键字:
子类继承父类,使用关键字extends
//定义Person类,作为父类
public class Person{
String name;
public void sayHello(){
System.out.println("hello~ I am "+name);
}
}
//定义Student类,作为子类
class Student extends Person{
}
public static void main(String[] args){
Student stu = new Student();
stu.name = "tom";//name属性从父类中继承而来
stu.sayHello();//sayHello方法从父类中继承而来
}
从语法上讲,一个类可以继承任何的另一个类,只要满足语法要求即可。但是从意义上来考虑的话,这种继承关系可能并不合适。一般只有具有"is a"关系的俩个类直接,才会考虑是否要使用继承
子类继承父类,继承了父类中的属性和方法,并可以在子类中访问这些属性和方法:
//定义Person类,作为父类
public class Person{
String name;
private int age;
public void sayHello(){
System.out.println("hello~ I am "+name);
}
}
//定义Student类,作为子类
class Student extends Person{
//这个是子类中单独定义的方法,和父类无关
public void hi(){
name = "jack";//访问从父类中继承过来的方法
sayHello();//调用从父类中继承过来的方法
age = 20;//编译报错,age是父类中的私有属性,子类中不能访问
}
}
父类中的私有属性和方法,子类中不能访问。
注意,父类中的构造器,子类是不能继承的。
java中,类和类之间的继承是单继承:
//编译报错,一个类只能有且只有一个父类,不能同时继承俩个父类
public class Stduent extends Person,Object{
}
java中,如果没有给类指定父类的话,那么这个类会默认继承父类Object,:
//默认继承了Object,编译后会自动生成 extends Object 的代码语句
public class Stduent{
}
注意,Object类中没有属性。
java中,每个类都是直接或者间接了继承了Object,可以说Object是所有类的父类类型
思考,java中是否存在一个类,它是没有父类型的?
子类继承父类,创建子类对象的时候,会先默认调用父类的构造器:
//定义Person类,作为父类
public class Person{
public Person(){
System.out.println("Person类中的构造器被调用");
}
}
//定义Student类,作为子类
class Student extends Person{
public Student(){
System.out.println("Student类中的构造器被调用");
}
}
public static void main(String[] args){
new Student();
}
//main方法执行会输出以下语句:
Person类中的构造器被调用
Student类中的构造器被调用
子类继承父类,会继承父类的属性和方法,那么就需要先调用父类的构造器对父类中的属性进行初始化,初始化完成后再给子类使用。
构造器的作用之一就是进行初始化
6 super关键字
在类中,除了可以使用this关键字之外,还可以使用super关键字
在子类中,使用super关键字一般做以下事情:
-
访问父类中的属性
-
调用父类中的方法
-
调用父类中的构造器
访问父类中的属性:
public class Person{
String name = "zs";
}
public class Student extends Person{
//注意,这里是故意和父类中的属性重名
String name = "lisi";
public void test(){
//直接使用name,表示Student中的name属性(就近原则)
System.out.println(name);
//也可以使用this和super来区分访问的是哪个name
System.out.println(this.name);//Student中的name
System.out.println(super.name);//父类Person中的name
}
}
调用父类中的方法:
public class Person{
public void run(){
System.out.println("person run..");
}
}
public class Student extends Person{
//注意,这里是故意和父类中的方法重名
public void run(){
System.out.println("student run..");
}
public void test(){
//直接使用name,表示Student中的run方法(就近原则)
run();
//也可以使用this和super来区分调用的是哪个run方法
this.run();
super.run();
}
}
调用父类中的构造器:
子类构造器中隐式调用父类无参构造器,例如
public class Person{
public Person(){
}
}
public class Student extends Person{
public Student(){
//这里会隐式调用(自动调用)父类的无参构造器
}
}
子类构造器中显式调用父类无参构造器,例如
public class Person{
public Person(){
}
}
public class Student extends Person{
public Student(){
//也可以使用super关键字,显示调用父类的构造器(有参的无参的都可以调用)
super();
}
}
子类构造器中显式调用父类有参构造器,例如
public class Person{
public Person(){
}
public Person(String name){
}
}
public class Student extends Person{
public Student(){
//也可以使用super关键字,显示调用父类的构造器(有参的无参的都可以调用)
super("tom");
}
}
在构造器中,可以使用this调用类中其他构造器,也可以使用super调用父类中的构造器。
但是this和super这俩中调用构造器的代码,不能同时出现,否则会报错。
这是因为this调用构造器的语句和super调用构造器的语句,都要求自己是第一句代码,但是构造器中的第一句代码只能有一个,所以它们俩个不能同时出现,例如
public class Person{
public Person(){
}
}
public class Student extends Person{
//编译报错,在使用this和super调用构造器功能的时候,它们俩个不能同时出现
public Student(){
this("tom");
super();
}
public Student(String name){
}
}
但是,如果this和super不是都要调用构造器,那么同时出现就没有问题,例如
public class Person{
public void sayHello(){
}
}
public class Student extends Person{
//编译通过
public Student(){
this("tom");
super.sayHello();
}
public Student(String name){
}
}
思考,在子类的构造器中,为什么要调用父类的构造器?
7 方法重写
如果子类和父类中出现了相同的方法,这种情况就叫做方法重写 (Override)。
注意,方法重载是发生在同一个类中,方法重写发生在子父类之间
父类中的一个方法和子类中的一个方法,满足以下要求,就是方法的重写:
-
方法名必须相同
-
参数列表必须相同
-
访问控制修饰符可以被扩大,但是不能被缩小
public > protected > default > private
-
方法抛出异常类型的范围可以被缩小,但是不能被扩大
例如:ClassNotFoundException --扩大–> Exception
例如:Exception --缩小–> ClassNotFoundException
-
返回类型可以相同,也可以不同
-
如果父类的返回类型是引用类型,子类重写后的方法返回类型可以和父类方法的返回类型保持一致,也可以是父类方法返回类型的子类型
例如,父类方法的返回类型是Person,子类重写后的返回类可以是Person也可以是Person的子类型
-
如果父类的返回类型是基本类型,那么子类重写后的返回类型必须和父类的保持一致
例如: 父类方法的返回类型是int,子类重写后的返回类也必须是int
-
注意,大多数情况下,子类中重写的方法 会和 父类中的方法 完全保持一致,只有方法的实现不同。(也就是大括号中代码不一样)
父类中哪些方法不能被重写:
- 父类中的静态方法不能被子类重写
- 父类中的私有方法不能被子类重写
也就是说,只有在子类中可以直接访问到的父类方法,并且是非静态的方法,才能被子类重写
常见的重写情况,例如
public class Person{
public void sayHello(){
System.out.println("你好!很高兴认识你");
}
}
public class Student extends Person{
//子类中,重写父类的sayHello方法
public void sayHello(){
System.out.println("hello!nice to meet you");
}
}
public static void main(String[] args) {
Student stu = new Student();
//由于子类重写了sayHello方法,所以这里将会调用到子类中重写后的sayHello
//如果子类中没有重写sayHello方法,那么这里将会调用到从父类继承过来的sayHello方法
stu.sayHello();
}
子类继承父类,在调用方法的时候,如果子类中没用重写,那么调用的时候从父类继承的方法,如果子类重写了这个方法,那么将会调用到子类中重写后的方法。(非常重要)
在JavaAPI中,String类继承了Object,并且重写了Object类中的toString、equals等方法:
注意,Object所所有类的父类,每一个类都是直接或间接继承了Object类
所以,在调用String对象的toString、equals等方法时候,其实调用的是子类String中重写后的方法!
思考,一般什么情况下,我们会在子类中,对从父类继承的方法进行重写?
子类继承父类,继承了父类中的方法,但是父类中的方法并不一定能满足子类中的功能需要,所以子类中需要把方法进行重写。
方法重写与方法重载的区别是什么?
overload:
发生在本类中
方法名与父类方法名相同,参数列表不同(类型,顺序,数量)
override
发生在子父类之间
方法名与父类方法相同,参数列表也与父类方法相同
重写的方法修饰符要么与父类方法相同,要么比父类方法权限小
重写的方法抛出的异常要么与父类方法相同,要么比父类方法小
重写的方法的返回类型为基本类型时,一定要与父类的返回值类型相同,如果是引用类型的话,重写的方法返回值类型要么更父类一致,要么是父类的返回值的子类
8 多态
相同类型的不同对象,调用同一个方法,最终执行结果是不同的
例如,猫、狗、猎豹都属于动物类,它们都有“跑”这个共同的行为,但是它们各自跑起来的方式又是不一样的。由此可见,不同的对象,进行同一个行为,但是它们的表现出来的确实不同的形态。
java中的多态,就是来描述这种情况的。
多态的前提:
- 子类继承父类
- 子类重写父类中的方法
- 父类的引用指向子类对象
注意,类实现接口,这也是一直特殊形式的继承,多态也可以体现在类和接口的关系中。
例如,
public class Person {
public void sayHello(){
System.out.println("你好!");
}
}
class Student extends Person{
public void sayHello(){
System.out.println("hello!我是一名酷酷的学生");
}
}
class Teacher extends Person{
public void sayHello(){
System.out.println("hi!我是一名酷酷的老师");
}
}
public static void main(String[] args) {
//声明父类的引用
Person person;
int random = (int) (Math.random()*10);
//根据随机数的情况,让父类引用person指向不同的子类对象(Student对象或者Teacher对象)
if(random%2 == 0){
person = new Student();
}
else{
person = new Teacher();
}
//使用person引用,调用子类对象中重写的方法
//关键点在于,在调用sayHello方法的时候,引用person指向的对象是谁
person.sayHello();
}
同一类型(Person)的引用,指向不同的子类对象(Student或者Teacher),调用同一个方法(sayHello),最后是不一样的表现形式(执行结果不同)!
例如,
public class Animal {
public void eat(){}
}
class Cat extends Animal {
public void eat() {
System.out.println("我是喵咪,我喜欢吃鱼");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("我是狗仔,我喜欢吃骨头");
}
}
public static void main(String[] args) {
//多态形式,创建对象
//父类引用指向子类对象
Animal a = new Cat();
//调用的是Cat中重写的eat方法
//因为这时候引用a指向的是Cat类型对象
a.eat();
//父类引用指向另一个子类对象
a = new Dog();
//调用的是Dog中重写的eat方法
//因为这时候引用a指向的是Dog类型对象
a.eat();
}
注意,一个父类型的引用,可以指向它的任何一个子类对象
程序中使用多态的好处:
有一个Game类,里面有俩个方法,可以分别运行篮球游戏和足球游戏。
第一种情况,不使用多态
public class Game {
public void start(BasketBall basketBall){
basketBall.play();
}
public void start(Football football){
football.play();
}
}
class BasketBall{
public void play(){
System.out.println("篮球游戏开始初始化...");
}
}
class Football{
public void play(){
System.out.println("足球游戏开始初始化...");
}
}
public static void main(String[] args) {
Game game = new Game();
BasketBall basketBall = new BasketBall();
game.start(basketBall);
Football football = new Football();
game.start(football);
}
在这种情况下, 如果多了一种乒乓球游戏要运行,代码代码要如果修改?
代码修改如下:
public class Game {
public void start(BasketBall basketBall){
basketBall.play();
}
public void start(Football football){
football.play();
}
//Game中新增运行乒乓球游戏的方法
public void start(PingPangBall pingPangBall){
pingPangBall.play();
}
}
class BasketBall{
public void play(){
System.out.println("篮球游戏开始初始化...");
}
}
class Football{
public void play(){
System.out.println("足球游戏开始初始化...");
}
}
//新增乒乓球类
class PingPangBall{
public void play(){
System.out.println("乒乓球游戏开始初始化...");
}
}
public static void main(String[] args) {
Game game = new Game();
BasketBall basketBall = new BasketBall();
game.start(basketBall);
Football football = new Football();
game.start(football);
//新增创建乒乓球对象
PingPangBall pingPangBall = new PingPangBall();
//新增调用game中的对应的运行乒乓球游戏的方法
game.start(pingPangBall);
}
思考,如果一直不断的新增游戏的种类,代码修改的起来是否会很麻烦?
第二种情况,使用多态
public class Game {
//这里只需要定义一个方法,参数类型是Ball类型
//这个Ball是父类,它的引用可以指向任何一个子类对象
public void start(Ball ball){
ball.play();
}
}
//父类
class Ball{
public void play(){}
}
//子类
class BasketBall extends Ball{
public void play(){
System.out.println("篮球游戏开始初始化...");
}
}
//子类
class Football extends Ball{
public void play(){
System.out.println("足球游戏开始初始化...");
}
}
public static void main(String[] args) {
Game game = new Game();
//声明父类的引用
Ball ball;
//指向一个子类对象
ball = new BasketBall();
//这里想要切换游戏,只需要让引用ball执行对应的子类对象即可
//ball = new FootBall();
game.start(ball);
}
在这种情况下, 如果多了一种乒乓球游戏要运行,代码代码要如果修改?
代码修改如下:
public class Game {
//这里只需要定义一个方法,参数类型是Ball类型
//这个Ball是父类,它的引用可以指向任何一个子类对象
public void start(Ball ball){
ball.play();
}
}
//父类
class Ball{
public void play(){}
}
//子类
class BasketBall extends Ball{
public void play(){
System.out.println("篮球游戏开始初始化...");
}
}
//子类
class Football extends Ball{
public void play(){
System.out.println("足球游戏开始初始化...");
}
}
/*整个代码中,基本只有这里是新增的,其他地方不需要改动*/
/*新增乒乓球类*/
class PingPangBall extends Ball{
public void play(){
System.out.println("乒乓球游戏开始初始化...");
}
}
public static void main(String[] args) {
Game game = new Game();
//声明父类的引用
Ball ball;
//指向一个子类对象
ball = new BasketBall();
//这里想要切换游戏,只需要让引用ball执行对应的子类对象即可
//ball = new FootBall();
//ball = new PingPangBall();
game.start(ball);
}
可以看出,这时候想新增一个球类游戏,并且去运行它,几乎不需要修改什么代码,只需要新增这个类即可
思考:下面这个方法的参数,都可以接收哪些对象?
public void test(Object obj){
}
9 instanceof
在使用多态的情况下,instanceof关键字就显得特别重要,因为它能告诉我们,当前父类的引用,到底是执行的哪一个子类对象
例如,
public class Person {
}
class Student extends Person{
}
class Teacher extends Person{
}
在这种情况下, 假设有一个test方法:
由多态可知,test方法的参数p,既可以接收Student类型的对象,也可以接收Teacher类型的对象,那么在这个test中,如何能知道,将来到底接收到的是哪一个类型的对象呢?
public void test(Person p){
//引用p到底是指向的Student对象还是Teacher对象呢??
}
通过instanceof关键字进行判断即可知道引用p到底接收的是一个类型的对象:
public void test(Person p){
if(p instanceof Student){
//说明p指向的是Student类型对象
}
else if(p instanceof Teacher){
//说明p指向的是Teacher类型对象
}
}
思考,假设通过instanceof关键字的判断,知道了引用p到底指向的是哪个类型对象,接下来又能做什么
10 引用类型的转换
多态中的转型分为向上转型与向下转型两种:
-
向上转型(子类转父类)
多态本身就是将子类的对象赋值给父类的引用,这就是一个自动向上转型的过程(类型自动转换)
例如,
Person p = new Student();
-
向下转型(父类转子类)
父类类型向子类类型转换,是向下转换的过程。(类型强制转换)
例如,
Person p = new Student(); Student s = (Student)p; //向下转型
为什么需要做向下转型:
当使用多态方式调用方法时,编译器会先检查父类中是否有该方法,如果父类中没有,则编译错误
在这种情况下,即使子类中有这个方法,也是无济于事,因为编译器这一关都过不去,更不要说去运行了。
也就是说,在使用多态的情况下,父类引用是无法调用到子类中独有的方法的!
例如,
public class Person {
public void sayHello(){}
}
class Student extends Person{
//注意,这是子类中单独定义的方法,和父类无关
public void study(){}
}
public static void main(String[] args) {
//多态,父类的引用指向子类对象
Person p = new Student();
p.sayHello();//编译运行都没问题,子类继承了父类中sayHello方法
//编译报错
//因为引用p是Person类型的,而Person类型中就没有定义这个study方法
//编译器检查到Person中没有这个方法就直接报错了
p.study();
}
在这个情况下,只能将引用p向下转型,转成Student类型,才能调用到study方法:
public static void main(String[] args) {
//多态,父类的引用指向子类对象
Person p = new Student();
p.sayHello();//编译运行都没问题,子类继承了父类中sayHello方法
Student s = (Student)p;
//编译通过
//因为引用s是Student类型的,而Student类型中真的有这个study方法
s.study();
}
引用类型强制转换时的异常:
在类型强制转换的过程中,可能会遇到类型转换异常,例如:
public class Person {
}
class Student extends Person{
//注意,这是子类中单独定义的方法,和父类无关
public void study(){}
}
class Teacher extends Person{
}
public static void main(String[] args) {
Person p = new Teacher();
//编译通过,运行报错!
//错误类型是:ClassCastException
Student s = (Student)p;
s.study();
}
上面代码运行报错的原因,是因为运行过程中,强制转换的时候,引用p实际指向的对象是Teacher类型的,在这种情况下,它是不能转为Student对象的,因为Teacher对象和Student类型没有任何子父类的关系!
那么这句代码 Student s = (Student)p;
想转换成功,最终运行不报错,只要一直情况,那就是在转换的时候,引用p指向的对象,真的就是Student类型的对象才可以。
那么如果取得引用p指向的对象真的就是Student类型的呢? 使用关键字instanceof进行判断即可:
public static void main(String[] args) {
Person p = new Teacher();
//如果引用p真的是指向Student类型的对象,那么才进行类型的强制转换
//避免在强制转换的过程中出现ClassCastException类型的异常
if(p instanceof Student){
Student s = (Student)p;
s.study();
}
}
11 案例
//动物园管理员
public class ZooManager {
//管理员要完成的工作,让动物吃饱然后让它活动活动
public void doWork(Animal animal){
//管理员让这个动物吃饱
animal.eat();//父类中的方法,所以动物都有
//管理员让这个动物活动活动
if(animal instanceof Bird){
//如果是鸟的话,就让他飞翔吧
Bird bird = (Bird) animal;
bird.fly();//子类中独有的方法,只能是鸟类对象才能调用
}
else if(animal instanceof Horse){
//如果是马的话,就让他奔跑吧
Horse horse = (Horse) animal;
horse.run();//子类中独有的方法,只能是马类对象才能调用
}
}
}
//动物类,父类
class Animal{
String name;
public void eat(){
System.out.println("我是"+name+",我吃饱了!");
}
}
//鸟类,子类
class Bird extends Animal{
//子类中独有的方法
public void fly(){
System.out.println("我要起飞啦~");
}
}
//马类,子类
class Horse extends Animal{
//子类中独有的方法
public void run(){
System.out.println("我要奔跑啦");
}
}
public static void main(String[] args){
ZooManager zooManager = new ZooManager();
Animal animal;
//可以更换动物,让管理员针对当前这个动物进行工作
animal = new Bird();
//animal = new Horse();
zooManager.doWork(animal);
}