一,封装
什么是封装?
从词含义来说就是将某些东西封起来保存,我们可以这样理解,一个东西需要发快递,首先我们是要将东西给到快递公司,他们会把你的快递装到纸盒子里,用胶带封好,这个操作就是封装。再比如插板,你看那些插孔就是提供给外界的接口,任何电器厂商的插头都按照那个插孔来实现,而不管插板内部是怎么做的。专业点来讲是信息隐藏,是指利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。系统的其他对象只能通过包裹在数据外面的已经授权的操作来与这个封装的对象进行交流和交互。也就是说用户是无需知道对象内部的细节(当然也无从知道),但可以通过该对象对外的提供的接口来访问该对象。
为什么要进行封装?
拿快递来说,进行封装是为了更好的提高空间利用率,防止物品破碎,保密性。对于编程语言来讲,是为了高内聚,低耦合。说白了,就是要把关键的,没必要给其他人看的逻辑和熟悉密封好,装起来,不让其他人看。同时也有其他好处在这里不多余解释,自己在以后的开发中慢慢理解。
1、良好的封装能够减少耦合。
2、类内部的结构可以自由修改。
3、可以对成员进行更精确的控制。
4、隐藏信息,实现细节。
如何进行封装?
使用访问控制符
java提供了三种访问权限,准确的说还有一种是默认的访问权限,加上它一共四种。
private 在当前类中可访问
default 在当前包内和访问
protected 在当前类和它派生的类中可访问
public 公众的访问权限,谁都能访问
在这里我们使用的第一种private,先看看代码
public class Husband {
/*
* 对属性的封装
* 一个人的姓名、性别、年龄、妻子都是这个人的私有属性
*/
private String name ;
private String sex ;
private int age ;
/*
* setter()、getter()是该对象对外开发的接口
*/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
我们可以看到,基本的属性都被private修饰,其他的方法都是public修饰符在这里说一下Java中四种权限修饰符。不同的权限修饰符有着不同的访问能力。
public:公共的。protected:受保护的default:默认的private:私有的
图中空着的表示没有访问权限,由图可知,public的访问权限最大,而private的最小
被private修饰后的成员变量和成员方法,只在本类中才能访问。default的在程序中不进行修饰他就是默认的
总结
1,需要修改属性的访问控制符(修改为private); 2,创建getter/setter方法(用于属性的读写); 3,在getter/setter方法中加入属性控制语句(用于判断属性值的合法性);
封装后如何调用?
为了实现良好的封装,我们通常将类的成员变量声明为private,为了能够在外部使用,可以通过定义public方法来对这个变量来访问。对一个变量的操作,一般有读取和赋值2个操作,我们分别定义2个方法来实现这2个操作,一个是getXX(XX表示要访问的成员变量的名字)用来读取这个成员变量,另一个是setXX()用来对这个变量赋值。
二,继承
什么是继承?
百度是这样解释的:继承是指一个对象直接使用另一对象的属性和方法。也指按照法律或遵照遗嘱接受死者的财产、职务、头衔、地位等继承是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。多个类可以称为子类,单独这个类称为父类、超类或者基类。子类可以直接访问父类中的非私有的属性和行为。通过 extends 关键字让类与类之间产生继承关系。
为什么要继承?
比如生活中的例子:
鸡、鸭、鹅等这些可以看做是家禽类;狮子、老虎凳可以看做野兽类;而它们又都是动物,所以还可以看做是动物类;所以继承需要符合is-a的关系,父类更通用、子类更具体可是,虽然家禽类动物和野兽类动物都是属于动物,但是两者的属性和行为上有差别,所以子类会具有父类的一般特性也会具有自身的特性。
继承有什么好处?
- 提高代码的复用性。
- 让类与类之间产生了关系,是多态的前提。
如何进行继承?
先看代码
/*
* 定义员工类Employee,做为父类
*/
class Employee {
String name; // 定义name属性
// 定义员工的工作方法
public void work() {
System.out.println("尽心尽力地工作");
}
}
/*
* 定义讲师类Teacher 继承 员工类Employee
*/
class Teacher extends Employee {
// 定义一个打印name的方法
public void printName() {
System.out.println("name=" + name);
}
}
/*
* 定义测试类
*/
public class ExtendDemo01 {
public static void main(String[] args) {
// 创建一个讲师类对象
Teacher t = new Teacher();
// 为该员工类的name属性进行赋值
t.name = "小明";
// 调用该员工的printName()方法
t.printName(); // name = 小明
// 调用Teacher类继承来的work()方法
t.work(); // 尽心尽力地工作
}
}
创建父类,创建子类对象通过 extends 关键字,声明一个子类继承另外一个父类
继承特点一
1.Java只支持单继承,不支持多继承。
//一个类只能有一个父类,不可以有多个父类。
class SubDemo extends Demo{} //ok class SubDemo extends Demo1,Demo2...//error
2.Java支持多层(重)继承(继承体系)。
class A{}
class B extends A{} class C extends B{}
继承特点二
1.成员变量
不重名:访问没有影响
重名:有影响
如何解决影响?
如果要继承父类的变量,用super关键字super :代表父类的存储空间标识(可以理解为父亲的引用)。
super.父类成员变量名
如果不继承父类变量,用this :代表当前对象的引用(谁调用就代表谁)。
this.子类变量名
super和this都可以访问成员和构造方法
2.成员方法
不重名:访问没有影响
重名:有影响
影响了什么?
如果子类方法和父类方法重名,会出现一种情况,方法重写
方法重写:子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现。
应用:子类可以根据需要,定义特定于自己的行为。既沿袭了父类的功能名称,又根据子类的需要重新实现父类方法,从而进行扩展增强。
注意事项:
1. 子类方法覆盖父类方法,必须要保证权限大于等于父类权限。
2. 子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。
3.构造方法
1. 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
2. 构造方法的作用是初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构
造方法中默认有一个 super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。
三,多态
什么是多态?
多态,多种形态。就像水,有固体,液体,气体。这就是水的多态。再举个例子,龙生九子,九个龙子都不一样。
再看看这个:
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用变量调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
简单点就是:允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)!!!
实现多态的必要条件 :继承、重写、向上转型(父类引用指向子类对象)
继承:在多态中必须存在有继承关系的子类和父类。
重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。
同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。
为什么要使用多态?
封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。而多态除了代码的复用性外,还可以解决项目中紧偶合的问题,提高程序的可扩展性.。耦合度讲的是模块模块之间,代码代码之间的关联度,通过对系统的分析把他分解成一个一个子模块,子模块提供稳定的接口,达到降低系统耦合度的的目的,模块模块之间尽量使用模块接口访问,而不是随意引用其他模块的成员变量。
怎么使用多态?
看看多态的前提是继承,重写,向上转型,要实现多态,有两种方法
第一种继承(单继承:一组相关的类提供一致的服务接口)
第二种接口(多继承多实现:一组相关或者不相关的接口进行组合与扩充,能够对外提供一致的服务接口)相对于继承来说有更好的灵活性
什么是接口?(重点)
是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量、构造方法和成员方法,那么接口的内部主要就是封装了方法,包含抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8),私有方法(JDK 9)。
抽象方法------abstract public abstract void 方法名();
默认方法------default() public void 方法名(){}; 可以继承,可以重写,二选一,但是只能通过实现类的对象来调用
静态方法------static public static void run(){}; 静态与.class 文件相关,只能使用接口名调用,不可以通过实现类的类名或者实现类的对象调用
私有方法------private private void func1(){}; 私有方法:只有默认方法可以调用
私有静态方法:默认方法和静态方法可以调用
建议:如果一个接口中有多个默认方法,并且方法中有重复的内容,那么可以抽取出来,封装到私有方法中,供默认方法
去调用。从设计的角度讲,私有的方法是对默认方法和静态方法的辅助。
接口的定义:它与定义类方式相似,但是使用 interface 关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。引用数据类型:数组,类,接口。接口的使用:它不能创建对象,但是可以被实现( implements ,类似于被继承)。一个实现接口的类(可以看做是接口的子类),需要实现接口中所有的抽象方法,创建该类对象,就可以调用方法了,否则它必须是一个抽象类。
接口多实现:
class 类名 [extends 父类名] implements 接口名1,接口名2,接口名3... {
// 重写接口中抽象方法【必须】
// 重写接口中默认方法【不重名时可选】
}
接口的多继承:
一个接口能继承另一个或者多个接口,这和类之间的继承比较相似。接口的继承使用 extends 关键字,子接口继承父接口的方法。如果父接口中的默认方法有重名的,那么子接口需要重写一次。
子接口重写默认方法时,default关键字可以保留。子类重写默认方法时,default关键字不可以保留。
接口中:无法定义成员变量,但是可以定义常量,其值不可以改变,默认使用public static final修饰。接口中:没有构造方法,不能创建对象。接口中:没有静态代码块。
实现多态举例一(继承)
定义父类:abstract (抽象的)---修饰符
public abstract class Animal {
public abstract void eat();
}
定义子类:
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
}
定义测试类:
public class Test {
public static void main(String[] args) {
// 多态形式,创建对象
Animal a1 = new Cat();
// 调用的是 Cat 的 eat
a1.eat();
// 多态形式,创建对象
Animal a2 = new Dog();
// 调用的是 Dog 的 eat
a2.eat();
}
}
子类继承父类的抽象方法,先进行方法重写。也就是这样:当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,执行的是子类重写后方法。
多态体现形式 Animal a1 = new Cat(); 父类 变量名 = new 子类();
实现多态举例二(接口)
摘自 https://blog.csdn.net/gcyqweasd/article/details/109752122
package fly;
public interface YunDong {
}
package fly;
public interface Fly extends YunDong{ //FLY继承了YunDong
public void doFly();
}
package fly;
/**
* 一个类实现了一个接口,他就必须要实现接口中的所有抽象方法
*/
public class DaYan implements Fly {
@Override // 注解,子类重写父类时写错了会给报错
public void doFly() {
System.out.println("大雁在飞");
}
}
package fly;
public class MaQue implements Fly {
@Override // 注解,子类重写父类时写错了会给报错
public void doFly() {
System.out.println("麻雀在飞");
}
}
package fly;
public class Plane implements Fly {
@Override
public void doFly() {
System.out.println("飞机在飞");
}
}
package fly;
public class ShenFei {
public void testFly(Fly fly) {
fly.doFly();
}
}
package fly;
public class Test {
public static void main(String[] args) {
ShenFei sf = new ShenFei(); //创建对象
Fly dayan = new DaYan(); //声明为接口类型
Fly Maque = new MaQue(); //父类类型 变量名 = new 子类对象;
Fly plane = new Plane();
sf.testFly(plane);
/*
* 多态:
* 一个父类的引用,指向它的子类对象
* 一个接口的引用,指向它的实现类对象
* 即多态可以用继承来实现也可以用接口来实现
* 如果存在继承关系,那么就用继承实现多态
* 如果不存在继承关系,那么就用接口实现多态
* 趋势是:都倾向于使用接口实现多态
*
* 继承更多的体现在重用方面
* 接口更多的体现在多态(制定规范)
*
* 面向对象最终实现的目标:可扩展 可重用 灵活性好
*
*/
}
}
多态的好处:
参考:https://www.cnblogs.com/hupp/p/4854918.html
1.可替换性(substitutability)。多态对已存在代码具有可替换性。例如,多态对圆Circle类工作,对其他任何圆形几何体,如圆环,也同样工作。
2.可扩充性(extensibility)。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。
3.接口性(interface-ability)。多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。如图8.3 所示。图中超类Shape规定了两个实现多态的接口方法,computeArea()以及computeVolume()。子类,如Circle和Sphere为了实现多态,完善或者覆盖这两个接口方法。
4.灵活性(flexibility)。它在应用中体现了灵活多样的操作,提高了使用效率。
5.简化性(simplicity)。多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。
多态种引用类型转换
向上转型:当父类引用指向一个子类对象时,便是向上转型
父类类型 变量名 = new 子类类型();
如:Animal a = new Cat();
向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的
子类类型 变量名 = (子类类型) 父类变量名;
如:Cat c =(Cat) a;
为什么要转型?
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥有,而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点"小麻烦"。
所以,想要调用子类特有的方法,必须做向下转型。
转型案例:
定义类:
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void catchMouse() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void watchHouse() {
System.out.println("看家");
}
}
定义测试类:
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat();
// 调用的是 Cat 的 eat
// 向下转型
Cat c = (Cat)a;
c.catchMouse();
// 调用的是 Cat 的 catchMouse
}
}
出现转型异常怎么办?
看个例子
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】
}
}
可以通过编译,但是运行时,却报出了 ClassCastException ,类型转换异常!这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。这两个类型并没有任何继承关系,不符合类型转换的定
义。为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验,格式如下:
变量名 instanceof 数据类型
如果变量属于该数据类型,返回true。
如果变量不属于该数据类型,返回false。
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
if (a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
} else if (a instanceof Dog){
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse
}
如果想多参考可以看看,红包的案例,在我的随笔里有。