Java core (上篇)

 

Java中的8种基本数据类型:

整型:byte (1字节),short(2字节),int(4字节),long(8字节)

浮点型:float(4字节),double(8字节)

字符型:char

布尔型:boolean

 

注意:

浮点数值不适用于禁止出现舍入误差的金融计算中!

如果需要在数值计算中不含有任何舍入误差,就应该使用BigDecimal类

 

常量

关键字final表示这个变量只能被赋值一次。一旦被赋值之后,就不能够再更改了!

如果希望某个常量可以在一个类中的多个方法中使用,则声明为静态的。

(public) static final int PI = 3.1415926;

 

位运算

处理整型数值时,可以直接对组成整型数值的各个位进行操作。这意味着可以使用屏蔽
技术获得整数中的各个位。

& (与)

|  (或)

^ (异或)

~ (非)

 

对于int类型,移位运算符右侧的参数需要进行模32的运算(int类型总共就32位)

1 << 35,1 << 3,计算结果都等于 8

对于long类型,需对右侧操作数模64 (long类型64位)

 

Math函数

求1个数的次幂,如x的a次幂:double y = Math.pow(x,a); [pow接收的参数为double类型]

比较大小,Math.max(), Math.min()

 

枚举类型

变量的取值只在一个有限的集合内。

枚举可以定义在类中(非public修饰枚举类)

也可以独立进行定义(public修饰时必须单独定义)。

 

package javacore;

public enum Size {
	SMALL("S"),MEDIUM("M"),LARGE("L"),EXTRA_LARGE("XL");
	
	private String abbreviation;
	
	private Size(String abbreviation) {
		this.abbreviation = abbreviation;
	}
	
	public String getAbbreviation() {
		return abbreviation;
	}
}

 

 

 

字符串

字符串是不可变类型

每个用双引号括起来的字符串都是String类的一个实例。

每次连接字符串,都会构建一个新的String对象,既耗时,又浪费空间。

当字符串拼接量大时,请使用StringBuilder,而不要直接用+号进行拼接

 

字符串不可变的优点:编译器会共享相同的字符串,在内存中只有一份。

 

如果所有字符串都在一个单线程中编辑(通常都是这样),则应该用StringBuilder替代StringBuffer。

 

比较字符串是否相等:

请使用equals()进行比较,不要使用==比较

原因:

 

String s1 = "abc";
String s2 = new String("abc");
System.out.println(s1==s2);//false
System.out.println(s1.equals(s2));//true

 

== 是直接比较内存地址

equals() 先比较内存地址,如果不同,会逐个比较字符串中的字符

 

 public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String) anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                            return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

 

 

启动路径

启动路径就是命令解释器的当前路径

String dir = System.getProperty("user.dir");
System.out.println(dir);

 输出:E:\project\NIO

代码块

代码块中的代码将在构造函数被调用时执行,执行顺序:

1.本来构造方法中,先通过super()调用父类构造方法

2.执行代码块中的语句

3.执行构造函数中未被执行的语句

switch语句

只能处理整型和枚举类型,另外需要小心case穿透!

BigInteger与BigDecimal

这两个类可以处理包含任意长度数字序列的数值。

BigInteger类实现了任意精度的整数运算;

BigDecimal实现了任意精度的浮点数运算,算钱的时候一定要用它。

 

 

类是实例对象的封装体,包含对象的属性和行为

 

类中的数据/字段field,称为实例域

每个特定的类实例都有一组特定的实例域值,这些值的集合就构成了当前对象的状态

 

类中的方法method,称为行为/功能

通过方法可以改变对象的状态,通过这些变化的状态再次去影响其它方法的执行

方法需要结合当前对象的状态去执行某种行为,称为有状态的方法

如果方法的执行不依赖对象的状态,那么该方法是无状态的,直接定义为静态即可!

 

类不应该直接将实例域开放给外界,而是提供访问的方法,让本类对象在外部进行调用。

 

设计一个类的时候,应该考虑:

对象是否具有状态(state)  : 如有,那应该存在哪些状态(属性)

对象的行为(behavior):需要具备哪些行为?这些行为是否与对象的状态有关?是否需要考虑多线程访问的安全性(同步)?

 

同一个类的不同实例都具备相同的行为,但是由于各个对象之间的状态互不相同,导致不同对象的行为结果也不相同。因此,对象的状态影响着对象的行为!!!

 

良好的类设计原则:

1. 一定将数据私有化

