Java面向对象之包与继承

546 阅读12分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第5天,点击查看活动详情


⭐️前面的话⭐️

本篇文章带大家认识Java基础知识——包与继承,在Java当中一切皆可视为对象,而对象是由类所实例化出来的,将类组织起来那就是一个包,类与类之间是可以存在关联的,例如猫,狗,鸟等动物存在相同的行为或特征,我们把这些相同的行为与特征都集中起来构建成一个新的类,则猫,狗,鸟等动物都继承了该类。

📒博客主页:未见花闻的博客主页
🎉欢迎关注🔎点赞👍收藏⭐️留言📝
📌本文由未见花闻原创!
📆掘金首发时间:🌴2022年4月6日🌴
✉️坚持和努力一定能换来诗与远方!
💭参考书籍:📚《Java编程思想》,📚《Java核心技术》
💬参考在线编程网站:🌐牛客网🌐力扣
博主的码云gitee,平常博主写的程序代码都在里面。
博主的github,平常博主写的程序代码都在里面。
🍭作者水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!


1.包

1.1概念

Java 允许使用包( package )将类组织起来。借助于包可以方便地组织自己的代码,并将自己的代码与别人提供的代码库分开管理。

标准的 Java 类库分布在多个包中,包括 java.lang、java.util 和java.net 等。标准的 Java包具有一个层次结构。如同硬盘的目录嵌套一样,也可以使用嵌套层次组织包。所有标准的Java 包都处于java 和 javax 包层次中。

使用包的主要原因是确保类名的唯一性。 假如两个程序员不约而同地建立了Employee类。只要将这些类放置在不同的包中, 就不会产生冲突。事实上,为了保证包名的绝对唯一性, Sun 公司建议将公司的因特网域名(这显然是独一无二的) 以逆序的形式作为包名,并且对于不同的项目使用不同的子包。例如, weijianhuawen.com 是一个的域名。逆序形式为 com.weijianhuawen。这个包还可以被进一步地划分成子包, 如 com.weijianhuawen.corejava

✨从编译器的角度来看, 嵌套的包之间没有任何关系。例如,java.util 包与java.util.jar 包毫无关系。每一个都拥有独立的类集合。

1.2类的组织

对类进行组织是由关键字package 来设置类所在的包路径。

package 路径;
package com.juejin.test;

⭐️规则:

  1. .java文件最上面加上package语句指定该文件的代码所在的包。
  2. 包名尽量指定成唯一的名字,一般采用公司或单位的域名的颠倒形式。(如juejin.com,则包名com.juejin....)
  3. 包名要与路径相统一,如一个包名为com.juejin.test,则它所对应的路径为com/juejin/test
  4. 如果没有package包,则这些类会被放在一个默认包中。

包的创建: 1

不过在创建包前要注意设置一下编译器,这样编译器才能自动将你所写的包名根据.分开自动生成路径,否则有可能出现包名为com.juejin.test2

包创建好后,创建一个类,编译器自动会生成语句package指定当前类所在的包。 3

1.3导入包中的类

在一个类中我们经常需要使用Java内置的一些方法,这个时候往往需要导包,比如以字符串输出数组的方法toString,我们需要导入java.util包中的Arrays类。

最原始的方法就是将类的具体路径写出来,再调用方法。

package com.juejin.test;

public class Package {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5,6,7,8,9};
        String s = java.util.Arrays.toString(arr);
        System.out.println(s);
    }
}

4

除此之外,有一个关键字import能够导入一个包中具体的类。

package com.juejin.test;

import java.util.Arrays;//导入java.util包中的Arrays类

public class Package {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5,6,7,8,9};
        System.out.println(Arrays.toString(arr));
    }
}

Arrays能够直接点出toString方法说明该方法是静态的。 3

