Java编程笔记12:类型信息_cannot convert type of expression new arraylist<>(-程序员宅基地

技术标签: JAVA  java  后端  开发语言  

Java编程笔记12:类型信息

5c9c3b3b392ac581.jpg

图源:PHP中文网

Class对象

为了实现多态,必然需要在运行时知晓当前对象的实际类型,Java通过在对象中记录一个Class对象引用来解决这个问题。

所谓的Class对象,实际上就是用于表示当前对象的类型,并作为当前对象创建时的”类型模板“。可以简单地看作是”对象化的类“。

通常我们所说的类(class)都是指源码中的静态类定义,而程序真正编译和运行的时候,编译器会读取源码并加载相应的类定义,在内存中创建相应的对象作为类定义的”动态实现“,并依赖这些对象完成相应类实例的创建和类属性的访问工作,这些对象就是Class对象。

如果熟悉Python就不难理解,这里的Class对象实际上就是”元类“(meta class)实例。

可以通过两种途径获取Class对象的引用。

获取Class对象引用

package ch12.get_cls;

class MyClass {
    
}

public class Main {
    
    public static void main(String[] args) {
    
        Class clsRef1 = null;
        try {
    
            clsRef1 = Class.forName("ch12.get_cls.MyClass");
        } catch (ClassNotFoundException e) {
    
            e.printStackTrace();
        }
        Class clsRef2 = MyClass.class;
        System.out.println(clsRef1 == clsRef2);
    }
}
// true

有两种方式可以获取Class对象引用:

  • 使用静态方法Class.forName,需要注意的是传入的类名必须是完整类名(包含包名)。
  • 使用类名+.class获取。

通过示例可以发现,用这两种方式尝试获取同一个类的Class对象引用,最后指向的是同一个Class对象。

类加载过程

实际上JVM在执行Java程序的时候,如果需要使用某个类,会执行以下操作:

  1. 加载,JVM的类加载器尝试从.class字节码文件加载类定义,并生成Class对象。
  2. 链接,如果类存在父类,尝试加载父类,并执行父类的类初始化工作。
  3. 初始化,如果需要,完成当前类的初始化工作。

执行这些工作后,一个类就是”真正可用“的了,可以用这个类创建实例或者访问属性。

forName与.class的差异

clsName.classClass.forName虽然都可以获取Class对象引用,但它们是有区别的:

package ch12.cls_init2;

class MyCls1 {
    
    static public int num = 47;
    static {
    
        System.out.println("MyCls1 is inited.");
    }
}

class MyCls2 {
    
    public static int num = 47;
    static {
    
        System.out.println("MyCls2 is inited.");
    }
}

public class Main {
    
    public static void main(String[] args) throws ClassNotFoundException {
    
        Class classRef1 = Class.forName("ch12.cls_init2.MyCls1");
        System.out.println("class1 ref is get.");
        Class classRef2 = MyCls2.class;
        System.out.println("class2 ref is get.");
        System.out.println(MyCls2.num);
    }
}
// MyCls1 is inited.
// class1 ref is get.
// class2 ref is get.
// MyCls2 is inited.
// 47

示例中,先尝试用Class.forName获取MyCls1Class对象引用,这立即触发了类的初始化。而之后通过MyCls2.class获取Class对象引用却并没有触发相应的类初始化,只有在之后访问类成员后才触发了类初始化。

由此可以看出,.class可以在不触发类初始化的情况下获取类的Class对象引用。这样做可以将可能的类初始化延后,对性能优化是有帮助的。

此外还应当注意到,Class.forName会产生一个”被检查异常“ClassNotFoundException,而.class则不会有类似的问题,这是因为后者可以在编译期完成检查,所以相比前者,使用起来更安全。

类成员访问与初始化

事实上访问类属性并不会一定触发类的初始化:

package ch12.cls_init;

import java.util.Random;

class MyCls1 {
    
    static final int num = 47;
    static {
    
        System.out.println("MyCls1 is inited.");
    }
}

class MyCls2 {
    
    static final int num = (new Random()).nextInt(100);
    static {
    
        System.out.println("MyCls2 is inited.");
    }
}

class MyCls3 {
    
    public final int num = 47;
    static {
    
        System.out.println("MyCls3 is inited.");
    }
}

public class Main {
    
    public static void main(String[] args) {
    
        Class MyCls1Class = MyCls1.class;
        Class MyCls2Class = MyCls2.class;
        Class MyCls3Class = MyCls3.class;
        System.out.println("class ref is geted.");
        System.out.println("access MyCls1's member.");
        System.out.println(MyCls1.num);
        System.out.println("access MyCls2's member.");
        System.out.println(MyCls2.num);
        System.out.println("access MyCls3's member.");
        MyCls3 mc = new MyCls3();
        System.out.println(mc.num);
    }
}
// class ref is geted.
// access MyCls1's member.
// 47
// access MyCls2's member.
// MyCls2 is inited.
// 56
// access MyCls3's member.
// MyCls3 is inited.
// 47

这个示例检验了三种情况:

  • 访问类的“编译时常量”属性
  • 访问类的非“编译时常量”属性
  • 访问对象属性

并非所有的static final声明的属性都是类的“编译时常量”属性,就像示例中MyCls2Class.num一样,该变量只有在运行时才会被初始化。

因为类的”编译时常量“属性在编译时就是一个确定值,并不依赖于类的初始化,所以在访问时不会触发类的初始化动作,而其余的成员访问都会先执行类的初始化动作(如果还没有初始化的话),然后再执行相关的属性访问。至于对象属性,显然必须先初始化类才能创建对象。

使用Class对象

使用Class对象可以打获取对应类的相关信息,比如类名:

package ch12.use_cls;

class MyCls {
    
}

public class Main {
    
    private static void printCls(Class cls) {
    
        System.out.println(cls.getName());
        System.out.println(cls.getSimpleName());
        System.out.println(cls.getCanonicalName());
    }

    public static void main(String[] args) {
    
        printCls(MyCls.class);
    }
}
// ch12.use_cls.MyCls
// MyCls
// ch12.use_cls.MyCls

更有用的是,可以通过Class对象获取到类的构造器,并用类构造器创建实例:

...
class MyCls {
    
    public MyCls() {
    
    }
}

public class Main {
    
	...
    private static Object createInstance(Class cls) throws InstantiationException, IllegalAccessException,
            IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
    
        Constructor c;
        c = cls.getConstructor();
        return c.newInstance();
    }

    public static void main(String[] args)
            throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
            NoSuchMethodException, SecurityException {
    
        printCls(MyCls.class);
        System.out.println(createInstance(MyCls.class));
    }
}
// ch12.use_cls2.MyCls
// MyCls
// ch12.use_cls2.MyCls
// ch12.use_cls2.MyCls@24d46ca6

