在Java中创建泛型类型的实例?


576

是否可以在Java中创建泛型类型的实例?我正在根据我所看到的答案进行思考no由于类型删除),但是如果有人能看到我所缺少的内容,我将很感兴趣:

class SomeContainer<E>
{
    E createContents()
    {
        return what???
    }
}

编辑:事实证明 超级类型令牌可以用来解决我的问题,但是它需要很多基于反射的代码,如下面的一些答案所示。

我将对此开放一会儿,看看是否有人提出与Ian Robertson的Artima Article截然不同的东西


2
刚刚在Android设备上测试了性能。10000次操作,并且:8-9 ms花费了新的SomeClass(),9-11 ms花费了Factory <SomeClass> .createInstance(),而64-71 ms花费了最短的反射:SomeClass z = SomeClass.class.newInstance()。并且所有测试都在单个try-catch块中。反射newInstance()抛出4个不同的异常,还记得吗?因此,我决定使用工厂模式
Deepscorn


4
使用Java 8,您现在可以传递一个构造函数引用或一个lambda,这使得解决此问题变得很简单。请参阅下面的详细信息。
丹尼尔·普赖登

我认为编写这样的代码不是一个好主意,解决底层问题的方式更优雅,更易读。
Krzysztof Cichocki

1
@DavidCitron “有一阵子”他说...从那以后已经十一年了……
MC Emperor

Answers:


332

你是对的。你做不到new E()。但您可以将其更改为

private static class SomeContainer<E> {
    E createContents(Class<E> clazz) {
        return clazz.newInstance();
    }
}

真痛苦 但这有效。以工厂模式包装它会使它更具容忍性。


11
是的,我看到了该解决方案,但是只有当您已经具有要实例化类型的Class对象的引用时,该解决方案才有效。
David Citron

11
是的,我知道。如果您可以进行E.class,那会很好,但是由于删除,它只会给您Object.class :)
Justin Rudd

6
这是解决此问题的正确方法。通常不是您想要的,而是您得到的。
约阿希姆·绍尔

38
以及如何调用createContents()方法?
亚历克西斯·杜弗洛尼

8
这不再是唯一的方法,现在有了更好的方法,不需要Class<?>使用Guava和TypeToken 传递引用,有关代码和链接

129

Dunno(如果有帮助),但是当您继承(包括匿名)泛型类型时,可以通过反射获得类型信息。例如,

public abstract class Foo<E> {

  public E instance;  

