Java 泛型:理解和应用

2023-05-24

概述

泛型是一种将类型参数化的动态机制,使用得到的话,可以从以下的方面提升的你的程序:

  1. 安全性:使用泛型可以使代码更加安全可靠,因为泛型提供了编译时的类型检查,使得编译器能够在编译阶段捕捉到类型错误。通过在编译时检查类型一致性,可以避免在运行时出现类型转换错误和 ClassCastException 等异常。减少由于类型错误引发的bug。
  2. 复用和灵活性:泛型可以使用占位符 <T> 定义抽象和通用的对象,你可以在使用的时候再来决定具体的类型是什么,从而使得代码更具通用性和可重用性。
  3. 简化代码,增强可读性:可以减少类型转换的需求,简化代码,可以使代码更加清晰和易于理解。通过使用具有描述性的泛型类型参数,可以更准确地表达代码的意图,还可以避免使用原始类型或Object类型,从而提供更多的类型信息,使代码更加具有表达力

这就是泛型的概念,是 Java 后期的重大变化之一。泛型实现了参数化类型,可以适用于多种类型。泛型为 Java 的动态类型机制提供很好的补充,但是 Java 的泛型本质上是一种高级语法糖,也存在类型擦除导致的信息丢失等多种缺点,我们可以在本篇文章中深度探讨和分析。

简单的示例

泛型在 Java 的主要作用就是创建类型通用的集合类,我们创建一个容器类,然后通过三个示例来展示泛型的使用:

  1. 没有使用泛型的情况
  2. 使用 Object 类型作为容器对象
  3. 使用泛型作为容器对象

示例1:没有使用泛型的情况

public class IntList {

    private int[] arr;		// 只能存储整数类型的数据
    private int size;

    public IntList() {
        arr = new int[10];
        size = 0;
    }

    public void add(int value) {
        arr[size++] = value;
    }

    public int get(int index) {
        return arr[index];
    }

    public int size() {
        return size;
    }

    public static void main(String[] args) {
        IntList list = new IntList();

        list.add(1);
        list.add(2);
        list.add(3);

        int value = list.get(1);  // 需要显式进行类型转换
        System.out.println(value);  // 输出: 2
    }
}

在上述示例中,使用了一个明确的 int 类型存储整数的列表类 IntList,但是该类只能存储整数类型的数据。如果想要存储其他类型的数据,就需要编写类似的类,导致类的复用度较低。

示例2:使用 Object 类型作为持有对象的容器

public class ObjectList {
    private Object[] arr;
    private int size;

    public ObjectList() {
        arr = new Object[10];
        size = 0;
    }

    public void add(Object value) {
        arr[size++] = value;
    }

    public Object get(int index) {
        return arr[index];
    }

    public int size() {
        return size;
    }

    public static void main(String[] args) {
        // 示例使用
        ObjectList list = new ObjectList();
        list.add(1);
        list.add("Hello");
        list.add(true);

        int intValue = (int) list.get(0);  // 需要显式进行类型转换
        String stringValue = (String) list.get(1);  // 需要显式进行类型转换
        boolean boolValue = (boolean) list.get(2);  // 需要显式进行类型转换
    }
}

在上述示例中,使用了一个通用的列表类 ObjectList,它使用了 Object 类型作为持有对象的容器。当从列表中取出对象时,需要显式进行类型转换,而且不小心类型转换错误程序就会抛出异常,这会带来代码的冗余、安全和可读性的降低。

示例3:使用泛型实现通用列表类

public class GenericList<T> {

    private T[] arr;
    private int size;

    public GenericList() {
        arr = (T[]) new Object[10];  // 创建泛型数组的方式
        size = 0;
    }

    public void add(T value) {
        arr[size++] = value;
    }

    public T get(int index) {
        return arr[index];
    }

    public int size() {
        return size;
    }

    public static void main(String[] args) {
        // 存储 Integer 类型的 List
        GenericList<Integer> intList = new GenericList<>();
        intList.add(1);
        intList.add(2);
        intList.add(3);

        int value = intList.get(1);  // 不需要进行类型转换
        System.out.println(value);  // 输出: 2

        // 存储 String 类型的 List
        GenericList<String> stringList = new GenericList<>();
        stringList.add("Hello");
        stringList.add("World");

        String str = stringList.get(0); // 不需要进行类型转换
        System.out.println(str); // 输出: Hello
    }
}

在上述示例中,使用了一个通用的列表类 GenericList,通过使用泛型类型参数 T,可以在创建对象时指定具体的类型。这样就可以在存储和取出数据时,不需要进行类型转换,代码更加通用、简洁和类型安全。

通过上述三个示例,可以清楚地看到泛型在提高代码复用度、简化类型转换和提供类型安全方面的作用。使用泛型可以使代码更具通用性和可读性,减少类型错误的发生,并且提高代码的可维护性和可靠性。

组合类型:元组

在某些情况下需要组合多个不同类型的值的需求,而不希望为每种组合创建专门的类或数据结构。这就需要用到元组(Tuple)。

元组(Tuple)是指将一组不同类型的值组合在一起的数据结构。它可以包含多个元素,每个元素可以是不同的类型。元组提供了一种简单的方式来表示和操作多个值,而不需要创建专门的类或数据结构。

下面是一个使用元组的简单示例:

class Tuple<T1, T2> {
    private T1 first;
    private T2 second;

    public Tuple(T1 first, T2 second) {
        this.first = first;
        this.second = second;
    }