需要注意的是,使用Class.getConstructor获取到的类构造器必须是类中确实定义了的构造器。如果开发者没有定义任何构造器,编译器会创建一个”默认构造器“,但这样的构造器无法通过Class.getConstructor获取,会产生一个NoSuchMethodException异常。

此外,还可以通过给getConstructor指定构造器的参数类型来获取到带特定参数的构造器:

...
class MyCls {
    
    public MyCls() {
    
    }

    public MyCls(int num) {
    
        Fmt.printf("MyCls(%d) is called.\n", num);
    }
}

public class Main {
    
	...
    private static MyCls createMyCls(int num) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
    
        Constructor<MyCls> c = MyCls.class.getConstructor(int.class);
        return c.newInstance(num);
    }

    public static void main(String[] args)
            throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
            NoSuchMethodException, SecurityException {
    
        printCls(MyCls.class);
        System.out.println(createInstance(MyCls.class));
        System.out.println(createMyCls(123));
    }
}
// ch12.use_cls2.MyCls
// MyCls
// ch12.use_cls2.MyCls
// ch12.use_cls2.MyCls@24d46ca6
// MyCls(123) is called.
// ch12.use_cls3.MyCls@6d311334

Class还有一个getConstructors方法,可以获取所有的构造器组成的数组,并且按定义的顺序排列,同样可以利用该方法调用需要的构造器:

...
public class Main {
    
	...
    private static MyCls createMyCls2(int num) throws NoSuchMethodException, SecurityException, InstantiationException,
            IllegalAccessException, IllegalArgumentException, InvocationTargetException {
    
        Constructor[] constructors = MyCls.class.getConstructors();
        return (MyCls) constructors[1].newInstance(num);
    }

    public static void main(String[] args)
            throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
            NoSuchMethodException, SecurityException {
    
        printCls(MyCls.class);
        System.out.println(createInstance(MyCls.class));
        System.out.println(createMyCls(123));
        System.out.println(createMyCls2(333));
    }
}
// ch12.use_cls2.MyCls
// MyCls
// ch12.use_cls2.MyCls
// ch12.use_cls2.MyCls@24d46ca6
// MyCls(123) is called.
// ch12.use_cls3.MyCls@6d311334
// MyCls(333) is called.
// ch12.use_cls4.MyCls@682a0b20

事实上还有一个Class.newInstance方法,可以简单地使用类的默认构造器创建实例并返回,但该方法已经被弃用。如果要使用默认构造器,应当使用getDeclaredConstructors,该方法返回的构造器中包含默认构造器。

还可以使用Class对象进行转型操作:

package ch12.use_cls5;

class MyCls{
    }

public class Main {
    
    public static void main(String[] args) {
    
        Object o = new MyCls();
        MyCls myCls = MyCls.class.cast(o);
        MyCls myCls2 = (MyCls)o;
    }
}

其效果与强制类型转换是一致的。

泛型

Class对象同样支持泛型,不使用泛型时,Class对象可以代表任意类型的Class对象,比如:

package ch12.generic;

class MyCls{
    }

public class Main {
    
    public static void main(String[] args) {
    
        Class cls = MyCls.class;
        cls = Object.class;
        cls = int.class;
    }
}

普通的Class对象实际上相当于Class<?>,两者效果相同:

package ch12.generic2;

class MyCls{
    }

public class Main {
    
    public static void main(String[] args) {
    
        Class<?> cls = MyCls.class;
        cls = Object.class;
        cls = int.class;
    }
}

但后者可以更明确地表示”你需要一个可以表示任意类型的Class对象“。

如果使用一个明确的类型作为泛型,可以让Class对象具备相应的静态类型检查功能,比如:

package ch12.generic3;

class MyCls{
    }

public class Main {
    
    public static void main(String[] args) {
    
        Class<MyCls> cls = MyCls.class;
        // cls = Object.class;
        // cls = int.class;
        
    }
}
// Type mismatch: cannot convert from Class<Object> to Class<MyCls>
// Type mismatch: cannot convert from Class<Integer> to Class<MyCls>

注释部分无法通过编译,使用泛型可以避免某些错误。

如果类涉及继承关系,同样可以用泛型进行表达:

package ch12.generic4;

class Parent {
    
}

class Child extends Parent {
    
}

public class Main {
    
    public static void main(String[] args) {
    
        Class<? extends Parent> cls;
        cls = Parent.class;
        cls = Child.class;
        Class<? super Child> cls2;
        cls2 = Parent.class;
        cls2 = Child.class.getSuperclass();
        cls2 = Child.class;
    }
}

Class<? extends Parent>表示这是一个Parent或其子类的Class对象。Class<? super Child>表示这是一个Child或其父类的Class对象。

使用过容器类泛型的开发者,可能会尝试用Class<Parent>来承接Child.class,但实际上这是无法通过编译的,原因是虽然ParentChild的基类,但Class<Parent>并非Class<Child>的基类。因此必须使用Class<? extends Parent>

instanceof

很多编程语言都支持instanceof或者类似的语法,以提供运行时类型检查。

下面通过一个例子来说明instanceof在Java中的用法:

假设有一个基于Animal的类继承层级:

package ch12.cls_counter;

public class Animal {
    
    @Override
    public String toString() {
    
        return this.getClass().getCanonicalName();
    }
}

class Cat extends Animal {
    
}

class Dog extends Animal {
    
}

class SportingDog extends Dog {
    
}

class WorkingDog extends Dog {
    
}

class HerdingDog extends Dog {
    
}

class PersianCat extends Cat {
    
}

class BirmanCat extends Cat {
    
}

设计一个类AnimalCreator用于批量随机生成以上类的实例:

package ch12.cls_counter;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

abstract public class AnimalCreator {
    
    private static Random rand = new Random();

    abstract protected List<Class<? extends Animal>> getAnimalTypes();

    private Animal randomAnimal() {
    
        int total = getAnimalTypes().size();
        if (total == 0) {
    
            return null;
        }
        try {
    
            return (Animal) getAnimalTypes().get(rand.nextInt(total)).getDeclaredConstructors()[0].newInstance();
        } catch (InstantiationException e) {
    
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
    
            throw new RuntimeException(e);
        } catch (IllegalArgumentException e) {
    
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
    
            throw new RuntimeException(e);
        } catch (SecurityException e) {
    
            throw new RuntimeException(e);
        }
    }

