1、封装
(1)创建一个Circle类:
public class Circle extends Shape{
private double radius; //private修饰,不允许外界直接访问
private double pi=3.1415926;
public Circle(){
}
public Circle(double radius){
this.radius = radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {//需通过get、set方法,且修改值时必须满足一定的条件
if(radius>=0)
this.radius = radius;
}
public double getArea(){
return radius*radius*pi;
}
public double getLength() {
return 2*pi*radius;
}
}
(2)封装的概念:
在面向对象程序中,就是把对象的属性和操作结合为一个独立的整体,并尽可能隐藏对象的内部实现细节。在java中主要体现为类对属性和方法的封装和方法对方法具体的实现细节的封装。通过public、protected、private等权限修饰符对外界的访问加以限制,不允许外界直接访问类内的属性和方法。
例如:在上面的Circle类中,属性的修饰符为private,显然,在类外部是不能直接对属性进行操作的,但是,类中有get、set方法,修饰符为public类型,可以通过方法来对属性进行操作,当然,可以在get、set方法内部加上一些限制条件,如,在对半径赋值时,要保证值在合理的范围内。
(3)封装的好处:
防止类以内的数据和方法被随机访问(权限修饰符对访问进行了一定的限制),如果没有权限的限制就能随意访问类内的属性和方法,就有可能出现半径为负数的情况,要想修改属性值需要调用get和set方法,避免负数对半径的赋值。
隐藏了方法内的具体实现细节。
2、继承
(1)在不使用继承的情况下,创建猫类和狗类:
猫类:
public class Cat {
private String name;
public Cat(String name) {
this.name = name;
}
public void eat(){
System.out.println(name+"正在吃饭");
}
public void run(){
System.out.println(name+"正在跑");
}
public void introduction() {
System.out.println("大家好,我叫"+ name + ".");
}
}
狗类:
public class Dog {
private String name;
public Dog(String name) {
this.name = name;
}
public void eat(){
System.out.println(name+"正在吃饭");
}
public void run(){
System.out.println(name+"正在跑");
}
public void introduction() {
System.out.println("大家好,我叫"+ name + ".");
}
}
测试类:
public class Test {
public static void main(String[] args) {
Dog d=new Dog("Jaee");
d.eat();
d.run();
Cat c=new Cat("Tom");
c.eat();
c.run();
}
}
上面的Cat类和Dog类含有共同的方法,也就是说这两个类有重叠的部分,这样的代码冗余度较高。
(2)使用继承创建Cat类和Dog类:
先创建动物类:
public class Animal {
public String name;
public void eat(){
System.out.println(name+"正在吃饭");
}
public void run(){
System.out.println(name+"正在跑");
}
public void introduction() {
System.out.println("大家好,我叫"+ name + ".");
}
}
创建Cat类:
public class Cat extends Animal{
public Cat(String name) {
this.name = name;
}
}
创建Dog类:
public class Dog extends Animal{
public Dog(String name) {
this.name = name;
}
}
测试类:
public class Test {
public static void main(String[] args) {
Dog d=new Dog("Jaee");
d.eat();
d.run();
Cat c=new Cat("Tom");
c.eat();
c.run();
}
}
创建的Animal类相当于将Cat类和Dog类的共同的方法抽取了出来,降低了代码的冗余度,但是,耦合度升高了。
(3)继承的种类:
支持多重继承,但是不支持多继承(一个类继承多个类),如:在实现多线程的时候在使用继承Thread类的方式实现的时候,如果该类已经继承过了其他的类南无就不能再继承Thread类的方式来实现多线程了,只能使用实现接口的方式。因为接口是支持多实现的,即使该类已经实现类其他的接口依旧可以实现多线程相关的接口来实现多线程。
(4)继承的特征
支持多重继承但是不支持多继承
子类只能继承父类的非private的属性和方法,不能继承类的构造方法,需要用super关键字调用父类的构造方法
子类在继承了父类的属性和方法之后还能有自己属性和方法
提高了类之间的耦合性(代码的联系性提高,独立性下降)
(5)缺点
破坏了类的封装性:父类对于子类来说是透明的
可移植性降低
增加对象之间的耦合性,如果一个类被其他的类所继承则当这个类需要修改的时候必须考虑到所有的字类,因为父类修改后所有涉及到字类的功能都可能产生故障
如果想要正确地使用继承,需要遵循里氏替换原则。在继承与组合实现的效果相同的情况下应该多用组合,少用继承
3、多态(接口的多种不同实现方式、可以将父对象设置成为它的子对象)javascript:void(0)
(1)多态的实现:
创建一个父类(Person类):
package pers.zhb.Person;
public class Person {
protected String name;
private String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public void sleep() {
System.out.println("睡觉");
}
public void eat() {
System.out.println("吃饭");
}
}
创建一个子类(Student类)继承父类的属性和方法:
package pers.zhb.Person;
public class Student extends Person {
public Student(String name) {
this.name = name;
}
public void sleep() {
System.out.println("学生睡觉");
}
public void eat() {
System.out.println("学生吃饭");
}
public void study() { System.out.println("学习"); } }
Student类不但继承了Person类的属性和方法,还有自己的方法study()。
创建测试类:
package pers.zhb.Person;
public class Test {
public static void main(String[] args) {
/*
* 当有子类对象赋值给一个父类引用时,便是向上转型,多态本身就是向上转型的过程。 只能使用父类和子类共有的的内容,而无法使用子类特有功能
*/
Person s = new Student("Tom");
s.eat();
s.sleep();
/*
* 为子类特有功能,需要向下转型 一个已经向上转型的子类对象可以使用强制类型转换的格式,将父类引用转为子类引用,这个过程是向下转型
*/
((Student) s).study();
}
}
将子类的对象用父类类型来接收是向上转型,当调用父类特有的方法的时候会执行父类的方法。当调用子类的特有的方法的时候要向下转型,否则,是无法调用子类的特有的方法
,向下转型后便可以调用子类的特有内容。在向上转型后调用的eat()和sleep()方法为Student类重写的方法,
(2)向上转型和向下转型
向上转型:当有子类对象赋值给一个父类引用时,便是向上转型,多态本身就是向上转型的过程,向上转型之后,只能调用子父类共有的内容。例如:
Person s = new Student("Tom");
s.eat();
s.sleep();
此时对象s只能调用父类和子类公有的属性和方法(即:子类继承来的和重写的方法),睡觉和吃饭是子类和父类都有的方法。
向下转型:一个已经向上转型的子类对象可以使用强制类型转换的格式,将父类引用转为子类引用,这个过程是向下转型。如果是直接创建父类对象,是无法向下转型的。向下转型后可以调用子类特有的功能。例如:
((Student) s).study();
学习方法为子类特有方法。
(3)多态的好处与弊端
好处:
不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化(扩展性)。
提高了代码的维护性(继承保证)
可扩充:新增的子类不会对原有的子类产生影响
接口性:向子类提供接口,由子类来完成具体的功能
弊端:
只能使用父类和子类共有的的内容,而无法使用子类特有功能。
ClassCastException异常,可以通过instanceof运算符,判断对象所属的类型。
(4)多态的特点
成员变量(编译运行看左边):
当子父类中出现同名的成员变量时,多态调用该变量时:
- 编译时期:参考的是引用型变量所属的类中是否有被调用的成员变量。没有,编译失败。
- 运行时期:参考的是引用型变量所属的类中是否有被调用的成员变量。没有,运行失败。
即:编译和运行都参考等号的左边。
将父类中的成员变量去掉后,编译和运行都出现错误。
成员方法(编译看左边,运行看右边):
- 编译时期:参考引用变量所属的类,如果类中没有调用的方法,编译失败。
- 运行时期:参考引用变量所指的对象所属的类,并运行对象所属类中的成员方法。
即:编译看左边,运行看右边。
编译去掉父类方法:
但是运行的时候,执行的是子类方法:
多态,意味着一个对象有着多重特征,可以在特定的情况下,表现不同的状态,从而对应着不同的属性和方法。父类的对象的引用在编译时期(看的是父类类型)并不能确定,只有在运行时期才会知道这个引用变量到底指向哪一个实例,这样的话,程序就拥有了多个运行状态
4、总结
继承与实现的选择
继承:多个类的某个部分的功能相同,那么可以抽象出一个类出来,把他们的相同部分都放到父类里,让他们都继承这个类。
实现:多个类处理的目标是一样的,但是处理的方法方式不同,那么就定义一个接口,也就是一个标准,让他们的实现这个接口,各自实现自己具体的处理方法来处理那个目标
继承的根本原因是因为要实现复用,而实现的根本原因是需要定义一个标准。
在接口中只能定义全局常量(static final)和无实现的方法(Java 8以后可以有defult方法),继承中可以定义属性方法,变量,常量等。