亚洲国产日韩欧美一区二区三区,精品亚洲国产成人av在线,国产99视频精品免视看7,99国产精品久久久久久久成人热,欧美日韩亚洲国产综合乱

Table of Contents
Preface
Normal assignment
Assignment of function parameters
Assignment of function return value
Covariance and contravariance in Java
How to implement covariance and contravariance with generics in Java
PECS
kotlin 中的協(xié)變與逆變
聲明處型變
類型投影
為什么要這么設(shè)計?
Summary
Home Java JavaBase Let's talk about kotlin's covariance and contravariance from Java

Let's talk about kotlin's covariance and contravariance from Java

Oct 13, 2020 am 11:14 AM
java kotlin

java Basic Tutorial column introduces covariance and contravariance of kotlin today.

Let's talk about kotlin's covariance and contravariance from Java

Preface

In order to better understand covariance and contravariance in kotlin and Java, let’s first look at some basic knowledge.

Normal assignment

In Java, the common assignment statements are as follows:

A a = b;復(fù)制代碼

The conditions that the assignment statement must meet are: the left side is either the parent class of the right side, or it is the same as the right side Same type. That is, the type of A must be "larger" than the type of B, such as Object o = new String("s");. For convenience, hereafter it is called A > B.

In addition to the most common assignment statements mentioned above, there are two other assignment statements:

Assignment of function parameters

public void fun(A a) {}// 調(diào)用處賦值B b = new B();
fun(b);復(fù)制代碼

When calling the fun(b) method, Assign the passed actual parameter B b to the formal parameter A a, that is, in the form A a = b. Similarly, the formal parameter type must be greater than the actual parameter, that is, A > B.

Assignment of function return value

public A fun() {
    B b = new B();    return b;
} 
復(fù)制代碼

The function return value type receives the value of the actual return type. The actual return type B b is equivalent to assigning a value to the function return value type A a, that is, B b assignment Given A a, that is, A a = b, then the type relationship of A > B must be satisfied.

So, no matter what kind of assignment, it must satisfy the left type > right type, that is, A > B.

Covariance and contravariance in Java

With the previous basic knowledge, covariance and contravariance can be easily explained.

If class A > class B, trans(A) and trans(B) obtained after changing trans still satisfy trans(A) > trans(B), then it is called agreement Change.

Contravariation is just the opposite. If class A > class B, the trans(A) and trans(B) obtained after a change trans satisfy trans(B) > trans(A), which is called Inverter.

For example, everyone knows that Java arrays are covariant. If A > B, then A[] > B[], so B[] can be assigned to A[]. For example:

Integer[] nums = new Integer[]{};
Object[] o = nums; // 可以賦值,因為數(shù)組的協(xié)變特性所以由 Object > Integer 得到 Object[] > Integer[]復(fù)制代碼

But Java's generics do not satisfy covariance, as follows:

List<Integer> l = new ArrayList<>();
List<Object> o = l;// 這里會報錯,不能編譯復(fù)制代碼

The above code reports an error because, although Object > Integer, the generics do not satisfy covariance. , so List<Object> > List<Integer> is not satisfied. Since the condition that the left side is greater than the right side is not satisfied, we know from the preface that naturally List< Integer> is assigned to List<Object>. It is generally said that Java generics do not support type variation.

How to implement covariance and contravariance with generics in Java

We know from the previous point that generics in Java do not support type changes, but this will produce a very strange Doubts are also mentioned in many articles about generics:

If B is a subclass of A, then List should be a subclass of List! This is a very natural idea!

But sorry, Java does not support it for various reasons. However, Java does not completely obliterate the variable characteristics of generics. Java provides and to make generics have covariance and contravariance characteristics.

and

are called upper bound wildcards, are called lower bound wildcards. Use upper bound wildcards to make generics covariant, and use lower bound wildcards to make generics contravariant.

For example, in the previous example

List<Integer> l = new ArrayList<>();
List<Object> o = l;// 這里會報錯,不能編譯復(fù)制代碼

If you use the upper bound wildcard,

List<Integer> l = new ArrayList<>();
List<? extends Object> o = l;// 可以通過編譯復(fù)制代碼

In this way, the type of List will be larger than the type of List<Integer> , thus achieving covariance. This is what is called "a subclass of a generic is a subclass of a generic."

