三大特征
一、概念
面向对象的语言一般应该具备三大特征:封装、继承和多态。
二、封装
下面的代码,年龄设置明显不符合实际,但是由于没有超出int的范围,所以不会报错。
public class Student { String name; int age; String sex; } public class Demo01 { public static void main(String[] args) { Student student = new Student(); student.name = "张三"; student.age = 20000; student.sex = "男"; } }
为了解决上面的问题,可以使用属性的封装。
2.1 属性封装
使用
private
关键字,将属性隐藏在类的内部,让外部不可访问,并添加一些外部可以访问属性的方法,来控制属性的修改和读取。
public class Student { private String name; private int age; private String sex; // 提供取值的方法 public int getAge() { return this.age; } // 提供赋值的方法 public void setAge(int age) { if(age <= 0 || age > 120) { System.out.println("不合法的年龄"); this.age = 18; }else { this.age = age; } } public String getName() { if(this.name == null) { return "暂无"; }else { return name; } } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } }
2.2 方法封装
将不需要暴露的方法隐藏(
private
),将需要外部使用的方法公开。
public class Kongtiao { // 需要外部调用的方法公开 public void 制冷() { 通电(); 压缩(); 送风(); } // 不需要外部调用的方法,进行隐藏 private void 通电() { System.out.println("通电"); } private void 压缩() { System.out.println("压缩机启动"); } private void 送风() { System.out.println("送风"); } }
三、继承
3.1 概念
子类通过继承的方式来继承父类的所有属性和方法。
语法:
class 子类 extends 父类{
}
优点:可以提高代码的重用性,减少代码的冗余。提供可扩展性和可维护性。
/** * 动物 */ public class Animal { public String brand; // 种类 public String sex; // 性别 public int age; // 年龄 public void eat() { System.out.println("动物在吃..."); } public void sleep() { System.out.println("动物在睡..."); } } public class Dog extends Animal{ } public class Cat extends Animal{ }
Java中继承的特点:
单继承(一个类只能继承一个父类),但是可以多级继承,属性和方法逐级叠加。
注意:所有的类自动继承Object类。Object作为所有类的顶层父类。
class A{}
class A extends Object{}
上面两种写法使用上基本没有区别,但是直接继承Object会让该类不能再继承其他类,而间接继承Object还可以继承其他类。
3.2 不可继承
构造方法只能创建本类的对象,视作不可继承。
使用
private
修饰的属性和方法,只能本类可见,在子类中无法直接访问,视作不可继承。当没有访问修饰符修饰的属性和方法时,该访问修饰符为默认,只能在同一个包中访问,如果子类不在同一个包中,无法直接访问,视作不可继承。
3.3 访问修饰符
范围由小到大:private、默认(default)、protected、public
本类 | 同包 | 非同包子类 | 其他 | |
---|---|---|---|---|
private | √ | × | × | × |
默认的 | √ | √ | × | × |
protected | √ | √ | √ | × |
public | √ | √ | √ | √ |
3.4 方法重写
方法重写是指在子类定义一个与父类中完全相同的方法。会覆盖父类的方法。称为方法重写(override)
当父类的方法无法满足子类需求时,可以在子类中重写该方法。
语法要求:
方法名称、参数列表、返回值类型、异常的声明必须与父类相同。
访问修饰可以比父类更广。
public class A { public void m1() { System.out.println("A===m1"); } } public class B extends A{ @Override // 编译检查是否方法重写 public void m1() { System.out.println("B===m1"); } }
可以使用@Override注解来编译检查方法是否构成重写,避免单词写错等误操作。
3.5 super关键字
表示父类对象,调用父类的属性和方法
public class Animal { public String name = "Animal===name"; public void eat() { System.out.println("动物在吃..."); } } public class Dog extends Animal{ public String name = "Dog====name"; @Override public void eat() { // 当父类的方法不满足子类的需求,子类需要增强父类的方法中的内容 // 为了避免代码的冗余,可以直接调用父类的方法 super.eat(); System.out.println("狗在吃..."); } public void desc() { // 调用父类属性 System.out.println(super.name); // 调用父类方法 super.eat(); System.out.println(name); eat(); } }
调用父类的构造方法
public class Animal { private String name; private String sex; private int age; public Animal(String name, String sex, int age) { super();//调用Object类的无参构造方法 this.name = name; this.sex = sex; this.age = age; } } public class Dog extends Animal{ private String furColor; public Dog(String name, String sex, int age, String furColor) { // 调用父类的构造方法 super(name, sex, age); this.furColor = furColor; } }
子类在继承父类后,子类的构造方法中会自动调用父类的无参构造方法。
如果父类没有无参构造方法,那么子类会报错。解决方法:1、在父类中声明无参构造方法。2、在子类的构造方法中手动调用父类的有参构造方法。
在构造方法中,如果调用父类的构造方法,必须放到第一行。
this和super的异同:
相同点:
都可以调用属性和方法
都可以调用构造方法
调用构造方法时需要放在第一行
区别:
this是调用当前类的属性和方法,以及构造方法。
super是调用父类的属性和方法,以及构造方法。
注意:this和super在调用构造方法时,不能同时出现,一般出现了this,就不用super,然后在this调用的构造方法中去写super。
3.6 在继承时对象创建过程
在创建子类对象时会先创建父类对象。
流程:分配父类+子类的空间,先创建父类的对象,并且初始化父类的属性,调用父类的构造方法,再创建子类的对象,初始化子类的属性,调用子类的构造方法。
四、多态
4.1 基本使用
当使用父类的引用去引用子类的对象时,会呈现多种状态,称为多态。
注意:当使用父类的引用去引用子类对象时,只能使用父类的属性和方法。子类独有的属性和方法不能直接调用。
public class Demo1 { public static void main(String[] args) { // 可以使用父类的引用指向子类的对象 Animal animal = new Dog(); Animal animal1 = new Cat(); // 不能使用子类的引用去引用父类的对象 // Cat cat = new Animal(); } }
当在子类中重写了父类的方法,即使是通过父类的引用来调用该方法,也会执行子类重写之后的方法,因为毕竟是子类对象,而该对象中已经覆盖了父类的方法。
使用方法重载来描述主人喂养动物的案例:
public class Bird extends Animal{ @Override public void eat() { System.out.println("鸟在吃..."); } } public class Fish extends Animal{ @Override public void eat() { System.out.println("鱼在吃..."); } } // 主人 public class Master { // 喂养 public void feed(Dog dog) { dog.eat(); } public void feed(Cat cat) { cat.eat(); } public void feed(Fish fish) { fish.eat(); } }
上面的代码每增加一种动物,都需要在主人类中添加一个喂养的方法,比较冗余,可以使用多态的方式。
public class Master { // 喂养 public void feed(Animal animal) { animal.eat(); } // 使用父类的引用,返回子类的对象 public Animal playGame() { return new Dog(); } }
此时,无论传入的是何种动物,都可以适用,而且由于子类重写了eat方法,都会去调用子类重写之后的方法。
public class Demo1 { public static void main(String[] args) { Master master = new Master(); Dog dog = new Dog(); master.feed(dog); Cat cat = new Cat(); master.feed(cat); Fish fish = new Fish(); master.feed(fish); Animal animal = new Bird(); master.feed(animal); master.feed(new Dog()); } }
4.2 装箱和拆箱
装箱:将子类对象使用父类的引用去指向。会自动完成装箱。
Animal a = new Cat();
拆箱:使用父类引用的子类对象,使用子类的引用去指向,称为拆箱,需要强制转换。
Animal a = new Dog(); Dog d = (Dog)a;
4.3 类型转换异常
当拆箱使用类型不当时,会出现类型转换异常。ClassCastException
Animal a = new Cat(); Dog d = (Dog)a; // 会出现ClassCastException
4.4 instanceof关键字
要避免类型转换异常,可以使用instanceof关键字来判断对象的类型。返回boolean值。
// 主人 public class Master { // 喂养 public void feed(Animal animal) { animal.eat(); // 如果是狗类对象 if(animal instanceof Dog) { Dog dog = (Dog)animal; dog.watch(); }else if(animal instanceof Cat) { Cat cat = (Cat)animal; cat.pa(); }else if(animal instanceof Bird) { Bird bird = (Bird)animal; bird.fly(); }else if(animal instanceof Fish) { Fish fish = (Fish)animal; fish.swim(); } } }