2. 一定对数据进行显示初始化,不要使用其默认值

3. 不要在类中使用过多基本数据类型,如String province,String city可以抽取到Address类

4. 将职责过多的类进行分解

5. 类名和方法名要见名知意

6. 正确的使用静态属性与静态方法

 

类与类之间建立关系 

依赖 use-a :

---------------->

体现为局部变量、方法的参数或者对静态方法的调用,如sellTick()依赖IDCard类

关联 has-a:

——————>

通过使用成员变量来实现关联,如人与身份证是关联关系。

聚合 has-a:

空心菱形——————>

是关联关系的一种,是强的关联关系,如汽车与轮胎是聚合关系。

 组合 has-a:

实心菱形——————>

代表整体的对象负责代表部分的对象的生命周期,如人与心脏就是组合关系。

继承 is-a:

———————空心箭头

侧重于对父类功能的复用

实现 is-a:

----------------------空心箭头

侧重于扩展子类的功能(面向接口编程,接着利用多态特性,动态绑定子类。。。)

 

对象 

对象就是所属类的一个实例。

OOP编程,其实就是创建各种对象,然后通过这些对象的行为去协作完成系列功能

 

对象的初始化过程

1.对所有数据域进行初始化(0,false,null);

2.按照类中的声明次序,依次执行所有域的初始化语句和代码块;

3.如果构造器第一行调用了其它构造器,则执行其它构造器的主体;

4.最后,才执行本构造器的主体。

 

一个对象变量并没有实际包含一个对象,而仅仅引用该对象在堆内存中的地址。

Java中的参数传递方式只有一种:值传递!

1.参数是普通数据类型,传递的就是值本身;

2.参数是引用类型,传递的就是对象在堆中的地址。

public class Sample {
	
	{
		this.name = "fds";
	}
	
	private String name;
	
	public Sample(String name) {
		super();
		this.name = name;
	}
	
	private static void swap(Sample x, Sample y) {
		//这里对两个局部变量的值进行了交换,对地址所引用到的堆内存进行了改变,改变是有效的
		Sample temp = x;
		x = y;
		y = temp;
		x.name = "xxx";
		y.name = "yyy";
	}
	
	public static void main(String[] args) throws CloneNotSupportedException {
		Sample x = new Sample("x");
		Sample y = new Sample("y");
		//仅仅是将x,y的地址值作为了参数进行传递
		swap(x,y);
		//x变量仍然指向原来的堆内存
		System.out.println(x.name); //输出:yyy
		//y变量仍然指向原来的堆内存
		System.out.println(y.name);//输出:xxx
	}
}

 

 

实例域(对象属性)的初始化方式

1. 定义的时候进行赋值

2. 构造函数中进行赋值

3. 代码块中进行赋值(一旦构造函数被调用,代码块就会被执行,且优先于构造函数中的赋值操作)

静态域(类变量)的初始化

如果静态域的初始化比较复杂,可以通过静态代码块解决!

设计实例域方法的相关原则:

1.私有化

2.提供共有的域访问方法,该方法可以隐藏一些内部细节,对外直接返回结果即可

3.提供共有的域更改方法,方法内部控制是否允许修改,以及如何修改,提高灵活性

 

注意:

不要编写返回引用可变对象的访问器方法。如返回Date引用,在外部可直接修改其值!

如,下面的做法非常危险! 

Date retireDate = new Date(2010-1900,1,1);

public Date getRetireDate() {
	return retireDate;//Do not do this!
	//return (Date) retireDate.clone();
}

public void someMethod() {
	Date newDate = getRetireDate();
	System.out.println(newDate);
	newDate.setTime(System.currentTimeMillis());
	System.out.println(getRetireDate());
}

 clone(): 返回一个可变数据域的拷贝。

 

对象行为的设计原则:

公有行为/功能需要开放为public权限;

对行为提供内部支持的方法设置为private权限,不需要暴露给外界

 

静态static

static修饰的实例域或者方法,属于类级别的,与对象无关。

对于static的应用,一般有一下几种:

1. 静态字段,一般用来设置公用常量,并且用final声明其不可变性。 

典型用法:public static final int PI = 3.1415926

2. 静态方法。什么时候适合将方法声明为静态的呢? 

当一个方法不需要访问对象的状态,其所需参数都是通过显示参数提供的,如Math.pow()

当一个方法只需要访问类的静态域

 

静态方法的灵活应用:演变为Facory方法 

通过静态方法返回类的实例,为什么这样做?直接通过构造函数new对象不行吗?

