通过学习 极客学院wikejava提高篇,记录一些比较重要的东西。
1. ISP(Interface Segregation Principle)
isp:使用多个专门的接口比使用单一的总接口要好。
一个类对另外一个类的依赖性应当是建立在最小的接口上的,没有关系的接口合并在一起,形成一个臃肿的大街口,这是对角色和接口的污染。
2. 使用序列化实现对象的拷贝
public class Person implements Cloneable{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
protected Person clone() {
Person person = null;
try {
person = (Person) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return person;
}
}
使用:
Person p1 = new Person();
Person p2 = p1.clone();
这种方式是浅拷贝,他是有选择的拷贝,它具有如下特征:
- 基本类型值,拷贝其值
- 对象,拷贝的是他的引用,这样的话,如果修改p1中对象引用的属性,那么 p2的值也会发生改变
- 字符串,拷贝其引用,但是如果你修改的话,他会在字符串常量池中重新创建一个字符串,这样的话,修改字符串并不会改变p2.
使用序列化即可避免这样情况
实现原理:把对象写入内存中的字节流,然后在从字节流中读取,获取的对象即为拷贝的对象。对象需要实现Serializable接口。
public class CloneUtils {
@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T obj){
T cloneObj = null;
try {
//写入字节流
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream obs = new ObjectOutputStream(out);
obs.writeObject(obj);
obs.close();
//分配内存,写入原始对象,生成新对象
ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream ois = new ObjectInputStream(ios);
//返回生成的新对象
cloneObj = (T) ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
return cloneObj;
}
}
3. 内部类
3.1 为什么使用内部类
如果想要实现多重继承的话,我们可以使用多个内部类来继承多个具体的或者抽象的类。
3.2 .this 和.new
创建内部类:outer.new InnerClass();在创建内部类对象的时候,我们需要一个外部类的对象,这样的话,内部类就与外部类产生了联系,在内部类中会保存这个外部类的对象,从而可以在内部类中使用外部类中属性和方法,包括私有属性和方法(在类内部,故可以使用)。
public class OuterClass {
private String name ;
private int age;
/**省略getter和setter方法**/
public class InnerClass{
public InnerClass(){
name = "chenssy";
age = 23;
}
public void display(){
System.out.println("name:" + getName() +" ;age:" + getAge());
}
}
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
OuterClass.InnerClass innerClass = outerClass.new InnerClass();
innerClass.display();
}
}
在内部类中我们可以使用OuterClass.this获取这个外部类的对象
4.匿名内部类
创建的格式:
new 类(包括抽象类)(参数列表)|实现接口()
{
//匿名内部类的类体部分,实现抽象方法或者接口
}
Bird:
public abstract class Bird {
String name;
public Bird(String name){
this.name = name;
}
public abstract int fly(int distance);
public String getName(){
return name;
}
}
Test:
public class Test {
public static void main(String[] args) {
Test t = new Test();
t.info(new Bird("xinwa") {
@Override
public String getName() {
// TODO Auto-generated method stub
return this.name;
}
@Override
public int fly(int distance) {
// TODO Auto-generated method stub
return distance;
}
});
}
}
public void info(Bird bird){
System.out.println(bird.getName()+"能飞多少米"+bird.fly(100));
}
}
使用匿名内部类的场景一般是对于对于类只需要使用一次
4.1使用的形参为何要为 final
public class OuterClass {
public void display(final String name,int age){
class InnerClass{
void display(){
System.out.println(name);
}
}
}
}
比方说上面这个例子,编译之后如下
public class OuterClass$InnerClass {
public InnerClass(String name,String age){
this.InnerClass$name = name;
this.InnerClass$age = age;
}
public void display(){
System.out.println(this.InnerClass$name + "----" + this.InnerClass$age );
}
}
会发现:在编译之后,内部类会对参数进行备份,这样其实内部类使用的和外部类传进来的参数是俩个东西。
如果不为final的话,看下面例子,当然下面例子在jdk7是错误的,只是用来演示,语法是错的。
public class OuterClass {
public void display(String name,String age){
name = "xinwa";
class InnerClass{
void display(){
System.out.println(name);
}
}
}
}
编译之后,内部类的值时copy参数的方法,当执行name=”xinwa”;这条语句之后,这时内部类中的name值并没有改变,依然等于参数的值,这显然不合常理,所以为了避免这样情况,我们给形参加一个final,让其值无法改变。
5.强制类型转换
在java中强制类型转换分为基本数据类型和引用数据类型。
Java中由于继承和向上转型,子类可以非常自然的转换为父类,但是父类转换为子类则需要强制转换。
当使用一个类型的构造器构造出一个对象时,这个对象的类型就已经确定了。
我们可以通过继承和向上转型的父类类型来引用他。
Father father = new Son();
Son son = (Son)father;
这种方式是可行的。
Father father = new Father();
Son son = (Son)father;
这种情况在编译时并不会出错,但是在运行时会报ClassCastException 异常信息
故:在继承中,子类可以自动转型为父类,但是父类强制转换为子类只有当父类的引用类型的对象为子类的类型时,才能转换成功,否则会发生类型转换异常
6.代码块
6.1 编译器如何处理构造代码块呢?
编译器会将 代码块 按照他们的顺序呢插入到所有构造函数的最前端,这样保证不管调用那个构造函数都会执行所有的构造代码块
6.2用途:
1.初始化实例变量:
如果一个类中存在若干个构造函数,这些构造函数都需要对实例变量进行初始化,那么可以简化代码,把它们单独放在代码块中
2.初始化实例环境
当构造一个对象时,需要先实现一些复杂的逻辑,这时为了让构造函数简单易懂,我们可以把实现的逻辑放在代码块中,这样程序就可以更加简单易懂。
3. 静态代码块、构造代码块、构造函数执行顺序
静态代码块会先执行,然后是构造代码块,最后是构造函数,但要注意一点是静态代码块只会在加载类的时候执行一次