前言
终于经历了漫长的前期学习的铺垫,我们终于见到了面向对象程序的三大特性:封装,继承,多态。今天我来向大家介绍其中两个特性:封装和继承。
封装
什么是封装呢?我们可以简单的理解为,把想让用户看见的东西展现给用户,把不想让用户看见的东西不让用户看见。例如我们的电脑,我们能看见的就只有开机按钮、键盘、显示器、各种接口等日常用户使用的东西,而计算机真正工作的是cpu、显卡、内存等原件,这些原件是被机箱封装起来的。向这样隐藏细节,只向用户提供使用接口的方式我们称为封装。总结一下:封装就是将数据和操作数据的方法进行有机的结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
包的封装
在Java中包其实就是一个文件夹,里面存放了很多的类,这样可以让我们方便管理类。同时包的概念也对类和接口实现了封装,比如一个包中的类不想让另一个包使用,注意:在同一个工程的不同包中,可以存在相同名称的类。
如何创建包
如图这样我们就可以创建一个包了,在包下面我们可以创建类:
现在让我们来看看包是如何实现封装的:我们在com.Test.demo1
这个包中创建一个test类,并且创建两个成员变量
package com.Test.demo1;
public class Test {
public int a = 20;
int b = 30;
}
我们在com.Test.demo2
也创建了一个test2类,我们实例化了一个Test类型的对象t,通过t去访问成员变量。
package com.Test.demo2;
import com.Test.demo1.Test;
public class Test2 {
public static void main(String[] args) {
Test t = new Test();
System.out.println(t.a);
System.out.println(t.b);
}
}
我们发现我们可以访问a,但是并不能访问b,这是为什么呢?原因是我们在创建a,b两个成员变量时使用的访问限定符不一样,一个我们设置的访问修饰符是public
另一个我们没有设置,这个可以我们在以后的分享中会与大家总结,现在大家大家只需要理解在不同包中,我们可以自己设置访问权限,去限定方法或者字段能否在不同包中访问,这就是包的封装。
导包
Java中有提供了许多已经实现的类为我们使用,但在使用时我们需要导包,如何导包呢?我们以Arrays
类为例:
public class Test {
public static void main(String[] args) {
int[] array = new int[] {1,2,3,5,2,1};
java.util.Arrays.sort(array);
System.out.println(java.util.Arrays.toString(array));
}
}
我们想对array这个数组排序并且打印,我们可以使用Arrays这个类下面的sort方法和toString方,使用java.util.Arrays.(方法);
就可以导入java.util
这个包中的Arrays
类,但是这样太麻烦了,我们可以使用import
一次性解决问题
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
int[] array = new int[] {1,2,3,5,2,1};
Arrays.sort(array);
System.out.println(Arrays.toString(array));
}
}
private类的封装
private
也是我们说的访问限定符,他是对类中的字段和方法的限定。我们来看代码:
public class Person {
public String name;
private int age;
private double wight;
public Person() {
}
public Person(String name,int age,double wight) {
this.name = name;
this.wight = wight;
this.age = age;
}
}
我们写了一个人类,我们说一般来说名字都是最先被人知道的,而自己的年龄和体重等是一些比较隐私的问题,所以我们在创建人类时将age wight
等成员变量的访问权限设为private
。
public class Persontest {
public static void main(String[] args) {
Person p = new Person("张三",18,60);
System.out.println(p.name);
System.out.println(p.age);
System.out.println(p.wight);
}
}
我们创建一个Persontest
类用于测试Person
类,我们实例化了一个对象,我们想去通过对象打印这个人的姓名年龄和体重。
我们发现我们并不可以访问,因为我们的年龄和体重都是个人隐私不能随便给陌生人人访问,陌生人能获取的信息只能是你的名字,当然我们也有家人与朋友他们可以获取到自己的近况年龄和体重等。所以我们可以提供给自己的家人一个方法去访问自己的年龄和体重。
public class Person {
public String name;
private int age;
private double wight;
public Person() {
}
public Person(String name,int age,double wight) {
this.name = name;
this.wight = wight;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getWight() {
return wight;
}
public void setWight(double wight) {
this.wight = wight;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", wight=" + wight +
'}';
}
}
我们给了一些方法,这些方法的访问限定符都是public
,我们可以告诉我们的家人和朋友这些方法,这样他们就可以通过这个方法获取我的信息。
public class Persontest {
public static void main(String[] args) {
Person p = new Person("张三",18,60);
System.out.println(p.name);
System.out.println(p.getAge());
System.out.println(p.getWight());
System.out.println(p.toString());
}
}
这就是我们面向对象的封装。
继承
继承是什么呢?继承的意义又是什么呢?我们先来看看代码:
public class Dog {
public String name;
public int age;
public void eat() {
System.out.println("吃饭");
}
public void wangwang() {
System.out.println("wangwang");
}
}
public class Cat {
public String name;
public int age;
public void eat() {
System.out.println("吃饭");
}
public void miaomiao() {
System.out.println("miaomiao");
}
}
这里我们写了两个类一个猫猫类一个狗狗类,但我们发现我们的代码中有很多重复的地方,我们想猫猫和狗狗都有自己的名字和年龄,都会吃饭,可不可以优化一下只写一次这些东西呢?当然可以,这时我们就可以使用我们的继承了。
public class Animal {
public String name;
public int age;
public void eat() {
System.out.println( name + "吃饭");
}
}
我们写一个动物类里面有姓名和年龄,还有一个吃饭的方法,让猫猫类和狗狗类继承这个动物类就可以了。继承的语法修饰符 class (子类) extend (父类)
。
public class Cat extends Animal{
public void miaomiao() {
System.out.println("miaomiao");
}
}
public class Dog extends Animal{
public void wangwang() {
System.out.println("wangwang");
}
}
我们在测试类中测试一下我们写的代码:
public class Animaltest {
public static void main(String[] args) {
Cat c = new Cat();
c.name = "猫猫";
c.age = 18;
c.eat();
c.miaomiao();
Dog d = new Dog();
d.name = "狗狗";
d.age = 20;
d.eat();
d.wangwang();
}
}
这就是我们的继承。
super
经过上面的例子,我们发现了子类可以访问父类的方法和属性,那我们就会有问题了,如果子类和父类都有相同的属性和方法会访问谁的呢?我们还是从例子中寻找答案:
class Base {
public int b = 20;
public int c = 30;
public void func2() {
System.out.println("父类的方法2");
}
public void func3() {
System.out.println("父类的方法3");
}
}
class Derived extends Base {
public int a = 1;
public int c = 3;
public void func() {
System.out.println("子类的方法1");
}
public void func3() {
System.out.println("子类的方法3");
}
public void print() {
System.out.println(a);
System.out.println(b);
System.out.println(c);
}
}
public class Test2 {
public static void main(String[] args) {
Derived d = new Derived();
d.func();
d.func2();
d.func3();
d.print();
}
}
我们来看看输出的结果:
所以我们可以总结出,在子类的方法中或者通过子类对象访问成员和方法时:
1、如果子类中有该成员变量或者方法,则调用子类中的,如果子类中没有该成员变量或者成员方法,那么就向父类中查找,如果父类中也没有就报错。
2、如果有相同名字的成员变量或者成员方法,那么优先访问子类中的。
有些兄弟又要问了,那我就想访问父类中的成员变量或者成员方法呢?这个时候我们的super
关键字就发挥了作用。super关键字,该关键字主要作用:在子类方法中访问父类的成员。
public class Test2 {
public static void main(String[] args) {
Derived d = new Derived();
d.func3();
d.print();
}
}
class Base {
public int c = 30;
public void func3() {
System.out.println("父类的方法3");
}
}
class Derived extends Base {
public int c = 3;
public void func3() {
System.out.println("子类的方法3");
super.func3();
}
public void print() {
System.out.println(c);
System.out.println(super.c);
}
}
子类的构造方法
关于子类中的构造方法我们只需要记住一句话就可以了:在构造子类的时候,一定要先帮助父类进行构造。因为总是现有爸爸再有孩子的嘛。
我们需要使用super()
去调用父类的构造方法:
注意:我们在类与对象的时候说过,如果我们没有给任何的构造方法,那么Java会提供一个无参的构造方法,父类与子类同样适用,如果子类和父类都没有任何的构造方法,Java会在子类和父类中提供这样两个构造方法:
总结一下:
- 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的
super()
调用,即调用基类构
造方法- 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的
父类构造方法调用,否则编译失败。- 在子类构造方法中,
super(...)
调用父类构造时,必须是子类构造函数中第一条语句。super(...)
只能在子类构造方法中出现一次,并且不能和this
同时出现,因为this
也需要放在第一条语句,super
也需要放在第一条语句,会冲突。
子类继承父类时的内存分配
public class Test3 {
public static void main(String[] args) {
Derived d = new Derived();
}
}
class Base1 {
public int a;
public int b;
public int c;
public Base1() {
}
public void func3() {
System.out.println("父类的方法3");
}
}
class Derived1 extends Base1 {
public int c;
public Derived1() {
super();
}
public void func3() {
System.out.println("子类的方法3");
super.func3();
}
public void print() {
System.out.println(c);
System.out.println(super.c);
}
}
上面实例化的对象d
是如何开辟空间的呢?我们之前说过引用指向对象,d
是一个局部变量,存储的是在一个指向对象的地址,而对象是我们new
出来的,存储在栈上,这个对象中既有子类特有的成员变量与成员方法,也有父类中的成员方法与成员变量。方法都被放在方法区上。
访问限定符的总结
在我们学习类、对象和封装时我们已经提到过这个概念了,我们已经见过了private public 包访问权限
还有一个访问权限protected
,他的作用是什么呢?他修饰的权限可以为不同包中的子类访问,我们来看看到底是怎么回事:
package com.Test.demo1;
public class Test {
public int a = 20;
protected int b = 30;
}
package com.Test.demo2;
import com.Test.demo1.Test;
public class Test2 extends Test{
public static void main(String[] args) {
Test t = new Test();
System.out.println(t.a);
System.out.println(t.b);
}
}
我们发现这样访问会报错,我们不是说不同包中的子类可以访问嘛?但我们的访问方式不是这样的,我们可以写一个方法去打印他
package com.Test.demo2;
import com.Test.demo1.Test;
public class Test2 extends Test{
public void func() {
System.out.println(super.b);
}
public static void main(String[] args) {
Test t = new Test();
System.out.println(t.a);
}
}
好了总结一下我们的访问限定符:
限定符 | 范围 |
---|---|
private | 同一包中的同一类 |
default (包访问符) | 同一包中的同一类、同一包中的不同类 |
protected | 同一包中的同一类、同一包中的不同类、不同包中的子类 |
public | 谁都可以访问 |
public
: 可以理解为公开的东西,例如你的长相
protected
: 主要用在继承中
default
: 可以理解为你家人知道的但陌生人不知道的东西。
private
: 就是你自己的秘密
以上就是我对Java中继承和封装的理解,如有错误希望大家指出。