0%

Java的三大特性:封装、继承、多态

一,封装

面向三大特性:封装、继承、多态

1.1 封装的概念

生活中的封装:打包、机箱

代码中的封装:方法、类、包

1.2 封装的好处

  1. 提高了代码的安全性
  2. 提高了代码的复用性
  3. 隐藏了实现的细节

1.3 属性的封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Student {
public String name;
public int age;
public void show(){
System.out.println("我叫"+this.name+",今年"+this.age+"岁");
}
}

public class Test1 {
public static void main(String[] args) {
Student s = new Student();
s.name = "张三";
s.age = 420;
s.show();
}
}

上述代码中对age的赋值出现了不合法的数据,所以调用show()方法出现的结果也是不正常的。

出现上述情况的原因:

  1. 属性可以通过对象任意调用
  2. 对属性值没有进行合理性判断

解决办法:

  1. 通过对属性进行私有化的封装来限制属性的调用
  2. 需要对属性进行合理性的判断
  1. 通过对属性进行私有化的封装来限制属性的调用

    使用关键字private修饰属性,private表示私有的,被private修饰的成员,只能在本类中使用

  2. 需要对属性进行合理性的判断

    set方法

    1
    2
    3
    4
    5
    6
    7
    8
    public void setAge(int age){
    System.out.println("set中的"+this);
    if(age >= 0 && age <= 150){
    this.age = age;
    }else{
    this.age = -1;
    }
    }

    get方法

    1
    2
    3
    4
    public int getAge(){
    System.out.println("get中的"+this);
    return this.age;
    }

注:如果属性是boolean类型的,那么get方法的方法名会变成isXxx()

1
2
3
4
5
6
7
public boolean isSex() {
return sex;
}

public void setSex(boolean sex) {
this.sex = sex;
}

一个标准的实体类(JavaBean)必须包含:

  1. 私有化的属性
  2. 对外提供属性的set/get方法
  3. 空参的构造函数

二,继承

2.1 继承的概念

生活中的继承:子女拥有父母的东西

代码中的继承:指的是类与类之间产生了关系,多个类中的共性内容进行了向上抽取

父类:超类、基类 superclass

​ 父类的范围往往比较大,而属性和方法一般都比较少

子类:派生类、衍生类 subclass

​ 子类的范围往往更小,而属性和方法一般更多

注:

  1. 所有类都直接或者间接的继承了Object类
  2. 一般子类名字的后缀是父类的名字
  3. 子类除了继承了父类的共性内容以外,还可以有自己的特有内容

2.2 继承的好处

  1. 提供了代码的复用性,子类可以直接访问父类中非私有的成员
  2. 为多态提供了前提,一个子类就是一个父类

2.3 子类父类的继承关系

​ 关键字:extends

格式:

1
public class 子类 extends 父类{}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 父类
public class Phone {
public String brand;
public String color;
public double price;

public void sendMsg(){

}
public void call(){

}
}
// 子类
public class SmartPhone extends Phone{
public void play(){

}
}

注:

  1. 一个父类可以有多个子类(多个子类可以继承同一个父类)
  2. 一个类不能同时继承多个父类
  3. 可以有多层继承(继承具有传递性)

2.4 子类父类中同名成员的问题

2.4.1 同名变量

当子父类中出现同名变量时,会根据“就近原则”,优先访问子类中的成员变量

1
2
3
4
5
6
7
8
9
10
11
12
class Fu{
int i = 4;
}
class Zi extends Fu{
int i = 5;
public void f(){
int i = 6;
System.out.println(super.i);//4 访问父类中的同名变量i
System.out.println(this.i);//5 访问本类中的同名变量i
System.out.println(i);//6 访问局部变量i
}
}
2.4.2 同名方法

当子父类中出现同名方法时,会根据“就近原则”,优先访问子类中的成员方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Fu{
public void f(){
System.out.println(1);
}
}
class Zi extends Fu{
public void f(){
System.out.println(2);
}

public void a(){
f();// 调用f()本质上是this.f(),是使用本类中的对象调用本类中的f方法
super.f();// 调用父类中的同名f方法
}
}

