第六章:面向对象下
包装类
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
char | Character |
float | Float |
double | Double |
boolean | Boolean |
上表可以看出,除了int和char之外,其他的基本数据类型的包装类都是将其首字母进行大写。在JDK1.5之前使用valueOf方法可将基本数据类型变量包装成包装类,反之可以使用xxxValue方法。这些方法已经过时,现在可以使用自动装箱和自动拆箱功能。
自动装箱和自动拆箱:自动装箱就是把一个基本数据类型赋给对于的包装类变量,或者给Object变量;自动拆箱则与之相反,允许把一个包装类对象直接赋值给对于的基本类型变量。
- 包装类提供的parseXxx(String s)静态方法(除char之外都提供了该方法)
- 利用包装类提供的valueOf(String s)静态方法
String类也提供了重写valueOf方法,用于将基本类型变量转换成字符串,示例如下:
public class Test{
public static void main(String [] args){
String intStr="123";
int a=Inter.ParseInt(intStr);
int b=Integer.valueOf(intStr);
System.out.println("a: "+a+",b: "+b);//a:123 b:123
String ftStr=String.valueOf(4.32f);
System.out.println(ftStr);//4.35
String bStr=String.valueOf(true);
System.out.println(bStr);true
}
}
包装类变量可以和数值类型的值直接进行比较:
Integer a=Integer.valueOf(6);
System.out.println(a>5.0);
两个包装类实例进行比较的情况比较复杂,包装类实例实际上是一个引用类型,只有两个包装类引用指向同一个对象才会返回true
System.out.println(Integer.valueOf(2)==Integer.valueOf(2));//false
在JDK1.5之后支持自动装箱,会出现一些特殊情况(上面使用Integer .valueOf进行包装,不会和出现)
Integer a=2;
Integer b=2;
System.out.println(a==b)//true
Integer c=128;
Integer d=128;
System.out.println(c==d)//false
通过查看Java.lang.Integer类源码,可以知道,系统把一个-128-127之间的整数自动装箱成Integer实例,并存入名为cache的数组中缓存起来,所以这里自动装箱成Integer时,实际是指向已经生成好的对应数组元素。
处理对象
Java中测试两个变量是否相等的方式:==和equals
如果两个变量时基本数据类型变量,且都是数值类型(不一定要求数据类型严格相等),则只要两个变量的值相等就返回true;对于两个引用引用类型变量 ,只有他们指向同一个对象时,==才会返回true;不过不能用于比较没有父子关系的两个对象。equals方法是Object类提供的方法,对于没有改写equals方法,其功能和(=/=)完全一样。String已经重写了equals方法,只要两个字符串包含字符列相同,则返回true。
类成员
理解类成员
类变量属于整个类,当系统第一次准备使用该类时,系统会变为该类分配内存空间,类变量开始生效,直到该类被卸载,该类变量所以=占有的内存才会被垃圾回收机制回收。
final修饰符
final修饰变量,表示该变量一旦获得了初始值就不可被改变 (不可被重新赋值)
final成员变量
final修饰的成员变量必须由程序员显示的指定初始值
类变量:必须在静态初始化中指定初始值或声明该类变量时指定初始值。
实例变量:必须在非静态初始化块,声明该实例变量或构造器中指定初始值。
final局部变量
系统不会对局部变量进行初始化,局部变量必须由程序员显示的初始化,因此final修饰局部变量时,即可以在定义时指定默认值妈耶可以不指定默认,如果没有指定默认值,在后面代码中进行赋值。
final修饰基本类型变量和引用变量的区别
final修饰基本类型变量时,不能对基本变量重新赋值,对于引用类型变量而言,它保存的仅是一个引用,只要保证这个 引用类型变量的地址不会改变,即一直引用同一个对象即可,而这个对象可以发生改变。
抽象类
抽象方法和抽象类
抽象方法和抽象类必须使用abstract修饰符类定义,有抽象方法的类必须定义成抽象类,抽象类中可以没有抽象方法。
抽象方法和抽象类的规则如下
- 抽象类和抽象方法必须abstract修饰,抽象方法不能有方法体。
- 抽象类不能被实例化
- 抽象类的构造器不能 用来实例化,主要用来被子类调用
- 含有抽象方法的类(包括定义了抽象方法,继承抽象类 但是没有完全实现抽象方法,或实现接口但是没有完全实现接口方法)只能被定为抽象类。
当使用abstract修饰类时表明只能被继承,修饰方法时表示只能被子类重写,而final修饰的类不能被继承,修饰的方法不能被重写,所以final和abstract不能同时使用
Java9改进的接口
接口的定义
定义接口不再使用class,而是使用interface关键字。由于接口定义的是一种规范,所以接口不能包括构造器和初始化块定义。接口定义可以包含成员变量(只能是静态变量),方法(抽象方法,类方法,默认方法或者私有方法),内部类。
接口里面的普通方法不能有方法实现(方法体),定义普通方法时,不管是否使用 public abstract修饰,系统自动为普通方法添加public abstract修饰符;但修饰类方法,默认方法(不能使用static,总是使用public修饰,也就是方法体的实例方法;默认方法的好处?解耦,不要求所有实现类重写default方法),私有方法都必须由方法实现(为接口中的类方法,私有方法提供支持)。默认方法没有使用static修饰,所以不能使用接口直接调用,要用接口类的实例进行调用 。
接口中的方法修饰符:
public default 默认方法
public static 类方法
private 私有方法
private static 私有静态方法
接口的继承
接口完全支持多继承
内部类
内部类提供了更好的封装,可以把内部类隐藏在外部类中,不允许同一包中其他类访问该类。内部类成员可以直接访问外部类私有数据,因为内部类被当做外部类成员。但外部类不能访问内部类的实现细节。匿名内部类用于创建那些 仅需要一次使用的类。内部类比外部类可以多使用上修饰符:private,protected,static。非静态内部类不能拥有静态成员。
非静态内部类
当在非静态内部类的方法访问某个变量时:
- 先在该内部类方法中找该变量
- 再到方法所在的内部类中找
- 再到外部类中找该变量
如果外部成员变量,内部类成员变量与内部类里方法的局部变量同名,则可以通过使用this,外部类类名.this作为限定 区分。
非静态内部类的成员可以访问外部类的实例成员,但反过来就不成立了。如果外部类需要访问非静态内部类实例成员,则必须显示的创建非静态内部类对象来调用访问其实例变量。
public claa Out{
private int outProp=10;
class In{
private int inPro=5;
public void accessOutProp(){
System.out.println("外部类outProp的值:"+outProp);
}
}
public void accessInProp(){
//System.out.println("内部类inProp的值:"+inProp); 报错,无法获得内部类的值
System.out.println("内部类inProp的值:" + new In().inProp);
}
public void accessInProp(){
//System.out.println("内部类inProp的值:"+inProp); 报错,无法获得内部类的值
System.out.println("内部类inProp的值:" + new In().inProp);
}
}
非静态内部类对象必须寄生在外部类的对象中,而外部类对象不一定有非静态内部类对象寄生其中。
根据静态成员不能访问非静态成员的规则 ,外部类的静态方法,静态代码不能访问非静态内部类,包括 不能使用费静态内部类定义变量,创建实例等。
public class StaticTest{
private class In{} //定义一个非静态内部类
public static void main(String[] args)
{
//下面这行代码将引发编译异常,因为静态成员main()方法无法访问非静态成员(In类)
new In;
}
}
内部类的实例寄生在外部类的实例中,要先创建外部类(在外部类中创建内部类实例)?应该是这样。
java不允许在非静态内部类例定义静态成员
public class Test2 {
private class innerClass{
//下面三个静态声明都将引发如下编译错误:非静态内部类不能有静态声明
static{
System.out.println("============");
}
private static int inProp;
private static void test(){};
}
}
静态内部类
使用static修饰的内部类成为静态内部类,这个内部类属于 外部类本身,而不属于外部类的某个对象。静态内部类不能 访问外部类的实例成员,只能访问外部类的类成员。即使是静态内部类的实例方法也不能访问外部类的实例对象成员,只能访问外部类的类成员。
public class Test2 {
private int p1 = 5;
private static int p2 = 10;
static class StaticInnerClass{
private static int age;
public void accessOutProp(){
//System.out.println(p1);静态内部类无法访问外部类的实例变量
System.out.println(p2);
}
}
}
外部类不能直接访问静态内部类的静态成员 ,但可以使用静态内部类 的类名作为调用者来访问静态内部类的类成员,也可以使用静态内部类的对象作为调用者来访问静态内部类的实例成员。
public class Test2 {
static class StaticInnerClass{
private static int p1 = 2;
private int p2 = 4;
}
public void accessInnerprop(){
//System.out.println(p1);
System.out.println(StaticInnerClass.p1);//通过类名访问静态内部类的类成员
System.out.println(new StaticInnerClass().p2);//通过实例访问静态内部类的实例成员
}
}
使用内部类
在外部类以外的地方使用内部类时,内部完整的类名应该是OutClass.InnerClass,如果外部类有包名应增加包名前缀
OutClass.InnerClass varName;
由于非静态内部类的对象必须寄存在外部类的对象里,因此创建内部类实例时,必须先创建外部类对象,语法如下:
outerInstance.new InnerConstructor()
匿名内部类
匿名内部类适合创建那些只需要使用一次的类,创建匿名内部类时会立即创建一个该类的实例,这个类 定义会 立即消失,匿名内部类不能重复使用,定义匿名内部类格式如下:
new 实现接口()|父类构造器(实参列表)
{
匿名内部类类体部分
}
由上面定义可以看出:匿名内部类必须继承父类,或实现一个借口,但最多只能继承一个父类或实现一个接口,关于匿名内部类如下两条 规则:
- 匿名内部类不能是抽象类,因为在创建匿名内部类时会立即创建匿名内部类对象
- 匿名 内部类不能定义构造器,因此匿名内部类没有类名,所以无法定义构造器
最常见的创建匿名内部类的方式是需要闯进某个接口类型的对象,如下:
interface Product{
double getPrice();
String getName();
}
public class AonoyMouseTest{
public void test(Product p){
System.out.println(p.getName()+"====================="+p.getPrice());
}
public static void main(String[] args){
AonoyMouseTest aonoyouseTest =new AonoyMouseTset();
annoymouseTest.test(new Product()){
public double getPrice(){
return 567.8;
}
public String getName(){
return "电脑";
}
};
}
}
当通过实现接口来创建匿名内部类时,匿名内部类不能显式的定义构造器,因此匿名内部类只有一个隐式的无参构造器,故new接口名后面不能传入参数值。
当如果通过继承父类来创建匿名类时,匿名内部类将拥有和父类相似的构造器,此处的相似指的是相同的形参数类表。
从Java8开始,如果局部变量被匿名内部类访问,俺么该局部变量相当于自动使用final修饰,如下:
interface A {
void test();
}
public class ATest{
public static void main(String[] args) {
int age = 8;
var a = new A()
{
public void test(){
System.out.println(age);//age被匿名内部类访问,自动加上final修饰
}
};
a.test();
}
}
Java11增强的Lambda表达式
Lambda表达式的主要作用是替代匿名内部类的繁琐语法,由三部分组成:
- 形参列表,形参列表允许省略形参类型
- 箭头->,必须通过英文划线和大于符号组成
- 代码块。如果代码只包含一条语句,Lambda表达式允许省略代码块的花括号,L阿米巴表达式只有一条return 语句,甚至可以省略return关键字
对象与垃圾回收
- 可达状态:当一个对象创建后,若有一个以上的引用变量引用它,则这个对象在程序中处于可达状态,程序通过引用变量来调用该变量的Field和方法。
- 可恢复状态:如果程序中某个对象不再有任何引用变量引用它,它就进入了可恢复状态。在这种状态,系统的垃圾回收机制准备回收该对象所占的内存,在回收该对象之前,系统对调用所有可恢复状态对象的finalize()方法进行资源清理,如果系统在调用finalize()方法时重新让一个引用变量引用该对象,则这个对象会在此变成可达状态;否则该对象进入不可达状态。
- 不可达状态:当对象与所有引用变量的关联都被切断,且系统已经调用所有对象的finalize()方法依然没有使该对象变成可达状态,
- 那么这个对象将永久性失去引用,最后变成不可达状态,当一个对象处于不可达状态,系统才会真正回收该对象所占的内存
强制垃圾回收
当一个对象失去引用后,系统何时调用它的finalize()方法对它进行资源清理,何时它可以变成不可达状态,系统何时回收它占有的内存,对程序完全透明。**程序只能控制一个对象不再被任何引用变量引用,不能控制它何时被回收。**大部分时候,程序强制垃圾回收总是有一些效果。
强制系统垃圾回收有如下两个方法:
- 调用System类gc()静态方法:System.gc()
- 调用Runtime对象的gc()实例方法:Runtime.getRuntime().gc()
public class GcTest
{
public static void main(String[] args)
{
for (int i = 0 ; i < 4; i++)
{
new GcTest();//创建四个匿名对象,创建完变成可恢复状态
}
}
public void finalize()
{
System.out.println("系统正在清理GcTest对象的资源...");
}
}
public class GcTest
{
public static void main(String[] args)
{
for (int i = 0 ; i < 4; i++)
{
new GcTest();
// 下面两行代码的作用完全相同,强制系统进行垃圾回收
// System.gc();
Runtime.getRuntime().gc();
}
}
public void finalize()
{
System.out.println("系统正在清理GcTest对象的资源...");
}
}
finalize方法
finalize方法是Object类提供的实例方法,任何类都可以重写该方法。