Similarly, the lower bound wildcard character can achieve inversion, such as:

public List<? super Integer> fun(){
    List<Object> l = new ArrayList<>();    return l;
}復(fù)制代碼

How can the above code achieve inversion? First of all, Object > Integer; in addition, we know from the preface that the function return value type must be greater than the actual return value type, here it is List<? super Integer> > List<Object>, just the opposite of Object > Integer. In other words, after the generic change, the type relationship between Object and Integer is reversed. This is contravariance, and what implements contravariance is the lower bound wildcard .

As can be seen from the above, the upper bound in is T, which means that the types generally referred to by are all subclasses of T or T itself, so T is greater than . The lower bound in is T, that is to say, the types generally referred to by are the parent class of T or T itself, so is greater than T.

雖然 Java 使用通配符解決了泛型的協(xié)變與逆變的問題,但是由于很多講到泛型的文章都晦澀難懂,曾經(jīng)讓我一度感慨這 tm 到底是什么玩意?直到我在 stackoverflow 上發(fā)現(xiàn)了通俗易懂的解釋(是的,前文大部分內(nèi)容都來自于 stackoverflow 中大神的解釋),才終于了然。其實只要抓住賦值語句左邊類型必須大于右邊類型這個關(guān)鍵點一切就都很好懂了。

PECS

PECS 準則即 Producer Extends Consumer Super,生產(chǎn)者使用上界通配符,消費者使用下界通配符。直接看這句話可能會讓人很疑惑,所以我們追本溯源來看看為什么會有這句話。

首先,我們寫一個簡單的泛型類:

public class Container<T> {    private T item;    public void set(T t) { 
        item = t;
    }    public T get() {        return item;
    }
}復(fù)制代碼

然后寫出如下代碼:

Container<Object> c = new Container<String>(); // (1)編譯報錯Container<? extends Object> c = new Container<String>(); // (2)編譯通過c.set("sss"); // (3)編譯報錯Object o = c.get();// (4)編譯通過復(fù)制代碼

代碼 (1),Container<Object> c = new Container<String>(); 編譯報錯,因為泛型是不型變的,所以 Container 并不是 Container 的子類型,所以無法賦值。

代碼 (2),加了上界通配符以后,支持泛型協(xié)變,Container 就成了 Container 的子類型,所以編譯通過,可以賦值。

既然代碼 (2) 通過編譯,那代碼 (3) 為什么會報錯呢?因為代碼 (3) 嘗試把 String 類型賦值給 類型。顯然,編譯器只知道 是 Obejct 的某一個子類型,但是具體是哪一個并不知道,也許并不是 String 類型,所以不能直接將 String 類型賦值給它。

從上面可以看出,對于使用了 的類型,是不能寫入元素的,不然就會像代碼 (3) 處一樣編譯報錯。

但是可以讀取元素,比如代碼 (4) 。并且該類型只能讀取元素,這就是所謂的“生產(chǎn)者”,即只能從中讀取元素的就是生產(chǎn)者,生產(chǎn)者就使用 通配符。

消費者同理,代碼如下:

Container<String> c = new Container<Object>(); // (1)編譯報錯Container<? super String> c = new Container<Object>(); // (2)編譯通過
 c.set("sss");// (3) 編譯通過
 String s = c.get();// (4) 編譯報錯復(fù)制代碼

代碼 (1) 編譯報錯,因為泛型不支持逆變。而且就算不懂泛型,這個代碼的形式一眼看起來也是錯的。

代碼 (2) 編譯通過,因為加了 通配符后,泛型逆變。

代碼 (3) 編譯通過,它把 String 類型賦值給 , 泛指 String 的父類或 String,所以這是可以通過編譯的。

代碼 (4) 編譯報錯,因為它嘗試把 賦值給 String,而 大于 String,所以不能賦值。事實上,編譯器完全不知道該用什么類型去接受 c.get() 的返回值,因為在編譯器眼里 是一個泛指的類型,所有 String 的父類和 String 本身都有可能。

同樣從上面代碼可以看出,對于使用了 的類型,是不能讀取元素的,不然就會像代碼 (4) 處一樣編譯報錯。但是可以寫入元素,比如代碼 (3)。該類型只能寫入元素,這就是所謂的“消費者”,即只能寫入元素的就是消費者,消費者就使用 通配符。