    public T1 getFirst() {
        return first;
    }

    public T2 getSecond() {
        return second;
    }
}

public class TupleExample {

    public static void main(String[] args) {
        Tuple<String, Integer> person = new Tuple<>("Tom", 18);
        System.out.println("Name: " + person.getFirst());
        System.out.println("Age: " + person.getSecond());

        Tuple<String, Double> product = new Tuple<>("Apple", 2.99);
        System.out.println("Product: " + product.getFirst());
        System.out.println("Price: " + product.getSecond());
    }
}

在上述示例中,定义了一个简单的元组类 Tuple,它有两个类型参数 T1T2,以及相应的 firstsecond 字段。在 main 方法中,使用元组存储了不同类型的值,并通过调用 getFirstgetSecond 方法获取其中的值。

你也们可以利用继承机制实现长度更长的元组:

public class Tuple2<T1, T2, T3> extends Tuple<T1, T2>{

    private T3 t3;

    public Tuple2(T1 first, T2 second, T3 t3) {
        super(first, second);
        this.t3 = t3;
    }
}

继续扩展:

public class Tuple3<T1, T2, T3, T4> extends Tuple2<T1, T2, T3> {

    private T4 t4;

    public Tuple3(T1 first, T2 second, T3 t3) {
        super(first, second, t3);
    }
}

如上所述,元组提供了一种简洁而灵活的方式来组合和操作多个值,适用于需要临时存储和传递多个相关值的场景。但需要注意的是,元组并不具备类型安全的特性,因为它允许不同类型的值的组合。

泛型接口

将泛型应用在接口,是在接口设计时常常需要考虑的,泛型可以提供接口的复用性和安全性。

下面是一个示例,展示泛型在接口上的使用:

// 定义一个泛型接口
interface Container<T> {
    void add(T item);
    T get(int index);
}

// 实现泛型接口
public class ListContainer<T> implements Container<T> {

    private List<T> list;

    public ListContainer() {
        this.list = new ArrayList<>();
    }

    @Override
    public void add(T item) {
        list.add(item);
    }

    @Override
    public T get(int index) {
        return list.get(index);
    }

    public static void main(String[] args) {
		// 示例使用
        Container<String> container = new ListContainer<>();
        container.add("Apple");
        container.add("Banana");
        container.add("Orange");

        String fruit1 = container.get(0);
        String fruit2 = container.get(1);
        String fruit3 = container.get(2);

        System.out.println(fruit1);  // 输出: Apple
        System.out.println(fruit2);  // 输出: Banana
        System.out.println(fruit3);  // 输出: Orange
    }
}

在上述示例中,我们定义了一个泛型接口 Container<T>,它包含了两个方法:add 用于添加元素,get 用于获取指定位置的元素。然后,我们通过实现泛型接口的类 ListContainer<T>,实现了具体的容器类,这里使用了 ArrayList 来存储元素。在示例使用部分,我们创建了一个 ListContainer<String> 的实例,即容器中的元素类型为 String。我们可以使用 add 方法添加元素,使用 get 方法获取指定位置的元素。

通过在接口上使用泛型,我们可以定义出具有不同类型的容器类,提高代码的可复用性和类型安全性。泛型接口允许我们在编译时进行类型检查,并提供了更好的类型约束和编码规范。

泛型方法

泛型方法是一种在方法声明中使用泛型类型参数的特殊方法。它允许在方法中使用参数或返回值的类型参数化,从而实现方法在不同类型上的重用和类型安全性。

泛型方法具有以下特点:

  1. 泛型方法可以在方法签名中声明一个或多个类型参数,使用尖括号 <T> 来表示
  2. 类型参数可以在方法内部用作方法参数类型、方法返回值类型、局部变量类型

方法泛型化要比将整个类泛型化更清晰易懂,所以在日常使用中请尽可能的使用泛型方法。

以下展示泛型方法的示例:

public class GenericMethodExample {
    // 带返回值的泛型方法
    public static <T> T getFirstElement(T[] array) {
        if (array != null && array.length > 0) {
            return array[0];
        }
        return null;
    }

    public static void main(String[] args) {
        Integer[] intArray = {1, 2, 3, 4, 5};
        String[] strings = {"Hello", "World"};

        System.out.println("First element in intArray: " + getFirstElement(intArray));
        System.out.println("First element in strings: " + getFirstElement(strings));
    }
}

可以看到通过泛型方法,让 getFirstElement() 更具备通用性,无需为每个不同的类型编写单独的获取方法。

再来看一个带可变参数的泛型方法:

public class GenericMethodExample {
    // 带返回值的泛型方法,接受变长参数列表
    public static <T> List<T> createList(T... elements) {
        List<T> list = new ArrayList<>();
        for (T element : elements) {
            list.add(element);
        }
        return list;
    }

    public static void main(String[] args) {
        List<String> stringList = createList("Apple", "Banana", "Orange");
        List<Integer> intList = createList(1, 2, 3, 4, 5);

        System.out.println("String List: " + stringList);    // 输出: String List: [Apple, Banana, Orange]
        System.out.println("Integer List: " + intList);      // 输出: Integer List: [1, 2, 3, 4, 5]
    }
}

泛型信息的擦除

当你深入了解泛型的时候,你会发现它没有你想象的那么安全,它只是编译过程的语法糖,因为泛型并不是 Java 语言的特性,而是后期加入的功能特性,属于编译器层面的功能,而且由于要兼容旧版本的缘故,所以 Java 无法实现真正的泛型。

