一、概念
面向对象的语言一般应该具备三大特征:封装、继承和多态。
二、封装
下面的代码,年龄设置明显不符合实际,但是由于没有超出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();
}
}
}