    public List<Animal> randomAnimals(int num) {
    
        List<Animal> animals = new ArrayList<>();
        for (int i = 0; i < num; i++) {
    
            animals.add(randomAnimal());
        }
        return animals;
    }
}

这个类是一个抽象类,有一个抽象方法getAnimalTypes提供用于生成Animal实例的类型。这是继上是模板方法模式的应用,不同的子类可以通过实现该方法以实现不同的随机创建实现。

这里提供一个RealCreator类实现:

package ch12.cls_counter;

import java.util.ArrayList;
import java.util.List;

public class RealCreator extends AnimalCreator {
    

    private static List<Class<? extends Animal>> animalTypes;
    private static String[] animalClsNames = new String[] {
    
            "ch12.cls_counter.Cat",
            "ch12.cls_counter.Dog",
            "ch12.cls_counter.SportingDog",
            "ch12.cls_counter.WorkingDog",
            "ch12.cls_counter.HerdingDog",
            "ch12.cls_counter.PersianCat",
            "ch12.cls_counter.BirmanCat"
    };
    static {
    
        loadAnimalTypes();
    }

    private static void loadAnimalTypes() {
    
        animalTypes = new ArrayList<>();
        for (String string : animalClsNames) {
    
            try {
    
                animalTypes.add((Class<? extends Animal>) Class.forName(string));
            } catch (ClassNotFoundException e) {
    
                throw new RuntimeException(e);
            }
        }
    }

    @Override
    protected List<Class<? extends Animal>> getAnimalTypes() {
    
        return animalTypes;
    }

}

该类使用一个类名字符串组成的类型列表,然后在初始化类时执行loadAnimalTypes静态方法以创建相应的Class对象列表,并以直接返回该列表的形式实现父类的抽象方法getAnimalTypes

最后创建一个用于统计随机创建的Animal实例的类:

package ch12.cls_counter;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class AnimalCounter {
    
    private Map<String, Integer> counter = new HashMap<>();
    private List<Animal> animals;

    public AnimalCounter(List<Animal> animals) {
    
        this.animals = animals;
    }

    private void countCls(String clsName) {
    
        Object value = counter.get(clsName);
        if (value == null) {
    
            counter.put(clsName, 1);
        } else {
    
            counter.put(clsName, (int) value + 1);
        }
    }

    public void count() {
    
        for (Animal animal : animals) {
    
            if (animal instanceof Animal) {
    
                countCls("Animal");
            }
            if (animal instanceof Cat) {
    
                countCls("Cat");
            }
            if (animal instanceof Dog) {
    
                countCls("Dog");
            }
            if (animal instanceof SportingDog) {
    
                countCls("SportingDog");
            }
            if (animal instanceof WorkingDog) {
    
                countCls("WorkingDog");
            }
            if (animal instanceof HerdingDog) {
    
                countCls("HerdingDog");
            }
            if (animal instanceof PersianCat) {
    
                countCls("PersianCat");
            }
            if (animal instanceof BirmanCat) {
    
                countCls("BirmanCat");
            }
        }
    }

    @Override
    public String toString() {
    
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for (Map.Entry<String, Integer> entry : counter.entrySet()) {
    
            sb.append(entry.getKey() + "=" + entry.getValue());
            sb.append(",");
        }
        sb.deleteCharAt(sb.length() - 1);
        sb.append("]");
        return sb.toString();
    }

}

因为Animal及其子类有3个继承层次,所以子类实例可能同时满足多个instanceof,这里用多个if语句进行判断。当然这样的设计是不好的,不仅显得笨拙,而且扩展性很差,每添加一个类型这里就可能需要添加一个if语句,稍后会改进这里的代码。

最后的测试语句很简单:

package ch12.cls_counter;

import java.util.List;

public class Main {
    
    public static void main(String[] args) {
    
        RealCreator rc = new RealCreator();
        List<Animal> animals = rc.randomAnimals(10);
        System.out.println(animals);
        AnimalCounter ac = new AnimalCounter(animals);
        ac.count();
        System.out.println(ac);
    }
}
// [ch12.cls_counter.WorkingDog, ch12.cls_counter.Dog, ch12.cls_counter.Dog,
// ch12.cls_counter.HerdingDog, ch12.cls_counter.BirmanCat,
// ch12.cls_counter.BirmanCat, ch12.cls_counter.BirmanCat,
// ch12.cls_counter.SportingDog, ch12.cls_counter.Dog, ch12.cls_counter.Cat]
// [Animal=10,WorkingDog=1,Cat=4,BirmanCat=3,SportingDog=1,Dog=6,HerdingDog=1]

改进后的代码

我们可以从两方面对代码进行改进:

  1. 直接使用.class,而不是Class.forName,前者可以提供编译时检查,这样可以避免额外的异常处理语句。
  2. 直接使用Class对象检查Animal实例的类型,以避免大量的if语句。

对于第一点,这里使用一个新的ClassCreator作为AnimalCreator的新实现:

package ch12.cls_counter2;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ClassCreator extends AnimalCreator {
    

    public static List<Class<? extends Animal>> animalTypes;
    private static List<Class<? extends Animal>> subTypes;
    static {
    
        loadAnimalTypes();
    }

    private static void loadAnimalTypes() {
    
        animalTypes = new ArrayList<>();
        Collections.addAll(animalTypes, Animal.class, Dog.class, Cat.class, SportingDog.class, WorkingDog.class,
                HerdingDog.class, PersianCat.class, BirmanCat.class);
        subTypes = animalTypes.subList(3, animalTypes.size());
    }

    @Override
    protected List<Class<? extends Animal>> getAnimalTypes() {
    
        return subTypes;
    }

}

这里animalTypes是完整的Animal及其子类Class对象集合,用于进行最终的类型统计。subTypes是前者的一个子集,用于随机生成Animal实例。

关于第二点,这里用一个新的AnimalCounter2来实现类型统计:

package ch12.cls_counter2;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class AnimalCounter2 {
    
    private Map<String, Integer> counter = new HashMap<>();
    private List<Animal> animals;
    private List<Class<? extends Animal>> animalTypes;

    public AnimalCounter2(List<Animal> animals, List<Class<? extends Animal>> animalTypes) {
    
        this.animals = animals;
        this.animalTypes = animalTypes;
    }

    private void countCls(String clsName) {
    
        Object value = counter.get(clsName);
        if (value == null) {
    
            counter.put(clsName, 1);
        } else {
    
            counter.put(clsName, (int) value + 1);
        }
    }

    public void count() {
    
        for (Animal animal : animals) {
    
            for (Class<? extends Animal> animalType : animalTypes) {
    
                if (animalType.isInstance(animal)){
    
                    countCls(animalType.getSimpleName());
                }
            }
        }
    }

    @Override
    public String toString() {
    
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for (Map.Entry<String, Integer> entry : counter.entrySet()) {
    
            sb.append(entry.getKey() + "=" + entry.getValue());
            sb.append(",");
        }
        sb.deleteCharAt(sb.length() - 1);
        sb.append("]");
        return sb.toString();
    }

}

