软构Lab3学习笔记(2)

不得不说,Lab3真的致命

学了很多东西,还没有详细整理,先放到这里咯。

java StringBuffer去除最后一位字符

根据分割符拼接字段,想去除最后一位分割符,可使用StringBuffer自带的deleteCharAt方法。

StringBuffer sb = new StringBuffer();

        sb.append("a").append('\001').append("b").append('\001').append("c").append('\001').append("d").append('\001');        

        sb.deleteCharAt(sb.length()-1);

        System.out.println(sb.toString());

在书写的过程中发现了泛型无法适用于重载的场景,报了both methods have same erasure的错误:

场景1. 当两个重载函数的参数如下

void func(Map<Integer, String> map) {}

void func(Map<Integer, List<String>> map) {}

//IDE会报出编译错误:both methods have same erasure

查询后发现网上写的原因是:

Map<String, Integer>的泛型参数无法提取出来,因为对于任意的Map<Key, Value>,在运行时都会产生类型擦除,可以在运行时通过getclass()检查Map中的对象类型。但并不意味着可以在运行时提取Key和Value的类型。

void func(Map<Integer, String> map) {}

void func(Map<Integer, List<String>> map) {}

中,由于Java泛型在编译时进行erase,上述方法都会变成void fanc(Map map),

查阅资料学习了https://www.jianshu.com/p/f9da328c91be

