day_01
值传递和引用传递的理解:
1、值传递
在方法的调用过程中,实参把它的实际值传递给形参,此传递过程就是将实参的值复制一份传递到函数中,这样如果在函数中对该值(形参的值)进行了操作将不会影响实参的值。因为是直接复制,所以这种方式在传递大量数据时,运行效率会特别低下。
2、引用传递
引用传递弥补了值传递的不足,如果传递的数据量很大,直接复过去的话,会占用大量的内存空间,而引用传递就是将对象的地址值传递过去,函数接收的是原始值的首地址值。在方法的执行过程中,形参和实参的内容相同,指向同一块内存地址,也就是说操作的其实都是源数据,所以方法的执行将会影响到实际对象。
面试的时候简单描述:
- 基本数据类型传值,对形参的修改不会影响实参;
- 引用类型传引用,形参和实参指向同一个内存地址(同一个对象),所以对参数的修改会影响到实际的对象。
- String, Integer, Double等immutable的类型特殊处理,可以理解为传值,最后的操作不会修改实参对象。
1.静态修饰符
注意:可以去修饰 成员变量(属性或者字段),不可以修饰 类(外部类),构造方法,局部变量
有static修饰的字段和方法,我们可以用字段所在的类的 类名.字段 类名.方法去访问
没有用static修饰的字段和方法,只能用实例去访问它,即创建对象去调用。
注意:
1.静态的属性放在 类的数据区 内, 所以 每个对象访问静态属性的时候 都是去 类的数据区中访问的静态属性的值
2.对象的数据区 中不包含静态属性。
3.一般情况下,类中的全局常量 用static修饰的较多,而类中的 字段 很少使用static修饰
static修饰的属性和方法在什么时候加载到类数据区里?
当程序编译时,该类中用到的所有类,这些类都会编译,然后这些的静态属性和静态方法都会被加载到类的数据区里去,看下图:
2. 封装 思想:对内部细节进行封装(隐藏),对外只提供能使用的方式,封装的目的是安全。
访问权限修饰符
作用:主要用来修饰类中的成员(属性,普通方法,构造方法),也可以去修饰类
private :该修饰符只能在本类中访问
default(缺省不写):在不同包下面不可以访问
protected:在不同包下面不可以访问
public:在任何地方都可以访问
注意:
1.public 、默认的(缺省不写) 这两种可以去修饰类(外部类,内部类)
2.private 和protected 不能去修饰外部类
3.所有的访问权限修饰符都不可以去修饰 局部变量
总结:
4. 公共的修饰(public):成员方法 字段(可以去修饰但是一般情况下用private修饰),构造方法, 内部类 ,类
私有的修饰(private): 字段,,成员方法(一帮情况成员方法用public修饰),构造方法 ,内部类
javabean:是一个标准的java类
要求:
1.类必须要是public修饰的
2.类中的属性必须是private修饰的
3.类中必须提供一个无参数构造器
4.每个私有化的属性必须提供一组getter/setter方法
注意: 1.Boolean类型(布尔的包装类),生成的get方法是get开头的(建议使用这个).
2.boolean类型,生成的get方法是is开头的 (用这个最好重写 getXxx() 格式的方法,
因为涉及到反射,反射一般会默认调取对象的get方法)
3. this关键字
用法:
1.在方法中出现的this代指调用该方法的对象
2.this可以解决属性与局部变量同名时的冲突
Public Class Student {
String name; //定义一个成员变量name
private void SetName(String name) { //定义一个参数(局部变量)name
this.name=name; //将局部变量的值传递给成员变量
}
}
3.this可以调用构造方法(注意:this必须是构造器中的第一条语句)
public class Student { //定义一个类,类的名字为student。
public Student() { //定义一个方法,名字与类相同故为构造方法
this(“Hello!”);
}
public Student(String name) { //定义一个带形式参数的构造方法
}
}
4.this可以作为方法的返回值,返回的是调用this所处方法的那个对象的引用,更简单点说,就是谁调用返回的就是谁。
由于返回的是对象引用,所以this不能用在静态成员方法中,只能在非静态成员方法中出现。
详细参考:https://www.cnblogs.com/chanchan/p/7812166.html
5.this可以作为方法的调用时的实参
6.this在自定义类型中的使用(同桌问题,一定要理解)
1 class People { 2 private String name; 3 private People friend; 4 public void setName(String name){ 5 this.name = name; 6 } 7 public String getName(){ 8 return this.name; 9 } 10 public void setFriend(People friend){ 11 if(this.getFriend() == friend){ 12 return; 13 } 14 this.friend = friend; 15 friend.setFriend(this);//此种方式注意死循环 16 } 17 public People getFriend(){ 18 return this.friend; 19 } 20 } 21 22 public class TestPeople { 23 public static void main(String[] args) { 24 People p1 = new People(); 25 p1.setName("小王"); 26 27 People p2 = new People(); 28 p2.setName("小张"); 29 30 //设置朋友关系 31 p1.setFriend(p2); 32 System.out.println(p1.getName()+"的朋友是:"+p1.getFriend().getName()); 33 //p2.setFriend(p1); 34 System.out.println(p2.getName()+"的朋友是:"+p2.getFriend().getName()); 35 } 36 }
day02
1.继承
子类继承父类中某些成员 , 父类(超类,基类,根类),子类(派生类,拓展类)
泛化:在多个子类的基础上面抽取共有的属性和行为到一个父类中去。
特化:在一个父类 的基础上拓展子类的特有属性和行为,生成一个新的子类。
原则:父类中存在共性,子类中存在特性。
优势: 提高代码的复用性。
子类可以去继承哪些成员?1.非私有化的成员(字段,方法) 2.private修饰的成员以及构造方法不能被继承
继承的特点
1.在java程序中继承只允许单继承(一个子类只允许有一个直接父类)
2.在java程序中允许每个类之间多重继承 eg: A extends B, B extends C C extends D
3.如果类都没有去直接继承另外一个类,那么该类会默认继承 超级基类(Object类)
2.方法的重写
概念:将父类的方法复制到子类重新定义方法体内的代码的过程
子类中拥有一个和父类完全一样(有几点可以不一样)的方法,将这两个方法称为重写
方法重写的要求:
1.保证子类方法和父类方法的方法签名(方法名+参数列表)一致,其中形参的名称是否一样无所谓
2.(访问权限等级高低 public > 默认的 > private)
子类的方法的 访问修饰符的等级 等于或者大于 父类的方法的访问修饰符
3.private修饰的方法不能被重写(private修饰的成员方法不能被继承到子类)
4.static修饰的方法不能被重写(static修饰的方法存在类的数据区,不在对象数据区)
5.子类中重写的方法的返回值类型 与 父类方法返回值类型 相同或者是父类的返回值类型的子类(不是java中的等级高低)
注意:
1.在编译阶段验证是否覆写: 在子类方法上面加 @Override ,用来检测子类中的方法是否重写父类的方法,让编译器来检查,如果是正确的覆写,编译通过,否则编译报错
2.子类方法和父类方法完全一样,也是方法的重写。
3.Object类
注意:★(所有的引用数据类型的对象都属于Object类型)
1.Object类位于java.lang包下面,使用时不需要导包
2.所有的引用数据类型的对象都可以使用Object类里的方法
3.基本数据类型变量 不可以使用Object类里的方法
toString()
哪个对象调用toString()方法,该方法就返回该对象的字符串表示形式
如果对象所属的类中没有重写toString,那么对象访问的是Objet类中toString方法
如果对象所属的类中重写了toString ,那么对象访问的是他自己本身类中重写的toString方法
注意:在自定义类中重写toString()的方法是因为打印对象名时,不想看到l类似这样"Student@56dd8cc"的值,而是想清楚的知道对象的属性值是什么。
equals方法
1.Object中的equals怎么定义的?
1 //lang包-- Object.java 中的equals(); 2 public boolean equals(Object obj) { 3 return (this == obj); 4 }
2. String 中的equals怎么定义的?
1 //lang包-- String.java 中的equals(); 2 public boolean equals(Object anObject) { 3 if (this == anObject) { 4 return true; 5 } 6 if (anObject instanceof String) { 7 //强制转换,装箱。 8 String anotherString = (String)anObject; 9 // value指前面的需要比较的字符串 10 int n = value.length; 11 if (n == anotherString.value.length) { 12 char v1[] = value; 13 char v2[] = anotherString.value; 14 int i = 0; 15 while (n-- != 0) { 16 if (v1[i] != v2[i]) 17 return false; 18 i++; 19 } 20 return true; 21 } 22 } 23 return false; 24 }
3.Object 和String 中的toString();
1 // Object 中的toString(); 2 public String toString() { 3 return getClass().getName() + "@" + Integer.toHexString(hashCode()); 4 } 5 // String 中的toString(); 6 public String toString() { 7 return this; 8 }
4.自定义类中的equals方法怎么定义?
1 //自定义类中的equals方法 2 public boolean equals(Student stu) { 3 if(((this.getName()).equals(stu.getName())) && ((this.getStuId())==(stu.getStuId()))){ 4 return true; 5 } 6 return false; 7 } 8 /* 1.编译器看到的obj的类型是 Object ,Object中是没有name和age的,编译报错 9 但是,我们知道实际调用的时候obj变量中装的是一个学生对象, 10 应该明确的告诉编译器这个obj是学生对象---》 obj强制转换成 Student*/ 11 /* 2.this.name 其实类型是String 是引用类型(非常特殊的引用类型) ; 12 使用== 比较有风险(可能比较的是地址),应该比较字符串的字面值 13 String类在设计的时候就于已经覆写了Object中的equals方法,比较规则就是使用的字面值进行比较 14 所以 上面this.name == s.getName() 应该调用String类中的equals方法*/ 15 public boolean equals(Object obj) { 16 Student s = (Student)obj; 17 if(this.name.equals(s.getName())&&this.age == s.getAge()){ 18 return true; 19 } 20 return false; 21 } 22 /* 3.上面的代码 if中的条件 本身就是一个逻辑运算,逻辑运算表达式的结果值就是一个boolean 23 所以没有必要写if else*/ 24 public boolean equals(Object obj) { 25 Student s = (Student)obj; 26 return this.name.equals(s.getName())&&this.age == s.getAge(); 27 }
★★:5.为什么调用println方法会 自动调用toString方法?
1 //io包--PrintStream.java 里面的println(); 2 public void println(Object x) { 3 String s = String.valueOf(x); 4 synchronized (this) { 5 print(s); 6 newLine(); 7 } 8 } 9 10 //lang包--String.java 里面的valueOf(); 11 public static String valueOf(Object obj) { 12 return (obj == null) ? "null" : obj.toString(); 13 }
4.== 和 equals 都是比较是否相等,请问它们到底有什么区别呢?
相等 : a 传统的理解一般都是数字值是否相等;
b 在程序中任何东西都是数据,都会比较是否相等
1 ==
基本数据类型: 比较的就是值是否相等;
引用数据类型: 比较的是对象的地址是否一样;(排除特殊 String)
2 equals (最初定义在根类Object中的)
基本数据类型 : 不能够使用! 基本数据类型不是对象,不能够调用Object中的方法
引用数据类型 : 在Object的源码中定义的就是==进行比较比较,比较的是对象的地址是否一样
而在String类型和包装类型(Integer)都重写了equals方法,比较是对应的值。
在实际开发中,我们一般比较对象都是通过对象的属性值进行比较(一般比较对象的地址没有多大用处),所以我们会经常覆写Object中的此方法,把自己的规则写在方法里面;
hashcode()
哈希码:该对象的内存地址通过JVM换算的一个10进制数字
day03
1.反编译 将字节码文件编译成java文件。
首先bin目录下要有jad.exe .
2. super关键字
作用:调用父类中的成员(属性,成员方法,构造方法)
1.在子类的方法中使用,代指父类对象,可以去调用父类属性和方法
2.在子类的构造方法 里访问父类的构造方法 用super() 调用无参父类构造方法。用super( 参数列表) 调用有参父类构造方法。
3.子类的构造器中的第一行代码会默认隐藏一个 super() ,当子类构造器中书写了super()或者this()的 (有参无参 )时,构造器中的隐藏的super()会被覆盖。
4.super() 和 this() 表达式 必须是构造器中的第一条语句,导致了super(),this()不能在构造器中同时使用,
使用场景:
1.super可以去访问父类中的非私有化的成员
2.调用父类构造方法(在子类构造器中 对父类私有化的属性进行初始化值)
3.子类构造器中第一条语句默认存在super();
3.多态
1.概念:一种事物(对象)有多种形态(类型)可以屏蔽不同的子类之间的实现差异,多态是指通过指向父类的指针,来调用在不同子类中实现的方法。
多态形式创建对象:
定义类型 对象名 = new 实际类型( );
定义类型:父类, 实际类型是:子类 ,也就是定义了一个 指向子类 的 父类引用类型 的 对象
2.多态方法调用编译和运行时的过程。
上面两句代码的编译,运行过程:
编译 : 第三行, 如果Animal是Person的父类,那么编译通过,否则编译报错;
第四行, (编译器把p1看成是Animal) 编译的时候会到p1的编译类型中找是否有eat方法,如果没有,会继续向p1的编译类型的父类中一直向上找,如果都没有找到,编译报错, 如果找到了编译通过
(不会向下到子类中去找, 找的时候就是编译的时候是不会执行代码的)
运行 : 第四行 , 先到运行时类型(Person)找eat方法,如果找到就执行,否则就向上到父类中找并执行
1.多态形式创建的对象可以调用哪些属性和方法,取决于定义类型
2.多态形式创建的对象的实际类型(子类)中,方法发生了重写,那么该对象调用的是子类中的方法
3.字段没有覆写一说
实际开发中一般不会在子类中定义一个和父类同名的字段,如果是有这样的情况存在,如果是使用的父类的对象,取值是父类里面的值; 如果子类对象,取值是子类里面的值;
也就是父类类型的引用可以调用父类中定义的所有属性和方法
参考:http://www.cnblogs.com/chenssy/p/3372798.html
http://www.cnblogs.com/hai-ping/articles/2807750.html
3.引用类型转换
小转大(向上转型):将子类对象 赋值给 父类对象的变量保存的过程,此时自动转换
大转小(向下转型):将父类对象 赋值给 子类对象的变量的过程,但此时需要强转
注意一般在开发中遇到向下转型时,都会对该对象的实际类型进行判断
boolean is = obj(目标对象) instanceof Type(目标对象类型)
4.final 关键字
final 修饰的类不能有子类,即不能被继承。
final 不能修饰构造方法,final修饰的方法不能被重写。
final 修饰的常量要初始化,且不能更改。(final修饰的常量要么直接赋值,要么使用有参或者无参的构造方法进行初始化,使用setter方法赋值会报错)
final 修饰的对象的引用不能改变。列如数组和对象的变量引用。
day04
1.单例模式 (介绍五种) 1. 饿汉模式 2. 懒汉模式 3. 双重检测锁模式 4. 枚举 5. 静态内部类模式:
单例类必须自己创建自己唯一的实例(对象)
1 //饿汉模式 2 public class Student { 3 //私有化无参的构造方法,其他类就不可以随便创建对象了。 4 private Student(){}; 5 //私有的,静态的对象,创建一次地址就不会改变,必须通过方法来访问 6 private static Student s = new Student(); 7 //静态的方法,外部直接使用 类名.方法 调用 8 public static Student getInstance(){ 9 return s; 10 } 11 }
1 //懒汉模式 2 public class Teacher { 3 4 private Teacher(){}; 5 6 private static Teacher t = null; 7 public static Teacher getInstance(){ 8 //if语句为了 防止在堆内存空间创建不同的对象 而 违反了单例原则 9 if(t == null){ 10 t = new Teacher(); 11 } 12 return t; 13 } 14 }
1 单例模式的类也是一个普通的类,其中也可以有其他的字段 方法等。。。
2 上面代码中,s对象是Student类被加载(把类放到JVM的过程中)的时候创建的
3 如果Student类中其他的字段和方法很多。。。,创建对象的过程比较长,类加载会比较慢
有可能加载之后很长时间其实都没有人来获得对象,浪费堆内存空间,这就是饿汉模式
4 在类加载的时候先不创建对象,而是在有人第一次来调用方法获得对象的时候才创建一个对象,
之后需要保存起来,以后再有人调用就不用创建对象。这就是懒汉模式
总结:
饿汉模式,类加载的时候效率低,获取单例对象时效率高,线程安全
懒汉模式,类加载的时候效率高,获取单例对象时效率低,线程不安全
懒汉模式的非线程安全问题的解决方法:双重检测锁模式
首先使用同步代码块,同步方法,效率低下;使用DCL(Double-Check Locking)双检查锁机制-
双重判空的的意义:
对于person1存在的情况,就直接返回。当person1为null并且同时存在两个线程调用getPerson1()方法时,它们都将通过第一重的person1==null的判断。
然后由于类锁机制,这两个线程只有一个可以获得锁并进入,另一个在外排队等候,必须要其中一个进入并出来后,另一个才能进入。
而此时如果没有了第二重的person1==null是否为null的判断,则第一个线程创建了实例,而第二个线程获得锁后还是可以继续再创建新的实例,这就没有达到单例的目的。
public class Person1 { private static volatile Person1 person1 =null; private Person1(){ } public static Person1 getPerson1() { if(person1==null){ synchronized(Person1.class){ if(person1==null) person1=new Person1(); } } return person1; } }
静态内部类模式:
public class SingleTon{ private SingleTon(){} private static class SingleTonHoler{ private static SingleTon INSTANCE = new SingleTon(); } public static SingleTon getInstance(){ return SingleTonHoler.INSTANCE; } }
2.代码块
构造代码块(初始化代码块,非静态代码块)
直接定义在类中,当调用了构造方法时,会先执行构造代码块(没有构造代码块就只执行自己)
普通代码块
(局部代码块):通常定义在方法内结合if switch 循环去使用
静态代码块 简述类加载的初始化顺序?
1静态初始化块只在类加载时执行,且优先于主方法执行,且只会执行一次,同时静态初始化块只能给静态变量赋值,不能初始化普通的成员变量。
2.可以在类加载的时候对静态的属性进行初始化
总结:
1.所有的静态代码块先执行,从顶级父类开始依次往后加载
2.在同一个构造器中,super() 优先于 构造代码块 优先于 构造器中的语句
3.当构造器中的第一条语句是this()时,构造器中构造代码块不存在了
day05
1.抽象类 理解: 我们都知道在面向对象的领域一切都是对象,同时所有的对象都是通过类来描述的,但是并不是所有的类都是来描述对象的。如果一个类没有足够的信息来描述一个具体的对象,而需要其他具体的类来支撑它,那么这样的类我们称它为抽象类。
1.使用abstract修饰的类是抽象类,抽象类本质也是一个类
2.类中有的成员 抽象类都可以有(字段 方法 构造方法),此外抽象类还比普通类多一个抽象方法, 故抽象类不允许创建对象。当然,抽象类中可以没有抽象方法。
3.抽象方法:用abstract修饰的方法 ,它没有方法体,并且定义时最后结束加;而且抽象方法 必须存于抽象类中(接口也可以),不能够放在普通类中。
4.一般将抽象的类作为父类, (普通)子类继承抽象父类,必须重写所有父类中的抽象方法。当然,如果子类也是抽象类,可以不用去重写父类中的抽象方法。
注意:abstarct 不能修饰属性,因为属性没有必要实现。
接口引用指向实现类的对象 比如:List list = new ArrayList();
2.接口 理解:接口本身就不是类, 接口是抽象类的延伸,接口是用来建立类与类之间的协议,它只提供一种形式,而没有具体的实现。
同时实现该接口的实现类 必须 要 实现该接口的所有方法,通过使用implements关键字,他表示该类在遵循 某个或 某组 特定的接口。
java为了保证数据安全是不能多重继承的,也就是说继承只能存在一个父类,但是接口不同,一个类可以同时实现多个接口,
不管这些接口之间有没有关系,所以接口弥补了抽象类不能多重继承的缺陷,但是推荐继承和接口共同使用,因为这样既可以保证数据安全性又可以实现多重继承。
接口定义: interface 接口名{}
接口内部可以有哪些成员--参考类
字段 全部都是全局常量(public static final修饰), 故可以直接调用
方法 全部都是抽象方法(缺省修饰 public abstract)(接口中可以没有抽象方法,但是没意义)
抽象方法需要子类类覆写才有意义,而static final修饰的方法都不能够被覆写,接口中的方法不可以用 static ,final修饰
接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
构造方法 没有!
注意
1.接口主要强调的是功能,必须要有实现类去实现(实现的时候注意public修饰符)
2.接口相当于实现类的父类(多态时体现),类可以去实现多个接口,类与类单继承,一个接口可以去继承多个接口,但接口不可以去实现接口 。
接口和抽象的区别:
1.接口概念
2.抽象概念
3. 相同点: 1. 抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
2. 抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类还只能是抽象类。
同样,一个类实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。
4. 不同点
1. 抽象层次不同。抽象类是对类抽象,而接口是对行为的抽象。即 抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。
2. 跨域不同。抽象类所跨域的是具有相似特点的类,而接口却可以跨域不同的类。
例如猫、狗可以抽象成一个动物类抽象类,具备叫的方法。鸟、飞机可以实现飞Fly接口,具备飞的行为,这里我们总不能将鸟、飞机共用一个父类吧!
所以说抽象类所体现的是一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is-a" 关系,即父类和派生类在概念本质上应该是相同的。对于接口则不然,
并不要求接口的实现者和接口定义在概念本质上是一致的, 仅仅是实现了接口定义的契约而已。
3.设计层次不同。对于抽象类而言,它是自下而上来设计的,我们要先知道子类才能抽象出父类,而接口则不同,它根本就不需要知道子类的存在,只需要定义一个规则即可,
至于什么子类、什么时候怎么实现它一概不知。
比如我们只有一个猫类在这里,如果你这是就抽象成一个动物类,是不是设计有点儿过度?我们起码要有两个动物类,猫、狗在这里,我们在抽象他们的共同点
形成动物抽象类吧!所以说抽象类往往都是通过重构而来的!但是接口就不同,比如说飞,我们根本就不知道会有什么东西来实现这个飞接口,怎么实现也不得而知,
我们要做的就是事前定义好飞的行为接口。所以说抽象类是自底向上抽象而来的,接口是自顶向下设计出来的。
3.谈谈你对面向对象的理解
面向对象是向现实世界模型的自然延伸,这是一种“万物皆对象”的编程思想。在现实生活中的任何物体都可以归为一类事物,而每一个个体都是一类事物的实例。
面向对象的编程是以对象为中心,以 消息为驱动,所以程序=对象+消息。
面向对象有三大特性,封装、继承和多态。
封装就是将一类事物的属性和行为抽象成一个类,使其属性私有化,行为公开化,提高了数据的隐秘性的同时,使代码模块化。这样做使得代码的复用性更高。
继承则是进一步将一类事物共有的属性和行为抽象成一个父类,而每一个子类是一个特殊的父类--有父类的行为和属性,也有自己特有的行为和属性。这样做扩展了已存在的代码块,进一步提高了代码的复用性。
如果说封装和继承是为了使代码重用,那么多态则是为了实现接口重用。多态的一大作用就是为了解耦--为了解除父子类继承的耦合度。如果说继承中父子类的关系式IS-A的关系,那么接口和实现类之之间的关系式HAS-A。简单来说,多态就是允许父类引用(或接口)指向子类(或实现类)对象。很多的设计模式都是基于面向对象的多态性设计的。
总结一下,如果说封装和继承是面向对象的基础,那么多态则是面向对象最精髓的理论。掌握多态必先了解接口,只有充分理解接口才能更好的应用多态。
4.面向对象和面向过程的区别?
做菜为例,其实面向过程就好像你是个厨师,要自己炒菜,所以要讲究步骤,
而面向对象就好像你是个食客,你只要通知厨师作菜,即发一个消息就可以了,至于厨师怎样作菜,是不用知道的。
---------------------------------------------------
两句话:
面向过程是一种以事件为中心的编程思想。就是分析出解决问题所需的步骤,然后用函数把这些步骤实现,并按顺序调用。(一种自顶向下的编程。)
面向对象是以“对象”为中心的编程思想。(自下向上先建立抽象模型然后再使用模型)。
5.面向对象开发的六个基本原则,迪米特法则
单一职责:一个类只做它该做的事情(高内聚)。在面向对象中,如果只让一个类完成它该做的事,而不设计与它无关的领域就是践行了高内聚的原则,这个类就只有单一职责。
开放封闭:软件实体应当对扩展开放,对修改关闭。要做到开闭有两个要点。第一、抽象是关键,一个系统中如果没有抽象类或接口系统就没有扩展点;第二、封装可变性,将系统中的各种可变因素封装到一个继承结构中,如果多个可变因素混杂在一起,系统将变得复杂而混乱。
里式替换:任何时候都可以用子类型替换掉父类型。子类一定是增加父类的能力而不是减少父类的能力,因为子类比父类的能力更多,把能力多的对象当成能力少的对象来用当然没有任何问题。
依赖倒置:面向接口编程(该原则说得直白和具体一些就是声明方法的参数类型、方法的返回类型、变量的引用类型时,尽可能使用抽象类型而不用具体的类型,因为抽象类型可以被它的任何一个子类型所替代)。
合成聚合复用:优先使用聚合或合成关系复用的代码。
接口隔离:接口要小而专,绝不能大而全。臃肿的接口是对接口的污染。既然接口表示能力,那么一个接口只应该描述一种能力,接口也应该是高内聚的。
迪米特法则
迪米特法则又叫最少知识原则,一个对象应当对其他对象有尽可能少的了解。
项目中用到的原则
单一职责、开放封闭、合成聚合复用(最简单的例子就是String类)、接口隔离。
6.枚举
当我们遇到 属性值是固定值个数(属性值有确定范围)正常来可以使用单例模式,我们在类中使用单例模式需要创建本类的对象,当我们声明多个对象时,public static final及构造器就是重复性的代码,而枚举简化了创建本类对象时的格式,从而简化了单例模式的声明的方式。即枚举能够解决属性值是固定个数的问题
看下面代码使用单利模式解决属性值确定范围的问题。显然重复性代码居多。
package cm.单例模式解决属性范围问题; // 以知人的性别只有男女之分,固属性值固定。 public class Gender { private Gender(){} private static Gender man = new Gender(); private static Gender woman = new Gender(); //外部通过方法获取私有的对象 public static Gender getMan() { return man; } public static Gender getWoman() { return woman; } //重写toString方法 public String toString(){ if(this == man){ return "男"; }else if(this == woman){ return "女"; } return null; } }
package cm.单例模式解决属性范围问题; public class Student { private Gender gender; public Gender getGender() { return gender; } public void setGender(Gender gender) { this.gender = gender; } }
package cm.单例模式解决属性范围问题; public class Test { public static void main(String[] args) { Student s = new Student(); s.setGender(Gender.getMan()); //打印对象 System.out.println(s.getGender()); //男 } }
看下面代码使用 枚举 解决 季节这个属性值固定问题
1 package cm.枚举; 2 3 public enum Season { 4 spring("春",1),summer("夏",2),autumn(),winter("冬",4); 5 6 private String name = "哈哈"; //初始值 7 private int index; 8 9 public String getName() { 10 return name; 11 } 12 public void setName(String name) { 13 this.name = name; 14 } 15 public int getIndex() { 16 return index; 17 } 18 public void setIndex(int index) { 19 this.index = index; 20 } 21 // 私有的有参的构造方法 22 private Season(String name,int index){ 23 this.name = name; 24 this.index = index; 25 } 26 //私有的无参构造方法 ,这样枚举对象里面可以不用传值。 27 private Season(){} 28 //重写toString(); 29 public String toString(){ 30 return this.name+""+this.index; 31 } 32 33 34 }
1 package cm.枚举; 2 3 import cm.枚举星期.Week; 4 5 public class Test { 6 7 public static void main(String[] args) { 8 Season s = Season.spring; //通过枚举类名.对象名访问 固定不变的属性(对象) 9 System.out.println(s); 10 11 Season[] ss = Season.values();//获取枚举类中定义的所有的对象,返回一个枚举类型的对象数组 12 for (Season i : ss) { 13 System.out.println(i.getName()+","+i.getIndex()); 14 } 15 } 16 17 }
结果为:
春1
春,1
夏,2
哈哈,0
冬,4
7.组合关系
①组合是(has-a)关系,而继承则是(is-a)关系;
② 组合关系在运行期决定,而继承关系在编译期就已经决定了。
③ 组合是在组合类和被包含类之间的一种松耦合关系,而继承则是父类和子类之间的一种紧耦合关系。
④ 当选择使用组合关系时,在组合类中包含了外部类的对象,组合类可以调用外部类必须的方法,而使用继承关系时,父类的所有方法和变量都被子类无条件继承,子类不能选择。