面向对象
面向对象的特征:
1.封装
2.继承
3.多态
封装
封装表现:
1.方法就是一个最基本封装体。
2.类其实也是一个封装体。
封装的好处:
1.提高了代码的复用性。
2.隐藏了实现细节,还要对外提供可以访问的方式。便于调用者的使用。这是核心之一,也可以理解为就是封装的概念。
3.提高了安全性。
举例:就如同一台电脑主机,你无法看见其中的内部构造,但你能使用其中的内容,如显卡,集成器一 类的,如果直接交给你触碰,很容易直接坏掉,所有会用外壳包裹,以防毁坏,封装就是这类的思想,只给你具体的使用,但不给你展现,可以随时调用
修饰符引入:
public class Test {
public static void main(String[] args) {
Cat cat=new Cat();
cat.age=5;
cat.name="猫";
}
}
class Cat{
String name;
int age;
}
从以上代码看到,定义了一个Cat类并在Test里面调用并赋值,但是,注意,封装要的是提供访问方式来使用,而不是让人随意直接修改,倘若能随意修改的话,比如机箱的话,一通修改,直接报废了,因此需要的是让专业的人(程序员)来访问修改,不让所有人随意使用,因此引入了修饰符
private
private关键字特点
是一个权限修饰符
可以修饰成员变量和成员方法
被其修饰的成员只能在本类中被访问
用private修饰Cat的成员属性后:
public class Test {
public static void main(String[] args) {
Cat cat=new Cat();
cat.age =5;
cat.name="猫";
}
}
class Cat{
private String name;
private int age;
}
//此时的age和name会报错
访问方法
那么此时该如何才能不报错呢?
提供一个访问渠道就可以了,就像机箱会有插口一样,我们也提供给访问的方法
一般对成员属性的访问动作:赋值(设置 set),取值(获取 get),因此对私有的变量访问的方式可以提供对应的 setXxx或者getXxx的方法
IDEA可以用快捷键 Alt + insert选择生成get和set方法快速生成
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
// cat.age=5;
// cat.name="猫";
cat.setAge(5);
cat.setName("猫");
//调用set方法设置具体
System.out.println("年龄是::" + cat.getAge() + "名字是:" + cat.getName());
//调用get方法得到
}
}
class Cat {
private String name;
private int 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;
}
//使用this防止变量冲突,也使得程序员看得懂,简单理解为此时的这个变量是这个
}
继承
这世界上人都有共同性,比如吃饭睡觉,如果每次创建类时如果都重复写一遍就很麻烦,那么就需要想个办法解决这些问题
而人都是继承来的,比如子与父,父亲会吃饭,会呼吸,那么自父母而来的孩子,也会吃饭睡觉,父亲或许会打球,如果孩子也会,那么就认为继承了父亲,因此便产生了使用继承的概念
通过对多个类中存在相同属性和行为进行抽取,将重复代码抽取到单独的类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。
Java中的继承是指在一个现有类的基础上去构建一个新的类,构建出来的新类被称为子类,现有的类被称为父类
继承格式
通过extends关键字可以实现类与类的继承
class 子类名 extends 父类名 {}
继承的好处:
- 提高了代码的复用性
- 提高了代码的维护性
- 让类与类之间产生了关系,这是多态的前提
演示:
public class Test {
public static void main(String[] args) {
//创建子类对象
//注意,全是子类,并没有调用过父类
//全部调用父类方法
Zi zi = new Zi();
zi.setBreathe("能呼吸");
zi.setEat("能吃");
zi.play();
System.out.println(zi.getBreathe()+" "+zi.getEat());
}
}
class Fu {
String eat;
String breathe;
//定义各自都有的
public void play() {
System.out.println("打篮球");
}
//定义两人共有爱好
//创建方法调用
public String getEat() {
return eat;
}
public void setEat(String eat) {
this.eat = eat;
}
public String getBreathe() {
return breathe;
}
public void setBreathe(String breathe) {
this.breathe = breathe;
}
}
class Zi extends Fu {
//此时子类中没有任何东西
}
继承注意事项:
1.Java只支持单继承,不支持多继承。(一个儿子只能有一个亲生父亲)
2.子类只能继承父类所有非私有的成员(成员方法和成员变量,你父亲愿意给你你才有)
3.子类不能继承父类的构造方法,但是可以通过super关键字去访问父类构造方法。
构造方法
什么是构造方法?
从字面上理解就是 创建对象 时要 执行 的方法。那么只要在 new 对象时,知道其执行的构造方法是什么,就可以在执行这个方法的时候给对象进行 属性赋值 。
构造方法也叫构造器,主要作用是创建对象,给对象中的成员进行初始化
构造方法格式
修饰符 构造方法名(参数列表){
}
构造方法特点
1.构造方法 没有返回值类型 以及 返回值 (特殊的方法,连void都没有)。
2.不需要写返回值。因为它是为构建对象的,对象创建完,方法就执行结束(只用这一次)
3.构造方法名称必须和 类名保持一致。
同样,IDEA可以用 Alt + insert 选择构造函数生成方法
public class Test {
public static void main(String[] args) {
//使用方法
//可以直接赋值,省去调用set方法
Fu fu=new Fu("吃饭","呼吸");
System.out.println(fu.getBreathe()+" "+fu.getEat());
}
}
class Fu {
String eat;
String breathe;
public void play() {
System.out.println("打篮球");
}
//定义无参方法(默认构造)
public Fu() {
}
//定义带参方法
//通过构造方法可以为成员变量赋值,也就是对成员变量进行了初始化
public Fu(String eat, String breathe) {
this.eat = eat;
this.breathe = breathe;
}
public String getEat() {
return eat;
}
public void setEat(String eat) {
this.eat = eat;
}
public String getBreathe() {
return breathe;
}
public void setBreathe(String breathe) {
this.breathe = breathe;
}
}
注意:上述代码为何要有个无参方法:
当在编译Java文件时,编译器会自动给class文件中添加 默认的构造方法。如果在描述类时,我们显式指定了构造方法,那么,当在编译Java源文件时,编译器就不会再给 class 文件中添加 默认构造 方法。
class Fu {
//如果没有显式指定构造方法,编译会在编译时自动添加默认的构造方法
//public Fu(){} //空参数的默认构造方法
}
建议无论如何都把无参方法写上,详情在后–>继承中的构造方法注意事项
方法重载
当我们只想用一个属性时就会报错
Fu fu=new Fu(“吃饭”);
public class Test {
public static void main(String[] args) {
//此时括号报错,因为缺少了"呼吸"
Fu fu=new Fu("吃饭");
System.out.println(fu.getEat());
}
}
因此可以使用方法的重载
public class Test {
public static void main(String[] args) {
Fu fu=new Fu("吃饭","呼吸");
System.out.println(fu.getBreathe()+" "+fu.getEat());
Fu fu1=new Fu("吃饭");
System.out.println(fu1.getEat());
}
}
class Fu {
String eat;
String breathe;
public void play() {
System.out.println("打篮球");
}
//定义无参方法
public Fu() {
}
//定义带参方法
public Fu(String eat, String breathe) {
this.eat = eat;
this.breathe = breathe;
}
//定义带一个参的方法
public Fu(String eat) {
this.eat = eat;
}
public String getEat() {
return eat;
}
public void setEat(String eat) {
this.eat = eat;
}
public String getBreathe() {
return breathe;
}
public void setBreathe(String breathe) {
this.breathe = breathe;
}
}
此时就可以用单个的eat属性了
super / this
在继承中,如果子类和父类变量重名了,那么子类想访问父类变量,此时就需要用super关键字
当局部变量和成员变量的名字相同的时,使用this关键字,解决局部变量隐藏(冲突)了成员变量的问题
super 使用
super.成员变量 调用父类的成员变量
super.(…) 调用父类构造方法
super.成员方法 调用父类成员方法
相识知识点:this关键字
this.成员变量 调用本类的成员变量
this(…) 调用本类的构造方法
this.成员方法 调用本类的成员方法
this和super的区别
this代表的是本类对象的引用
super代表的是父类存储空间的标识(可以理解成父类的引用,可以操作父类的成员)
继承中成员变量的关系
子类中的成员变量和父类中的成员变量名称不一样,可以直接使用
子类中变量名与父类中成员变量名一样:
变量寻找方式,遵循就近原则
1.先在局部位置找变量
2.如果局部找不到,那么就到当前类成员变量找
3.如果当前类找不到,那么到父类里面找
4.再找不到,就报错
注意:当子类覆盖父类的成员变量时,每个类使用成员都相当于在前面加了 一个this指针。
public class Test {
public static void main(String[] args) {
//创建子类对象
Zi z = new Zi();
z.show();
}
}
class Fu {
public int num = 20;
public void showFu(){
System.out.println(num);
}
}
class Zi extends Fu{
public int num2 = 40;
//第二次演示 跟父类同名的变量
public int num = 100;
public void show(){
//第三次演示 定义局部变量 num
int num = 200;
System.out.println(num2);//子类的
//System.out.println(num);//第一次演示是父类的
//System.out.println(num);//第二次演示 100
System.out.println(num);
showFu();
}
}
结果:
40
200
20
继承中构造方法的关系
子类中所有的构造方法默认都会访问父类中空参数的构造方法
为什么呢?
因为子类会继承父类中的数据,可能还会使用父类的数据。
所以,子类初始化之前,一定要先完成父类数据的初始化。
其实:
每一个构造方法的第一条语句默认都是:super()
如果这个类没有去继承其他的类,默认继承Object类,每个类都使用 Object 作为超类(最大父类)
public class Test {
public static void main(String[] args) {
//创建对象
Zi zi = new Zi();
System.out.println("------------------------------");
Zi zi1 = new Zi("子");
}
}
class Fu {
public Fu() {
System.out.println("我是Fu的无参构造!");
}
public Fu(String name) {
//super();
System.out.println("我是Fu的带参构造!");
}
}
class Zi extends Fu {
public Zi() {
//super();
System.out.println("我是Zi的无参构造!");
}
public Zi(String name) {
//super();
//访问父类的带参构造
super(name);
System.out.println("我是Zi的有参构造!");
}
}
结果:
我是Fu的无参构造!
我是Zi的无参构造!
------------------------------
我是Fu的带参构造!
我是Zi的有参构造!
继承中构造方法的注意事项
父类没有无参构造方法,子类怎么办?
在父类中添加一个无参的构造方法
子类通过super去显示调用父类其他的带参的构造方法
子类通过this去调用本类的其他构造方法
子类其他构造必须首先访问父类构造
public class Test {
public static void main(String[] args) {
//创建对象
//Zi z = new Zi();
Zi z2 = new Zi("子");
}
}
class Fu {
/*
public Fu(){
System.out.println("父类无参构造!");
}
*/
public Fu(String name) {
System.out.println("父类带参构造!");
}
}
class Zi extends Fu {
public Zi() {
//super();
super("张三");
System.out.println("子类无参构造!");
}
public Zi(String name) {
//super();
//super("aa");
this();
System.out.println("子类带参构造!");
}
}
结果:
父类带参构造!
子类无参构造!
子类带参构造!
继承中成员方法关系
当子类的方法名和父类的方法名不一样的时候
当子类的方法名和父类的方法名一样的时候
通过子类调用方法:
先查找子类中有没有该方法,如果有就使用
再看父类中有没有该方法,有就使用
如果没有就报错
public class Test {
public static void main(String[] args) {
//创建一个子类对象
Zi z = new Zi();
z.show();
z.method();
//z.function();// z.function();找不到
}
}
class Fu {
public void show() {
System.out.println("show Fu!");
}
}
class Zi extends Fu {
public void method() {
System.out.println("method Zi!");
}
public void show() {
System.out.println("show Zi!");
}
}
结果:
show Zi!
method Zi!
方法重写
当子类继承了父类中的方法时,但又要更改一部分,例如手机,所有手机都能打电话,新的手机继承以前手机的功能,但是新的手机还可以在打电话时视频等,因此就可以使用方法重写来既继承打电话功能,又可以添加新的视频等功能
public class Test {
public static void main(String[] args) {
newPhone np=new newPhone();
np.call();
}
}
class Phone{
public void call(){
System.out.println("打电话");
}
}
class newPhone extends Phone{
@Override
public void call() {
super.call();//这句话体现了继承的魅力
System.out.println("开启视频功能");
}
}
结果:
打电话
开启视频功能
方法重写注意事项
父类中私有方法不能被重写
因为父类私有方法子类根本就无法继承(你父亲给你看你才能看)
子类重写父类方法时,访问权限不能更低(你父亲有资格知道你的,你没资格比他知道的多)
最好就一致
父类静态方法,子类也必须通过静态方法进行重写
其实这个算不上方法重写,但是现象确实如此,至于为什么算不上方法重写,多态中会讲解
子类重写父类方法的时候,最好声明一模一样。
多态
多态概述
现实事物经常会体现出多种形态,如学生,学生是人的一种,则一个具体的同学张三既是学生也是人,即出现两种形态。
Java作为面向对象的语言,同样可以描述一个事物的多种形态。如Student类继承了Person类,一个Student的对象便既是Student,又是Person。
Java中多态的代码体现在一个子类对象(实现类对象)既可以给这个子类(实现类对象)引用变量赋值,又可以给这个子类的父类类型变量赋值。
如Student类可以为Person类的子类。那么一个Student对象既可以赋值给一个Student类型的引用,也可以赋值给一个Person类型的引用。
最终多态体现为父类引用变量可以指向子类对象。
多态的前提是必须有子父类关系或者类实现接口关系,否则无法完成多态。
在使用多态后的父类引用变量调用方法时,会调用子类重写后的方法
多态的定义与使用格式
多态的定义格式:就是父类的引用变量指向子类对象
父类类型 变量名 = new 子类类型();
变量名.方法名();
普通类(继承关系)多态定义的格式
父类 变量名 = new 子类();
如: class Fu {}
class Zi extends Fu {}
//类的多态使用
Fu f = new Zi();
抽象类多态定义的格式
抽象类 变量名 = new 抽象类子类();
如:
abstract class Fu {
public abstract void method();
}
class Zi extends Fu {
public void method(){
System.out.println(“重写父类抽象方法”);
}
}
//类的多态使用
Fu fu= new Zi();
接口多态定义的格式
接口 变量名 = new 接口实现类();
如:
interface Fu {
public abstract void method();
}
class Zi implements Fu {
public void method(){
System.out.println(“重写接口抽象方法”);
}
}
//接口的多态使用
Fu fu = new Zi();
多态中成员变量
public class Test {
public static void main(String[] args) {
Fu f = new Zi();
System.out.println(f.num);
Zi z = new Zi();
System.out.println(z.num);
}
}
class Fu {
int num = 4;
}
class Zi extends Fu {
int num = 5;
}
结果:
4
5
当子父类中出现同名的成员变量时,多态调用该变量时:
编译时期:参考的是引用型变量所属的类中是否有被调用的成员变量。如果没有,编译错误
运行时期:也是调用引用型变量所属的类中的成员变量。
简单记:编译和运行都参考等号的左边。编译运行看左边。
Java成员变量没有重写的功能,多态调用时先确定变量的类型,如
//此时f类型是Fu(门面类型),只能取到Fu中的值
//(父类不知道子类有什么属性,但是子类是知道父类的属性的)
//可以理解为,父子两人的回答,往往只能是父的更容易让人信服
Fu f = new Zi();
f.num;
多态中成员方法
public class Test {
public static void main(String[] args) {
Fu f = new Zi();
f.show();
}
}
class Fu {
void show() {
System.out.println("Fu show num");
}
}
class Zi extends Fu {
void show() {
System.out.println("Zi show num");
}
}
结果:
Zi show num
编译时期:参考引用变量所属的类,如果类中没有调用的方法,编译失败。
运行时期:参考引用变量所指的对象所属的类,并运行对象所属类中的成员方法。
简而言之:编译看左边,运行看右边。(因为方法可以重写)
Java成员方法是有重写的功能,多态调用时先确定变量的类型,如
//此时f类型是Fu(门面类型),但是实际的类型是Zi
//可以理解为,时代变了!大人,你的方法过时了!年轻人更改了最新最好用的,所以听我的!
Fu f = new Zi();
f.show();//f的门面类型是Fu,但是实际类型是Zi,所以调用的是重写后的方法
多态的转型
多态的转型分为向上转型与向下转型两种:
向上转型:当有子类对象赋值给一个父类引用时,便是向上转型,多态本身就是向上转型的过程。
使用格式:
父类类型 变量名 = new 子类类型();
如:Fu f = new Zi();
向下转型:一个已经向上转型的子类对象可以使用强制类型转换的格式,将父类引用类型转为子类引用类型,这个过程是向下转型。
如果是直接创建父类对象,是无法向下转型的!
使用格式:
子类类型 变量名 = (子类类型) 父类类型的变量;
如:Zi zi = (Zi) f; //变量p 实际上指向Student对象
转型举例:
描述土豪
土豪拥有吃饭功能
描述富二代
富二代拥有玩游戏功能
都拥有吃饭的方法
某一天土豪生病了,富二代代替土豪去签合同
签名:签的是土豪的名字
吃饭:富二代喜欢吃啥就点啥
回家后换回身份:玩游戏
public class Test {
public static void main(String[] args) {
// 土豪生病了,但是有合约要签
Fu f = new Zi();// 富二代代替他爹去签合约 向上转型(此时可以看作是土豪)
System.out.println(f.name);// 土豪的名字(使用的是变量)
f.eat();// 签完合同去吃饭,这里表象是Fu,但是实际是Zi(使用的是方法)
Zi z = (Zi) f;// 回家,换回自己的身份 向下转型(转回富二代)
z.playGames();
}
}
class Fu {
int age = 50;
String name = "土豪";
public void eat() {
System.out.println("清粥白菜...");
}
}
class Zi extends Fu {
String name = "富二代 ";
int age = 25;
public void eat() {
System.out.println("大鱼大肉!");
}
public void playGames() {
System.out.println("玩游戏!");
}
}
结果:
土豪
大鱼大肉!
玩游戏!
多态好处与弊端
好处:
当父类的引用指向子类对象时,就发生了向上转型,即把子类类型对象转成了父类类型。向上转型的好处是隐藏了子类类型,提高了代码的扩展性。
弊端:
但向上转型也有弊端,只能使用父类共性的内容,而无法使用子类特有功能,功能有限制。如果想要使用子类内容,那么必须强转为子类类型
案例:
f.playGames ();
public class Test {
public static void main(String[] args) {
// // 土豪生病了,但是有合约要签
// Fu f = new Zi();// 富二代代替他爹去签合约 向上转型(此时可以看作是土豪)
// System.out.println(f.name);// 土豪的名字(使用的是变量)
// f.eat();// 签完合同去吃饭,这里表象是Fu,但是实际是Zi(使用的是方法)
// Zi z = (Zi) f;// 回家,换回自己的身份 向下转型(转回富二代)
// z.playGames();
//富二代冒充土豪
Fu f = new Zi();
//富二代用土豪身份玩游戏
f.playGames ();//报错,此时的富二代是土豪,土豪不喜欢玩游戏,因此不能玩游戏
//富二代换回自己身份
Zi z = (Zi) f;
z.playGames();//可以玩了
}
}
class Fu {
int age = 50;
String name = "土豪";
public void eat() {
System.out.println("清粥白菜...");
}
}
class Zi extends Fu {
String name = "富二代 ";
int age = 25;
public void eat() {
System.out.println("大鱼大肉!");
}
public void playGames() {
System.out.println("玩游戏!");
}
}
什么时候使用向上转型:
当不需要面对子类类型时,通过提高扩展性,或者使用父类的功能就能完成相应的操作,这时就可以使用向上转型。
如:Fu f = new Zi();
f.eat();
什么时候使用向下转型
当要使用子类特有功能时,就需要使用向下转型。
如:Zi z = (Zi) f; //向下转型
z.playGames();//调用富二代的玩游戏方法
向下转型
好处是:可以使用子类特有功能。
弊端是:需要面对具体的子类对象;在向下转型时容易发生ClassCastException类型转换异常。在转换之前必须做类型判断。
如:if(!f instanceof Zi){…}
instanceof关键字
我们可以通过instanceof关键字来判断某个对象是否属于某种数据类型。如学生的对象属于学生类,学生的对象也属于人类。
使用格式:
boolean b = 对象 instanceof 数据类型;
如
Person p1 = new Student(); // 前提条件,学生类已经继承了人类(Student extends Person)
boolean flag = p1 instanceof Student; //flag结果为true
boolean flag2 = p2 instanceof Teacher; //flag结果为false
总结
学习到这里,面向对象的三大特征学习完了。
总结下封装、继承、多态的作用:
封装:把对象的属性与方法的实现细节隐藏,仅对外提供一些公共的访问方式
继承:子类会自动拥有父类所有可继承的属性和方法。
多态:配合继承与方法重写提高了代码的复用性与扩展性;如果没有方法重写,则多态同样没有意义。