场景2:

 
class Dog {
    private String name;
    private int age;
    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

class Apple {
    private String name;
    private int age;
    public Apple(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

@Test
void test() {
    List<Dog> dogList = Lists.newArrayList();
    dogList.add(new Dog("Dog B", 2));
    dogList.add(new Dog("Dog A", 2));
    dogList.add(new Dog("Dog A", 1));
    specialSort(dogList);
    System.out.println(JSON.toJSONString(dogList));
    List<Apple> appleList = Lists.newArrayList();
    appleList.add(new Apple("Apple B", 2));
    appleList.add(new Apple("Apple A", 2));
    appleList.add(new Apple("Apple A", 1));
    specialSort(appleList);
    System.out.println(JSON.toJSONString(appleList));
}

public static void specialSort(List<Dog> dogList) {
    dogList.sort((o1, o2) -> {
        int result = 0;
        if (o1.getAge() != o2.getAge()) {
            result = o1.getAge() - o2.getAge();
        } else {
            Objects.requireNonNull(o1.getName());
            Objects.requireNonNull(o2.getName());
            result = ObjectUtils.compare(o1.getName(), o2.getName());
        }
        return result;
    });
}

public static void specialSort(List<Apple> appleList) {
    appleList.sort((o1, o2) -> {
        int result = 0;
        if (o1.getAge() != o2.getAge()) {
            result = o1.getAge() - o2.getAge();
        } else {
            Objects.requireNonNull(o1.getName());
            Objects.requireNonNull(o2.getName());
            result = ObjectUtils.compare(o1.getName(), o2.getName());
        }
        return result;
    });
}

 

报错:

‘specialSort(List)’ clashes with ‘specialSort(List)’;both methods have same erasure

 

下面进行具体学习↘

Java泛型: 类型擦除(type erasure)

Java的泛型不同于C++的模板:Java泛型是"type erasure",C++模板是"reified generic"。

·  type erasure:泛型类型仅存在于编译期间,编译后的字节码和运行时不包含泛型信息,所有的泛型类型映射到同一份字节码。 (object)

·  reified generic:泛型类型存在于编译和运行期间,编译器自动为每一种泛型类型生成类型代码并编译进二进制码中。

type erasure的本质
泛型(T) --> 编译器(type erasure) --> 原始类型(T被Object替换)
泛型(? extends XXX) --> 编译器(type erasure) --> 原始类型(T被XXX替换)
原始类型指被编译器擦除了泛型信息后,类型变量在字节码中的具体类型。

假如,我们定义一个泛型类Generic是这样的:

class Generic<T> {
    private T obj;

    public Generic(T o) {
        obj = o;
    }

    public T getObj() {
        return obj;
    }
}
那么,Java编译后的字节码中genatic相当于这样的
class Generic {
    private Object obj;

    public Generic(Object o) {
        obj = o;
    }

    public Object getObj() {
        return obj;
    }
}

 

假如,我们使用Generic类是这样的:

public static void main(String[] args) {
    Generic<String> generic = new Generic<String>("hehe...");
    String str = generic.getObj();
}

那么,Java编译后的字节码中相当于这样的:

public static void main(String[] args) {
    Generic generic = new Generic("hehe...");
    String str = (String) generic.getObj();

所以,所有Generic的泛型类型实质是同一个类:

public static void main(String[] args) {
    Generic<Integer> a = new Generic<Integer>(111);
    Generic<String> b = new Generic<String>("bbb");
    System.out.println("a'class: " + a.getClass().getName());
    System.out.println("b'class: " + b.getClass().getName());
    System.out.println("G'class: " + Generic.class.getName());
    System.out.println("a'class == b'class == G'class: " + (a.getClass() == b.getClass() && b.getClass() == Generic.class));
}

上述代码执行结果: 

a'class: generic.Generic
b'class: generic.Generic
G'class: generic.Generic
a'class == b'class == G'class: true

小结:Java的泛型只存在于编译时期,泛型使编译器可以在编译期间对类型进行检查以提高类型安全,减少运行时由于对象类型不匹配引发的异常。

type erasure导致泛型的局限性

类型擦除降低了泛型的泛化性,使得某些重要的上下文环境中不能使用泛型类型,具有一定的局限性。

运行时隐含类型转换的开销

使用泛型时,Java编译器自动帮我们生成了类型转换的代码,这相对于C++模板来说无疑带来了额外的性能开销。

类型参数不能实例化

 T obj = new T(); // compile error

    T[] objs = new T[10]; // compile error

     Generic<String> generic = new Generic<String>[10]; // compile error

 

//实例化概念有些模糊

类型参数不能进行类型查询(类型查询在运行时,运行时类型参数已被擦除)

    Generic<Integer> a = new Generic<Integer>(111);
    if(a instanceof Generic<String>)// compile error
    if(a instanceof Generic<T>) // compile error
    if(a instanceof Generic) // 仅测试了a是否是Generic,忽略了类型参数

 

不能在静态域和静态方法中引用类型变量

    class Generic<T> {
        private static T obj;// compile error
        public static T func(){...}// compile error
    }

因为所有泛型类最终映射到同一个原始类型类,而静态属性是类级别的(?),类和实例共同拥有它的一份存储,因此一份存储无法安放多个类型的属性。静态方法也是如此。

重载方法签名冲突:

public boolean equals(T obj) // compile error

public boolean equals(T obj)被擦除类型后变为public boolean equals(Object obj),与根类Object的public boolean equals(Object obj)签名一样,而两者均不能覆盖对方,导致编译期名称冲突

一个类不能实现同一个泛型接口的两种变体:

interface IFace<T>() {}
class FaceImpParent implements IFace<String> {}
class FaceImpChild extends FaceImpParent implements IFace<Integer> {} // compile error

 

原因是IFace<String>IFace<Integer>在擦除类型后是同一个接口,一个类不能实现两次同一个接口。

泛型类不能扩展java.lang.Throwable

    class GenericException <T> extends Exception {} // compile error

对于场景2的解决方案:

解决方案

方案一:针对集合对象,方法声明不同基类/实现类作为方法参数类型(不推荐)

既然ListList在编译时都回变成List,如果我已知其中一个集合对象是ArrayList对象,以List为例,那就可以将其对应的方法参数类型改成ArrayList。再或者,直接将其中一个方法改成Collection

如此,因为参数类型不同,便满足重载的条件。

个人认为,这样的做法会大大影响代码的可读性,且会造成大量的冗余代码,虽然能解决问题,但真的不推荐。

此方案参考自:both methods have same erasure:如何无损扩展代码

// Class Dog 和 Class Apple 的定义不变
@Test
void test() {
    ArrayList<Dog> dogList = Lists.newArrayList();
    dogList.add(new Dog("Dog B", 2));
    dogList.add(new Dog("Dog A", 2));
    dogList.add(new Dog("Dog A", 1));
    specialSort(dogList);
    System.out.println(JSON.toJSONString(dogList));
    ArrayList<Apple> appleList = Lists.newArrayList();
    appleList.add(new Apple("Apple B", 2));
    appleList.add(new Apple("Apple A", 2));
    appleList.add(new Apple("Apple A", 1));
    specialSort(appleList);
    System.out.println(JSON.toJSONString(appleList));
}
public static void specialSort(List<Dog> dogList) {
    dogList.sort((o1, o2) -> {
        int result = 0;
        if (o1.getAge() != o2.getAge()) {
            result = o1.getAge() - o2.getAge();
        } else {
            Objects.requireNonNull(o1.getName());
            Objects.requireNonNull(o2.getName());
            result = ObjectUtils.compare(o1.getName(), o2.getName());
        }
        return result;
    });
}
public static void specialSort(ArrayList<Apple> appleList) {
    appleList.sort((o1, o2) -> {
        int result = 0;
        if (o1.getAge() != o2.getAge()) {
            result = o1.getAge() - o2.getAge();
        } else {
            Objects.requireNonNull(o1.getName());
            Objects.requireNonNull(o2.getName());
            result = ObjectUtils.compare(o1.getName(), o2.getName());
        }
        return result;
    });
}

方案二:设置共同基类/接口,抽象出方法所需要内容

如果两个是类型相近的类,如DogCat,那么可以考虑新建一个公共基类,随后在基类中暴露出为方法所需要的属性获取方法。

可重现问题代码中,方法目的是为了按特殊规则排序,那么也可以选择直接在基类中实现Comparable接口,重写比较的方法。

但对于类型不相近,或较难抽象出公共基类的类,那这个方案就显得不适用了。

abstract class ParentClass {
    public abstract String getName();
    public abstract int getAge();
}

class Dog extends ParentClass{
    private String name;
    private int age;
    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

class Apple extends ParentClass{
    private String name;
    private int age;
    public Apple(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}
@Test
void test() {
    List<Dog> dogList = Lists.newArrayList();
    dogList.add(new Dog("Dog B", 2));
    dogList.add(new Dog("Dog A", 2));
    dogList.add(new Dog("Dog A", 1));
    specialSort(dogList);
    System.out.println(JSON.toJSONString(dogList));
    List<Apple> appleList = Lists.newArrayList();
    appleList.add(new Apple("Apple B", 2));
    appleList.add(new Apple("Apple A", 2));
    appleList.add(new Apple("Apple A", 1));
    specialSort(appleList);
    System.out.println(JSON.toJSONString(appleList));
}

public static <T extends ParentClass> void specialSort(List<T> list) {
    list.sort((o1, o2) -> {
        int result = 0;
        if (o1.getAge() != o2.getAge()) {
            result = o1.getAge() - o2.getAge();
        } else {
            Objects.requireNonNull(o1.getName());
            Objects.requireNonNull(o2.getName());
            result = ObjectUtils.compare(o1.getName(), o2.getName());
        }
        return result;
    });
}

 

方案三:在方法中,直接使用映射机制//需进一步学习映射机制

在我看来,这是最暴力的方案了,方法中先可以直接通过映射机制获取到对象的属性值,随后便可以进行原来的逻辑代码操作了。

这个方案,不需要重载,省去了那些冗余的逻辑代码,只是反射机制需要格外的开销。

// Class Dog 和 Class Apple 的定义不变
@Test
void test() {
    List<Dog> dogList = Lists.newArrayList();
    dogList.add(new Dog("Dog B", 2));
    dogList.add(new Dog("Dog A", 2));
    dogList.add(new Dog("Dog A", 1));
    specialSort(dogList);
    System.out.println(JSON.toJSONString(dogList));
    List<Apple> appleList = Lists.newArrayList();
    appleList.add(new Apple("Apple B", 2));
    appleList.add(new Apple("Apple A", 2));
    appleList.add(new Apple("Apple A", 1));
    specialSort(appleList);
    System.out.println(JSON.toJSONString(appleList));
}
public static void specialSort(List list){
        list.sort((o1, o2) -> {
            int result = 0;
            // 暂不考虑特殊异常处理
            try {
                String method = "getAge";
                int age1 = (int) o1.getClass().getMethod(method).invoke(o1),
                        age2 = (int) o2.getClass().getMethod(method).invoke(o2);
                if (age1 != age2) {
                    result = age1 - age2;
                } else {
                    method = "getName";
                    String name1 = (String) o1.getClass().getMethod(method).invoke(o1),
                            name2 = (String) o2.getClass().getMethod(method).invoke(o2);
                    Objects.requireNonNull(name1);
                    Objects.requireNonNull(name2);
                    result = ObjectUtils.compare(name1, name2);
                }
            } catch (Exception e) {
                System.out.println(e.getMessage());
                e.printStackTrace();
            }
            return result;
        });
    }

 

但上述描述的都是在同一个class中重载函数导致的编译错误,我的是在接口和实现类中发生冲突,经检查是实现类在implement接口时少了一个<L>......whatever,学到了新东西了就是。

查询了treeset及其相对应的排序方法,且其排序方法的重写可利用于list(同属Collection集合)

具体实现见:https://www.jianshu.com/p/12f4dbdbc652

list的排序方法见:https://www.cnblogs.com/Lxiaojiang/p/6805151.html

list重写与treeset相似

Set的四种排序方法:https://blog.csdn.net/the_fool_/article/details/82389351

学习了this的关键字详解:

http://c.biancheng.net/view/953.html

  • this( ) 不能在普通方法中使用,只能写在构造方法中。
  • 在构造方法中使用时,必须是第一条语句。

Java Pattern类的用法详解(正则表达式)

https://www.cnblogs.com/sparkbj/articles/6207103.html

https://blog.csdn.net/woniu317/article/details/52186694?utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.baidujs&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.baidujs

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值