1. 可以给方法起一个更直观的名字,让调用者一看便知道将返回一个什么对象

2.通过方法返回对象,方法内部可以做各种控制,甚至返回一个其它对象,或抛出异常

 

什么时候适合用静态导入?

1. 调用Math函数的方法时,静态导入Math类,然后直接使用max(),min等方法;

2. 如果静态域所在类名很长,可以考虑将其静态导入;

其它情况则不推荐,因为静态导入会降低程序的可读性。

 

 继承

如果类之间存在 is-kind -a 关系,则可用继承来设计。

继承设计原则:

在设计类的时候,将通用的方法定义在父类中,具有特殊用途的方法则定义在子类中。

 

使用继承可以实现什么?

继承最直观的作用就是复用父类已经存在的非私有的域和方法。

子类在此基础上再扩充新的域和方法,提供更多的功能。

 

一种为纯复用式,子类继承父类的域和方法,如Manager extends Employee;

 

另一种则更灵活,父类既提供可复用的域和方法给子类,又以多态的方式参与到程序中。

 

比如,模板方法设计模式中:

父类定义1个统一操作的方法,该方法中会调用到另外几个抽象方法。

子类按各自需求复写这些抽象方法。

在运行时调用模板方法,父类引用将动态绑定到子类对象上,最终执行子类复写的方法。

 

程序中,任何出现父类的地方,都可以用子类进行替换!

 

 因此,如果考虑将类设计为继承关系,请明确这样做的目的是什么? 复用 / 多态?

 实际开发中,父类是根据一系列子类向上抽取得到的!

至于抽取哪些域和方法放到父类中,需要根据实际所处环境而定。

 

 子类中如何操作父类中的属性

子类如果要操作父类中的私有属性,有两种途径:

 

1. 在子类构造方法中,通过super()调用父类构造方法。如,super(name)。

然后子类中通过getName()便可获取到name值;

实际例子:

Thread子类设置线程名称时,就是通过super(name)去调用Thread类的构造方法,设置Thread类中的私有name属性(private char[] name ),然后子类再使用getName()方法获取到所设置的线程名称。

 

super.getName()  明确指定访问的getName()是父类的,而不是子类的。 

当在子类从父类继承下来的getName()方法中,需要获取父类私有属性name时,只能通过父类对外开放的getName()。如果在子类的getName()中直接调用getName(),将发生无限循环,此时,必须通过super.getName()明确的告诉编译器访问的是父类的getName()。

 

2.将父类中私有属性权限提高到protected权限,子类便可直接操作这些属性。

 

 子类覆盖父类中的方法,可见性必须大于父类中对于方法的可见性

final的用法

将final声明在类上,可以阻止该类被继承,类中的方法自然也限定为final的,而不包括域;

可以单独限制类中的方法为final,但类中的其它方法仍可以被子类覆盖;

可以单独限制final域,构造对象之后就不可以改变该域的值。

 

父类与子类之间的转换

向上转型

子类可以直接转换为父类(多态:父类引用具体子类对象)

向下转型

将超类转换为子类之前,应该使用instanceof进行检查,防止ClassCastException

 

抽象类

如果基类只作为派生其它类而存在,而不需要作为实例类,则可以将其设计为抽象类

 

包含1个或多个抽象方法的类,必须声明为抽象类

除抽象方法外,抽象类可以包含具体数据域和方法

抽象类中可以没有抽象方法,可以阻止该类被实例化

抽象类中,可以有一部分抽象方法,一部分具体方法。如模板设计模式

抽象类的变量,可以引用非抽象子类的实例。如模板设计模式

 

访问权限控制

private       仅在本类中可见

package    仅在本包可见

protected   在本包和子类中可见

public        对所有类可见

 

权限设计的建议:

1. 属性都设为私有的。当有继承关系存在:

为了方便子类直接使用父类中的域,可在父类中将这些域设置为protected权限;

2. 如果方法只在本类使用,则设置为private权限;

3. 如果需要一定程度的限制某个方法的调用,则设置为protected权限;

4. 如果方法是公用的,则设置为public权限;

 

 

Object类中的方法 

 

equals(Object otherObj)

Object类中的equals()比较的是两个对象的内存地址是否相同;

比较对象是否相同,参与比较的双方应该都是同一个类型才有意义。

 

如何定义2个对象是否相同,需要从对象"相等"的语义出发:

如,只要属性都相同,就是同一个对象;或者只要一部分属性相同,就认为同一个对象。