除了import java.util.Arrays导入包中的类,还可以使用import java.util.*,这个语句会导入java.util包中所有的类,但是不是直接将这个包中所有类全部导入,而是用到哪一个类就导入哪一个类。比如说只用Arrays类,实际上只会导入Arrays类,而这个*可以理解为通配符。 但是使用*导入类可能会存在一个问题,就是你导入两个或多个包,其中里面有两个包有同名的类,这个时候就会报错,因为编译器不知道你究竟需要导哪一个类。比如Date类, 5 6

这个时候要么写全路径访问,要么导包时具体导入所需要的那一个类。 如果你要得到一个时间戳,则导入包时语句需改为:

import java.util.Date;
import java.sql.*;
package com.juejin.test;
import java.util.Date;
import java.sql.*;

public class Import {
    public static void main(String[] args) {
        Date date = new Date();
        //获取时间戳
        System.out.println(date.getTime());
    }
}

7

如果不想改import语句,这需要写出类的全路径创建对象。

package com.juejin.test;
import java.util.*;
import java.sql.*;

public class Import {
    public static void main(String[] args) {
        java.util.Date date = new java.util.Date();
        //获取时间戳
        System.out.println(date.getTime());
    }
}

8

因为时间一直在变,所以时间戳也一直在变,所以不同时间运行程序结果是不同的。

如果导入类的类名与所在类类名相同,请把类名修改,否则会报错。

//被导入的类
package com.juejin.test;
public class Package {
    public int add(int a, int b) {
        return a + b;
    }
}

9

import com.juejin.test.Package;

public class Package2 {
    public static void main(String[] args) {
        Package p = new Package();
        int sum = p.add(2,3);
        System.out.println(sum);
    }
}

10

1.4静态导入

使用import static可以导入包中静态的方法或字段。 比如我们最常使用的打印方法:

package com.juejin.test;
import static java.lang.System.*;

public class Test {
    public static void main(String[] args) {
        String name = "未见花闻";
        out.println(name);
    }
}

11 还有数学运算方法Math包,里面的方法也是静态的。

package com.juejin.test;

import static java.lang.Math.*;

public class Test {
    public static void main(String[] args) {
        int a =2;
        double b = pow(a, 3);
        System.out.println(b);
        System.out.println(abs(-999));
        System.out.println(min(1, 5));
        System.out.println(max(122, 867));
    }
}

12

注意:java.lang:系统常用基础类(StringObject),此包从JDK1.1后自动导入。注意: java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。

1.5包访问权限

前面我们已经了解了publicprivatepublic和private这两个关键字分别表示公共与私有。但是一个类中的成员我们既没有说明public也没有说明private,没有任何访问权限的关键字修饰,则表示默认权限,即包访问权限,就是在同一个包内能够访问,不同包之间是不能访问的。

package com.juejin;

public class Demo2 {
    int val = 10;
}

在包外访问不到这个val: 13 包内访问:

package com.juejin;

public class Demo2 {
    int val = 10;

    public static void main(String[] args) {
        Demo2 d = new Demo2();
        System.out.println(d.val);
    }
}

14

2.继承

2.1什么是继承?

先来看一段代码:

class Dog {
    public String name;
    public int age;
    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void eat() {
        System.out.println(name + "在吃饭!");
    }
    public void run() {
        System.out.println(name + "正在跑!");
    }
}

