面向对象的三大特征:继承、封装、多态
封装性
封装是面向对象编程的核心思想,将对象的属性和行为封装隐藏起来。而这些封装起来的属性和行为通常对外隐藏细节,只允许外部使用公开的数据,避免外部操作导致内部数据改变,来保证类内部数据和结果的完整性,提高程序的可维护性。
为什么要封装
在Java面向对象的思想中,封装可以被认为是一个保护屏障,防止本类的代码和数据被外部程序随机访问。下面通过一个例子具体讲解什么是封装
class Student {
String name;
int age;
void read() {
System.out.println("我是"+name+",今年"+age);
}
}
public class Test{
public static void main(String[] args) {
Student s=new Student();
s.setName("张三");
s.setAge(-18);
s.read();
}
}
第10行代码将年龄赋值为-18岁,这在程序中是不会有任何问题的,因为int的值可以取负数。但是在现实中,-18明显是一个不合理的年龄值。为了避免这种错误的发生,在设计Student类时,应该对成员变量的访问做出一些限定,不允许外界随意访问,这就需要实现类的封装。
如何实现封装
类的封装是指将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象的内部信息,而是通过该类提供的方法实现对内部信息的操作访问。
在Java开发中,在定义一个类时,将类中的属性私有化,即使用private关键字修饰类的属性,被私有化的属性只能在类中被访问。如果外界想要访问私有属性,则必须通过setter和getter方法设置和获取属性值。
下面修改例子,使用private关键字修饰name属性和age属性,实现类的封装。
class Student {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age <= 0) {
System.out.println("您输入的年龄有误!");
} else {
this.age = age;
}
}
void read() {
System.out.println("我是" + name + ",今年" + age);
}
}
public class Test{
public static void main(String[] args) {
Student s=new Student();
s.setName("张三");
s.setAge(-18);
s.read();
}
}
使用private关键字将属性name 和age声明为私有变量,并对外界提供公有的访问方法,其中,getName()方法和getAge()方法用于获取name属性和age属性的值,setName()方法和setAge()方法用于设置name属性和age属性的值。
运行结果:
您输入的年龄有误!
我是张三,今年0
当调用setAge()方法传入了-18这个负数后, age显示为初始值0。这是因为setAge ()方法对参数age进行了判断,如果age的值小于或等于0,会打印“您输入的年龄有误!”,并将age设置为0。
类的继承
在现实生活中,继承一般是指子女继承父辈的财产。在程序中,继承描述的是事物之间的所属关系,通过继承可以使多种事物之间形成一种关系体系。例如,猫和狗都属于动物,程序中便可以描述为猫和狗继承自动物,同理,波斯猫和巴厘猫继承猫科,而沙皮狗和斑点狗继承自犬科。这些动物之间会形成一个继承体系。
在Java中,类的继承是指在一个现有类的基础上去构建一个新的类,构建出来的新类称为子类,现有类称为父类。子类继承父类的属性和方法,使得子类对象(实例)具有父类的特征和行为。在程序中,如果想声明一个类继承另一个类,需要使用extends关键字,语法格式如下。
class 父类{
......
}
class 子类extenda父类{
......
}
从上述语法格式可以看出,子类需要使用extends关键字实现对父类的继承。下面通过一个案例类是如何继承父类的。
class Animal {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
class Dog extends Animal{
}
public class Test{
public static void main(String[] args) {
Dog dog=new Dog();
dog.setName("旺旺");
dog.setAge(1);
System.out.println("我是"+dog.getName()+",今年"+dog.getAge()+"岁");
}
}
运行结果:
我是旺旺,今年1岁
Dog类中并没有定义任何操作,而是通过extends关键字继承了Animal类,成为Animal类的子类。子类虽然没有定义任何属性和方法,但是能调用父类的方法。这就说明子类在继承父类的时候,会自动继承父类的成员。除了继承父类的属性和方法,子类也可以定义自己的属性和方法.
class Dog extends Animal{
private String color;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
public class Test{
public static void main(String[] args) {
Dog dog=new Dog();
dog.setName("旺旺");
dog.setAge(1);
dog.setColor("黑");
System.out.println("我是"+dog.getName()+",今年"+dog.getAge()+"岁,"+dog.getColor()+"色");
}
}
在类的继承中,需要注意一些问题
1.在Java中,类只支持单继承,不允许多重继承。也就是说,一个类只能有一个直接父类,例如下面这种情况是不合法的。
class A{}
class B{}
class C extends A,B{} //C类不可以同时继承A类和B类
2.多个类可以继承一个父类,例如下面这种情况是允许的。
class A{}
class B extends A{}
class C extends A{} //B类和C类都可以继承A类
3.在Java中,多层继承也是可以的,即一个类的父类可以再继承另外的父类。例如,C类继承自B 类,而B类又可以继承自A类,这时,C类也可称为A类的子类。例如,下面这种情况是允许的。
class B extends A{} //B类继承A类,B类是A类的子类
class C extends B{} //C类继承B类,C类是B类的子类,同时也是A类的子类
在继承中,子类不能直接访问父类中的私有成员,子类可以调用父类的非私有方法,但是不能调用父类的私有成员。
方法的重写
在继承关系中,子类会自动继承父类中定义的方法,但有时在子类中需要对继承的方法进行一些修改,即对父类的方法进行重写。在子类中重写的方法需要和父类被重写的方法具有相同的方法名、参数列表和返回的类型,且在子类重写的方法不能拥有比父类方法更加严格的访问权限。
class Animal {
void shout() {
System.out.println("动物再叫");
}
}
class Dog extends Animal{
void shout() {
System.out.println("狗再叫,汪汪汪");
}
}
public class Test{
public static void main(String[] args) {
Dog dog=new Dog();
dog.shout();
}
}
运行结果:
狗再叫,汪汪汪
dog对象调用的是子类重写的shout()方法,而不是父类的shout()方法。
子类重写父类方法时,不能使用比父类中被重写方法更严格的访问权限。例如,父类中的方法是public权限,子类的方法就不能是private权限。如果子类在重写父类方法时定义的权限缩小,则在编译时将出现错误提示。
super关键字
当子类重写父类的方法后,子类对象将无法访问父类被重写的方法,为了解决这个问题, Java提供了super 关键字, super关键字可以在子类中调用父类的普通属性、方法和构造方法。
(1)使用super关键字访问父类的成员变量和成员方法
super.成员变量
super.成员方法(参数1,参数2…)
class Animal {
String name="中华田园犬";
void shout() {
System.out.println("动物再叫");
}
}
class Dog extends Animal{
void shout() {
super.shout(); //调用父类中的shout()方法
System.out.println("狗再叫,汪汪汪");
}
public void printName() {
System. out. println("名字:"+super.name); //调用父类中的name属性 //调用父类中的name属性
}
}
public class Test{
public static void main(String[] args) {
Dog dog=new Dog();
dog.shout();
dog.printName();
}
}
运行结果:
动物再叫
狗再叫,汪汪汪
名字:中华田园犬
在Dog类的shout()方法中使用“super. shout()”调用了父类被重写的shout()方法。在printName()方法中使用“super. name”访问父类的成员变量name。子类通过super关键字可以成功地访问父类成员变量和成员方法。
(2)使用super关键字访问父类中指定的构造方法,具体格式如下:
super(参数1,参数2…)
class Animal {
private String name;
private int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String info() {
return "名称:" + this.getName() + ",年龄:" + this.getAge();
}
}
// 定义Dog类继承Animal类
class Dog extends Animal {
private String color;
public Dog(String name, int age, String color) {
super(name, age);
this.setColor(color);
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
// 重写父类的info()方法
public String info() {
return super.info() + ",颜色:" + this.getColor();// 扩充父类中的方法
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog("旺旺",3,"黑色");
System. out. println(dog. info());
}
}
运行结果:
名称:旺旺,年龄:3,颜色:黑色
使用super()调用了父类中有两个参数的构造方法,子类Dog中重写了父类Animal中的info()方法。在子类中重写了父类的info()方法,使用子类的实例化对象调用info()方法时,会调用子类中的info()方法。
通过super()调用父类构造方法的代码必须位于子类构造方法的第一行,并且只能出现一次。super与this关键字的作用非常相似,都可以调用构造方法、普通方法和属性,但是两者之间还是有区割的, super与this的区别如表所示。
this和super两者不可以同时出现,因为this和super在调用构造方法时都要求必须放在构造方法的首行。
多态
多态性是面向对象思想中的一个非常重要的概念,在Java中,多态是指不同对象在调用同一个方法时表现出的多种不同行为。例如,要实现一个动物叫的方法,由于每种动物的叫声是不同的,因此可以在方法中接收一个动物类型的参数,当传入猫类对象时就发出猫类的叫声,传人犬类对象时就发出犬类的叫声。在同一个方法中,这种由于参数类型不同而导致执行效果不同的现象就是多态。Java中多态主要有以下两种形式。
(1)方法的重载。
(2)对象的多态性(方法重写)。
abstract class Animal {
abstract void shout(); // 定义抽象shout()方法
}
// 定义Cat类继承Animal抽象类
class Cat extends Animal {
// 实现shout()方法
public void shout() {
System.out.println("喵喵……");
}
}
// 定义Dog类继承Animal抽象类
class Dog extends Animal {
// 实现shout()方法
public void shout() {
System.out.println("汪汪……");
}
}
public class Test {
public static void main(String[] args) {
Animal an1 = new Cat();// 创建Cat对象,使用Animal类型的变量an1引用
Animal an2 = new Dog();// 创建Dog对象,使用Animal类型的变量an2引用
an1.shout();
an2.shout();
}
}
运行结果:
喵喵……
汪汪……
定义了两个继承Animal的类Cat和Dog,并在Cat类和Dog类中重写了Animal类中的shout()方法。创建了Cat类对象和Dog类对象,并将Cat 类对象和Dog类对象向上转型成了Animal类型的对象,然后通过Animal类型的对象anl和an2调用shout()方法,对象an1和an2调用的分别是Cat类和Dog类中的shout()方法。
对象类型的转换
(1)向上转型:子类对象→父类对象。
(2)向下转型:父类对象→子类对象。
对于向上转型,程序会自动完成,而向下转型时,必须指明要转型的子类类型。
对象向上转型:
文类类型 文类对象=子类实例;
对象向下转型:
父类类型 父类对象=子类实例;
了类类型 子类对象= (子类)父类对象;
class Animal {
public void shout() {
System.out.println("喵喵……");
}
}
// Dog类
class Dog extends Animal {
// 重写shout()方法
public void shout() {
System.out.println("汪汪……");
}
public void eat() {
System.out.println("吃骨头……");
}
}
public class Test {
public static void main(String[] args) {
Animal dog = new Dog();// 创建Dog对象
dog.shout();
}
}
运行结果:
汪汪……
对象发生了向上转型关系后,所调用的方法一定是被子类重写过的方法。
父类Animal的对象an是无法调用Dog类中的eat()方法的,因为eat()方法只在子类中定义,没有在父类中定义。
在进行对象的向下转型前,必须发生对象向上转型
public class Test {
public static void main(String[] args) {
Animal an = new Dog();// 创建Dog对象
Dog dog=(Dog)an;
dog.shout();
dog.eat();
}
}
运行结果:
汪汪……
吃骨头……