Nota 12 do Kotlin - Comparação de paradigmas em Java e Kotlin (1)

O paradigma no Kotlin é semelhante ao do Java. Você pode consultar o meu artigo sobre a introdução do paradigma Java:

Paradigma Java essas coisas (1)

Paradigma Java essas coisas (2)

Paradigma Java essas coisas (3)

Java Paradigma Essas Coisas (4)

No post acima, ele explica por que o Java deve introduzir o paradigma na versão 1.5 e alguns pontos de conhecimento básico sobre o paradigma Java.


Se você dividir um objeto em uma declaração e usá-lo em duas partes. Os genéricos concentram-se principalmente na reutilização de código de declarações de tipo e os curingas se concentram na reutilização de código em uso. Os genéricos são usados ​​para definir a parametrização dos tipos de dados internos e os curingas são usados ​​para definir a parametrização dos tipos de objetos utilizados.

O uso de genéricos e curingas aprimora a reutilização de código. Ao mesmo tempo, o tipo do objeto é verificado quanto à segurança do tipo, reduzindo erros durante o processo de conversão do tipo.


 Alterações de paradigma e tipo de matriz

 Matrizes em Java são covariantes

O código a seguir pode ser compilado e executado corretamente:

        Integer[] ints = new Integer[3];
        ints[0] = 0;
        ints[1] = 1;
        ints[2] = 2;
        Number[] numbers = new Number[3];
        numbers = ints;
        for (Number n : numbers) {
            System.out.println(n);
        }

Em Java, como Integer é um subtipo de Number, o tipo de matriz Integer [] também é um subtipo de Number [], portanto, um valor Inteiro [] pode ser fornecido sempre que o valor Number [] for necessário.

Java não é covariante para listar genéricos <T>

Em outras palavras, a lista <número> não é um subtipo da lista <número> e tentar fornecer a lista <número> onde a lista <número> é necessária é um erro de tipo. O código a seguir, o compilador relatará diretamente um erro:

Mesmo se usarmos caracteres curinga, escreva:

 Ainda é um erro.

Por que o objeto Number pode ser instanciado por Inteiro, mas o objeto ArrayList <Number> não pode ser instanciado por ArrayList <Integer>? O <? Extends Number> na lista declara que seu elemento é uma classe derivada de Number ou Number.Por que não pode adicionar Inteiro? Para resolver esses problemas, você precisa entender a inversão e covariância em Java e o uso de curingas em genéricos.

Inversão, covariância e invariância são usadas para descrever o relacionamento de herança após a transformação do tipo Sua definição: se A e B representam tipos, f (⋅) representa a conversão de tipos e ≤ representa o relacionamento de herança (por exemplo, A ≤B significa A é uma subclasse derivada de B)

  •    Quando A≤B, f (A) ≤f ​​(B) se mantém, então f (⋅) é covariante
  •    Quando A≤B, f (B) ≤f (A) se mantém, então f (⋅) é contravariante
  •    Quando A≤B, as duas fórmulas acima não são verdadeiras, ou seja, f (A) ef (B) não têm relação de herança entre si ef (⋅) é invariável

Tanto a covariância como a covariância inversa são de tipo seguro.

 

A matriz de Kotlin não é covariante

abstract class Animal(val size: Int)
class Dog(val cuteness: Int): Animal(100)
class Cat(val terrorFactor: Int): Animal(1)

O seguinte erro de compilação da matriz:

val dogArr: Array<Dog> = arrayOf(Dog(1), Dog(2))
val animalArr: Array<Animal> = dogArr

Como objetos comuns em Java, o seguinte código pode ser compilado:

val dog: Dog = Dog(10)
var animal: Animal = dog

No Kotlin, uma classe de paradigma é definida da seguinte maneira e, em seguida, será compilada com erros quando usada:

class ReadableList<T>{

}

val dogReadable: ReadableList<Dog> = ReadableList()
 //提示报错,需要ReadableList<Animal>,但却传了ReadableList<Dog>
val animalReadable: ReadableList<Animal> = dogReadable

Kotlin é covariante para listar genéricos <T>

