一、类的封装详解
在“面向对象编程基础(java)”的时候讲过,封装是面向对象编程的核心思想。同时我们也知道类是载体,只不过我们把对象的属性和行为封装在载体中。
现我们用封装的方式来实现,一个顾客去一家餐饮吃饭,点了一份西红柿炒蛋。
分析:
- 顾客去餐馆,要跟餐馆服务员点菜
- 餐馆服务员拿菜单去通知后厨的厨师
- 厨师拿到菜单,开始准备工作和烹饪
注意:顾客是把想吃的菜告诉了餐馆服务员,那么顾客是可以跟餐馆服务员进行接触的,同时服务员还要记录顾客想吃的菜品。因为要把这个菜品转达给后厨厨师。所以后厨厨师是一直在后面没有跟顾客接触。
//定义餐馆类,为顾客提供服务 public class Restaurant{ //每一个餐馆都有厨师 private Cook cook = new Cook(); public static void main(String[] args){ //实例化餐馆,提供服务 Restaurant rest = new Restaurant(); System.out.println("我想吃西红柿炒蛋,帮我来一份"); rest.takeOrder("西红柿炒蛋"); } //有为顾客下单行为 public void takeOrder(String dish){ cook.cooking(dish); System.out.println(dish + "已经炒好了,请慢慢享用"); } } //定义厨师类 class Cook{ //厨师名字 private String name; public Cook(){ this.name = "托尼"; } //洗菜行为 private WashDishes(){ System.out.println(this.name + "洗菜"); } //炸花生米行为 private void Rice(){ System.out.println(this.name + "炸花生米"); } //炒菜行为 public void Cooking(String dish){ WashDishes(); Rice(); System.out.println(name + "开始炒" + dish); } }
在简述封装时,就提到过手机类,关于手机当中的功能用户只要负责使用,内部怎么实现不要管。那么关于餐馆,顾客是跟服务员进行沟通而餐馆的厨师并没有和顾客沟通。也就是说,顾客只负责吃自己下单的菜,关于厨师是谁并不知道,所以关于这个过程我们就可以称之为封装。
延伸:请分别以餐馆类、用户类、服务员类、厨师类来完成这个工作。
//餐馆类 public class Restaurant3 { //餐饮名字 public static String name = "三无真味私厨"; public static void main(String[] args){ Restaurant3 rest = new Restaurant3(); User user = new User(); Waiter waiter = new Waiter(); System.out.println("欢迎光临"+name+","+user.name+",我是"+waiter.name+"为您服务"); System.out.println("请问你要吃一点什么?"); System.out.println("给我来一份"+user.SayLive()); waiter.TakeOrder(user.SayLive()); //顾客吃完去结账 waiter.Cash(user); System.out.println(user.name + "慢走"); System.out.println("你多大了,长得很漂亮哦!"); waiter.Que(); } } //用户类 class User { public String name; public User(){ this.name = "老板"; } //说出自己喜欢吃的菜 public String SayLive(){ return "西红柿炒蛋"; } //付钱 public void Pay(double money){ System.out.println("我已经支付了" + money + ",确认一下。"); } } //服务员类 class Waiter { public String name; private int age = 19; private Cook cook = new Cook(); public Waiter(){ this.name = "赵敏"; } //下单 public void TakeOrder(String dish){ cook.GetOrder(dish); System.out.println("菜已经炒好了,请慢用"); } //收银 public void Cash(User user){ System.out.println(user.name+"您好,本次一共消费500元"); user.Pay(500.00); } //询问芳年 public void Que(){ System.out.println("年龄是不方便说滴"); } } //厨师类 class Cook { private String name; public Cook(){ this.name = "周芷若"; } //洗菜 private void Wash(){ System.out.println(this.name + "正清洗青菜"); } //打碎鸡蛋 private void Eggs(){ System.out.println(this.name + "正在打碎鸡蛋"); } //炒菜 private void cooking(String dish){ Wash(); Eggs(); System.out.println(this.name + "开始炒菜" + dish); } //接单子 public void GetOrder(String dish){ System.out.println("厨师接到顾客菜单"+dish); this.cooking(dish); } }
总结:就是把一些只提供使用的功能隐藏在载体里,也就是类中!
二、类的继承
继承的思想就是子类可以继承父类原有的属性和方法,也可以增加父类所不具备的属性和方法,或者直接重写父类中的某些方法。
语法:子类名 extends 父类名
分析:继承是让一个类继承另一个类,只需要使用enxtends关键字就可以了,同时也要注意java中仅支持单一继承,也就是一个类只有一个父类。
例:电脑类和平板电脑之间的继承
//电脑类 class Computer { String Color = "黑色"; String Screen = "液晶显示屏"; void startUp(){ System.out.println("电脑正在开机,请稍等...."); } } //平板电脑 public class Pad extends Computer { //平板电脑自己的属性 String Battery = "10000毫安锂电池"; public static void main(String[] args){ //父类自己实例化 Computer cp = new Computer(); System.out.println("Computer的颜色是" + cp.Color); //电脑类对象调用开机方法 cp.startUp(); //创建Pad平板电脑类 Pad ipad = new Pad(); //调用父类的属性 System.out.println("ipad的屏幕是:"+ipad.Screen); //ipad调用自己的属性 System.out.println("ipad的电池:" + ipad.Battery); ipad.startUp(); } }
分析:从上面代码可以得出一个结论,继承只是关系。父类和子类都可以进行实例化,子类对象可以去使用父类的属性和方法,而不要做具体实现。同时子类也可以扩展自己的属性和方法。关于父类属性和方法被子类对象使用就是代码重复性。
三、方法的重写
父类的成员都会被子类继承,当父类的某个方法并不适用于子类时,就需要在子类重写父类的这个方法。
重写的实现
继承不只是扩展父类的功能,还可以重写父类的成员方法。重写可以理解为覆盖,就是在子类中将父类的成员方法名称保留,再重新编写父类成员方法的实现内容,更改成员方法的储存权限,或修改成员方法的返回数据类型。
总结:
- 数据返回类型可以进行重写
- 重写方法也可以增加方法的参数
- 访问权限只能从小到大,也就是父类为private,子类可以写成public
注意 :
子类与父类的成员方法返回值、方法名称、参数类型以及个数完全相同,唯一不同的是方法实现内容,这一种方式来重写可以称之为重构。
//子类重写父类Eat方法,同时也修改了父类Eat方法的返回类型 public class Mans extends Humans{ public String Eat(){ return "吃东西行为"; } public static void main(String[] args){ Mans m = new Mans(); System.out.println(m.Eat()); } } //人类 class Humans{ public void Eat(){ System.out.println("吃东西的行为"); } }
//子类重写父类方法Eat(),并在子类Eat()方法中可以接收一个参数 public class Mans extends Humans{ public void Eat(String name){ System.out.println("吃东西行为" + name); } public static void main(String[] args){ Mans m = new Mans(); m.Eat("利害吧!"); } } class Humans{ public void Eat(){ System.out.println("吃东西行为"); } }
//重写父类方法Eat(),重写内容是修改父类的访问权限 public class Mans extends Humans{ public void Eat(){ System.out.println("吃东西行为"); } public static void main(String[] args){ Mans m = new Mans(); m.Eat(); } } class Humans{ private void Eat(){ System.out.println("吃东西行为"); } }
子类如何来调用父类属性和方法
如果子类重写了父类的方法之后就无法调用父类的方法了吗?答,是可以的,在子类重写父类方法时还想调用父类方法可以使用super关键字来调用父类的方法。
在子类里super关键字代表父类对象。
public class Child extends Supers{ public void Pint(){ super.Pint(); System.out.println("我重写了父类的方法"); } public static void main(String[] args){ Supers s = new Supers(); s.Pint(); } } class Supers{ public void Pint(){ System.out.println("我是父类的方法"); } }
四、所有类的父类——Object
在java中所有的类都直接或者间接继承了java.lang.Object类。也称Object为基类。所以Object类比较特殊,因为他是所有类的父类,是java类中最高层类。也就是说,在我们创建一个class时,如果没有指定该类的继承类,那么java.leng.Object类都是他们默认的继承类
public Humans{} => public Humans extends Object{}
分析:在Object类中主要包括clone()、finalize()、equals()、toString()等这一些方法。所以所有的类都可以重写Object类中的方法。
注意:Object类中的getClass()、notify()、notifyAll()、wait()等方法是不能重写的,因为这一些方法定义了final类型,关于final类型下面会讲到。
1.getClass()方法
getClass()方法会返回某个对执行时的Class实例也就是对象,再通过Class实例调用getName()方法获取类的名称。
//得到一个对象的类名称 public class Hello{ public static void main(String[] args){ Hello h = new Hello(); System.out.println(h); //Hello } public String toString(){ return getClass().getName(); } }
总结:getClass()和getName()这两个方法的配合就是获取对象的类名。
2.equals()方法在Object类中是用来比较两个对象的引用地址是否相等。
class H{} public class Hello{ public static void main(String[] args){ String s1 = new String("111"); String s2 = new String("222"); System.out.println(s1.equals(s2)); //true H h1 = new H(); H h2 = new H(); System.out.println(h1.equals(h2)); //false } }
五、类的多态
多态在程序里面的意思就是一种定义有多种实现。例如:java里的“+”有个个数
相加、求和,还有就是字符串连接符。类的多态性可以从两个方面体现:一是方法的重载,二是类的上下转型。
5.1 方法的重载
构造方法的名称由类名决定。如果以不同的方式创建类的对象,那么就需要使用多个形参不同的构造方法来完成。在类中如果有多个构造方法存在,那么这一些构造方法的形参个数不一相同,而且必须且到方法“方法的重载”。那么方法的重载就是在同一个类中允许同时存在多个同名方法,只要这些方法的参数个数或者类型不同就可以。
public class OverLoadDemo{ public static int add(int a){ return a; } public static int add(int a, int b){ return a + b; } public static double add(double a, double b){ return a + b; } public static int add(int a, double b){ return (int)(a + b); } public static int add(double a, int b){ return (int)(a + b); } public static int add(int... a){ int sum = 0; for (int a = 0; i < a.length; i++){ sum += a[i]; } return sum; } public static void main(String[] args){ System.out.println("调用add(int)方法:" + add(1)); System.out.println("调用add(int,int)方法:" + add(1,2)); System.out.println("调用add(double,double)方法:" + add(1.5,2.9)); System.out.println("调用add(int,double)方法:" + add(15,2.9)); System.out.println("调用add(double,int)方法:" + add(15.8,9)); System.out.println("调用add(int... a)方法:" + add(1,2,3,4,5,6,7,8,9)); System.out.println("调用add(int... a)方法:" + add(1,2,3,4,5)); } }
注意:在类方法中只要方法的参数个数或者类型不同,并且方法名称一样,那么这样一来就构成了重载
5.2 向上转型
子类引用的对象转化为父类类型称为向上转型。通俗地来说就是将子类对象转为父类对象。此处父类对象也可以是接口。
class Parent { public static void draw(Parent p){ System.out.println("我开始来了"); } } public class Zhuangxing extends Parent { public static void main(String[] args){ Zhuangxing zx = new Zhuangxing(); zx.draw(zx); }
Parent是我们Zhuangxing的父类,父类中有一个draw()有一个参数,这个参数的型是Parent,也就是父类自己。在Zhuangxing类中的主方法里我们得到子类对象,并调用了父类的draw()方法,同时也吧子类对象作为参数传入draw()方法。因为Parent类和Zhuangxing是继承关系,所以吧子类对象赋值给父类类型对象时,这我们就管他称为向上转型。
class Parent{ public static void hello(){ System.out.println("你好"); } } public class Zhuangxing extends Parent{ public String name = "张三"; public static void main(String[] args){ //向上转型是子类对象当成是父类对象 //Zhuangxing zx = new Parent(); 这是错误的 Parent p = new Zhuangxing(); //把子类对象当成父类对象看 p.name; //报错,找不到此方法 } }
分析:
在向上转型时,父类的对象无法调用子类独有的属性或者方法。而我们父类对象调用自己的属性或者方法是可以的。这其实就是向上转型的一个特性,向上转型的对象会遗失子类中父类没有的方法,而且子类的同名方法会覆盖父类的同名方法。相当于向上转型时,改对象对于只存在子类中而父类中不存在的方法是不能访问的。同时,若子类重写了父类的某些方法,在调用这些方法时,实际上调用的是子类定义的方法,这也就是动态链接、动态调用。通俗的来理解,苹果可以是水果,但不能说水果就是苹果。如果要让水果调用苹果的特有属性和方法,这是不符合常理的。
class Fruit //水果类 { public void sayName(){ System.out.println("我是水果"); } } public class Apple extends Fruit //苹果类并继承水果类 { public static void main(String[] args){ //向上转型 Fruit apple = new Apple(); apple.sayName(); apple.Hello(); //这里就会报错,说没有此方法 } //这是苹果类独有的方法 public void Hello(){ System.out.println("我是苹果类独有的方法"); } public void sayName(){ System.out.println("我是苹果..."); } }
总结:向上转型是对父类对象的方法进行扩充,即父类对象可以访问子类对象重写父类的方法。
5.3 向下转型
向下转型就是指父类类型的对象转型为子类类型。也就是,声明的是子类类型,但引用的是父类类型的对象。
同时向上转型可以由java编译器自动来完成的,但是向下转型就要人工强制转换。
class Fruit{ public void sayName(){ System.out.println("我是水果"); } } public class Apple extends Fruit{ public void sayName(){ System.out.println("我是苹果"); } //子类独有方法 public void Hello(){ System.out.println("我是子类独有方法"); } public static void main(String[] args){ Fruit apple = new Apple(); //父类类型引用的是子类对象 apple.sayName(); //输出的是 - 我是苹果 //强制转播换->父类类型Fruit的对象apple向下转型为子类类型Apple Apple fruit = (Apple)apple; fruit.sayName(); //我是苹果 fruit.Hello(); //不会报错->输出我是子类独有方法内容 } }
分析:
首先apple的类型是Fruit水果类,但实际引用的是子类Apple的实例,所以apple是向上转型Fruit类型。从代码来看,apple是父类类型(fruit),引用的是子类对象。所以再将apple从Fruit类型向下转换为Apple类型是安全的。因为我们apple对象本身指向的就是子类对象。
注意-->上下转型关于加载问题
不管是向上还是向下,加载类时都会加载到内存当中。不过向上转型时,对象虽然遗失了父类没有同名的方法,但这些已经加载到了内存当中,因为是向上转型,所以对象不能调用这一些方法罢了。同时加载类,除了对象特有的属性不会被加载外,其他的都会被加载。
向上和向下转型总结
- 向上转型和向下转型是实现多态的一种机制
- 向上转化编译器自动实现,目的是抽象化对象,简化编程
- 向上转型时,子类将遗失父类不同名的方法,子类同名的方法将覆盖父类的方法
- 向下转型需要强制转换
- 向下转型要注意对象引用的是子类对象还是父类对象,引用子类对象不会报错,引用父类就会报错
同时向上转型和向下转型是java抽象编程的奥秘。向上转型是实现多态的一种机制,我们可以通过多态性提供的动态分配机制执行相应的动作,使用多态编写的代码不使用多种类型进行检测的代码更加易于扩展和维护。
5.4 instanceof关键字
class Fruit{ public void sayName(){ System.out.println("我是水果"); } } public class Apple extends Fruit{ public static void main(String[] args){ //fruit是父类类型引用指向的还是父类类型 Fruit fruit = new Fruit(); //Fruit类型的fruit向下强制转型为Apple类型 Apple apple = (Apple)fruit; apple.sayName(); } public void sayName(){ System.out.println("我是苹果"); } }
代码编译环境不会报错,但运行代码就会包java.lang.ClassCastException:Fruit cannot be cast to Apple错。这也是我们刚开始学习转型时会遇到的场景。
对象A instanceof 类B => 翻译就是:对象A是否为类B的实例,如果是返回true,否则为false
class Fruit{ public void sayName(){ System.out.println("我是水果"); } } public class Apple extends Fruit{ public static void main(String[] args){ Fruit apple = new Apple(); // 向下转型 Apple fruit = null; //判断apple是不是Apple的实例 if (apple instanceof Apple){ fruit = (Apple)apple; } fruit.sayName(); //我是苹果 fruit.Hello();//我是子类独有方法 } public void sayName(){ System.out.println("我是苹果"); } public void Hello(){ System.out.println("我是子类独有方法"); } }