重写:

在继承关系中,子类对父类的功能进行扩展就是方法的重写

重写的格式要求:

  1. 方法名相同

  2. 参数列表相同

  3. 子类方法的范围修饰符必须大于等于父类的

    public > protected > [defalut] > private

  4. 子类方法的返回类型必须是父类方法的返回类型,或者是父类方法返回类型的子类

注:@Override 重写的注解,可以用来校验当前是否满足重写规则

1
2
3
4
5
6
7
8
9
10
11
12
class Phone{
public void sendMsg(String msg){
System.out.println("发送了:"+msg);
}
}
class SmartPhone extends Phone{
@Override // 注解
public void sendMsg(String msg) {
super.sendMsg(msg);
System.out.println("发送了:"+"^.-");
}
}

2.5 父类属性私有化的问题

问题:父类中有子类的共性属性,这些在父类中应该使用private进行私有化封装,但是一旦私有化了对子类就是不可见的,子类无法访问父类中的这些私有的共性属性,如何在子类中访问到这些私有属性?

解决办法:

方式一:

父类中的属性私有化了,但是会对外提供访问这些属性的set/get方法,set/get方法是公共的,所以可以通过父类的set/get方法来访问

方式二:

重载父类和子类的构造函数,在子类的构造函数中通过super(参数列表)调用父类的构造函数来对子类对象的属性进行初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Cat extends Pet {
public Cat(String nickname,int age) {
super(nickname,age);
}
}

class Pet{
private String nickname;
private int age;
public Pet() {
}
public Pet(String nickname,int age) {
this.nickname = nickname;
this.age = age;
}
// set/get方法
}

对子类特有属性的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Emp{
private String name;
private double salary;
public Emp() {
}
public Emp(String name, double salary) {
this.name = name;
this.salary = salary;
}
// set/get方法
}

class Programmer extends Emp{
private double jiangJin;
public Programmer() {
}
public Programmer(String name, double salary) {
super(name, salary);
}
public Programmer(String name, double salary,double jiangJin) {
super(name, salary);
this.jiangJin = jiangJin;
}
// set/get方法
}

2.6 继承关系中的构造函数问题

  1. 任何一个类的任何一个构造函数中都调用父类的默认构造函数
  2. 父类构造函数的调用必须在子类构造函数的第一行

2.7 super 和 this

super表示当前类的父类对象

作用:

  1. 调用父类的属性、方法
  2. 调用父类的构造函数
    1. 任何一个类的任何一个构造函数中都调用父类的默认构造函数
    2. 子类的重载构造函数中可以通过父类的重载构造函数对子类属性初始化

this表示当前类的对象,在方法中this表示当前方法的调用者

作用:

  1. 调用本类的属性、方法

  2. 调用本类中的其他构造方法this(参数列表)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Stu{
    String name;
    int age;
    // 健康状态
    String health;
    public Stu(){
    System.out.println("默认的");
    }
    public Stu(String name,int age){
    this.name = name;
    this.age = age;
    }
    public Stu(String name,int age,String health){
    this(name,age);
    this.health = health;
    }
    }

注:

  1. 构造函数的调用只能在构造函数中
  2. 构造函数的调用必须是第一条语句
  3. 如果本类中的构造函数中调用了本类的其他构造函数,那么就不再调用父类的构造函数了

2.8 final

final可以修饰:

  1. 变量、对象:创建后不能再被赋值

    注:成员变量被final修饰后,必须手动赋值初始化

  2. 类:最终类,不能被继承

    1
    final class People{}
  3. 方法:不能被重写

    1
    public final void f(String s){}

三,抽象类

3.1 抽象方法和抽象类的概念

概念:当子类的共性方法抽取到父类中,而父类无法描述每个子类的具体实现时,这种方法就是抽象的。

抽象方法必须在抽象类中

关键字:abstract

抽象方法和抽象类的定义格式:

1
2
3
public abstract class Shape {
public abstract double getS(double r);
}

3.2 抽象类的使用

步骤:

  1. 创建子类继承抽象类

  2. 重写抽象类中的所有抽象方法

    注:如果一个类继承了抽象类,要么重写它的所有抽象方法,要么这个类也是一个抽象类

  3. 创建子类对象调用方法