Ou seja, o código a seguir pode ser compilado através

val dogList: List<Dog> = listOf(Dog(10), Dog(20))
playAnimal(dogList)

fun playAnimal(animalList: List<Animal>) {
    ...
}

Como fazer com que Java e Kotlin adicionem suporte a covariantes e inversores

Os genéricos em Java permanecem inalterados, mas às vezes você precisa implementar inversão e covariância, o que você deve fazer? No momento, precisamos usar os curingas de que falamos antes ? .

No Java e Kotlin, você pode adicionar suporte para tipos de parâmetros que não suportam covariância por padrão. Mas as duas linguagens, Java e Kotlin, são tratadas de maneira diferente:

  •    Java: variação do site de uso
  •    Kotlin: variação do local da declaração

Pode-se observar que Java usa alterações de tipo de terminal, enquanto o Kotlin usa tipos de terminal declarativos. Qual é a diferença entre os dois?

O entendimento pessoal é que o uso da variação do site final (variação do site de uso) é covariável ao usar especificamente (inicializar) um objeto de Classe.

Java <? extends T>implementa covariância genérica

List<? extends Number> list = new ArrayList<>();  

A ? extends Numberrepresentação aqui é a classe Number ou suas subclasses, que abreviamos como C.

Aqui C <= Number, esta relação mantém List<C> <= List< Number >verdadeiro: . Existem:

List<? extends Number> list1 = new ArrayList<Integer>();  
List<? extends Number> list2 = new ArrayList<Float>();  

Outro exemplo, conforme mostrado no código a seguir:

List<Cat> catList = new ArrayList<>();
List<? extends Animal> animalList = catList;

Como você pode ver, quando declaramos animalList, fizemos uma pequena modificação no tipo genérico.Depois de usar ? Extends Animal para modificar, o código acima pode ser compilado e executado com êxito. Mesmo podemos definir um método para aceitar esse tipo de parâmetro da seguinte maneira:

List<Cat> cats = new ArrayList<>();
playAnimal(cats);

public static void playAnimal(List<? extends Animal> animal) {
    ...
}

A compilação pode ser passada sem problemas, portanto, nosso código será mais escalável!

⚠️ Nota: Neste momento, para além do nulo, o objecto não pode ser adicionado a qualquer animal subclasse animalList, isto é, tendo em conta o seguinte código:

如果可以添加的话,List<? extends Number>Ele conterá objetos de vários subtipos de números (Byte, Inteiro, Flutuante, Duplo, etc.). Para proteger sua consistência de tipo, o Java proíbe adicionar objetos à Lista <? Extends Number>, mas nulo pode ser adicionado.

Java <? super T>implementa inversão genérica

List<? super Number> list = new ArrayList<>();  

? super Number Os curingas indicam que o limite inferior do tipo é Número. Ou seja, o tipo pai F está aqui ? super Numbere o tipo filho C é Number. Ou seja, quando F <= C, existe f (C) <= f (F), essa é a inversão. Exemplo de código:

List<? super Number> list3 = new ArrayList<Number>();  
List<? super Number> list4 = new ArrayList<Object>();  
list3.add(new Integer(3));  
list4.add(new Integer(4)); 

Em outras palavras, não podemos List<? super Number >adicionar nenhum objeto pai de Number a ele. Mas você pode adicionar Number e seus objetos de subclasse à List <? Super Number>.

 

PECS: Quando usar estende? Quando usar super?

Joshua Bloch se refere a objetos nos quais você só pode ler como produtores e objetos nos quais você pode escrever apenas como consumidores . Ele sugeriu: " Para maximizar a flexibilidade, use tipos curinga em parâmetros de entrada que representam produtores ou consumidores " e propôs as seguintes mnemônicas:

PECS significa Producer-Extens, Consumer-Super (Produtor-Extends, Consumer-Super).

Nota : Se você usar um objeto produtor, por exemplo  List<? extends Foo>, a chamada add() ou  o objeto não será permitido  set(). Mas isso não significa que o objeto seja imutável : por exemplo, nada impede que você chame  clear()para excluir todos os itens da lista, porque  clear() nenhum parâmetro é necessário. A única coisa garantida pelos curingas (ou outros tipos de alterações de tipo) é a segurança do tipo . A imutabilidade é outra questão inteiramente.

 

