一、 面向过程 和面向对象思想
面向过程和面向对象都是对软件分析、设计和开发的一种思想,它指导着人们以不同的方式去分析、设计和开发软件。早期先有面向过程思想,随着软件规模的扩大,问题复杂性的提高,面向过程的弊端越来越明显的显示出来,出现了面向对象思想并成为目前主流的方式。两者都贯穿于软件分析、设计和开发各个阶段,对应面向对象就分别称为面向对象分析(OOA)、面向对象设计(OOD)和面向对象编程(OOP)。C 语言是一种典型的面向过程语言, JAVA 是一种典型的面向对象语言。
面向过程思想思考问题时,我们首先思考“怎么按步骤实现?”并将步骤对应成方法,一步一步,最终完成。 这个适合简单任务,不需要过多协作的情况下。
面向过程适合简单、不需要协作的事务,重点关注如何执行。但是当我们思考比较复杂的设计任务时,此时面向对象思想就应运而生了。 面向对象(Oriented-Object)思想 更契合人的思维模式。我们首先思考的是“ 怎么设计这个事物?” 。
面向对象可以帮助我们从宏观上把握、从整体上分析整个系统。 但是,具体到实现部分的微观操作(就是一个个方法),仍然需要面向过程的思路去处理。我们千万不要把面向过程和面向对象对立起来。他们是相辅相成的。面向对象离不开面向过程!
面向对象和面向过程思想的总结: 都是解决问题的思维方式,都是代码组织的方式。 面向过程是一种“执行者思维” ,解决简单问题可以使用面向过程。 面向对象是一种“设计者思维” ,解决复杂、需要协作的问题可以使用面向对象。面向对象离不开面向过程:宏观上:通过面向对象进行整体设计;微观上:执行和处理数据,仍然是面向过程。
二、对象和类的详解
类:我们叫做 class。 对象:我们叫做 Object,instance(实例)。以后我们说某个类的对象,某个类的实例。是一样的意思。
总结: 类可以看成一类对象的模板,对象可以看成该类的一个具体实例。类是用于描述同一类型的对象的一个抽象概念,类中定义了这一类对象所应具有的共同的属性、方法。
1.类的定义
// 每一个 源文件 必须有且只有一个public class,并且类名和文件名保持一致!
public class Car {
}
class Tyre { // 一个Java文件可以同时定义多个class
}
class Engine {
}
class Seat {
}
上面的类定义好后,没有任何的其他信息,就跟我们拿到一张张图纸,但是纸上没有任何信息,这是一个空类,没有任何实际意义。所以,我们需要定义类的具体信息。对于一个类来说, 一般有三种常见的成员:属性 field、方法 method、构造器 constructor 。这三种成员都可以定义零个或多个。
2.属性(field 成员变量)
属性用于定义该类或该类对象包含的数据或者说静态特征 。属性作用范围是整个类体。在定义成员变量时可以对其初始化,如果不对其初始化, Java 使用默认的值对其初始化 。
成员变量的默认值 | ||
数据类型 | 默认值 | |
整型 | 0 | |
浮点型 | 0.0 | |
字符型 | ‘\u0000’ | |
布尔型 | false | |
所有引用类型 | null |
属性定义的格式:
【修饰符】 属性类型 属性名=【默认值】;
3.方法
方法用于定义该类或该类实例的行为特征和功能实现。 方法是类和对象行为特征的抽象。方法很类似于面向过程中的函数。面向过程中,函数是最基本单位,整个程序由一个个函数调用组成。面向对象中,整个程序的基本单位是类,方法是从属于类和对象的。
方法定义格式:
方法的详细说明:
形式参数: 在方法声明时用于接收外界传入的数据。
实参: 调用方法时实际传给方法的数据。
返回值: 方法在执行完毕后返还给调用它的环境的数据。
返回值类型: 事先约定的返回值的数据类型,如无返回值,必须指定为 void 。
注意事项:
1.实参的数目、数据类型和次序必须和所调用的方法声明的形式参数列表匹配。
2.return 语句终止方法的运行并指定要返回的数据。
3.Java 中进行方法调用中传递参数时,遵循值传递的原则(传递的都是数据的副本):基本类型传递的是该数据值的 copy 值。引用类型传递的是该对象引用的 copy 值,但指向的是同一个对象。
方法的重载( Overload ):
方法的重载是指一个类中可以定义多个方法名相同,但参数不同的方法。调用时,会根据不同的参数自动匹配对应的方法。重载的方法,实际是完全不同的方法,只是名称相同而已!
构成方法重载的条件 :
不同的含义: 形参 类型、形参个数、形参顺序不同。
只有返回值不同不构成方法的重载,如:int a(String str){}与 void a(String str){}不构成方法重载。
只有形参的名称不同,不构成方法的重载,如:int a(String str){}与 int a(String s){}不构成方法重载。
package cn.pxy.test;
public class OverloadTest {
public static void main(String[] args) {
System.out.println(add(,5));
System.out.println(add(,5,10));
System.out.println(add(.0,5));
System.out.println(add(,3.0));
}
//求和方法
public static int add(int n,int n2) {
int sum=n+n2;
return sum;
}
//方法名相同,参数个数不同构成重载
public static int add(int n,int n2,int n3) {
int sum=n+n2+n3;
return sum;
}
//方法名相同,参数类型不同构成重载
public static double add(double n,int n2) {
double sum=n+n2;
return sum;
}
//方法名相同,参数顺序不同,构成重载
public static double add(int n,double n2) {
double sum=n+n2;
return sum;
}
}
运行结果:
4.构造方法(构造器constructor)
构造方法 基础用法:
构造器 也叫构造方法(constructor),用于对象的初始化。构造器是一个创建对象时被自动调用的特殊方法,目的是对象的初始化。构造器的名称应与类的名称一致。 Java 通过new 关键字来调用构造器,从而返回该类的实例, 是一种特殊的方法。
声明格式:
[修饰符] 类名(形参列表){
//n 条语句
}
构造器 4 个要点:
1.构造器通过 new 关键字调用!2.构造器虽然有返回值,但是不能定义返回值类型(返回值的类型肯定是本类),不能在构造器里使用 return 返回某个值。3.如果我们没有定义构造器,则编译器会自动定义一个无参的构造方法。如果已定义则编译器不会自动添加!4.构造器的方法名必须和类名一致!
对象的创建完全是由构造方法实现的吗?
不完全是。构造方法是创建 Java 对象的重要途径,通过 new 关键字调用构造器时,构造器也确实返回了该类对象,但这个对象并不是完全由构造器负责创建的。创建一个对象分为如下四步:
1.分配对象空间,并将对象成员变量初始化为 0 或空
2.执行属性值的显式初始化
3.执行构造方法
4.返回对象的地址给相关的变量
构造方法的重载:
package cn.pxy.test;
public class User{
int id;
String name;
String pwd;
public User() {
}
public User(int id,String name) {
this.id=id;
this.name=name;
}
public User(int id,String name,String pwd) {
this.id=id;
this.name=name;
this.pwd=pwd;
}
public static void main(String[] args) {
User u=new User();
User u=new User(101,"李四");
User u=new User(102,"张三","123456");
}
}
如果方法构造中形参名与属性名相同时,需要使用 this 关键字区分属性与形参。如上例所示:this.id 表示属性 id;id 表示形参 id。
5.一个典型的学生类的定义与 UML 图
package cn.pxy.test;
public class SxtStu {
int id;
String name;
int age;
Computer comp;
void study() {
System.out.println("我在使用我的"+comp.brand+"电脑学习!");
}
SxtStu(){
}
public static void main(String[] args) {
SxtStu stu=new SxtStu();
stu.name="张三";
Computer comp=new Computer();
comp.brand="联想";
stu.comp=comp;
stu.study();
}
}
class Computer{
String brand;//品牌
}
运行结果:
对应的UML图:
三、面向对象的内存分析
1.程序执行的内存分析过程
Java虚拟机 内存模型 :
为了分析程序执行的内存,Java 虚拟机的内存可以简单的分为三个区域:虚拟机栈 stack、堆 heap、方法区 method area。
虚拟机栈(简称:栈)的特点如下:
1.栈描述的是方法执行的内存模型。每个方法被调用都会创建一个栈帧(存储局部变量、操作数、方法出口等)
2. JVM 为每个线程创建一个栈,用于存放该 线程 执行方法的信息(实际参数、局部变量等)
3.栈属于线程私有,不能实现线程间的共享!
4.栈的存储特性是“先进后出,后进先出”
5.栈是由系统自动分配,速度快!栈是一个连续的内存空间!
堆的特点如下:
1.堆用于存储创建好的对象和数组(数组也是对象)
2.JVM 只有一个堆,被所有线程共享
3. 堆是一个不连续的内存空间,分配灵活,速度慢!
方法区(又叫静态区,也是堆)特点如下:
1.方法区是 JAVA 虚拟机规范,可以有不同的实现。
i.JDK7 以前是“永久代”
ii.JDK7 部分去除“永久代”,静态变量、字符串常量池都挪到了堆内存中
iii.JDK8 是“元数据空间”和堆结合起来。
2.JVM 只有一个方法区,被所有线程共享!
3.方法区实际也是堆,只是用于存储类、常量相关的信息!
4.用来存放程序中永远是不变或唯一的内容。(类信息、静态变量、字符串常量等)
示例 :
创建Person类:
package cn.pxy.test;
public class Person {
String name;
int age;
public void show() {
System.out.println("姓名:"+name+",年龄:"+age);
}
}
创建Person类对象并使用:
package cn.pxy.test;
public class TestPerson {
public static void main(String[] args) {
//创建p对象
Person p=new Person();
p.name="张三";
p.age=18;
p.show();
//创建p对象
Person p=new Person();
p.name="李四";
p.age=22;
p.show();
}
}
运行结果:
内存分配图: 同一类的每个对象有不同的成员变量存储空间。同一类的每个对象共享该类的方法。
2.参数传值机制
Java 中,方法中所有参数都是“值传递”,也就是“传递的是值的副本”。 也就是说,我们得到的是“原参数的复印件,而不是原件”。因此,复印件改变不会影响原件。
基本数据类型参数的传值: 传递的是值的副本。 副本改变不会影响原件。
引用类型参数的传值: 传递的是值的副本。但是引用类型指的是“对象的地址”。因此,副本和原参数都指向了同一个“地址”,改变“副本指向地址对象的值,也意味着原参数指向对象的值也发生了改变”。
示例 :多个变量指向同一个对象
package cn.pxy.test;
public class User{
int id;
String name;
String pwd;
public User(int id,String name) {
this.id=id;
this.name=name;
}
public static void main(String[] args) {
User u=new User(101,"李四");
User u=u1;
System.out.println(u.name);
u.name="张三";
//引用类型参数传递会改变原先的值
System.out.println(u.name);
}
}
运行结果:
四、this、static 关键字
1.this关键字
对象创建的过程和 this 的本质:
构造方法是创建 Java 对象的重要途径,通过 new 关键字调用构造器时,构造器也确实返回该类的对象,但这个对象并不是完全由构造器负责创建。创建一个对象分为如下四步:
1.分配对象空间,并将对象成员变量初始化为 0 或空
2.执行属性值的显式初始化
3.执行构造方法
4.返回对象的地址给相关的变量
this 的本质就是“创建好的对象的地址”! 由于在构造方法调用前,对象已经创建。因此,在构造方法中也可以使用 this 代表“当前对象”。
this 最常的用法:
在程序中产生二义性之处,应使用 this 来指明当前对象;普通方法中,this 总是指向调用该方法的对象。构造方法中,this 总是指向正要初始化的对象。
使用 this 关键字调用重载的构造方法,避免相同的初始化代码。但只能在构造方法中用,并且必须位于构造方法的第一句。
this 不能用于 static 方法中。
this关键字的使用 :
package cn.pxy.test;
public class User {
int id; //id
String name; //账户名
String pwd; //密码
public User() {
}
public User(int id, String name) {
System.out.println("正在初始化已经创建好的对象:"+this);
this.id = id; //不写this,无法区分局部变量id和成员变量id
this.name = name;
}
public void login(){
System.out.println(this.name+",要登录!"); //不写this效果一样
}
public static void main(String[ ] args) {
User u = new User(101,"张三");
System.out.println("打印张三对象:"+u);
u.login();
}
}
运行结果:
this()调用重载构造方法 :
package cn.pxy.test;
public class TestThis {
int a,b,c;
TestThis(){
System.out.println("正要初始化一个Hello对象");
}
TestThis(int a,int b){
//TestThis();//这样是无法调用构造方法的
this();//调用无参构造方法,并且必须位于第一行
a=a;//这里都是指的局部变量而不是成员变量
this.a=a;//这样就区分了局部变量和成员变量,这种情况占了this使用情况的大多数
this.b=b;
System.out.println(a+b);
}
TestThis(int a,int b,int c){
this(a,b);//调用带参的构造方法,并且必须位于第一行
this.c=c;
System.out.println(a+b+c);
}
void sing() {
System.out.println("sing....");
}
void eat() {
this.sing();//调用本类的sing()
System.out.println("回家吃饭!");
}
public static void main(String[] args) {
TestThis hi=new TestThis(,3);
hi.eat();
}
}
运行结果:
2.static关键字
在类中,用 static 声明的成员变量为静态成员变量,也称为类变量。 类变量的生命周期和类相同,在整个应用程序执行期间都有效。它有如下特点:
为该类的公用变量,属于类,被该类的所有实例共享,在类被载入时被显式初始化。
对于该类的所有对象来说,static 成员变量只有一份。被该类的所有对象共享!!
一般用“类名.类属性/方法”来调用。(也可以通过对象引用或类名(不需要实例化)访问静态成员。)
在 static 方法中不可直接访问非 static 的成员。
static关键字的使用:
package cn.pxy.test;
public class User {
int id; //id
String name; //账户名
String pwd; //密码
static String company="头条号";//公司名
public User(int id, String name) {
this.id = id; //不写this,无法区分局部变量id和成员变量id
this.name = name;
}
public void login(){
System.out.println(this.name+",要登录!"); //不写this效果一样
}
public static void printCompany() {
//login();//调用非静态成员,编译就会报错
System.out.println(company);
}
public static void main(String[ ] args) {
User u = new User(,"张三");
User.printCompany();
User.company="阿里巴巴";
User.printCompany();
}
}
运行结果:
静态初始化块:
构造方法用于对象的初始化!静态初始化块,用于类的初始化操作!在静态初始化块中不能直接访问非 static 成员。
静态初始化块执行顺序:
上溯到 Object 类,先执行 Object 的静态初始化块,再向下执行子类的静态初始化块,直到类的静态初始化块为止。构造方法执行顺序和上面顺序一样
package cn.pxy.test;
public class User {
int id; //id
String name; //账户名
String pwd; //密码
static String company;//公司名
static {
System.out.println("这里执行类的初始化工作");
company="头条号";
printCompany();
}
public static void printCompany() {
System.out.println(company);
}
public static void main(String[ ] args) {
User u = new User();
}
}
运行结果:
五、包机制(package、import)
包机制是 Java 中管理类的重要手段。 开发中,我们会遇到大量同名的类,通过包我们很容易对解决类重名的问题,也可以实现对类的有效管理。 包对于类,相当于文件夹对于文件的作用。
1.package
我们通过 package 实现对类的管理,package 的使用有两个要点:
1.通常是类的第一句非注释性语句。
2.包名:域名倒着写即可,再加上模块名,便于内部管理类。
写项目时都要加包,不要使用默认包。com.pxy 和 com.pxy.car,这两个包没有包含关系,是两个完全独立的包。只是逻辑上看起来后者是前者的一部分。
JDK 中的主要包:
JDK 中的主要包 | ||
java中的常用包 | 说明 | |
java.lang | 包含一些 Java 语言的核心类,如 String、Math、Integer、System 和 Thread,提供常用功能。 | |
java. awt | 包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。 | |
java.net | 包含执行与网络相关的操作的类。 | |
java.io | 包含能提供多种输入/输出功能的类。 | |
java.util | 包含一些实用工具类,如定义系统特性、使用与日期日历相关的函数。 |
2.导入类import
如果我们要使用其他包的类,需要使用 import 导入,从而可以在本类中直接通过类名来调用,否则就需要书写类的完整包名和类名。import 后,便于编写代码,提高可维护性。
注意要点:
Java 会默认导入 java.lang 包下所有的类,因此这些类我们可以直接使用。
如果导入两个同名的类,只能用包名+类名来显示调用相关类:
java.util.Date date = new java.util.Date();
示例 :导入同名类
import java.sql.Date;
import java.util.*;//导入该包下所有的类。会降低编译速度,但不会降低运行速度。
public class Test{
public static void main(String[ ] args) {
//这里指的是java.sql.Date
Date now;
//java.util.Date因为和java.sql.Date类同名,需要完整路径
java.util.Date now = new java.util.Date();
System.out.println(now);
//java.util包的非同名类不需要完整路径
Scanner input = new Scanner(System.in);
}
}
静态导入 :
静态导入(static import)是在 JDK1.5 新增加的功能,其作用是用于导入指定类的静态属性和静态方法,这样我们可以直接使用静态属性和静态方法。
package cn.pxy.test;
//以下两种静态导入的方式二选一即可
import static java.lang.Math.*;//导入Math类的所有静态属性
import static java.lang.Math.PI;//导入Math类的PI属性
public class Test{
public static void main(String [ ] args){
System.out.println(PI);
System.out.println(random());
}
}
运行结果:
六、Object类详解
1.Object 类基本特性
Object 类是所有 Java 类的根基类,也就意味着所有的 Java 对象都拥有 Object 类的属性和方法。如果在类的声明中未使用 extends 关键字指明其父类,则默认继承 Object 类。
2.toString 方法
Object 类中定义有 public String toString()方法,其返回值是 String 类型。Object类中 toString 方法的源码为:
public String toString() {
return getClass().getName() + “@” + Integer.toHexString(hashCode());
}
根据如上源码得知,默认会返回“类名+@+16 进制的 hashcode”。在打印输出或者用字符串连接对象时,会自动调用该对象的 toString()方法。
示例 :重写toString方法
package cn.pxy.test;
class Person {
String name;
int age;
@Override
public String toString() {
return name+",年龄:"+age;
}
}
public class Test {
public static void main(String[ ] args) {
Person p=new Person1();
p.age=;
p.name="李四";
System.out.println("info:"+p);
Test t = new Test();
System.out.println(t);
}
}
运行结果:
3.==和 equals 方法
“==”代表比较双方是否相同。如果是基本类型则表示值相等,如果是引用类型则表示地址相等即是同一个对象。
Object 类中定义有:public boolean equals(Object obj)方法,提供定义“对象内容相等”的逻辑。比如,我们在学籍系统中认为学号相同的人就是同一个人。
Object 的 equals 方法默认就是比较两个对象的 hashcode,是同一个对象的引用时返回 true 否则返回 false。但是,我们可以根据我们自己的要求重写 equals 方法。
示例 : 重写 equals()方法
package cn.pxy.test;
public class TestEquals {
public static void main(String[] args) {
Person p1=new Person2(100,"张三");
Person p2=new Person2(100,"李四");
System.out.println(p==p2);//false,不是同一个对象
System.out.println(p.equals(p2));//true,id相同则认为两个对象内容相同
String s=new String("胖咸鱼");
String s=new String("胖咸鱼");
System.out.println(s==s2);//false,两个字符串不是同一个对象
System.out.println(s.equals(s2));//true,两个字符串内容相同
}
}
class Person{
int id;
String name;
public Person(int id,String name) {
this.id=id;
this.name=name;
}
public boolean equals(Object obj) {
if(obj==null) {
return false;
}else {
if(obj instanceof Person) {
Person c=(Person2)obj;
if(c.id==this.id) {
return true;
}
}
}
return false;
}
}
运行结果:
JDK 提供的一些类,如 String、Date、包装类等,重写了 Object 的 equals 方法,调用这些类的 equals 方法, x.equals (y),当 x 和 y 所引用的对象是同一类对象且属性内容相等时(并不一定是相同对象),返回true 否则返回 false。
4.super关键字
super“可以看做”是直接父类对象的引用。可以通过 super 来访问父类中被子类覆盖的方法或属性。使用 super 调用普通方法,语句没有位置限制,可以在子类中随便调用。
在一个类中,若是构造方法的第一行代码没有显式的调用 super(…)或者 this(…);那么Java 默认都会调用 super(),含义是调用父类的无参数构造方法。这里的 super()可以省略。
package cn.pxy.test;
public class TestSuper {
public static void main(String[] args) {
new ChildClass().f();
}
}
class FatherClass{
public int value;
public void f() {
value=;
System.out.println("FatherClass.value="+value);
}
}
class ChildClass extends FatherClass{
public int value;
public void f() {
super.f();//调用父类的普通方法
value=;
System.out.println("ChildClass.value="+value);
System.out.println(value);
System.out.println(super.value);//调用父类的成员变量
}
}
super的使用运行结果:
七、继承
继承是面向对象编程的三大特征之一,它让我们更加容易实现对于已有类的扩展、更加容易实现对于现实世界的建模。继承有两个主要作用:1.代码复用,更加容易实现类的扩展2.方便建模
1.继承的实现
继承让我们更加容易实现类的扩展。 比如,我们定义了人类,再定义 Boy 类就只需要扩展人类即可。子类是父类的扩展。继承的使用:
package cn.pxy.test;
public class Test{
public static void main(String[] args) {
Student s=new Student("李四",,"java");
s.rest();
s.study();
}
}
class Person{
String name;
int age;
public void rest() {
System.out.println("我要休息一会!");
}
}
class Student extends Person{
String major;
public void study() {
System.out.println("我在学习英语!");
}
public Student(String name,int age,String major) {
//拥有父类的属性
this.name=name;
this.age=age;
this.major=major;
}
}
运行结果:
2.instanceof运算符
instanceof 是二元运算符,左边是对象,右边是类;当对象是右面类或子类所创建对象时,返回 true;否则,返回 false。比如:在上例基础上测试:
public class Test{
public static void main(String[] args) {
Student s=new Student("李四",,"java");
System.out.println(s instanceof Person);
System.out.println(s instanceof Student);
}
}
两条输出语句都返回true。
3.继承使用的注意点
1.父类也称作超类、基类。子类:派生类等。
2.Java 中只有单继承,没有像 C++那样的多继承。多继承会引起混乱,使得继承链过于复杂,系统难于维护。
3.Java 中类没有多继承,接口有多继承。
4.子类继承父类,可以得到父类的全部属性和方法 (除了父类的构造方法),但不见得可以直接访问(比如,父类私有的属性和方法)。
5. 如果定义一个类时,没有调用 extends,则它的父类是:java.lang.Object。
4.方法的重写override
子类通过重写父类的方法,可以用自身的行为替换父类的行为。方法的重写是实现多态的必要条件。
方法的重写需要符合下面的三个要点:
1.“= =”:方法名、形参列表相同。
2.“≤”:返回值类型和声明异常类型,子类小于等于父类。
3.“≥”: 访问权限,子类大于等于父类
package cn.pxy.test;
public class TestOverride {
public static void main(String[] args) {
Vehicle v=new Vehicle();
Vehicle v=new Plane();
v.run();
v.stop();
v.run();
v.stop();
}
}
class Vehicle{//交通工具类
public void run() {
System.out.println("跑步。。。");
}
public void stop() {
System.out.println("停下来。。。");
}
}
class Plane extends Vehicle{
public void run() {//重写父类方法
System.out.println("天上飞~!!");
}
public void stop() {
System.out.println("停下来就坠机~!!");
}
}
运行结果:
5.final关键字
final 关键字的作用:
修饰变量: 被他修饰的变量不可改变。一旦赋了初值,就不能被重新赋值。
final int MAX_SPEED = 120;
修饰方法: 该方法不可被子类重写。但是可以被重载!
final void study(){}
修饰类: 修饰的类不能被继承。比如:Math、String 等。
final class A {}
6.继承树追溯
属性/方法查找顺序:(比如:查找变量 h)
1.查找当前类中有没有属性 h。
2.依次上溯每个父类,查看每个父类中是否有 h,直到 Object。
3.如果没找到,则出现编译错误。
4.上面步骤,只要找到 h 变量,则这个过程终止。
构造方法调用顺序:
构造方法第一句总是:super(…)来调用父类对应的构造方法。所以,流程就是:先向上追溯到 Object,然后再依次向下执行类的初始化块和构造方法,直到当前子类为止。
注: 静态初始化块调用顺序,与构造方法调用顺序一样,不再重复。
继承条件下构造方法执行过程:
package cn.pxy.test;
public class TestSuper {
public static void main(String[] args) {
System.out.println("开始创建一个ChildClass对象。。。");
new ChildClass();
}
}
class FatherClass{
public FatherClass() {
System.out.println("创建FatherClass");
}
}
class ChildClass extends FatherClass2{
public ChildClass() {
System.out.println("创建ChildClass");
}
}
运行结果:
7.继承和组合
我们可以通过继承方便的复用已经定义类的代码。还有一种方式,也可以方便的实现“代码复用”,那就是:“组合”。
“组合”不同于继承,更加灵活。“组合”的核心就是“将父类对象作为子类的属性”,然后,“子类通过调用这个属性来获得父类的属性和方法”。
package cn.pxy.test;
public class Test{
public static void main(String[] args) {
Student s=new Student("李四",,"java");
s.person.rest();
s.study();
}
}
class Person{
String name;
int age;
public void rest() {
System.out.println("我要休息一会!");
}
}
class Student /*extends Person*/{
Person person=new Person1();
String major;
public void study() {
System.out.println("我在学习英语!");
}
public Student(String name,int age,String major) {
//拥有父类的属性
this.person.name=name;
this.person.age=age;
this.person.rest();
this.major=major;
}
}
运行结果:
组合比较灵活。继承只能有一个父类,但是组合可以有多个属性。继承除了代码复用、也能方便我们对事物建模。所以,对于“is -a”关系建议使用继承,“has-a”关系建议使用组合。比如:上面的例子,Student is a Person 这个逻辑没问题,但是:Student has a Person就有问题了。这时候,显然继承关系比较合适。再比如:笔记本和芯片的关系显然是“has-a”关系,使用组合更好。
八、封装(encapsulation)
封装是面向对象三大特征之一。对于程序合理的封装让外部调用更加方便,更加利于写作。同时,对于实现者来说也更加容易修正和改版代码。
1.封装的作用和含义
封装就是把对象的属性和操作结合为一个独立的整体,并尽可能隐藏对象的内部实现细节。我们程序设计要追求“高内聚,低耦合”。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合是仅暴露少量的方法给外部使用,尽量方便外部调用。
就像我们要看电视,只需要按一下开关和换台就可以了,没有必要了解电视机内部的结构,制造厂家为了方便我们使用电视,把复杂的内部细节全部封装起来,只给我们暴露简单的接口,比如:电源开关。具体内部是怎么实现的,我们不需要操心。
编程中封装的具体优点:
提高代码的安全性。
提高代码的复用性。
“高内聚”:封装细节,便于修改内部代码,提高可维护性。
“低耦合”:简化外部调用,便于调用者使用,便于扩展和协作。
2.封装的实现—使用访问控制符
Java 是使用“访问控制符”来控制哪些细节需要封装,哪些细节需要暴露的。 Java中 4 种“访问控制符”分别为 private、default、protected、public,它们说明了面向对象的封装性,所以我们要利用它们尽可能的让访问权限降到最低,从而提高安全性。访问权限范围如表:
访问权限修饰符 | ||||||||
修饰符 | 同一个类 | 同一个包中 | 子类 | 所有类 | ||||
private | * |
|
|
| ||||
default | * | * |
|
| ||||
protected | * | * | * |
| ||||
public | * | * | * | * |
1.private 表示私有,只有自己类能访问
2.default 表示没有修饰符修饰,只有同一个包的类能访问
3.protected 表示可以被同一个包的类以及其他包中的子类访问
4.public 表示可以被该项目的所有包中的所有类访问
3.封装的使用细节
开发中封装的简单规则:
属性一般使用 private 访问权限。
属性私有后, 提供相应的 get/set 方法来访问相关属性,这些方法通常是public 修饰的,以提供对属性的赋值与读取操作(注意:boolean 变量的 get方法是 is 开头!)。
方法:一些只用于本类的辅助性方法可以用 private 修饰,希望其他类调用的方法用 public 修饰。
封装的使用:
package cn.pxy.test;
public class Test{
public static void main(String[] args) {
Person p1=new Person1();
//p.name="李四";//编译错误
p.setName("李四");
p.setAge(45);
System.out.println(p);
Person p2=new Person1("张三",21);
System.out.println(p.getName());
}
}
class Person{
private String name;
private int age;
public Person() {
}
public Person(String name,int age) {
setName(name);
setAge(age);
}
public void setName(String name) {
this.name=name;
}
public String getName() {
return name;
}
public void setAge(int age) {
//在复制前先判断年龄是否合法
if(age>||age<0) {
this.age=;//不合法赋值默认值18
}else {
this.age=age;
}
}
public int getAge() {
return age;
}
public String toString() {
return "Person[name="+name+",age="+age+"]";
}
}
运行结果:
九、多态(polymorphism)
1.多态概念和实现
多态指的是同一个方法调用,由于对象不同可能会有不同的行为。 现实生活中,同一个方法,具体实现会完全不同。 比如:同样是调用人的“休息”方法,张三是睡觉,李四是旅游。
多态的要点 :
1.多态是方法的多态,不是属性的多态(多态与属性无关)。
2.多态的存在要有 3 个必要条件:继承,方法重写,父类引用指向子类对象。
3.父类引用指向子类对象后,用该父类引用调用子类重写的方法,此时多态就出现了。
package cn.pxy.test;
class Animal{
public void shout() {
System.out.println("叫了一声。");
}
}
class Dog extends Animal{
public void shout() {
System.out.println("汪汪汪!");
}
public void Work(){
System.out.println("看门!");
}
}
class Cat extends Animal{
public void shout() {
System.out.println("喵喵喵!");
}
}
public class TestPolym {
public static void main(String[] args) {
Animal a=new Cat();//向上可以自动类型转换
//传的具体是哪一类就调用那一个类的方法。大大提高了程序的可扩展性
animalCry(a);
Animal a=new Dog();
animalCry(a);
/*
* 编写程序时,如果想调用运行时类型的方法,只能进行强制类型转换
* 否则通不过编译器的检查
*/Dog dog=(Dog)a;
dog.Work();
}
//有了多态,只需要让增加的这个类继承Animal类就可以了
static void animalCry(Animal a) {
a.shout();
}
/**
* 如果没有多态,这里需要写很多重载的方法
* 每增加一种动物,就需要重载一种动物的叫法,非常麻烦
* static void animalCry(Dog d){
* d.shout();
* }
* static void animalCry(Cat c){
* c.shout;
* }
*/}
运行结果:
示例展示了多态最为多见的一种用法,即父类引用做方法的形参,实参可以是任意的子类对象,可以通过不同的子类对象实现不同的行为方式。
由此,可以看出多态的主要优势是提高了代码的可扩展性,符合开闭原则。但是多态也有弊端,就是无法调用子类特有的功能,比如,不能使用父类的引用变量调用 Dog类特有的 Work()方法。
2.对象的转型(casting)
父类引用指向子类对象,我们称这个过程为向上转型,属于自动类型转换。向上转型后的父类引用变量只能调用它编译类型的方法,不能调用它运行时类型的方法。这时,我们就需要进行类型的强制转换,我们称之为向下转型!
示例:对象的转型:
package cn.pxy.test;
public class TestCasting {
public static void main(String[] args) {
Object obj=new String("胖咸鱼先生说");//向上可以自动转型
//obj.charAt();无法调用,编译器认为obj是Object类型而不是String类型
/*
* 编写程序时,如果想调用运行时类型的方法,只能进行强制类型转换
*/String str=(String)obj;//向下转型
System.out.println(str.charAt());//位于0索引位置的字符
System.out.println(obj==str);//true,他们运行时是同一个对象
}
}
运行结果:
示例:类型转换异常:
package cn.pxy.test;
public class TestCasting {
public static void main(String[] args) {
Object obj=new String("胖咸鱼先生说");
//真实的子类类型是String,但是此处向下转型为StringBuffer
StringBuffer str=(StringBuffer)obj;
System.out.println(str.charAt());
}
}
运行结果:
示例:向下转型中使用instanceof:
package cn.pxy.test;
public class TestCasting {
public static void main(String[] args) {
Object obj=new String("胖咸鱼先生说");
if(obj instanceof String) {
String str=(String)obj;
System.out.println(str.charAt());
}else if(obj instanceof StringBuffer) {
StringBuffer str=(StringBuffer)obj;
System.out.println(str.charAt());
}
}
}
运行结果:
十、抽象类和接口
1.抽象方法和抽象类
抽象方法
使用 abstract 修饰的方法,没有方法体,只有声明。定义的是一种“规范”,就是告诉子类必须要给抽象方法提供具体的实现。
抽象类
包含抽象方法的类就是抽象类。通过 abstract 方法定义规范,然后要求子类必须定义具体实现。通过抽象类,我们就可以做到严格限制子类的设计,使子类之间更加通用。
例如:
package cn.pxy.test;
//抽象类
abstract class Animal{
//抽象方法
abstract public void shout();
}
class Pig extends Animal{
//子类必须实现父类的抽象方法
public void shout() {
System.out.println("哼哼哼");
}
}
public class TestAbstractClass {
public static void main(String[] args) {
Pig a=new Pig();
a.shout();
}
}
抽象类的使用要点:
1.有抽象方法的类只能定义成抽象类
2.抽象类不能实例化,即不能用 new 来实例化抽象类。
3.抽象类可以包含属性、方法、构造方法。但是构造方法不能用来 new 实例,只能用来被子类调用。
4.抽象类只能用来被继承。
5.抽象方法必须被子类实现。
2.接口 interface
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是…则必须能…”的思想。如果你是汽车,则必须能跑。
2.1接口的作用:
为什么需要接口?接口和抽象类的区别?
接口就是比“抽象类”还“抽象”的“抽象类”,可以更加规范的对子类进行约束。全面地专业地实现了: 规范和具体实现的分离 。
抽象类还提供某些具体实现,接口不提供任何实现,接口中所有方法都是抽象方法。接口是完全面向规范的,规定了一批类具有的公共方法规范。
从接口的实现者角度看,接口定义了可以向外部提供的服务。从接口的调用者角度看,接口定义了实现者能提供那些服务。
接口是两个模块之间通信的标准,通信的规范。如果能把你要设计的模块之间的接口定义好,就相当于完成了系统的设计大纲,剩下的就是添砖加瓦的具体实现了。接口和实现类不是父子关系,是实现规则的关系。即,普通类是具体实现;抽象类是具体实现、规范(抽象方法);接口是规范。
2.2如何定义和使用接口(JDK8 以前):
声明格式:
[访问修饰符] interface 接口名 [extends 父接口 1,父接口 2…]{
常量定义;
方法定义;
}
2.3定义接口的详细说明:
访问修饰符: 只能是 public 或默认。
接口名: 和类名采用相同命名机制。
extends: 接口可以多继承。
常量: 接口中的属性只能是常量,总是:public static final 修饰。不写也是。
方法: 接口中的方法只能是:public abstract。 省略的话,也是 public abstract。
2.4要点
子类通过 implements 来实现接口中的规范。
接口不能创建实例,但是可用于声明引用变量类型。
一个类实现了接口,必须实现接口中所有的方法,并且这些方法只能是 public 的。
JDK1.8(不含 8)之前,接口中只能包含静态常量、抽象方法,不能有普通属性、构造方法、普通方法。 JDK1.8(含 8)后,接口中包含普通的静态方法、默认方法。
2.5 接口中定义静态方法和默认方法(JDK8 以后)
JAVA8 之前,接口里的方法要求全部是抽象方法。JAVA8(含 8)之后,以后允许在接口里定义默认方法和类方法。
默认方法:
Java 8 及以上新版本,允许给接口添加一个非抽象的方法实现,只需要使用 default 关键字即可,这个特征又叫做默认方法(也称为扩展方法)。默认方法和抽象方法的区别是抽象方法必须要被实现,默认方法不是。作为替代方式,接口可以提供默认方法的实现,所有这个接口的实现类都会通过继承得到这个方法。
静态方法:
JAVA8 以后,我们也可以在接口中直接定义静态方法的实现。这个静态方法直接从属于接口(接口也是类,一种特殊的类),可以通过接口名调用。如果子类中定义了相同名字的静态方法,那就是完全不同的方法了,直接从属于子类。可以通过子类名直接调用。
2.6接口的多继承
接口完全支持多继承。和类的继承类似,子接口扩展某个父接口,将会获得父接口中所定义的一切。
interface A{
void testa();
}
interface B{
void testb();
}
/**接口可以多继承*/interface C extends A,B{
void teatc();
}
public class Test implements C{
public void testc(){
}
public void testa(){
}
public void testb(){
}
}
2.7面向接口编程
面向接口编程是面向对象编程的一部分。接口就是规范,就是项目中最稳定的核心! 面向接口编程可以让我们把握住真正核心的东西,使实现复杂多变的需求成为可能。
通过面向接口编程,而不是面向实现类编程,可以大大降低程序模块间的耦合性,提高整个系统的可扩展性和和可维护性。面向接口编程的概念比接口本身的概念要大得多。设计阶段相对比较困难,在你没有写实现时就要想好接口,接口一变就乱套了,所以设计要比实现难!
十一、字符串String类详解
1.String基础
String 类又称作不可变字符序列。
String 位于 java.lang 包中,Java 程序默认导入 java.lang 包下的所有类。
Java 字符串就是 Unicode 字符序列 ,例如字符串“Java”就是 4 个 Unicode 字符’J’、’a’、’v’、’a’组成的。
Java 没有内置的字符串类型,而是在标准 Java 类库中提供了一个预定义的类String,每个用双引号括起来的字符串都是 String 类的一个实例。
Java 允许使用符号”+”把两个字符串连接起来。符号”+”把两个字符串按给定的顺序连接在一起,并且是完全按照给定的形式。当”+”运算符两侧的操作数中只要有一个是字符串(String)类型,系统会自动将另一个操作数转换为字符串然后再进行连接。
2.字符串相等判断
equals 方法用来检测两个字符串内容是否相等。如果字符串 s 和 t 内容相等,则s.equals(t)返回 true,否则返回 false。
要测试两个字符串除了大小写区别外是否是相等的,需要使用 equalsIgnoreCase方法。
判断字符串是否相等不要使用”==”。