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

一、对Java“包”的理解

1. Java中的包就是win操作系统的文件夹,一个包一个文件夹
2. 当创建多个包的时候,用“.”分割
3. 声明一个包用package关键字
4. 包主要是为了解决同名类的问题
5. 类的全名称:包名.类名
6. 举例:package www.baidu.com

图示:在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

导入包中的某个类

在目前的类下面需要使用其它类,就用import关键字导入类

语法:import 包名.类名;

import static–可以导入包中的静态方法和静态属性(不常用,了解即可)

常用的系统包

  1. java.lang: JDK的基础类——System,String,Object等
  2. java.util: 工具包(集合类)
  3. java.io: I/O开发包,文件读取和写入
  4. java.net: 网络编程开发包——Socket
  5. java.sql: 数据库开发包

二、封装(类与对象篇有讲,这里简单介绍)

定义:使用private将属性进行封装(这个属性当前类的内部可见,对外部隐藏)
作用:保护性和易用性
举例:手机
用户拿到手机就可以用,手机功能如何实现的用户不需要知道,这个功能的实现就对用户隐藏了

三、继承

前情提要

方法的重载
定义:在同一个类中,定义若干个方法名相同,参数列表(参数个数、参数类型)不同,与返回值无关的方法叫方法重载。

权限修饰符(范围从小到大)
private:私有权限,只在当前类的内部可见,除了这个类就不可见了
default:包访问权限,在当前包,同级目录下内可见,不包含子包
protectd:继承权限,不同包有继承关系的类之间可见
public:公开权限,当前项目可见

代码展示

public class Animal {
    public String name;
    public Animal(String name){
        this.name = name;
    }
    public void eat(String food){
        System.out.println(this.name + "会吃" + food);
    }
}
class Dog{
    public String name;
    public Dog(String name){
        this.name = name;
    }
    public void eat(String food){
        System.out.println(this.name + "会吃" + food);
    }
}
class Test{
    public static void main(String[] args) {
        Animal animal = new Animal("动物");
        animal.eat("食物");
        Dog dog = new Dog("狗狗");
        dog.eat("冻干");
    }
}
输出:
动物会吃食物
狗狗会吃冻干

从上面代码我们可以看到Animal和Dog类定义了重复的属性和方法,造成代码冗余,为了解决这个问题,引出了继承这个概念
当两个类之间满足**is a关系,一定存在继承关系,这是天然发生的。
举例:Cat is a Animal Dog is a Animal
当一个类继承了另一个类,另一类中的所有属性和方法子类就拥有了,等于”子承父业“
在java中,使用
extends**关键字来表示继承
语法:子类 extends 父类
举例:Dog(子类) extends Animal(父类)
于是,上述代码可以优化一下

public class Animal {
    public String name;
    public void eat(String food){
        System.out.println(this.name + "会吃" + food);
    }
}
class Dog extends Animal{}
class Test{
    public static void main(String[] args) {
        Animal animal = new Animal();
        animal.name = "动物";
        animal.eat("食物");
        Dog dog = new Dog();
        animal1.name = "小狗";
        animal1.eat("冻干");
    }
}
输出:
动物会吃食物
狗狗会吃冻干

继承的规则

  1. 要使用继承,一定要满足类之间is a关系
  2. 一个子类只能使用extends继承一个父类(单继承)
    class Keji extends Dog,Animal //错误,不允许多重继承
  3. 子类会继承父类的所有属性和方法(静态和普通都会继承),只要还是看权限修饰符
  4. 显示继承:public修饰的属性和方法可以直接使用
  5. 隐式继承:private修饰的属性和方法子类也继承了父类的属性和方法,但不能直接使用

java虽然不允许多重继承,但可以多层继承,子子孙孙无穷无尽

public class Animal {
    public String name;
    public void eat(String food){
        System.out.println(this.name + "会吃" + food);
    }
}
class Dog extends Animal{}
class Keji extends Dog{}
class Test{
    public static void main(String[] args) {
        Animal animal = new Animal();
        animal.name = "动物";
        animal.eat("食物");
        Dog dog = new Dog();
        animal1.name = "小狗";
        animal1.eat("冻干");
        Keji keji = new Keji();
        animal2.name = "柯基";
        animal2.eat("狗粮");
    }
}
输出:
动物会吃食物
小狗会吃冻干
柯基会吃狗粮

图示:在这里插入图片描述

隐式继承

子类其实已经继承了父类的属性和方法,但pivate修饰的是只能在当前类看到,所以子类不能直接用
话不多说,上代码

public class Animal {
    public String name;
    private int age;
    public void eat(String food){
        System.out.println(this.name + "会吃" + food);
    }
}
class Dog extends Animal{}
class Keji extends Dog{}
class Test{
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.name = "狗狗";
        dog.age = 12;//error
        dog.eat("冻干");
    }
}
报错:
java: age 在 animal.Animal 中是 private 访问控制

