封装、继承、多态是面向对象的语言的共同特性,不止JAVA。
目录
封装
封装顾名思义就是封藏一个事物的内在属性、具体细节等,而JAVA里的封装是选择性的将一个类的一些属性、方法用private修饰符修饰达到封装目的,被private修饰后,被修饰的属性/方法仅能在本类中使用。
public class Test {
private int age;
private public void func(){
System.out.println("这是父类封装的方法");
}
}
class Test1 extends Test{
public static void main(String[] args) {
Test1 te=new Test1();
System.out.println(te.age);//1
te.func();//2
}
}
//Test1继承自Test,也继承了age和func(),却不能被子类来使用,1、2都会编译错误
若想不通过引用Test类的对象来访问被封装的内容,可以通过设置get和set类方法来访问和修改成员变量,也可以通过其他类方法来间接访问private权限的方法。例如:
public class Test {
private int age;
private void func(){
System.out.println("这是父类封装的方法");
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void control(){
func();
}
}
class Test1 extends Test{
public static void main(String[] args) {
Test1 te=new Test1();
te.setAge(99);//修改父类private权限的age的值
System.out.println(te.getAge());//得到age的值
te.control();//间接调用了func()
}
}
继承
继承正文
当我们需要创建一些具有相同属性和特征的类时,如抽象建立红毛猩猩类、狒狒类、金丝猴类。它们都有毛发、吃饭等共同特征,而一个一个重复创建太浪费代码空间,这时候我们就需要使用到继承。继承关系中分父类(也称基类、超类)和子类(派生类),关键字:extends。子类将继承父类定义的所有字段、类方法,只要不是private权限的字段、类方法,子类创建的对象都可以使用,子类还可以定义自己的字段、方法,可以说子类是父类的扩展。
public class Monkey {
private int age;
String color;
public void eat(){
System.out.println("灵长类都会吃饭。。。");
}
}
class Monkey1 extends Monkey{
public static void main(String[] args) {
Monkey1 monkey=new Monkey1();
// System.out.println(monkey.age);这里将会编译错误,
// 子类引用的对象不可访问父类private的age,即便子类继承了这个private的age
monkey.color="red";
monkey.eat();
}
}
注意:
- 当子类引用的对象使用成员变量、调用方法时,先在子类中找,再去父类找。
- 若想在子类定义与父类重名的成员变量/类方法,如果不用super来显式调用,子类引用的对象访问它们时,优先去子类寻找。子类定义与父类同名的类方法时,不可将返回类型改变而不改参数列表,因为这样定义重名方法的操作是遵从方法重载的规则的。
- 一个父类可以被多个子类继承,但一个子类不可继承多个父类,理论上父类可被子类继承、子类又被继承、子类的子类被继承........,但这样在十几种并不合理,如果一个类不想被继承,可使用final关键词修饰。
class A{
}
class B{
}
class C extends A,B{
}//非法的继承操作
——————————————————————
public final class Monkey {
/*代码.......*/
}
class Monkey1 extends Monkey{//Monkey已被final修饰,再继承将会编译错误
}
子类的构造方法
super
super是子类显式访问父类成员变量、类方法的关键字,主要用于在子类里对父类成员变量的初始化,当子类有与父类同名的成员变量、方法时,可以使用super来直接访问父类的变量、方法。
通常有以下几种构造情况:(如果父类自己定义了无参构造方法,编译器不会自动提供无参构造方法,super()调用的也将是自己定义的无参构造,需要注意:super构造必须是子类构造方法的第一句)
- 当父类和子类都未定义构造方法的情况,编译器自动生成这两种构造方法:
public class Monkey { /*如果定义有父类成员变量*/ public Monkey(){ /*如果有成员变量,将会赋默认值*/ } } class Monkey1 extends Monkey{ /*如果定义有子类成员变量*/ public Monkey1(){ super(); /*子类成员变量的赋值操作,同样赋默认值*/
a.父类未定义构造方法,而子类定义了有/无参构造,编译器自动提供父类的无参构造进行初始化。b.父类定义了无参构造,子类未定义构造方法,编译器提供子类的构造方法,并且第一句为super();。
- 父类未定义有参构造,子类定义了构造方法(无论是否有参),子类的构造方法的第一句编译器将会自动加上super();这个情况前提是父类无有参构造方法,否则编译器不会自动添上super()语句。
public class Monkey { /*如果定义有父类成员变量*/ public Monkey(){ /*如果有成员变量,将会赋默认值*/ } } class Monkey1 extends Monkey { String name; public Monkey1(String name) { super(); // 这段代码如果不人为添上,编译器也会自动添在第一行。 this.name = name; /*子类成员变量的赋值操作,同样赋默认值*/ } }
- 当父类定义了有参构造,子类的构造方法需要人为在第一行加上super(参数1,参数2......);当然,在这种情况下,子类也理所应当的为有参构造,否则无法为父类的成员变量初始化
public class Monkey { public int age; public Monkey(int age){ this.age=age; } } class Monkey1 extends Monkey { public String name; public Monkey1(int age,String name) { super(age); // 这段代码如果不人为添上,编译器将会报错 this.name = name; } }
方法重写
重写需要满足:(重写是后面实现多态的基础)
- 子类定义的方法同父类的重名,且参数列表、返回类型也一样(除非二者的返回类型构成父子类关系),仅仅方法体内容不同。
public class A{ //public class A{ public A func(){} // public Object func() {} } //} class B extends A{ //class B extends A{ public B func(){} // public String func() {} } //} //B是A的子类,String类是Object的子类,上面都是合法重写
- 如果父类的方法需要重写,父类方法不可为private权限,且子类重写的方法权限必须大于等与被重写方法的权限,public>protected>default(默认访问权限)>private。
- 重写方法不可用static、final修饰。
一般我们在重写的方法上面加上@Override;来标识这个重写方法,能够标识它是否重写,判断是否符合重写规范。
public class Flower { void func(){/*代码块...*/} } class Rose extends Flower{ @Override//如果重写不规范将会编译错误。 void func(){/*代码块...*/} }
多态
向上转型和向下转型
向上转型:父类引用子类的实例对象。但是注意下面的flower可以调用func1(),但不可调用func2()(除非func2()是重写方法,这种情况就涉及到多态了),因为flower是父类的引用,不可引用子类的属性和方法(除非func2()是重写的)。
- 直接赋值,父类new一个子类对象。
public class Flower{ void func1(){} void func3(){ System.out.println("这是父类的..."); } } class Rose extends Flower{ void func2(){} void func3(){System.out.println("这是子类的...");} public static void main(String[] args){ Flower flower=new Rose(); flower.func1(); //flower.func2();这句将会报错 flower.func3();//体现多态,打印的将是:"这是子类的..." } }
- 方法传参。
public class Flower { String color; void func(Flower flower){ System.out.println(flower.color); // System.out.println(flower.age);虽然传过来的是子类引用的 //子类实例对象,但是在方法传参时发生了向上转型,所以仍然不可以访问子类的age属性。 } } class Rose extends Flower{ int age; public static void main(String[] args) { Rose flower=new Rose(); flower.func(flower); } }
- 方法返回值。
public class Flower{ Flower func(Rose flower){//返回类型为Flower return flower; } } class Rose extends Flower{ public static void main(String[] args){ Rose flower=new Rose(); Flower flower1=flower.func(flower);//flower1将会引用被向上转型后的flower } }
向下转型:经过上面的向上转型,我们发现转型后的无法再调用子类特有的方法,那么就可以将向上转型后的再向下转型回来。
public class Flower{
}
class Rose extends Flower{
void print(){System.out.println("我被向下转型了," +
"我是Rose的但被赋值给Flower引用了,现在又是Rose了");}
public static void main(String[] args) {
Flower rose=new Rose();
// rose.print();编译错误
((Rose) rose).print();
}
}
转来转去,难免绕晕,因此我们引入instanceof来做向下转型前的检查:防止父类类型的对象不是子类对象的实例而出错。
public class Flower{
}
class Rose extends Flower{
void print(){System.out.println("我是Rose....");}
}
class SunFlower extends Flower{
void print(){System.out.println("我是SunFlower...");}
public static void main(String[] args) {
Rose flower=new Rose();
Flower flower1=new SunFlower();
if(flower1 instanceof Rose){//flower1是父类Flower的引用,
// instanceof判断flower1的对象是不是Rose类
flower=(Rose)flower1;//强制类型转换
flower.print();//成功将打印:"我是SunFlower..."
}else{
flower.print();//否则打印:"我是Rose...."
}
}
}
//最后的结果是打印:"我是Rose....",因为flower1本质是SunFlower而不是Rose
静态绑定、动态绑定
两种绑定在方法调用里的使用效果:
静态绑定:在编译阶段,编译器只知道对象的类型而不知道对象的引用类型,编译器将根据方法传参类型确定调用哪个方法,典型场景:方法重载。
动态绑定:当父类的引用变量指向一个子类对象,这个引用调用重写了的方法时,在编译阶段,由于重写,编译器无法判断调用父类的还是子类的方法,到了Java虚拟机(JVM),JVM将选择调用子类对象里的对象。
public class Flower{
void print(){System.out.println("我是Flower...");}
}
class Rose extends Flower{
void print(){System.out.println("我是Rose...");}
}
class SunFlower extends Rose{
void print(){System.out.println("我是SunFlower...");}
}
class Test{
public static void main(String[] args) {
Flower flower1=new Rose();
Flower flower2=new SunFlower();
Rose flower3=new SunFlower();
Flower flower4=new SunFlower();
flower1.print();//打印:我是Rose...
flower2.print();//打印:我是SunFlower...
flower3.print();//打印:我是SunFlower...
flower4.print();//打印:我是SunFlower...
}
}
多态正文
多态:不同的对象去完成同一件事会产生不同的结果。多态可以减少代码的“圈复杂度”,避免大量的if-else语句,更直观、好理解,也提高了代码的可扩展度。
注意:
- 类中的属性没有多态性,构造方法没有多态性。
- 多态性通过继承和方法重写实现。
- 必须由父类引用子类对象才可体现多态。
flower1、2、3三种不同对象,但做func()这同一件事情时,却产生不同效果,这便是多态。
public class Flower{
void print(){System.out.println("我是Flower...");}
}
class Rose extends Flower{
void print(){System.out.println("我是Rose...");}
}
class SunFlower extends Flower{
void print(){System.out.println("我是SunFlower...");}
}
class Test{
public void func(Flower flower){//传过来的引用都将被向上转型,父类
// 引用子类对象又调用重写方法满足动态绑定的条件
flower.print();
}
public static void main(String[] args) {
Test test=new Test();
Flower flower1=new Flower();
Rose flower2=new Rose();
SunFlower flower3=new SunFlower();
test.func(flower1);//打印:我是Flower...
test.func(flower2);//打印:我是Rose...
test.func(flower3);//打印:我是SunFlower...
}
}
完。