所以,需要根据具体情况去决定equals()的比较逻辑。

 

在比较对象是否相同时,首先需要判断对象是否同属于一类的范畴。

 

如果子类拥有自己的相等概念,则对称性需求将强制采用getClass()进行检测;

如果由超类决定相等概念,则可以使用instanceof进行检测,这样可以在不同子类之间进行相等的比较;

 

如果子类都使用同一个的语义比较对象是否相等,则使用instanceof;

if(!(otherObject instanceof ClassName)) return false; 

 

如果equals()的语义在不同子类之间有所改变,就需要使用getClass(),防止失去对称性;

if(this.getClass()!==otherObject.getClass())  return false;

 

标准的equals()书写范例:

Parent.class

 

@Override
public boolean equals(Object obj) {
	if (this == obj)
		return true;
	if (obj == null)
		return false;
	//if(!(obj instanceof Parent))
	if (getClass() != obj.getClass())
		return false;
	Parent other = (Parent) obj;
	if (age != other.age)
		return false;
	if (name == null) {
		if (other.name != null)
			return false;
	} else if (!name.equals(other.name))
		return false;
	return true;
}

  

Child.class

 

@Override
public boolean equals(Object obj) {
	//先调用父类的equals进行比较
	if(!(super.equals(obj)))
		return false;
	Child other = (Child) obj;
	if (bounds != other.bounds)
		return false;
	return true;
}

 

hashCode()

计算对象的散列码。

当使用哈希表数据结构存放数据时,就需要调用此方法计算对象的哈希码值。

 

当哈希码值相同时,需要调用对象的equals()比较对象是否相同,如果相同,则丢弃。

只有同时将equals和hashCode进行复写,才能保证使用基于哈希表结构的集合操作不出错!

 

如,HashSet基于hashCode()和equals()可以实现集合中元素的唯一性;

HashMap同样基于hashCode()和equals(),实现Map集合中的Key唯一; 

 

toString()

返回对象值的字符串表现形式

建议为所有自定义的类增加toString()

复写toString()的技巧:

父类:使用getClass.getName(),而不是具体的父类名

子类:调用super.toString()+子类特有的描述

Parent.class

 

@Override
public String toString() {
	return getClass().getName()+"[name=" + name + ", age=" + age + "]";
}

 

Child.class

 

@Override
public String toString() {
	return super.toString()+",[bounds=" + bounds + "]";
}

 

getClass()

返回包含对象信息的类Class

Java提供的类运行时的描述,内容被封装在Class类中

 

基于字符串创建对象。默认调用无参构造方法,所以要确保该类有无参构造方法存在。

Class clazz = Class.forName("xxxx");

clazz.newInstance();

 

如果需要调用带参数的构造方法,则使用:

clazz.getConstructor(Class...paramTypes).newInstance(Object...params);

 

clone()

创建一个对象的副本,系统将为新复制的对象在堆内存中分配新的存储空间。

克隆对象的时候要注意浅复制与深复制的不同。

被复制的对象,必须实现Cloneable接口才能被复制。

 

ArrayList

可变数组,底层仍通过数组进行数据的存储,只是将功能封装得更便于操作。

当容量不够时,ArrayList会自动创建更大容量的数组,并将原数组中的元素复制到新数组中。

对于增删操作频繁的情况,会导致频繁移动数组中的其它元素,这种情况下应该使用基于链表结构的LinkedList。

 

ArrayList<T>通过泛型限定,使集合操作更加安全,避免了类型转换导致的问题

 

ensureCapacity(minCapacity) 

需要保证ArrayList至少容纳多少元素

trimToSize()

将数组列表的存储容量削减到当前尺寸

ArrayList<Integer>与int[ ]之间效率的比较

 ArrayList<Integer>效率远远低于int[ ]的效率,因为涉及到基本数据类型值的装箱与解包。

当数组元素少的时候,方便性比执行效率更重要,使用ArrayList<Integer>;

当数组元素多的时候,可能就需要考虑使用底层的int[ ]了;

 

装箱:list.add(3) --->  list.add(new Integer(3));

解包:list.get(i).intValue()

装箱与解包由编译器自动完成,程序员只需要知道有这样一个过程,以及其对效率的影响即可。

 

NumberFormat的使用

 

NumberFormat nf1 = NumberFormat.getPercentInstance();
nf1.setMaximumFractionDigits(2);
String percent = nf1.format(0.23456);
System.out.println(percent);//23.46%

