推荐学习专栏:Java 编程进阶之路【从入门到精通】
Java 是一门面向对象
程序设计语言,其具有封装,继承和多态三大特征。
封装
是指将具有相同属性和行为的事物封装在一个类中,并且私有化成员变量和方法,即使用访问权限修饰符来控制类中属性和方法的可见度,保护成员的安全。
Java 中使用 extends 关键字来实现继承
,子类继承父类后可以拥有父类公开给子类的方法或者属性,用来实现代码的复用。继承是多态实现的前提。
多态
是指一个对象既可以看做子类的类型,也可以看做父类的类型,在继承的基础上,子类重写父类的方法,方便程序的扩展和维护。
封装是把对象的所有组成部分组合在一起,封装使用访问权限控制符将类的数据隐藏起来,控制用户对类的修改和访问数据的程度。这样做有什么好处呢?
当我们使用 private
修饰成员变量或成员方法时,我们无法在类外访问和使用该变量或方法,防止变量在类外被随意修改,大大提高了程序的安全性,并且使代码更加容易维护。
那么是不是在类外就无法使用该私有化成员变量呢?不是的,当我们使用 private 修饰成员变量时,我们要提供对应的 getXxx() / setXxx() 方法来使用该变量。
示例:
/*
学生类
*/
class Student {
//成员变量
String name;
private int age;
//提供get/set方法
public void setAge(int a) {
if(a<0 || a>120) {
System.out.println("你给的年龄有误");
} else {
age = a;
}
}
public int getAge() {
return age;
}
//成员方法
public void show() {
System.out.println(name + "," + age);
}
}
/*
学生测试类
*/
public class StudentDemo {
public static void main(String[] args) {
//创建对象
Student s = new Student();
//给成员变量赋值
s.name = "张三";
s.setAge(20);
//调用show方法
s.show();
}
}
不同的访问权限修饰符控制的范围不同,大致如下:
修饰符 | 同一类 | 同一子类 | 同一包 | 其他 |
---|---|---|---|---|
public | yes | yes | yes | yes |
private | yes | no | no | no |
protected | yes | yes | yes | no |
default | yes | no | yes | no |
其中 public 的范围最大,private 只有在当前类中可以使用。default 在不显示的说明访问权限时的默认值。
继承是从已有的类中派生出新的类,新的类能吸收已有类的属性和行为,并能扩展新的属性和行为。Java 使用 extends 表示继承关系。
Java是单继承的,不支持多继承。这样使得Java的继承关系很简单,一个类只能有一个父类,易于管理程序,解决了像 C++ 这样的面向对象编程语言中多继承的继承向上二义性,同时一个类可以实现多个接口,从而克服单继承的缺点。
private 修饰的成员变量或方法是不能被继承。其语法:
[修饰符] class 子类 extends 父类 {
//类体
}
示例:定义一个学生类和一个继承自学生类的大学生类
//父类
public class Student {
}
//子类
class BStudent extends Student{
}
子类继承了父类中所有的成员方法,但是实际问题中有时子类中的方法与父类中的该方法实现的功能不完全相同,此时我们就要在子类中重写或者隐藏父类中的该方法。
当一个子类中一个实例方法具有与其父类中的一个实例方法相同的签名(指名称,参数个数和类型)和返回值时,称子类中的方法“重写”
了父类的方法。例如:
class Animal{
//...
public void walk(){
System.out.println("动物在走路...");
}
}
class Dog extends Animal{
//...
public void walk(){
super.walk();
System.out.println("小狗在走路...");
}
}
如果一个子类定义了一个静态类方法,而这个类方法与其父类的一个类方法具有相同的签名(指名称、参数格式和类型)和返回值,则称在子类中的这个类方法“隐藏”了父类中的该类方法。例如:
class Animal{
//...
public void sing(){
System.out.println("动物在唱歌...");
}
public static void walk(){
System.out.println("动物在走路...");
}
}
class Dog extends Animal{
//...
public void sing(){
System.out.println("小狗在唱歌...");
}
public static void walk(){
System.out.println("小狗在走路...");
}
}
public class Test {
public static void main(String[] args){
Dog dog=new Dog();
dog.sing();
Animal dog2=new Dog();
dog2.sing();
Dog.walk();
Animal.walk();
Animal animal=new Animal();
animal.sing();//动物在唱歌...
Animal.walk();//动物在走路...
}
}
当调用的是被子类重写的方法时,调用的其实是子类的方法,当调用的是被子类隐藏的方法时,调用的方法取决于是从父类中调用还是从子类中调用。
例如上述代码中,无论从父类还是从子类中调用子类中重写的方法,结果都是调用的子类的方法:
Dog dog=new Dog();
dog.sing();
Animal dog2=new Dog();
dog2.sing();
/*
小狗在唱歌...
小狗在唱歌...
*/
当调用的是被子类隐藏的方法时:
Dog.walk();
Animal.walk();
/*
小狗在走路...
动物在走路...
*/
问题:在继承过程中父类的构造方法有没有被子类继承呢?
没有,但是子类可以调用父类的构造方法,我们使用 super 关键字来实现。前面说到 this 返回当前类的引用,其实 super 指向了实现当前对象的类的父类的引用。
调用父类构造方法:
class superClass{
private int n;
public superClass(){
System.out.println("这是父类的无参构造方法");
}
public superClass(int n){
System.out.println("这是父类的有参构造方法");
this.n=n;
}
}
class subClass extends superClass{
public subClass(){
System.out.println("这是子类的无参构造方法");
}
public subClass(int n){
super(n);
System.out.println("这是子类的有参构造方法");
}
}
public class Test {
public static void main(String[] args){
superClass superClass=new subClass();
subClass subClass2=new subClass(10);
}
}
/*
这是父类的无参构造方法
这是子类的无参构造方法
这是父类的有参构造方法
这是子类的有参构造方法
*/
由于继承中子类可以访问父类的成员,所以 this 除了可以访问自己的成员还可以访问父类的成员,但是为了明确的指明访问的是父类的成员,则要用关键字 super 来指定。
调用父类中重写的方法和隐藏的成员变量:
在子类中实现了一个和父类中签名和返回值相同的方法,叫做方法的重写。而在子类中重新定义了一个和父类完全相同的成员变量叫做隐藏的成员变量,使用 super 关键字可以调用父类中重写的方法和隐藏的成员变量。
示例:
class A{
//...
public String name="张三";
public void Show(){
System.out.println("这里是A类");
}
}
class B extends A{
//...
public String name="李四";
public void Show(){
super.Show();
System.out.println("这里是B类");
System.out.println(super.name);
}
}
public class Test {
public static void main(String[] args){
B b=new B();
b.Show();
}
}
/*
这里是A类
这里是B类
张三
*/
final 表示不可改变的含义,使用 final 修饰的类不能被继承,使用 final 修饰的方法不能被重写,使用 final 修饰的变量不能被修改,且必须在定义时初始化。如果修饰的是一个引用,则这能指向一个对象不能修改。
注:final 关键字可以用来修饰类,方法,成员变量和局部变量,构造方法不能被 final 修饰。
fianl 关键字修饰方法语法:
修饰符 final 返回值类型 方法名称(参数列表) {
// 方法体
}
对于类、方法来说,abstract 关键字和 final 关键字不能同时使用,因为矛盾。abstract 表示抽象类或者方法,有抽象方法的 abstract 类被继承时,其中的方法必须被子类 Override,而 final 不能被Override。
final 修饰一个变量时,其值不可改变,在定义时必须初始化,也就是说可以当做常量使用,而修饰一个引用变量时,表示其指向的地址不可改变,其指向的对象的内容可以改变。
前面说到,子类继承了父类中所有的非 private 修饰的内容,那么是否可以使用父类来访问子类中继承的属性和方法呢?答案是肯定的。
前面我们通过关键字 this 和 super 在类里面来使用当前类或者父类,那么在类外如何实现呢?Java提供了类对象间的转换来满足这个需求。其中类型转换分为以下两种,父类和子类之间的转换和基本数据类型的强制转换类似。
示例:
Student student = new DaXueSheng();//隐式转换
DaXueShen daXueShen= (DaXueSheng)student;//强制转换
使用隐式类型转换,父类的引用指向了子类的对象,此时就可以使用父类来访问子类中继承的属性和方法。
总结:一个子类类型的对象可以看做一个父类类型的对象,例如,一个大学生也是一个学生。但是,一个父类的对象不能看做子类的对象,例如学生不一定是大学生,此时可以根据需要进行强制类型转换。父类引用可以指向子类的对象,但是不可以访问子类中新增的属性和方法。
instanceof关键字:Java 提供了 instanceof 关键字,使我们在强制转换之前,判断前一个对象是否后一个类的实例,是否可以成功的转换,以增强代码的健壮性。
示例:
class A {
//...
}
class B extends A {
//...
}
public class Test {
public static void main(String[] args) {
A a = new B();//隐式转换
B b = (B) a;//强制转换
boolean boo = a instanceof B;
System.out.println(boo);
System.out.println();
}
}
运行结果:true
,表示 A 类型对象 a 是类B的实例化,所以可以把对象 a 转化为 B 类型。此时是向下转型,属于强制类型转换。注意下面的程序是错误的:
A a = new A();//隐式转换
B b = (B) a;
此时 A 类型对象 a 不是B类的示例,所以不能进行强制类型转换。
多态指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式,这里的发送消息就是函数调用。
例如有一个父类动物类,子类有猫类,狗类等,当我们实例化了不同类的对象,小狗1和小猫1,我们想要小猫1和小狗1调用同一个方法,但是产生的效果不同,这是就必须使用多态技术实现。
多态发生的三个条件:
示例:
class Animal {
public void walk(){
//...
};
}
class Cat extends Animal {
//...
public void walk(){
System.out.println("小猫在走路...");
}
}
class Dog extends Animal{
//...
public void walk(){
System.out.println("小狗在走路...");
}
}
public class Test {
public static void main(String[] args) {
Animal cat=new Cat();
Animal dog=new Dog();
cat.walk();
dog.walk();
}
}
运行结果:
小猫在走路...,
小狗在走路...
在上面的例子中,使用不同的对象调用了同一个方法,但是运行结果不同,这样就达到了多态的效果,同一消息根据发送对象的不同而采用多种不同的行为方式。与方法重载不同的是,这里的方法重写
是指子类继承父类的方法,并且方法名,参数列表,返回类型都相同,这时候可以发生方法重写。其实就是子类不想原封不动的继承父类的方法,把父类的方法重写一遍。方法重写是多态发生的必要条件。
这里只是对 Java 多态的思想做了初步的认识 ,后面学习抽象类和接口的时候会更加深入的学习,这里你要理解的是 Java 允许不同类的对象对同一消息做出不同的响应,这样不仅提高了代码的复用性,而且大大提高了代码的扩展性和可维护性。
当我们添加一个新的子类并且实例化对象时,我们只需要重写父类中的方法便可以实现和其他同类对象相似的功能,不需要重新添加一个新的方法,大大减少了维护的成本,提高代码扩展性。
Java 作为一门面向对象编程的程序设计语言,具有封装,继承,多态的特点,本文谈论了其特点和使用方法,是面向对象思想学习很重要的一部分。
欢迎订阅学习Java编程进阶之路专栏,专栏文章持续更新!