Declarar alteração de tipo

Suponha que exista uma interface genérica  Source<T>, não há T método como parâmetro na interface  , mas o método retorna o T valor do  tipo:

// Java
interface Source<T> {
    T nextT();
}

Portanto,   é extremamente seguro Source <Object> armazenar Source <String>a referência da instância em uma variável do tipo  -no consumer-method pode ser chamado. Mas Java não sabe disso e ainda proíbe tais operações:

// Java
void demo(Source<String> strs) {
    Source<Object> objects = strs; // !!!在 Java 中不允许
    // ……
}

Para corrigir isso, devemos declarar o tipo do objeto como  Source<? extends Object>, o que não faz sentido, porque podemos chamar os mesmos métodos no objeto como antes, para que tipos mais complexos não tragam valor. Mas o compilador não sabe.

No Kotlin, há uma maneira de explicar isso ao compilador. Isso é chamado de alteração do tipo de declaração : podemos marcar  Source o parâmetro type T  para garantir que ele seja retornado (produzido) apenas pelos  Source<T> membros e nunca consumido. Para isso, fornecemos o   modificador out :

interface Source<out T> {
    fun nextT(): T
}

fun demo(strs: Source<String>) {
    val objects: Source<Any> = strs // 这个没问题,因为 T 是一个 out-参数
    // ……
}

O princípio geral é: quando C o parâmetro de tipo de  uma classe  T é declarado como  desativado  , ele pode aparecer apenas na posição de  saídaC  do membro , mas o retorno é uma superclasse que   pode ser usada com segurança  .C<Base>C<Derived>

Em resumo, eles dizem que a classe  C está no parâmetro  T em um covariante ou  T uma covariante parâmetro de tipo. Você pode pensar  C é  T o produtor , não  T o consumidor .

O modificador de saída é chamado de anotação de alteração de tipo e , como é fornecido na declaração de parâmetro de tipo, falaremos sobre a declaração de alteração de tipo . Isso é o oposto do uso de alteração de tipo por Java , cujo tipo de uso de curingas cria covariância de tipo.

Além de  sair , Kotlin adicionou um comentário de alteração de tipo: in . Faz uma inversão de parâmetro de tipo : só pode ser consumida, mas não produzida. Um bom exemplo do tipo de inversor é  Comparable:

interface Comparable<in T> {
    operator fun compareTo(other: T): Int
}

fun demo(x: Comparable<Number>) {
    x.compareTo(1.0) // 1.0 拥有类型 Double,它是 Number 的子类型
    // 因此,我们可以将 x 赋给类型为 Comparable <Double> 的变量
    val y: Comparable<Double> = x // OK!
}

Acreditamos que as   palavras dentro  e  fora são auto-explicativas (porque elas foram usadas com sucesso em C # por um longo tempo), portanto as mnemônicas mencionadas acima não são realmente necessárias e podem ser reescritas para um objetivo maior:

A  transformação existencial : consumidor entra , produtor sai!  :-)

 

Identificador ao definir uma classe, da seguinte maneira:

// 使用out关键字
class ReadableList<out T>{

}

val dogReadable: ReadableList<Dog> = ReadableList()
val animalReadable: ReadableList<Animal> = dogReadable

O código acima única diferença agora é que definimos antes ReadableList é adicionar um pouco restrições genéricas  OUT , então você pode ser bem sucedida atribuição dogReadable para animalReadable objeto. Devemos ver isso antes Kotlin API Por que pode adivinhar o A lista <generic> suporta covariância.

Nota: Mas depois de modificado com a palavra-chave out, não pode haver métodos com T como o tipo de parâmetro dentro da classe ReadableList

 

 


Referência: 

 

Publicado 82 artigos originais · Gosto 86 · Visite mais de 110.000

Acho que você gosta

Origin blog.csdn.net/unicorn97/article/details/81843897
Recomendado
Clasificación