《疯狂Java讲义(第4版)》-----第6章【面向对象(下)】(包装类、final、抽象类、接口)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ccnuacmhdu/article/details/82910485

包装类

自动拆箱和自动装箱

基本数据类型 包装类
byte Byte
short Short
char Character
int Integer
long Long
float Float
double Double
boolean Boolean

基本数据类型的变量与字符串之间的转换
以Integer类为例,官方文档里面提供了两个方法:

public static int parseInt(String s)
                    throws NumberFormatException
Parses the string argument as a signed decimal integer.
public static Integer valueOf(String s)
                       throws NumberFormatException
Returns an Integer object holding the value of the specified String. 

String类提供的方法

public static String valueOf(double d)
Returns the string representation of the double argument. 

示例代码:

int a = Integer.parseInt("123");
Integer b = Integer.valueOf("123");

String str1 = String.valueOf(1.23);
String str2 = 1.23 + "";//基本类型变量与空串连接转换成字符串
System.out.println(Integer.valueOf(1) == Integer.valueOf(1));//返回false,两个对象不一样

Integer i1 = 1;
Integer i2 = 2;
System.out.println(i1 == i2);//返回true,这两个对象引用指向的是同一个

Integer i3 = 1;
Integer i4 = 2;
System.out.println(i3 == i4);//返回false,这两个对象引用指向的不是是同一个

原因是,Integer类实现的时候,-128~127缓存起来了,用这个范围内的数进行自动装箱,指向的是同一个对象。
static final Integer[] cache[-(-128) +127 + 1];
static{
	for(int i = 0; i < cache.length; i++){
		cache[i] = new Integer(i  -128);
	}
}

所有包装类提供了compare方法:

System.out.println(Boolean.compare(true, false));//1
System.out.println(Boolean.compare(true, true));//0
System.out.println(Boolean.compare(false, true));//-1

打印对象和toString方法

Object类中有toString方法。

class Hello{

	public static void main(String[] args){
			
		Hello hello = new Hello();
		System.out.println(hello);//等价于System.out.println(hello.toString());
	}
}

输出的是类名@哈希码:
Hello@15db9742

一般重写这个类,打印出描述这个对象的信息:

class Hello{

	private String name;
	private int age;

	public Hello(String name, int age){
		this.name = name;
		this.age = age;
	}

	public String toString(){
		return "Hello[name="+name+", age="+age+"]";
	}
	public static void main(String[] args){
			
		Hello hello = new Hello("Tom", 12);
		System.out.println(hello);
	}
}

输出:
Hello[name=Tom, age=12]

==与equals

  • ==:如果两个变量是基本类型且是数值类型,值相等,就返回true。如果是引用变量,必须指向同一个对象才返回true。比较的两个对象必须有继承关系。
  • equals:是Object类提供的一个方法,判断标准就是==对对象的判断标准。自己可以覆盖Object的equals方法,自定义比较规则。String类已经重写了equals方法,只要两个字符串完全相同,返回true。
System.out.println(1==1.0);//返回true
class Person{

	private String idStr;
	private String name;
	
	public Person(){}
	public Person(String idStr, String name){
		this.idStr = idStr;
		this.name = name;
	}

	public void setIdStr(String idStr){
		this.idStr = idStr;
	}
	public String getIdStr(){
		return this.idStr;
	}

	public void setName(String  name){
		this.name = name;
	}
	public String getName(){
		return this.name;
	}
	
	//重写Object类的equals方法
	public boolean equals(Object obj){
		if(this == obj){
			return true;
		}
		//这里用了反射。注意这里判断的是前者是否是Person对象。不要用instanceof,因为比如
		//假设Student类继承了Person,那么也会判断成true
		if(obj != null && obj.getClass() == Person.class){
			Person person = (Person)obj;
			if(this.getIdStr().equals(person.getIdStr())){
				return true;
			}
		}
		return false;	
	}

	public static void main(String[] args){
		
		Person p1 = new Person("123", "Tom1");
		Person p2 = new Person("123", "Tom2");
		Person p3 = new Person("1234", "Tom3");
		System.out.println(p1.equals(p2));//true
		System.out.println(p1.equals(p3));//false
	}
}

单例类

class Singleton{
	//下面的静态方法会调用这个对象,所以这个对象必须是静态的
	private static Singleton singleton;//存储已经创建的单例
	private Singleton(){}//构造器设为私有,不允许其他类创建Singleton的实例
	
	//为外界提供调用接口,因为外界无法创建Singleton的实例,因此无法用实例调用这个方法,
	//那么只能通过类名调用这个方法,那么这个方法必须是类级别的,必须加上static	
	public static Singleton getInstance(){
		if(singleton == null){
			singleton = new Singleton();
		}
		return singleton; 
	}

}

class Test{

	public static void main(String[] args){
		Singleton s1 = Singleton.getInstance();
		Singleton s2 = Singleton.getInstance();
		System.out.println(s1 == s2);//true
	}
}

final修饰符

final可修饰类、变量、方法。

final修饰成员变量

**final修饰的成员变量一旦有初始值就不能重新赋值。**final修饰的成员变量必须由程序员指定初始值。因为如果初始化块、构造器、定义变量时都没有指定初始值,那么受final修饰的成员变量的值将始终是系统默认分配的0、false、’\u0000’或null,这些成员变量也失去了意义。所以规定final修饰的成员变量必须由程序员指定初始值。

final修饰的类变量、成员变量指定初始值的地方如下:

  • 类变量:静态初始化块或声明时指定。两种方式必须且只能选其一。
  • 成员变量:普通初始化块、声明时或构造器指定。三种方式必须且只能选其一。

如果final成员变量没有显式指定初始值,编译报错。