AnimalCounter2直接接收一个Class对象集合作为统计依据,并在统计时依次调用Class对象的isInstance方法检查类型是否匹配,其作用与instanceof相同,但是避免了大量if语句的使用。

测试代码是相似的:

package ch12.cls_counter2;

import java.util.List;

class Animal{
    
    @Override
    public String toString() {
    
        return this.getClass().getCanonicalName();
    }
}
class Cat extends Animal{
    }
class Dog extends Animal{
    }
class SportingDog extends Dog{
    }
class WorkingDog extends Dog{
    }
class HerdingDog extends Dog{
    }
class PersianCat extends Cat{
    }
class BirmanCat extends Cat{
    }

public class Main {
    
    public static void main(String[] args) {
    
        AnimalCreator rc = new ClassCreator();
        List<Animal> animals = rc.randomAnimals(10);
        System.out.println(animals);
        AnimalCounter2 ac = new AnimalCounter2(animals, ClassCreator.animalTypes);
        ac.count();
        System.out.println(ac);
    }
}

使用递归进行类型统计

虽然上边的代码进行了一定程度的改进,但是依然需要维护统计所需的类型列表,并将其传递给统计代码。如果需要统计的类型列表发生变化,就要对列表进行相应的维护。

事实上可以给统计代码指定一个“基础的根类型”,然后在统计类型时依次检查当前类型和其父类型是否为“根类型”的子类型,如果是,就进行统计。这样做的好处是只要统计的类型都是根类型的子类型,即使我们添加了一些新类型,程序也无需做出任何改变。这无疑是在“可扩展性”上的一次进步。

package ch12.cls_counter3;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TypeCounter {
    
    private Map<Class<?>, Integer> counter = new HashMap<>();
    private Class<?> rootType;

    public TypeCounter(Class<?> rootType) {
    
        if (null == rootType) {
    
            throw new RuntimeException("param must not null.");
        }
        this.rootType = rootType;
    }

    public void count(List<?> objs) {
    
        for (Object object : objs) {
    
            Class<?> type = object.getClass();
            if (!this.rootType.isAssignableFrom(type)) {
    
                throw new RuntimeException(type.toString() + " is not a count target type.");
            }
            countType(type);
        }
    }

    @Override
    public String toString() {
    
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for (Map.Entry<Class<?>, Integer> entry : counter.entrySet()) {
    
            sb.append(entry.getKey().getSimpleName());
            sb.append("=");
            sb.append(entry.getValue());
            sb.append(", ");
        }
        sb.delete(sb.length() - 2, sb.length());
        sb.append("]");
        return sb.toString();
    }

    private void countType(Class<?> type) {
    
        doCount(type);
        Class<?> superCls = type.getSuperclass();
        if (superCls != null && rootType.isAssignableFrom(superCls)) {
    
            countType(superCls);
        }
    }

    private void doCount(Class<?> type) {
    
        int value = counter.getOrDefault(type, 0);
        counter.put(type, value + 1);
    }

}

测试:

...
public class Main {
    
    public static void main(String[] args) {
    
        AnimalCreator rc = new ClassCreator();
        List<Animal> animals = rc.randomAnimals(10);
        System.out.println(animals);
        TypeCounter tc = new TypeCounter(Animal.class);
        tc.count(animals);
        System.out.println(tc);
    }
}
// [ch12.cls_counter3.SportingDog, ch12.cls_counter3.SportingDog,
// ch12.cls_counter3.HerdingDog, ch12.cls_counter3.HerdingDog,
// ch12.cls_counter3.BirmanCat, ch12.cls_counter3.HerdingDog,
// ch12.cls_counter3.PersianCat, ch12.cls_counter3.SportingDog,
// ch12.cls_counter3.WorkingDog, ch12.cls_counter3.WorkingDog]
// [PersianCat=1, WorkingDog=2, Cat=2, SportingDog=3, Animal=10, HerdingDog=3,
// BirmanCat=1, Dog=8]

这样做还有一个好处:TypeCOunter具备相当的通用性,完全可以用这个类来统计任意的“类簇”。

注册工厂

Class对象批量创建对象存在一些限制,比如只能通过相应的构造器创建,以及需要处理获取Class对象或构造器时可能产生的异常等。

事实上,一般情况下创建对象的工作应当用“工厂模式”来实现,这里用“工厂模式”改写上边的例子:

package ch12.cls_counter4;

public interface AnimalFactory {
    
    Animal create();
}

为了避免添加太多的类,以内部类的形式添加相应的工厂类:

...
class SportingDog extends Dog {
    
    public static AnimalFactory factory = new AnimalFactory() {
    
        public Animal create() {
    
            return new SportingDog();
        };
    };
}

class WorkingDog extends Dog {
    
    public static AnimalFactory factory = new AnimalFactory() {
    
        public Animal create() {
    
            return new WorkingDog();
        };
    };
}

class HerdingDog extends Dog {
    
    public static AnimalFactory factory = new AnimalFactory() {
    
        public Animal create() {
    
            return new HerdingDog();
        };
    };
}

class PersianCat extends Cat {
    
    public static AnimalFactory factory = new AnimalFactory() {
    
        public Animal create(){
    
            return new PersianCat();
        }
    };
}

class BirmanCat extends Cat {
    
    public static AnimalFactory factory = new AnimalFactory() {
    
        public Animal create() {
    
            return new BirmanCat();
        };
    };
}

使用“注册工厂”实现Animal对象批量生产的FactoryCreator类:

package ch12.cls_counter4;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class FactoryCreator {
    
    private static Random rand = new Random();
    private List<AnimalFactory> factorys = new ArrayList<>();

    public List<Animal> randomAnimals(int num) {
    
        List<Animal> animals = new ArrayList<>();
        for (int i = 0; i < num; i++) {
    
            animals.add(randomAnimal());
        }
        return animals;
    }

    public void registeFactory(AnimalFactory factory) {
    
        factorys.add(factory);
    }

    public void registeAllFactory() {
    
        registeFactory(SportingDog.factory);
        registeFactory(WorkingDog.factory);
        registeFactory(HerdingDog.factory);
        registeFactory(PersianCat.factory);
        registeFactory(BirmanCat.factory);
    }

    private Animal randomAnimal() {
    
        if (factorys.size() <= 0) {
    
            return null;
        }
        int index = rand.nextInt(factorys.size());
        return factorys.get(index).create();
    }
}

