封装
概念:对象属性的封装隐藏,方法的公开;属性私有化后,则其他类不能直接使用对象名.属性名访问,必须通过提供的公开方法。控制在程序中属性的读和修改的访问级别。
目的:增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过外部接口,一特定的访问权限来使用类的成员。
基本要求:把所有的属性私有化,对每个属性提供getter和setter方法
一个小例子:
//封装之前
public class TestDemo {
public String name;
public String address;
public String age;
public TestDemo(String name, String address, String age) {
this.name = name;
this.address = address;
this.age = age;
}
TestDemo() {}
}
//调用是这样的
public class Test {
public static void main(String[] args) {
TestDemo testDemo = new TestDemo();
String name = testDemo.name;
String address = testDemo.address;
String age = testDemo.age;
testDemo.name = "hello";
}
}
//封装之后
public class TestDemo {
private String name;
private String address;
private String age; //构造私有属性,并且不对外公开获取,只能从构造函数设置
public TestDemo(String name, String address, String age) {
this.name = name;
this.address = address;
this.age = age;
}
TestDemo() {
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//使用是这样的
public class Test {
public static void main(String[] args) {
TestDemo testDemo = new TestDemo();
TestDemo testDemo1 = new TestDemo("Hello", "world", "man");
String name = testDemo1.getName();
String address = testDemo1.getAddress();
//String age = testDemo1.getAge(); //无法获取age
testDemo1.setName("world"); //调用公开方法进行属性的修改
}
}
继承
实现代码的复用,可以基于已经存在的类构造一个新类,继承已存在的类就是复用这个类的方法和域,在此基础上还可以添加一些新的方法和域,一个类只能继承一个类
缺点:
- 父类变,子类就必须变。
- 继承破坏了封装,对于父类而言,它的实现细节对与子类来说都是透明的。
- 继承是一种强耦合关系。
访问修饰符:
- private:仅对本类可见
- protected:对本包和所有子类可见
- public:对所有类可见
- 默认(无修饰):对本包可见
继承和权限:
| Public | 无修饰 | Private | Protected | final | abstract | static | |
| 类继承 | 可继承 | 同一包中的可继承 | 不能修饰类 | 不能修饰类 | 不能派生子类(不可继承) | 一般可继承 | 不能修饰类 |
| 方法重载 | 可重载 | 可重载 | 不能重载 | 可重载 | 不可重载 | 可重载 | 可重载(修饰主函数就不能重载) |
| 成员变量 | 父类属性被隐藏,使用调用super | 父类属性被隐藏,使用调用super | 子类不能直接访问父类的私有变量(通过父类构造器初始化父类私有变量) | 父类属性被隐藏,使用调用super | 必须赋初值 | 不能修饰成员变量 | 每个实例共享这个类变量 |
类、超类、子类
引例:一家公司,有经理、普通员工等等,但是所有人都是老板的雇员,以此设计它们之间的关系
分析:不管是经理、普通员工都是雇员的一类,但是各种身份又有一些不同的属性、不同的功能
- 定义父类(超类) Employee
public class Employee {
/**
* 姓名
*/
private String name;
/**
* 工资
*/
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public double getSalary() {
return salary;
}
}
- 定义子类 Manager
public class Manager extends Employee {
/**
* 奖金信息,特有的属性
*/
private double bonus;
public Manager(String name, double salary) {
//Manager的构造器不能访问父类的私有域,所以通过父类的构造器进行初始化
super(name, salary);
bonus = 0;
}
/**
* 重写父类的方法
*/
@Override
public double getSalary() {
//调用父类的方法
double baseSalary = super.getSalary();
return baseSalary + bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
}
继承关系:
强制类型转化:
- 只能在继承层次内进行类型转换
- 在将超类转换成子类之前,应该使用instance检查
Object:所有类的超类
Object类是Java中所有类的始祖,如果一个类没有明确指出超类,那么默认超类是Object。
-
euqals方法
用于检测一个对象是否等于另一个对象,判断两个对象是都具有相同的引用public boolean equals(Object obj) { return (this == obj); }一般equals和==是不一样的,但是在Object中两者是一样的
-
toString
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } -
getClass 获得运行时类型
public final native Class<?> getClass(); -
hashCode
public native int hashCode();
该方法用于哈希查找,可以减少在查找中使用equals的次数,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。一般必须满足obj1.equals(obj2)==true。可以推出obj1.hash- Code()==obj2.hashCode(),但是hashCode相等不一定就满足equals。不过为了提高效率,应该尽量使上面两个条件接近等价。
-
notify 唤醒在该对象上等待的某个线程
public final native void notify(); -
notifyAll 唤醒在该对象上等待的所有线程
public final native void notifyAll(); -
wait(long timeout) 在规定时间内没有获得锁就返回
public final native void wait(long timeout) throws InterruptedException; -
wait(long timeout, int nanos)
在规定时间内没有获得锁就返回,有一个附加时间public final void wait(long timeout, int nanos) throws InterruptedException { if (timeout < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos > 0) { timeout++; } wait(timeout); } -
wait() 一直等待,直到获得锁或者被中断
public final void wait() throws InterruptedException { wait(0); } -
finalize 用于释放资源
protected void finalize() throws Throwable { } -
clone
protected native Object clone() throws CloneNotSupportedException;
实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法。主要是JAVA里除了8种基本类型传参数是值传递,其他的类对象传参数都是引用传递,我们有时候不希望在方法里讲参数改变,这是就需要在类中复写clone方法。
抽象类、接口
抽象类
类也可以继承抽象类,抽象类可以继承类,也可以继承抽象类,可以实现接口。
-
abstract
使用abstract class 定义的类都是抽象类,可以含有、也可以不含有抽象方法
还是上面的例子,不管是经理还是雇员,都是人,所以人可以定义为一个抽象类//抽象类Person abstract class Person { public abstract String getDescription(); private String name; public Person(String name) { this.name = name; } public String getName() { return name; } } //Employee继承Person public class Employee extends Person { private double salary; public Employee(String name, double salary) { super(name); this.salary = salary; } @Override public String getDescription() { return "a employee"; } public double getSalary() { return salary; } } //定义新的Student类继承Person public class Student extends Person { private String major; public Student(String name, String major) { super(name); this.major = major; } public String getMajor() { return major; } @Override public String getDescription() { return "a student" + major; } } -
注意问题:
- 抽象类不能被实例化,实例化的工作应该交由它的子类来完成,它只需要有一个引用即可。
- 抽象方法必须由子类来进行重写
- 只要包含一个抽象方法的抽象类,该方法必须要定义成抽象类,不管是否还包含有其他方法
- 抽象类中可以包含具体的方法,当然也可以不包含抽象方法
- 子类中的抽象方法不能与父类的抽象方法同名
- abstract不能与final并列修饰同一个类
- abstract 不能与private、static、final或native并列修饰同一个方法
接口
类可以实现接口,抽象类也可以实现接口,但是接口只能实现接口。类、抽象类、接口都可以实现多个接口
-
接口示例:
Comparable接口public interface Comparable { int compareTo(Object other); } //jdk 1.5后使用泛型定义 public interface Comparable<T> { int compareTo(T other); }使用 implements关键字
public class Employee extends Person implements Comparable { ... public int compareTo(Object other) { Employee other = (Employee) other; //使用泛型后不用强转了,比较的方式自定义 return Double.compare(salary, other.salary); } } -
接口的默认方法
可以为接口提供一个默认实现,必须用default修饰符标记public interface Comparable { default int compareTo(Object other) { return 0; } }默认方法可能导致冲突,例如现在一个接口中将方法定义为默认方法,然后又在超类或另一个接口中定义了同样的方法
-
接口的继承
一个接口可以继承另一个接口public interface Work { public void work(); } public interface Study extends Work { public void study(); } //实现Study的时候会重写两个方法 public class Student extends Person implements Study { ... public void work() {} public void study() {} }Study接口自己声明了一个方法,又继承了Work,所以实现Study接口的时候,一共有两个方法需要重写
-
接口与回调
参考一个经典例子让你彻彻底底理解java回调机制 -
注意事项:
- 一个Interface的所有方法访问权限自动被声明为public。确切的说只能为public,当然你可以显示的声明为protected、private,但是编译会出错!
- 接口中可以定义“成员变量”,或者说是不可变的常量,因为接口中的“成员变量”会自动变为为public static final。可以通过类命名直接访问:ImplementClass.name
- 接口中不存在实现的方法
- 实现接口的非抽象类必须要实现该接口的所有方法。抽象类可以不用实现
- 不能使用new操作符实例化一个接口,但可以声明一个接口变量,该变量必须引用(refer to)一个实现该接口的类的对象。可以使用 instanceof 检查一个对象是否实现了某个特定的接口。例如:if(anObject instanceof Comparable){}。
- 在实现多接口的时候一定要避免方法名的重复
多态
态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。
例子:利用抽象类的例子写一个测试类
public class Test {
public static void main(String[] args) {
Person person = new Employee("Employee", 5000); //向上转型为Person,person指向Employee
System.out.println(person.getDescription()); //最终调用的是Employee重写的方法
Employee employee = (Employee) person; //向下转型,需要强制转换
System.out.println(employee.getDescription()); //调用的是Employee重写的方法
person = new Student("Student", "java"); //向上转型,将person重新指向一个新的Student
System.out.println(person.getDescription()); //调用Student重写的方法
}
}
/** 结果
* a employee
* a employee
* a studentjava
* /
Employee是Person的子类, Person person = new Employee(“Employee”, 5000) 编译时变量和运行时变量不一样,所以多态发生了。
(1)Person person作为一个引用类型数据,存储在JVM栈的本地变量表中。
(2)new Employee(“Employee”, 5000)作为实例对象数据存储在堆内存中
Employee的对象实例数据(接口、方法、field、对象类型等)的地址也存储在堆中
Employee的对象的类型数据(对象实例数据的地址所执行的数据)存储在方法区中,方法区中对象类型数据中有一个指向该类方法的方法表。(3)Java虚拟机规范中并未对引用类型访问具体对象的方式做规定,目前主流的实现方式主要有两种:
1. 通过句柄访问
在这种方式中,JVM堆中会专门有一块区域用来作为句柄池,存储相关句柄所执行的实例数据地址(包括在堆中地址和在方法区中的地址)。这种实现方法由于用句柄表示地址,因此十分稳定。
2.通过直接指针访问
通过直接指针访问的方式中,reference中存储的就是对象在堆中的实际地址,在堆中存储的对象信息中包含了在方法区中的相应类型数据。这种方法最大的优势是速度快,在HotSpot虚拟机中用的就是这种方式。(4)实现过程
首先虚拟机通过reference类型(Person的引用)查询java栈中的本地变量表,得到堆中的 对象类型数据的地址,从而找到方法区中的对象类型数据(Employee的对象类型数据),然后查询方法表定位到实际类(Employee类)的方法运行。
多态存在的三个必要条件
-
继承
如例子中的Employee是Person的子类 -
重写
Employee重写了Person的getDescription方法 -
父类引用指向子类对象
Person person = new Employee(“Employee”, 5000);
多态的优点
- 消除类型之间的耦合关系
- 可替换性
- 可扩充性
- 接口性
- 灵活性
- 简化性
多态性
多态性分为编译时的多态性和运行时的多态性。
运行时的多态性
方法重写(override)实现的是运行时的多态性(也称为后绑定),运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事:1. 方法重写(子类继承父类并重写父类中已有的或抽象的方法);2. 对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。编译时的多态
方法重载(overload)实现的是编译时的多态性(也称为前绑定)。
-
方法调用绑定
将一个方法主体关联起来被称作绑定
(1)前期绑定(静态绑定):在程序执行之前进行绑定(默认的绑定方式)
(2)后期绑定(动态绑定或运行时绑定):在运行时根据对象的类型进行绑定Java中除了static方法和final方法(private方法属于final方法)之外,其他所有的方法都是后期绑定
-
方法的型构:指方法的组成结构,具体包括方法的名称和参数,涵盖参数的数量、类型以及出现的顺序,但是不包括方法的返回值类型,访问权限修饰符,以及 abstract、static、final 等修饰符。
-
相同型构
public void method(String s) {} public String method(String s) {} -
不同型构
public void method(int i, String s) {} public void method(String s, int i) []
-
-
重载
指在同一个类中定义了一个以上具有相同名称,但是型构不同的方法。 -
重写
指在继承情况下,子类中定义了与其父类中方法具有相同型构的新方法,就称为子类把父类的方法重写了(子类必须重写父类为抽象类的方法)。这是实现多态必须的步骤