目录
3、类的三大特性详解(封装、继承、多态)
3.1 封装
封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。要访问该类的代码和数据,必须通过严格的接口控制。封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。
封装的通俗解释就是将类中的信息进行私有保护,类就像住的房子一样,属性和方法等信息像房子里的(private)私有物品,一般不对外开放,从而达到了封装的效果。别人想使用你的东西(属性或方法),要经过你的同意才可以使用,并且这些私有(private)物品(属性或方法)外部无法进行修改。
下面就是对属性和方法进行封装的简单例子:
import java.util.Date;
public class Worker {
private String name;
private Date birthday;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
private String WorkAdress(){
return "北京";
}
public String AA(){
return WorkAdress()+"朝阳区";
}
}
封装的优点:
-
1. 良好的封装能够减少耦合。
-
2. 类内部的结构可以自由修改。
-
3. 可以对成员变量进行更精确的控制。
-
4. 隐藏信息,实现细节。
3.2 继承
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。继承可以使用 extends 和 implements (后面接口讲)这两个关键字来实现继承。需要注意的是 Java 不支持多继承,但支持多重继承。
下面是一个继承的简单实例------经理(Manager)继承Employee(员工):
import java.util.Date;
public class Employee {
private String name;
private double salary;
private Date hireDay;
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public double getSalary() {return salary;}
public void setSalary(double salary) {this.salary = salary;}
public Date getHireDay() {return hireDay; }
public void setHireDay(Date hireDay) {this.hireDay = hireDay;}
//获取当前员工的工资
public void raiseSalary(double byPercent){
double raise = salary*byPercent/100;
salary+=raise;
}
}
public class Manager extends Employee{
private double bonus;//奖金
public Manager(String name, double salary, Date hireDay, double bonus) {
super(name, salary, hireDay);
this.bonus = bonus;
}
@Override
public double getSalary() {
double baseSalary = super.getSalary();
return baseSalary+bonus;
}
}
Manager称作为子类、派生类,Employee称作超类、基类或父类 。经理属于员工,但是他却有比其他员工多的功能,例如经理有奖金(bonus),员工没有(所有还是努力工作当经理!!)。
在重写(@Override)的getSalary的方法中,使用了super关键字,表示引用父类的方法,如果不添加super关键字是无法使用的。下面了解一下super关键字的使用:
3.2.1 super关键字的使用
super关键字表示对某个类的父类的引用。
super有两种通用形式:第一种用来访问被子类的成员隐藏的父类成员;第二种则是可以调用父类的构造函数。使用形式如下:
调用父类的成员 super.属性
在Java语言中,用过继承关系实现对成员的访问是按照最近匹配原则进行的,规则如下:
(1)在子类中访问成员变量和方法时将优先查找是否在本类中已经定义,如果该成员在本类中存在,则使用本类的,否则,按照继承层次的顺序往父类查找,如果未找到,继续逐层向上到其祖先类查找。
(2)super特指访问父类的成员,使用super首先到直接父类查找匹配成员,如果未找到,再逐层向上到祖先类查找。
调用父类的构造函数 super(参数)
子类与父类的构造方法调用的顺序或关系如下:
(1)按继承关系,构造方法是从顶向下进行调用的。
(2)如果子类没有构造方法,则它默认调用父类无参的构造方法,如果父类中没有无参数的构造方法,则将产生错误。
(3)如果子类有构造方法,那么创建子类的对象时,先执行父类的构造方法,再执行子类的构造方法。
(4)如果子类有构造方法,但子类的构造方法中没有super关键字,则系统默认执行该构造方法时会产生super()代码,即该构造方法会调用父类无参数的构造方法。
(5)对于父类中包含有参数的构造方法,子类可以通过在自己的构造方法中使用super关键字来引用,而且必须是子类构造函数方法中的第一条语句。
(6)Java语言中规定当一个类中含有一个或多个有参构造方法,系统不提供默认的构造方法(即不含参数的构造方法),所以当父类中定义了多个有参数构造方法时,应考虑写一个无参数的构造方法,以防子类省略super关键字时出现错误。
3.2.2 阻止继承:final
有些时候一些类为了防止类或方法等中的信息遭到篡改,所以不让其他类继承,例如String类;则使final关键字.
final 关键字声明类可以把类定义为不能继承的,即最终类;或者用于修饰方法,该方法不能被子类重写。
3.2.2.3 抽象类和接口
抽象类
抽象类是将一些类中公用的属性和方法抽象到一个单独的类中,这个类作为其他类的基类。
public abstract class Person {
private String name;
//构造函数
public Person(){}
//抽象方法
public abstract String Person1e();
//非抽象方法
public String Personlew(){
return null;
}
}
抽象类的特点:
- 抽象类不能被实例化
- 抽象类中可以包含抽象方法和非抽象方法,继承抽象类后的对象可以直接访问抽象类的非抽象方法
- 抽象类的修饰符必须是public protected 这些,因为抽象类是必须要可以被继承的
- 抽象类中可以有构造方法,但是不能创建对象
抽象类使用的场景:
- 当拥有一些方法,并且有一些需要实现,有一些不需要实现的,可以使用抽象类
- 想要捕捉一些类的通用特性的时候,可以使用抽象类
接口
接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。
接口中里面包含的都是抽象方法和常量,一个类如果想要实现接口,必须实现所有的方法,否则要定义为抽象类。
/* 文件名 : Animal.java */
interface Animal {
public void eat();
public void travel();
}
/* 文件名 : MammalInt.java */
public class MammalInt implements Animal{
public void eat(){
System.out.println("Mammal eats");
}
public void travel(){
System.out.println("Mammal travels");
}
public int noOfLegs(){
return 0;
}
public static void main(String args[]){
MammalInt m = new MammalInt();
m.eat();
m.travel();
}
}
接口特性
- 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
- 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
- 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
抽象类和接口的区别
- 1. 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
- 2. 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
- 3. 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
- 4. 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
接口的继承
一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,子接口继承父接口的方法。
接口可以多继承,public interface Hockey extends Sports, Event
3.2.4 对象克隆
什么是克隆?
有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能 会需要一个和A完全相同新对象B,并且此后对B任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象确定的。在 Java语言中,用简单的赋值语句是不能满足这种需求的。要满足这种需求虽然有很多途径,但实现clone方法是其中最简单,也是最高效的手段。
使用clone方法的必要条件:
- 克隆的类必须要实现Cloneable接口,并且重写clone方法(实现Cloneable接口只是一种标志,真正重写的是在Object类中的clone方法),如果没有实现接口,就会抛出异常CloneNotSupportedException。
- 使用public访问修饰符重新定义clone方法
深克隆与浅克隆
浅克隆:
浅克隆就是复制一个对象的复本.若只需要复制对象的字段值(对于基本数据类型,如:int,long,float等,则复制值;对于复合数据类型仅复制该字段值,如数组变量则复制地址,对于对象变量则复制对象的reference
深克隆:
深克隆就是不但复制对象的基本类型的值,也要将该对象的引用类型的值也全部克隆过来,成为完全独立的完整的对象。
深克隆的方式有三种:序列化与反序列化、json转换、手动赋值(推荐,效率最高)
下面是深克隆与浅克隆的实例(使用的是手动赋值的方式):
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class Teacher implements Cloneable{
private String name;
private double salary;
private Date hireDay;
private String[] range;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public Date getHireDay() {
return hireDay;
}
public void setHireDay(Date hireDay) {
this.hireDay = hireDay;
}
public String[] getRange() {
return range;
}
public void setRange(String[] range) {
this.range = range;
}
public Teacher(String name, double salary, Date hireDay, String[] range) {
this.name = name;
this.salary = salary;
this.hireDay = hireDay;
this.range = range;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", salary=" + salary +
", hireDay=" + hireDay +
", range=" + Arrays.toString(range) +
'}';
}
@Override
public Object clone() throws CloneNotSupportedException {
Teacher teacher = null;
teacher = (Teacher)super.clone();
//加上这句话的话,就将数组进行了深克隆
//teacher.setRange(teacher.getRange().clone());
return teacher;
}
public static void main(String[] args) throws Exception{
String arr[] = {"1","2","2"};
Teacher teacher = new Teacher("张三",1200,new Date(),arr);
System.out.println("teacher:"+teacher.toString());
Teacher teacher1 = (Teacher)teacher.clone();
System.out.println("teacher1:"+teacher1.toString());
teacher1.setHireDay(new SimpleDateFormat("YYYY-mm-dd").parse("2014-6-7"));
//改变了数组后,如果直接继承父类的方法,浅克隆的是数组地址
teacher1.getRange()[0]="5";
teacher1.setRange(teacher1.getRange());
System.out.println("teacher:"+teacher.toString());
System.out.println("teacher1:"+teacher1.toString());
}
}
3.2.5 内部类
在 Java 中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。广泛意义上的内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类。
1、成员内部类
成员内部类是最普通的内部类,它的定义为位于另一个类的内部,形如下面的形式:
class Circle {
double radius = 0;
public Circle(double radius) {
this.radius = radius;
}
class Draw { //内部类
public void drawSahpe() {
System.out.println("drawshape");
}
}
}
成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。如果内部类与外部类的名称相同的情况下,想要调用外部类的成员要使用【外部类.this.成员】。
public class Car {
private String name;
private String code;
public class Type{
private String name;
private String g2;
public void methid(){
System.out.println(name);
System.out.println(Car.this.name);
}
}
}
在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问:
class Circle {
private double radius = 0;
public Circle(double radius) {
this.radius = radius;
getDrawInstance().drawSahpe(); //必须先创建成员内部类的对象,再进行访问
}
private Draw getDrawInstance() {
return new Draw();
}
class Draw { //内部类
public void drawSahpe() {
System.out.println(radius); //外部类的private成员
}
}
}
成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如下:
创建静态内部类对象的一般形式为: 外部类类名.内部类类名 xxx = new 外部类类名.内部类类名()
创建成员内部类对象的一般形式为: 外部类类名.内部类类名 xxx = 外部类对象名.new 内部类类名()
public class Test {
public static void main(String[] args) {
//第一种方式:
Outter outter = new Outter();
Outter.Inner inner = outter.new Inner(); //必须通过Outter对象来创建
//第二种方式:
Outter.Inner inner1 = outter.getInnerInstance();
}
}
class Outter {
private Inner inner = null;
public Outter() {
}
public Inner getInnerInstance() {
if(inner == null)
inner = new Inner();
return inner;
}
class Inner {
public Inner() {
}
}
}
2.局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。注意: 局部内部类就像是方法里面的一个局部变量一样,是不能有 public、protected、private 以及 static 修饰符的。
class People{
public People() {
}
}
class Man{
public Man(){
}
public People getWoman(){
class Woman extends People{ //局部内部类
int age =0;
}
return new Woman();
}
}
3.匿名内部类(重点!!)
匿名内部类应该是平时我们编写代码时用得最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更加容易维护。
public interface ParentInterface {
public void method();
}
public class Person {
private ParentInterface parentInterface ;
public ParentInterface getParentInterface() {
return parentInterface;
}
public void setParentInterface(ParentInterface parentInterface) {
this.parentInterface = parentInterface;
}
}
public class Test {
public static void main(String[] args) {
Person person = new Person();
person.setParentInterface(new ParentInterface() {
public void method() {
System.out.println("hh");
}
});
}
}
4.静态内部类
静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。
创建静态外部类的方式:外部类类名.内部类类名 xxx = new 外部类类名.内部类类名()
public class Car {
private static String name;
private String code;
private static void kk(){
}
public void CarType(){
new Type().methid();
}
public static final class Type{
private String name;
private String g2;
public void methid(){
System.out.println(Car.name);//可以使用
// System.out.println(Car.this.code);//报错,不能直接引用外部的非静态成员。
}
}
}
3.3 多态
多态是同一个行为具有多个不同表现形式或形态的能力。例如同样是动物的兔子和鱼,都有着运动的行为,但是两种动物的行为方式不同,这就是多态。
多态的优点
- 1. 消除类型之间的耦合关系
- 2. 可替换性
- 3. 可扩充性
- 4. 接口性
- 5. 灵活性
- 6. 简化性
多态存在的三个必要条件
- 继承
- 重写
- 父类引用指向子类对象
多态一般分为两种:重写式多态和重载式多态。
重载式多态,也叫编译时多态。也就是说这种多态再编译时已经确定好了。重载大家都知道,方法名相同而参数列表不同的一组方法就是重载。在调用这种重载的方法时,通过传入不同的参数最后得到不同的结果。
重写式多态,也叫运行时多态。这种多态通过动态绑定(dynamic binding)技术来实现,是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。也就是说,只有程序运行起来,你才知道调用的是哪个子类的方法。
向上转型
子类引用的对象转换为父类类型称为向上转型。通俗地说就是是将子类对象转为父类对象。此处父类对象可以是接口
public class Animal {
public void eat(){
System.out.println("animal eatting...");
}
}
public class Cat extends Animal{
public void eat(){
System.out.println("我吃鱼");
}
}
public class Dog extends Animal{
public void eat(){
System.out.println("我吃骨头");
}
public void run(){
System.out.println("我会跑");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Cat(); //向上转型
animal.eat();
animal = new Dog();
animal.eat();
}
}
//结果:
//我吃鱼
//我吃骨头
转型过程中需要注意的问题
- 向上转型时,子类单独定义的方法会丢失。比如上面Dog类中定义的run方法,当animal引用指向Dog类实例时是访问不到run方法的,
animal.run()
会报错。- 子类引用不能指向父类对象。
Cat c = (Cat)new Animal()
这样是不行的。
向上转型的好处
- 减少重复代码,使代码变得简洁。
- 提高系统扩展性。
向下转型
与向上转型相对应的就是向下转型了。向下转型是把父类对象转为子类对象
向下转型注意事项
- 向下转型的前提是父类对象指向的是子类对象(也就是说,在向下转型之前,它得先向上转型)
向下转型只能转型为本类对象(猫是不能变成狗的)。