测试:

package ch12.cls_counter4;

import java.util.List;

public class Main {
    
    public static void main(String[] args) {
    
        FactoryCreator fc = new FactoryCreator();
        fc.registeAllFactory();
        List<Animal> animals = fc.randomAnimals(10);
        System.out.println(animals);
        TypeCounter tc = new TypeCounter(Animal.class);
        tc.count(animals);
        System.out.println(tc);
    }
}
// [ch12.cls_counter4.SportingDog, ch12.cls_counter4.WorkingDog,
// ch12.cls_counter4.WorkingDog, ch12.cls_counter4.HerdingDog,
// ch12.cls_counter4.PersianCat, ch12.cls_counter4.SportingDog,
// ch12.cls_counter4.HerdingDog, ch12.cls_counter4.SportingDog,
// ch12.cls_counter4.SportingDog, ch12.cls_counter4.HerdingDog]
// [Dog=9, Cat=1, PersianCat=1, Animal=10, HerdingDog=3, SportingDog=4,
// WorkingDog=2]

反射

反射(reflect)简单地讲,就是在程序运行时获取“组件”的“细节“,很多编程语言都支持反射。

一般情况下我们是用不到反射的,因为所有的代码都是在”本地“编译和加载,所有的类型结构都是已知的,但某些时候我们需要在运行时获取某个对象的类型以及内部结构,比如通过RMI(Remote Method Invocation,远程方法调用)运行的程序,其一部分代码是在另外的机器上编译和运行的,如果要获得通过这种方式获取的对象的类型和结构,就需要使用反射。

Java的反射主要使用java.lang.reflect这个包,包括ConstructorMethod等代表代码结构的类。

下面用一个示例说明反射的作用,在这个示例中,将通过命令行参数指定一个类名,将获取该类的构造器和方法,并打印:

package ch12.reflect1;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.regex.Pattern;

public class Main {
    
    public static void main(String[] args) throws ClassNotFoundException {
    
        if (args.length <= 0) {
    
            System.out.println("Please enter params:[class_name <key_word>]");
        }
        String ClsName = args[0];
        String KeyWord = "";
        if (args.length >= 2) {
    
            KeyWord = args[1];
        }
        Class<?> cls = Class.forName(ClsName);
        Constructor[] constructors = cls.getConstructors();
        Pattern prefixPattern = Pattern.compile("\\w+\\.");
        for (Constructor constructor : constructors) {
    
            String funcSig = constructor.toString();
            funcSig = prefixPattern.matcher(funcSig).replaceAll("");
            if (KeyWord.length() == 0) {
    
                System.out.println(funcSig);
            } else if (funcSig.indexOf(KeyWord) != -1) {
    
                System.out.println(funcSig);
            } else {
    
                ;
            }
        }
        Method[] methods = cls.getMethods();
        for (Method method : methods) {
    
            String funcSig = method.toString();
            funcSig = prefixPattern.matcher(funcSig).replaceAll("");
            if (KeyWord.length() == 0) {
    
                System.out.println(funcSig);
            } else if (funcSig.indexOf(KeyWord) != -1) {
    
                System.out.println(funcSig);
            } else {
    
                ;
            }
        }
    }
}

class Test {
    
    public Test() {
    
    }

    public Test(int num) {
    
    }

    public void publicMethod() {
    
    };

    public static void publicStaticMethod() {
    
    }

    private void privateMethod() {
    
    };

    private static void privateStaticMethod() {
    
    }
}

测试结果:

❯ java Main.java ch12.reflect1.Test
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8
public Test()
public Test(int)
public void publicMethod()
public static void publicStaticMethod()
public final void wait(long,int) throws InterruptedException
public final void wait() throws InterruptedException
public final native void wait(long) throws InterruptedException
public boolean equals(Object)
public String toString()
public native int hashCode()
public final native Class getClass()
public final native void notify()
public final native void notifyAll()

可以看到反射依然遵循访问控制,我们通过反射获取到的方法只包含公共的构造器和方法。

此外,通过Consturcotr.toStringMethod.toString获取到的是包含返回值类型的方法签名,其中方法名包含完整包名和类名,代码中通过正则来去除前缀。

通过反射获取到的方法包含了从Object继承的方法。

除了通过反射打印代码结构,也可以编写程序读取源码,进行字符解析,但那样更为麻烦。反射相当于利用了语言本身的解析器。

动态代理

通过代理模式,可以以“不修改原有类型”为前提,给原类型添加一些额外特性。

关于代理模式的更多内容,可以阅读设计模式 with Python 11:代理模式 - 魔芋红茶’s blog (icexmoon.xyz)

下面看一个简单示例:

package ch12.proxy;

import java.util.Random;

import util.Fmt;

class OriginalClass {
    
    public void func1(int num) {
    
        Fmt.printf("OriginalClass.func1(%d) is called.\n", num);
    }
}

class OriginalClassProxy extends OriginalClass {
    
    private OriginalClass oc;

    public OriginalClassProxy(OriginalClass oc) {
    
        this.oc = oc;
    }

    @Override
    public void func1(int num) {
    
        Fmt.printf("OriginalClassProxy.func1(%d) is called.\n", num);
        oc.func1(num);
    }
}

public class Main {
    
    private static void testOriginalClass(OriginalClass oc, int num) {
    
        oc.func1(num);
    }

    public static void main(String[] args) {
    
        OriginalClass oc = new OriginalClass();
        Random rand = new Random();
        testOriginalClass(oc, rand.nextInt(100));
        testOriginalClass(new OriginalClassProxy(oc), rand.nextInt(100));
    }
}
// OriginalClass.func1(90) is called.
// OriginalClassProxy.func1(5) is called.
// OriginalClass.func1(5) is called.

利用反射机制和实现动态代理,这里用动态代理改写这个示例:

package ch12.proxy2;

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

import util.Fmt;

class OriginalClass {
    
    public void func1(int num) {
    
        Fmt.printf("OriginalClass.func1(%d) is called.\n", num);
    }

    public InvocationHandler getInvocationHandler() {
    
        return new InvocationHandler() {
    

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
                StringBuilder sb = new StringBuilder();
                for (Object arg : args) {
    
                    sb.append(arg.toString());
                    sb.append(", ");
                }
                if (sb.length() >= 2) {
    
                    sb.delete(sb.length() - 2, sb.length());
                }
                Fmt.printf("DynamicProxy.%s(%s) is called.", method.getName(), sb.toString());
                return method.invoke(OriginalClass.this, args);
            }

        };
    }
}