泛型擦除是指在编译时期,泛型类型参数会被擦除或替换为它们的上界或限定类型。这是由于Java中的泛型是通过类型擦除来实现的,编译器在生成字节码时会将泛型信息擦除,以确保与旧版本的Java代码兼容。

以下是一个代码示例,展示了泛型擦除的效果:

public class GenericErasureExample {

    public static void main(String[] args) {
        // 定义一个 String 类型的集合
        List<String> stringList = new ArrayList<>();
        stringList.add("Hello");
        stringList.add("World");

        // 定义一个 Integer 类型的集合
        List<Integer> intList = new ArrayList<>();
        intList.add(10);
        intList.add(20);

        // 你无法通过反射获取泛型的类型参数,因为泛型信息会在编译时被擦除
        System.out.println(stringList.getClass());   // 输出: class java.util.ArrayList
        System.out.println(intList.getClass());      // 输出: class java.util.ArrayList

        // 原本不同的类型,输出结果却相等
        System.out.println(stringList.getClass() == intList.getClass());    // 输出: true

        // 使用原始类型List,可以绕过编译器的类型检查,但会导致类型转换错误
        List rawList = stringList;
        rawList.add(30); // 添加了一个整数,导致类型转换错误

        // 从rawList中取出元素时,会导致类型转换错误
        String str = stringList.get(0);  // 类型转换错误,尝试将整数转换为字符串
    }
}

通过上述代码,我们演示类的泛型信息是怎么被擦除的,并且演示由于泛型信息的擦除所导致的安全和转换错误。这也是为什么在泛型中无法直接使用基本类型(如 int、boolean 等),而只能使用其包装类的原因之一。

为什么要擦除 ?

Java 在设计泛型时选择了擦除泛型信息的方式,主要是为了保持与现有的非泛型代码的兼容性,并且提供平滑的过渡。泛型是在 Java 5 中引入的,泛型类型参数被替换为它们的上界或限定类型,这样可以确保旧版本的 Java 虚拟机仍然可以加载和执行这些类。

尽管泛型擦除带来了一些限制,如无法在运行时获取泛型类型参数的具体类型等,但通过类型通配符、反射和其他技术,仍然可以在一定程度上处理泛型类型的信息。擦除泛型信息是 Java 泛型的设计妥协,为了在保持向后兼容性和类型安全性的同时,提供了一种灵活且高效的泛型机制。

擦除会引发哪些问题 ?

设计的本质就是权衡,Java 设计者为了兼容性不得已选择的擦除泛型信息的方式,虽然完成了对历史版本的兼容,但付出的代价也是显著的,擦除泛型信息对于 Java 代码可能引发以下问题:

  1. 无法在运行时获取泛型类型参数的具体类型:由于擦除泛型信息,无法在运行时获取泛型类型参数的具体类型。(如上所示)
  2. 类型转换和类型安全性:擦除泛型信息可能导致类型转换错误和类型安全性问题。(如上所示)
  3. 无法创建具体的泛型类型实例:由于擦除泛型信息,无法直接创建具体的泛型类型的实例。例如,无法使用 new T() 的方式
  4. 与原始类型的混淆:擦除泛型信息可能导致与原始类型的混淆。并且泛型无法使用基本数据类型,只能依赖自动拆箱和装箱机制

Class 信息丢失

这是一段因为擦除导致没有任何意义的代码:

public class ArrayMaker<T> {

    private Class<T> kind;

    public ArrayMaker(Class<T> kind) {
        this.kind = kind;
    }

    @SuppressWarnings("unchecked")
    T[] create(int size) {
        return (T[]) java.lang.reflect.Array.newInstance(kind, size);
    }

    public static void main(String[] args) {
        ArrayMaker<String> stringMaker = new ArrayMaker<>(String.class);
        String[] stringArray = stringMaker.create(10);
        System.out.println(Arrays.toString(stringArray));
    }
}

输出结果:

[null, null, null, null, null, null, null, null, null, null]

泛型边界

泛型边界(bounds)是指对泛型类型参数进行限定,以指定其可以接受的类型范围。泛型边界可以通过指定上界(extends)或下界(super)来实现。泛型边界允许我们在泛型代码中对类型参数进行限制,它们有助于确保在使用泛型类或方法时,只能使用符合条件的类型。

泛型边界的使用场景包括:

  1. 类型限定:当我们希望泛型类型参数只能是特定类型或特定类型的子类时,可以使用泛型边界。
  2. 调用特定类型的方法:通过泛型边界,我们可以在泛型类或方法中调用特定类型的方法,访问其特定的属性。
  3. 扩展泛型类型的功能:通过泛型边界,我们可以限制泛型类型参数的范围,以扩展泛型类型的功能。

上界(extends)

用于设定泛型类型参数的上界,即,类型参数必须是特定类型或该类型的子类,示例

public class MyExtendsClass<T extends Number> {
    
    public static void main(String[] args) {
        MyExtendsClass<Integer> integerMyExtendsClass = new MyExtendsClass<>();  // 可以,因为 Integer 是 Number 的子类
        MyExtendsClass<Double> doubleMyExtendsClass = new MyExtendsClass<>();   // 可以,因为 Double 是 Number 的子类
//        MyClass<String> myStringClass = new MyClass<>(); // 编译错误,因为 String 不是 Number 的子类
    }
}

