大家好,欢迎来到IT知识分享网。
面向对象程序的三大特性:封装,继承,多态
1. 封装
封装:将数据和操作数据的方法有机结合,隐蔽对象的属性和实现细节,仅对外公开接口来和对象进行交互。
1.1 访问限定符
JAVA主要通过类和访问权限来实现封装:类将数据和封装数据的方法结合在一起,而访问限定符控制方法或者属性能否直接在类外使用,除了可以限定类中成员的可见性,也可以控制类的可见性
范围从小到大依次是private,default,protected,public
可以这样理解:
public:一个人的外貌特征,谁都可以看到 default(什么都不写时的默认权限):对于自己家族不是秘密(同一个包中),对于其他人来说是隐私 private:只有自己知道,其他人都不知道
1.2 先看private:
例:
public class Student {
private String name; public int age; } public class Test {
public static void main(String[] args) {
Student s=new Student(); s.name="zuozuo";//这一句编译不通过,会报错 } } //结果:java: name 在 TESTDEMO.Student 中是 private 访问控制
我们知道,private修饰的属性只可以在当前类中使用
1.3 默认权限
默认权限只能在同一个包中使用,默认权限也叫做包访问权限,被其修饰的属性方法只能在当前包中使用。
1.3.1 扩展一下包的概念:
软件包:为了更好的管理类,把多个类收集在一起成为一组,称为软件包。
包的另一个重要作用:在同一个工程中允许存在名称相同的类,只要处在不同的包中即可。
1.3.2 导入包中的类:
JAVA中提供了很多现成的类供我们使用,例如Date类,可以使用java.util.Date
导入java.util
这个包中的Date
类
一种简便的方法,使用import语句导入包:
例:如果需要使用java.util中的其他类,可以使用import java.util.*
import java.util.*; public class Test {
public static void main(String[] args) {
Date date=new Date(); System.out.println(date.getTime());//得到一个毫秒级别的时间戳 } }
但是,更建议显式的指定要导入的类名,否则容易出现冲突的情况
例:
import java.util.*; import java.sql.*; public class Test {
public static void main(String[] args) {
Date date=new Date(); System.out.println(date.getTime()); } } //结果:java: 对Date的引用不明确 //java.sql 中的类 java.sql.Date 和 java.util 中的类 java.util.Date 都匹配
因为util和sql
中都存在一个Date这样的类,此时会出现歧义,编译出错
在这种情况下需要使用完整的类名:
import java.util.*; import java.sql.*; public class Test {
public static void main(String[] args) {
java.util.Date date=new java.util.Date(); System.out.println(date.getTime()); } }
可以使用import static
导入包中的静态方法和属性
例:
import static java.lang.Math.*; public class Test {
public static void main(String[] args) {
double x=30; double y=40; //静态导入的方式写起来更方便 //double result=Math.sqrt(Math.pow(x,2)+Math.pow(y,2)); double result=sqrt(pow(x,2)+pow(y,2)); System.out.println(result); } }
注意,import static java.lang.Math.*;
这个语句导入了Math类的所有静态方法,但是是随用随取。
并且,import
和C++的#include
差别很大。
1.4 pretected关键字
被protected修饰的可以在同一包中的同一类使用,可以在同一包中的不同类,可以在不同包中的子类中被使用(与继承有关)。
注意:
父类中private成员变量虽然子类不能直接访问,但是也继承到子类了
1.5 自定义包
基本规则:
1.在文件最上方加一个package语句指定该段代码在哪个包中
例:
package com.bite.www2; //声明当前类在哪个包底下
2.如果一个类没有package语句,则被放到默认包中
1.6 static成员
以学生类为例子,我们对学生类实例化了三个队象s1,s2,s3,每个对象都有自己特有的名字,年龄,性别,分数:
例:
public class Student {
public String name; public int age; public String sex; public double score; public Student(String name, int age, String sex, double score) {
this.name = name; this.age = age; this.sex = sex; this.score = score; } @Override public String toString() {
return "Student{" + "name='" + name + '\'' + ", age=" + age + ", sex='" + sex + '\'' + ", score=" + score + '}'; } public static void main(String[] args) {
Student s1=new Student("hh",6,"nv",98); Student s2=new Student("zuozuo",9,"nv",90); Student s3=new Student("ww",10,"nan",70); System.out.println(s1.toString()); } }
s3对象内容如图:
我们假设三个同学是一个班级的,那上课肯定在同一个教室,那能否给类中加一个成员变量,来保存同学上课时的教室,答案是不行的。
因为之前在Student类中定义的成员变量,每个对象中都包含一份(称为实例变量),教室这个属性并不需要每个学生对象中都存储一份,而是需要让所有的学生来共享。在JAVA中,被static修饰的成员,称为静态成员,或者类成员,其不属于某个具体的对象,是所有对象共享的。
1.6.1 static修饰成员变量
static修饰的成员变量称为静态成员变量,静态成员变量最大的特性是:不属于某个具体的对象,是所有对象所共享的。
特性: 1.不属于某个具体的对象,是类的属性,所有对象共享,不存在某个对象的空间中 2.既可以通过**对象访问**,也可以通过**类名访问**,更推荐类名访问 3.类变量**存储在方法区**中 4.生命周期伴随类的一生(随类的加载而创建,类的卸载而销毁)
例:
public class Student {
public String name; public int age; public String sex; public double score; public static String classRoom="102";//classRoom被static修饰,不属于任何一个对象 public Student(String name, int age, String sex, double score) {
this.name = name; this.age = age; this.sex = sex; this.score = score; } @Override public String toString() {
return "Student{" + "name='" + name + '\'' + ", age=" + age + ", sex='" + sex + '\'' + ", score=" + score + '}'; } public static void main(String[] args) {
Student s1=new Student("hh",6,"nv",98); Student s2=new Student("zuozuo",9,"nv",90); Student s3=new Student("ww",10,"nan",70); System.out.println(s1.classRoom); System.out.println(s2.classRoom); System.out.println(s3.classRoom); System.out.println(Student.classRoom);//也可以通过类名访问 } } //结果: //102 //102 //102 //102
上述代码通过调试,可以在监视窗口发现,静态成员变量并没有存储到某个具体的对象中。
1.6.2 static修饰成员方法
一般类中的成员属性设置为private,成员方法设置为public,那设置之后,Student类中的classRoom属性如何在类外访问?
例:
public class Student {
private String name; private int age; private String sex; private double score; private static String classRoom="102"; public Student(String name, int age, String sex, double score) {
this.name = name; this.age = age; this.sex = sex; this.score = score; }
在另一个类中:
public class Test {
public static void main(String[] args) {
System.out.println(Student.classRoom); } } //结果:java: classRoom 在 TESTDEMO.Student 中是 private 访问控制
静态成员变量一般通过静态成员方法来访问:
例:
public class Student {
private String name; private int age; private String sex; private double score; private static String classRoom="102"; public Student(String name, int age, String sex, double score) {
this.name = name; this.age = age; this.sex = sex; this.score = score; } public static String getClassRoom(){
return classRoom; }
在另一个类中:
public class Test {
public static void main(String[] args) {
System.out.println(Student.getClassRoom()); } } //结果:102
静态方法可以互相调用:
例:
public static void func(){
System.out.println("hhh"); } public static void func2(){
func();//或者Student.func(); System.out.println("www"); }
不能在静态方法中访问非静态成员变量:
例:
public static String getClassRoom(){
System.out.println(this); return classRoom; } //结果:java: 无法从静态上下文中引用非静态 变量 this
例:
public static String getClassRoom(){
age+=1; return classRoom; } //结果:java: 无法从静态上下文中引用非静态 变量 this
静态方法中不可以调用任何非静态方法,因为非静态方法有this参数,在静态方法中调用时无法传递this引用
例:
public static String getClassRoom(){
doClass(); return classRoom; } public void doClass(){
System.out.println("上课"); } //结果:java: 无法从静态上下文中引用非静态 变量 this
1.6.3 static成员变量初始化
静态成员变量初始化分为两种:就地初始化和静态代码块初始化
首先看就地初始化:也就是在定义时直接给出初始值
1.7 代码块
概念:使用{ }定义的一段代码称为代码块,根据代码块定义的位置以及关键字,可分为四种:
1.普通代码块 2.构造快 3.静态块 4.同步代码块(与多线程有关)
1.7.1 普通代码块
也就是定义在方法中的代码块
例:
public class Test {
public static void main(String[] args) {
{
//直接使用{}定义 int x=10; System.out.println("x1="+x); } } }
1.7.2 构造代码块
构造块:定义在类中的代码块(不加修饰符),也叫实例代码块,构造代码块一般用于初始化实例成员变量。
例:
public class Student {
private String name; private int age; private String sex; private double score; private static String classRoom="102"; //实例代码块 {
this.name="zuozuo"; this.age=18; this.sex="nv"; this.score=90; } public String toString() {
return "Student{" + "name='" + name + '\'' + ", age=" + age + ", sex='" + sex + '\'' + ", score=" + score + '}'; } public static void main(String[] args) {
Student student=new Student(); System.out.println(student.toString()); } } //结果:Student{name='zuozuo', age=18, sex='nv', score=90.0}
1.7.3 静态代码块
概念:使用static修饰的代码块,用于初始化静态成员变量
例:
public class Student {
private String name; private int age; private String sex; private double score; private static String classRoom="102"; //实例代码块 {
this.name="zuozuo"; this.age=18; this.sex="nv"; this.score=90; System.out.println("I am instance init()"); } //静态代码块 static{
classRoom="108"; System.out.println("I am static init()"); } public Student(){
System.out.println("I am Student init()"); } public static void main(String[] args) {
Student student1=new Student(); System.out.println("========="); Student student2=new Student(); } } //结果:I am static init() //I am instance init() //I am Student init() //========= //I am instance init() //I am Student init()
我们发现: 1.静态代码块**不管生成多少个对象,其只会执行一次** 2.静态成员变量是类的属性,因此是在JVM加载类时开辟空间并初始化的 3.如果**一个类中包含多个静态代码块**,在编译代码时,编译器会**按照定义的先后次序依次执行(合并)** 4.**实例代码块只有在创建对象时才会执行**
1.8 内部类
当一个事物的内部还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么这个内部的完整结构最好使用内部类。在JAVA中,可以将一个类定义在另一个类或者方法的内部,前者为内部类,后者为外部类。
例:
public class OutClass {
//外部类 class InnerClass{
//内部类 } }
注意:定义在class 类名{
}花括号外部的,即使在一个文件(包)里,都不能称为内部类
例:
public class A {
} class B{
} //A和B是两个独立的类,彼此没有关系
补充:内部类和外部类共用同一个JAVA源文件,但是经过编译之后,内部类会形成单独的字节码文件。
1.8.1 内部类的分类
1.实例内部类:未被static修饰的类
例:
public class OuterClass {
private int a; static int b; int c; public void methodA(){
a=10; System.out.println(a); } public static void methodB(){
System.out.println(b); } //实例内部类 class InnerClass{
int c; public void methodInner(){
//在实例内部类中可以直接访问外部类中:任意访问限定符修饰的成员 a=100; b=200; methodA(); methodB(); //如果外部类和实例内部类中具有相同名称成员时,优先访问内部类自己的 c=300; System.out.println(c); //如果要访问外部类同名成员时,必须:外部类名称.this.同名成员名字 OuterClass.this.c=400; System.out.println(OuterClass.this.c); } } public static void main(String[] args) {
OuterClass outerclass=new OuterClass(); System.out.println(outerclass.a); System.out.println(OuterClass.b); System.out.println(outerclass.c); outerclass.methodA(); outerclass.methodB(); System.out.println("========实例内部类的访问========="); //要访问实例内部类中的成员,必须创建实例内部类的对象 //而普通内部类定义与外部类成员定义位置相同,因此创建实例内部类对象时必须借助外部类 //创建实例内部类对象 OuterClass.InnerClass innerClass1=new OuterClass().new InnerClass(); //也可以先将外部类对象创建出来,然后再创建实例内部类对象 OuterClass.InnerClass innerClass2=outerclass.new InnerClass(); innerClass2.methodInner(); } } 结果: //0 //0 //0 //10 //0 //========实例内部类的访问========= //10 //200 //300 //400
注意:
1.外部类中的任何成员都可以在实例内部类方法中直接访问 2.实例内部类所处的位置与外部类成员位置相同,因此也受访问限定符的约束 3.在实例内部类方法中访问同名成员时,优先访问自己的,如果要访问外部类同名的成员,必须:`外部类名称.this.同名成员`来访问 4.实例内部类对象必须在先有外部类对象前提下才能创建 5.实例内部类的非静态方法中包含了一个指向外部类对象的引用 6.外部类中,不能直接访问实例内部类中的成员,如果要访问必须先创建内部类的对象
2.静态内部类:被static修饰的成员类
public class OuterClass {
private int a; static int b; public void methodA(){
a=10; System.out.println(a); } public static void methodB(){
System.out.println(b); } //静态内部类 static class InnerClass{
public void methodInner(){
//在静态内部类只能访问外部类的静态成员 a=100;//编译失败,因为a不是类成员变量 b=200; methodA();//编译失败,因为methodA()不是类成员方法 methodB(); } } public static void main(String[] args) {
//静态内部类对象创建和成员访问 OuterClass.InnerClass innerClass=new OuterClass.InnerClass(); innerClass.methodInner(); } }
注意: 1.在静态内部类只能访问外部类中的静态成员 2.创建静态内部类对象时,不需要先创建外部类对象
3.局部内部类
定义在外部类的方法体或者{}中,该种内部类只能在其定义的位置使用
public class OuterClass {
int a=10; public void method() {
int b = 10; //局部内部类,不能被public,static等修饰符修饰 class InnerClass {
public void methodInnerClass() {
System.out.println(a); System.out.println(b); } } //只能在该方法体内部使用,其他位置都不能用 InnerClass innerClass = new InnerClass(); innerClass.methodInnerClass(); } public static void main(String[] args) {
OuterClass.InnerClass innerClass=null;//编译失败 } }
注意: 1.局部内部类只能在所定义的方法体内部使用 2.不能被public,static等修饰符修饰
4.匿名内部类
2.继承
对共性的抽取,实现代码复用。是代码可以复用的重要手段,允许在保持原有特性基础上进行拓展,增加新功能,这样产生的类,叫派生类。
如:猫和狗都是动物Animal,都有name,age,weight等属性,狗有brak()方法,猫有miao()方法。我们可以认为Animal这个类有name,age,weight这些属性,有eat(),sleep()这些方法,猫和狗这两个类继承Animal这个类,在这个原有基础上,猫增加了miao()这个方法,狗增加了bark()这个方法。
Animal类称为父类或基类,Dog和Cat称为子类或者派生类。继承后,子类可以复用父类中的成员,也可以增加新的成员。子类继承父类之后,必须要添加自己特有的成员,体现出与基类的不同,否则就没有继承的必要了。
2.1 继承的语法
修饰符 class 子类 extends 父类{
}
2.2 父类成员的访问
2.2.1 子类访问父类的成员变量
子类和父类不存在同名的成员变量
public class Base {
int a; int b; } public class Derived extends Base{
int c; public void method(){
a=10; b=20; c=30; } }
子类和父类成员变量同名
public class Base {
int a; int b; int c; } public class Derived extends Base{
int a; char b; public void method(){
a=10;//访问父类继承的还是自己的? b=20;//访问父类继承的还是自己的? c=101;//子类没有,肯定是父类的 } }
注意: 1.如果访问的成员变量子类有,则优先访问子类自己的 2.如果子类访问的成员变量子类无,则访问父类继承下来的,如果父类也没有定义,则编译报错 3.如果子类访问的成员变量与父类中成员变量同名,则优先访问自己的
也就是说,遵循就近原则
2.3 super关键字
有时会出现子类和父类存在同名的成员,如果要在子类方法中访问父类同名成员时,就要用到super关键字,主要作用是:在子类方法中访问父类成员。
public class Base {
int a; int b; public void methodA(){
System.out.println("Base methodA"); } public void methodB(){
System.out.println("Base methodB"); } } public class Derived extends Base{
int a;//与父类中成员变量同名且同类型 char b;//与父类中成员变量同名但不同类型 public void methodA(int a){
//与父类methodA方法构成重载 System.out.println("Derived methodA"); } public void methodB(){
//与父类methodB方法构成重写 System.out.println("Dreived methodB"); } public void methodC(){
//对于同名的成员,直接访问时,访问的都是子类的 a=100;//等价于:this.a=a; b=10;//this指当前对象的引用 super.a=200;//super获取子类从父类对象中继承下来的 super.b=20; //子类和父类构成重载的方法,直接可以通过参数列表区分访问的是子类还是父类的 methodA();//没有传参,访问的是父类的 methodA(20);//有int参数,访问的是父类的 //在子类中访问重写的父类方法,要借助super methodB();//直接访问,访问的是子类的 super.methodB();//访问的是父类的 } }
注意: 1.super只能在非静态方法中使用 2.super代表父类的引用,这样的理解是不对的,因为并没有实例化父类对象。正确的理解是:它只是一个关键字,最大的作用就是体现出更好的可读性。
2.4 子类构造方法
注意:子类对象构造时,需要先调用父类的构造方法,然后再调用子类的构造方法
例:
public class Base {
public Base(){
System.out.println("Base()"); } } public class Derived extends Base{
public Derived(){
//super(); 子类构造方法中默认会调用父类的无参构造方法,用户没有写时,编译器自动添加,而且必须是子类构造方法的第一句,并且只出现一次 System.out.println("Derived()"); } } public class Test {
public static void main(String[] args) {
Derived d=new Derived(); } } //结果:Base() //Derived()
在子类构造方法中,没有写关于父类构造的代码,但在构造子类对象时,先执行父类的构造方法,然后执行子类的构造方法。因为:子类的成员一部分是父类继承下来的,一部分是自己新增的,先有父再有子,所以构造子类对象时,先调用父类的构造方法,将其从父类继承下来的成员构造完整,然后再调用子类的构造方法,将子类新增的成员初始化完整。
注意: 1.如果父类显式定义无参或默认的构造方法,则子类构造方法的第一行默认有super() 2.如果父类的构造方法有参数,则需要程序员为子类显式定义构造方法,并选择合适的父类构造方法调用,否则编译失败 3.super()必须是子类构造方法中第一行语句 4.super()在子类构造方法中只能出现一次,且不能和this同时出现
第一条补充:
当父类和子类都没有提供任何构造方法时,编译器会默认提供这两个方法:
父类:
public Animal(){
}
子类:
public Dog(){
super(); }
2.5 super和this
相同点:
1.都是Java中的关键字 2.只能在类的非静态方法中使用,访问非静态成员变量和方法 3.在构造方法中调用时,必须时构造方法中第一条语句,并且不能同时存在
不同点:
1.this是当前对象的引用 2.在非静态成员方法中,this访问本类的方法属性,super访问父类继承下来的方法属性 3.在构造方法中,this()用于调用本类构造方法,super()用于调用父类的构造方法,两种调用不能同时在构造方法中出现 4.构造方法中一定会存在super()的调用,用户没有写编译器也会自动添加,但是this()用户不写则没有
2.6 类中实例代码块 静态代码块 构造代码块执行的顺序
例:
public class Person {
public String name; public int age; public Person(String name,int age){
this.name=name; this.age=age; System.out.println("构造方法执行"); } {
System.out.println("实例代码块执行"); } static{
System.out.println("静态代码块执行"); } } public class Test {
public static void main(String[] args) {
Person person1=new Person("zuozuo",18); System.out.println("====================="); Person person2=new Person("huahua",18); } } //结果:静态代码块执行 //实例代码块执行 //构造方法执行 //===================== //实例代码块执行 //构造方法执行
结论:静态最优先,父类优先于子类
1.静态代码块先执行,并且只执行一次 2.当有对象创建时,才会执行实例代码块,实例代码块执行后,构造方法才执行 3.父类优先子类
例:
public class Animal {
public String name; public int age; public Animal(String name, int age){
this.name=name; this.age=age; System.out.println("Animal构造方法执行"); } {
System.out.println("Animal实例代码块执行"); } static{
System.out.println("Animal静态代码块执行"); } } public class Dog extends Animal{
public Dog(String name,int age){
super("zuozuo",18); this.name=name; this.age=age; System.out.println("Dog构造代码块执行"); } {
System.out.println("Dog实例代码块执行"); } static{
System.out.println("Dog的静态代码块执行"); } } public class Test {
public static void main(String[] args) {
Dog dog1=new Dog("huahua",19); System.out.println("==============="); Dog dog2=new Dog("zuozuo",18); } }
结果:
Animal静态代码块执行 Dog的静态代码块执行 Animal实例代码块执行 Animal构造方法执行 Dog实例代码块执行 Dog构造代码块执行 =============== Animal实例代码块执行 Animal构造方法执行 Dog实例代码块执行 Dog构造代码块执行
结论:
1.父类静态代码块优先子类的,并且最早执行 2.父类实例代码块和构造代码块紧接依次执行,然后是子类的实例代码块和构造代码块 3.第二次实例化子类对象时,父类和子类的静态代码块都将不再执行,也就是静态的只执行一次
2.7 继承方式
继承方式:单继承,多层继承,不同类继承同一个类
,注意,不支持多继承
2.8 final关键字
final可以修饰变量,方法 类
2.8.1 final修饰变量
被final修饰后的变量变为常量,不能再次修改
,一般会大写。如果成员变量被final修饰,必须同时给定一个初始值。
例:
public class Test {
public static void main(String[] args) {
final int a=10; a=20; } }
结果:
无法为最终变量a分配值
例:
public class Test {
public static void main(String[] args) {
final int[] array={
1,2,3};//final修饰的是array这个引用所指向的对象 //array=new int [10];//这句会报错, array[0]=10;//这句不会报错 } }
结果就是证明:被final修饰的array这个引用指向的对象不能改变,但是可以改变里面的值
array里面存储的是对象的地址
,而不是对象中的内容
,所以array被final修饰后,array里面的地址就不可以再变
了,但是其中的内容可以变
。
2.8.2 修饰类
被final修饰后的类,表示此类不能被继承
2.8.3 修饰方法
被final修饰的方法,表示该方法不能被重写
2.9 继承与组合
继承表示对象之间的关系是is-a
:如狗是动物
组合表示对象之间的关系是has-a
:如汽车有发动机(发动机有自己的零件和功能),方向盘。汽车是由这些组成的。
例:
public class Student {
} public class Teacher {
} public class School {
public Student[] students=new Student[3]; public Teacher[] teachers=new Teacher[3]; } //涉及到组合
3.多态
去完成某个行为,不同的对象去完成会产生不同的效果。也就是,同一件事情,发生在不同对象身上,会产生不同的结果。
3.1 多态的实现条件
1.必须在继承体系下
2.子类必须对父类方法重写
3.通过父类的引用调用重写的方法
例:
public class Animal {
String name; int age; public Animal(String name, int age){
this.name=name; this.age=age; } public void eat(){
System.out.println("吃饭"); } } public class Cat extends Animal {
public Cat(String name,int age){
super(name,age); } public void eat(){
//重写Animal中的eat方法 System.out.println(name+"吃猫粮"); } } public class Dog extends Animal {
public Dog(String name,int age){
super(name,age); } public void eat(){
//重写Aniaml中的eat方法 System.out.println(name+"吃狗粮"); } } public class Teat {
public static void eat(Animal a){
a.eat(); } public static void main(String[] args) {
Cat cat=new Cat("zuozuo",10); Dog dog=new Dog("huahua",18); eat(cat);//当a引用Cat对象时调用Cat中的eat方法 eat(dog);//当a引用Dog对象时调用Dog中的eat方法 } } //结果: //zuozuo吃猫粮 //huahua吃狗粮
调用eat这个方法时,参数类型为Animal
,此时并不知道a调用的是哪个子类的实例
,此时a这个引用调用eat()方法可能会有多种表现
,这就是多态。
3.2 重写(Override)
重写,也称为覆盖。重写就是子类对父类非静态,非private修饰,非final修饰,非构造方法
的代码块进行重新编写,返回值和参数列表不能改变
。也就是核心重写,外形不变。
3.2.1方法重写的规则
1.子类重写父类
方法时,返回值
和参数列表
还有方法名
要一致
2.子类重写的方法访问权限不能比父类中被重写的方法访问权限更低
(大于等于)。例如:如果父类被public
修饰,则子类重写的方法不能被protected
修饰
3.父类被static,private,final修饰的方法
(密封方法),构造方法
都不能重写
4.重写的方法可以使用@Override
注解来显式指定
,这个注解可以帮我们进行合法性校验
。如把重写的方法的名称写错,编译器就会发现没有这个方法,就会编译报错,提示无法构成重写。
3.2.2 静态和动态绑定
静态绑定(前期绑定或者早绑定):在编译时,根据用户传递的实参类型
就确定了调用的是哪个方法。典型代表就是重载。
动态绑定(动态绑定或者早绑定):在编译时,不能确定方法的行为,要等运行时,才能确定具体调用的是哪个方法。
例:
1.向上转型 2.重写 3.通过父类引用调用父类和子类重写的方法
3.3 向上转型和向下转型
3.3.1 向上转型
理论上,=左右两边的类型要一致,否则赋值会出错,但发生向上转型是可以让 = 左右两边类型不一致就可以进行赋值的。
就是创建一个子类对象,将其当作父类对象使用。
语法格式:父类类型 对象名=new 子类类型()
例:Animal animal=new Dog("zuozuo",18);
animal是父类类型,但可以引用一个子类对象,因为是从Dog这个小范围类型向Animal这个大范围类型转换。
猫和狗都是动物,因此将子类对象转化为父类引用是合理的,大范围可以包括小范围,是安全的。
注意:
当发生向上转型后,通过父类的引用只能访问父类自己的成员,不能访问到子类特有的。
3.3.2 向上转型的方式
例:
public class Teat {
public static void eat(Animal a){
//方法传参:形参为父类引用,接收任意子类的对象 a.eat(); } public static Animal buyAnimal(String var){
//返回值:返回任意子类对象 if("狗".equals(var)){
return new Dog("gougou",12); }else if("猫".equals(var)){
return new Cat("maomao",18); }else{
return null; } } public static void main(String[] args) {
Animal cat=new Cat("zuozuo",19);//直接赋值 Dog dog=new Dog("huahua",10); eat(dog); Animal animal=buyAnimal("狗"); animal.eat(); animal=buyAnimal("猫"); animal.eat(); } }
结果:
huahua吃狗粮 gougou吃狗粮 maomao吃猫粮
优点:代码更简单灵活
缺点:不能调用子类特有的方法
3.3.3 向下转型
向下转型不太安全
例:
public class Teat {
public static void main(String[] args) {
Cat cat=new Cat("huahua",18); Dog dog=new Dog("zuozuo",19); //向上转型,将animal当作Animal对象来处理 Animal animal=cat; animal.eat(); animal=dog; animal.eat(); //animal.bark();//编译失败,因为animal此时为Animal类型,父类中没有bark方法,父类不能访问子类特有的 //向上转型,下面这两行代码可以通过编译,但运行时会抛异常,因为animal此时指向的是狗,无法还原为猫 cat=(Cat)animal; cat.miaomiao(); //animal本来指向的是狗,因此将animal还原为狗是安全的 dog=(Dog)animal; dog.bark(); } }
结果:
huahua吃猫粮 zuozuo吃狗粮 zuozuo汪汪叫
为了提高向下转型的安全性,引入了instanceof
.如果该表达式为true,则可以安全转换。
例:
public class Teat {
public static void main(String[] args) {
Cat cat=new Cat("huahua",18); Dog dog=new Dog("zuozuo",19); //向上转型 Animal animal=cat; animal.eat(); animal=dog; animal.eat(); if(animal instanceof Cat){
cat=(Cat)animal; cat.miaomiao(); } if(animal instanceof Dog){
dog=(Dog)animal; dog.bark(); } } }
3.4 多态的优缺点
例:
public class Shape {
public void draw(){
System.out.println("画图形"); } } public class Rect extends Shape{
public void draw(){
System.out.println("矩形"); } } public class Circle extends Shape{
public void draw(){
System.out.println("⚪"); } } public class Flower extends Shape{
public void draw(){
System.out.println("❀"); } } public class Teat {
public static void drawShape(Shape shape){
shape.draw(); } public static void main(String[] args) {
Rect rect=new Rect(); drawShape(rect); Circle circle=new Circle(); drawShape(circle); Flower flower=new Flower(); drawShape(flower);//或者这样写:drawShape(new Flower()); } }
结果:
矩形 ⚪ ❀
另一种写法:
public class Teat {
public static void drawShape(){
//创建一个Shape对象的数组 Shape[] shapes={
new Flower(),new Rect(),new Circle()}; for(Shape s:shapes){
s.draw(); } } public static void main(String[] args) {
drawShape(); } }
3.4.1 多态的好处
1.降低代码的圈复杂度,避免使用大量if-else
圈复杂度:一段代码中条件语句和循环语句出现的个数
2.可扩展能力强
3.4.2 多态的缺点
3.5 避免在构造方法中调用重写的方法
例:B为父类,D是子类,D中重写func方法,并在B的构造方法中调用func
public class B {
public B(){
func(); } public void func(){
System.out.println("B.func"); } } public class D extends B{
private int num=1; public void func(){
System.out.println("D.func"+" "+num); } } public class Test {
public static void main(String[] args) {
D d=new D(); } }
结果:
D.func 0
分析:
1.构造D对象之前,会先调用B的构造方法去构造B。 2.当在父类的构造方法中调用父类和子类重写的方法的时候,会触发动态绑定,会调用子类的。 3.D类型的对象自身还没有构造,num处于未初始化的状态,为0
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/126449.html