NumberFormat nf2 = NumberFormat.getCurrencyInstance(Locale.CHINA);
nf2.setMinimumFractionDigits(4);
nf2.setMinimumIntegerDigits(4);
String currency = nf2.format(122.34567);
System.out.println(currency);//¥0,122.3457

 

可变参数

//参数采用数组接收
public void print1(String param, Object[] objects) {
}

//Java5.0新增特性:可变参数
public void print2(String param, Object...objects) {	
}

 

 

反射 

在运行时分析类

在运行时查看对象

在运行时动态调用方法

 

Class类:在程序运行期,Java为每个对象都维护一个运行时的标识, 保存对象的状态

Class.forName(Stirng className) 

根据字符串生成对应类的Class对象

newInstance() 

通过默认构造方法创建对象

newInstance(Object[] args)

通过带参数的构造方法创建对象

printStackTrace() 

将Throwable对象和栈的轨迹输出到标准错误流

获取Class的三种方式:

1.通过对象获取:

Person p = new Person(); Class clazz = p.getClass();

2.通过Class.forName(className)获取:

Class clazz = Class.forName("com.gc.Client");

3.直接通过类获取:

Class clazz = Client.class;

  

通过反射分析类,灵活访问类中的域和调用类中的方法 

 

三个类:Field、Method和Constructor,分别用于描述类的域、方法和构造器。

 

Field类有一个getType方法,用来返回描述域所属类型的Class对象

 

Method和Constructor类有能够报告参数类型的方法:getParameterTypes()

 

Method类还有一个可以报告返回类型的方法:getReturnType()

 

这三个类还有一个叫做getModifiers的方法,它将返回一个整型数值,用不同的位开关描述public和static这样的修饰符使用状况。

 

Modifier类中的isPublic、isPrivate或isFinal判断方法或构造器是否是public、private或final。

 

Class类中的getFields、getMethods和getConstructors方法将分别返回类提供的public域、方法和构造器数组,其中包括超类的公有成员。

 

Class类的getDeclareFields、getDeclareMethods和getDeclaredConstructors方法将分别返回类中声明的全部域、方法和构造器,其中包括私有和受保护成员,但不包括超类的成员。

 

setAccessible(boolean flag)

flag为true表明屏蔽Java语言的访问检查,使得对象的私有属性也可以被查询和设置。

注意:一个Class对象实际表示的是一种类型,而此类型不一定是类。

如int不是类,但int.class是一个Class类对象。

基本数据类型的数组类型int[]可以被转换成Object,但不能转换成对象数组。

Object arr = new int[100]; //ok

Object[] arr = new int[100]; //error

getComponentType() 返回数组中元素的类型,如果操作对象不是数组,则返回null

Object objArr = new Child[10];
Class elementType = objArr.getClass().getComponentType();
System.out.println(elementType);//class javacore.Child

 

利用反射编写泛型数组拷贝

 

public static void main(String[] args) {
	int[] arr = {1,2,3,4,5};
	
	Object newArrObj = autoGrow(arr);
	int[] newArr = (int[])newArrObj;
	System.out.println(Arrays.toString(newArr));//[1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0]
}

public static Object autoGrow(Object arr) {
	Class<?> clazz = arr.getClass();
	if(!clazz.isArray()) {
		return null;
	}
	Class<?> componentType = clazz.getComponentType();
	int length = Array.getLength(arr);
	int newLength = length*10/11+10;//扩容
	Object newArray = Array.newInstance(componentType, newLength);
	System.arraycopy(arr, 0, newArray, 0, length);
	return newArray;
}

 

回调方法(通过反射完成)

Java中没有方法指针的概念,但是可以通过反射进行方法的动态调用。

public Object invoke(Object implicitParameter,Object[] explicitParamenters)

 

1.循环Method[],找到需要被调用的方法,执行invoke()

 

public static Object methodInvoke(Object obj, Object...params) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
	Method[] methods = obj.getClass().getDeclaredMethods();
	for(Method m : methods) {
		if(m.getName().startsWith("prefix"))
			return m.invoke(obj, params);
	}
	return null;
}

 

2.直接根据方法名进行获取,需额外指定方法的参数类型(如果有),以便匹配到正确的方法签名

 

public static Object methodInvoke(Object obj) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
	Method m = obj.getClass().getMethod("methodName");//如果方法被重载了,需要额外指定方法参数类型
	return m.invoke(obj);//如果有参数,需要传入参数值
}

 