  public Foo() throws Exception {
    instance = ((Class)((ParameterizedType)this.getClass().
       getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
    ...
  }

}

因此,当您将Foo子类化时,您将获得Bar的实例,例如,

// notice that this in anonymous subclass of Foo
assert( new Foo<Bar>() {}.instance instanceof Bar );

但这是很多工作,并且仅适用于子类。虽然可以方便。


2
是的,这很好,特别是如果泛型类是抽象的,则可以在具体的子类中实现:)
Pierre Henry

如果类Foo不是抽象的,则此方法也适用。但是为什么只对Foo的匿名子类起作用?假设我们Foo具体化了(我们省略了abstract),为什么会new Foo<Bar>();导致错误,而new Foo<Bar>(){};不会呢?(例外:“无法将类
强制转换

2
@TimKuipers <E>in class Foo<E>没有绑定到任何特定类型。你会看到每当异常行为E是不是静态的约束,如:new Foo<Bar>()new Foo<T>() {...},或class Fizz <E> extends Foo<E>。第一种情况不是静态绑定的,而是在编译时擦除的。第二种情况用另一个类型变量(T)代替,E但仍未绑定。在最后一种情况下,显然E仍然没有约束。
威廉·普赖斯

4
静态绑定type参数的示例是class Fizz extends Foo<Bar>-在这种情况下,用户Fizz获得的内容是a,Foo<Bar>而不能是a Foo<Bar>。因此,在这种情况下,编译器很乐意将该信息编码为该类的元数据,Fizz并使其可作为ParameterizedType反射代码使用。当您创建匿名内部类时,就像new Foo<Bar>() {...}在做相同的事情,除了Fizz编译器会生成一个“匿名”类名,直到外部类被编译,您才知道它。
威廉·普赖斯

4
请注意,如果类型参数也是ParameterizedType,则此方法将无效。例如,Foo<Bar<Baz>>。您将创建一个ParameterizedTypeImpl无法显式创建的实例。因此,最好检查是否getActualTypeArguments()[0]返回ParameterizedType。如果是,那么您想要获取原始类型,并创建该实例的实例。
暗恋

106

在Java 8中,您可以使用Supplier功能接口轻松实现此目的:

class SomeContainer<E> {
  private Supplier<E> supplier;

  SomeContainer(Supplier<E> supplier) {
    this.supplier = supplier;
  }

  E createContents() {
    return supplier.get();
  }
}

您将像这样构造此类:

SomeContainer<String> stringContainer = new SomeContainer<>(String::new);

String::new行的语法是构造函数参考

如果构造函数接受参数,则可以使用lambda表达式代替:

SomeContainer<BigInteger> bigIntegerContainer
    = new SomeContainer<>(() -> new BigInteger(1));

6
好一个 它避免了反思,而不得不对待异常。
Bruno Leite

2
太赞了。不幸的是,对于Android用户,这要求API级别为24或更高。
Michael Updike '18

1
根据较少的大惊小怪和实用的方法,我认为这是可以接受的答案
Tomas

2
…与这个更老的答案没有什么不同,表明它背后的技术模式甚至比Java对lambda表达式和方法引用的支持还要老,而在升级编译器后,您甚至可以将这些更老的代码与它们一起使用……
Holger

可以放公正SomeContainer stringContainer = new SomeContainer(String::new);吗?
亚伦弗兰克

87

您将需要某种某种类型的抽象工厂,以将责任传递给:

interface Factory<E> {
    E create();
}

class SomeContainer<E> {
    private final Factory<E> factory;
    SomeContainer(Factory<E> factory) {
        this.factory = factory;
    }
    E createContents() {
        return factory.create();
    }
}

..以及Factory.create()的外观如何?
OhadR

6
@OhadR Factory<>是一个接口,因此没有正文。关键是您需要一个间接层来将buck传递给“知道”构造实例所需的代码的方法。用普通代码执行此操作要好得多,而不要使用元语言方法,Class否则Constructor反射会给整个世界带来伤害。
Tom Hawtin-定位线

2
现在,您可以使用以下方法引用表达式创建工厂实例:SomeContainer<SomeElement> cont = new SomeContainer<>(SomeElement::new);
Lii

24
package org.foo.com;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/**
 * Basically the same answer as noah's.
 */
public class Home<E>
{

    @SuppressWarnings ("unchecked")
    public Class<E> getTypeParameterClass()
    {
        Type type = getClass().getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) type;
        return (Class<E>) paramType.getActualTypeArguments()[0];
    }

    private static class StringHome extends Home<String>
    {
    }

    private static class StringBuilderHome extends Home<StringBuilder>
    {
    }

    private static class StringBufferHome extends Home<StringBuffer>
    {
    }   

    /**
     * This prints "String", "StringBuilder" and "StringBuffer"
     */
    public static void main(String[] args) throws InstantiationException, IllegalAccessException
    {
        Object object0 = new StringHome().getTypeParameterClass().newInstance();
        Object object1 = new StringBuilderHome().getTypeParameterClass().newInstance();
        Object object2 = new StringBufferHome().getTypeParameterClass().newInstance();
        System.out.println(object0.getClass().getSimpleName());
        System.out.println(object1.getClass().getSimpleName());
        System.out.println(object2.getClass().getSimpleName());
    }

}

3
如果在泛型中使用泛型类型,则此代码的正确方法可能会导致ClassCastException。然后,您将检索actualType参数,应检查它是否也是ParamterizedType,如果是,则返回其RawType(或比它更好的值)。与此相关的另一个问题是,当我们扩展更多内容时,一旦此代码也将抛出ClassCastExeption。
DamianLeszczyński-Vash

3
由以下原因引起:java.lang.ClassCastException:sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl无法转换为java.lang.Class
2012年

@DamianLeszczyński-Vash也将失败,例如class GenericHome<T> extends Home<T>{}
Holger

22

如果需要在泛型类中使用类型参数的新实例,则使构造函数需要其类...

public final class Foo<T> {