public class Main {
    
    private static void testOriginalClass(OriginalClass oc, int num) {
    
        oc.func1(num);
    }

    public static void main(String[] args) {
    
        OriginalClass oc = new OriginalClass();
        Random rand = new Random();
        testOriginalClass(oc, rand.nextInt(100));
        OriginalClass dynamicProxy = (OriginalClass) Proxy.newProxyInstance(OriginalClass.class.getClassLoader(),
                new Class<?>[] {
    },
                oc.getInvocationHandler());
        testOriginalClass(dynamicProxy, rand.nextInt(100));
    }
}

最关键的是使用Proxy.newProxyInstance在运行时创建动态代理,该方法接受三个参数:

  • 类加载器(Class Loader),代表动态代理的基类,可以通过Class.getClassLoader获取。
  • 动态代理需要实现的接口,用一个Class对象数组表示。
  • 调用器(Invocation Handler),实际负责处理动态代理转发的方法调用。

调用器需要实现InvocationHandler接口,这里通过一个匿名内部类来实现,这样做的好处是动态代理往往需要关联一个被代理的对象,而匿名内部类“天然”就关联其附属的类实例。

一切都看上去很棒,但实际上这段代码是无法正常运行的,如果尝试运行就会出现Exception in thread "main" java.lang.ClassCastException: class jdk.proxy1.$Proxy0 cannot be cast to class ch12.proxy2.OriginalClass这样的错误提示,这是因为虽然我们使用了OriginalClass.class.getClassLoader()作为类加载器,但可以看到,生成的动态代理实例的实际类型依然是jdk.proxy1.$Proxy0,而非我们期望的OriginalClass,所以是无法转换为我们期望的类型的。

要修复这个问题,就需要使用接口:

...
interface OriginalInterface {
    
    void func1(int num);
}

class OriginalClass implements OriginalInterface {
    
	...
}

public class Main {
    
	...
	public static void main(String[] args) {
    
	...
	OriginalInterface dynamicProxy = (OriginalInterface) Proxy.newProxyInstance(OriginalClass.class.getClassLoader(),
                new Class<?>[] {
     OriginalInterface.class },
                oc.getInvocationHandler());
        testOriginalClass(dynamicProxy, rand.nextInt(100));
    }
}
// OriginalClass.func1(64) is called.
// DynamicProxy.func1(15) is called.
// OriginalClass.func1(15) is called.

这样就可以正常运行了。

空对象

虽然可以在编程的时候用null来表示一个“空对象”,但这意味着程序中需要频繁地通过==null来判断对象是否为空,并作出相应处理。有时候我们可以用一个空对象(Null Object)来取代null,这样做的好处是可以避免使用大量的==null判断语句。

下面看一个简单示例:

package ch12.null_object;

class Room {
    
    private String number = "";
    private Student[] beds = new Student[4];

    public Room(String number) {
    
        this.number = number;
    }

    public boolean addStudent(Student student) {
    
        for (int i = 0; i < beds.length; i++) {
    
            if (beds[i] == null) {
    
                beds[i] = student;
                return true;
            }
        }
        return false;
    }

    @Override
    public String toString() {
    
        StringBuilder sb = new StringBuilder();
        sb.append(number);
        sb.append("[");
        String splite = ", ";
        for (Student student : beds) {
    
            if (student == null) {
    
                sb.append("empty");
            } else {
    
                sb.append(student.getName());
            }
            sb.append(splite);
        }
        sb.delete(sb.length() - splite.length(), sb.length());
        sb.append("]");
        return sb.toString();
    }
}

class Student {
    
    private String name;

    public Student(String name) {
    
        this.name = name;
    }

    public String getName() {
    
        return name;
    }
}

public class Main {
    
    public static void main(String[] args) {
    
        Room room = new Room("404");
        room.addStudent(new Student("Li Lei"));
        System.out.println(room);
    }
}
// 404[Li Lei, empty, empty, empty]

这个示例中,Room表示一个宿舍,Student表示学生,Room中的Student数组beds表示为学生分配的床位。因为一个宿舍中是固定的4个床位,所以初始状态用null来占位。相应的,在toStringaddStudent方法中,用检测是否床位为null来处理床位为空的情况。

下面用“空对象”来改写这个示例:

package ch12.null_object2;

class Room {
    
    private String number = "";
    private Student[] beds = new Student[4];
    {
    
        for (int i = 0; i < beds.length; i++) {
    
            beds[i] = Student.NULL;
        }
    }

    public Room(String number) {
    
        this.number = number;
    }

    public boolean addStudent(Student student) {
    
        for (int i = 0; i < beds.length; i++) {
    
            if (beds[i] == Student.NULL) {
    
                beds[i] = student;
                return true;
            }
        }
        return false;
    }

    @Override
    public String toString() {
    
        StringBuilder sb = new StringBuilder();
        sb.append(number);
        sb.append("[");
        String splite = ", ";
        for (Student student : beds) {
    
            sb.append(student.getName());
            sb.append(splite);
        }
        sb.delete(sb.length() - splite.length(), sb.length());
        sb.append("]");
        return sb.toString();
    }
}

class Student {
    
    private String name;
    public static final Student NULL = new Student("empty");
	...
}
...

这里用一个Student的类属性NULL来表示Student的空对象,因为空对象一般都是单例,所以用final进行修饰。在Room中,我们在beds数组创建后在初始化块中用空对象Student.NULL进行了初始化。因此初始数组中并不包含null,所以在toString方法中删除了==null的相关语句。对于空对象,同样可以用.getName获取相应的名称(empty)。

addStudent中,检查床位为空,只要使用==Student.NULL进行判断就可以了,因为空对象是单例,这么检测是没有问题的。

实际上这里是有漏洞的,客户端程序是可以通过.addStudent(null)的方式添加nullRoom对象中的,这样可能造成程序运行出错,可以通过检查参数和抛出异常的方式来修复这个问题。

因为示例代码规模很小,所以这里并不能显示出使用空对象的优势,但如果涉及复杂的持有对象的代码,空对象可能对降低代码复杂度有相当程度的好处。

此外,因为这里的空对象实现相当容易,所以用Student NULL = new Student("empty")的方式实现,如果空对象实现比较复杂,可以单独用一个Student的子类NullStudent来实现。

使用动态代理实现空对象

如果有大量的类需要实现空对象,我们可以利用动态代理来简化代码:

package ch12.null_object3;

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

class Room {
    
    private String number = "";
    private NameAble[] beds = new NameAble[4];
    {
    
        for (int i = 0; i < beds.length; i++) {
    
            beds[i] = Student.NULL;
        }
    }