使用反射获得方法指针的代码要比仅仅直接调用方法明显慢一些

建议仅在必要的时候才使用Method对象进行方法调用

可替代解决方案:接口、内部类

反射实现回调方法的另一种更好替代方案:接口

继承的设计技巧:

1.共有域和方法放在超类

2.不要使用protected权限修饰域

3.如果是is-a关系,则继承

4.除非所有继承得到的方法对子类都有意义,否则不要使用继承

5.覆盖方法时,不要改变原本的预期行为

6.充分利用多态性,而非直接面向子类编程

7.不要过多使用反射,虽然程序更通用,但出错几率也随之增加

8.尽量少用继承,多用组合

 

接口

如果类遵从某个特定接口,那么就履行这项服务。

使用接口的最终目的:为运行时动态绑定提供支持(多态性)

 

面向接口设计,是一种从全局出发的程序设计思想:

接口中定义一个/组抽象行为,外部提供一个面向接口的具体编程逻辑。

A)编译阶段:面向接口编程,编译器检查接口中确实声明了这些方法,因此,编译不会报错;

B)运行阶段:由于每个实现了此接口的子类一定存在那些通过接口进行调用的方法,因此,动态绑定到具体子类之后调用这些方法也是一定存在的。

 

接口中只允许出现2种成员:

1. 常量: 接口中定义的域默认就是public static final的;

2. 抽象方法: 接口中定义的方法默认就是public abstract的;

因此,在定义接口时,没必要书写这些多余的关键字,因为它们都是默认并且强制的!

 

如Comparable接口,描述了对象之间相互比较的一种能力。

具体比较逻辑由子类来实现,外部则封装对compareTo()进行调用的逻辑即可。

 

克隆

拷贝与克隆的区别

拷贝:当拷贝一个变量时,改变一个变量所引用的对象将同步对另一个对象产品影响

克隆:创建一个新对象,且新对象的状态与原始对象完全相同。当对克隆对象进行改变时,不会对原始对象造成影响。

 

要想让一个类的实例对象具备克隆能力,必须让该类实现Cloneable接口,该接口是一个标记接口。

 

clone()是从Object类继承下来的。

默认提供的是"浅拷贝",并不会克隆对象的内部对象。

 

克隆-深复制:

被克隆的对象内部如果包含其它引用,则需要进一步克隆,否则对克隆对象的子对象改变会同步到原来对象的子对象上。

 

不推荐使用clone(),所以了解即可!

 

 

接口回调 callback

 

某个特定事件发生时应该采取的动作,监听器Listener应该就是这样做的。

 

1.首先将子类对象以接口的类型注册到某个地方;

2.当某个事件发生时,马上调用一个notice方法,在方法内部拿到已注册对象,让这些对象分别去执行自己实现的那部分具体行为;

 

方法的回调仍然通过所属对象进行调用,只是调用时机由某个事件何时发生来决定!

 

 

 

 

内部类 

内部类技术主要用于设计具有相互协作关系的类集合。

 

内部类的对象默认持有一个外部类的引用,通过该隐式的引用让内部类可以直接访问外部类的所有状态(域和方法)。

 

1.简单内部类,可以直接访问外部类的实例域

(内部类需要的数据是通过外部类构造函数传入的)

package javacore;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Date;

import javax.swing.JOptionPane;
import javax.swing.Timer;


public class InnerClassTest {
	public static void main(String[] args) {
		TalkingClock clock =  new TalkingClock(1000, true);
		clock.start();
		JOptionPane.showMessageDialog(null, "Quit program?");
		System.exit(0);
	}
}

class TalkingClock {
	
	private int interval;
	private boolean beep;
	
	public TalkingClock(int interval, boolean beep) {
		this.interval = interval;
		this.beep = beep;
	}
	
	public void start() {
		ActionListener listener = new TimePrinter();
		Timer t = new Timer(interval, listener);
		t.start();
	}
	
	/**
	 * TimePrinter实现了ActionListener接口
	 * 由于需要使用到beep变量,将其设置为内部类非常合适
	 */
	public class TimePrinter implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent e) {
			Date now = new Date();
			System.out.println("Now: " + now);
			//直接访问外部类的成员
			if(beep)
				java.awt.Toolkit.getDefaultToolkit().beep();
		}
	}
}

 

2.内部类转化为外部类

内部类正式为了方便访问外部类的实例域,才将其作为内部类进行定义。