可以用父类提供的get和set方法去调用这些属性,这体现了java的保护性

public class Animal {
    public String name;
    private int age;
    public int getAge(){
        return age;
    }
    public void setAge(int age){
        this.age = age;
    }
    public void eat(String food){
        System.out.println(this.name + "会吃" + food);
    }
}
class Dog extends Animal{}
class Test{
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.name = "狗狗";
        dog.setAge(10);
        dog.getAge();
        dog.eat("冻干");
    }
}
输出:
10
狗狗会吃冻干

继承访问权限:protected

  1. 在不同包下的具有继承关系的类的内部可见,出了子类的内部就不可见了
  2. 在同一个包中没有继承关系的类之间也是可见的,因为protect权限>default权限,所以default有的protected都有

代码一

  1. 在animal包下面创建Animal类,test类
package animal;
import person.Person;
public class Animal {
    protected String name = "测试人";
}
class Test{
    public static void main(String[] args) {
        Person person = new Person();
        person.fun();
    }
}
  1. 在person包下面创建Person类,fun()方法
package person;
import animal.Animal;
public class Person extends Animal {
    public void fun(){
        System.out.println("人的名字:" + name);
    }
}
  1. 输出
    人的名字:测试人

代码二
1.在person包中创建test类

class Test{
    public static void main(String[] args) {
        Animal animal = new Animal();
        System.out.println(animal.name);//error
    }
}

3.报错
java: name 在 animal.Animal 中是 protected 访问控制

解析:代码一:Person和Animal是有继承关系的,所以可以使用protected修饰的name属性,不同包下有继承关系的子类内部是可以用的
代码二:Test类和Animal类是没有继承关系的,且不在同一个包下,所以Aniaml的name属性是不可用的

super关键字

前情提要

this关键字:表示当前对象的引用

  1. 修饰属性:表示当前对象的同名属性
  2. 修饰方法:表示当前对象的同名方法
  3. 表示当前对象的引用

super的用法

  1. 修饰属性:表示直接从父类找同名属性
  2. 修饰方法:表示直接从父类找同名方法
  3. 要产生一个子类对象,默认首先产生父类对象
  4. 一个类只要有父类,那么在它实例化的时候,一定是从顶级的父类开始创建
  5. 当你用子类的无参构造函数创建子类对象时,会去先递归调用父类的无参构造方法

代码

public class Animal {
     public Animal(){
         System.out.println("1.父类的无参构造");
     }

}
class Person extends Animal{
    public Person(){
        System.out.println("2.子类的无参构造");
    }
}
class Test{
    public static void main(String[] args) {
        //创建一个子类对象
        Person person = new Person();
    }
}
输出:
1.父类的无参构造
2.子类的无参构造

简简单单一道笔试题

public class B {
    public B(){
        System.out.println("1.B的构造方法-----------");
    }
    {
        System.out.println("2.B的构造块-------------");
    }
    static{
        System.out.println("3.B的静态方法块----------");
    }
}
public class D extends B{
    public D(){
        System.out.println("4.D的构造方法-----------");
    }
    {
        System.out.println("5.D的构造块-------------");
    }
    static{
        System.out.println("6.D的静态代码块----------");
    }

    public static void main(String[] args) {
        System.out.println("7.main开始-------------");
        new D();
        new D();
        System.out.println("8.main结束-------------");
    }
}

输出结果
3.B的静态方法块----------
6.D的静态代码块----------
7.main开始-------------
2.B的构造块-------------
1.B的构造方法-----------
5.D的构造块-------------
4.D的构造方法-----------
2.B的构造块-------------
1.B的构造方法-----------
5.D的构造块-------------
4.D的构造方法-----------
8.main结束-------------

解析

  1. 由于main存在在D子类中,若JVM要调用main,首要要加载主类。 一加载主类,先加载主类的静态方法,D继承了B,先加载福利日的静态块才加载子类的静态块。3——6
  2. 静态方法加载完成,执行main方法——7
  3. 创建一个子类对象,首先先要创建一个顶级父类对象,调用父类对象的构造方法,这里是B类。构造块优先于构造方法执行,有几个对象调用几次构造块。——2——1
  4. 父类对象构造方法调用完成,开始调用子类对象的构造方法。5——4
    5.又创建了一个D类对象,重复上述步骤。2——1——5——4
    6.最后执行8,程序结束
    3——6 7 2——1——5——4 2——1——5——4 8

super修饰属性

先看一个代码

