您的位置 首页 java

Java基础——面向对象

一、 面向过程 和面向对象思想

面向过程和面向对象都是对软件分析、设计和开发的一种思想,它指导着人们以不同的方式去分析、设计和开发软件。早期先有面向过程思想,随着软件规模的扩大,问题复杂性的提高,面向过程的弊端越来越明显的显示出来,出现了面向对象思想并成为目前主流的方式。两者都贯穿于软件分析、设计和开发各个阶段,对应面向对象就分别称为面向对象分析(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(3,5));
		System.out.println(add(3,5,10));
		System.out.println(add(3.0,5));
		System.out.println(add(5,3.0));
	}
	//求和方法
	public static int add(int n1,int n2) {
		int sum=n1+n2;
		return sum;
	}
	//方法名相同,参数个数不同构成重载
	public static int add(int n1,int n2,int n3) {
		int sum=n1+n2+n3;
		return sum;
	}
	//方法名相同,参数类型不同构成重载
	public static double add(double n1,int n2) {
		double sum=n1+n2;
		return sum;
	}
	//方法名相同,参数顺序不同,构成重载
	public static double add(int n1,double n2) {
		double sum=n1+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 u1=new User();
		User u2=new User(101,"李四");
		User u3=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) {
		//创建p1对象
		Person p1=new Person();
		p1.name="张三";
		p1.age=18;
		p1.show();
		//创建p2对象
		Person p2=new Person();
		p2.name="李四";
		p2.age=22;
		p2.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 u1=new User(101,"李四");
		User u3=u1;
		System.out.println(u1.name);
		u3.name="张三";
		//引用类型参数传递会改变原先的值
		System.out.println(u1.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 u3 = new User(101,"张三"); 
		System.out.println("打印张三对象:"+u3); 
		u3.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(2,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(101,"张三"); 
		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 now2 = new java.util.Date(); 
    System.out.println(now2);
    //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 Person1 { 
	String name; 
	int age; 
	@Override 
	public String toString() { 
		return name+",年龄:"+age; 
	} 
}
public class Test { 
	public static void main(String[ ] args) { 
		Person1 p=new Person1(); 
		p.age=20; 
		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) {
		Person2 p1=new Person2(100,"张三");
		Person2 p2=new Person2(100,"李四");
		System.out.println(p1==p2);//false,不是同一个对象
		System.out.println(p1.equals(p2));//true,id相同则认为两个对象内容相同
		String s1=new String("胖咸鱼");
		String s2=new String("胖咸鱼");
		System.out.println(s1==s2);//false,两个字符串不是同一个对象
		System.out.println(s1.equals(s2));//true,两个字符串内容相同
	}
}
class Person2{
	int id;
	String name;
	public Person2(int id,String name) {
		this.id=id;
		this.name=name;
	}
	
	public boolean equals(Object obj) {
		if(obj==null) {
			return false;
		}else {
			if(obj instanceof Person2) {
				Person2 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=100;
		System.out.println("FatherClass.value="+value);
	}
}
class ChildClass extends FatherClass{
	public int value;
	public void f() {
		super.f();//调用父类的普通方法
		value=200;
		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("李四",18,"java");
		s.rest();
		s.study();
	}
}
class Person1{
	String name;
	int age;
	public void rest() {
		System.out.println("我要休息一会!");
	}
}
class Student extends Person1{
	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("李四",18,"java");
		System.out.println(s instanceof Person1);
		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 v1=new Vehicle();
		Vehicle v2=new Plane();
		v1.run();
		v1.stop();
		v2.run();
		v2.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 TestSuper1 {
	public static void main(String[] args) {
		System.out.println("开始创建一个ChildClass对象。。。");
		new ChildClass2();
	}
}
class FatherClass2{
	public FatherClass2() {
		System.out.println("创建FatherClass");
	}
}
class ChildClass2 extends FatherClass2{
	public ChildClass2() {
		System.out.println("创建ChildClass");
	}
}  

运行结果:

7.继承和组合

我们可以通过继承方便的复用已经定义类的代码。还有一种方式,也可以方便的实现“代码复用”,那就是:“组合”。

“组合”不同于继承,更加灵活。“组合”的核心就是“将父类对象作为子类的属性”,然后,“子类通过调用这个属性来获得父类的属性和方法”。

 package cn.pxy.test;

public class Test{
	public static void main(String[] args) {
		Student s=new Student("李四",18,"java");
		s.person.rest();
		s.study();
	}
}
class Person1{
	String name;
	int age;
	public void rest() {
		System.out.println("我要休息一会!");
	}
}
class Student /*extends Person1*/{
	Person1 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) {
		Person1 p1=new Person1();
		//p1.name="李四";//编译错误
		p1.setName("李四");
		p1.setAge(45);
		System.out.println(p1);
		Person1 p2=new Person1("张三",21);
		System.out.println(p2.getName());
	}
}
class Person1{
	private String name;
	private int age;
	public Person1() {
		
	}
	public Person1(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>130||age<0) {
			this.age=18;//不合法赋值默认值18
		}else {
			this.age=age;
		}
	}
	public int getAge() {
		return age;
	}
	public String toString() {
		return "Person1[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 a1=new Cat();//向上可以自动类型转换
		//传的具体是哪一类就调用那一个类的方法。大大提高了程序的可扩展性
		animalCry(a1);
		Animal a2=new Dog();
		animalCry(a2);
		/*
		 * 编写程序时,如果想调用运行时类型的方法,只能进行强制类型转换
		 * 否则通不过编译器的检查
		 */
		Dog dog=(Dog)a2;
		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(0);无法调用,编译器认为obj是Object类型而不是String类型
		/*
		 * 编写程序时,如果想调用运行时类型的方法,只能进行强制类型转换
		 */
		String str=(String)obj;//向下转型
		System.out.println(str.charAt(0));//位于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(0));
	}
}  

运行结果:

示例:向下转型中使用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(0));
		}else if(obj instanceof StringBuffer) {
			StringBuffer str=(StringBuffer)obj;
			System.out.println(str.charAt(0));
		}
	}
}  

运行结果:

十、抽象类和接口

1.抽象方法和抽象类

抽象方法

使用 abstract 修饰的方法,没有方法体,只有声明。定义的是一种“规范”,就是告诉子类必须要给抽象方法提供具体的实现。

抽象类

包含抽象方法的类就是抽象类。通过 abstract 方法定义规范,然后要求子类必须定义具体实现。通过抽象类,我们就可以做到严格限制子类的设计,使子类之间更加通用。

例如:

 package cn.pxy.test;

//抽象类
abstract class Animal1{
	//抽象方法
	abstract public void shout();
}
class Pig extends Animal1{
	//子类必须实现父类的抽象方法
	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方法。

判断字符串是否相等不要使用”==”。

文章来源:智云一二三科技

文章标题:Java基础——面向对象

文章地址:https://www.zhihuclub.com/179183.shtml

关于作者: 智云科技

热门文章

评论已关闭

1条评论

网站地图