当然,也可以将内部类转化为外部类,这种情况下访问“外部类”的实例域比较麻烦。

 

3.局部内部类

(内部类访问的数据是方法中的局部变量)

 

局部类不能用public或private访问说明符进行声明。它的作用域被限定在声明这个局部类
的块中。

 

这些被内部类使用到的局部变量必须限制为final的。因为局部变量会随着方法结束一起出栈,为了让内部类继续使用这些局部变量,需要通过final进行声明(实际会将局部变量拷贝出一份数据进行存储,供内部类使用)

 

package javacore;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Date;

import javax.swing.JOptionPane;
import javax.swing.Timer;


public class InnerClassTest {
	public static void main(String[] args) {
		TalkingClock clock =  new TalkingClock();
		clock.start(1000, true);
		JOptionPane.showMessageDialog(null, "Quit program?");
		System.exit(0);
	}
}

class TalkingClock {
	
	public TalkingClock() {}
	
	public void start(int interval, final boolean beep) {
		/**
		 * 局部内部类 
		 */
		class TimePrinter implements ActionListener {
			@Override
			public void actionPerformed(ActionEvent e) {
				Date now = new Date();
				System.out.println("Now: " + now);
				//被访问的局部变量必须声明为final的
				if(beep)
					java.awt.Toolkit.getDefaultToolkit().beep();
			}
		}
		ActionListener listener = new TimePrinter();
		Timer t = new Timer(interval, listener);
		t.start();
	}
	
}

 

扩展:

final关键字可以应用于局部变量、实例变量和静态变量。

在定义final变量的时候,不必进行初始化。

在创建这个变量之后,只能够为之赋值一次。此后,再也不能修改它的值了,这就是final。

 

在内部类改变final变量的值:使用一个长度为1的数组

 

public static void main(String[] args) {
	final int[] count = new int[1];
	Date[] dates = new Date[100];
	for(int i=0;i<dates.length;i++) {
		dates[i] = new Date() {
			@Override
			public int compareTo(Date anotherDate) {
				//局部final变量也可以修改了
				count[0]++;
				return super.compareTo(anotherDate);
			}
		};
	}
	Arrays.sort(dates);
	System.out.println("共比较次数:"+count[0]);
}

 

 

4.匿名内部类

如果只使用1次,可以考虑匿名内部类的写法。

如开启新线程传入Runnable实例:

new Runnable() { public void run() {

}}

 

5.静态内部类嵌套在辅助类中

当内部类不需要持有外部类的引用时,就可以定义为静态的,以便取消产生的outer引用。

只有内部类才可以修饰为静态的,普通类不可以被static修饰。

 

代理 Proxy

使用JDK动态代理,需要注意以下几点:

1.被代理类必须实现某个接口。没有实现接口的类无法使用JDK的动态代理!

原因:运行阶段生成的代理类会实现这些接口,然后返回生成的代理对象。这样,在外部通过接口进行引用才能保证代理对象能转换为接口类型;

 

动态代理底层细节:利用Java语言多态性实现。

A) 通过Interface接口类型去引用返回的代理对象;

B) 当通过接口引用调用方法时,此时的对象动态绑定到代理对象上,然后执行代理对象上的方法(代理对象实现了此接口,因此其内部具有接口中的方法);

C) 当代理对象中的方法被调用时,会自动调用InvocationHandler实现类中的invoke();

D) 在invoke()内部,再利用反射:method.invoke(target,args)去调用真正的对象;

E) 在method.invoke(target,args)前后,可以加入另外的辅助逻辑,这正是我们需要的;

F) 最终,真正的对象上的方法被调用执行了。

 

2.传入的目标对象是接口的具体实现类,target = new UserServiceImpl();

3.通过代理类Proxy创建代理对象时,传入newProxyInstance()的loader和interfaces应该通过target对象获取。如果传入其它对象的loader和interface会造成生成的代理对象无法使用。

 

动态代理使用示例:

首先需要1个接口和一个实现类

然后,创建一个封装好的用于生成动态代理的工具类

最后,将返回的代理对象转换为接口类型,调用接口中的方法 

package javacore;

public interface IService {
	String doSomething(Object...args);
}

 

package javacore;

import org.apache.catalina.tribes.util.Arrays;

public class ServiceImpl implements IService {

	@Override
	public String doSomething(Object... args) {
		System.out.println(this.getClass().getName() + "--->doSomething() is running");
		return Arrays.toString(args);
	}

}

 

