封装(Encapsulation)
1. definition
封装是指将类的数据或者过程隐藏起来的一个过程。在面向对象程序设计中,一个类以及其行为和属性都应该被视作一个整体,所以是不应该让其他类随意修改的。封装的目的就是将这些属性和方法通过访问域关键字隐藏后,提供一个简单的访问接口给其他类。
public class Person{
private String name;
private int age;
public int getAge(){
return age;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age = age;
}
public void setName(String name){
this.name = name;
}
}
2. 为什么不直接把属性设为public,而用getter和setter方法?
如果直接访问,那么任何程序都可以随意更改这个属性的值,即使把他置空或者置为非法值
这里不光是指语法错误,可能是语义错误。比如规定x>0,直接设为了x<=0)。
通过间接访问的方式,可以通过setter方法来对其赋值过程进行一个过滤和加工。虽然通常并没有过滤的这个过程,但是一旦出错,我们可以直接在setter中实现。
另外,getter方法可以决定是返回其真实值还是克隆抑或是其他任意安全的值。
继承(inheritance)
1. definition
继承是面向对象中复用代码的一种手段,一个类可以衍生出子类,而子类通常是在功能或者行为上与父类相似的但却更具体的类。
class 父类 {
}
class 子类 extends 父类 {
}
Java中支持多重继承(纵向),但并不能同时继承多个(横向)
final 关键字声明类可以把类定义为不能继承的,即最终类;或者用于修饰方法,该方法不能被子类重写
2. 构造方法
当调用子类的构造函数时,子类会(隐式或显式)调用父类的构造函数。
如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。
如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。
class A{
A(){System.out.println("A");}
A(int i){System.out.println("A:"+i);}
}
class Aa extends A{
Aa(int i){}
}
当直接调用new Aa(i)时,会隐式调用super()即A(),这个时候会输出A;而当A不存在第一种构造函数时,则必须在Aa(int i)中显式的调用super(int i)。
隐式的调用一定是无参的构造函数
接口(Interface)和抽象类(Abstract Class)
1. 抽象类
不可以被实例化的一种特殊的类,但是可以被继承。
抽象方法
public abstract class Employee
{
private String name;
private String address;
private int number;
public abstract double computePay();
//其余代码
}
抽象方法就是一个并没有实现的方法,有抽象方法的类一定是一个抽象类,但是抽象类并不一定含有抽象方法
子类如果继承抽象类的话,如果该抽象类中存在抽象方法,那么子类必须实现该抽象方法才能被实例化;否则必须也声明为抽象类。
显然一个没有被实现的方法如果被实例化是不符合逻辑的,因为根本不能调用
2. 接口
接口应该可以被理解为一种行为方式的规范,当实体类需要做某些事的时候,需要按照这些规范去实现,而怎么实现可以不必关心。
interface Animal {
public void eat();
public void travel();
}
与抽象类的区别:
- 接口中的所有方法必须都是公共的抽象方法。在实现上是隐式的,也就是所有方法前面默认加上了public abstract
- 接口中的所有属性都隐式的加上了static final,也就是说是静态不可修改的,同时这种情况下必须在定义的时候就对变量进行初始化。
- 一个类可以同时实现多个接口
多态(Polymorphism)
1. definition
多态就是指同一个行为可以通过多种形态进行表现,具体到代码中就是一个方法可以由多种途径实现(重载和重写)。
2. 实现
class A{
void s() {};
}
class Aa extends A{
void s() {System.out.println(1111);};
void s(int i) {System.out.println(i);};
}
Aa中第一种s的实现叫做重写(override)即把父类的代码覆盖了,第二种叫对s()的重载(overload)即重新定义了一种新的名为s的方法的形态。
向上/下 转型
1. 向上转型(从具体到抽象)
向上转型: 用子类的对象去实例化父类,父类可以调用被覆盖的方法
比如
class A {
public void print() {
System.out.println("A:print");
}
}
class B extends A {
public void print() {
System.out.println("B:print");
}
}
public class Test{
public static void main(String args[])
{
A a = new B(); //通过子类去实例化父类
a.print();
}
}
最终打印的结果是"B:print"
这是因为我们通过子类B去实例化的,所以父类A的print方法已经被子类B的print方法覆盖了。
另外要注意:
向上转型父类引用只能调用父类中原本存在而被子类覆盖的方法,子类单独的方法不可调用
2. 向下转型(从抽象到具体)
父类对象强制转换成子类对象
比如:
class A {
public void print() {
System.out.println("A:print");
}
}
class B extends A {
public void print() {
System.out.println("B:print");
}
public void funcB(){
System.out.println("funcB");
}
}
class C extends A {
public void print() {
System.out.println("C:print");
}
public void funcC(){
System.out.println("funcC");
}
}
public class Test{
public static void func(A a)
{
a.print();
if(a instanceof B)
{
B b = (B)a; //向下转型,通过父类实例化子类
b.funcB(); //调用B类独有的方法
}
else if(a instanceof C)
{
C c = (C)a; //向下转型,通过父类实例化子类
c.funcC(); //调用C类独有的方法
}
}
public static void main(String args[])
{
func(new A());
func(new B());
func(new C());
}
}
很容易想到结果,这里就不给出了
通过向下转型可以单独调用子类其特有的方法
3. 结论
向上向下转型的好处在于编写函数的时候,只需将父类(接口)定义为参数即可,因为所有子类都可以看成是特殊的父类;而向下转型感觉一般用的比较少