封装

什么是封装

面向对象三大特征之一

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

Java 面向对象(三)-LMLPHP

(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;
}
}

继承

Java 面向对象(三)-LMLPHP

上面三个类发现有共同的代码

Java 面向对象(三)-LMLPHP

我们可以用继承来解决上面的代码重复问题

Java 面向对象(三)-LMLPHP

父类:

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();
}
}

输出结果:

企鹅起飞
飞到天空了

继承内存分析

在类加载字节码时,会先判断有没有父类,如果有,会先把父类加载成字节码放到内存当中,然后再去把自己加载到内存当中。先加载父类,再加载自己。

就上面的例子分析

Java 面向对象(三)-LMLPHP

子类初始化过程

在创建子类对象之前,会先创建父类对象。调用子类构造器之前,在子类构造器中会先调用父类的构造器( 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);
}
}

输出结果:

狗啃骨头
猫吃鱼

分析:

Java 面向对象(三)-LMLPHP

当把不同的子类对象都当作父类类型来看待,可以屏蔽不同子类对象之间的实现差异,从而写出通用的代码达到通用编程,以适应需求的不断变化。

在这里使用了多态后,只需要写一个方法就能达到相同的功能

类的强制类型转换

子类对象可以声明为父类类型,父类对象不可以声明为子类类型

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(); // 汪汪汪
}
}
05-11 15:20
查看更多