class Bird {
    public String name;
    public int age;
    public Bird(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void eat() {
        System.out.println(name + "正在吃饭!");
    }

    public void fly() {
        System.out.println(name + "正在飞!");
    }
}
public class Test1 {
    public static void main(String[] args) {
        Dog dog = new Dog("小狗", 18);
        dog.run();
        dog.eat();
        Bird bird = new Bird("小鸟", 19);
        bird.fly();
        bird.eat();
    }
}

15 我定义了两个类,一个是DogDog,另一个是BirdBird,里面存放了两种动物的行为与特征,但是我们发现它们有不少共同点,比如它们都有名字,年龄,都会吃饭,这是所有动物都有的特征与行为,所以我们不妨将这些共同的特征封装成一个类AnimalAnimal,然后在定义Dog,BirdDog,Bird类的时候后面加上extends Animal,这代表Dog,BirdDog,Bird类继承了类AnimalAnimalDog,BirdDog,Bird类中不需要定义名字,年龄字段,吃饭的方法,就能使用这些成员变量和方法,这就叫做继承继承。其中Dog,BirdDog,Bird为子类(导出类),AnimalAnimal类为父类(基类,超类)。所谓继承,就是子类继承父类的字段和方法,private修饰的成员子类不能继承,或者说能继承,但是子类不能访问它,因为private的属性就是只能在类中进行访问,子类与父类不是同一个类,所以子类访问不了父类private修饰的成员。

class Animal {
    public String name;
    public int age;
    public void eat() {
        System.out.println(name + "在吃饭!");
    }
}
class Dog extends Animal{
    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void run() {
        System.out.println(name + "正在跑!");
    }
}

class Bird extends Animal{
    public Bird(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void fly() {
        System.out.println(name + "正在飞!");
    }
}
public class Test1 {
    public static void main(String[] args) {
        Dog dog = new Dog("小狗", 18);
        dog.run();
        dog.eat();
        Bird bird = new Bird("小鸟", 19);
        bird.fly();
        bird.eat();
    }
}

16

2.2类的继承

前面用了一个实例引出了什么是继承,接下来我们来讨论一下继承的一些细节。打个比方我们不妨将图纸分为两类,一类具有共性,另一类具有特性,具有特性的图纸继承了具有共性的图纸,则具有共性的图纸(类)称为父类(基类,超类),具有特性的图纸(类)称为子类,两者的关系是子类继承父类,子类实例化出的对象就相当于两张图纸实例的一个对象,继承往往具有子类is a 父类或者子类is like a父类。

2.2.1继承的特征

继承的特征指的是对共性的抽取 ,使用extendsextends关键字进行处理,能够对代码进行重复使用。

2.2.2Java中继承的规则

✨基本规则:

  1. Java中的继承是单继承,不能同时继承多个类(两个或两个以上),但是可以连续继承。
  2. 子类构造时,需 对父类帮助进行构造。
  3. supersuper关键字表示父类对象的引用(不能出现在静态方法中),可以使用该关键字调用父类的构造方法与成员。

⭐️关于supersuper