綜上,這就是 PECS 原則。

kotlin 中的協(xié)變與逆變

kotlin 拋棄了 Java 中的通配符,轉(zhuǎn)而使用了聲明處型變類型投影。

聲明處型變

首先讓我們回頭看看 Container 的定義:

public class Container<T> {    private T item;    public void set(T t) { 
        item = t;
    }    public T get() {        return item;
    }
}復(fù)制代碼

在某些情況下,我們只會使用 Container<? extends T> 或者 Container<? super T> ,意味著我們只使用 Container 作為生產(chǎn)者或者 Container 作為消費者。

既然如此,那我們?yōu)槭裁匆诙x Container 這個類的時候要把 get 和 set 都定義好呢?試想一下,如果一個類只有消費者的作用,那定義 get 方法完全是多余的。

反過來說,如果一個泛型類只有生產(chǎn)者方法,比如下面這個例子(來自 kotlin 官方文檔):

// Javainterface Source<T> {
  T nextT(); // 只有生產(chǎn)者方法}// Javavoid demo(Source<String> strs) {
  Source<Object> objects = strs; // ?。?!在 Java 中不允許,要使用上界通配符 <? extends Object>
  // ……}復(fù)制代碼

Source<Object> 類型的變量中存儲 Source<String> 實例的引用是極為安全的——因為沒有消費者-方法可以調(diào)用。然而 Java 依然不讓我們直接賦值,需要使用上界通配符。

但是這是毫無意義的,使用通配符只是把類型變得更復(fù)雜,并沒有帶來額外的價值,因為能調(diào)用的方法還是只有生產(chǎn)者方法。但 Java 編譯器只認死理。

所以,如果我們能在使用之前確定一個類是生產(chǎn)者還是消費者,那在定義類的時候直接聲明它的角色豈不美哉?

這就是 kotlin 的聲明處型變,直接在類聲明的時候,定義它的型變行為。

比如:

class Container<out T> { // (1)
    private  var item: T? = null 
        
    fun get(): T? = item
}

val c: Container<Any> = Container<String>()// (2)編譯通過,因為 T 是一個 out-參數(shù)復(fù)制代碼

(1) 處直接使用 指定 T 類型只能出現(xiàn)在生產(chǎn)者的位置上。雖然多了一些限制,但是,在 kotlin 編譯器在知道了 T 的角色以后,就可以像 (2) 處一樣將 Container 直接賦值給 Container,好像泛型直接可以協(xié)變了一樣,而不需要再使用 Java 當中的通配符

同樣的,對于消費者來說,

class Container<in T> { // (1) 
    private  var item: T? = null 
     fun set(t: T) {
        item = t
    }
}val c: Container<String> = Container<Any>() // (2) 編譯通過,因為 T 是一個 in-參數(shù)復(fù)制代碼

代碼 (1) 處使用 指定 T 類型只能出現(xiàn)在消費者的位置上。代碼 (2) 可以編譯通過, Any > String,但是 Container 可以被 Container 賦值,意味著 Container 大于 Container ,即它看上去就像 T 直接實現(xiàn)了泛型逆變,而不需要借助 通配符來實現(xiàn)逆變。如果是 Java 代碼,則需要寫成 Container<? super String> c = new Container<Object>(); 。

這就是聲明處型變,在類聲明的時候使用 out 和 in 關(guān)鍵字,在使用時可以直接寫出泛型型變的代碼。

而 Java 在使用時必須借助通配符才能實現(xiàn)泛型型變,這是使用處型變

類型投影

有時一個類既可以作生產(chǎn)者又可以作消費者,這種情況下,我們不能直接在 T 前面加 in 或者 out 關(guān)鍵字。比如:

class Container<T> {    private  var item: T? = null
    
    fun set(t: T?) {
        item = t
    }    fun get(): T? = item
}復(fù)制代碼

考慮這個函數(shù):

fun copy(from: Container<Any>, to: Container<Any>) {
    to.set(from.get())
}復(fù)制代碼

當我們實際使用該函數(shù)時:

val from = Container<Int>()val to = Container<Any>()
copy(from, to) // 報錯,from 是 Container<Int> 類型,而 to 是 Container<Any> 類型復(fù)制代碼