    private Class<T> typeArgumentClass;

    public Foo(Class<T> typeArgumentClass) {

        this.typeArgumentClass = typeArgumentClass;
    }

    public void doSomethingThatRequiresNewT() throws Exception {

        T myNewT = typeArgumentClass.newInstance();
        ...
    }
}

用法:

Foo<Bar> barFoo = new Foo<Bar>(Bar.class);
Foo<Etc> etcFoo = new Foo<Etc>(Etc.class);

优点:

  • 比罗伯逊的超级类型令牌(STT)方法简单得多(且问题更少)。
  • 比STT方法(早餐时会吃掉手机)要高效得多。

缺点:

  • 无法将Class传递给默认构造函数(这就是Foo是最终的原因)。如果确实需要默认构造函数,则可以随时添加setter方法,但必须记住稍后再给她打电话。
  • 罗伯逊的异议...比“败类”多得多的杠铃(尽管再指定一次类型实参类不会完全杀死您)。与罗伯逊的主张相反,无论如何这都不违反DRY主体,因为编译器将确保类型正确。
  • 不能完全Foo<L>证明。对于初学者...newInstance()如果类型实参类没有默认构造函数,则将引发摆动。无论如何,这确实适用于所有已知的解决方案。
  • 缺乏STT方法的整体封装。不过,这没什么大不了的(考虑到STT的惊人性能开销)。

22

您现在可以执行此操作,并且不需要一堆反射代码。

import com.google.common.reflect.TypeToken;

public class Q26289147
{
    public static void main(final String[] args) throws IllegalAccessException, InstantiationException
    {
        final StrawManParameterizedClass<String> smpc = new StrawManParameterizedClass<String>() {};
        final String string = (String) smpc.type.getRawType().newInstance();
        System.out.format("string = \"%s\"",string);
    }

    static abstract class StrawManParameterizedClass<T>
    {
        final TypeToken<T> type = new TypeToken<T>(getClass()) {};
    }
}

当然,如果您需要调用需要进行一些反思的构造函数,但是有据可查,该技巧并非如此!

这是TypeTokenJavaDoc


6
此解决方案仅适用于少数情况,就像@noah的反射式回答一样。今天我全部尝试了这些方法……最后,我将参数类的实例传递给参数化的类(以便能够调用.newInstance())。新的Foo <Bar>(Bar.class); ... class Foo <T> {私有最终Class <T> mTFactory; Foo(Class <T> tClass){mTFactory = tClass; ...} T instance = tFactory.newInstance(); }
yvolk'3

这在所有情况下都有效,即使采用通用参数的静态工厂方法也是如此

13

考虑一种更实用的方法:不要无所事事地创建一些E(这显然是一种代码味道),而是传递一个知道如何创建一个E的函数,即

E createContents(Callable<E> makeone) {
     return makeone.call(); // most simple case clearly not that useful
}

6
从技术上讲,您不是在传递函数,而是在传递函数对象(也称为函子)。
Lorne Laliberte 2015年

3
或者也许是克服捕获Exception用途Supplier<E>
马丁D

10

Java教程-泛型限制

无法创建类型参数的实例

您不能创建类型参数的实例。例如,以下代码会导致编译时错误:

public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}

解决方法是,可以通过反射创建类型参数的对象:

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // OK
    list.add(elem);
}

您可以按以下方式调用append方法:

List<String> ls = new ArrayList<>();
append(ls, String.class);

6

这是我想出的一个选项,可能会有所帮助:

public static class Container<E> {
    private Class<E> clazz;

    public Container(Class<E> clazz) {
        this.clazz = clazz;
    }

    public E createContents() throws Exception {
        return clazz.newInstance();
    }
}

编辑:或者,您可以使用此构造函数(但它需要E的实例):

@SuppressWarnings("unchecked")
public Container(E instance) {
    this.clazz = (Class<E>) instance.getClass();
}

是的,即使没有泛型,其工作原理也一样-使用泛型,此容器的实例化变得有点多余(您必须指定两次“ E”)。
David Citron

好吧,这就是当您使用Java和泛型时发生的事情……它们并不漂亮,并且存在严重的局限性……
Mike Stone

6

如果您不想在实例化过程中两次键入类名,例如:

new SomeContainer<SomeType>(SomeType.class);

您可以使用工厂方法:

<E> SomeContainer<E> createContainer(Class<E> class); 

像:

public class Container<E> {

    public static <E> Container<E> create(Class<E> c) {
        return new Container<E>(c);
    }

    Class<E> c;

    public Container(Class<E> c) {
        super();
        this.c = c;
    }

    public E createInstance()
            throws InstantiationException,
            IllegalAccessException {
        return c.newInstance();
    }

}

6

不幸的是,Java不允许您要执行的操作。请参阅官方解决方法

您不能创建类型参数的实例。例如,以下代码会导致编译时错误:

public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}

解决方法是,可以通过反射创建类型参数的对象:

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // OK
    list.add(elem);
}

您可以按以下方式调用append方法:

List<String> ls = new ArrayList<>();
append(ls, String.class);

您能告诉我为什么要这么做吗?我不明白为什么官方的解决方法不是一个好的解决方案。谢谢。
Neepsnikeep

我猜您会被否决,因为您的回答与贾斯汀·陆克文(Justin Rudd)的回答基本相同:stackoverflow.com/a/75254/103412
Torsten

5

当您在编译时使用E时,您实际上并不在乎实际的泛型类型“ E”(您使用反射还是使用泛型类型的基类),因此让子类提供E的实例。

Abstract class SomeContainer<E>
{

    abstract protected  E createContents();
    public doWork(){
        E obj = createContents();
        // Do the work with E 

     }
}


BlackContainer extends SomeContainer<Black>{
    Black createContents() {
        return new  Black();
    }
}

4

您可以使用:

Class.forName(String).getConstructor(arguments types).newInstance(arguments)

但是您需要提供确切的类名称,包括包。java.io.FileInputStream。我用它来创建数学表达式解析器。


15
以及如何在运行时获取泛型的确切类名?
David Citron

您必须使用该类的实例保存它。可行,尽管不方便。如果您的泛型具有类型E(或T或其他类型)的成员,则获取其二进制名称为just foo.getClass().getName()。那个实例来自哪里?我目前正在将一个传递给正在处理的项目中的构造函数。
Mark Storer

3

希望这为时不晚为您服务!!!

Java是类型安全的,只有Object才能创建实例。

就我而言,我无法将参数传递给createContents方法。我的解决方案是使用扩展而不是所有的波纹管答案。

private static class SomeContainer<E extends Object> {
    E e;
    E createContents() throws Exception{
        return (E) e.getClass().getDeclaredConstructor().newInstance();
    }
}

这是我无法传递参数的示例案例。

public class SomeContainer<E extends Object> {
    E object;

    void resetObject throws Exception{
        object = (E) object.getClass().getDeclaredConstructor().newInstance();
    }
}

如果您使用无对象类型扩展通用类,则使用反射创建运行时错误。要将泛型类型扩展为对象,请将此错误转换为编译时错误。


3

使用TypeToken<T>类:

public class MyClass<T> {
    public T doSomething() {
        return (T) new TypeToken<T>(){}.getRawType().newInstance();
    }
}

