封装、继承、多态
1、private关键字
private是一个权限修饰符,可以用来修饰成员属性和成员方法。
问题1:为什么要对属性进行封装?好处是什么?
看一个例子来进行说明:
public class Student {
// 姓名(name)
String name;
// 年龄(age)
int age;
// 类的行为 ---> 成员方法
public void show(){
System.out.println("姓名:"+name+",年龄:"+age);
}
}
public class Demo1Student {
public static void main(String[] args) {
// 创建Student对象
Student stu = new Student();
// 给stu对象的属性赋值
stu.name = "guang";
// stu.age = 18;
stu.age = -18;
// 调用show()方法
stu.show();// 姓名:guang,年龄:-18
}
}
可以看到,直接使用对象来进行赋值的时候,发现最终的年龄是一个负数。显然这个是不合理的,所以利用对象来进行直接赋值是不合适的。那么只好使用方法来对属性来进行赋值,在方法中对属性进行赋值并进行判断,如下:
public void setAge(int age){
if(age<=0){
sout("参数赋值不合理");
}
this.age = age;
}
private是权限最小的修饰符,private修饰的成员变量和成员方法只能够在本类中进行使用,在本类外的地方是无法进行使用的。
public是权限最大的修饰符,public修饰的成员变量和成员方法可以在一个项目中的任何地方可以使用到。
比如说,上面的例子中,要想在Demo1Student中使用Student类,可以创建对象,但是不能够直接使用"对象名.属性"来完成赋值,要是想赋值的话,那么只能通过public修饰的方法来进行赋值操作。
说明:
1、使用private关键字修饰的原因是为了不让在类外直接操作类中的属性;
2、在以后的工作当中在对对象的属性进行赋值的时候,都是使用方法来对对象进行赋值的,而不是直接使用”对象.属性名“来进行赋值的。
2、this关键字
在上面的setAge方法中,使用了this关键字,下面来解释下为什么要使用this关键字。
public void setAge(int age){
if(age<=0){
sout("参数赋值不合理");
}
this.age = age;
}
如果不适用this关键字,那么将会是下面的样子
public void setAge(int age){
if(age<=0){
sout("参数赋值不合理");
}
age = age;
}
对于这个方法来说,int age定义的是局部变量,那么在方法中进行赋值的还会是局部变量,根据上一章描述的,方法中使用的还是局部变量,所以赋值完成之后,不会对成员变量产生任何的影响,只会对局部变量产生影响。
因为局部变量是在栈内存中的,方法执行完成之后,就会出栈;成员变量是在堆内存中的;这样的赋值将会毫无意义
所以最终成员变量age还会是原来的值,没有被赋上新的值。
而this代表的是当前对象在堆内存中的地址,代表的也就是当前对象,this.age代表的是当前类的实例的对象的age属性
this.age表示的是要给堆内存中的age成员变量来进行赋值,值是局部变量的值。所以这句话的意思就是说将局部变量的值赋给成员变量。成员变量age最终将会被赋予新的值。
注意:this关键字常常是会被省略的。那么什么情况下不会被省略?什么情况下不会被省略?
1、当局部变量和成员变量一样的时候,用来当做区分哪个是成员变量,哪个是局部变量,需要加上this关键字;
2、在一个类中,出现了方法调用的情况,常常省略了this关键字;
3、构造函数
上一章中其实用到了构造函数,构造方法的目的是为了创建对象和初始化成员变量的。上一章中使用到的是:
Student student = new Student();
// 1、new是创建对象,在内存中分配空间;
// 2、为对象开辟的空间中的成员属性初始化值,最原始的值。
如果是引用类型,值默认为null
基本类型中,数字类型的为0,boolean为false。
格式:
public class Student {
String name;
int age;
// 默认的构造函数,如果不写其他的构造函数,那么默认的会有
public Student() {
}
// 手动设置的构造函数。设置的是只有一个属性参与的
public Student(String name) {
this.name = name;
}
// 手动设置的构造函数。设置的是只有一个属性参与的
public Student(int age) {
this.age = age;
}
// 手动设置的构造函数。设置的是两个属性都参与的
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
利用测试类来进行测试:
public static void main(String[] args) {
Student student = new Student();
System.out.println(student);
}
控制台输出:
Student{name='null', age=0}
输出的是构造函数来进行初始化的值
4、封装
封装、继承、多态是面向对象的三大特征,都是对现实生活的模拟,虽然无法具体描述出来,但是可以抽象的模拟出来对应的数据。
在现实生活中,成员属性都是隐藏在对象内部的,外界是无法来进行操作的。比如说人的年龄,是每个人每个个体具备的,没有人可以来进行改变的;人的血型是无法改变的,也是每个对象具备的;虽然说每个人都有,但是外界是无法干预的。
所以编程语言只是对现实世界的模拟。
4.1、封装的好处
1、将对成员变量的操作交给方法来进行操作,提高安全性。比如说上面的给age进行赋值的时候先进行判断;
2、通过方法来进行操作成员变量,可以达到重复利用的目的。方法是可以重复进行操作的,并非是一次性的。
额外说明:
在一个类中,封装不仅仅体现在方法中,还可以体现在类中,因为类也是对一组或者是一群类似的事物的封装,使其具备了一组、一群事物共同具备的特征。
4.2、标准的javabean
学习到了封装之后,就可以写出来一个标准的Javabean了。
属性都用private关键字来进行修饰,方法都使用public关键字来进行修饰。
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name) {
this.name = name;
}
public Student(int age) {
this.age = age;
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
5、继承
前面也说过,编程语言是对现实世界中的模拟。在现实生活中父子、母子关系、师徒关系,存在着血缘和技能的传承,正是有了这种传承,人类文明的传承才得以慢慢积累,最终变得丰富多彩。在java中,这种传承叫做继承。
但是编程语言中无法具体的描述出人类世界这种复杂的关系,只能退而求其次,简单点来进行描述,只描述继承的个体之间关系。
5.1、概述
儿子继承父亲/母亲,儿子叫做子类,父亲/母亲被称之为父类。因为在编程世界中为了简单区分,把父亲/母亲都叫做父类。
既然是继承,那么子类有了父类的“影子”。现实生活中常说的“xxx跟你爸/妈长的真像”,这里就是“影子”。那么在java中这个“影子”指的就是继承父类的“成员变量和成员方法”。
子类继承了父类的成员变量和成员方法了之后,那么子类就不需要再去书写同样的代码了。不仅仅提升了代码的复用性,还省去了书写大量相同代码的麻烦。
需要注意的是java中所有的类都直接或者是间接的继承了Object类。
5.2、继承格式
public class A{
String name;
int age;
public void say(){
sout("hello");
}
}
public class B extends A{
}
这里描述的是类B继承类A,那么类B中就有了name和age属性、say方法了。
在java中,继承是单继承的,也就是说类B只能继承A,不能够同时继承类C或者是其他的类了。
5.3、继承后成员的访问原则
5.3.1 继承后构造函数的访问规则
- 子类不能够继承父类的构造函数
public class Fu{
int age;
String name;
public Fu(){
}
public Fu(String name,int age){
}
}
public class Zi extends Fu{
}
public class Demo {
public static void main(String[] args) {
Zi z = new Zi("刘德华", 17);//编译错误,Zi类没有全参构造。没有将父类的全参构造继承下来
}
}
- 子类在创建对象的时候需要访问父类的构造函数
public class Fu{
int age;
String name;
public Fu(){
sout("name的值为:"+name);
}
public Fu(String name,int age){
}
}
public class Zi extends Fu{
}
public class Demo {
public static void main(String[] args) {
Zi z = new Zi();// name的值是:null
}
}
5.3.2 继承后私有成员后的访问规则
父类的私有成员可以被子类继承,但是子类却无法直接访问父类中的私有成员
public class Fu{
private int num = 100;//私有成员,只能在父类内部使用。
private void method(){
System.out.println("私有成员方法");
}
}
public class Zi extends Fu{
}
public class Demo {
public static void main(String[] args) {
Zi z = new Zi();
System.out.println(z.num);// 编译错误
z.method();// 编译错误
}
}
5.3.3 继承后非私有成员后的访问规则
通过子类访问非私有成员时,先在子类中进行查找,如果子类中没有扎到,那么再去父类中进行查找;知道找到Object类去。
public class Fu{
int money = 100;
public void method(){
System.out.println("Fu 类中的成员方法method");
}
}
public class Zi extends Fu{
int money = 1;
public void method(){
System.out.println("Zi 类中的成员方法method");
}
}
public class Demo{
public static void main(String[] args){
Zi z = new Zi();
System.out.println(z.money);//1
z.method();// Zi 类中的成员方法method
}
}
5.3.4 总结
1、父类的构造函数是无法被继承的;
2、父类中的private修饰的属性是可以被继承的,但是在父类外是无法被访问到的;
3、子类继承了父类非私有的成员变量,使用的时候先从子类中查找,没有再从父类中进行查找;
6、继承后的方法重写
6.1、概述
子类继承了父类的成员之后,有时候根据自己的需要或者其他情况,觉得父类的方法难以适用或者说是觉得父类的方法不够好,自己需要重新来进行书写。
6.2、格式:
正确的方法重写的格式是:子类需要重写的方法需要和父类的方法声明完全一致,方法体中的可以不同。
如下面案例:
public class Fu{
public void eat(){
System.out.println("我吃牛肉炖土豆...");
}
}
public class Zi extends Fu{
@Override
public void eat(){//方法重写
System.out.println("我吃红烧狮子头...");
}
}
可以看到子类的eat方法上,有一个@Override注解,这就说明了子类中的方法重写成功了。
方法重写需要的注意地方:
1、方法重写发生在子父类之间;
2、子类和父类的方法声明是一模一样的,包括参数列表、参数顺序以及方法名称、返回值类型;
3、子类重写后的方法权限要大于等于父类的方法权限修饰符;【这个是以后会遇到的】
7、抽象类和接口
7.1、抽象类概述
抽象类是类的一种延伸,类分为抽象类和非抽象类。抽象类是一种类数据类型。但是不能够利用抽象类不可以用来创建对象。
类声明上有无abstract关键字 | 方法中有无abstract关键字 | 是否有方法体 | |
---|---|---|---|
抽象类 | 有 | 如果有,一定是抽象类;如果没有,不一定不是抽象类 | 抽象类中的方法没有方法体;但是没有方法体的不一定是抽象类(Object类的native方法) |
非抽象类 | 无 | 没有 | 不一定有(Object类中的native方法) |
抽象类在java中的作用是作为子类的共性抽取,什么意思?看下面的例子
public abstract class Animal{
String name;
int age;
public abstract void grow();
}
----------------------------------
public class Dog{
String name;
int age;
public void grow(){
sout("茁壮成长");
}
}
----------------------------------
public class Cat{
String name;
int age;
public void grow(){
sout("慢慢就长大了");
}
}
可以发现上面的案例中Dog和Cat类都存在着相似的代码,那么将其共同的代码抽取出来,用继承来进行实现。如下所示
public abstract class Animal{
String name;
int age;
public abstract void grow();
}
----------------------------------
public class Dog extends Animal{
@Override
public void grow(){
sout("茁壮成长");
}
}
----------------------------------
public class Cat extends Animal{
@Override
public void grow(){
sout("慢慢就长大了");
}
}
通过上面的对比,可以发现以下优势:
1、代码量少了;
2、使用继承的方式,可以根据自己的需求来对方法进行重写了;
java中的Object类,是所有类共同的老祖宗。所有的类都会直接或者是间接的继承Object类,继承它的成员变量或者是成员方法。
方便子类来根据实际需求来进行扩展。
具体的可以参见Object类和自己日常写的java类。
7.2、接口概述
java中的接口是作为一种规范、标准的,是已经确定下来的方案。比如说国家发布了一套方案,全国各地需要根据自己实际的情况来进行执行。接口也是属于类数据类型的,但是不能够利用抽象类不可以用来创建对象。
7.2.1、接口的定义:
public interface 接口名称{
......
}
7.2.2、接口可以存在哪些东西
接口中可以存在成员变量和成员方法
成员变量:
全部都是常量:也就是public static final 数据类型 变量名;
但是static和final关键字通常是省略的
成员方法:
在jdk8中接口中可以存在的方法如下:
1、抽象方法(用abstract关键字来进行修饰的);
2、默认方法和静态方法(不常用)
7.3、抽象类和接口的对比
抽象类和接口天生就是来当“爹”的,需要类通过继承和实现来进行实现。
是否是数据类型 | 是否可以new对象 | 是否支持多继承 | 是否可以有非抽象方法 | 实现方式关键字 | |
---|---|---|---|---|---|
抽象 | 是 | 否 | 只支持单继承 | 可以有 | extends |
接口 | 是 | 否 | 支持多继承 | jdk8中可以有了 | implement |
8、多态
从字面意思来进行理解,多种姿态、多种形式。比如:你既是爸爸的儿子,又是你爷爷的孙子,又是你姐姐的弟弟,又是你妹妹的哥哥。
但是无论怎么说,这个"你"在不同的“人”中充当的角色是不同的。
java中将这种情况下的“你”称之为多态。
8.1、多态的格式:
父类数据类型 变量名字 = new 子类数据类型();
- 多态时成员变量的访问特点
- 编译看左边,运行看左边
- 简而言之:多态的情况下,访问的是父类的成员变量
- 编译看左边,运行看左边
- 多态时成员方法的访问特点
- 非静态方法:编译看左边,运行看右边
- 简而言之:编译的时候去父类中查找方法,运行的时候去子类中查找方法来执行
- 静态方法:编译看左边,运行看左边(很少用)
- 简而言之:编译的时候去父类中查找方法,运行的时候去父类中查找方法来执行
- 非静态方法:编译看左边,运行看右边