Lets talk about kotlins covariance and contravariance from Java

這樣使用的話,編譯器報錯,因為我們把兩個不一樣的類型做了賦值。用 kotlin 官方文檔的話說,copy 函數(shù)在”干壞事“, 它嘗試一個 Any 類型的值給 from, 而我們用 Int 類型來接收這個值,如果編譯器不報錯,那么運行時將會拋出一個 ClassCastException 異常。

所以應(yīng)該怎么辦?直接防止 from 被寫入就可以了!

將 copy 函數(shù)改為如下所示:

fun copy(from: Container<out Any>, to: Container<Any>) { // 給 from 的類型加了 out
    to.set(from.get())
}val from = Container<Int>()val to = Container<Any>()
copy(from, to) // 不會再報錯了復(fù)制代碼

這就是類型投影:from 是一個類受限制的(投影的)Container 類,我們只能把它當作生產(chǎn)者來使用,它只能調(diào)用 get() 方法。

同理,如果 from 的泛型是用 in 來修飾的話,則 from 只能被當作消費者使用,它只能調(diào)用 set() 方法,上述代碼就會報錯:

fun copy(from: Container<in Any>, to: Container<Any>) { // 給 from 的類型加了 in
    to.set(from.get())
}val from = Container<Int>()val to = Container<Any>()
copy(from, to) //  報錯復(fù)制代碼

Lets talk about kotlins covariance and contravariance from Java

其實從上面可以看到,類型投影和 Java 的通配符很相似,也是一種使用時型變

為什么要這么設(shè)計?

為什么 Java 的數(shù)組是默認型變的,而泛型默認不型變呢?其實 kolin 的泛型默認也是不型變的,只是使用 out 和 in 關(guān)鍵字讓它看起來像泛型型變。

為什么這么設(shè)計呢?為什么不默認泛型可型變呢?

在 stackoverflow 上找到了答案,參考:stackoverflow.com/questions/1…

Java 和 C# 早期都是沒有泛型特性的。

但是為了支持程序的多態(tài)性,于是將數(shù)組設(shè)計成了協(xié)變的。因為數(shù)組的很多方法應(yīng)該可以適用于所有類型元素的數(shù)組。

比如下面兩個方法:

boolean equalArrays (Object[] a1, Object[] a2);void shuffleArray(Object[] a);復(fù)制代碼

第一個是比較數(shù)組是否相等;第二個是打亂數(shù)組順序。

語言的設(shè)計者們希望這些方法對于任何類型元素的數(shù)組都可以調(diào)用,比如我可以調(diào)用 shuffleArray(String[] s) 來把字符串數(shù)組的順序打亂。

出于這樣的考慮,在 Java 和 C# 中,數(shù)組設(shè)計成了協(xié)變的。

然而,對于泛型來說,卻有以下問題:

// Illegal code - because otherwise life would be BadList<Dog> dogs = new List<Dog>();
List<Animal> animals = dogs; // Awooga awoogaanimals.add(new Cat());// (1)Dog dog = dogs.get(0); //(2) This should be safe, right?復(fù)制代碼

如果上述代碼可以通過編譯,即 List 可以賦值給 List,List 是協(xié)變的。接下來往 List 中 add 一個 Cat(),如代碼 (1) 處。這樣就有可能造成代碼 (2) 處的接收者 Dog dogdogs.get(0) 的類型不匹配的問題。會引發(fā)運行時的異常。所以 Java 在編譯期就要阻止這種行為,把泛型設(shè)計為默認不型變的。

Summary

1. Java generics are not mutable by default, so List is not a subclass of List. If you want to implement generic type variation, you need extends T> and super T> wildcards, which is a way to use type variation. Using the extends T> wildcard means that the class is a producer and can only call methods like get(): T . Using the super T> wildcard means that the class is a consumer and can only call methods such as set(T t) and add(T t).

2. Kotlin generics are not variable by default. However, using the out and in keywords to change the type at the class declaration can achieve the effect of direct type change at the point of use. But this will limit the class to be either a producer or a consumer when declared.

Using type projection can avoid the class being restricted when it is declared, but when using it, you must use the out and in keywords to indicate whether the role of the class at this moment is a consumer or a producer. Type projection is also a method of using type changes.

Related free learning recommendations: java basic tutorial