如果您使用番石榴代替GSON,则有些不同:(T) new TypeToken<T>(getClass()){}.getRawType().newInstance();
Jacob van Lingen

2

我以为我可以做到,但令人失望:它行不通,但我仍然值得分享。

也许有人可以纠正:

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

interface SomeContainer<E> {
    E createContents();
}

public class Main {

    @SuppressWarnings("unchecked")
    public static <E> SomeContainer<E> createSomeContainer() {
        return (SomeContainer<E>) Proxy.newProxyInstance(Main.class.getClassLoader(),
                new Class[]{ SomeContainer.class }, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Class<?> returnType = method.getReturnType();
                return returnType.newInstance();
            }
        });
    }

    public static void main(String[] args) {
        SomeContainer<String> container = createSomeContainer();

    [*] System.out.println("String created: [" +container.createContents()+"]");

    }
}

它产生:

Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.String
    at Main.main(Main.java:26)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

第26行是带有的行[*]

唯一可行的解​​决方案是@JustinRudd提供的解决方案


2

@Noah答案的改进。

变化的原因

a]如果更改顺序使用的泛型类型大于1,则更安全。

b]类通用类型签名会不时更改,因此您不会对运行时中无法解释的异常感到惊讶。

健壮的代码

public abstract class Clazz<P extends Params, M extends Model> {

    protected M model;

