一、封装(encapsulation)
从表面意思来看就是封闭的包装。而Java中,一个类有属性和方法,但是不可能想修改就修改,毫无安全性可言,而且有的类只适用于某一场景,其内部的方法和属性不对外开放。这就是封装的作用,用来设置访问权限。
下图为Java的访问权限符和其作用域
访问范围从大到小:public --> proetected --> default --> private
public:修饰之后,能被所有类访问,访问权限最宽松。
protected: 作用域只在同类、同包和子类中,protected使用最多场景就是父类方法给子类重写。
default: 作用域只在同类和同包中。未使用权限修饰符,默认采用。
private: 作用域只在同一个类中。常用的就是成员变量私有化,只有同一个类中才能访问。即暴露出来的setter 和 getter方法来访问,保证了安全性。还有对构造器私有化。工厂模式或者单例模式中有用到。注意,默认构造器私有时,其子类的构造器无法使用,这涉及到子类初始化时会对父类初始化。
import 与 package 关键字的区别:
package 表示当前类属于那个包中。从作用域来看,包也是一种权限,不同包的类是无法相互访问的,包的作用域和default差不多。
import是用来导入别的包。比如要使用Math类,但Math类不属于同一个包中,那相当于不存在,所以无法使用。这就需要import 来导入一个Math类所在的包,这样才可以使用该包中的类。
在Java中,java.lang包是默认导入的,里面都是java的核心类,如String 类等。
二、继承(extend)
使用继承,子类可以得到父类所有字段和方法,除了private所修饰的。继承增加了代码的复用。而且继承是多态实现的基础。多说一下,继承只能单继承,但是接口却可以实现多个,接口相当于是多继承。但是接口是一个特别抽象类,只提供规范,不提供具体实现。而且接口中只能定义常量。
下面代码可以看出继承和包权限的作用:
package com.test1; //使用com.test1 包
//A类作为父类
public class A {
private int a; //私有化
protected int b; //子类能访问
public int c;//访问权限最高
public static int d = 10086; //A的静态变量
public static void infoPub() {System.out.println("A类的public方法 :" + new A().a);} //a是私有的,在同类中可以访问
protected static void infoPro() {System.out.println("A类的protected方法");}
private static void infoPri() {System.out.println("A类的private方法");}
}
package com.test2;//使用com.test2 包
import com.test1.A; //A类不在同包中,所以要导入。
//继承A类
public class B extends A {
//调用继承A类的方法
public void testB() {
infoPro(); //A类的protected修饰的方法
infoPub(); //A类的public修饰的方法
System.out.println(d);//子类也能获得父类的类变量
}
//在B中类中启动主方法来测试
public static void main(String[] args) {
B b = new B();
//B类中只能得到A类的 b变量和c变量
//b 是 protected修饰的,子类中能访问
//c 是public修饰的,哪里都能访问
System.out.println(b.b);
System.out.println(b.c);
b.testB();
}
}
使用另一个非子类来测试,可以看到只有c变量能被访问到,而b变量已经无法访问了,因为b变量是protected修饰。
下图会看到有equals() 、getClass()、hasCode()等方法 。这是因为在Java中,有一个基类,那就是Object类,他是所有类的父类,只要创建一个类就默认继承了Object类,所以会得到Object类的方法。在类型转换中,所有类都可以转换成Object类。
三、多态(Ploymorphism)
多态的定义:指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。
按个人理解,就是做同样的事情,呈现不同的结果。而且多态提高了代码的通用性和灵活性,在面向对象中,多态是重点。
要实现多态的前提:
一、要有继承关系;
二、子类对父类进行重写;
三、父类引用指向子类对象。
二、子类对父类进行重写;
三、父类引用指向子类对象。
下面给上一个简单的多态实现代码:
package com.test.polymorphsim;
//父类
public class Father {
public int a;
protected Father() {System.out.println("Father类的默认构造器");}
//看书
protected void readBook() {
System.out.println("Father喜欢看科学书");
}
//玩游戏
protected void playGame() {
System.out.println("Father喜欢玩体育游戏");
}
}
//大儿子类
class Son1 extends Father{
public Son1() {System.out.println("Son1默认构造器");}
//重写父类方法
@Override
protected void readBook() {
System.out.println("大儿子喜欢看游戏书");
}
//如果Son1类不重写父类的playGame(),多态时调用就会调用父类的该方法
@Override
protected void playGame() {
// TODO Auto-generated method stub
super.playGame();
}
}
//二儿子类
class Son2 extends Father{
public Son2() {System.out.println("Son2默认构造器");}
@Override
public void readBook() {
System.out.println("二儿子看编程书");
}
@Override
public void playGame() {
System.out.println("二儿子喜欢玩单机游戏");
}
}
//三儿子类
class Son3 extends Father{
public Son3() {System.out.println("Son3默认构造器");}
@Override
protected void readBook() {
System.out.println("三儿子看数学书");
}
@Override
protected void playGame() {
System.out.println("三儿子玩数字游戏");
}
}
package com.test.polymorphsim;
public class Test {
public static void main(String[] args) {
//父类引用指向子类对象
Father son1 = new Son1();
Father son2 = new Son2();
Father son3 = new Son3();
//传入上面参数
show(son1);
show(son2);
show(son3);
}
//根据传入的Father对象,呈现不同结果
//传入一个Father类型参数,调用Father类中的方法。
public static void show(Father fa) {
fa.readBook();
fa.playGame();
}
}
//输出结果
Father类的默认构造器
Son1默认构造器
Father类的默认构造器
Son2默认构造器
Father类的默认构造器
Son3默认构造器
大儿子喜欢看游戏书
Father喜欢玩体育游戏
二儿子看编程书
二儿子喜欢玩单机游戏
三儿子看数学书
三儿子玩数字游戏
在show()方法中,只要传入一个Father参数,就可以调用其中的readBook() 和 playGame() 方法。也就是方法是固定的,但是根据我们传入的参数,会出现不同的结果。这就是多态的灵活性和通用性。
在输出结果中会看到出现三次Father类的默认构造器。这是因为在Java中,只要创建一个类的对象,就意味着该类会被初始化。
这要提到Java的类加载机制,步骤简要分为三步,加载--连接--初始化,一般情况都是直接完成三步。在初始化一个类时,会先初始化该类的父类,保证一整个继承体系都初始化完成,才能开始使用该类。
在上面代码中创建了三个子类对象,所以父类的无参构造器会被调用三次。