简而言之,就是使用接口来定义要实现的策略方法,然后具体的实现类来实现不同的接口从而实现不同的策略,这就是所谓的函数对象来表示策略
1.嵌套类包括以下四种,除了静态成员类,其他的都可以被称为内部类
原文中这一条想要告诉我们的就是泛型的不正确声明是会导致泛型的擦除和脏数据的,如下代码示例
public class Test {
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
StackDemo stackDemo = new StackDemo();
unsafeAdd(strings,stackDemo);
unsafeAdd(strings,new Integer(1));
System.out.println(strings);
}
//该方法会导致泛型被擦除,导致脏数据的产生
private static void unsafeAdd(List strings, StackDemo stackDemo) {
strings.add(stackDemo);
}
//该方法就会限制对应的参数操作也必须是对应的String类型,所以会编译报错不通过
private static void unsafeAdd(List<String> strings, Integer stackDemo) {
strings.add(stackDemo);
}
//综上所述:需要采用第二种方式来声明和使用泛型才是合理的
}
在开发当中会遇到很多编译器警告,例如在JDK1.8以前的编译器会对下面这段代码有警告
//JDK1.8之前的编译器要求我们必须要在实例当中声明泛型类型,否则会有编译警告
Set<String> strings = new HashSet();
//如果编译器比较早的,采用下面这个方式即可解决
Set<String> strings = new HashSet<String>();
有些警告是不好消除的,此时就可以使用注解@SuppressWarings(“unchecked”)来消除我们所信任的不必要的警告
使用数组的问题在编译时期比较不易被发现,但是使用集合搭配泛型就更容易发现问题,如下代码所示
public class GenericsTest {
public static void main(String[] args) {
//使用这种方式需要运行时才能发现它的错误
Object[] objects = new Long[1];
objects[0] = "不要这样操作";
//使用这种方式,编译本身就不通过,异常比较容易发现
List<Object> objectList = new ArrayList<Long>();
objectList.add("可以这样做");
}
}
如下所示我们可以对第六条当中的代码进行泛型改装,这样可以使得它的拓展性更好,而且减少了不必要的警告
public class StackDemo<E> {
private E[] elements;
private int size = 0;
private final int DEFAULT_INITIAL_CAPACITY = 16;
/**
* 功能描述:
* 〈初始化栈〉
*
* @params : []
* @return :
* @author : cwl
* @date : 2019/5/5 16:59
*/
public StackDemo(){
elements = (E[])new Object[DEFAULT_INITIAL_CAPACITY];
}
/**
* 功能描述:
* 〈压栈〉
*
* @params : [o]
* @return : void
* @author : cwl
* @date : 2019/5/5 16:58
*/
public void push(E o){
ensureCapacity();
elements[size++] = o;
}
/**
* 功能描述:
* 〈出栈〉
*
* @params : []
* @return : java.lang.Object
* @author : cwl
* @date : 2019/5/5 16:59
*/
public E pop(){
if (size == 0){
throw new EmptyStackException();
}
return elements[size--];
}
/**
* 功能描述:
* 〈自动扩容〉
*
* @params : []
* @return : void
* @author : cwl
* @date : 2019/5/5 16:59
*/
private void ensureCapacity() {
if(elements.length == size){
elements = Arrays.copyOf(elements,2*size+1);
}
}
}
总而言之:只要时间允许,尽可能的去做到泛型化,对于代码的可拓展性会更好一些
错误示例:当不使用泛型的时候,即便s1和s2两个是不同类型的集合此时也会被添加到一起.
private static Set union(Set s1, Set s2) {
s1.addAll(s2);
return s1;
}
正确示例:采用下列这种方式,当你想要添加一个set集合时,它会根据类型推导,要求E的类型必须都是一致的,从而来限制这个集合的数据准确,而不至于产生脏数据
public class Test {
public static void main(String[] args) {
Set<String> s1 = new HashSet<>();
s1.addAll(Arrays.asList("1","2","3"));
Set<String> s2 = new HashSet<>();
s2.addAll(Arrays.asList("4","5","6"));
Set<String> s3 = union(s1, s2);
System.out.println(s3);
}
private static <E> Set<E> union(Set<E> s1, Set<E> s2) {
s1.addAll(s2);
return s1;
}
}
如我们之前代码例子:如果我在addAll的方法当中只是如代码示例2中的方式,那么我所要添加的类型可以是Set或者是Set都是可以被编译通过的.这样是不合理的,所以我们需要设置泛型的上限就是当前类型T,才能保证添加的类型都是属于同一类的
public class ChildHashSet<T> extends HashSet<T> {
private int count = 0;
public ChildHashSet() {
}
public ChildHashSet(int initCap , int loadFactory) {
super(initCap,loadFactory);
}
@Override
public boolean add(T t) {
count++;
return super.add(t);
}
@Override
public boolean addAll(Collection<? extends T> c) {
count = c.size();
return super.addAll(c);
}
public int getCount() {
return count;
}
}
代码示例2:
//错误做法
@Override
public boolean addAll(Collection<T> c) {
count = c.size();
return super.addAll(c);
}
异构容器:利用字节码对象来使得一个容器当中可以容下多种类型,而且还是可以合法使用的.这样就可以像一个对象一样,存储像数据库当中的行项目那样包含了不同种类的类型.具体代码实现如下:
public class Hobbies {
private Map<Class<?>,Object> hobbies = new HashMap<>();
/**
* 功能描述:
* 〈添加爱好〉
*
* @params : [type, instance]
* @return : void
* @author : cwl
* @date : 2019/5/6 17:51
*/
public <T> void putHobbies(Class<T> type,T instance){
if(type == null){
throw new NullPointerException("type is null");
}
hobbies.put(type,instance);
}
/**
* 功能描述:
* 〈查询爱好〉
*
* @params : [type]
* @return : T
* @author : cwl
* @date : 2019/5/6 17:52
*/
public <T> T getHobbies(Class<T> type){
return type.cast(hobbies.get(type));
}
}
本质来说如果我们能够使用类来表示数据库当中的一个行项目,就不要采用这样的集合来封装想要的信息,我们要尽量做到面向对象编程,而非面向集合编程
对于枚举类,它可以使用的范围相对比int常量要广泛的多,下面列举一个枚举类的综合使用.需求是区分工作日和休息日,不同的工作时间对应不同的工作计算汇率
public enum PayrollyDay {
MONDAY(PayType.WEEKDAY),
TUESDAY(PayType.WEEKDAY),
WENDESDAY(PayType.WEEKDAY),
THURSDAY(PayType.WEEKDAY),
FRIDAY(PayType.WEEKDAY),
SATURDAY(PayType.WEEKEND),
SUNDAY(PayType.WEEKEND);
private final PayType payType;
//当创建一个枚举对象的时候,有多少个枚举字段就会调用多少次构造器,可以通过人肉打印测试
PayrollyDay(PayType payType){
//System.out.println("调用构造器");
this.payType = payType;
}
double pay(double hoursWorked,double payRate){
return payType.pay(hoursWorked,payRate);
}
private enum PayType{
WEEKDAY{
double overTimePay(double hours,double payRate){
return hours <= HOURS_PER_SHIFT ? 0 : (hours - HOURS_PER_SHIFT) * payRate / 2;
}
},
WEEKEND{
double overTimePay(double hours,double payRate){
return hours = payRate / 2;
}
};
//枚举类当中的固定写法,具体实现时会找到上面两个枚举当中的任意一个
abstract double overTimePay(double hours,double payRate);
private static final int HOURS_PER_SHIFT = 8;
double pay(double hoursWorked,double payRate){
double basePay = hoursWorked * payRate;
return basePay + overTimePay(hoursWorked,payRate);
}
}
}
测试用例
public static void main(String[] args) {
//参数1:时薪
//参数2:工作时间
//参数3:加班工资计算汇率
//此处double精度不够,有兴趣的可以自行转换成Bigdecmail计算
double pay = PayrollyDay.MONDAY.pay(50,10, 0.8);
System.out.println(pay);
}