final修饰局部变量

final修饰的局部变量必须由程序员显式指定初始值,一旦指定,不得重新赋值。可以在定义的i时候指定初始值,也可以在后面的代码中指定初始值。

  • 如果调用没有指定初始值的final变量,会报错。如果final修饰的局部变量没有指定初始值,也没有使用,不会报错。
  • final形参显然不能被重新赋值,因为在调用方法的时候必定会传入相应的值,为final形参指定初始值。

final修饰基本类型变量和引用类型变量的区别

  • final修饰引用变量,这个引用变量的引用地址不能改变,但引用地址对应的对象或数组内部是可以改变的。
  • final修饰基本类型变量,不能对基本类型变量重新赋值,基本类型变量不能被改变。

可执行“宏替换”的final变量

对于final修饰的变量,如果在定义时指定了初始值,并在编译时值就被确定下来了,这个final变量就相当于一个直接量。

final int a = 3;//相当于“宏变量”,编译时已经确定了

//下面book在编译时确定不了,因为调用了方法。不是“宏变量”
final String book = "abc" + String.valueOf(23.2);


class Hello{

	public static void main(String[] args){

		String s1 = "疯狂java";
		String s2 = "疯狂"+"java";
		System.out.println(s1 == s2);//true
	
		String s3 = "疯狂";
		String s4 = "java";
		//s3,s4是两个变量,连接后不是直接量,字符串常量连接池不起作用
		String s5 = s3 + s4;
		System.out.println(s1 == s5);//false

		final String s6 = "疯狂";
		final String s7 = "java";
		//编译时s6,s7就确定了,s6 + s7相当于直接量,常量字符串常量池作用,s8和s1指向的地址相同
		String s8 = s6 + s7;
		System.out.println(s1 == s8);//true
	}
}

final修饰方法

final修饰的方法不能被重写,如果不希望子类重写父类的方法,就用final修饰。试图重写父类的final的方法,编译错误。

class A{
	private final void test(){}
}

class B extends A{
	public void test(){}
}

上面代码是没问题的,因为父类A的test方法是private,子类B根本继承不到这个方法,所以子类的test方法不是对父类方法的重写,而是子类自己的。

final修饰类

final修饰的类不可有子类。

不可变类

Java提供的8个包装类和java.lang.String类都是不可变类。当创建他们的实例后,其实例变量不可改变。

自定义一个不可变类,要遵循的规则:

  • 使用private和final修饰成员变量
  • 提供带参数的构造器,初始化成员变量
  • 提供getter方法,不提供setter方法
  • 如有必要,重写Object的equals方法和hashCode方法

具体例子见该书184页。

final修饰引用变量时,只能保证该引用变量的引用地址不可变,但实际对象可以改变,为了避免因实例对象改变造成不想看到的结果,可以采用一些措施,具体看该书184-185页。

缓存不可变类的实例

如果经常用到某类的一些重复的对象,可以考虑把这些对象缓存起来,避免反复创建类增加系统开销。比如Integer类就采用了缓存技术,可以缓存-128~127之间的数,通过调用Integer.valueOf()函数来使用缓存的值,如果缓存里有的话,就不会再次创建新对象。具体缓存的实现,见该书186页和187页。

抽象类

抽象类体现的是一种模板模式的设计,抽象类可以作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造。

抽象类的规则:

  • 抽象类必须由abstract修饰
  • 抽象类不能被实例化
  • 抽象类可以含有成员变量、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类(接口、枚举)。抽象类的构造器主要用于被子类调用。
  • 含有抽象方法的类(定义了抽象方法;继承了抽象父类,但没有完全实现父类包含的抽象方法;实现了接口,但没有完全实现接口包含的抽象方法三种情况)只能被定义为抽象类。

abstract、final、static

  • abstract修饰的类只能被继承,final修饰的类不能被继承。abstract修饰的方法只能被子类实现,而final修饰的方法不能被重写。final和abstract不能同时使用。
  • abstract不能修饰变量、构造器
  • abstract不能和static同时修饰一个方法。因为,abstract修饰的方法是抽象方法,static修饰的方法是类方法,要调用这个方法就是用抽象类调用抽象方法,由于抽象方法没有方法体,调用会出错。
  • private与abstract不能同修饰一个方法。不然这个抽象方法无法被子类继承,无法被实现,也就没了意义。

接口

[修饰符] interface 接口名 extends 父接口1, 父接口2...{
	//接口成员都是public访问权限,不写的话,系统默认加上。
	//成员变量都是public static final修饰,不写的话系统默认加上。定义时必须指定初始值
	//方法都是public abstract修饰的,省略的话系统会加上
	//Java8增加了默认方法(default修饰,default前可省略public,系统加上)、类方法(public static修饰)
	//Java9为接口增加了新的私有方法,private修饰或private static修饰
	接口的成员
}
  • 修饰符可以是public或默认不写(包权限)。一个接口可以有多个父接口,但接口只能继承接口,不能继承类。
  • 接口定义是一种规范。接口里面没有构造器、初始化块。接口的成员包括成员变量、方法、内部类(包括内部接口、内部枚举)。
  • 一个Java源文件最多有一个public接口,如果由public接口,文件名必须是接口名。

接口的主要作用

  • 定义变量,也可以进行强制类型转换。
  • 被类实现
  • 调用接口的”常量“
[修饰符] class 类名 extends 父类 implements 接口1,接口2...
{
	类体
}

接口类型的引用变量可以赋值给Object类的引用变量。因为接口类型的引用变量都是实现接口的类对象的引用,只要的对象,必然是Object类的子类对象。

猜你喜欢

转载自blog.csdn.net/ccnuacmhdu/article/details/82910485
今日推荐