package javacore;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JDKProxyHelper {
	
	public static JDKProxyHelper getHelper() {
		return new JDKProxyHelper();
	}
	
	private Object target;
	
	public Object newProxyInstance(Object target) {
		//真正的对象
		this.target = target;
		//代理对象
		Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), 
											  target.getClass().getInterfaces(), 
											  new InnerProxyHandler());
		//返回代理对象
		return proxy;
	}
	
	//内部类直接访问外部类的实例域
	private class InnerProxyHandler implements InvocationHandler {

		@Override
		public Object invoke(Object proxy, Method method, Object[] args)
				throws Throwable {
			Object retVal = null;
			System.out.println("method invoke before: "+method.getName());
			try {
				retVal = method.invoke(target, args);
			} catch(RuntimeException e) {
				System.out.println("error occur in method: "+method.getName());
				throw e;
			}
			System.out.println("method invoke after: "+method.getName());
			return retVal;
		}
		
	}
}

 

package javacore;

public class ProxyTester {
	public static void main(String[] args) {
		ServiceImpl target = new ServiceImpl();
		IService service = (IService)JDKProxyHelper.getHelper().newProxyInstance(target);
		String retVal = service.doSomething("Hello proxy", 100);
		System.out.println("return:" + retVal);
	}
}

 

 

另一个例子:

追踪compareTo()的调用轨迹

首先将用1~1000整数的代理填充数组,然后调用Arrays类中的binarySearch方法在数组中查找一个随机整数。最后,打印出与之匹配的元素。

 

package javacore;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Random;

public class ProxyTest {
	public static void main(String[] args) {
		Object[] elements = new Object[1000];
		for(int i=0;i<elements.length;i++) {
			Integer value = i+1;
			InvocationHandler h = new TraceHandler(value);
			Object proxy = Proxy.newProxyInstance(null, new Class[]{Comparable.class}, h);
			elements[i] = proxy;
		}
		Integer key = new Random().nextInt(elements.length)+1;
		int result = Arrays.binarySearch(elements, key);
		if(result>=0) {
			System.out.println(elements[result]);
		}
	}
}

class TraceHandler implements InvocationHandler {
	
	private Object target;
	
	public TraceHandler(Object target) {
		this.target = target;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		System.out.print(target);
		System.out.print("."+method.getName()+"(");
		if(args!=null) {
			for(int i=0;i<args.length;i++) {
				System.out.print(args[i]);
				if(i<args.length-1)
					System.out.print(", ");
			}
		}
		System.out.println(")");
		return method.invoke(target, args);
	}
	
}

 

异常处理

Java中,所有异常类都继承自Throwable类

Throwable

Error(系统级错误)

Exception

IOException(已检查异常)

RuntimeException(运行时异常)

 

1. 早抛出,晚捕获;

2. 传递异常比捕获异常更好,让高层次的方法通知用户发生了错误;

3. 当遇到子类方法不能抛出checkedException的时候,可以将其转换为RuntimeException抛给调用者,这样就不需在方法上声明了;

4. 自定义异常,更直观的描述异常;

5. 不要只抛出RuntimeException异常。应该寻找更加适当的子类或创建自己的异常类。

 

自定义异常类,覆盖Throwable中的构造方法

 

package javacore.exception;

public class AppException extends RuntimeException {

	/**
	 * 用给定的“诱饵”构造一个RuntimeException对象。
	 * @param message 异常的简单描述
	 * @param cause 引发此异常的诱饵
	 */
	public AppException(String message, Throwable cause) {
		super(message, cause);
	}

	public AppException(Throwable cause) {
		super(cause);
	}

	
}

 

package javacore.exception;

import java.sql.SQLException;

public class Dao {
	public void dao() throws SQLException {
		System.out.println("this is dao");
		throw new SQLException("Connection timeout");
	}
}

 

package javacore.exception;

import java.sql.SQLException;

public class Service {
	public void service() {
		try {
			new Dao().dao();
			System.out.println("this is service");
		} catch (SQLException e) {
			//自定义异常,设置异常消息和诱饵
			throw new AppException("error occur in dao",e);
		}
	}
}

 

package javacore.exception;

public class Action {
	public static void main(String[] args) {
		try {
			new Service().service();
		} catch (AppException e) {
			e.printStackTrace();
//			System.out.println(e.getMessage());
//			System.out.println(e.getCause().getMessage());
		}
	}
}

 

 

 

猜你喜欢

转载自just2learn.iteye.com/blog/2071816