3.3 注意事项

  1. 抽象类不能被实例化(不能创建抽象类的对象,不能new)
  2. 抽象类一定是一个父类,因为抽象类是子类的共性内容抽取而来的
  3. 抽象方法一定在抽象类中
  4. 抽象类中既可以抽象方法,也可以有非抽象方法,也可以没有抽象方法
  5. 不能与abstract关键字一起使用的关键字:final、static、private
  6. 抽象类中可以有构造方法

3.4 匿名对象

概念:创建对象时不指定对象名(引用变量名)

1
new Random().nextInt(10)+1;

好处:书写简便

弊端:

  1. 只能使用一次
  2. 可读性差

3.5 匿名创建子类对象(匿名内部类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
abstract class Emp{
public abstract void work();
public abstract void sleep();
}

new Emp(){
@Override
public void work(){
System.out.println("work");
}
@Override
public void sleep(){
System.out.println("sleep");
}
};

格式:

1
2
3
new 抽象类{
// 方法重写
};

四,接口

4.1 接口的概念

概念:接口是多个类的公共规则,接口是一种引用数据类型,它的本质是一个类

作用:

  1. 提供了规范、标准
  2. 对功能进行了扩展

4.2 接口的定义

关键字:interface

格式:

1
2
3
public interface 接口名{

}

接口在编译后生成的仍是.class文件

4.3 接口中成员的特点

4.3.1 接口中成员变量的特点

接口中的成员变量默认被public static final修饰

4.3.2 接口中成员方法的特点

接口中的成员方法默认被public abstract修饰

4.4 接口的使用

关键字:implements

步骤:

  1. 创建实现类实现接口
  2. 重写所有抽象方法
  3. 创建实现类对象调用方法

接口也可以使用匿名内部类的方式创建实现类的对象

1
2
3
4
5
6
7
8
9
new 接口(){
// 重写方法
};

new Usb(){
public void connect(){

}
};

4.5 接口的新特性

4.5.1 接口中的普通(默认)方法

从 Java8 开始,允许在接口中定义普通(默认)方法

格式:

1
2
3
4
5
public interface 接口 {
public default 返回类型 方法名(参数列表){
方法体
}
}

意义:为了解决接口中功能升级的问题

注:

  1. 接口中的普通(默认)方法也可以重写,重写后一定不能加上default

  2. 如果实现类实现的多个接口中出现了同名同参的普通方法,那么实现类必须重写它,

    此时,如果在实现类中想要调用某个指定接口的普通方法,必须接口名.super.方法名()进行调用

4.5.2 接口中的静态方法

从 Java8 开始,允许在接口中定义静态方法

格式:

1
2
3
4
5
public interface 接口 {
public static 返回类型 方法名(参数列表){
方法体
}
}

注:

  1. 接口中的静态方法的定义和使用与普通类中静态方法的定义和使用相同
  2. 接口中的静态方法只能通过接口名调用,不能通过实现类名或者实现类的对象调用
4.5.3 接口中的私有方法

从 Java9 开始,允许在接口中定义私有方法

格式:

1
2
3
4
5
public interface 接口 {
private 返回类型 方法名(参数列表){
方法体
}
}

作用:目的是为接口中的普通(默认)方法提供不需要对外暴露的功能支持

4.6 类、接口的关系

4.6.1 类与类的关系

类与类的关系是继承:子类继承父类

1
2
3
public class 子类 extends 父类{

}

注:类与类之间只存在单继承

4.6.2 类与接口的关系

类与接口的关系是实现:实现类实现接口

1
2
3
public class 实现类 implements 接口A,接口B{

}

注:

  1. 一个类可以实现多个接口
  2. 实现类必须实现多个接口中的所有抽象方法,如果多个接口中有同名同参的方法,只要实现一次
  3. 接口中的普通(默认)方法与另一个接口中的普通(默认)或者抽象方法同名同参时,实现类必须重写
4.6.3 接口与接口的关系

接口与接口的关系是继承:子接口继承父接口

1
2
3
public interface 子接口 extends 父接口A,父接口B{

}

注:

  1. 一个接口可以继承多个接口
  2. 父接口中的普通(默认)方法与另一个父接口中的普通(默认)或者抽象方法同名同参时,子接口必须重写
4.6.4 类在继承类的同时实现接口
1
2
public class 实现类/子类 extends 父类 implements 接口A,接口B  {
}

注:

  1. 父类中的方法与接口中的普通(默认)方法同名同参时,优先调用父类的
  2. 父类中的方法与接口中的抽象方法同名同参时,实现类不需要再重写

以上注意点依据“就近原则”

4.7 接口和抽象类的异同

同:

  1. 都不能创建对象
  2. 都可以定义抽象方法
  3. 都需要子类/实现类来继承/实现

异:

  1. 抽象类被子类继承(extends),接口被实现类实现(implements)
  2. 类与类之间只存在单继承,接口与接口之间可以多继承
  3. 接口中的成员变量都是公共的静态常量,接口中的成员方法都是公共的抽象的
  4. 抽象类中有构造函数,接口中没有构造函数
  5. 抽象类封装的是子类的共性内容,接口是对功能的封装,接口是方法的集合

抽象类和接口的选用:建议优先使用接口,接口避免了单继承的局限性

五,多态

5.1 多态的概念

多态的前提是继承或者实现

一个子类就是一个父类

代码中的体现:父类的引用变量指向子类对象(把子类对象赋值给父类的引用变量)

1
2
3
4
5
6
7
8
class Emp{
}

class Programmer extends Emp{
}

// 父类 引用变量 = 子类对象;
Emp e = new Programmer();

5.2 多态下调用成员的特点

5.2.1 多态下调用成员变量的特点

编译时期:父类中没有这个变量,编译失败;父类中这个变量,编译才通过

运行时期:访问的是父类中的变量

简单记忆:编译运行都看左边

小结:多态下,不能访问子类的特有变量

5.2.2 多态下调用成员方法的特点

编译时期:父类中没有这个方法,编译失败;父类中有这个方法,编译才通过

运行时期:访问的是子类中的方法

简单记忆:编译看左边,运行看右边

小结:多态下,不能访问子类的特有方法

5.3 多态的应用

场景1:使用父类作为方法的形参,调用方法时,实参可以是该父类或者它的任何一个子类对象

1
2
3
4
5
6
7
8
LinkedList list = new LinkedList();
System.out.println(list);
list.add("a");
list.add("b");
list.add("c");
list.add("d");
Collections.shuffle(list);// shuffle方法的形参是List,实参可以传递List的任何一个子类/实现类
System.out.println(list);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
abstract class Human{
public abstract void work();
}
class Teacher extends Human{
@Override
public void work(){
System.out.println("讲课");
}
}
class Nurse extends Human{
@Override
public void work(){
System.out.println("护理");
}
}

public class Test {
public static void main(String[] args) {
Nurse n = new Nurse();
f(n);
Teacher t = new Teacher();
f(t);
}
public static void f(Human h){
h.work();
}
}

场景2:使用父类作为方法的返回类型,此时方法的返回值可以是该父类或者它的任何一个子类对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Factory{
public HardWare build(int num){
if(num == 1){
return new Mouse();
}else{
return new KeyBoard();
}
}
}
class Mouse extends HardWare{
}
class KeyBoard extends HardWare{
}
class HardWare{
}

5.4 多态的转型

5.4.1 向上转型

多态的本质就是向上转型,父类的引用变量指向子类对象

注:多态下也就是向上转型后,无法访问子类的特有内容

5.4.2 向下转型

多态下,无法访问子类的特有内容,必须向下转型

格式:

1
2
3
4
子类/实现类 变量名 = (子类/实现类)父类的引用变量;

Human h = new Teacher();
Teacher t = (Teacher)h;

注:在向下转型的过程中,如果强转的目标类型不是多态时指定的类型会发生ClassCastException类型转换异常

解决类型转换异常需要使用关键字instanceof

1
对象 instanceof 类型

作用:判断该对象是否是该类型的,返回布尔值