在泛型方法中,extends 关键字在泛型的读取模式(Producer Extends,PE)中常用到。比如,一个方法返回的是 List<? extends Number>,你可以确定这个 List 中的元素都是 Number 或其子类,可以安全地读取为 Number,但不能向其中添加任何元素(除了 null),示例:

public void doSomething(List<? extends Number> list) {
    Number number = list.get(0); // 可以读取
//        list.add(3); // 编译错误,不能写入
}

下界(super)

用于设定类型参数的下界,即,类型参数必须是特定类型或该类型的子类。示例:

    public void addToMyList(List<? super Number> list) {
        Object o1 = new Object();
        list.add(3);  // 可以,因为 Integer 是 Number 的子类
        list.add(3.14); // 可以,因为 Double 是 Number 的子类
//      list.add("String");     // 编译错误,因为 String 不是 Number 的子类
    }

在泛型方法中,super 关键字在泛型的写入模式(Consumer Super,CS)中常用到。比如,一个方法参数的类型是 List<? super Integer>,你可以向这个 List 中添加 Integer 或其子类的对象,但不能从中读取具体类型的元素(只能读取为 Object),示例:

public void doSomething(List<? super Integer> list) {
    list.add(3);        // 类型符合,可以写入
//  Integer number = list.get(0);     // 编译错误,不能读取具体类型
    Object o = list.get(0);     // 可以读取 Object
}

熟练和灵活的运用 PECS 原则(Producer Extends, Consumer Super)我们也可以轻松实现 Collection 里面的通用类型集合的 Copy 方法,示例:

public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    for (T t : src) {
        dest.add(t);
    }
}

public static void main(String[] args) {
    List<Object> objectList = new ArrayList<>();
    List<Integer> integerList = Arrays.asList(1, 2, 3);

    copy(objectList, integerList);

    System.out.println(objectList);     // [1, 2, 3]
}

记住,无论是 extends 还是 super,它们都只是对编译时类型的约束,实际的运行时类型信息在类型擦除过程中已经被删除了。

无界(?)

无界通配符 <?> 是一种特殊的类型参数,可以接受任何类型。它常被用在泛型代码中,当代码可以工作在不同类型的对象上,并且你可能不知道或者不关心具体的类型是什么。你可以使用它,示例:

public static void printList(List<?> list) {
    for (Object elem : list)
        System.out.println(elem + " ");
    System.out.println();
}

public static void main(String[] args) {
    List<Integer> li = Arrays.asList(1, 2, 3, 4, 5);
    List<String> ls = Arrays.asList("one", "two", "three");
    printList(li);
    printList(ls);
}

那么,问题来了。

那我为什么不直接使用 Object ? 而要使用 <?> 无界通配符 ?

它们好像都可以容纳任何类型的对象。但实际上,List<Object>List<?> 在类型安全性上有很大的不同。

例如,List<Object> 是一个具体类型,你可以向 List<Object> 中添加任何类型的对象。但是,List<Object> 不能接受其他类型的 List,例如 List<String>List<Integer>

相比之下,List<?> 是一个通配符类型,表示可以是任何类型的 List。你不能向 List<?> 中添加任何元素(除了 null),因为你并不知道具体的类型,但你可以接受任何类型的 List,包括 List<Object>List<String>List<Integer> 等等。

示例代码:

public static void printListObject(List<Object> list) {
    for (Object obj : list)
        System.out.println(obj);
}

public static void printListWildcard(List<?> list) {
    for (Object obj : list)
        System.out.println(obj);
}

public static void main(String[] args) {
    List<String> stringList = Arrays.asList("Hello", "World");

    printListWildcard(stringList); // 有效
    // printListObject(stringList); // 编译错误
}

因此,当你需要编写能接受任何类型 List 的代码时,应该使用 List<?> 而不是 List<Object>

目前存在的问题

在 Java 引入泛型之前,已经有大量的 Java 代码在生产环境中运行。为了让这些代码在新版本的 Java 中仍然可以运行,Java 的设计者选择了一种叫做 “类型擦除” 的方式来实现泛型,这样就不需要改变 JVM 和已存在的非泛型代码。

但这样的设计解决了向后兼容的问题,但也引入很多问题需要大多数的 Java 程序员来承担,例如:

  1. 类型擦除:这是Java泛型中最主要的限制。这意味着在运行时你不能查询一个泛型对象的真实类型
  2. 不能实例化泛型类型的类:你不能使用 new T()new E()这样的语法来创建泛型类型的对象,还是因为类型被擦除
  3. 不能使用基本类型作为类型参数:因为是编译器的语法糖,所以只能使用包装类型如 IntegerDouble 等作为泛型类型参数
  4. 通配符的使用可能会导致代码复杂:如 ? extends T? super T 在理解和应用时需要小心
  5. 因为类型擦除,泛型类不能继承自或者实现同一泛型接口的不同参数化形式

尽管 Java 的泛型有这些缺点,但是它仍然是一个强大和有用的工具,可以帮助我们编写更安全、更易读的代码。

总结

在泛型出现之前,集合类库并不能在编译时期检查插入集合的对象类型是否正确,只能在运行时期进行检查,这种情况下一旦出错就会在运行时抛出一个类型转换异常。这种运行时错误的出现对于开发者而言,既不友好,也难以定位问题。泛型的引入,让开发者可以在编译时期检查类型,增加了代码的安全性。并且可以编写更为通用的代码,提高了代码的复用性。