public class Person {
    public String name = "person";
    public void fun(){
        System.out.println(this.name);
    }
}
class China extends Person{
    public String name = "china";
    public void fun(){
        System.out.println(this.name);
    }
    public static void main(String[] args) {
        China china = new China();
        china.fun();
    }
}
输出:china

当我们把China类里的name注释掉后,输出结果是person

1.当有继承关系时,this关键字默认在当前类中寻找同名属性,若没有,再从父类中寻找是否有同名属性。若不写this关键字,系统会默认加上这个关键字。

在运行上述代码时,需要访问父类的属性,我们可以使用super关键字

System.out.println(super.name);

super先从直接父类寻找同名属性,若不存在,在向上寻找

若Person extends Animal,而Person类的name属性是private修饰的,这时候china无权访问Animal的属性。也就是,直接父类找到了,不在向上搜寻

不能使用super直接调用private修饰的属性

super修饰方法

看一段代码

public class Animal {
    public Animal(){
        System.out.println("顶级父类的无参构造");
    }
}
class Person extends Animal{
    public Person(){
        System.out.println("直接父类的无参构造");
    }
}
class China extends Person{
    public China(){
        System.out.println("子类的无参构造");
    }
    public static void main(String[] args) {
        China china = new China();
    }
}
输出
顶级父类的无参构造
直接父类的无参构造
子类的无参构造
  1. 当产生子类对象时,默认先产生父类对象;若父类对象还有继承,继续向上产生顶级父类对象。
    2.这个思维很自然,没有祖宗,何来后代?祖宗的一些东西都没准备好,后代怎么继承去用?
  2. 要产生父类对象,就要先调用父类的构造方法。
  3. 当创建子类对象,系统会默认调用父类的无参构造super(),若父类中没有无参构造,子类构造方法的首行需要显示的调用父类的有参构造super(参数)
  4. 子类构造方法中,不能显示的调用this(),super()
public class Person{
    private String name;
    public Person(String name){
        this.name = name;
        System.out.println("直接父类的有参构造");
    }
}
class China extends Person{
    public China(){
    	//要产生父类对象,就要调用父类的构造方法
        super("爸爸");
        System.out.println("子类的无参构造");
    }
    public static void main(String[] args) {
        China china = new China();
    }
}

super修饰普通方法

和修饰属性一样,直接从父类中寻找同名方法,就近匹配原则

  1. super和this的最大区别,不能直接引用父类对象
    2.想要访问父类的属性和方法,需要super.方法()

final关键字

final就是个终结器,修饰属性属性值不能变;修饰类,这个类无法被继承;修饰方法,表示这个方法不能被复写

四、多态

定义:一个引用可以表现出多种行为/特性,就是多态性

向上转型

创建一个Dog对象
Dog dog = new Dog();
类名称 类引用 = new 类对象();

若Dog extends Animal
我们可以这样写:
Animal animal = new Dog()
父类名称 父类引用 = new 子类实例();

解析:这两个类满足is a关系,狗一定是动物,所以dog既是Dog类也是Animal类,Animal有的属性和方法Dog都有,天然语义。

向上转型发生在有继承关系的类之间,顶级父类可以引用所有的子类,层层关系,不一定是直接的子类。

我们可以假设一下,如果没有向上转型,当我要写fun(参数类型 参数名)这个方法的时候,有多少个类就要重载fun方法多少次,因为即使是有继承关系的类,他们还是不同的类型,就构成了方法重载。

当我要调用fun方法,我需要知道所有类的对象,因为每个类都有一个fun方法,就会很繁琐。

这体现了Java的易扩展性,当产生一个新的子类的时候,我依旧可以用一个共同的父类引用去指代所有子类对象

所以直接用一个顶级父类去引用所有的子类,何乐而不为呢?代码如下所示

public class Animal {}

class Bird extends Animal{}
class Duck extends Animal{}
class Dog extends Animal{}

class Test {
    public static void main(String[] args) {
        fun(new Animal());
        fun(new Duck());
        fun(new Dog());
    }
    // 只要是Animal及其子类,都是天然的Animal对象,都满足is a关系
    // 通过Animal最顶层的父类引用,指代所有的子类对象。
    public static void fun(Animal animal) {}
}

当代码变成了如下样子,会产生什么结果呢?

public class Animal {
    public void eat(){
        System.out.println("Animal的eat方法");
    }
}
class Bird extends Animal{
    public void eat(){
        System.out.println("Bird的eat方法");
    }
}
class Duck extends Animal{
    //方法重写
    public void eat(){
        System.out.println("Duck的eat方法");
    }
}
class Dog extends Animal{
    //方法重写
    public void eat(){
        System.out.println("Dog的eat方法");
    }
}

