Java面向对象特性
Java语言的OPP特性
封装
将数据与操作数据的方法相结合,通过方法将数据的对象以及实现的细节保护起来
通过封装和数据隐藏的机制,将一个对象的相关变量和方法封装为一个独立的软件体,并单独进行维护和实现
通过封装,使得对象能够在系统内部方便的进行传递,同时也保证了数据对象的一致性,同时使得程序更加方便维护
继承
当A类是B类的一个特例的时候,我们就说B是A的父类。
子类继承了父类的方法和成员变量,子类可以重复使用父类的代码,可以在父类中对一些共同的操作和属性只进行一次说明,来减少代码的复用
继承的分类:
- 单继承:限制一个类最多只能够继承一个类,在单继承方式下,类的层次结构为树型结构,通过类之间的继承实现
- 多重继承:一个类可以直接继承多个类,在多重继承方式下,类的层次结构为网状结构,通过接口实现
多态
定义:对外一个接口,但是在内部却有多种实现方式
分类:
- 运行时多态:通过重载的方式实现
- 编译时多态:通过类之间的继承性、方法的重写以及晚联重写技术实现运行时的多态
多态使得程序具有良好的可扩展性以及使得程序易于维护和理解
Java中类和对象的基本概念
描述同一类对象都具有的数据和行为,类是将这些数据和行为进行封装,形成一种复杂的符合数据类型,类在程序中只定义一次,我们通过new运算操作符实例化同一个类或者对象
示例:
class EmpInfo{
String name;
String designation;
String department;
public EmpInfo(String ename,String eDesign,String eDept){
this.name = ename;
this.designation = eDesign;
this.department = eDept;
}
public void print(){
System.out.printf("%s is a %s at %s.\n",this.name,this.designation,this.department);
}
}
class test{
public static void main(String[] args){
EmpInfo test1 = new EmpInfo("Robert Java","Manager","Coffee shop");
EmpInfo test2 = new EmpInfo("Tom java","Worker","Coffee shop");
test1.print();
test2.print();
}
}
类的定义
类的基本结构
类的基本成分:变量和方法
类的成员变量可以是基于本类型的数据或者数组,也可以是一个类的实例
类的方法用于处理该类中的数据
方法和函数的区别:
- 方法只能是类的成员,且只能在类中定义
- 调用一个类的成员方法,实际上就是进行对象之间或者用户与对象之间的消息传递
Java类定义的基本格式:
<modifiers> class <class name>{
[<attribute_declarations>],
[<constructor_declarations>],
[<methods_declarations>]
}
Java类的定义分为两部分
- 类声明
- 类体:包括成员变量的声明、构造方法和成员方法的构造与声明
类的声明
主要是声明了类的名字以及类的其他属性
类声明的完整格式如下:
[public] [abstract|final] class className [extends Super_Class_Name] [implements Interface_Name_List]{……}
[public] [abstract|final]:说明了类的属性
extends Super_Class_Name:说明类继承了Super_Class_Name为类名的父类
implements Interface_Name_List:说明类实现了Interface_Name_List中列出的所有接口
public:指明任意的类均可以访问这个类,如果没有public只有在和该类在同一个包下的类才可以访问这个类
类体
出现在类声明之后的{}中的内容就是类体。
类体提供了这个类的对象在生命周期内所需要的全部代码
- 构造和初始化新对象的构造方法
- 表示类及其对象状态的变量
- 实现类及其对象行为的方法
上述元素中的变量和方法统称为类的成员,构造方法不是类的方法,所以构造方法不是类的方法
类的封装以及信息隐藏
**实现方式:**通过对类成员的限定性访问实现的
**成员访问的权限:**public、private、protected以及默认权限(没有权限定义符)
private:将类内部的数据隐藏起来,只允许类内部符成员进行使用
public:能够使得外界任何类都对这个成员方法进行访问
protected:
- 基类的protected成员是包内可见的,并且对子类可见;
- 若子类与基类不在同一包中,那么在子类中,子类实例可以访问其从基类继承而来的protected方法,而不能访问基类实例的protected方法。
示例:
public class Mydata{
private int day;
private int month;
private int year;
public String GetData(){
return this.day + "/" + this.month + "/" + this.year;
}
public int Set_data(int a,int b,int c){
if((a > 0 && a <= 31) && (b > 0 && b <= 12)){
this.day = a;
this.month = b;
this.year = c;
return 0;
}else
return -1;
}
}
public class test {
public static void main(String[] args) {
Mydata test = new Mydata();
if(test.Set_data(22, 5, 2009) == 0)
System.out.println(test.GetData());
/*替换第6行
test.day = 22;
test.month = 5;
test.year = 2009;
出现以下问题:
Exception in thread "main" java.lang.Error: 无法解析的编译问题:
字段 Mydata.day 不可视
字段 Mydata.month 不可视
字段 Mydata.year 不可视
at test2.test.main(test.java:9)
*/
}
}
成员变量
**成员变量:**变量的声明出现在类体中,但是该变量不属于任何一个方法
类的成员变量和普通变量一样,在声明的时候必须包括类型和变量名,但是增加了许多可选的修饰项
成员变量的定义:
[public|private|protected][static][final][transient][volatile] type varible_name;
[public|private|protected]:说明了访问的权限
[static]:用于限制该成员变量为类属性,没有用static修饰的成员变量为实例变量
[final]:用于声明一个常量,其值不可以修改
[transient]:用于声明一个暂时性的变量,在默认情况下,类中的所有变量都是对象永久状态的一部分,当对象被保存到外存的时候,这些变量必须同时进行保存,transient限定的变量则指示Java虚拟机,该变量并不属于对象的永久变量,进而不能够被永久存储
[volatile]:在多个并发线程共享的时候,系统采取更优化的控制方法提高线程并发执行的效率
成员方法
成员方法的定义:方法声明和方法定义
方法的声明
格式
[access_Level][static][final|abstract][native][synchronized]<return_type><name>([<argument_list>])[throws<expection_list>]{
<block>
}
access_Level:和成员变量相同,可以使用[public|private|protected]限定对成员方法的访问权限
static:限定他的方法为类方法,实例方法是不需要static限定词
abstract:表示方法为抽象方法,并没有实体
final:指明方法不能够被重写
native:表明方法是使用其他语言实现的
synchronized:用来控制多个并发线程对共享数据的访问
throws<expection_list>:列出该方法将要抛出的例外
**方法体:**是对方法的实现,包括所有的局部变量以及所有的合法的Java语句
在方法体中可以声明该方法中用到的局部变量,但是其作用域只在该方法的内部。
如果局部变量的名称和成员变量的名称相同的话,成员变量会被隐藏,如果需要使用成员变量,则需要在前面加“this”
实例:
public class VarTest {
private int x = 1;
private int y = 1;
private int z = 1;
public void changevar(int a, int b, int c) {
x = a;
int y = a;
int z = 9;
System.out.printf("In changvar:x = %d y = %d z = %d\n", x, y, z);
this.z = c;
}
public String get_XYZ() {
return "x = " + x + " y = " + y + " z = " + z;
}
}
public class test {
public static void main(String[] args) {
VarTest test = new VarTest();
System.out.println("Before changVar:"+test.get_XYZ());
test.changevar(10, 10, 10);
System.out.println("Alter changVar:"+test.get_XYZ());
}
}
result:
Before changVar:x = 1 y = 1 z = 1
In changvar:x = 10 y = 10 z = 9
Alter changVar:x = 10 y = 1 z = 10
注意:return语句的返回值的类型应该和方法声明中的返回值匹配,当声明为void的时候,则不使用带返回值的return语句
方法中的调用参数的传递
Java中方法调用的参数传递方式为传值,即方法的调用不会改变调用程序中作为方法参数的变量的值
当方法的参数类型是对象或者数组等引用类型的时候,在方法调用中传递给该参数的任然是调用程序中对应变量的值,也就是我们常说的对某个变量或者数组的引用。但是如果说,在方法中对该参数指向的对象进行修改,那么主程序中的参数也会受到影响
在方法中可能会改变引用型参数所指向的对象的内容,但是对象的引用不会改变
public class PassTest {
float ptValue;
//参数类型是基本类型
public void changeInt(int value) {
value = 55;
}
//参数类型是引用型,并且方法中改变了参数的值
public void changeStr(String value) {
value = new String("different");
}
//参数类型是引用型,并且方法中改变了参数所指对象的成员变量值
public void changeObjvalue(PassTest ref) {
ref.ptValue = 99.0f;
}
}
public class test {
public static void main(String[] args) {
String str;
int val;
PassTest pt = new PassTest();//创建PassTest()的对象
//测试基本类型参数的传递
val = 11;
pt.changeInt(val);
System.out.println("Int value is :" + val);
//测试引用类型参数的传递
str = new String("Hello");
pt.changeStr(str);
System.out.println("Str value is :" + str);
//测试引用类型参数的传递
pt.ptValue = 101.0f;
pt.changeObjvalue(pt);
System.out.println("Pt value is :" + pt.ptValue);
}
}
result:
Int value is :11
Str value is :Hello
Pt value is :99.0
通过测试可知,当我们传递的参数为对象的时候,函数中的值改变,主函数中的值也会发生改变
可变参数列表
使用可变参数表,可以使方法具有数目不定的多个参数。
定义格式:
type ... 参数名
type后面的...表示该参数在方法调用时将传递进来一个数组或者一个参数序列
示例:
public class Calculation {
public float avg(int... nums) {
int sum = 0;
for (int i : nums)
sum += i;
return (float) sum / nums.length;
}
}
public class test {
public static void main(String[] args) {
Calculation testCalculation = new Calculation();
float avarage_1 = testCalculation.avg(10,20,30);
float avarage_2 = testCalculation.avg(5,6,7,8,9,10);
System.out.println("The avarage of 10,20,30 is "+avarage_1);
System.out.println("The avarage of 5,6,7,8,9,10 is "+avarage_2);
}
}
result:
The avarage of 10,20,30 is 20.0
The avarage of 5,6,7,8,9,10 is 7.5
注意:
- 可变参数只能够作为方法参数列表中的最后一个参数
- 一般情况下,不要重载带有可变参数列表的方法,否则很难确定具体被调用的是哪个重载方法
方法的重载
方法的重载是允许在一个类的定义当中,多个方法使用相同的方法名
方法的重载是面向对象程序语言多态性的一种形式,它实现了Java编译时多态。由编译器在编译的时刻确定具体调用哪个被重载的方法
Java中使用重载的注意事项:
- 方法的参数表必须不同,包括参数的类型或者个数,以此来区分不同的方法体
- 方法的返回类型、修饰符可以相同也可以不相同
this
this是Java中一个具有特殊意义的引用,它指向当前对象它自己
实际上,在一个方法被调用的执行的时候,Java会自动的给对象的变量和方法都加上this引用,用于指向内存堆中的对象。
构造方法
Java中所有的类都有构造方法
构造方法是用来初始化该类的对象,同时构造方法也存在有名称、参数和方法体以及访问权限的限制
定义格式:
[public|protected|private] <class_name>([<argument_list>]){
[<statements>]
}
public:任何类都能够创建这个类的实例对象
protected:只有这个类的子类以及与该类在同一个包下的类可以创建这个类的实例对象
private:没有其他类可以实例化这个类。此时,这个类中可能包含一个具有public权限的方法,只有这些方法能够构造该类的对象并返回
default:只有与该类处于同一个包中的类才可以创建这个类的实例对象
构造方法的特点:
- 构造方法的名称必须和类名相同
- 构造方法不能够有返回值
- 用户不能够直接调用构造方法,必须通过关键字new自动调用它
示例:
public class Dog {
private int weight;
public Dog() {
weight = 42;
}
public int get_weight() {
return weight;
}
public void set_weight(int new_weight) {
weight = new_weight;
}
}
public class test {
public static void main(String[] args) {
Dog testDog = new Dog();
System.out.println("This dog weight is "+testDog.get_weight());
}
}
result:
This dog weight is 42
默认构造方法
在类的定义当中,可以不定义构造方法,而其他类任可以通过调用new XXX()来实例化一个XXX类的对象。这是因为Java在编译时默认给没有定义构造方法的类加入一个特殊的构造方法,这个方法不带参数且方法体为空
用默认构造方法初始化对象的时候,又系统默认值初始化对象的成员变量,各种数据类型的默认值为
数值型 | 0 |
---|---|
boolean | false |
char | ‘\0’ |
对象 | null |
注意:一旦在类中定义了构造方法,默认的构造方法就不会再被加入到类的定义当中,此时如果再使用默认构造方法会造成编译错误,为了避免这类错误,如果类中定义了带参数的构造方法,通常也将加入不带参数的构造方法
重载构造方法
构造方法可以重载,而重载构造方法的目的是为了使对象具备不同的初始值,为对象的初始化提供方便
示例:
public class Employee {
private String name;
private int Salary;
public Employee(String n,int s) {
name = n;
Salary = s;
}
public Employee(String n) {
this(n, 0);
}
public Employee() {
this("Unknown");
}
public String getName() {
return name;
}
public int get_Salary() {
return Salary;
}
}
public class test {
public static void main(String[] args) {
Employee testEmployee_1 = new Employee();
System.out.println("Name:"+testEmployee_1.getName()+" Salary:"+testEmployee_1.get_Salary());
Employee testEmployee_2 = new Employee("景清");
System.out.println("Name:"+testEmployee_2.getName()+" Salary:"+testEmployee_2.get_Salary());
Employee testEmployee_3 = new Employee("景清",4000);
System.out.println("Name:"+testEmployee_3.getName()+" Salary:"+testEmployee_3.get_Salary());
}
}
result:
Name:Unknown Salary:0
Name:景清 Salary:0
Name:景清 Salary:4000
过程分析:当我们在初始化的时候没有带有任何参数的时候,会先去调用Employee(),在这一步会调用this("unknown")这条语句,这条语句就等同于Employee("unknown"),所以最后会输出Name:Unknown Salary:0这一结果
访问控制
在Java中,可以在类的定义中使用权限修饰符来保护类的变量和方法
四种不同的访问权限:
- public
- protected
- private
- default,不使用任何修饰符
对于类的成员变量和方法可以定义上述4种访问级别,对于类(内部类除外)可以有public和default两种
四种权限的访问范围
同一个类 | 同一个包 | 子类 | 全局 | |
---|---|---|---|---|
private | 1 | |||
default | 1 | 1 | ||
protected | 1 | 1 | 1 | |
public | 1 | 1 | 1 | 1 |
private
类中带有private的成员只能够被这个类自身访问。
private对于权限的限制是最大的,使得外界无法访问想要隐藏的数据或者方法,有利于数据的安全性以及保证数据的一致性,也符合程序设计中隐藏内部信息处理细节的原则
构造方法可以限定为private。如果构造方法限定为private则其他类不能够生成该类的实例对象
public class Alpha {
private int iamprivate;
private void privateMethod() {
System.out.println("privateMethod");
}
}
public class test {
Alpha testAlpha = new Alpha();
testAlpha.iamprivate = 0;//描述:标记“iamprivate”上有语法错误
testAlpha.privateMethod();//描述 :标记“privateMethod”上有语法错误
}
注意: 同一个类的不同对象之间可以访问对方的private成员变量和方法,这是因为访问控制权限是在类级别上的,而不是在对象级别上的
实例:
public class Alpha {
private int iamprivate;
public Alpha(int i) {
// TODO 自动生成的构造函数存根
iamprivate = i;
}
boolean isEqualTo(Alpha anotherAlpha) {
if(this.iamprivate == anotherAlpha.iamprivate)
return true;
else {
return false;
}
}
}
public class test {
public static void main(String[] args) {
Alpha testAlpha_1 = new Alpha(10);
Alpha testAlpha_2 = new Alpha(12);
if(testAlpha_1.isEqualTo(testAlpha_2))
System.out.println("equal");
else {
System.out.println("not equal");
}
}
}
default
不加任何访问权限限定的成员采用的是默认的访问权限,称为default或者package
default权限意味着可以被这个类本身和该类所在的包中的类所访问
在其他包中定义的类,即便是这个类的子类,也不能访问这些成员
对于构造方法,如果不加任何修饰词的话也是default访问权限,则除这个类本身和同一个包中的类之外,其他的类都不能够生成该类的实例
protected
类的定义中带有protected的成员可以被这个类本身、它的子类、以及处于同一个包中的其他类所访问,而无关的类不能够访问这些类成员
public
带有pubic的成员可以被所有的类访问,任何一个包中的类都可以直接访问public的变量和方法,对于构造方法,如果说权限限定为public,则在所有的类中都可以生成该类的实例
一般将外界需要直接访问的类成员,设置为public,用来做为和外界进行信息交换的接口
内部类
内部类的概念
内部类是在一个类的声明里面声明的类,也称为嵌套类。内部类和包容它的类可以形成一个有机的整体
示例:
class A{
……
class B{
……
}
}
我们将B称为内部类,A称为B的外包类
内部类的使用
1.内部类作为外包类的一个成员使用
内部类可以作为外包类的一个成员来使用,可以访问外包类的所有成员和方法
示例:内部类访问外包类成员
public class Outer {
private int size;//初始化为0
/**定义内部类Inner*/
class Inner{
public void doStuff() {
size++;
}
}
Inner i = new Inner();
public void increase_Size() {
i.doStuff();//调用内部类的方法
}
public int get_Size() {
return size;
}
}
public class test {
public static void main(String[] args) {
Outer testOuter = new Outer();
for(int i = 0; i < 4; i++) {
testOuter.increase_Size();
System.out.println("The value of size is " + testOuter.get_Size());
}
}
}
result:
The value of size is 1
The value of size is 2
The value of size is 3
The value of size is 4
示例:内部类中加上修饰符访问同名外包类成员
public class Outer {
private int size;
/** 定义内部类*/
public class Inner {
private int size;
public void doStuff(int size) {
size++;//存取局部变量
this.size++;//存取内部类的成员变量
Outer.this.size++;//存取外包类的成员变量
System.out.println("size in Inner.doStuff() " + size);
System.out.println("size in Inner class " + this.size);
System.out.println("size in Outer class " + Outer.this.size);
}
}
Inner testInner = new Inner();
public void increase_Size(int a) {
testInner.doStuff(a);//调用内部类的方法
}
}
public class test {
public static void main(String[] args) {
Outer testOuter = new Outer();
testOuter.increase_Size(10);
}
}
result:
size in Inner.doStuff() 11
size in Inner class 1
size in Outer class 1
内部类访问外包类中同名变量或者方法的方式:外部包名.this.同名变量名
2.外包类的语句块中定义内部类
内部类可以在一个方法体的语句块中定义
此时内部类可以访问语句块中的局部变量,但是仅仅限于在运行该语句块的时候,当这个方法结束之后,内部类将不能够访问所在语句块的局部变量
但是对于在内部类中定义的final变量在方法结束后任然存在,内部类对于带有final的局部变量的访问不受上述限制
注意:按照上述方法定义的内部类只能够用于定义它的语句块之中,不能够出现在定义它的语句块之外
示例:在语句块中定义的内部类访问语句块的局部变量
public class Outer {
private int size = 5;
/** 方法make_Inner*/
public Object make_Inner(final int final_local_var) {
int local_var=9;
/*local_var =9;
* 在内部类里面使用的变量,它只有一次赋值的机会,
* 就是我们默认会认为它是一个final类型的变量*/
class Inner{
public String toString() {
//local_var =1;语法糖,
/*这只是个语法糖,局部变量在赋值后,不再修改,
*符合final的特征,所以编译器不强制加final。
*如果你再次修改局部变量的值,编译器就会提示你需要加final。
* */
//System.out.println(local_var);
//local_var = 1;
return ("#<Inner size = "+size+
" localVar = " + local_var+//此处对局部变量local_var的非法访问
" final_local_var = "+final_local_var+">");
}
}
return new Inner();
}
}
public class test {
public static void main(String[] args) {
Outer outer = new Outer();
Object obj = outer.make_Inner(40);
System.out.println("The Object is "+obj.toString());
}
}
jdk1.8里面的结果:
The Object is #<Inner size = 5 localVar = 6 final_local_var = 40>
jdk1.8以前的版本会在local_var非法访问部分报错,内部类的生命周期必局部变量的长
3.在外包类以外的其他类中访问内部类
Java中,内部类的访问权限有:public、private、protected和default四种
Java中,普通类的访问权限只有:public和default
对于可以在外包类之外访问的内部类,引用内部类的时候必须使用完整的标识:外包类名.内部类名,并且在创建内部类对象的时候,必须和外部类的对象有关
示例:在外包类之外访问内部类
public class Outer {
private int size;
/** 定义内部类 */
class Inner{
void do_Stuff() {
size++;
System.out.println("The size value of the Outer class:" + size);
}
}
}
public class test {
public static void main(String[] args) {
Outer testOuter = new Outer();
//声明并创建内部类的对象
Outer.Inner in_testInner = testOuter.new Inner();
//调用内部类的方法
in_testInner.do_Stuff();
}
}
result:
The size value of the Outer class:1
从上述代码中我们可以看出在外包类之外访问内部类的方法的格式为:
设类B是类A的内部类,在其他类中这样访问类B
A a = new A();
A.B test = a.new B();
内部类的特性
- 内部类的类名只用于定义它的类或者语句块内,在外部引用它的时候必须给出带有外部包类名的完整名称,并且内部类的名字不能够和外包类的名字相同
- 内部类可以访问外包类的静态或者实例成员变量
- 内部类可以在成员方法中定义,该成员方法的局部变量和参数必须是final的才能够被内部类使用
- 内部类可以使用public、protected、private和default四种访问控制权限
- 内部类可以被声明为static,普通的类不可以被声明为static,这样的内部类就变成了顶层类,相当于将类放到了外部,不再是嵌套的内部类,并且它的对象中将不包含指向外包类对象的指针,所以不能再引用外包类对象
- 内部类可以是抽象类或者接口,如果是接口,则可以由其他内部类实现
- 只有顶层类可以声明static成员,如果说内部类需要定义static成员,则该内部类必须声明为static,否则,一般的内部类成员不能被声明为static
匿名类
本地类:一个内部类在方法体中存在声明,它采用常规的类声明
匿名类:一个内部类没有类名,是在一个表达式中定义的,是表达式的一部分
匿名类要继承一个父类或者说是要实现一个接口,类似于调用父类或者接口的构造方法,只是在构造方法后的代码块中包含了匿名类的定义
匿名类定义的语法包含:
- new运算符
- 所继承的父类或所实现接口的名称
- 用括号括起的构造方法的参数(如果是实现接口,因为接口没有构造方法,则括号中的内容为空)
- 匿名类的声明块,在这个块中允许出现方法的声明,但是不允许出现语句
示例:匿名类示例
public class classA {
void test_method() {}
}
public class test {
public static void main(String[] args) {
classA test_ClassA = new classA() {
//在没有使用匿名类的时候,会有警告信息
//必须在创建的时候对匿名类进行补充
void test_method() {
System.out.println("Hello World!");
}
};
test_ClassA.test_method();
}
}
result:
Hello World!
Lambda表达式
日后再看
对象的生命周期
对象的创建
1.对象的创建步骤
Point origin_one = new Point(23,94);
Rectangle rect_one = new Rectangle(origin_one, 100, 200);
Rectangle rect_two = new Rectangle(50, 100);
-
声明对象变量
以Some_Class Object_Var的形式声明保存这个对象引用的变量,以后通过这个变量对对象进行操作
对象变量的声明并没有创建对象,系统只是为这个变量分配了一个引用空间
-
对象的实例化
通过使用new运算符进行对象的实例化,new Some_Class();
对象实例化的过程:
- 为对象分配空间,执行new运算符后的构造方法完成对象的初始化
- 返回该对象的引用
2.创建与初始化对象的操作
point类的定义:
public class Point{
public int x = 2;
public int y = 2;
public Point(int x, int y){
this.x = x;
this.y = y;
}
}
new Some_Class的过程:
- 为对象分配内存空间,并将成员变量进行按照系统默认值进行初始化
- 执行显式初始化,执行在类成员声明的时候带有的简单赋值表达式
- 执行构造方法,实现对象的初始化操作
对象的使用
通过**圆点运算符(.)**访问对象的状态和方法
使用对象的变量:object_reference.variable_name
使用对象的方法:object_reference.method_name(argument_list);
**注意:**对对象进行直接操作可能会产生一些无意义、或者破坏完整性的以后后果。建议通过使用对象提供的设置和获取变量值的方法进行读写,同时,通过这种方法修改变量的属性的时候,不会影响访问对象变量的程序
对象的清除
在Java中,运行系统会自动的确定某个对象在不再被使用后自动将其删除,这个过程垃圾收集
垃圾收集器的对象:确认不存在任何引用的对象
一个变量保存的引用通常是在其作用域内有效的,在其变量的作用域之外的变量及其包含的引用将不复存在
显式删除一个对象:赋值为NULL
注意:对于一个对象,只有对该对象的所有引用都进行删除,垃圾收集器才能够回收这个对象
垃圾收集器
Java中的垃圾收集器是周期性的不断释放不再被引用的对象所占据的内存,自动执行内存回收
垃圾收集器的优先级较低在系统空闲期间执行,故而其速度较慢
显示调用垃圾收集器:调用System.gc()
对象的最终化处理
对象的最终化:一个对象在被收集之前,垃圾处理器将调用对象的finalize()方法,使得对象能够做最后的处理,释放占有的内存
只有执行对象的最终化处理的对象才意味着被废弃,一般情况下不需要实现finalize()方法
finalize()方法是Object类的一个成员方法,Object类是Java类的体系的根系,是所有Java类的父类,任何一个Java类都可以重写finalize()这个方法实现对象的最终化处理
**注意:**如果重写了finalize()方法,在该方法结束前要调用super.finalize()方法,对该对象继承而来的资源进行最终化处理
类的继承与多态
类的继承
1.子类及其继承
类之间的继承关系是面向对象程序设计语言**(OOP)的基本特征之一,从继承我们可以看出子类是父类的一个特例**
在Java中,子类的声明用关键字extends。具体格式如下:
class SubClass extends SuperClass{……}
将SubClass称为SuperClass的直接子类
子类可以继承父类的方法和属性,所以子类只需要声明自己独有的内容,但是子类并不能够继承父类的所有变量和方法,以下变量和方法是子类不能够继承的:
- 带private修饰符的方法和变量
- 构造方法
2.单继承
Java并不支持多重继承,只支持单继承,也就是说在Java中,只能够从一个类中继承,extends关键字后面的类名只能够有一个
优点:可以避免多个直接父类之间可能产生的冲突,使得代码更加可靠
Java是通过接口机制,允许一个类实现多个接口,避免了多重继承的复杂性,同时达到了多重继承的目的
3.super关键字
super关键字指向该关键字所在的父类,用来引用父类的成员变量或者方法
通过super.some_method([paramlist])调用some_method方法的时候,该方法不一定是当前类的父类中定义的,但是可以是直接父类在类的层次体系中继承而来
示例:通过super关键字实现对父类构造方法和成员方法的访问
public class Employee {
private String name;
private int salary;
public Employee(String name, int salary) {
// TODO 自动生成的构造函数存根
this.name = name;
this.salary = salary;
}
public String get_details() {
return "Name:" + name + "\nSalary:" + salary;
}
}
public class Manager extends Employee {
private String department;
public Manager(String name, int salary, String department) {
super(name, salary);
// TODO 自动生成的构造函数存根
this.department = department;
}
public String get_details() {
return super.get_details() + "\nDepartment:" + department;
}
}
public class test {
public static void main(String[] args) {
Manager testManager = new Manager("Tom", 2000, "Finance");
System.out.println(testManager.get_details());
}
}
result:
Name:Tom
Salary:2000
Department:Finance
调用父类的构造方法的时候,使用super关键字,且该语句要出现在子类构造方法的第一句
4.子类对象的创建以及实例化过程
- 分配对象所需要的全部内存空间,并初始化为0值
- 按照继承关系,从顶向下显示初始化
- 按照继承关系,从顶向下调用构造方法
当前类的各级父类直到类体系的根类,均要执行第2和3步
注意:
-
Java的安全模型要求对象在初始化的时候,必须先将从父类继承来的部分进行完全初始化
-
一般在子类的第一行通过super([paramlist])调用父类的某个构造方法,如果不使用super关键字,Java将调用父类的默认构造方法(不带参数),如果在父类中没有无参数构造方法,将产生错误
方法的重写
1.子类中父类成员的隐藏
隐藏:通过子类对象调用子类中和父类同名的变量和方法的时候,操作的是这些变量和方法咋子类中的定义
子类通过成员变量的隐藏和方法的重写,将父类的状态和行为改写为自己的状态和行为
在类层次结构中,当子类的成员变量和父类的成员变量同名的时候,父类的成员变量会被隐藏
当子类中的方法和父类中的方法具有相同的结构的时候,子类就重写了父类的方法
2.方法重写
重写是指子类重写父类的方法,子类可以改写父类的成员方法,但是必须和父类具有相同的参数列表、返回值、以及方法名
示例:方法重写
public class Employee {
protected String name;
protected int salary;
public Employee(String name, int salary) {
// TODO 自动生成的构造函数存根
this.name = name;
this.salary = salary;
}
public String get_details() {
return "Name:" + name + "\nSalary:" + salary;
}
}
public class Manager extends Employee {
private String department;
//private int salary;
public Manager(String name, int salary, String department) {
super(name, salary);
// TODO 自动生成的构造函数存根
this.department = department;
}
public String get_details() {
return "Name:" + name + "\nSalary:" + salary + "\nDepartment:" + department;
}
}
public class Secretary extends Employee{
public Secretary(String name, int salary) {
super(name, salary);
// TODO 自动生成的构造函数存根
}
}
public class test {
public static void main(String[] args) {
Manager testManager = new Manager("Tom", 2000, "Finance");
Secretary test = new Secretary("Mary", 1500);
System.out.println(testManager.get_details());
System.out.println(test.get_details());
}
}
3. 方法重写的规则
- 子类重写父类的方法的时候返回值的类型必须和父类相同
- 子类重写的方法的时候访问权限不能缩小
- 子类重写方法的时候,不能够抛出新的异常
运行时的多态
上溯造型
由于子类继承了父类的所有变量和方法,所以说父类所具有的方法也可以在它所派生出来的子类中使用,发送给父类的任何消息当然也可以发送给子类,子类的对象其实也是父类的对象,即子类的对象既可以作为该子类的类型,同时也可以作为其父类的类型对待
从一个基础父类派生出来的各种子类均可以以基础父类的类型对待
上溯造型:将一种类型的对象引用(子类)转换为另一种类型(父类)的对象引用
上溯造型要做的事情就是实现子类向父类的转换
由于子类中包含比父类更多的元素,故而上溯造型实现的是从具体到抽象的一个过程,这个过程是安全的
下塑造型,即我们常说的强制数据类型转换,数据的强制类型转换是不安全的,在进行强制数据类型的转换的时候,我们需要进行类型的检查
根据我们学习C语言的常识我们可以知道,数组中的数据类型是要保持一致的。但是在Java中,由于上溯造型这一概念的存在,使得Java在数组中被允许存放不同类型的数据对象,示例
Employee[] staff = new Employee[3];
Staff[0] = new Manger();
Staff[1] = new Secretary();
Staff[2] = new Employee();
当一个数组的类型是object的时候,该数组可以包含任意类型的对象
运行时多态
由于上溯造型的存在,使得一个对象既可以作为自己的类型又可以作为其父类型对待,使得子类对象可以作为父类对象使用,父类对象的变量也可以去指向子类
通过父类变量发出的方法调用,可能执行的是该方法在父类中的实现,也可能是在某个子类中的实现,具体如何只能够根据运行的时候的具体状态来确定
同一个父类派生出的多个子类可以被当做同一类型来对待,然后相同的代码就可以处理所有不同的类型
多态性的使用使得代码的组织以及可读性均得到改善,使得程序的可扩展性加强
运行时多态的原理
联编:将一个方法调用和一个方法体连接在一起
晚联编:在运行时刻执行的联编,联编的操作对象是根据对象的具体类型确定的,在Java中,除了final类型的变量,其余的所有方法的联编均是采用晚联编技术
final:一方面防止方法的重写,另一方面有效的阻止晚联编,提高代码的运行效率
示例:运行时多态
public class Shape {
protected void draw() {
}
protected void erase() {
}
}
public class Circle extends Shape{
protected void draw(){
System.out.println("Calling Circl.draw()");
}
protected void erase() {
System.out.println("Calling Circl.erase()");
}
}
public class Square extends Shape{
protected void draw(){
System.out.println("Calling Square.draw()");
}
protected void erase() {
System.out.println("Calling Square.erase()");
}
}
public class Triange extends Shape{
protected void draw(){
System.out.println("Calling Triange.draw()");
}
protected void erase() {
System.out.println("Calling Triange.erase()");
}
}
import java.util.Random;
public class test {
static void draw_one_shape(Shape s) {
s.draw();
}
static void draw_shapes(Shape[] tt) {
for (int i = 0; i < tt.length; i++) {
tt[i].draw();
}
}
public static void main(String[] args) {
Random rand = new Random();
Shape[] s = new Shape[9];
for (int i = 0; i < s.length; i++) {
switch(rand.nextInt(3)) {
case 0:s[i] = new Circle();break;
case 1:s[i] = new Square();break;
case 2:s[i] = new Triange();break;
}
}
draw_shapes(s);
}
}
result:
Calling Circl.draw()
Calling Circl.draw()
Calling Triange.draw()
Calling Triange.draw()
Calling Triange.draw()
Calling Square.draw()
Calling Triange.draw()
Calling Circl.draw()
Calling Square.draw()
/*
关于多态的特点的一点困惑,但是还没有检验:
就是说我们可以在数组中存放同一个父类的子类,那是不是说我们可以在数组中存放的不同类型的类,毕竟所有的类不都是object的子类的么?
*/
多态的意义
可扩展性:当程序从通用的基础类派生出任意多的新类的时候,或者向基础类派生任意多的新类的时候,无需修改对原有对基础类进行处理的相关程序,并且可以处理这些新类
为了利用多态使得程序具有良好的可扩展性,程序中的方法应该尽量利用基础类的接口
对象类型的强制类型转换
强制类型转换又称为向下造型,是将父类类型的对象变量强制转换成子类类型
Java中允许上溯造型的存在,使得父类类型的变量可以指向可以指向子类对象,通过该变量只能访问父类中定义的变量和方法,子类特有的部分被隐藏,不能够访问,只有我们将父类类型的变量强制转换成子类类型的时候,才能够通过该变量访问子类特有的成员
在强制类型转换中,一般要先测试确定对象的类型,然后再执行转换
我们一般使用instanceof运算符来测定对象的类型
格式:instanceof运算符的格式
a_object_varible instanceof some_class
- 当a_object_varible的结果为some_class的时候,值为true
- 当a_object_varible的结果不为some_class的时候,值为false
强制类型转换的一般格式为:
(some_class)a_object_variable
注意:
- 对象变量的转换的目标类型,一定是当前对象类型的子类。这个规则由编译器检查
- 在运行时刻也要进行对象类型的检查,例如某个进行对象类型的强制转化中,省略了instanceof的检查且对象的类型并不是要转换的目标的类型,那么程序就会抛出异常
object类
object类是Java中的根类,即Java中的每个类均是object类的子类或者间接子类
object类中可以被重写的类:
- clone()
- equals()和hashCode():这两个方法必须同时重写
- finalize()
- toString():返回对象的字符串表达形式
其余的类均被定义为final类型,不可被重写
clone()方法
利用clone()方法可以将一个已有对象复制为另外一个对象
示例:
a_cloneable_object.clone()
通过上述方法创建一个和a_cloneable_object类型相同的对象,并将该对象成员变量值初始化为a_cloneable_object中相应成员变量的值
注意:
-
object类本身并没有实现这个接口,所以我们如果需要使用这个功能必须先去实现这个接口
-
clone是shallow copy而不是deep copy
shallow copy:如果被复制对象的成员变量是一个引用型变量,则复制对象中不包括该变量指向的对象
deep copy:在上述情况下,同时复制该变量所指向的对象
有一说一,这块我是真的没看懂
equals()方法
public boolean equals(object obj)比较当前对象的引用是否与参数obj指向同一个变量,是的话就返回true
String、Data、File类和所有包装类均重写了该方法,改为比较两个对象的内容
在Java中,“==”被用于比较两个变量所指的地址,所以比较两个字符串是否相等,应该使用equals方法,例如:str1.equals(str2)
toString()方法
toString()方法的返回对象是字符串类型的,表达的内容因具体的对象而异
一般我们都在自己的类中重写该方法
getClass()方法
getClass()返回对象的类信息,该方法返回一个Class类型的对象
Class类型常用于在运行时刻创建编译时候不知道类型的对象
示例:
Object create_new_Instanceof(object obj){
return obj.getClass().newinstance();//这个后面的instance是什么意思
//是说重写后的getClass()方法么
}
ect_variable
**注意:**
1. 对象变量的转换的目标类型,一定是当前对象类型的子类。这个规则由编译器检查
2. 在运行时刻也要进行对象类型的检查,例如某个进行对象类型的强制转化中,省略了instanceof的检查且对象的类型并不是要转换的目标的类型,**那么程序就会抛出异常**
### object类
object类是Java中的根类,即Java中的每个类均是object类的子类或者间接子类
object类中可以被重写的类:
1. clone()
2. equals()和hashCode():这两个方法必须同时重写
3. finalize()
4. toString():返回对象的字符串表达形式
其余的类均被定义为final类型,不可被重写
#### clone()方法
利用clone()方法可以将一个已有对象复制为另外一个对象
示例:
```java
a_cloneable_object.clone()
通过上述方法创建一个和a_cloneable_object类型相同的对象,并将该对象成员变量值初始化为a_cloneable_object中相应成员变量的值
注意:
-
object类本身并没有实现这个接口,所以我们如果需要使用这个功能必须先去实现这个接口
-
clone是shallow copy而不是deep copy
shallow copy:如果被复制对象的成员变量是一个引用型变量,则复制对象中不包括该变量指向的对象
deep copy:在上述情况下,同时复制该变量所指向的对象
有一说一,这块我是真的没看懂
equals()方法
public boolean equals(object obj)比较当前对象的引用是否与参数obj指向同一个变量,是的话就返回true
String、Data、File类和所有包装类均重写了该方法,改为比较两个对象的内容
在Java中,“==”被用于比较两个变量所指的地址,所以比较两个字符串是否相等,应该使用equals方法,例如:str1.equals(str2)
toString()方法
toString()方法的返回对象是字符串类型的,表达的内容因具体的对象而异
一般我们都在自己的类中重写该方法
getClass()方法
getClass()返回对象的类信息,该方法返回一个Class类型的对象
Class类型常用于在运行时刻创建编译时候不知道类型的对象
示例:
Object create_new_Instanceof(object obj){
return obj.getClass().newinstance();//这个后面的instance是什么意思
//是说重写后的getClass()方法么
}
如果说我们已经知道了类的名字,也可以类法名字获得一个Class对象