The above is the detailed content of Let's talk about kotlin's covariance and contravariance from Java. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undress AI Tool

Undress AI Tool

Undress images for free

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

Hot Topics

PHP Tutorial
1488
72
VSCode settings.json location VSCode settings.json location Aug 01, 2025 am 06:12 AM

The settings.json file is located in the user-level or workspace-level path and is used to customize VSCode settings. 1. User-level path: Windows is C:\Users\\AppData\Roaming\Code\User\settings.json, macOS is /Users//Library/ApplicationSupport/Code/User/settings.json, Linux is /home//.config/Code/User/settings.json; 2. Workspace-level path: .vscode/settings in the project root directory

How to handle transactions in Java with JDBC? How to handle transactions in Java with JDBC? Aug 02, 2025 pm 12:29 PM

To correctly handle JDBC transactions, you must first turn off the automatic commit mode, then perform multiple operations, and finally commit or rollback according to the results; 1. Call conn.setAutoCommit(false) to start the transaction; 2. Execute multiple SQL operations, such as INSERT and UPDATE; 3. Call conn.commit() if all operations are successful, and call conn.rollback() if an exception occurs to ensure data consistency; at the same time, try-with-resources should be used to manage resources, properly handle exceptions and close connections to avoid connection leakage; in addition, it is recommended to use connection pools and set save points to achieve partial rollback, and keep transactions as short as possible to improve performance.

Mastering Dependency Injection in Java with Spring and Guice Mastering Dependency Injection in Java with Spring and Guice Aug 01, 2025 am 05:53 AM

DependencyInjection(DI)isadesignpatternwhereobjectsreceivedependenciesexternally,promotingloosecouplingandeasiertestingthroughconstructor,setter,orfieldinjection.2.SpringFrameworkusesannotationslike@Component,@Service,and@AutowiredwithJava-basedconfi

How to work with Calendar in Java? How to work with Calendar in Java? Aug 02, 2025 am 02:38 AM

Use classes in the java.time package to replace the old Date and Calendar classes; 2. Get the current date and time through LocalDate, LocalDateTime and LocalTime; 3. Create a specific date and time using the of() method; 4. Use the plus/minus method to immutably increase and decrease the time; 5. Use ZonedDateTime and ZoneId to process the time zone; 6. Format and parse date strings through DateTimeFormatter; 7. Use Instant to be compatible with the old date types when necessary; date processing in modern Java should give priority to using java.timeAPI, which provides clear, immutable and linear

Understanding the Java Virtual Machine (JVM) Internals Understanding the Java Virtual Machine (JVM) Internals Aug 01, 2025 am 06:31 AM

TheJVMenablesJava’s"writeonce,runanywhere"capabilitybyexecutingbytecodethroughfourmaincomponents:1.TheClassLoaderSubsystemloads,links,andinitializes.classfilesusingbootstrap,extension,andapplicationclassloaders,ensuringsecureandlazyclassloa

Google Chrome cannot open local files Google Chrome cannot open local files Aug 01, 2025 am 05:24 AM

ChromecanopenlocalfileslikeHTMLandPDFsbyusing"Openfile"ordraggingthemintothebrowser;ensuretheaddressstartswithfile:///;2.SecurityrestrictionsblockAJAX,localStorage,andcross-folderaccessonfile://;usealocalserverlikepython-mhttp.server8000tor

Understanding Network Ports and Firewalls Understanding Network Ports and Firewalls Aug 01, 2025 am 06:40 AM

Networkportsandfirewallsworktogethertoenablecommunicationwhileensuringsecurity.1.Networkportsarevirtualendpointsnumbered0–65535,withwell-knownportslike80(HTTP),443(HTTPS),22(SSH),and25(SMTP)identifyingspecificservices.2.PortsoperateoverTCP(reliable,c

Comparing Java Frameworks: Spring Boot vs Quarkus vs Micronaut Comparing Java Frameworks: Spring Boot vs Quarkus vs Micronaut Aug 04, 2025 pm 12:48 PM

Pre-formanceTartuptimeMoryusage, Quarkusandmicronautleadduetocompile-Timeprocessingandgraalvsupport, Withquarkusoftenperforminglightbetterine ServerLess scenarios.2.Thyvelopecosyste,

See all articles