六、封装
封装是面向对象三大特征之一,其含义有两个(掌握思想):
- 把对象的字段和方法存放在一个独立的模块中(类)
- 信息隐藏,尽可能隐藏对象的数据和功能的实现细节
封装的好处:
- 保证数据的安全性,防止调用者随意修改数据
- 提高组件的重用性,把公用功能放到一个类中,谁需要该功能,直接调用即可
限制字段不能够随意修改的利器就是访问修饰符
6.1、访问修饰符
访问修饰符,决定了有没有权限访问某个资源,封装的本质就是要让有些类看不到另外一些类中定义的字段和方法。Java提供了不同的访问权限修饰符来限定类中的成员让谁可以访问到
修饰符 | 类的内部 | 同一个包 | 子类 | 任何地方 |
private | √ | |||
无 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
- private:表示当前类是私有的,只能在本类中访问,离开本类之后就不能直接访问
- 不写:表示当前包私有,定义和调用只能在同一个包中,才能访问
- protected:表示子类访问权限,同一个包的可以访问,即使不同包但是有继承关系也是可以访问的
- public:表示公共的,可以在当前项目的任何地方访问
一般在开发中都是提供私有属性,暴露公有的访问方法
6.2、JavaBean规范
JavaBean是一种某些符合条件的特殊类,但是必须遵循一定的规范:
- 类必须使用public修饰
- 必须保证有公共无参数构造器,即使手动提供了带参数的构造器,也得手动提供无参数构造器
- 段使用private修饰,每个字段提供一对getter和setter方法
public String getName(){ return name; //返回name字段存储的值 } public void setName(String n){ name = n; //把传过来的参数n的值,存储到name字段中 } 复制代码
6.3、构造器和setter方法
构造器和setter方法都可以给对象设置数据:
- 构造器,在创建对象的时候设置初始数据,只能初始化一次。
- setter方法,创建对象后再设置初始数据,可以设置多次。
七、继承
面向对象的继承思想,可以解决多个类存在共同代码的问题
- 被继承的类,称之为父类、基类
- 继承父类的类,称之为子类,拓展类
- 父类:存放多个子类共同的字段和方法
- 子类:存放自己特有的字段和方法
7.1、继承的语法
在程序中,如果一个类需要继承另一个类,此时使用extends关键字
public class 子类名 extends 父类名{ } 复制代码
注意:Java中类只支持单继承,不支持多继承,但是支持多重继承。也就是说一个子类只能有一个直接的父类,父类也可以再有父类。一个父类也可以有多个子类
class Student1(); class Student2(); class Student extend Student1,Student2(){}//错误 复制代码
Object类是Java语言的根类,任何类都是Object的子类,要么是直接子类,要么是间接子类
7.2、子类可以继承到父类哪些成员
子类继承父类之后,可以拥有到父类的某一些成员(字段和方法),根据访问修饰符来判断:
- 父类中用public和protected修饰的成员,子类均可以继承
- 如果父类和子类在同一个包中,使用缺省访问修饰的成员,此时子类可以继承到
- 如果父类中的成员用private修饰,子类继承不到,因为private只能在奔本类中访问
- 父类的构造器,子类也无法继承,因为构造器必须和类名相同
7.3、方法的重写
当子类存在一个和父类一模一样的方法时,我们就称之为子类覆盖了父类的方法,也称之为重写。那么我们就可以在子类方法体中,重写编写逻辑代码
方法调用的顺序为:通过对象调用方法时,先在子类中查找有没有对应的方法,若存在就执行子类的,若子类不存在就执行父类的,如果父类也没有,报错。
方法重写需要注意的点:
- private修饰的方法不能被子类所继承,也就不存在重写的概念
- 实例方法签名必须相同(方法签名=方法名+方法参数列表)
- 子类方法的返回值类型和父类的返回值类型相同或者是其子类
- 子类方法中声明抛出的异常小于或者等于父类方法声明抛出的异常类型
- 子类方法的访问权限比父类的访问权限更大或者相等
- 一般开发都是直接拷贝父类的方法定义粘贴到子类中,重新编写子类的方法体
7.4、super关键字
在子类中的某一个方法中需要去调用父类中被覆盖的方法,此时得使用super关键字。
如果调用被覆盖的方法不使用super关键字,此时调用的是本类中的方法。super关键字表示父类对象的意思
7.5、抽象
抽象方法用abstract来修饰方法,被abstract修饰的方法具备两个特征:
- 该方法没有方法体
- 要求子类必须覆盖该方法
7.5.1、抽象方法
使用abstract修饰的方法,称为抽象方法
public abstract 返回类型 方法名(参数); 复制代码
抽象方法的特点:
- 使用abstract修饰,没有方法体,留给子类去覆盖
- 抽象方法必须定义在抽象类或者接口中
7.5.2、抽象类
使用abstract修饰的类,称为抽象类
public abstract class 类名{ } 复制代码
抽象类的特点:
- 抽象类不能创建对象,调用没有方法体的抽象方法没有任何意义
- 抽象类中可以同时拥有抽象方法和普通方法
- 抽象类必须有子类才有意义,子类必须覆盖父类的抽象方法,否则子类也得作为抽象类
7.6、Object类和常用方法
Object本身表示对象的意思,是Java中的根类,要么是一个类的直接父类,要么就是一个类的间接父类。任何类都直接(间接)继承了Object类
class A{} //等价于 class A extends Object{} 复制代码
因为所有类都是Object类的子类, 所有类的对象都可以调用Object类中的方法,经常使用的方法有
7.6.1、boolean equals(Object obj)
boolean equals(Object obj):拿当前调用该方法的对象和参数obj做比较
在Object类中的equals方法和“ == ”符号相同,都是比较对象是否是同一个的存储地址。
public class ObjectDemo { public static void main(String[] args) { //创建Person对象p1 Person p1 = new Person(); //创建Person对象p2 Person p2 = new Person(); //比较p1和p2的内存地址是否相同 boolean ret1 = p1 == p2; boolean ret2 = p1.equals(p2); System.out.println(ret1); //false System.out.println(ret2); //false } } 复制代码
7.6.2、toString方法
toString表示把对象中的字段信息转换为字符串格式
打印对象其实就是打印对象的toString()方法,但是toString()方法默认打印的是对象的hashCode的值
com._04_object.Person@15db9742 复制代码
所以一般我们都会重写toString()方法
public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } 复制代码
7.7、== 符号详解
每一次使用new关键字,都表示在堆内存中创建一块新的内存空间
- 如果是基本数据类型:比较的是两个值是否相等
- 如果是对象数据类型:比较的是是否为同一块内存地址
八、多态
8.1、接口
接口是一种约定规范,是多个抽象方法的集合。仅仅只是定义了应该有哪些功能,本身不实现功能,至于每个功能具体怎么实现,就交给实现类完成。
接口中的方法是抽象方法,并不提供功能实现,体现了规范和实现相分离的思想,也体现了组件之间低耦合的思想。接口仅仅提供方法的定义,却不提供方法的代码实现
所谓耦合度,表示组件之间的依赖关系。依赖关系越多,耦合性越强,同时表明组件的独立性越差,在开发中往往提倡降低耦合性,可提高其组件独立性,举一个很简单的例子:
- 集成显卡:显卡和主板焊死在一起,显卡坏了,只能换主板
- 独立显卡:显卡和主板相分离,显卡插到主板上即可,显卡坏了,只换显卡,不用换主板
8.1.1、接口的定义
接口可以认为是一种特殊的类,但是定义类的时候使用class关键字,定义接口使用interface关键字,接口中的方法都是公共的抽象方法
public interface 接口名{ //抽象方法1(); //抽象方法2(); //抽象方法2(); } 复制代码
注意:从Java 8开始, Java支持在接口中定义有实现的方法
public interface IMan { public abstract void walk();//抽象方法 default void defaultMethod(){ System.out.println("有默认实现的方法, 属于对象"); } static void defaultMethod(){ System.out.println("有默认实现的方法, 属于类"); } } 复制代码
类可以继承类,但是只能单继承的,接口也可以继承接口,但是却可以继承多个接口,也就是说一个接口可以同时继承多个接口
8.1.2、接口实现类
和抽象类一样,接口是不可以创建对象的,如果想实现接口的功能,就必须定义一个类去实现接口,并且覆盖接口中的所有方法,这个类称为实现类,这种类和接口的关系称为实现关系
public class 类名 implements 接口名{ //覆盖接口中抽象方法 } 复制代码
- 接口:定义多个抽象方法,仅仅定义有哪些功能,不做任何实现
- 实现类:实现接口,重写接口中的抽象方法,完成具体功能的实现
8.2、多态
在继承关系,是一种”is A”的关系,也就说子类是父类的一种特殊情况
public class Animal{} public class Dog extends Animal{} public class Cat extends Animal{} 复制代码
此时我们可以认为猫和狗都是一种特殊动物,那么可以使用动物类型来表示猫或者狗
Dog dog = new Dog(); //创建一只狗对象,赋给子类类型变量 Animal animal = new Cat(); //创建一只猫对象,赋给父类类型变量 复制代码
此时对象animal具有两种类型:
- 编译类型:声明对象时候的类型(Animal)
- 运行类型:对象真实的类型(Cat)
当编译类型和运行类型不一致的时候,多态就产生了
所谓的多态就是表示一个对象有多种形态,简单来说就是同一引用类型,由于引用的实例不同,对同一方法产生的结果也不同
Person person = null; person = new Man(); //person此时表示Man类型 person = new Woman();//person此时表示Woman类型 复制代码
多态一定建立在方法重写或者实现之上,可以是继承关系(类和类),也可以是实现关系(接口和实现类),在开发中,一般都指接口和实现类之间的关系,多态在在开发中有两种定义格式
8.2.1、操作继承关系(开发中不是很多)
父类引用变量指向于子类对象,调用方法时实际调用的是子类的方法
父类 变量名 = new 子类(); 变量名.方法(); 复制代码
Animal类:
public class Animal { public void shout() { System.out.println("动物...叫..."); } } 复制代码
Cat类:
public class Cat extends Animal{ public void shout() { System.out.println("猫叫..."); } } 复制代码
Test类:
public class AnimalDemo{ public static void main(String[] args){ Animal animal = new Cat(); animal.shout(); } } 复制代码
运行结果:
猫叫 复制代码
8.2.2、操作实现关系
接口 变量名 = new 实现类(); 变量名.方法(); 复制代码
ISwimable 接口:
public interface ISwimable{ void swim(); } 复制代码
Fish类:
public class Fish implements ISwimable{} public void swim(){ Sysout.out.println("鱼在游") } } 复制代码
Test类:
public class Fish implements{ public static void main(String[] args){ ISwimable fish = new Fish(); fish.swim(); } } 复制代码
结果:
鱼在游泳 复制代码
8.2.3、多态中方法调用问题
如果我们把子类对象赋值给父类对象
Animal animal = new Cat(); animal.shout(); 复制代码
那么animal对象在调用shout方法的时候,他调用的是子类自己的方法还是父类的方法呢?
我们可以看得出,在编译时期,JVM会先去找父类的方法,如果找到了就去找子类的同名方法,如果子类也有就运行子类的方法,否则就运行父类的方法,如果连父类的方法都没有找到的话,直接编译失败。
8.2.4、类型转换和instanceof运算符
8.2.4.1、自动类型转换
自动类型转换:把子类对象赋给父类变量(多态)
Animal a = new Dog(); Object obj = new Dog(); //Object是所有类的根类 复制代码
8.2.4.2、强制类型转换
把父类类型对象赋给子类类型变量(前提:该对象的真实类型应该是子类类型)
当需要调用子类特有的方法时,必须经过强制类型转换,不过有一个要求:父类必须是真实的子类类型才可以转换
Animal a = new Dog(); Dog d = (Dog) a;//正确 Cat c = (Cat) a;//错误,真实类型为Dog 复制代码
8.2.4.3、instanceof 运算符
判断该对象是否是某一个类的实例
语法格式
boolean b = 对象A instanceof 类B; //判断 A对象是否是 B类的实例?如果是,返回true 复制代码
8.2.4.4、多态总结
面向接口编程,体现的就是多态,其好处:把实现类对象赋给接口类型变量,屏蔽了不同实现类之间的实现差异,从而可以做到通用编程
8.3、面试题
接口和抽象类的区别
- 接口中所有的方法隐含的都是抽象的,但是抽象类中可以同时包含抽象方法和普通方法以及静态常量
- 类可以实现很多个接口,但是只能继承一个抽象类
- 类如果要实现一个接口,那么他必须要实现接口声明的所有方法,但是类可以不实现抽象类中的所有方法,但是这个类必须是抽象类
- 接口中不存在构造方法,因为接口的成员变量都是static final变量,是在编译的时候就完成了初始化操作了,无需通过构造方法来进行初始化操作,而抽象类必须有构造方法
- 抽象类和接口的概念的区别:
- 抽象类是从一些类中抽取他们的共有的属性,方法的修饰符可以是public或者是protected以及缺省,抽象类注重对类本身的抽象,抽象方法没有方法体,仅仅是声明了该方法,让继承他的子类去实现
- 接口主要是对类的行为抽象,接口也可以有变量和方法,但是变量以及方法的修饰符必须是public static final(缺省时会默认加上)和public abstract(缺省时默认也是这个)