class Test {
    public static void main(String[] args) {
        Animal animal1 = new Bird();
        Animal animal2 = new Duck();
        Animal animal3 = new Dog();
        fun(animal1);
        fun(animal2);
        fun(animal3);
    }
    public static void fun(Animal animal) {
        animal.eat();
    }
}
输出:
Animal的eat方法
Duck的eat方法
Dog的eat方法

同一个方法名,根据传入参数对象的不同,表现出来了不同的行为,这就是多态性。

那么这种特性是如何来的呢=>引入方法重写

方法重写

  1. 定义:发生在有继承关系的类之间,子类定义了和父类除了权限不同,其他全都相同的方法,这样的一组方法叫方法重写。

  2. 规则:不用去看前半部分,看当前是通过哪个类new的对象,若该类对象重写的相关方法,则调用的一定是重写后的方法。

  3. 上述代码分析:

fun(animal1); //不看前半部分,看animal1对象new的是Dog类,所以调用的是Dog类的eat方法,其他两个对象类似。

若子类没有重写相关方法,则向上搜寻碰到第一个父类重写的方法,就调用最“接近”的相关方法。(一层层向上找,找到了就调用)

  1. 重写权限:当发生重写时,子类权限必须>=父类权限才可以重写(除了private,因为父类里的方法是私有的,子类继承了但没有权限调用)
  2. static方法不能重写,因为多态的本质就是因为调用了不同的子类对象,这些对象所属的类重写相应的方法。static与对象无关,何谈重写呢?
public class Animal {
    protected void eat(){
        System.out.println("Animal的eat方法");
    }
}
class Dog extends Animal{
    void eat(){
        System.out.println("Dog的eat方法");
    }
}

class Test {
    public static void main(String[] args) {
        fun(new Animal());
        fun(new Dog());
    }
    public static void fun(Animal animal) {
        animal.eat();
    }
}
报错:
java: polymorphism.Dog中的eat()无法覆盖polymorphism.Animal中的eat()
  正在尝试分配更低的访问权限; 以前为protected

Animal的eat方法是用protected修饰的,Dog的eat方法是用default修饰的,default<protected,所以会报错。

总结:向上转型一共可以发生在三个位置

  1. 方法的传参——上述代码有体现

public static void fun(Animal animal) {}

  1. 引用赋值时

Animal animal1 = new Bird();

  1. 方法的返回值——实例返回类型是Animal,返回值是bird
public static Animal test() {
       Bird bird = new Bird();
       return bird;
   }

看一个代码

public class B {
    public B(){
        fun();
    }
    public void fun(){
        System.out.println("B.fun");
    }
}
class D extends B{
    private int num = 10;
    public void fun(){
        System.out.println("D.fun,num = " + num);
    }

    public static void main(String[] args) {
        D d = new D();
        System.out.println(d.num);
    }
}
输出
D.fun,num = 0
10

原因:创建了D的对象,自动调用D的无参构造方法。

​ public D(){
​ super(); //调用父类的无参构造
​ num = 10;

​ }

​ 因为D继承了B,所以先创建B的对象。

​ 创建B的对象,先调用B的无参构造方法。

​ 由于子类重写了fun方法,并且new的是子类对象,所以调用了子类重写的fun方法,输出num = 0;

​ 接着调用D的无参构造,初始化num = 10;

​ 主方法调用了d.fun,输出10;

​ 程序结束

向下转型

语法:类名称 引用名称 = new 类实例

引用名称.方法名称();

若在调用方法的时候,这个类并没有定义这个方法,而他的子类定义了这个方法,我们不能通过向上转型,即父类对象.方法名去调用,只能子类对象.方法名去调用

举例:Animal animal = new Dog();

animal.方法名();

总结:这个方法能不能调用,看animal,因为这个引用还是父类的引用,仅存在有继承关系的向上转型中。

如果我们想调用一个方法在父类没有创建,在子类创建的方法。可以采用向下转型。

Animal animal = new Dog();
animal.play();//此时是Animal类型,但父类没有play()方法 error
Dog dog = (Dog)animal //向下转型,强转
//语法:子类名称 子类引用 = (子类名称)父类引用
dog.play() //运行通过
//将父类引用强制转换为子类引用
代码如下:
public class Animal {
    public void eat(){
        System.out.println("Animal的eat方法");
    }
}
class Dog extends Animal{
    public void play(){
        System.out.println("Dog的play方法,至此一个");
    }
}

class Test {
    public static void main(String[] args) {
        Animal animal = new Dog(); //披着Animal外衣的Dog类对象
        //animal.play(); //error
        Dog dog = (Dog) animal; //强转成Dog类型
        dog.play();
    }
}

总结:要发生向下转型,首先父类引用就是用过用该类向上转型产生的。

Animal animal = new animal();
Dog dog = (Dog)animal //error
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值