当前位置 博文首页 > zhouyu的博客:面向对象(下)笔记
为8种基本数据类型分别定义了相应的引用类型,并称之为基本数据类型的包装类
包装类对象和基本类型变量之间可以进行自动转化或强制转化
字符串类型的值转化为基本类型的值:
利用包装类提供的parseXxx(String s)
利用包装类提供的valueOf(String s)
String也有多个重载valueOf()方法,用于将基本类型变量装换成字符串
包装类型可以直接与对应基本类型的值进行比较
两个包装类的实例进行比较时只有其指向同一个对象时才会返回true
自动装箱在数值在-128到127时是引用系统中已经存在的对象,所以完全相等,但是不在这个范围里时,系统总会重新创建一个Integer实例,所以他们有区别不相等,但大小比较和数学运算等仍可正常进行
compare(xxx val1,xxx val2)方法,比较两个基本类型的大小
打印对象:
使用输出函数打印实例化对象实际上是打印对象的toString()方法的返回值
toString()方法是Object类里的一个实例方法,所以所有对象都具有toString()方法
toString()方法是对象对自己的“自我描述”,初始其返回值为“类名+@+hashCode”值,要真正实现“自我描述”的功能,必须要重写toString()方法
大部分情况,重写toString()方法总是返回该对象的所有令人感兴趣的信息所组成的字符串。通常可返回如下格式的宇符串:
类名[field1=值1,field2=值2,...]
==和equals方法:
==和eauals为判断两个变量是否相等的两种方法
对于两个引用类型变量,只有他们指向同一个对象时,==判断才会返回true,==不可用于比较类型上没有父子关系的两个对象
常量池(constant pool)专门用于管理在编译时被确定并被保存在已编译的.class文件中的一些数据.它包括了关于类、方法、接口中的常量,还包括字符串常量
equals()方法时Object类的一个实例方法,默认判断相等标准和==一致,但是可以自己重写equals()方法来实现自定义的相等标准。
String已经重写了equals()方法
通常而言,正确地重写 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)多少次,返回的结果应该保持一致,要么一直是 true ,要么一直是 false
对任何不是 null 的x,x.equals(nul1)定返回false
理解:
使用 static 关键字修饰的成员就是类成员
类中只能包含成员变量、方法、构造器、初始化块、内部类(包括接口、枚举)5种成员,其中 static 可以修饰成员变量、方法、初始化块、内部类
当实例对象访问类变量时,系统还是转化为通过该类来访问类变量,类方法同理
类成员(包括方法、初始化块、内部类和枚举类)不能访问实例成员(包括成员变、方法、初始化块、内部类和枚举类)
单例类:
如果一个类始终只能创建一个实例,则这个类被称为单例类
用private修饰类的构造器可以避免其他类自由创建该类的实例
一旦把该类的构造器隐藏起来,就必须提供一个 public 方法作为该类的访问点,用于创建该类的对象,且该方法必须用 static 修饰(因为调用时还不存在方法,所以需要调用的只能是类方法)
该类还需要用一个静态成员变量保存是否创建过该类的对象
final 关键字可用于修饰类、变量和方法,用于表示它修饰的类、方法和变量不可改变
final 修饰变量时,表示该变量一旦获得了初始值就不可被改变,final既可以修饰成员变量(包括类变量和实例变量),也可以修饰局部变量、形参
final 成员变量:
对于final修饰的成员变量,必须显式地指定初始值,不然将一直是系统默认分配的0、'\u0000'、false或null,那就失去了存在的意义
类变量:
必须在静态初始化块中指定初始值或声明该类变量时指定初始值,而且只能在两个地方的其中之一指定。
实例变量:
必须在非静态初始化块、声明该实例变量或构造器中指定初始值 而且只能在三个地方的其中之一指定。
在初始化之前不能直接访问 final 成员变量,但是可以通过方法访问 final 成员变量,但并不建议在显式初始化之前访问
final 局部变量:
系统不会对局部变量进行初始化,局部变量必须显式初始化,因此使用final修饰局部变量时,可以不指定默认值,若没有指定默认值,则在之后的代码中可以对该变量初始化,但只能一次
不能对final修饰的形参赋值
final 修饰基本类型变量和引用类型变量的区别:
当final修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变
但修饰引用变量时,保存的仅仅是一个引用,只能保证这个引用类型变量所引用的地址不变,但这个对象内部完全可以发生改变
可执行“宏替换”的 final 变量:
对于final类变量、实例变量和局部变量,满足以下条件,则该变量就不再是一个变量,而是相当与一个直接量,会在编译阶段吧所有使用这个变量的地方用该变量的值进行替换:
使用final修饰符修饰
在定义该final变量时指定了初始值
该初始值可以在编译时就被确定下来
如果被赋的表达式只是基本的算术表达式或字符串连接运算,没有访问普通变量,调用方法, Java 编译器同样会将这种 final 变量当成宏变量处理。
final 方法:
final修饰的方法不可被重写
如果入private修饰方法,则即使用final修饰,也可以在其子类中定义与该方法同名、同形参列表、同返回值类型的方法,但是这并不是方法的重写
final 修饰的方法可以进行重载
final 类:
final修饰的类不可以有子类
不可变类:
创建该类的实例后,该实例的实例变量不是可改变的,八个包装类和java.lang.String类都是不可变类
创建自定义的不可变类规则:
使用 private和final修饰符来修饰该类的成员变量。
提供带参数构造器,用于根据传入参数来初始化类里的成员变量
仅为该类的成员变量提供getter方法,不要为该类的成员变量提供 setter方法,因为普通方法无法修改final修饰的成员变量。
如果有必要,重写Object类的hashCode()和equal()方法。equal()方法根据关键成员变量来作为两个对象是否相等的标准,除此之外,还应该保证两个用equals() 方法判断为相等的对象的 hashCode()也相等
对于引用类型变量,如果其成员是可以改变的,就必须采取必要措施来保护该成员变量所引用的对象不会被修改,这样才能创建真正的不可变类
所以在存入和返回引用变量的值时,可以创建新的副本然后存入或返回,即保证外部无法访问它
缓存实例的不可变类:
不可变类的实例状态不可改变,可以很方便地被多个对象所共享。如果程序经常需要使用相同的不可变类实例,则应该考虑缓存这种不可变类的实例
例如使用数组作为缓存池
抽象方法和抽象类:
抽象方法和抽象类必须使用 abstract 修饰符来定义,有抽象方法的类只能被定义成抽象类,抽象类里可以没有抽象方法
抽象方法和抽象类规则:
抽象类和抽象方法必须用abstract修饰符修饰,抽象方法不能有方法体
抽象类不能被实例化
抽象类可以包含成员变量、方法(普通和抽象方法都可以)、构造器、初始化块、内部类等五种成分,抽象类的构造器不能用于创建实例,主要是用于被其子类调用
含有抽象方法的类(包括直接定义了一个抽象方法;或继承了一个抽象父类,但没有完全实父类包含的抽象方法;或实现了一个接口,但没有完全实现接口包含的抽象方法三种情况)只能被定义成抽象类
抽象方法:
[修饰符] 返回值类型 方法名();
修饰符必须包含abstract
抽象类不能用于创建实例,只能当做父类被其他子类继承
abstract 修饰类时表明这个类只能被继承,修饰方法时表明该方法必须由子类提供实现(即重写),而final修饰的类不能被继承,final修饰的方法不能重写,所以final和abstract不能同时使用
static和abstract不能同时修饰某个方法
abstract 方法必须被其子类重写,所以abstract方法不能定义为private访问权限
抽象类的作用:
抽象类是从多个具体类中抽象出来的父类,以这个抽象类作为子类的模板,从而避免子类设计的随意性
模板模式在面向对象的软件中很常用,其原理简单,实现也很简单,下面是使用模板模式的一些简单规则:
抽象父类可以只定义需要使用的某些方法,把不能实现的部分抽象成抽象方法,留给其子类去实现
父类中可能包含需要调用其他系列方法的方法,这些被调方法既可以由父类实现,也可以由其子类实现。父类里提供的方法只是定义了一个通用算法,其实现也许并不完全由自身实现,而必须依赖于其子类的辅助
接口概念:
同一个类的内部状态数据、各种方法的实现细节完全相同,类是一种具体实现体,而接口定义了一种规范,接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它只规定这批类里必须提供某些方法,提供这些方法的类就可满足实际需要
接口是从多个相似类中抽象出来的规范,接口不提供任何实现,接口体现的是规范和现实分开的设计哲学
接口的定义是多个类公同的公共行为规范,这些行为是与外部交流的通道,这就意味着接口里通常是定义一组公用方法。
接口的定义:
[修饰符] interface 接口名 extends 父接口1,父接口2...
{
零个到多个常量定义...
零个到多个抽象方法定义...
零个到多个内部类、接口、枚举定义...
零个到多个私有方法、默认方法或类方法定义...
}
修饰符可以是public或者省略,若省略则默认采取包权限访问控制符,只有在相同包结构下才可以访问该接口
接口名与类名命名规范一致
接口可以有多个直接父接口,但接口只能继承接口,不能继承类
接口不包含构造器和初始化块定义,接口里可以包含成员变量(静态常量)、方法(只能是抽象实例方法、类方法、默认方法、私有方法)、内部类(包括内部接口、枚举)定义
私有方法为默认方法和类方法提供支持,不能用default修饰,可以使用static修饰,私有方法既可以是类方法也可以是实例方法
接口的静态常量不管是否使用public、static和final修饰,系统都会默认为添加这三个修饰
接口里的内部类、内部接口、内部枚举都默认采用public static两个修饰符
定义默认方法需要defailt修饰,且会自动添加public修饰,不能使用static修饰,所以只能使用接口的实现类的实例来调用默认方法
类方法不能使用defailt修饰,必须使用static修饰,默认添加public修饰
接口可以看成一个特殊的类,一个源文件只能有一个public接口,如果源文件定义了一个public接口,则源文件的主文件名必须和该接口同名
接口的继承:
接口支持多继承,一个接口可以有多个直接父接口
接口的使用:
接口不能用于创建实例,但接口可以用于声明引用类型变量,这个引用类型变量必须被引用到其实现类的对象
接口的主要用途:
定义变量,也可用于进行强制类型转换
调用接口中定义的常量
被其他类实现
一个类可以实现一个或多个接口,实现使用 implements 关键字
[修饰符] class 类名 extends 父类 implements 接口1,接口2...
{
类体部分
}
实现接口与继承父类相似,一样可以获得所实现接口里定义的常量(成员变量)、方法(包括抽象方法和默认方法)
implements部分必须放到extends部分后面
一个类实现了一个或多个接口之后 ,这个类必须完全实现这些接口里所定义的全部抽象方法(也就是重写这些抽象方法);否则,该类将保留从父接口那里继承到的抽象方法,该类也必须定义成抽象类。
实现类实现接口里的方法只能使用public访问权限
接口和抽象类:
相同特征:
都不能被实例化,用于被其他类实现和继承
都包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法
差别:
接口体现的是一种规范,类似于整个系统的总纲,制定了系统各个模块的标准,因此接口不应该经常改变
抽象类体现的是一种模板式设计,抽象类可以当做中间产品,已经实现类系统的部分功能,但必须更进一步的完善
用法上的差别:
接口里只能包含抽象方法、静态方法、默认方法和私有方法,不能为普通方法提供方法实现,抽象象类则完全可以包含普通方法。
接口里只能定义静态常量,不能定义普通成员变量;抽象类里则既可以定义普通成员变量,也可以定义静态常量。
接口里不包含构造器;抽象类里可以包含构造器,抽象类里的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作
接口里不能包含初始化块;但抽象类则完全可以包含初始化块
一个类最多只能有一个直接父类,包括抽象类;但一个类可以直接实现多个接口,通过实现多个接口可以弥补 Java 单继承的不足。
面向接口编程:
工厂模式:
//接收类,实现类的方法写入:
public class Compter
{
private Output out;
public Compter(Output out)
{
this.out = out;
}
......
}
//工厂类:
public class OutputFactory
{
public Output getOutput()
{
return new Printer();
}
......
}
//实现类:
public class Printer implements Output,Product
{
......;
}
这样如果修改了实现类的名字,则只需要修改工厂类中的getOutput()函数的内容
降低了耦合性,使所有需要使用的Output对象的类只需与Output接口耦合,而不需要与具体的实现类耦合
命令模式:
例:
//接口封装处理行为:
public interface Command
{
void process(int[] target);
}
//接受处理对象和处理方法并执行:
public class ProcessArray
{
public void process(int[] target,Command cmd)
{
cmd.process(target);
}
}
//具体处理:
public class PrintCommand implements Command
{
public void process(int[] target)
{
for(int tmp:target)
{
System.out.println("迭代输出目标数组的元素:"+tmp);
}
}
}
public class AddCommand implements Command
{
public void process(int[] target)
{
int sum = 0;
for(int tmp:target)
{
sum += tmp;
}
System.out.println("数组元素的总和是:"+sum);
}
}
//主函数:
public class CommandTest
{
public static void main(String[] args)
{
ProcessArray pa = new ProcessArray();
int[] target = {3,-4,6,4};
//第一次处理
pa.process(target,new PrintCommand());
//第二次处理
pa.process(target,new AddCommand());
}
}
对于 PrintCommand和AddCommand 两个实现类而言,实际有意义的部分就是process(int[] target)方法,该方法的方法体就是传入ProcessArray类里的process() 方法的" 处理行为",通过这种方式就可实现process() 方法和 "处理行为 "的分离。
定义在类内部的类称为内部类,包含内部类的类称为外部类:
内部类提供了更好的封装,可以吧内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类
内部类成员可以直接访问外部类的私有数据,因为内部类被当成其外部类成员,同一个类的成员之间可以互相访,但外部类不能访问内部类的实现细节,例如内 部类的成员变量
匿名内部类适合用于创建那些仅需要一次使用的类
语法区别:
内部类比外部类可以多使用三个修饰符: private 、 protected 、 static 外部类不可以使用这三个修饰符
非静态内部类不能拥有静态成员
外部类的静态成员不能直接使用非静态内部类
非静态内部类:
public class OuterClass
{
......
}
可以定义在一个类内部,在方法中也可以定义内部类(称为局部内部类)
成员内部类是一种类成员,局部内部类和匿名内部类则不是类成员
非静态内部类不需要使用static修饰,静态内部类需要使用static修饰
正因为内部类是其外部类的成员,所以可以使用任意访问控制符private、protected和public等修饰
内部类的四个作用域:同一个类、同一个包、父子类和任意位置、因此可以使用四种访问控制权限
如果外部类成员变量、内部类成员变量与内部类里方法的局部变量同名,则可通过使用this、外部类类名.this作为限定来区分
非静态内部类的成员只在非静态内部类范围内是可知的,并不能被外部类直接使用。如果外部类需要访问非静态内部类的成员,则必须显式创建非静态内部类对象来调用访问其实例成员
非静态内部类对象存在时外部类对象一定存在,外部类对象存在时非静态内部类对象不一定存在
静态内部类:
使用static修饰的内部类属于外部类本身,被称为类内部类,也称为静态内部类
静态内部类可以包含静态成员和非静态成员,静态内部类不能访问外部类的实例成员,即使是静态内部类的实例方法也不能访问外部类的实例成员
外部类不能直接访问静态内部类的成员,但可以使用静态内部类的类名作为调用者来访问静态内部类的类成员
接口里可以定义内部类,默认使用public static修饰,所以接口内部类只能是静态内部类
使用内部类:
在外部类内部使用内部类:
不能在外部类的静态成员中使用非静态内部类
外部类以外使用非静态内部类:
内部类不能使用private修饰
省略控制符,只能在与外部类处于同一个包中的类访问
使用prote修饰,可被外部类的子类访问
使用public修饰,可以在任何地方被访问
在外部类以外定义内部类变量的语法格式:
OuterClass.InnerClss varName;
在外部类以外的地方创建非静态内部类实例的语法:
OuterInstance.new InnerConstructor();
非静态内部类的构造器必须由外部类对象来调用
非静态内部类的子类需要显式调用其构造器:
out.super("hello");
out为外部类
非静态内部类的子类不一定是内部类,但是存在与之对应的外部类对象
在外部类以外使用静态内部类:
在外部类以外创建静态内部类的语法:
new OuterClass.InnerConstructor();
局部内部类:
在方法里定义的内部类称为局部内部类,局部内部类仅在该方法里有效,不能在外部类的方法以外的地方使用,所以不能使用访问控制符和static修饰符修饰
如果需要用局部内部类定义变量、创建实例或派生子类,那么都只能在局部内部类所在的方法内进行
匿名内部类:
匿名内部类适合创建那种只需要一次使用的类,定义后会立即消失,不能重复使用
定义格式:
new 实现接口() | 父类构造器(实参列表)
{
//匿名内部类的类体部分
}
匿名内部类必须继承一个父类,或者实现一个接口
规则:
匿名内部类不能是抽象类,因为创建匿名内部类时会立即创建其对象
匿名内部类不能定义构造器,因为没有类名,所以无法定义构造器,但可以定义初始化块,可以通过实例初始化块来完成构造器需要完成的任务
匿名内部类可以当做一个参数传递给一个方法
创建匿名内部类时,必须实现接口或抽象父类的所有抽象方法,也可以重写父类的普通方法
如果局部变量被匿名内部类访问,那么该局部变量相当于自动使用了final修饰,即初始化后不能进行更改
Lambda表达式支持将代码块作为方法参数,Lambda表达式允许使用更简洁的代码来创建一个只有一个抽象方法的接口(称为函数式接口)的实例
Lambda表达式作用:代替匿名内部类的繁琐语法,由三部分组成:
形参列表:形参列表允许省略形参类型,如果形参列表中只有一个参数,甚至连形参列表的圆括号也可以省略
箭头(->):必须通过英文中画线和大于符号组成
代码块:如果代码块只包含一条语句 Lambda 表达式允许省略代码块的花括号,那么这条语句就不要用花括号表示语句结束。Lambda代码块只有一条return语句,甚至可以省略reutrn关键字。Lambda表达式需要返回值,而它的代码块中仅有一条省略了return的语句, Lambda表达式会自动返回这条语句的值
定义类的方式实现枚举类设计:
通过private将构造器隐藏起来
把这个类的所有可能实例都使用 public static final 修饰的类变量来保存
如果有必要,可以提供一些静态方法,允许其他程序根据特定参数来获取与之匹配的实例
使用枚举类可以使程序更加健壮,避免创建对象的随意性
枚举类定义:
使用 enum 关键字(与class和interface关键字的地位相同)定义枚举类
例:
public enum SeasonEnum
{
SPRING,SUMMER,FALL,WINTER;
}
枚举类是一种特殊的类,它一样可以有自己的成员变量、方法,可以实现一个或者多个接口,也可以定义自己的构造器
一个源文件最多只能定义一个public访问权限的枚举类,且该源文件必须与该枚举类的类名相同
枚举类和普通类的区别:
枚举类不能显示继承其他类
使用 enum 定义、非抽象的枚举类默认会使用 final 修饰,因此枚举类不能派生子类。
枚举类的构造器只能用private修饰
枚举类的所有实例必须在枚举类的第一行显式列出,否则这个枚举类永远都不能产生实例
使用枚举类的某个实例:枚举类名.实例名
枚举类默认提供一个valuse()方法,可以遍历所有的枚举值
switch扩展:switch表达式可以是任何枚举类,且后面case表达式中的值可以直接使用枚举值的名字
枚举类默认继承java.lang.Enum类,可以直接使用该类中的方法
枚举类的成员变量、方法和构造器:
枚举类的实例对象是枚举值
尽量不要让枚举类的成员变量随便改变
枚举类通常应该设置成不可变类,成员变量值不应该允许改变,尽量使用private final修饰
如果将成员变量使用final修饰,则需要在构造器中为这些成员指定初始值,所以应该为枚举类显示定义带参数的构造器
一旦为枚举类显式定义了带参数的构造器,列出枚举值时就必须对应地传入参数。
例:
public enum Gender
{
MALE("男"),FEMALE("女");//相当于调用构造器创建实例
private final String name;
private Gender(String name)
{
this.name = name;
}
}
实现接口的枚举类:
枚举类在实现接口时需要实现该接口所包含的方法
所有的枚举实例调用的方法是一样的,因为方法体相同
如果想要不同实例执行的方法结果不一样则需要让每个枚举值分别来实现该方法
包含抽象方法的枚举类:
在枚举类里定义一个抽象方法,然后让枚举类值提供实现
定义每个枚举值时必须为抽象方法提供实现,否则将出现编译错误
public enum Operation {
PLUS{
public double eval(double x,double y){
return x + y;
}
},
MINUS{
public double eval(double x,double y){
return x - y;
}
},
TIMES{
public double eval(double x,double y){
return x * y;
}
},
DIVIDE{
public double eval(double x,double y){
return x / y;
}
};
//为枚举类定义一个抽象方法
//这个抽象方法由不同的枚举类值提供不同的实现
public abstract double eval(double x,double y);
public static void main(String[] args){
System.out.println(Operation.PLUS.eval(5,4));
System.out.println(Operation.MINUS.eval(5,4));
System.out.println(Operation.TIMES.eval(5,4));
System.out.println(Operation.DIVIDE.eval(5,4));
}
}
垃圾回收机制特征:
对象在内存中的状态:
可达状态:对象被创建后有一个以上的引用变量引用它
可恢复状态:如果程序中某个对象不再有任何引用变量引用它,它就进入了可恢复状态。在这种状态下,系统的垃圾回收机制准备回收该对象所占用的内存,在回收该对象之前,系统会调用所有可恢复状态对象的 finalize() 方法进行资源清理如果系统在调用 finalize() 方法时重新让一个引用变量引用该对象,则这个对象会再次变为可达状态;否则该对象将进入不可达状态。
不可达状态:对象与所有引用变量的关联被切断,且系统已经调用了所有对象的finalize()方法后仍没有引用变量指向该对象,那这个对象将永久失去引用,变成不可达状态,系统才会真正回收该对象所占有的资源
强制垃圾回收:
程序只能控制一个对象何时不再被任何引用变量引用,决不能控制它何时被回收
强制回收只能通知系统进行垃圾回收,但系统是否进行垃圾回收仍然不确定,系统只会尽快进行垃圾回收
强制垃圾回收方式:
调用System类的gc()静态方法:System.gc
调用Runtime对象的gc()实例方法:Runtime.getRuntime().gc()
finalize方法:
在垃圾回收机制在回收某个对象之前,会调用finalize()方法进行清理资源操作
垃圾回收机制何时调用对象的 finalize()方法是完全透明的,只有当程序认为需要更多的额外内存时,垃圾回收机制才会进行垃圾回收
finalize()方法4个特点:
永远不要主动调用某个对象的finalize()方法,该方法应交给垃圾回收机制调用
finalize()方法何时被调用,是否被调用具有不确定性,不要把 finalize()方法当成一定会被执行的方法
当JVM执行可恢复对象的finalize()方法时,可能使该对象或系统中其他对象重新变成可达状态。
当JVM执行finalize()方法时出现异常时,垃圾回收机制不会报告异常,程序继续执行
对象的软、弱、和虚引用:
......
cs