  1. super()super(),调用父类的构造方法。
  2. super.func()super.func(),调用父类的成员方法。
  3. super.datasuper.data,访问父类的成员变量。
  4. super()super()在子类构造方法没有写,默认调用父类无参构造。

细心的同学已经发现了,上面所写的代码,并没有发现子类帮助父类进行构造,但是程序依然可以正常运行,其实当没有定义任何构造方法时,编译器会自动生成一个不带参数的构造方法,父类中没有定义构造方法,所以父类会生成一个不带参数的构造方法,子类的构造方法如果没有帮助父类调用构造方法时,相当于会自动生成不带参数的super()super(),所以程序依然可以正常运行。相当于下面的代码:

class Animal {
    public String name;
    public int age;
    public void eat() {
        System.out.println(name + "在吃饭!");
    }
}
class Dog extends Animal{
    public Dog(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    public void run() {
        System.out.println(name + "正在跑!");
    }
}

class Bird extends Animal{
    public Bird(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    public void fly() {
        System.out.println(name + "正在飞!");
    }
}

如果在父类中定义了一个带参数的构造方法,如果子类没有调用父类的构造方法,则会报错。 16 我们再来看一段代码:

class Animal {
    public String name;
    public int age;
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void eat() {
        System.out.println(name + "在吃饭!");
    }
}
class Dog extends Animal{
    public Dog(String name, int age) {
        super("动物", 1);
        this.name = name;
        this.age = age;
    }
    public void run() {
        System.out.println(name + "正在跑!");
    }
    public void fatherClass() {
        System.out.println(super.name + super.age);
    }
}
public class Test1 {
    public static void main(String[] args) {
        Dog dog = new Dog("小狗", 18);
        dog.fatherClass();
    }
}

✨这个程序会输出什么呢?

A.小狗18A.小狗18

B.动物1B.动物1

你们觉得是哪个选项呢?我们了看看输出结果:

17 答案是A。为什么呢?这段代码首先帮助父类进行构造name = "动物" age = 1,然后子类再构造name = "小狗" age = 18,因为子类中没有name age,所以两者访问的都是父类对象的name age,所以输出小狗18。

当然如果子类中有name age,则会输出动物1。

class Animal {
    public String name;
    public int age;
    public Animal(String name, int age) {
        this.age = age;
        this.name = name;
    }
    public void eat() {
        System.out.println(name + "在吃饭!");
    }
}
class Dog extends Animal {
    public String name;
    public int age;
    public Dog(String name, int age) {
        super("动物", 1);
        this.name = name;
        this.age = age;
    }
    public void run() {
        System.out.println(name + "正在跑!");
    }
    public void fatherClass() {
        System.out.println(super.name + super.age);
    }
}

public class Test2 {
    public static void main(String[] args) {
        Dog dog = new Dog("小狗", 18);
        dog.fatherClass();
    }
}

18

2.3访问权限

除了public, private, default(包访问权限),还有一个protected访问权限关键字,范围是在default的基础是加上父子类。注意default是默认权限,不需要使用关键字修饰成员。 19

20 我们希望类要尽量做到 "封装", 即隐藏内部实现细节, 只暴露出 必要 的信息给类的调用者.因此我们在使用的时候应该尽可能的使用 比较严格 的访问权限. 例如如果一个方法能用 private, 就尽量不要用public. 另外, 还有一种 简单粗暴 的做法: 将所有的字段设为 private, 将所有的方法设为 public. 不过这种方式属于是对访问权限的滥用, 还是更希望同学们能写代码的时候认真思考, 该类提供的字段方法到底给 "谁" 使用(是类内部自己用, 还是类的调用者使用, 还是子类使用).

✨如果一个类不想被继承可以使用关键字final修饰类。 final修饰字段(变量),则变量只能初始化赋值一次,不能修改,相当于常量。 final修饰方法,则方法不能重写(下一篇博客详细介绍)。

2.4this与super区别

✨super来引用父类对象,用this来引用当前对象。

⭐️相同点:

  1. 均可以调用构造方法,但是两者不能同时出现在同一构造方法。
  2. 调用某构造方法时必须放在另一个构造方法第一行。
  3. 都不能放在静态方法中使用。

⭐️不同点:

  1. 引用对象不同。
  2. super()从子类中调用父类的构造方法,this()调用当前类的构造方法。

3.留给读者

下面代码输出什么?

class Animal {
    public String name;
    public int age;
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void eat() {
        System.out.println(name + "在吃饭!");
    }
}
class Dog extends Animal{
    public Dog(String name, int age) {
        super("动物", 1);
        this.name = name;
        this.age = age;
    }
    public void run() {
        System.out.println(name + "正在跑!");
    }
    public void fatherClass() {
        System.out.print(super.name + super.age);
    }
}

class Bird extends Animal{
    public Bird(String name, int age) {
        super("动物", 1);
        this.name = name;
        this.age = age;
    }
    public void fly() {
        System.out.println(name + "正在飞!");
    }
    public void fatherClass() {
        System.out.print(super.name + super.age);
    }
}
public class Test1 {
    public static void main(String[] args) {
        Dog dog = new Dog("小狗", 18);
        Bird bird = new Bird("小鸟", 19);
        dog.fatherClass();
        bird.fatherClass();
    }
}

A.小狗18小鸟19A.小狗18小鸟19

B.小鸟19小鸟19B.小鸟19小鸟19

C.小狗18小狗18C.小狗18小狗18

D.小狗18动物1D.小狗18动物1

答案找博主,或者下篇博文见!