面向对象程序的三大特性:封装,继承,多态
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());
}
}
调试发现,s1对象中的内容如图:
s2对象内容如图:
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];
}
//涉及到组合
以上代码所涉及的图:
每当new一个school对象,就会new一个student和teacher对象,它们在内存中是这样指向的。
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 向上转型的方式
1.直接赋值
2.方法传参
3.方法返回
例:
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 多态的缺点
1.属性没有多态性
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