你之因此能优于别人,正是由于你坚持了别人所不能坚持的。
本文相关代码在个人Github,欢迎Star~
https://github.com/zhangzhibo1014/DaBoJava
在此以前,咱们已经把Java基础的基础语法总结了一下,今天咱们来学习一下面向对象的相关知识,今天的内容理论性偏多,但愿你们能耐心的看完,相信会收获不少。都说 Java
是面向对象程序设计的语言,那么究竟什么是面向对象呢?若是你没有面向对象程序设计的应用背景,那么和我一块儿来认真的阅读本文吧!java
面向对象程序设计(简称 OOP
),是当今主流的设计范型。面向对象程序是由对象组成的,每一个对象包含对用户公开的特定功能部分和隐藏的实现部分。在 OOP
中,没必要关心对象的具体实现,只要能知足用户的需求便可。git
类( class)是构造对象的模板或蓝图。由类构造(construct) 对象的过程称为建立类的实例 (instance )。github
封装( encapsulation , 有时称为数据隐藏) 是与对象有关的一个重要概念。从形式上看,封装不过是将数据和行为组合在一个包中, 并对对象的使用者隐藏了数据的实现方式。对象中的数据称为实例域( instance field ), 操纵数据的过程称为方法( method ) 。对于每一个特定的类实例(对象)都有一组特定的实例域值。这些值的集合就是这个对象的当前状态( state )。不管什么时候,只要向对象发送一个消息,它的状态就有可能发生改变。sql
OOP
的另外一个原则会让用户自定义 Java
类变得垂手可得,这就是:能够经过扩展一个类来创建另一个新的类。在扩展一个已有的类时, 这个扩展后的新类具备所扩展的类的所有属性和方法。在新类中,只需提供适用于这个新类的新方法和数据域就能够了。编程
要想使用 OOP
,必定要清楚对象的三个主要特性segmentfault
在面向对象的学习中,咱们首先要学会设计类,而后在往类中添加所需的方法。微信
识别类的简单规则是在分析问题的过程当中寻找名词,而方法对应着动词。框架
例如:在订单处理系统中,有这样一些名词:ide
这些名词极可能成为类 Item、 Order 等。函数
接下来, 查看动词:商品被添加到订单中, 订单被发送或取消, 订单货款被支付。对于每个动词如:“ 添加”、“ 发送”、“ 取消” 以及“ 支付”, 都要标识出主要负责完成相应动做的对象。例如,当一个新的商品添加到订单中时, 那个订单对象就是被指定的对象, 由于它知道如何存储商品以及如何对商品进行排序。也就是说,add 应该是 Order 类的一个方法, 而 Item 对象是一个参数。
面向对象将之前的过程当中的执行者,变成了指挥者
过程和对象在咱们程序中是如何体现的呢? 过程其实就是函数,对象是将函数等一些内容进行了封装
要想建立一个完整的程序, 应该将若干类组合在一块儿, 其中只有一个类有 main
方法。
在Java中,最简答的类的形式以下:
class Employee { //成员变量 field1; field2; .... //构造器 constructor1; constructor2; ... //成员方法 method1; method2; .... }
在类中定义其实都称之为成员。成员有两种:
成员变量和局部变量的区别?
局部变量定义在方法中,参数上,语句中
局部变量只在本身所属的大括号内有效,大括号结束,局部变量失去做用域
局部变量存在于栈内存中,随着所属区域的运行而存在,结束而释放
构造器 用于给对象进行初始化,是给与之对应的对象进行初始化。
new
操做符的执行被调用。0
个、 1
个或多个参数public Student { private String name; //声明变量name,存储学生的姓名 private int age; //声明变量age,存储学生的年龄 //无参构造器 public Student() { } //带有一个参数的构造器 public Student(String aName) { name = aName; } //带有两个参数的构造器 public Student(String aName, int aAge){ name = aName; age = aAge; } } new Student();// 使用此方法new一个对象实例会调用Student()构造器,name被初始化为null,age被初始化为0 new Student("Tom");//使用此方法new一个对象实例会调用Student(String aName)构造器,age被初始化为0
定义 指隐藏对象的属性和实现细节,仅提供对外公共访问方式
// 自定义Employee类 class Employee { // 成员变量 private String name; private double salary; private LocalDate hireDay; // 构造器 或 构造函数 public Employee(String n, double s, int year, int month, int day) { name = n; salary = s; hireDay = LocalDate.of(year, month, day); } // 成员方法 // 获取姓名 public String getName() { return name; } //获取薪资 public double getSalary() { return salary; } //获取雇用日期 public LocalDate getHireDay() { return hireDay; } /** * 按百分比涨工资 * @param byPercent 百分比 */ public void raiseSalary(double byPercent) { double raise = salary * (byPercent / 100); salary += raise; } } 使用private来修饰的成员变量为私有的,只能被当前类使用,体现了良好的封装性。 getName() getSalary() getHireDay()使用public来修饰,供外界来访问类的私有属性
若是将变量定义为static
,每一个类中只有一个这样的变量,每个对象都共享这样一个static
变量,这个static
变量不属于任何对象,只属于这个类
public Student { // 该静态变量只属于Student类,无论声明多少个学生对象,每一个学生都共有这一个学校名。都是清华大学 private static String schoolName = "清华大学"; }
成员变量和静态变量的区别
静态变量所属于类,因此也称为类变量
静态变量存在于方法中
静态变量随着类加载而存在,随着类的消失而消失
静态变量,也能够被对象调用,也能够被类调用。
静态变量用的比较少,但静态常量用的相对比较多
例如,例如在Math类中定义一个静态常量
public Math { private static final double PI = 3.1415926; }
在程序中,可使用Math.PI
的方式来使用静态常量,若是省去 static
,则必须经过 Math
的对象来访问 PI
静态方法是一种不能向对象实施操做的方法。
public static String getSchoolName(){ return schoolName; } 静态方法只能经过类名去访问。 example: Student.getSchoolName();
该成员变量的数据是不是全部对象都同样
若是是,那么该变量须要被静态修饰,由于是共享数据
若是不是,那么就说这是对象的特有数据,要存储到对象中
如何判断成员函数是否被静态修饰呢?
只要参考,该函数内是否访问了对象中特有的数据
若是有访问特有数据,那么方法不能被静态修饰
若是没有访问特有数据,那么这个方法须要被静态修饰
有些类可能有不少个构造器。例如
public Student { private String name; //声明变量name,存储学生的姓名 private int age; //声明变量age,存储学生的年龄 //无参构造器 public Student() { } //带有一个参数的构造器 public Student(String aName) { name = aName; } //带有两个参数的构造器 public Student(String aName, int aAge){ name = aName; age = aAge; } }
这种特征叫作重载(overload)。
若是多个方法有相同的名字、不一样的参数,便产生了重载。
Java容许重载任何方法,不只仅是构造器。
不能有两个名字相同、 参数类型也相同却返回不一样类型值的方法,这不是方法的重载。
在一个类的声明中,能够包含多个代码块。只要构造类的对象,这些块就会被执行。例如:
public Student{ private String name; private int age; //初始化块 { age = 18; } } 不管使用哪一个构造器构造对象,age变量都在对象初始化块中被初始化。 首先运行初始化块,而后才运行构造器的主体部分。
对于静态成员初始化,可使用静态初始化块
public Student{ private static String schoolName; static{ schoolName = "清华大学"; } }
没有显式初始化的成员变量会默认进行初始化
0
false
null
静态初始化块、初始化块、构造函数同时存在时的执行顺序:静态初始化块 -> 初始化块 -> 构造函数
public class Demo3 { public static void main(String[] args) { People people = new People(); System.out.println(people.toString()); } } class People { private String name; private int age; { System.out.println("构造块"); } static { System.out.println("静态构造块"); } public People() { System.out.println("Person 构造器"); } public String toString() { return getClass().getName() + "[name=" + name + ",age=" + age + "]"; } } 执行结果: 静态构造块 构造块 Person 构造器 People[name=null,age=0] //默认初始化值
this
表明当前对象。就是所在方法所属对象的引用
this(实际参数)
this
对象后面跟上 .
调用的是成员变量和成员方法this
对象后面跟上()
调用的是本类中的对应参数的构造函数final
final
修饰的类是一个最终类,不能够被继承。final
修饰的方法是一个最终方法,不能够被覆盖。final
修饰的变量是一个常量,只能赋值一次。Java 容许使用包( package ) 将类组织起来。借助于包能够方便地组织本身的代码,并将本身的代码与别人提供的代码库分开管理。
一个类可使用所属包中的全部类, 以及其余包中的公有类( public class。)
咱们能够采用两种方式访问另外一个包中的公有类
在每一个类名以前添加完整的包名。
java.tiie.LocalDate today = java.tine.LocalDate.now();
使用 import
语句
import java.util .*; LocalDate today = LocalDate.now();
在发生命名冲突的时候,就不能不注意包的名字了。例如,java.util
和 java.sql
包都有日期( Date) 类。在每一个类名的前面加上完整的包名。
java.util.Date deadline = new java.util.Date(); java.sql.Date today = new java.sql.Date();
import
语句不只能够导入类,还增长了导入静态方法和静态变量的功能
import java.lang.System.*; out.println();
要想将一个类放入包中, 就必须将包的名字放在源文件的开头,包中定义类的代码以前。
package com.robin.java;
若是没有在源文件中放置 package
语句, 这个源文件中的类就被放置在一个默认包( defaulf package ) 中。
get
和 set
方法关键字 extends
public Animal{ private int age; public void eat(){ System.out.println("aminal eat food"); } } // 狗继承动物 public Dog extends Animal{ private String sex; } Animal称之为:超类,基类,父类 Dog称之为:子类,派生类 Dog不只从超类中继承了age属性,并且还定义了属性sex,此时Dog类中有age,sex两个属性 eat()方法,Dog也能够同时使用
超类中的方法不必定彻底适用于子类,因此须要提供一个新的方法来覆盖超类中的方法。
Animal中的eat()方法是用来吃食物,而Dog中也须要eat()方法,可是须要吃骨头,所以咱们能够提供一个新的方法来覆盖超类中的方法 public void eat() { System.out.println("Dog eat bone"); }
注意
子类在重写超类的方式时,子类方法不能低于父类方法的可见性。如超类的方法是 public
,子类必定为 public
Java
中只支持单继承子父类出现后,类中的成员都有了哪些特色
当子父类出现同样的属性时,子类类型的对象,调用该属性,值是子类的属性值。
若是想要调用父类的属性值,须要使用一个关键字: super
this
表明是本类类型的对象引用
super
表明是子类所属父类中内存空间的引用
当子父类中出现了如出一辙的方法时,创建子类对象会运行子类中的方法
因此这种状况,是函数的另外一个特性:覆写(重写,复写)
发现子类构造函数运行时,先运行了父类的构造函数。为何呢?
缘由:子类的全部构造函数的第一行,其实都有一条隐身的语句 super()
super()
和 this()
是否能够同时出如今构造器中。
两个语句只能有一个定义在第一行,因此只能出现其中一个。
若是自下而上在类的继承层次结构中上移,位于上层的类更具备通用性,甚至可能更加抽象。从某种角度看, 祖先类更加通用, 人们只将它做为派生其余类的基类,而不做为想使用的特定的实例类。
在不断抽取过程当中,将共性内容中的方法声明抽取,可是方法不同,没有抽取,这时抽取到的方法,并不具体,须要被指定关键字 abstract
所标示,声明为抽象方法。
抽象类的特色
abstract
关键字修饰(能够描述类和方法,不能够描述变量)抽象类细节
有,用于给子类对象进行初始化
能够,其实,抽象类和通常类没有太大区别,都是在描述事物,只不过抽象类在描述事物时,有些功能不具体。因此抽象类和通常类在定义上,都是须要定义属性和行为的。只不过,比通常类多了一个抽象函数,并且比通常类少了一个建立对象的部分
abstract
和哪些不能够共存?final
private
static
能够。抽象方法目的仅仅为了避免让该类建立对象
Java
中提供了4种访问控制符
private
- 仅对本类可见public
- 对全部类课件protected
- 对本包和全部子类可见Object
类是 Java
中全部类的始祖, 在 Java
中每一个类都是由它扩展而来的。可是并不须要这样写:
public class Student extends Object
public boolean equals(Object obj) { return (this == obj); }
Object
类中的 equals
方法用于检测一个对象是否等于另一个对象。在 Object
类中,这个方法将判断两个对象是否具备相同的引用。若是两个对象具备相同的引用, 它们必定是相等的。
Java语言规范要求 equals
方法具备如下特性 :
x
,x.equals(x)
应该返回 true
x
和 y
,当且仅当 y.equals(x)
返回 true
,x.equals(y)
也应该返回 true
x
, y
和 z
,若是 x.equals(y)
返回 true
, y.equals(z)
返回 true
,x.equals(z)
也应该返回 true
x
和 y
引用的对象没有发生变化,反复调用 x.equals(y)
应该返回一样的结果x
, x.equals(null)
应该返回 false
public boolean equals(Object otherObject){ if (this == otherObject) return true;//检测this与otherObject是否引用同一个对象 if (otherObject == null) return false;//检测otherObject是否为null,若是是返回false if (getClass() != otherObject.getClass()) return false;//比较this与otherObject是否属于同一个类 ClassName other = (ClassName)otherObject;//将otherObject转为相应类类型变量 return field1 == other.field1 && Object.equals(field2, other.field2) && .... ; //对每项成员变量进行比较 }
散列码( hash code ) 是由对象导出的一个整型值。散列码是没有规律的。
因为 hashCode
方法定义在 Object
类中, 所以每一个对象都有一个默认的散列码,其值为对象的存储地址。
hashcode
必定相同hashcode
相同,对象不必定相同用于返回表示对象值的字符串
Object中的toString() public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } 类名@哈希值 = getClass().getName()+'@'+Integer.toHexString(hasCode())//默认格式
在自定义类中建议重写 toString
方法,用来返回类中的各个属性值
public Student{ private String name; private int age; public String toString() { return getClass().getName() + "[name=" + name + ",age=" + age + "]"; } }
Class getClass()
: 获取任意对象运行时所属字节码文件对象
String getName()
: 返回这个类的名字
函数自己就具有多态性,某一种事物有不一样的具体的体现
体现 :父类引用或者接口的引用指向了本身的子类对象。Animal a = new Cat()
多态的好处 :提升了程序的扩展性
多态的弊端 :当父类引用指向子类对象时,虽然提升了扩展性,可是只能访问父类中具有的方法,不能够访问子类中特有的方法。
若是想用子类特有的方法,如何判断对象是哪一个具体的子类类型呢?
能够经过一个关键字 instanceof
判断对象是否实现了指定的接口或继承了指定的类
格式: <对象 instanceof 类型>
判断一个对象是否所属于指定类型
Student instanceof Person == true; //Student继承了Person
在编译期:参考引用型变量所属的类中是否有调用的成员(编译时不产生对象只检查语法错误)
在运行期:参考引用型变量所属的类中是否有调用的成员
成员变量 - 编译运行都看 - 左边
在编译期:参考引用型变量所属的类中是否有调用方法
在运行期:参考的是对象所属的类中是否有调用方法
成员函数 - 编译看左边 - 运行看右边
在编译期:参考引用型变量所属的类中是否有调用的成员
在运行期:参考引用型变量所属的类中是否有调用的成员
静态函数 - 编译运行都看 - 左边
有时, 须要将 int 这样的基本类型转换为对象。 全部的基本类型都冇一个与之对应的类。
Integer 类对应基本类型 int。一般, 这些类称为包装器。
这些对象包装器类拥有很明显的名字:Integer、Long、Float、Double、Short、Byte、Character 和 Boolean (前 6 个类派生于公共的超类 Number)。对象包装器类是不可变的,即一旦构造了包装器,就不容许更改包装在其中的值。同时, 对象包装器类仍是 final , 所以不能定义它们的子类。
关于包装类的具体使用,后续在经常使用类的文字中详细介绍
当int值赋给Integer对象时,将会自动装箱 Integer i = 3; Integer i = Integer.valueOf(3); 这种变换称之为自动装箱 当将一个Integer对象赋给一个int值时,将会自动地拆箱 Integer i = new Integer(3); int n = i; int n = i.intValue(); 这种变化称之为自动拆箱
让你的设计应当对扩展开放 ,对修改关闭 。抽象化 是开闭原则的关键。
用抽象构建框架,用实现扩展细节。
全部引用基类(父类)的地方必须能透明地使用其子类的对象
通俗的说:软件中若是可以使用基类对象,那么必定可以使用其子类对象。
在程序中尽可能使用基类类型来对对象进行定义,在运行过程当中使用子类对象。
子类能够扩展父类的功能,但不能改变父类原有的功能。
依赖倒置原则
要针对接口编程,不用针对实现编程。
层模块不该该依赖底层模块,他们都应该依赖抽象。抽象不该该依赖细节,细节应该依赖于抽象。 依赖三种写法: 1.构造函数注入 2.Setter依赖注入 3.接口注入 依赖原则本质:经过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合。 原则使用: 每一个类尽可能有接口或抽象类,或者抽象类和接口二者都具有 变量的类型尽可能使接口或者抽象类 任何类都不该该从具体类派生 尽可能不要覆写基类的方法 结合里氏替换原则
在软件系统中,一个类只负责一个功能领域中的相应职责。
应该仅有一个引发它变化的缘由。
该原则的核心就是解耦和加强内聚性
将一个接口拆分多个接口,知足不一样的实现类。
面向对象的思想博大精深,所以咱们不只要学会编写代码, 更更更 重要的是学会面向对象的思想。
相关代码记录于GitHub中,欢迎各位伙伴 Star !
有任何疑问 微信搜一搜 [程序猿大博] 与我联系~
若是以为对您有所帮助,请 点赞 ,收藏 ,若有不足,请评论或私信指正,谢谢~