封装
什么是封装
面向对象三大特征之一
1、 把对象的状态和行为看成一个统一的整体,将字段和方法放到一个类中。
2、 信息隐藏:把不需要让外界知道的信息隐藏起来。尽可能隐藏对象功能实现细节,向外界暴露方法,保证外界安全访问功能。
封装的好处
1、 保证数据的安全
2、 提高组件的复用性
现假设 提交表单提交,表单内容: 姓名、账号、邮箱、密码、验证码、是否同意协议。
没有用封装:
- 参数列表各个参数都要写,会很多;
- 调用时,不确定的参数,要给初始值。
public class Register {
static void commit(String name, String id, String email, String pwd, int checkCode, boolean isAgree) {
System.out.println("注册");
}
public static void main(String[] args) {
commit("zs", "", "", "", 5521, true);
}
}
封装后:
class User {
String name;
String id;
String email;
String pwd;
int checkCode;
boolean isAgree;
}
public class Register {
static void commit(User user) {
System.out.println("注册");
}
public static void main(String[] args) {
User user = new User();
user.name = "zs";
commit(user);
}
}
保证数据的安全:
- 把想要保护的字段用 private 修饰
- 内部提供方法进行设置,在设置方法当中判断设置的数据是否合法
- 提供get方法获取修饰的值
封装前:输入错误年龄
class User {
String name;
int age;
void show() {
System.out.println("我是" + name + ",年龄" + age);
}
}
public class Login {
public static void main(String[] args) {
User user = new User();
user.name = "zs";
user.age = -10;
user.show();
}
}
封装后:
class User {
String name;
private int age;
void show() {
System.out.println("我是" + name + ",年龄" + age);
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age>0) {
this.age = age;
} else {
System.out.println("输入数据不合理");
}
}
}
public class Login {
public static void main(String[] args) {
User user = new User();
user.name = "zs";
user.setAge(-10);
user.show();
}
}
访问修饰符
什么是访问修饰符
访问权限修饰符来规定在一个类里,能看到什么,能暴露什么。
访问修饰符
访问修饰符 | 说明 |
---|---|
private | 表示私有的,表示类访问权限,只能在本类中访问,离开本类之后,就不能直接访问 |
默认 | 表示包访问权限,访问者的包必须和当前定义类的包相同才能访问,没能继承 |
protected | 表示子类访问权限,同包中的可以访问,不同包不能访问,继承也可以访问 |
public | 表示全局的可以公共访问权限,使用了public修饰,则可以在当前项目中任何地方访问 |
访问权限表:
作用域 | 当前类 | 同一包中 | 子孙类 | 其它包 |
---|---|---|---|---|
private | 可以访问 | 不能访问 | 不能访问 | 不能访问 |
默认 | 可以访问 | 可以访问 | 不能访问 | 不能访问 |
protected | 可以访问 | 可以访问 | 可以访问 | 不能访问 |
public | 可以访问 | 可以访问 | 可以访问 | 可以访问 |
this 关键字
在一个方法当中,要给变量赋值,它会先到方法当中去找有没有该变量。
如果有,就给方法内部的变量赋值,不会往上再去找了,如果没有 ,就往它上一级去找。
public class User {
String name;
int age;
public User(String name, int age) {
name = name;
age = age;
}
public static void main(String[] args) {
User user = new User("zs", 10);
System.out.println(user.name);
System.out.println(user.age);
}
}
输出结果:
null
0
在构造器中,变量赋值给自己,都是构造器的参数,没有给对象当中的字段赋值
在方法当中变量前加上了this,就代表直接给对象当中的字段赋值
public class User {
String name;
int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public static void main(String[] args) {
User user = new User("zs", 10);
System.out.println(user.name);
System.out.println(user.age);
}
}
输出结果:
zs
10
this : "这个" ——当前正在使用对象的地址
public class User {
String name;
int age;
public User(String name, int age) {
this.name = name;
this.age = age;
System.out.println(this);
}
public static void main(String[] args) {
System.out.println("use1:");
User user1 = new User("zs", 10);
System.out.println(user1);
System.out.println("use2:");
User user2 = new User("ls", 20);
System.out.println(user2);
}
}
输出结果:
use1:
Test.User@70dea4e
Test.User@70dea4e
use2:
Test.User@5c647e05
Test.User@5c647e05
可以看到 this 和当前使用对象的地址是一样的。
this的使用:
(1)帮我们区分成员变量和局部变量的二异性, 必须得要使用 this
public User(String name, int age) {
this.name = name;
this.age = age;
}
(2)在同类当中 ,实例方法的调用,前面其实是有this,可以省略
public class User {
String name;
int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
void show() {
System.out.println(this.name);
}
void sayHello() {
this.show();
}
public static void main(String[] args) {
User user1 = new User("zs", 10);
user1.sayHello();
}
}
输出结果:zs
(3)可以把this做为参数传递
(4)可以当做返回值 返回
(5)static 不能和 this一起使用
(6)构造器的重载互调,this(); 此时的this代表的是构造器名,必须写到第一行。
public class User {
String name;
int age;
public User(String name) {
this.name = name;
}
public User(String name, int age) {
// 必须写在第一行。this代表的是当前构造器
this(name);
this.age = age;
}
}
继承
上面三个类发现有共同的代码
我们可以用继承来解决上面的代码重复问题
父类:
public class Person {
String name;
int age;
public void eat() {
}
}
子类:
extends 表示继承 。 后面是父类 前面是子类
public class Teacher extends Person {
String position;
public void teach() {
}
}
public class Student extends Person {
String sno;
public void study() {
}
}
public class Employee extends Person {
String hireDate;
public void work() {
}
}
什么是继承
从已有类中,派生出新的类,新的类中吸收已有类当中的状态和行为,并能扩展出新的能力。
Java继承是使用已有类作为基础,建立新的类。
继承是一种从一般到特殊的关系,是一种 “is a” 的关系,即子类是对父类的派生,是一种特殊的父类。
比如:狗是动物的一种特殊情况,狗属于动物;卡车是车的一种特殊情况,卡车属于车…
如何表示继承
基于某个父类对对象的定义加以拓展,在Java语言中,存在多个类的时候,我们使用 ”extends” 关键字来表示子类和父类之间的关系。
语法格式: 在定义子类的时候来表明自己需要拓展于哪一个父类。
public class 子类类名 extends 父类类名
{
编写自己特有的状态和行为
}
继承的作用
(1)解决代码重复问题
(2)真正的作用,表示出一个体系
先写好父类还是先写子类?
一般的,我们在开发工程中先编写多个自定义类。写完之后,发现多个类之间存在共同的代码,此时可以抽去出一个父类。
子类与父类的继承关系
子类继承父类之后,可以拥有父类的某一些状态和行为。
子类复用了父类的功能或状态。但并不是父类当中所有的内容,子类都可以直接使用。
子类可以使用父类当中的哪些成员
(1)如果父类中的成员使用public修饰,子类可以继承。
(2)如果父类中的成员使用protected修饰,子类可以继承,不同包也能继承。
(3)如果父类和子类在同一个包中,此时子类可以继承父类中默认的成员. 不同包不能继承默认成员
(4)如果父类中的成员使用private修饰,子类打死都继承不到。因为private只能在本类中访问。
(5)父类的构造器,子类也不能继承。因为构造器必须和当前的类名相同。
方法覆盖
子类扩展了父类,可以获得父类的部分方法和成员变量。可是当父类的某个方法不适合于子类本身的特征时,可以进行覆盖,重新定义父类当中的方法,我们称子类重新定义父类当中方法的过程为方法覆盖 (或方法重写)
方法覆盖原则
(1)子类方法必须得要和父类当中方法的方法签名相同(方法名+方法参数)
(2)子类方法的返回值类型是和父类方法的返回类型相同。
(3)子类方法的访问权限要比父类方法访问权限大或相等。不能比父类的还小。
判断方法是否为覆盖方法
判断是否是覆写方法的必杀技:@Override标签:若方法是覆写方法,在方法前或上贴上该标签, 编译通过,否则,编译出错。
只有方法存在覆盖的概念,字段没有覆盖。
什么时候要使用方法覆盖
当父类的某一个行为不符合子类具体的特征的时候,此时子类需要重新定义父类的方法,并重写方法体。
方法的重载和方法覆盖的区别
方法重载: Overload
方法重写: Override
本身二者一点关系都没有,仅仅只是因为名字很像。
方法重载: Overload
作用: 解决了同一个类中,相同功能的方法名不同的问题。 既然是相同的功能,那么方法的名字就应该相同。
规则: 同类中,方法名相同,方法参数列表不同。
方法重载: Overload
作用: 解决子类继承父类之后,可能父类的某一个方法不满足子类的具体特征。此时需要重新在子类中定义该方法,并重写方法体。
规则:父类和子类的方法签名是相同的,即方法名相同,方法参数列表也相同。
super 关键字
在子类中的某一个方法中,去调用父类被覆盖的方法。此时就要使用super关键字
this:当前对象,谁调用this所在的方法,this就是哪一个对象。
super:当前对象的父类对象。
父类:
public class Bird {
void fly() {
System.out.println("飞到天空了");
}
}
子类:
public class Penguin extends Bird{
@Override
void fly() {
System.out.println("企鹅起飞");
}
void test() {
this.fly(); // 代表是当前对象, 到自己类当中去找方法
super.fly(); // 代表是父类对象, 到父类当中去找指定的方法
}
}
main方法:
class Test {
public static void main(String[] args) {
Penguin p = new Penguin();
p.test();
}
}
输出结果:
企鹅起飞
飞到天空了
继承内存分析
在类加载字节码时,会先判断有没有父类,如果有,会先把父类加载成字节码放到内存当中,然后再去把自己加载到内存当中。先加载父类,再加载自己。
就上面的例子分析
子类初始化过程
在创建子类对象之前,会先创建父类对象。调用子类构造器之前,在子类构造器中会先调用父类的构造器( super(); ),默认调用的是父类无参数构造器。
(1)如果父类不存在可以被子类访问的构造器,则不能存在子类。
(2)如果父类没有提供无参数构造器,此时子类必须显示通过super语句去调用父类带参数的构造器。
必须先有父类对象,而后才能有子类对象。必须先调用父类构造器,而后再调用子类构造器。
调用父类构造这句话,必须作为子类构造器的第一句话。
Super关键字使用的场景
(1)可以使用 super 解决子类隐藏了父类的字段情况,该情况我们一般不讨论,因为破坏封装。
(2)在子类方法中,调用父类被覆盖的方法
(3)在子类构造器中,调用父类构造器,此时必须使用super语句: super([实参])。和 this() 一样,super() 构造方法必须放在第一行。但不能和 this() 一起使用。
隐藏
(1)满足继承的访问权限下,隐藏父类静态方法:若子类定义的静态方法的签名和父类中的静态方法签名相同,那么此时就是隐藏父类方法。注意:仅仅是静态方法,子类存在和父类一模一样的静态方法。
(2)满足继承的访问权限下,隐藏父类字段:若子类中定义的字段和父类中的字段名相同(不管类型),此时就是隐藏父类字段,此时只能通过 super 访问被隐藏的字段。
(3)隐藏本类字段:若本类中某局部变量名和字段名相同,此时就是隐藏本类字段,此时只能通过 this 访问被隐藏的字段。
注意:static不能和super以及this共存!
Object 类
Object类是Java语言的根类,要么是一个类的直接父类,要么就是一个类的间接父类。
class 类名 { }
其实等价于
class 类名 extends Object { }
Object本身指对象的意思,我们发现所有的对象都具有某一些共同的行为,所以,我们抽象出一个类:Object,表示对象类,其他都会继承于Object类,也就拥有Object类中的方法
Object 类常用方法
(1)取得对象信息的方法:toString()
该方法在打印对象时被调用,将对象信息变为字符串返回,默认输出对象地址。
(2)对象相等判断方法:equals()
该方法用于比较对象是否相等,而且此方法必须被重写。默认equals()方法比较的是两个对象的地址,想要比较对象值是否相等,就要重写
(3)对象签名:hashCode()
该方法用来返回其所在对象的物理地址(哈希码值),常会和equals方法同时重写,确保相等的两个对象拥有相等的.hashCode。
(4)返回运行时类:getClass
返回此 Object的运行时类。
多态
准备四个类:
class Person {
void feedAnimal(Animal animal) {
animal.eat();
}
}
class Animal {
void eat() {
System.out.println("动物吃东西");
}
}
class Dog extends Animal {
void eat() {
System.out.println("狗啃骨头");
}
}
class Cat extends Animal {
void eat() {
System.out.println("猫吃鱼");
}
}
什么是多态
既然子类是一种特殊的父类,那么我们可不可以认为,狗对象/猫对象就是动物类型的对象。
Animal dog = new Dog();
一个对象有多种形态,就称它是多态(一个表示自己的类,一个表示自己父类的类型)
多态运行时,还是自己 new 的对象本身类。可以通过 对象.getClass() 来查看对象在运行时是那个类
多态的特点
把子类对象赋给父类对象,在运行时期会表现出具体的子类特征,调用子类的方法。
多态运行时表现出来的还是子类的特征 (编译时,看左边,运行时,看右边)
public class Test {
public static void main(String[] args) {
Animal dog = new Dog();
dog.eat();
Animal cat = new Cat();
cat.eat();
}
}
输出结果:
狗啃骨头
猫吃鱼
多态的作用
准备饲养员类 Person 喂动物吃
class Person {
void feedDog(Dog dog) {
dog.eat();
}
void feedCat(Cat cat) {
cat.eat();
}
}
如果有非常多的动物,每个动物都要写一个喂养的方法太麻烦。这时候就用到了多态。
class Person {
void feedAnimal(Animal animal) {
animal.eat();
}
}
测试下:
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
Cat cat = new Cat();
Person person = new Person();
person.feedAnimal(dog);
person.feedAnimal(cat);
}
}
输出结果:
狗啃骨头
猫吃鱼
分析:
当把不同的子类对象都当作父类类型来看待,可以屏蔽不同子类对象之间的实现差异,从而写出通用的代码达到通用编程,以适应需求的不断变化。
在这里使用了多态后,只需要写一个方法就能达到相同的功能
类的强制类型转换
子类对象可以声明为父类类型,父类对象不可以声明为子类类型
Animal animal = new Dog(); // 对
Dog dog = new Animal(); // 错
在子类对象声明为父类类型后,可以通过强制转型,转型回来
Animal animal = new Dog();
Dog dog = (Dog) animal;
而父类对象声明为父类类型之后,并不能执行强制类型转化
Animal animal = new Animal();
Dog dog = (Dog) animal; // 错
因为在子类对象声明为父类类型后,其实对象的真实意义还是子类对象。
类的类型转换:
(1)向上转型:将一个子类的引用赋给一个超类变量,编译器是允许的,不用进行强制类型转换。
格式: 超类 超类变量 = new 子类();
(2)向下转型:但是将一个超类的引用赋给 一个子类变量,必须进行强制类型转换,这样才能够通过运行时的检查
格式: 子类 子类对象变量名 = (子类) 父类对象引用
instanceof 关键字
判断一个对象是否是指定的类, 如果是,返回 true; 如果不是, 就返回 false。
格式:
boolean result = Object instanceof class
Object: 任意对象表达式
class: 任意已定义的对象类
void feedAnimal(Animal anim) {
if (anim instanceof Dog) {
Dog dog = (Dog) anim;
dog.doWork();
} else if (anim instanceof Cat) {
Cat cat = (Cat) anim;
cat.watch();
}
}
字段不存在多态
字段不存在多态,字段前面对象是什么类型,就调用谁的。 在编译的时候,就已经确定要去调用谁的。
class Animal {
String name = "动物";
void eat() {
System.out.println("动物吃东西");
}
}
class Dog extends Animal {
String name = "狗";
void eat() {
System.out.println("狗啃骨头");
}
}
public class Test {
public static void main(String[] args) {
// 把子类对象赋值给父类类型
// 运行时,表现的还是子类的特征,去调用子类的方法
Animal dog = new Dog();
dog.eat();
// 字段不存大多态。
// 字段前面对象是什么类型,就调用谁的。 在编译的时候,就已经确定要去调用谁的
System.out.println(dog.name);
}
}
输出结果:
狗啃骨头
动物
多态的3个必要条件:
(1)继承
(2)重写
(3)父类引用指向子类对象
多态总结
父类类型的引用可以调用父类中定义的所有属性和方法,而对于子类中定义而父类中没有的方法,它是无可奈何的; 同时,父类中的一个方法只有在父类中定义而在子类中没有重写的情况下,才可以被父类类型的引用调用; 对于父类中定义的方法,如果子类中重写了该方法,那么父类类型的引用将会调用子类中的这个方法,这就是动态连接。也可以叫做动态绑定。
动态绑定是指”在执行期间(而非编译期间)“判断所引用对象的实际类型,根据实际的类型调用其相应的方法。
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果有,再去调用子类的同名方法;如果没有,则编译错误。
对于多态,可以总结它为:
(1)使用父类类型的引用指向子类的对象;
(2)该引用只能调用父类中定义的方法和变量;
(3)如果子类中重写了父类中的方法,那么在调用方法的时候,将会调用子类中的方法;(动态连接、动态调用)
多态和继承的对比
class Animal {
int age = 3;
String name = "动物";
int n = 100;
void eat() {
System.out.println("动物吃东西");
}
void work() {
System.out.println("动物工作");
}
}
class Dog extends Animal {
int age = 5;
String name = "狗";
int m = 20;
void eat() {
System.out.println("狗啃骨头");
}
void shout() {
System.out.println("汪汪汪");
}
}
多态:
public class Test {
public static void main(String[] args) {
System.out.println("多态:父类引用子类对象");
Animal dog = new Dog();
/* 字段不存在多态 */
System.out.println(dog.age); // 3
System.out.println(dog.name); // 动物
System.out.println(((Dog) dog).age); // 5
System.out.println(((Dog) dog).name); // 狗
System.out.println(dog.n); // 100
/* System.out.println(dog.m); 父类访问不到子类,要强制类型转换 */
System.out.println(((Dog) dog).m); // 20
dog.eat(); // 狗啃骨头
dog.work(); // 动物工作
/* dog.shout(); 父类访问不到子类,要强制类型转换 */
((Dog) dog).shout(); // 汪汪汪
}
}
继承:
public class Test {
public static void main(String[] args) {
System.out.println("继承");
Dog dog2 = new Dog();
System.out.println(dog2.age); // 5
System.out.println(dog2.name); // 狗
System.out.println(((Animal) dog2).age); // 3
System.out.println(((Animal) dog2).name); // 动物
System.out.println(dog2.n); // 100
System.out.println(dog2.m); // 20
dog2.eat(); // 狗啃骨头
((Animal) dog2).eat(); // 狗啃骨头
dog2.work(); // 动物工作
dog2.shout(); // 汪汪汪
}
}