然而,泛型设计并非完美,主要的问题出在类型擦除上,为了保持与老版本的兼容性所做的妥协。因为类型擦除,Java 的泛型丧失了一些强大的功能,例如运行时类型查询,创建泛型数组等。

尽管 Java 泛型存在一些限制,但是 Java 语言仍然在不断的发展中,例如在 Java 10 中,引入了局部变量类型推断的特性,使得在使用泛型时可以更加方便。对于未来,Java 可能会在泛型方面进行更深入的改进。

本文转载于网络 如有侵权请联系删除

相关文章

  • Vue安装及环境配置、开发工具

    大家好,又见面了,我是你们的朋友全栈君。 本文主要介绍了Vue的安装及环境配置,新建vue项目,简单介绍vue开发工具和项目结构。文章目录前言一、node.js安装和配置1.下载安装node.js2.配置默认安装目录和缓存日志目录3.node.js环境配置4.配置淘宝镜像源二、安装vue及脚手架1.安装vue.js2.安装webpack模板3.安装脚手架vue-cli2.x4.vue-cli2创建vue项目三、安装vue-cli3.x1、卸载旧版本2、安装新版本3、新建项目4、运行项目五、cli3下拉取2.x模板四、开发工具1、用VS查看vue代码2、HbuilderX五、vue项目结构六、我的vue的系统学习笔记vue笔记一:Vue技术栈Vue笔记二:基础语法前言vue前端框架的环境搭建一、node.js安装和配置1.下载安装node.js官网下载最新版本:https://nodejs.org/en/download/ 可以下载安装包(安装教程见:https://www.runoob.com/nodejs/nodejs-install-setup.html) 或者下载zip文件找个目

  • Java新手程序员提问和解决问题的正确姿势

    一、背景老手,大牛都是从新手走过来的,偶尔也会在群里解答一些问题。但是确实很多新手的提问让人摸不着头脑。常见的是问题很笼统,好像大神都会特异功能,都可以穿越时空,逆转到过去来到他的电脑旁看到了事情的经过一样。描述不清楚,没有配图,没有交代清楚背景等等。本文结合自己的体会,提几点建议。二、建议2.1参考这篇github描述《高效提问》写的就不错https://github.com/mmflys/blog/blob/master/common/EffectiveAsking.md强调描述环境、操作描述、期望、现象注重问题的清晰度给出更多的线索,阐述的顺序根据序号递增,根据重要性递减。2.2我的建议(1)自己先做出一些尝试!!  最重要的一点是,要分析不要猜测,分析可能的情况,然后去印证!!!F12大法,根据请求参数,响应码,响应数据等定位是前端还是后端问题。codereview分析代码逻辑是否有问题使用各种命令辅助排错,比如dubbo命令查看服务是否可调通,git命令拉新分支尝试解决问题,mvn或gradle命令编译是否可以通过,查看依赖树等等。控制变量法,比如新增了3个类出错,可以依次删

  • 5G 独立自组织网络中的机器学习(CS NI)

    机器学习(ML)包含在自组织网络(SON)中,是增强操作维护管理(OAM)的关键驱动因素。它包括在5G独立组网(SA)系统中,是将4G网络转变为基于移动应用的下一代技术的5G通信轨道之一。这项研究的主要目的是概述5G独立核心网络中的机器学习(ML)。5G独立组网被服务供应商视为关键推动因素,因为它提高了网络边缘的吞吐量。它还有助于推进新的蜂窝使用案例,如支持频率组合的超可靠低延迟通信(URLLC)。原文题目:MachineLearning(ML)Ina5GStandalone(SA)SelfOrganizingNetwork(SON)原文:Machinelearning(ML)isincludedinSelf-organizingNetworks(SONs)thatarekeydriversforenhancingtheOperations,Administration,andMaintenance(OAM)activities.Itisincludedinthe5GStandalone(SA)systemisoneofthe5Gcommunicationtracksthattrans

  • 解决移动端水平滚动使用justify-content显示不全问题

    今天做移动端页面遇到一个坑,我需要实现的效果是这样的 由于选项的个数是不一定的,如果很少的话不会有横向滚动,是需要居中的 自然而然的是这么写的.father{ display:flex; justify-content:center; overflow-x:auto; margin:030px; .child{ display:flex; flex-direction:column; align-items:center; } }复制结果会发现第一个元素是展示不全的, 如果把father里的justify-content:center;换成justify-content:space-between;或者justify-content:left;是可以的,但是当只展示两三个选项时就会间距过大。而我们需要选项过少的时候是center效果,多了则显示全并能滚动。 想过对第一个选项的css单独处理,但是还要判断选项个数,其实和自己试出来的页面能展示多少个选项不滚动没区别。不想写js和复杂的css,尝试了很多次,最终发现在father外面再包一层div给他justify-content:c

  • WordPress添加页面导航

    WordPress原生的页面导航不好用,我们可以利用插件改成分页导航。我们使用WP-PageNavi插件来替代原生的页面导航。安装结束后进入主题编辑器,找到首页index.php,找到navigation或者nav相关的代码,注释掉相关代码,并在位置上添加如下代码:<?phpwp_pagenavi();?>如果注释掉原有的导航代码会导致页面错乱,可以使用一个隐藏标签把原有的页面导航隐藏掉。比如我使用的主题直接注释掉原导航代码,页面就会乱,隐藏掉原导航代码即可。<divstyle="display:none;visibility:hidden"> <?phpnisarg_posts_navigation();?> </div>复制效果如下:

  • 聚焦新基建探索未来经济,腾讯年度最重磅峰会关注哪些重点?

    疫情爆发以来,具有数字化布局的企业显然展现了更强的韧性,在疫情期间不仅能通过线上办公、远程服务保持业务的连贯性;在疫情控制住之后也能更快地实现复产复工。伴随各行各业触网上云进程的深入,数字化已经从企业的“可选项”变成了“必选项”。据有关机构预测,到2023年,数字化转型企业的产品和服务将驱动全球一半以上的GDP,全球经济也将达到一个临界点。在这个临界点上,随着以云计算、AI和5G等为代表的新基建技术的共同作用,将使得数字化企业在边际成本上获得压倒性的先发竞争优势,更好地成就业务创新与绩效增长。“数字优先”成为企业战略思考的起点,也预示着新的机遇与可能性。然而,朝向数字化目标前进的过程,每家企业都面临诸多的挑战。数字化转型过程不一的企业如何践行数字优先?如何选择数字化工具,如何评价数字化落地的ROI,最佳实践的复用过程中如何克服水土不服?哪些因素将直接影响企业数字化转型的成败?这一切,在9月9日-11日召开的腾讯全球数字生态大会中都将找到答案。01洞悉未来数字经济揭示产业安全新机遇腾讯全球数字生态大会是腾讯集团规格最高、规模最大、覆盖最广的产业互联网盛会。今年的大会更是疫情以来国内首场产

  • 为什么要使用 package-lock.json[每日前端夜话0xCE]

    每日前端夜话0xCE每日前端夜话,陪你聊前端。每天晚上18:00准时推送。正文共:1533字预计阅读时间:12分钟作者:KostasBariotis翻译:疯狂的技术宅来源:logrocket Aguidetousingpackage-lock.jsoninNPM 在本文中,我们将介绍package-lock.json为什么重要,以及如何与NPMCLI一起使用。历史NPMv5引入了package-lock.json,将其作为捕获在任意时刻安装的确切依赖树的机制。这会有助于在不同环境中进行协作,在这种环境中,你希望每个人都为项目的特定版本获取依赖项以得到同一棵依赖树。package.json使用语义版本【https://semver.org/】定义所需的依赖项及其各自的版本。但是语义版本控制可能很棘手。考虑一个表示为"express":"^4.16.4"的依赖项。该模块的发布者(不使用package-lock.json)将安装版本为4.16.4的Express,因为他们安装了最新版本。如果express在我下载该模块并尝试安装依赖项时发布了新版本,则

  • [golang][译]使用os/exec执行命令

    [golang][译]使用os/exec执行命令https://colobu.com/2017/06/19/advanced-command-execution-in-Go-with-os-exec/原文: AdvancedcommandexecutioninGowithos/exec byKrzysztofKowalczyk. 完整代码在作者的github上: advanced-execGo可以非常方便地执行外部程序,让我们开始探索之旅吧。执行命令并获得输出结果最简单的例子就是运行ls-lah并获得组合在一起的stdout/stderr输出。funcmain(){ cmd:=exec.Command("ls","-lah") out,err:=cmd.CombinedOutput() iferr!=nil{ log.Fatalf("cmd.Run()failedwith%s\n",err) } fmt.Printf("combinedout:\n%s\n",string(out)) }复制将stdout和

  • 王飞:云+ AI 技术在高端酒店行业的应用

    12月15日,由腾讯云主办的首届“腾讯腾讯云开发者社区开发者大会”在北京举行。本届大会以“新趋势•新技术•新应用”为主题,汇聚了超40位技术专家,共同探索人工智能、大数据、物联网、小程序、运维开发等热门技术的最新发展成果,吸引超过1000名开发者的参与。以下是物联网的场景化技术应用分论坛的演讲内容,稍作整理,分享给大家。在云计算与物联网技术升级后,酒店内部管理和人机交互等内容均开始实现智能化。本次分享将会结合客人在酒店的消费体验场景,分析云和AI技术在高端酒店落地的价值点和实现方式。大家对酒店都不陌生,知道它是一个传统行业,属于传统服务业,从人类开始有了异地化的交易开始,这个行业就应运而生了。如此传统的一个行业,为什么会有我们这样一个部门的存在,我们这个部门存在的意义是什么,可能想必在座的各位心里也会有这样的疑惑。希望通过我今天的一些知识分享能够多多少少解除大家心中的疑惑。刚才提到酒店的行业属于一种服务业,酒店是服务的一个内核,从服务的角度来讲,先给大家简单了解一下服务方面的知识。从服务的角度来讲,我们可以把酒店大概分为几个档次,可以说是豪华型、高档型、中档带有餐饮服务、中档没有餐饮服

  • Promise, Generator, async/await的渐进理解

        作为前端开发者的伙伴们,肯定对Promise,Generator,async/await非常熟悉不过了。Promise绝对是烂记于心,而async/await却让使大伙们感觉到爽(原来异步可以这么简单)。可回头来梳理他们的关联时,你惊讶的发现,他们是如此的密切相关。一、三者关系与发展史     我对他们三者之间的关联理解如上图所示,Promise是基础,Generator和async/await串连多个Promise的同步执行,也就是把Promise的异步特性变为同步,编程更爽。当然Generator的yield也可以跟非Promise类型的对象,对于Generator更可以理解为迭代器。而async/await则是多个Promise同步执行的简单方法。     但我们不难发现在处理Promise的时候generator和asnyc/await的返回结果也会一个Promise,你可以按照一个标准的Promise进行处理。发展史:     本人最先接触Promise是Jquery的Deferred对象,然后就出现了bluebird这类实现PromiseA+规范的库,在后来就是ES

  • EasyGBS平台通过域名访问,视频无法播放是什么原因?

    EasyGBS是基于GB28181协议的视频平台,拥有视频直播、录像、存储、检索与回放、云台控制、告警上报、语音对讲、平台级联等功能。平台可提供流媒体接入、处理、转发等服务,支持内网、公网的监控设备通过国标GB/T28181协议进行视频监控直播。有用户反馈,EasyGBS升级后,用域名访问,查看监控画面会提示一直加载,但是直播画面依旧出不来,但是本地IP+公网IP播放都正常。技术人员针对用户反馈,立即进行远程排查。在排查中发现,通过域名访问,播放时返回的还是IP,并不是域名地址,所以返回的地址解析不了,导致视频无法正常播放。如图所示,域名访问调用播放返回的并不是域名地址,而是返回写死的地址。经过查看发现确实是域名和公网IP信息没做转换,将此处的机制修改后,平台的视频就能正常播放了。EasyGBS国标视频云服务平台不仅支持无缝、完整接入内网或者公网的国标设备。随着我国平安城市、平安乡村、雪亮工程等项目的不断落地建设,EasyGBS凭借优秀的视频能力,在这些场景中也发挥了极大的作用。除了提供API接口供用户调用、集成与二次开发,EasyGBS还能提供个性化定制,以满足用户的多样化功能需求。

  • Node.js精进(5)——HTTP

      HTTP(HyperTextTransferProtocol)即超文本传输协议,是一种获取网络资源(例如图像、HTML文档)的应用层协议,它是互联网数据通信的基础,由请求和响应构成。   在Node.js中,提供了3个与之相关的模块,分别是HTTP、HTTP2和HTTPS,后两者分别是对HTTP/2.0和HTTPS两个协议的实现。   HTTP/2.0是HTTP/1.1的扩展版本,主要基于Google发布的SPDY协议,引入了全新的二进制分帧层,保留了1.1版本的大部分语义。   HTTPS(HTTPSecure)是一种构建在SSL或TLS上的HTTP协议,简单的说,HTTPS就是HTTP的安全版本。   本节主要分析的是HTTP模块,它是Node.js网络的关键模块。   本系列所有的示例源码都已上传至Github,点击此处获取。 一、搭建Web服务器   Web服务器是一种让网络用户可以访问托管文件的软件,常用的有IIS、Nginx等。   Node.js与ASP.NET、PHP等不同,它不需要额外安装Web服务器,因为通过它自身包含的模块就能快速搭建出Web服务器。   运行

  • Django组件 之 分页器(paginator)

    --------------------------------------------------------------------------------路虽远,行则将至. 事虽难,做则必成. view fromdjango.shortcutsimportrender,HttpResponse #Createyourviewshere. fromapp01.modelsimport* fromdjango.core.paginatorimportPaginator,EmptyPage,PageNotAnInteger defindex(request): ''' 批量导入数据: Booklist=[] foriinrange(100): Booklist.append(Book(title="book"+str(i),price=30+i*i)) Book.objects.bulk_create(Booklist) ''' ''' 分页器的使用: book_list=Book.objects.all() paginator=Pag

  • 6.9赛后总结

    6.9模拟赛赛后总结 赛时历程 大约八点看完题目感到,啥也不会,好像T3的暴力最可做。 打着打着发现第一个性质分也好做,记录个las然后小小讨论一下就好。 对拍很成功,已经九点多。 其它啥也不会怎么搞,T1想到了一个\(O(y-x)\)(y-x<=1e9)的伞兵DP,估计也骗不了分。 慢慢的,到点了。 赛后发现 我发现以前的三段结构很合理,所以现在换回来。 1T1果然没分。T3竟然不知道怎么的吧freopen给注释了,光荣抱玲。 2T3正解可以用CDQ,都写了这么多CDQ了,还是没感觉,发现不了这是CDQ可做的,不够敏感,主要是也不知道能转换成二维平面,就更不谈什么三维偏序了。 3T1的DP其实并不难,但是基础的\(n^2\)都没有想到,着实有点菜,应该多思考思考的(猛然想起这句话不是第一次提到,确实需要多思考啊,不能光说)。 简单题解 好好总结。 T1要考虑DP,首先要考虑特殊点,也就是喜爱的城市的状态,只有他们会影响到决策(不然肯定每次跳z个会最优),因此,考虑喜爱的城市到喜爱的城市的转移,为了直接考虑到起点和终点,把他们当成数值特殊点的喜爱的城市即可。然后考虑\(f[i]\

  • poi linkageerror weblgic12.2.1.3

    系统升级weblogic12.1.2.0升级到12.2.1.3 ,poi生成excel报错linkageerror。 weblogic已设置优先使用应用的jar包。 解决办法: 删除xmlbean.2.3.0.jar包,重启应用。      

  • 洛谷-P5601 小D与笔试

    洛谷-P5601小D与笔试 原题链接:https://www.luogu.com.cn/problem/P5601 题目背景 题目描述 输入格式 输出格式 输入输出样例 说明/提示 C++代码 题目背景 小D是一位即将参加ION的IO选手,然而笔试题库中数量繁多的奇怪题目让他大伤脑筋,快来帮帮他! 题目描述 笔试题库可以抽象为\(n\)道题目,每道题目由题面和答案组成,都是一个字符串,保证所有题目题面互不相同。 为了检验小D背笔试的效果,教练进行了一次模拟考试,考试包含\(q\)道题目,每道题目都有\(4\)个选项,小D需要从\(4\)个选项中选出与答案相符的选项。 现在你需要帮助小D完成这场考试。 输入格式 第一行两个正整数\(n,q\)。 接下来\(n\)行,每行\(2\)个用空格分隔的字符串,表示这道题目的题面和答案。 接下来\(q\)行,每行\(5\)个用空格分隔的字符串,第一个字符串表示模拟考试中这道题目的题面,其余\(4\)个字符串按顺序分别为这道题目的选项A到选项D,保证选项各不相同。 输出格式 对于模拟考试中的每道题目,输出一个字符表示这道题目答案对应的选项,

  • windows10 安装win10和ubuntu 16.04双系统

    安装教程如下亲测可用 https://www.cnblogs.com/masbay/p/10844857.html   镜像路径如下 http://releases.ubuntu.com/16.04/     安装完成之后出现没有ubuntu引导界面直接进入windows解决如下 以管理员的身份在cmd中敲入命令: bcdedit/set"{bootmgr}"path\EFI\ubuntu\grubx64.efi 然后重启就有ubuntu引导 不太相信弯道超车,更欣赏仰望星空与脚踏实地

  • istio踩坑实记

    作者:肥嘟嘟左卫门熊 前言 虽然标题是没有的坑,但是我会把碰到的有和没有的坑都写一下,后人好乘凉阿! 1.状态码:426UpgradeRequired 这是一个常见问题,作者当时很快也就查到了,但是还是记录一下 背景 Istio使用Envoy作为数据面转发HTTP请求,而Envoy默认要求使用HTTP/1.1或HTTP/2,当客户端使用HTTP/1.0时就会返回426UpgradeRequired。 常见的nginx场景 如果用nginx进行proxy_pass反向代理,默认会用HTTP/1.0,你可以显示指定proxy_http_version为1.1: upstreamhttp_backend{ server127.0.0.1:8080; keepalive16; } server{ ... location/http/{ proxy_passhttp://http_backend; proxy_http_version1.1; proxy_set_headerConnection""; ... } } 复制 参考资料 Envoywon’tconnecttomyHTTP/

  • 使用Dom4j生成xml文件

    场景:使用dom4j生成以下xml文件 <?xmlversion="1.0"encoding="UTF-8"?> <result> <code>1</code> <data> <person> <name>张三</name> <id>1</id> <url>http://192.168.191.1:9999/TestWeb/c7fe21616d2a5e2bd1e84bd453a5b30f.jpg</url> <courses> <course> <courseName>语文</courseName> <courseMarks>90</courseMarks> <courseId>1</courseId> </course> <course> <courseName>数学</courseName> &

  • 2019-2020-2 网络对抗技术 20172327 Exp2 后门原理与实践

    目录1.实践基础1.1什么是后门?1.2基础问题回答2.实践内容2.1使用netcat获取主机操作Shell,cron启动,包括2.2使用socat获取主机操作Shell,任务计划启动2.3使用MSFmeterpreter(或其他软件)生成可执行文件,利用ncat或socat传送到主机并运行获取主机Shell2.4使用MSFmeterpreter生成获取目标主机音频、摄像头、击键记录等内容,并尝试提权3.实验总结 1.实践基础 1.1什么是后门? 后门就是不经过正常认证流程而访问系统的通道 哪里有后门呢? 编译器后门 操作系统后门 最常见的当然还是应用程序中留后门 还有就是潜伏于操作系统中的伪装为特定应用的专用后门程序 狭义后门概念: 特指潜伏于操作系统中专门做后门的一个程序 “坏人”可以连接这个程序 远程执行各种指令 概念和木马有重叠 1.2基础问题回答 例举你能想到的一个后门进入你系统中的可能方式 1.随意点击网页链接浏览未知内容的网页 2.打开邮箱中未知邮件,无意进入其链接 3.随意在网上下载未经认证的软件 4.使用游戏外挂 例举你知道的后门如何启动起来(win和Linu

  • pyhton:图像旋转

    最近一个作业中要用到图像旋转,分享一下学习过程。如有讲错的地方,恳请指正! 图像旋转,想想真简单啊,不就是将图像矩阵乘上一个旋转平移矩阵就完了吗?实际上还真没这么简单。首先这个旋转平移矩阵怎么获得?通过这篇博客我们能够轻松理解这个过程。http://www.cnblogs.com/xianglan/archive/2010/12/26/1917247.html 该旋转平移矩阵可以通过以图像左上角为原点的矩阵坐标系转换到以图像中心为原点的笛卡尔坐标系,再乘上一个旋转矩阵,再将旋转后的图像转换到原点矩阵坐标系。 当然,通过实验我也获得了该博客中所提到的蜂窝点。 当然要获得完整的图片,这篇博文也有程序。针对蜂窝点,我尝试中值滤波,但使用cv2.medianBlur(iLRotate30,3)对图像滤波时,总是报错 cv2.error:..\..\..\opencv-2.4.13\modules\imgproc\src\smooth.cpp:1695:error:(-210)infunctioncv::medianBlur复制 搞了半天,不知为啥,尴尬只好自己写了一个中值滤波。顺便把本文

相关推荐

推荐阅读