面向对象的三大特征:封装、继承、多态。
一、封装
是指将对象的状态信息都隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问。封装的优点:
- 隐藏类的实现细节
- 良好的封装能够减少耦合
- 便于修改,提高代码的可维护性
- 可进行数据的检查,有利于保证对象的信息完整性
二、继承
继承就是子类继承父类的特征和行为(通过关键字extends),使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。每一个子类只有一个直接的父类。
- 继承的特性
1.子类拥有父类非private的属性,方法
2.子类可以拥有自己的属性和方法,即子类可以对父类进行扩展
3.子类可以用自己的方式实现父类的方法
4.Java的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如A类继承B类,B类继承C类,所以按照关系就是C类是B类的父类,B类是A类的父类。 - this 和 super
this关键字:指向自己的引用。构造器中引用该构造器正在初始化的对象;在方法中引用调用该方法的对象
super关键字:用来实现对父类成员的访问,引用当前对象的父类。子类定义的实例方法中可通过super来访问父类中被隐藏的实例变量。
总结:this和super都不能出现在static修饰的方法中;this调用的是同一类中的重载的构造方法,super调用的是其父类的构造方法,且调用时都必须放在第一行。
public class Test003 extends TestAll{
public Test003(){
super("003","me");
System.out.println("Test003 无参构造方法");
}
public static void main(String [] args){
new Test003();
}
}
class TestAll extends Tester{
public TestAll(String date){
System.out.println("TestAll 带一个参数构造方法,date=" +date);
}
public TestAll(String date, String who){
this(date);
System.out.println("TestAll 带两个参数构造方法,date="+ date +" who="+who);
}
}
class Tester{
public Tester(){
System.out.println("Tester 无参构造方法");
}
}
// 打印结果为:
/*
Tester 无参构造方法
TestAll 带一个参数构造方法,date=003
TestAll 带两个参数构造方法,date=003 who=me
Test003 无参构造方法
*/
三、多态
多态是同一个行为具有多个不同表现形式或形态的能力。同一个实现接口,使用不同的实例而执行不同的操作。
引用变量的两种类型:编译时类型--由声明该变量时使用的类型决定;运行时类型--由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就可能出现多态。
public class SubClass extends BaseClass {
public String book = "READ BOOK";
public void sub(){
System.out.println("子类的普通方法");
}
public void test(){
System.out.println("子类test方法 , 《"+this.book+"》");
}
public static void main(String [] args){
BaseClass bc = new BaseClass(); // 编译时类型与运行时类型一样,不存在多态
System.out.println(bc.book); // 10
bc.base(); //父类普通方法
bc.test(); //父类test方法 , book=10
// bc.sub(); 编译报错,父类不可以调用子类的方法
SubClass sc = new SubClass(); // 编译时类型与运行时类型一样,不存在多态
System.out.println(sc.book); //READ BOOK
sc.base(); //父类普通方法 ; 子类可以调用父类的方法
sc.sub(); //子类的普通方法
sc.test(); //子类test方法 , 《READ BOOK》 ; 覆盖了父类的test方法
BaseClass b = new SubClass(); // 编译时类型与运行时类型不一样,多态发生
System.out.println(((SubClass) b).book); // READ BOOK
System.out.println(b.book); // 10
b.base(); //父类普通方法
if(b instanceof BaseClass){
((SubClass) b).sub(); // 子类的普通方法 ; 强制类型转换
}
b.test(); //子类test方法 , 《READ BOOK》
}
}
class BaseClass{
public int book = 10;
public void base(){
System.out.println("父类普通方法");
}
public void test(){
System.out.println("父类test方法 , book=" + this.book);
}
}
该实例中 将子类的对象直接赋值给父类的引用变量b ,无需任何转型(向上转型)【子类是一种特殊的父类】。引用变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法
四、重载与重写
重写:子类包含与父类同名方法的现象,也称为方法的覆盖,重写发生在子类和父类的同名方法之间。重写遵循的规则:方法名相同、形参列表相同;子类的返回值类型比父类的更小或相等。;子类方法声明抛出的异常比父类的更小或相等;子类方法的访问权限比父类的更大或相等。
重载:主要发生在同一个类的多个同名方法之间。重载遵循的规则:参数个数或类型不一样;返回类型、修饰符也可以不一样;无法以返回值类型作为重载的区分标准。(典型的重载--构造器重载)
五、接口与抽象类
共同:
- 接口和抽象类都不能实例化,都位于继承树的顶端,用于被其他类实现和继承
- 接口和抽象都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。
差别:
- 接口里只能包含抽象方法、静态方法和默认方法,不能为普通方法提供方法的实现;抽象类则完全可以包含普通方法。
- 接口只能定义静态常量,不能定义普通成员变量;抽象类里既可以定义普通成员遍历,也可以定义静态常量。
- 接口里不包含构造方法;抽象类里可以包含构造方法--构造方法并不是用于创建对象,而是让子类调用这些构造方法来完成属于抽象类的初始化操作。
- 接口不能包含初始化块;抽象类可以包含初始化块。
- 一个类最多只能有一个直接的父类,包括抽象类;但一个类可以实现多个接口。一个接口可继承多个接口。
注意:接口里定义的内部类、接口、枚举默认都采用public static两个修饰符,不管定义时是否指定了这俩个修饰符,系统会自动使用public static对它们进行修饰。
public class Test0804 extends Speed implements Transport{
// 抽象类和接口都不能实例化,通过子类的构造方法进行初始化,
public Test0804(){
//super(); //可以隐藏
System.out.println("我是子类的无参构造方法");
}
public Test0804(int i) {
// 调用父类的构造方法初始化
super(i);
System.out.println("我是子类的带参构造方法");
}
public static void main(String [] args){
// 接口
Test0804 t = new Test0804();
System.out.println("我是静态变量:"+Transport.HEIGHT);
Transport.tools();
t.run();
t.use();
System.out.println("----------------分割线-----------------");
// 抽象类
Test0804 t1 = new Test0804(2);
Speed.is();
t1.has(); // 通过子类实例对象,调用父类的普通方法
System.out.println("私有变量的值为:" + t1.getCount());
t1.print();
/* 结果为:
我是无参构造方法
我是子类的无参构造方法
我是静态变量:5
我是静态方法
实现了抽象方法 run
实现了默认方法 use
----------------分割线-----------------
我是带参构造方法
我是子类的带参构造方法
抽象类的静态方法
我是普通方法 count=2
私有变量的值为:2
实现了抽象类的抽象方法
*/
}
// 重写接口的 run 方法
@Override
public void run() {
System.out.println("实现了抽象方法 run");
}
// 重写接口的 use 方法
@Override
public void use() {
System.out.println("实现了默认方法 use");
}
// 重写抽象类的 print 方法
@Override
public void print() {
System.out.println("实现了抽象类的抽象方法");
}
}
interface Transport{
/* 接口里不包含构造方法,不能定义普通成员变量,不能为普通方法提供方法的实现,不能包含初始化块
private int i =0; //编译报错
public Transport(){ // 编译报错
}
public void run1(){ // 编译报错
System.out.println("我是普通方法");
}
// 编译报错
{
System.out.println("初始化块");
}
*/
int HEIGHT = 5; // 系统会自动添加 public static final
static void tools(){ // 系统会自动添加 public
System.out.println("我是静态方法");
}
void run(); // 抽象方法 自动添加 public abstract
default void use() {
System.out.println("我是默认方法");
}
}
abstract class Speed{
private int count;
// 显示声明无参构造方法
public Speed(){
System.out.println("我是无参构造方法");
}
public Speed(int count){
System.out.println("我是带参构造方法");
this.count = count;
}
public abstract void print();
public static void is(){
System.out.println("抽象类的静态方法");
}
public void has(){
System.out.println("我是普通方法 count="+ this.count);
}
public int getCount(){
return count;
}
}
六、继承与组合
继承表达式一种“是(is-a)”的关系,而组合表达的是“有(has-a)”的关系。对于继承而言,子类可以直接获得父类的public方法,程序使用子类时,将可以直接访问该子类从父类那里继承的方法;而组合则是把旧类对象作为新类的成员变量组合进来,用以实现新类的功能,用户看到的是新类的方法,而不能看到被组合对象的方法。
// ------继承------
public class InheritTest{
public static void main(String [] args){
Bird b = new Bird();
b.breath();
b.fly();
Fish f = new Fish();
f.breath();
f.swim();
}
}
class Animal {
private void beat(){
System.out.println("心脏跳动...");
}
public void breath(){
beat();
System.out.println("吸一口气,吐一口气,呼吸中...");
}
}
//继承Animal ,直接复用父类的 breath 方法
class Bird extends Animal{
public void fly(){
System.out.println("我在天空飞翔...");
}
}
//继承Animal ,直接复用父类的 breath 方法
class Fish extends Animal{
public void swim(){
System.out.println("我在水里游动...");
}
}
// ------组合------
public class CompositeTest {
public static void main(String [] args){
Bird1 b1 = new Bird1(new Animal1());
b1.breath();
b1.fly();
Fish1 f1 = new Fish1 (new Animal1());
f1.breath();
f1.swim();
}
}
class Animal1 {
private void beat(){
System.out.println("心脏跳动...");
}
public void breath(){
beat();
System.out.println("吸一口气,吐一口气,呼吸中...");
}
}
// 将原来的父类组合到原来的子类,作为子类的一个组合成分
class Bird1{
private Animal1 a;
public Bird1(Animal1 a){
this.a = a;
}
// 直接复用 Animal1提供的 breath 方法来实现 Bird1 的 breath 方法
public void breath(){
a.breath();
}
public void fly(){
System.out.println("我在天空飞翔...");
}
}
// 将原来的父类组合到原来的子类,作为子类的一个组合成分
class Fish1{
private Animal1 a;
public Fish1(Animal1 a){
this.a = a;
}
// 直接复用 Animal1提供的 breath 方法来实现 Fish1 的 breath 方法
public void breath(){
a.breath();
}
public void swim(){
System.out.println("我在水里游动...");
}
}
七、初始化块
Java使用构造器对单个对象进行初始化操作,使用构造器先完成整个java对象的状态初始化,然后将java对象返回给程序。与构造器作用类似的是初始化块,也可以对java对象进行初始化操作。
构造器与初始化块: 从某种程度来看,初始化块是构造器的补充,初始化块总是在构造器执行之前执行。
public class InitBlock {
public static void main(String [] args){
// 类初始化阶段(编译阶段),先执行最顶层父类的静态初始化块,直到执行当前类的静态初始化块
// 对象初始化阶段(执行阶段),先执行最顶层的父类初始化块、构造方法。直到执行到当前类的初始化、构造方法。
new Leaf();
/* 结果为:
Root 的静态初始化块
Mid 的静态初始化块
Leaf 的静态初始化块
Root 的普通初始化块
Root 的无参构造方法
Mid 的普通初始化块
Mid 的无参构造方法
Mid 的带参构造方法, 参数值:This is a test
Leaf 的普通初始化块
执行 Leaf 构造方法
*/
}
}
class Root{
static{
System.out.println("Root 的静态初始化块");
}
{
System.out.println("Root 的普通初始化块");
}
public Root(){
System.out.println("Root 的无参构造方法");
}
}
class Mid extends Root{
static{
System.out.println("Mid 的静态初始化块");
}
{
System.out.println("Mid 的普通初始化块");
}
public Mid(){
System.out.println("Mid 的无参构造方法");
}
public Mid(String msg){
this();
System.out.println("Mid 的带参构造方法, 参数值:"+msg);
}
}
class Leaf extends Mid{
static{
System.out.println("Leaf 的静态初始化块");
}
{
System.out.println("Leaf 的普通初始化块");
}
public Leaf(){
super("This is a test");
System.out.println("执行 Leaf 构造方法");
}
}
注:
当JVM 第一次主动使用某一个类时,系统会在类准备阶段为该类的所有静态成员变量分配内存;初始化阶段则负责初始化这些静态成员变量,初始化静态成员变量就是执行类初始化代码块或声明类成员变量时指定的初始值,它们执行顺序与源代码中的排列顺序相同
注:讲解较详细的博客
本文大部分内容来自于《JAVA疯狂讲义》