    public Room(String number) {
    
        this.number = number;
    }

    public boolean addStudent(Student student) {
    
        for (int i = 0; i < beds.length; i++) {
    
            if (beds[i] instanceof NULL) {
    
                beds[i] = student;
                return true;
            }
        }
        return false;
    }

    public boolean setStudent(int index, NameAble student) {
    
        NameAble bed = beds[index];
        if (bed instanceof NULL) {
    
            beds[index] = student;
            return true;
        }
        return false;
    }

    @Override
    public String toString() {
    
        StringBuilder sb = new StringBuilder();
        sb.append(number);
        sb.append("[");
        String splite = ", ";
        for (NameAble bed : beds) {
    
            sb.append(bed.getName());
            sb.append(splite);
        }
        sb.delete(sb.length() - splite.length(), sb.length());
        sb.append("]");
        return sb.toString();
    }
}

interface NameAble {
    
    public String getName();
}

interface NULL {
    
}

class Student implements NameAble {
    
    private String name;
    public static final NameAble NULL = getNullStudent(Student.class);

    public Student(String name) {
    
        this.name = name;
    }

    public String getName() {
    
        return name;
    }

    public static NameAble getNullStudent(Class<?> type) {
    
        return (NameAble) Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[] {
     NameAble.class, NULL.class },
                new StudentInvocationHandler(type));
    }
}

class GoodStudent extends Student {
    
    public static final NameAble NULL = getNullStudent(GoodStudent.class);

    public GoodStudent(String name) {
    
        super(name);
    }
}

class BadStudent extends Student {
    
    public static final NameAble NULL = getNullStudent(BadStudent.class);

    public BadStudent(String name) {
    
        super(name);
    }
}

class NormalStudetn extends Student {
    
    public static final NameAble NULL = getNullStudent(NormalStudetn.class);

    public NormalStudetn(String name) {
    
        super(name);
    }
}

class StudentInvocationHandler implements InvocationHandler {
    
    private Class<?> type;

    public StudentInvocationHandler(Class<?> type) {
    
        this.type = type;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
        if (method.getName() == "getName") {
    
            if (!Student.class.isAssignableFrom(type)) {
    
                return "Unknow class";
            } else if (BadStudent.class.isAssignableFrom(type)) {
    
                return "BadStudetn emtpy";
            } else if (GoodStudent.class.isAssignableFrom(type)) {
    
                return "GoodStudent empty";
            } else if (NormalStudetn.class.isAssignableFrom(type)) {
    
                return "NormalStudent empty";
            } else {
    
                return "Studetn empty";
            }
        }
        return null;
    }

}

public class Main {
    
    public static void main(String[] args) {
    
        Room room = new Room("404");
        room.addStudent(new Student("Li Lei"));
        System.out.println(room);
        room.setStudent(1, GoodStudent.NULL);
        room.setStudent(2, BadStudent.NULL);
        room.setStudent(3, NormalStudetn.NULL);
        System.out.println(room);
        room.addStudent(new Student("Han Meimei"));
        System.out.println(room);
    }
}
// 404[Li Lei, Studetn empty, Studetn empty, Studetn empty]
// 404[Li Lei, GoodStudent empty, BadStudetn emtpy, NormalStudent empty]
// 404[Li Lei, Han Meimei, BadStudetn emtpy, NormalStudent empty]

这里用动态代理,同时为Student及其三个子类分别实现了空对象,这些空对象的行为差异由调用处理器StudentInvocationHandler类来处理。

因为Room需要同时处理多个类型的空对象,所以这里让所有空对象都实现NULL接口,用这个接口表示这是一个空对象,需要检查对象是否为空对象的时候只要使用instanceof NULL即可,这种方式比让空对象实现isNULL方法更简单。

反射的隐患

反射无疑是一种强大和有用的特性,但同时也会带来一些隐患,下面用一些示例进行说明。

在Java中,接口(interface)用于代码解耦,我们通常期待客户端程序仅使用接口暴露的部分功能,但事实上客户端程序可以通过向下转型来获取额外功能:

package ch12.bad;

import util.Fmt;

interface NameAble {
    
    String getName();
}

class Person implements NameAble {
    
    private String name;

    public Person(String name) {
    
        this.name = name;
    }

    @Override
    public String getName() {
    
        return name;
    }

    public void walk() {
    
        Fmt.printf("%s is walking.", name);
    }

}

class School {
    
    public static NameAble getNameAble() {
    
        return new Person("New Employee");
    }
}

public class Main {
    
    public static void main(String[] args) {
    
        NameAble n = School.getNameAble();
        Person p = (Person) n;
        p.walk();
    }
}
// New Employee is walking.

这个问题可以通过访问权限来解决,比如将Student设置为包访问权限,这样包外就无法访问Student,类似的,在同一个包内还可以将Student设置为私有的内部类来限制访问:

package ch12.bad2;

import util.Fmt;

interface NameAble {
    
    String getName();
}

class School {
    
    public static NameAble getNameAble() {
    
        return new Person("New Employee");
    }

    private static class Person implements NameAble {
    
        private String name;

        public Person(String name) {
    
            this.name = name;
        }

        @Override
        public String getName() {
    
            return name;
        }

        public void walk() {
    
            Fmt.printf("%s is walking.", name);
        }

    }
}

public class Main {
    
    public static void main(String[] args) {
    
        NameAble n = School.getNameAble();
        System.out.println(n.getName());
        // Person p = (Person) n;
        // p.walk();
        // Person cannot be resolved to a type
    }
}
// New Employee

注释部分的代码无法运行,因为PersonSchool的私有内嵌类,除了School,其它作用域都无法访问。

这样看起来不错,很安全,但其实依然是一种假象,使用反射机制可以轻松突破这种限制:

...
public class Main {
    
    public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchFieldException {
    
        NameAble n = School.getNameAble();
        System.out.println(n.getName());
        Method method = n.getClass().getMethod("walk");
        method.invoke(n);
        Field name = n.getClass().getDeclaredField("name");
        name.setAccessible(true);
        name.set(n, "Li Lei");
        method.invoke(n);
    }
}
// New Employee
// New Employee is walking.
// Li Lei is walking.

使用反射,不仅可以调用School的私有类型Personwalk方法,甚至可以通过getDeclaredField获取到其私有属性name,并在使用setAccessible方法修改访问权限后使用set方法修改其值。

类似的,可以使用getDeclaredMethod获取到私有方法并进行调用。

因此,在Java中,访问控制并不是绝对限制,事实上可以通过反射来突破这种限制。这和Python是类似的,在Python中,以___开头的属性和方法是私有或被保护的,但是实际上依然是可以通过某种方式来进行访问。