    protected void createModel() {
    Type[] typeArguments = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments();
    for (Type type : typeArguments) {
        if ((type instanceof Class) && (Model.class.isAssignableFrom((Class) type))) {
            try {
                model = ((Class<M>) type).newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

或用一根内胆

一线代码

model = ((Class<M>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1]).newInstance();

2

你能做的是-

  1. 首先声明该泛型类的变量

    2.然后构造它并实例化该对象

  2. 然后在任何您想使用它的地方使用它

例-

1个

private Class<E> entity;

2

public xyzservice(Class<E> entity) {
        this.entity = entity;
    }



public E getEntity(Class<E> entity) throws InstantiationException, IllegalAccessException {
        return entity.newInstance();
    }

3。

E e = getEntity(entity);



0

如果您的意思是 new E() 那是不可能的。而且我要补充一点,它并不总是正确的-您如何知道E是否具有公共的无参数构造函数?但是,您始终可以将创建委托给其他知道如何创建实例的类-它可以是Class<E>您的代码,也可以是这样的自定义代码

interface Factory<E>{
    E create();
}    

class IntegerFactory implements Factory<Integer>{    
  private static int i = 0; 
  Integer create() {        
    return i++;    
  }
}

0
return   (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();

1
这在原始问题的示例中不起作用。的超类SomeContainerObject。因此,this.getClass().getGenericSuperclass()返回一个Class(类java.lang.Object),而不是一个ParameterizedType。同行回答stackoverflow.com/questions/75175/…实际上已经指出了这一点。
David Citron

1
完全错误:线程“ main”中的异常java.lang.ClassCastException:java.lang.Class无法转换为java.lang.reflect.ParameterizedType
2013年

0

您可以使用以下代码段实现此目的:

import java.lang.reflect.ParameterizedType;

public class SomeContainer<E> {
   E createContents() throws InstantiationException, IllegalAccessException {
      ParameterizedType genericSuperclass = (ParameterizedType)
         getClass().getGenericSuperclass();
      @SuppressWarnings("unchecked")
      Class<E> clazz = (Class<E>)
         genericSuperclass.getActualTypeArguments()[0];
      return clazz.newInstance();
   }
   public static void main( String[] args ) throws Throwable {
      SomeContainer< Long > scl = new SomeContainer<>();
      Long l = scl.createContents();
      System.out.println( l );
   }
}

4
完全错误:线程“ main”中的异常java.lang.ClassCastException:java.lang.Class无法转换为java.lang.reflect.ParameterizedType
2013年

0

有许多库可以E使用类似于Robertson文章讨论的技术为您解决。这createContents是使用TypeTools解析由E表示的原始类的实现:

E createContents() throws Exception {
  return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
}

假定getClass()解析为SomeContainer的子类,否则将失败,因为如果未在子类中捕获E的实际参数化值,则会在运行时将其擦除。


0

createContents是使用TypeTools解析由表示的原始类的实现E

E createContents() throws Exception {
  return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
}

此方法仅在SomeContainer子类化的情况下有效,因此必须E在类型定义中捕获的实际值:

class SomeStringContainer extends SomeContainer<String>

否则,E的值将在运行时删除,并且不可恢复。


-1

您可以用一个类加载器和类名,最后加上一些参数。

final ClassLoader classLoader = ...
final Class<?> aClass = classLoader.loadClass("java.lang.Integer");
final Constructor<?> constructor = aClass.getConstructor(int.class);
final Object o = constructor.newInstance(123);
System.out.println("o = " + o);

这比仅通过类对象还要糟糕
newacct 2014年

您根本不需要显式引用类加载器。
Stefan Reich

-1

这是一个改进的解决方案,基于 ParameterizedType.getActualTypeArguments @ noah,@ Lars Bohl和其他一些人已经提到的。

首先是实施方面的小改进。工厂不应返回实例,而应返回类型。一旦您使用实例返回实例Class.newInstance(),就减少了使用范围。因为只能像这样调用无参数构造函数。更好的方法是返回一个类型,并允许客户端选择他要调用的构造函数:

public class TypeReference<T> {
  public Class<T> type(){
    try {
      ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
      if (pt.getActualTypeArguments() == null || pt.getActualTypeArguments().length == 0){
        throw new IllegalStateException("Could not define type");
      }
      if (pt.getActualTypeArguments().length != 1){
        throw new IllegalStateException("More than one type has been found");
      }
      Type type = pt.getActualTypeArguments()[0];
      String typeAsString = type.getTypeName();
      return (Class<T>) Class.forName(typeAsString);

    } catch (Exception e){
      throw new IllegalStateException("Could not identify type", e);
    }

  }
}

这是一个用法示例。@Lars Bohl仅显示了一种通过扩展获得通用基因的简单方法。@noah仅通过使用创建实例{}。这是证明两种情况的测试:

import java.lang.reflect.Constructor;

public class TypeReferenceTest {

  private static final String NAME = "Peter";

  private static class Person{
    final String name;

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

  @Test
  public void erased() {
    TypeReference<Person> p = new TypeReference<>();
    Assert.assertNotNull(p);
    try {
      p.type();
      Assert.fail();
    } catch (Exception e){
      Assert.assertEquals("Could not identify type", e.getMessage());
    }
  }

  @Test
  public void reified() throws Exception {
    TypeReference<Person> p = new TypeReference<Person>(){};
    Assert.assertNotNull(p);
    Assert.assertEquals(Person.class.getName(), p.type().getName());
    Constructor ctor = p.type().getDeclaredConstructor(NAME.getClass());
    Assert.assertNotNull(ctor);
    Person person = (Person) ctor.newInstance(NAME);
    Assert.assertEquals(NAME, person.name);
  }

  static class TypeReferencePerson extends TypeReference<Person>{}

  @Test
  public void reifiedExtenension() throws Exception {
    TypeReference<Person> p = new TypeReferencePerson();
    Assert.assertNotNull(p);
    Assert.assertEquals(Person.class.getName(), p.type().getName());
    Constructor ctor = p.type().getDeclaredConstructor(NAME.getClass());
    Assert.assertNotNull(ctor);
    Person person = (Person) ctor.newInstance(NAME);
    Assert.assertEquals(NAME, person.name);
  }
}

注意:您可以通过将此类抽象化来强制实例创建时TypeReference始终使用的客户端。我没有这样做,只是为了显示已删除的测试用例。{}public abstract class TypeReference<T>

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.