文章目录
前言
面向对象编程的三大特性,封装,继承,多态
一、包
1.包的存在是为了解决类同名的问题,此时一个类的全名称就是包名.类名
2.导入一个包中的某个具体的类,使用import,我们常说的导包,其实导入的是一个包中的具体的某个类
3.此时若还想导入这个包中的其他类,可以不用一行行的写,直接使用import.java.util.,此时将整个包中的所有类按需加载,导入的还是一个类,而不是包
4.但是不建议使用import.,因为可能会产生歧义,此时Data这个类在java.util 和java.sql包下都有,此时就会产生歧义
5.当在程序中使用到了两个相同的类的时候,有两种解决办法
(1)使用类的全名称
(2)使用import明确指定导入的是哪个包下的哪个类
6.静态导入,import static可以导入包中的某个类的静态方法和静态属性
7.常见的系统包(面试中可能会问到)
(1)java.lang:JDK中的基础类,System,String,Object都在这个包下,JDK1.1之后,这个包下的所有类都是自动导入
(2)java.lang.reflect:反射开发包
(3)java.util:工具包(集合类都在这个包下,Arrays,LinkedList,HashMap)
(4)java.io:I/O开发包,文件的读取和写入
(5)java.net:网络编程开发包
(6)java.sql:数据库开发用到的包
二、封装
在我们写代码的时候经常会涉及两种角色: 类的实现者和类的调用者.
封装的本质就是让类的调用者不必太多的了解类的实现者是如何实现类的, 只要知道如何使用类就行了.
这样就降低了类使用者的学习和使用成本, 从而降低了复杂程度。
主要作用:保护性和易用性
1.private实现封装
private实现属性和方法的封装只是封装的一种
类比现实生活:
1.保护性:对于银行卡这个类来说,银行卡的卡号,余额,密码这三个属性直接暴露在外部,直接写在卡上,这样是不合理的,也是不安全的,不能让这些属性通过对象直接调用,这种情况就要用到封装。
2.易用性:对于汽车这个类来说,汽车真正发动起来是需要许多属性相互配合的,这些属性对于用户来说是不可见的,也是不关注的,用户只需要按下一键启动(方法),方法的内部把这些属性进行配合。
1.1权限修饰符
在Java中,所谓的权限修饰符,指的是,你修饰的属性,方法,类到底可见的范围有多大,一共有四大访问修饰符,可见范围由小到大依次为:
private < default(不需要写这个关键字) < protected < public,这篇我们主要看public和private
1.1.1public
public是公共的意思,被public修饰的东西,在当前程序(项目)中都是可见的,都是可以使用的
class Person {
public String name = "张三";
public int age = 18;
}
class Test {
public static void main(String[] args) {
Person person = new Person();
System.out.println("我叫" + person.name + ", 今年" + person.age + "岁");
}
}
// 执行结果
我叫张三, 今年18
1.1.2private
private额能否修饰一个类(外部类)?
答案是否定的,因为类定义出来就是为了产生对象给外部使用的,假设用private修饰了一个类,那么这个类定义之后,外部根本都不可见,那么这个类就失去了意义。
private是私有的的意思,被private修饰的方法和属性,只有在当前类的内部是可见的,出了类的{},就对外完全隐藏了,外部不知道有其存在。
class Person {
private String name = "张三";
private int age = 18;
public void show() {
System.out.println("我叫" + name + ", 今年" + age + "岁");
}
}
class Test {
public static void main(String[] args) {
Person person = new Person();
person.age = 20;
person.show();
}
}
// 编译出错
Test.java:13: 错误: age可以在Person中访问private
person.age = 20;
^
1 个错误
阿里编码规范:
Java类中所有的成员变量一律使用private封装,并且根据属性的实际情况对外提供getter和setter方法。
2.getter和setter方法
当我们使用 private 来修饰字段的时候, 就无法直接使用这个字段了,此时如果需要获取或者修改这个 private 属性, 就需要使用 getter / setter 方法.
代码如下(示例):
class Person {
private String name;//实例成员变量
private int age;
public void setName(String name){
//name = name;//不能这样写
this.name = name;//this引用,表示调用该方法的对象
}
public String getName(){
return name;
}
public void show(){
System.out.println("name: "+name+" age: "+age);
}
}
public static void main(String[] args) {
Person person = new Person();
person.setName("caocao");
String name = person.getName();
System.out.println(name);
person.show();
}
// 运行结果
caocao
name: caocao age: 0
注意事项
1.getName 即为 getter 方法, 表示获取这个成员的值.
2.setName 即为 setter 方法, 表示设置这个成员的值
3.当set方法的形参名字和类中的成员属性的名字一样的时候,如果不使用this, 相当于自赋值. this 表示当前实例
的引用.
4.不是所有的字段都一定要提供 setter / getter 方法, 而是要根据实际情况决定提供哪种方法.
5.在 IDEA 中可以使用 alt + insert (或者 alt + F12) 快速生成 setter / getter 方法.
三、继承
1.认识继承
首先我们看三段代码
我们可以看到上面三个类的代码完全一样,此时代码冗余度太高,
Dog is an Animal
Cat is an Animal
当类和类之间满足 is a关系,一定是存在继承关系,天然的继承关系例如
Duck is an Animal
Person is an Animal
当一个类继承了另一个类,另一个类中的所有的属性和方法子类都天然的具备了
在Java中使用extends表示类的继承关系
不能为了省略代码而使用继承,必须满足is a 关系才能使用继承
2.继承的规则
1.要能使用继承,前提是满足is a 关系
2.一个子类只能使用extends继承一个父类(单继承)
举个浅显易懂的例子,你只能有一个父亲,但是你的父亲可以有多个儿子女儿,你可以有多个兄弟姐妹
虽然不能多重继承,但是可以多层继承
3.子类会继承父类的所有属性和方法
(1)显示继承:public属性和方法可以直接使用
(2)隐式继承:private属性和方法,子类也继承了,但是不能直接使用,需要使用父类提供的方法才能够使用
在举个浅显易懂的例子,在现实生活中,你继承于你的父亲,你继承了你父亲的所有财产,但是你父亲的私房钱就是一个private修饰的属性,虽然你也继承l,但是你没办法直接使用,只能你父亲告诉你放在哪里才能够使用,这也就是父类提供的方法
(3)静态属性和方法是归于某个类所有的,当一个类继承了另一个类,肯定继承了所有的静态属性和方法,但是能否直接使用,还是得看权限是否是public
3.关于protected访问权限
protected是继承访问权限,它的作用域是,在不同包的子类中是可见的,同包中所有类都是可见的
(1)此时Test不是Animal的子类,而且不在一个包中,所以无法访问
(2)此时Person类继承了Animal类,所以protected修饰的属性,在当前类中可见
4.继承的原则
1.产生子类对象
要产生一个子类的对象,默认先产生父类对象,当产生子类对象调用子类的无参构造时,先默认调用父类的构造方法产生父类对象,然后才会执行子类的构造方法
2.练习
写出下列的输出结果
结果
1.静态块比主方法先执行,而父类的静态块又比子类的静态块先执行
2.然后执行主方法
3.在产生子类对象的时候,先要产生父类对象,在产生对象的时候,构造块优先于构造方法,所以2 1 5 4 就是产生一个子类对象,但是题中产生了两个子类对象,所以要执行两遍
4.最后主方法结束
3.super关键字
(1)修饰属性,表示直接从直接父类中寻找同名属性,若不存在再向上搜寻
(2)修饰方法,表示直接从父类中寻找方法
(1)修饰属性
当有继承关系时,this首先在当前类中寻找同名属性,当前类中没有找到,则继续向上搜寻父类中是否包含同名属性。
但是如果此时当前类中和父类中都含有同名属性,此时就想访问父类中的属性,我们就可以使用super关键字,但是如果父类中的属性被private修饰,那就不能访问了,因为没有权限
若此时父类还有父类,并且使用super在直接父类中没有找到同名属性,会一直向上搜寻,知道碰到第一个同名属性,不管是否被private修饰,都不会再向上搜寻了,被private修饰之后只是无权访问;而this是先从当前类中寻找同名属性,若不存在再向上搜寻
(2)修饰构造方法
在产生一个子类对象时,默认先要产生父类对象,不管有多少层继承,都要先产生所有的祖类对象;其实是因为子类的构造方法首行隐式默认使用了super()调用了父类的无参构造,可写可不写
但是当父类中产生了有参构造之后,父类中默认的无参构造就不会再产生了
此时就得在子类的构造方法首行显示的使用super调用父类的有参构造,不能省略了
super()调用父类的构造方法,必须放在子类的构造方法的首行,this调用构造方法,也必须放在首行,所以在一个构造方法中无法显示的使用super()和this()同时出现,但是一个显示一个隐式就不会出错
(3)修饰普通方法
super修饰方法和修饰属性一样,直接从父类中找同名方法
super和this的区别就是super不能指代父类的对象引用,必须使用’ . '来访问父类的属性或者方法
4.final关键字
(1)修饰属性,表示属性的值不能改变,常量
(2)修饰类,表示这个类无法被继承
final class Person ---- >Person无法被继承
四、多态
多态的概念:一个引用可以表现出多种行为或者特性
1.向上转型
最大的意义在于参数统一化,降低使用者的使用难度
1.向上转型产生对象的方式:
我们最常见的产生对象的方式是:
类名称 类引用 = new 该类对象();
Dog dog = new Dog();
但此时Animal是Dog的父类,我们可以这样产生对象:
父类名称 父类引用 = new 子类对象();
Animal animal = new Dog();
此时父类引用指向了一个子类对象,我们把这种操作叫做向上转型,所以向上转型一定发生在有继承关系的类之间
此时有一个三层的继承关系,产生对象的方式:
Animal animal = new animal();
Bird bird = new bird();
Duck duck = new Duck();
向上转型产生对象的方式:
只要是一条继承树上的子类,都可以向上转型,变为祖类引用
Aniaml animal1 = new Bird();
Bird bird = new Duck();
Animal animal2 = new Duck();
2.向上转型会给我们带来许多方便
如果没有向上转型,对于类的实现者和类的使用者都很麻烦
既然子类是天然的父类,那么就可以让父类指代所有的子类
同一个引用(变量名称),同一个方法名称,根据不同的对象表现出不同的行为 ---- > 多态
方法重载(overload):发生在同一个类中,定义了若干个方法名称相同,参数列表不同的一组方法
方法重写(override):发生在有继承关系的类之间,子类定义了和父类除了权限不同,其他的都相同的方法,子类的权限必须大于等于父类的权限(返回值向上转型的返回值除外)
在产生一个对象的时候,到底调用的是谁的方法
Animal animal = new Dog();
不用去看前半部分,看new的是谁的对象,若这个类重写了父类的相关方法,那么一定调用的是重写后的方法,若该类没有重写相关方法,则向上搜寻,碰到第一个父类重写了相关方法,就调用该方法,就近原则
父类中使用private修饰方法,子类不能重写父类的方法,Java中有一个注解@override,使用这个注解写在重写方法之前,可以帮忙校验方法重写是否符合规则
方法重写不能重写static方法:
多态的本质就是根据不同的“对象”表现出不同的行为,而static和对象无关,所有不能重写static方法,方法重写只发生在普通方法中。
方法重写和方法重载的区别
向上转型发生的时机
1.方法传参 – 使用的最多
2.方法返回值
3.引用赋值的时候
重写 + 构造方法练习
此时有两个类,D是B的子类
1.产生一个子类对象,首先默认调用父类无参构造,父类的无参构造中调用了fun()方法
2.但是此时子类中覆写了fun()方法,直接跳到子类的fun()方法中
3.此时还在父类的构造方法中,当父类的构造方法执行完了才执行子类的构造方法,还没有执行子类的构造方法,所以num还是默认值
4.输出的是:D.fun(),num = 0
2.向下转型
在向上转型的基础上
Animal animal = new Dog();
假设此时子类中有一个扩展方法,是子类中独有的方法,父类中不具备,此时就不能通过父类引用调用这个方法
总结:
类名称 引用名称 = new 类实例();
引用名称 . 方法名称();
能通过“ . ”操作符访问哪些方法是等号前面的类说了算,能访问的方法必须在前面的类中定义过了
至于这个方法最后表现的是哪个类实现的样子,看new后面的实例是通过哪个类产生的,该类是否覆写了相关方法
Animal animal = new Dog();
animal . play();
animal这个引用,本质上是个Dog,只是批了一层Animal的外衣,此时只能调用Animal中定义的方法
若此时就想调用子类中扩展的独有的方法,那就脱掉这层外衣,还原为子类引用,这种操作就是向下转型
子类名称 子类引用 = (子类名称) 父类引用
Dog dog = (Dog) animal;将一个父类引用强制转换为子类引用,将animal引用向下转型还原为dog类型,此时就可以通过dog引用来调用子类中的独有方法
要发生向下转型,首先得发生向上转型,两个毫无关系的类之间是无法强转的,强制将两个毫无关系的类型进行强制类型转换,会出现类型转换异常;在发生向下转型是存在类型转换异常的风险,可以使用instanceof关键字来规避这个风险
引用名称 instanceof 类名称 - - - > 返回一个布尔值,表示该引用指向的本质是否是该类的对象
当要发生向下转型的时候,先使用instanceof判断要转型的引用是否指向要转型的类的对象,返回true再转型
3.总结
1.什么时候使用向上转型,方法接收一个类和他的子类,参数指定为相应的父类引用,此时就是向上转型
2.在某个特殊情况下,需要用到子类中扩展的独有的方法,此时就要用到向下转型