访问控制的意义在于,这是一种包开发者和客户端开发者的约定,包开发者会认为客户端开发者会遵守这种约定而不是通过反射来破坏,同样的,通过反射这种非正常方式使用包代码,不会享受到包开发者“安全升级”包代码的保护,和可能在包代码升级后而影响到现有代码的正常运行。

谢谢阅读。

参考资料

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/hy6533/article/details/122828503

智能推荐

微信小程序开发——字符串转date对象转时间戳 ios显示NaN(踩坑记录)_微信小程序日期文本转时间对象-程序员宅基地

文章浏览阅读1.5k次,点赞2次,收藏3次。问题描述:数据库中存的日期是字符串,yyyyMMdd格式取出后转为"yyyy-MM-dd 00:00:00:000"格式之后date = new Date(yyyy-MM-dd 00:00:00:000)之后转时间戳 date.getTime()。用这个时间戳计算得到一整周的日期数据,并显示在页面中。在微信开发者工具和安卓手机中日期均正常显示,但苹果手机显示NaN。解决办法..._微信小程序日期文本转时间对象

Android studio3.1 汉化_android+studio+3.1汉化-程序员宅基地

文章浏览阅读4.4k次。 下载资源:https://download.csdn.net/download/u014549283/10608422或者 百度云链接 密码 3y9k _android+studio+3.1汉化

Solidworks踩坑随笔_安装管理程序不能核实此服务器存在: 25734@localhost。确定您正确键入了名称。您-程序员宅基地

文章浏览阅读1.9w次,点赞3次,收藏22次。Solidworks无法打开问题终极解决办法网上流传的solidworks无法打开的解决办法有如下几种:使用solidworks安装包自带的修复工具修复缺点:耗费时间长,而且不一定能找到原来下载的安装包,况且不能根除此问题使用_SolidSQUAD_中的文件替换,之后运行server_remove.bat和server_install.bat脚本文件重新安装flexnet_server..._安装管理程序不能核实此服务器存在: 25734@localhost。确定您正确键入了名称。您

Beego学习笔记:ORM在MySQL生成表的过程_orm框架类转化为表-程序员宅基地

文章浏览阅读706次。个人github(包括golang学习笔记、源码):https://github.com/fangguizhen/Notes/blob/master/Golang%E7%9F%A5%E8%AF%86%E7%82%B9.md前期安装:MySQL驱动go get github.com/go-sql-driver/mysql介绍:Beego中内嵌了ORM框架,它可以将结构体和数据..._orm框架类转化为表

应运而生! 双11当天处理数据5PB—HiStore助力打造全球最大列存储数据库-程序员宅基地

文章浏览阅读134次。2019独角兽企业重金招聘Python工程师标准>>> ..._histore助力打造

Oracle备份的几种方式_oracle数据库备份-程序员宅基地

文章浏览阅读1.2w次,点赞17次,收藏106次。Oracle备份的几种方式这里使用Oracle 12C来大概演示说明一下rman的基本用法,这里不会深入讨论,因为本人也只是刚刚才接触,只是结合了网上的一些文章以及自己的实践来总结并拿出来大家学习,谢谢目录一、关于备份与恢复二、逻辑备份(expdp和impdp)三、物理备份四、数据库日常备份计划及脚本参考一、关于备份与恢复1、备份定义备份就是把数据库复制到转储设备的过程。其中,转储设备是指用于放置数据库副本的磁带或磁盘。通常也将存放于转储设备中的数据库的副本称为原数据库的备份或转储。备_oracle数据库备份

随便推点

java安装步骤及dos命令_dos命令行重装java指令-程序员宅基地

文章浏览阅读203次。一.常用的dos命令打开dos窗口的方式:window+r|开始->cmd1.切换盘符: 目标盘符: 大小写都可以2.切换路径: cd 路径 相对路径|绝对路径 如果不同盘符下的路径切换需要手动切换盘符3.回到上一层路径: cd.. 4.回到盘符跟路径: cd\5.罗列出当前路径下的所有子文件|子文件夹: dir6.自动补全: tab7.查找使用过的命令: 方向上下..._dos命令行重装java指令

Module build failed: TypeError: Cannot read properties of undefined (reading ‘toString‘)-程序员宅基地

文章浏览阅读3.7k次。Bilibili—狂神说Vue快速入门一、错误视频教程中,P16的代码,创建并写好Login.vue,输入npm run dev 运行报错。Module build failed: TypeError: Cannot read properties of undefined (reading ‘toString’)二、原因百度和谷歌了半天,没有找到解决方法。翻了翻bilibili评论区,看到有人提示到 lang=scss。删除 lang=“scss” 后,运行成功。三_module build failed: typeerror: cannot read property 'tostring' of undefined

GO学习之 协程(goroutine)_go 协程-程序员宅基地

文章浏览阅读920次。在 Go 语言中,goroutine 是一种非常轻量的执行单元,有 Go 运行是(runtime)进行调度,不是有固定大小的线程来处理的。与传统线程相比,goroutine的创建和切换开销很小,因此可以创建大量的 goroutine 来并行执行任务,而不会造成太大的系统负担。* goroutine 更像是一种高效的协程,它在并发编程中提供了轻量且较高的方式来处理并发,而不需要显式地创建和管理线程池。_go 协程

Linux下解决Redis安装时的编译报错问题_make[1]: *** [makefile:403: adlist.o] error 1-程序员宅基地

文章浏览阅读983次,点赞5次,收藏7次。1.报错:“cc”:未找到命令[root@server2 redis-5.0.3]# make解决办法如下: Linux环境安装gcc。[root@server1 redis-5.0.3]# yum install gcc -y2.报错:make[1]:***[adlist.o]错误1解决办法如下:[root@server1 redis-5.0.3]# make C..._make[1]: *** [makefile:403: adlist.o] error 1

基于MRTK的HoloLens开发(1)_mrtk项目报告-程序员宅基地

文章浏览阅读2.7k次,点赞3次,收藏16次。基于MRTK的HoloLens开发(1)Hololens开发环境配置Visual Studio具体配置MRTK 工具包配置Unity配置如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流..._mrtk项目报告

最新VMware虚拟机安装Linux系统-CentOS(详细教程)_vmware liunx centeros-程序员宅基地

文章浏览阅读985次,点赞24次,收藏16次。最近有网友反应初学Linx不会安装,找了许多教程不是太全面,总会遇到一些要不是启动不了,要不是连不上网,各种问题,为了让大家能够顺利的安装,小乐写了一个非常详细的教程,让大家少入坑。_vmware liunx centeros

推荐文章

热门文章

相关标签