ความแตกต่างระหว่าง Arrays.asList (อาร์เรย์) และ ArrayList ใหม่ <Integer> (Arrays.asList (array))


119

อะไรคือความแตกต่างระหว่าง

1.List<Integer> list1 = new ArrayList<Integer>(Arrays.asList(ia));  //copy
2.List<Integer> list2 = Arrays.asList(ia);

ที่iaเป็นอาร์เรย์ของจำนวนเต็ม

ฉันทราบมาว่าการดำเนินการบางอย่างไม่ได้รับอนุญาตในlist2. ทำไมถึงเป็นเช่นนั้น? เก็บไว้ในหน่วยความจำอย่างไร (อ้างอิง / คัดลอก)?

เมื่อฉันสับเปลี่ยนรายการlist1ไม่ส่งผลกระทบต่ออาร์เรย์เดิม แต่list2ทำ แต่ก็ยังlist2ค่อนข้างสับสน

การArrayListอัปเดตรายการแตกต่างจากการสร้างใหม่อย่างไรArrayList

list1 differs from (1)
ArrayList<Integer> list1 = new ArrayList<Integer>(Arrays.asList(ia));

2
ฉันขอแนะนำให้คุณดูแลตัวเลือกของ Google Guava Lists.newArrayList(ia)ทำสำเนาอิสระเช่นเดียวกับตัวเลือกแรก มันเป็นเรื่องทั่วไปและดีกว่าที่จะมอง
qben

คำตอบ:


228
  1. ก่อนอื่นมาดูว่าสิ่งนี้ทำอะไร:

    Arrays.asList(ia)

    ใช้อาร์เรย์iaและสร้าง Wrapper ที่ใช้งานList<Integer>ซึ่งทำให้อาร์เรย์ดั้งเดิมพร้อมใช้งานเป็นรายการ ไม่มีสิ่งใดถูกคัดลอกและทั้งหมดมีเพียงออบเจ็กต์ Wrapper เดียวเท่านั้นที่ถูกสร้างขึ้น การดำเนินการบน list wrapper จะแพร่กระจายไปยังอาร์เรย์ดั้งเดิม ซึ่งหมายความว่าหากคุณสับเปลี่ยนรายการ Wrapper อาร์เรย์เดิมจะถูกสับเช่นกันหากคุณเขียนทับองค์ประกอบจะถูกเขียนทับในอาร์เรย์เดิมเป็นต้นแน่นอนว่าListการดำเนินการบางอย่างไม่ได้รับอนุญาตใน Wrapper เช่นการเพิ่มหรือ การลบองค์ประกอบออกจากรายการคุณสามารถอ่านหรือเขียนทับองค์ประกอบได้เท่านั้น

    โปรดทราบว่า list wrapper ไม่ได้ขยายออกไปArrayList- เป็นอ็อบเจ็กต์ประเภทอื่น ArrayLists มีอาร์เรย์ภายในเป็นของตัวเองซึ่งเก็บองค์ประกอบไว้และสามารถปรับขนาดอาร์เรย์ภายในได้เป็นต้น wrapper ไม่มีอาร์เรย์ภายในของตัวเอง แต่จะเผยแพร่การดำเนินการไปยังอาร์เรย์ที่กำหนดให้เท่านั้น

  2. ในทางกลับกันหากคุณสร้างอาร์เรย์ใหม่ในภายหลังเป็น

    new ArrayList<Integer>(Arrays.asList(ia))

    จากนั้นคุณสร้างใหม่ArrayListซึ่งเป็นสำเนาต้นฉบับที่เป็นอิสระเต็มรูปแบบ แม้ว่าที่นี่คุณจะสร้างกระดาษห่อตัวโดยใช้Arrays.asListเช่นกัน แต่จะใช้เฉพาะในระหว่างการสร้างใหม่ArrayListและจะถูกเก็บรวบรวมในภายหลัง โครงสร้างของใหม่ArrayListนี้ไม่ขึ้นกับอาร์เรย์เดิมโดยสิ้นเชิง ประกอบด้วยองค์ประกอบเดียวกัน (ทั้งอาร์เรย์เดิมและใหม่นี้ArrayListอ้างอิงจำนวนเต็มเดียวกันในหน่วยความจำ) แต่จะสร้างอาร์เรย์ภายในใหม่ที่เก็บการอ้างอิง ดังนั้นเมื่อคุณสับเปลี่ยนเพิ่มลบองค์ประกอบ ฯลฯ อาร์เรย์เดิมจะไม่เปลี่ยนแปลง


9
@Dineshkumar wrapper คือรูปแบบการออกแบบที่แปลอินเทอร์เฟซหนึ่งสำหรับคลาสเป็นอินเทอร์เฟซอื่น ดูบทความรูปแบบกระดาษห่อหุ้ม | คุณต้องอัปเดตที่ไหน ฉันขอแนะนำให้ใช้List<Integer>สำหรับประเภทตัวแปรของคุณ (หรืออาร์กิวเมนต์วิธีการ ฯลฯ ) สิ่งนี้ทำให้โค้ดของคุณมีความกว้างมากขึ้นและคุณสามารถเปลี่ยนไปListใช้งานอื่นได้ตามต้องการโดยไม่ต้องเขียนโค้ดซ้ำมากนัก
Petr Pudlák

นั่นคือคำอธิบายที่ดี การขยายคำถามนี้วิธี Arrays.asList () ยังใช้ varargs ถ้าฉันส่งผ่านค่าที่เฉพาะเจาะจงให้บอกว่าฉันทำ: Arrays.asList (1,3,5) สองครั้งมันจะส่งคืน List เดียวกันหรือไม่
ออกอากาศ

27

อย่างนี้เป็นเพราะArrayListเกิดจากการไม่ได้เป็นประเภทArrays.asList() สร้างประเภทที่ไม่ขยายแต่ขยายเท่านั้นjava.util.ArrayListArrays.asList()ArrayListjava.util.Arrays$ArrayListjava.util.ArrayListjava.util.AbstractList


9
List<Integer> list1 = new ArrayList<Integer>(Arrays.asList(ia));  //copy

ในกรณีนี้เป็นประเภทlist1ArrayList

List<Integer> list2 = Arrays.asList(ia);

ที่นี่รายการจะถูกส่งกลับเป็นListมุมมองซึ่งหมายความว่ามีเฉพาะวิธีการที่แนบมากับอินเทอร์เฟซนั้น list2ดังนั้นทำไมวิธีการบางอย่างที่ไม่ได้รับอนุญาตบน

ArrayList<Integer> list1 = new ArrayList<Integer>(Arrays.asList(ia));

ที่นี่คุณกำลังสร้างไฟล์ArrayList. คุณเพียงแค่ส่งค่าในตัวสร้าง นี่ไม่ใช่ตัวอย่างของการคัดเลือกนักแสดง ในการคัดเลือกนักแสดงอาจมีลักษณะดังนี้:

ArrayList list1 = (ArrayList)Arrays.asList(ia);

4

ฉันมาสายพอสมควรอย่างไรก็ตามรู้สึกว่าการอธิบายด้วยการอ้างอิงเอกสารจะดีกว่าสำหรับคนที่กำลังมองหาคำตอบ

  1. java.util.Arrays
  • นี่คือคลาสยูทิลิตี้ที่มีวิธีการแบบคงที่เพื่อใช้งานกับอาร์เรย์ที่กำหนด
  • asListเป็นวิธีการแบบคงที่ที่ใช้อินพุตอาร์เรย์และส่งคืนอ็อบเจ็กต์ของjava.util.Arrays.ArrayListซึ่งเป็นคลาสที่ซ้อนกันแบบคงที่ที่ขยาย AbstractList ซึ่งจะนำอินเทอร์เฟซรายการไปใช้
  • ดังนั้น Arrays.asList (inarray) จะส่งคืน List wrapper รอบ ๆ อาร์เรย์อินพุต แต่ wrapper นี้คือjava.util.Arrays.ArrayList ไม่ใช่ java.util.ArrayListและอ้างอิงถึงอาร์เรย์เดียวกันดังนั้นการเพิ่มองค์ประกอบเพิ่มเติมในรายการอาร์เรย์ที่ห่อจะมีผลต่อ orignal ด้วยและเราไม่สามารถเปลี่ยนความยาวได้
  1. java.util.ArrayList
  • ArrayList มีคอนสตรัคเตอร์มากเกินไป

    Public ArrayList () - // ส่งคืนอาร์เรย์ด้วยความจุเริ่มต้น 10

    ArrayList สาธารณะ (คอลเลกชัน c)

    ArrayList สาธารณะ (int initialCapacity)

  • ดังนั้นเมื่อเราส่ง Arrays.asList ที่ส่งคืนวัตถุเช่น List (AbstractList) ไปยังตัวสร้างที่สองด้านบนมันจะสร้างอาร์เรย์แบบไดนามิกใหม่ (ขนาดอาร์เรย์นี้จะเพิ่มขึ้นเมื่อเราเพิ่มองค์ประกอบมากกว่าความจุและองค์ประกอบใหม่จะไม่ส่งผลกระทบต่ออาร์เรย์ดั้งเดิม ) การคัดลอกอาร์เรย์ดั้งเดิมแบบตื้น (การคัดลอกแบบตื้นหมายถึงการคัดลอกทับการอ้างอิงเท่านั้นและไม่สร้างชุดของวัตถุใหม่เช่นเดียวกับในอาร์เรย์ orignal)


4
String names[] = new String[]{"Avinash","Amol","John","Peter"};
java.util.List<String> namesList = Arrays.asList(names);

หรือ

String names[] = new String[]{"Avinash","Amol","John","Peter"};
java.util.List<String> temp = Arrays.asList(names);         

คำสั่งด้านบนจะเพิ่ม wrapper บนอาร์เรย์อินพุต ดังนั้นวิธีการเช่น add & remove จะไม่สามารถใช้ได้กับวัตถุอ้างอิงรายการ 'namesList'

หากคุณพยายามเพิ่มองค์ประกอบในอาร์เรย์ / รายการที่มีอยู่คุณจะได้รับ "Exception in thread" main "java.lang.UnsupportedOperationException"

การดำเนินการข้างต้นเป็นแบบอ่านอย่างเดียวหรือดูอย่างเดียว
เราไม่สามารถดำเนินการเพิ่มหรือลบในรายการวัตถุได้ แต่

String names[] = new String[]{"Avinash","Amol","John","Peter"};
java.util.ArrayList<String> list1 = new ArrayList<>(Arrays.asList(names));

หรือ

String names[] = new String[]{"Avinash","Amol","John","Peter"};
java.util.List<String> listObject = Arrays.asList(names);
java.util.ArrayList<String> list1 = new ArrayList<>(listObject);

ในข้อความข้างต้นคุณได้สร้างตัวอย่างที่เป็นรูปธรรมของคลาส ArrayList และส่งผ่านรายการเป็นพารามิเตอร์

ในกรณีนี้วิธีการเพิ่มและลบจะทำงานได้อย่างถูกต้องเนื่องจากทั้งสองวิธีมาจากคลาส ArrayList ดังนั้นที่นี่เราจะไม่ได้รับ UnSupportedOperationException
การเปลี่ยนแปลงในวัตถุ Arraylist (วิธีการเพิ่มหรือลบองค์ประกอบใน / จากรายการอาร์เรย์) จะไม่สะท้อนไปยังวัตถุ java.util.List ดั้งเดิม

String names[] = new String[] {
    "Avinash",
    "Amol",
    "John",
    "Peter"
};

java.util.List < String > listObject = Arrays.asList(names);
java.util.ArrayList < String > list1 = new ArrayList < > (listObject);
for (String string: list1) {
    System.out.print("   " + string);
}
list1.add("Alex"); //Added without any exception
list1.remove("Avinash"); //Added without any exception will not make any changes in original list in this case temp object.


for (String string: list1) {
    System.out.print("   " + string);
}
String existingNames[] = new String[] {
    "Avinash",
    "Amol",
    "John",
    "Peter"
};
java.util.List < String > namesList = Arrays.asList(names);
namesList.add("Bob"); // UnsupportedOperationException occur
namesList.remove("Avinash"); //UnsupportedOperationException

3

อันดับแรกของคลาส Arrays คือคลาสยูทิลิตี้ที่ไม่มี ของวิธีการยูทิลิตี้ในการทำงานบน Arrays (ขอบคุณคลาส Arrays ไม่เช่นนั้นเราจะต้องสร้างวิธีการของเราเองเพื่อดำเนินการกับวัตถุ Array)

asList () วิธีการ:

  1. asListเมธอดเป็นหนึ่งในวิธียูทิลิตี้ของArrayคลาสซึ่งเป็นวิธีการแบบคงที่ซึ่งเป็นเหตุผลที่เราสามารถเรียกเมธอดนี้ด้วยชื่อคลาส (เช่นArrays.asList(T...a))
  2. ตอนนี้คือการบิดโปรดทราบว่าวิธีนี้ไม่ได้สร้างArrayListวัตถุใหม่เพียงแค่ส่งคืนการอ้างอิงรายการไปยังArrayวัตถุที่มีอยู่(ดังนั้นหลังจากใช้asListวิธีการแล้วการอ้างอิงสองรายการไปยังArrayวัตถุที่มีอยู่จะถูกสร้างขึ้น)
  3. และนี่คือเหตุผลที่วิธีการทั้งหมดที่ทำงานบนListวัตถุอาจไม่ทำงานกับวัตถุ Array นี้โดยใช้Listการอ้างอิงเช่นArrayขนาด s มีความยาวคงที่ดังนั้นคุณจึงไม่สามารถเพิ่มหรือลบองค์ประกอบออกจากArrayวัตถุโดยใช้Listการอ้างอิงนี้ได้อย่างชัดเจน (เช่นlist.add(10)หรือlist.remove(10);มิฉะนั้นจะโยน UnsupportedOperationException)
  4. การเปลี่ยนแปลงใด ๆ ที่คุณกำลังทำโดยใช้การอ้างอิงรายการจะปรากฏในการออกจากArrayอ็อบเจ็กต์ s (ในขณะที่คุณกำลังดำเนินการกับอ็อบเจ็กต์ Array ที่มีอยู่โดยใช้การอ้างอิงรายการ)

ในกรณีแรกคุณกำลังสร้างArraylistออบเจ็กต์ใหม่(ในกรณีที่ 2 จะสร้างเฉพาะการอ้างอิงไปยังออบเจ็กต์ Array ที่มีอยู่เท่านั้น แต่ไม่ใช่ArrayListอ็อบเจ็กต์ใหม่) ดังนั้นตอนนี้จึงมีอ็อบเจ็กต์ที่แตกต่างกันสองอ็อบเจ็กต์หนึ่งArrayอ็อบเจกต์และอีกArrayListอ็อบเจกต์เป็นอ็อบเจกต์และไม่มีการเชื่อมต่อระหว่างอ็อบเจ็กต์ (ดังนั้นการเปลี่ยนแปลง ในวัตถุหนึ่งจะไม่สะท้อน / ได้รับผลกระทบในวัตถุอื่น (ในกรณีที่ 2 ArrayและArraylistเป็นวัตถุสองชิ้นที่แตกต่างกัน)

กรณีที่ 1:

Integer [] ia = {1,2,3,4};
System.out.println("Array : "+Arrays.toString(ia));
List<Integer> list1 = new ArrayList<Integer>(Arrays.asList(ia));  // new ArrayList object is created , no connection between existing Array Object
list1.add(5);
list1.add(6);
list1.remove(0);
list1.remove(0);
System.out.println("list1 : "+list1);
System.out.println("Array : "+Arrays.toString(ia));

กรณีที่ 2:

Integer [] ia = {1,2,3,4};
System.out.println("Array : "+Arrays.toString(ia));
List<Integer> list2 = Arrays.asList(ia); // creates only a (new ) List reference to existing Array object (and NOT a new ArrayList Object)
//  list2.add(5); //  it will throw java.lang.UnsupportedOperationException - invalid operation (as Array size is fixed)
list2.set(0,10);  // making changes in existing Array object using List reference - valid 
list2.set(1,11); 
ia[2]=12;     // making changes in existing Array object using Array reference - valid
System.out.println("list2 : "+list2);
System.out.println("Array : "+Arrays.toString(ia));

3

หลายคนตอบรายละเอียดกลไกไปแล้ว แต่ก็น่าสังเกตว่า: นี่เป็นตัวเลือกการออกแบบที่ไม่ดีโดย Java

asListวิธีการของ Java จัดทำเป็นเอกสาร "Returns a fixed-size list ... " หากคุณรับผลลัพธ์และเรียก (พูด) .addเมธอดมันจะพ่นUnsupportedOperationExceptionไฟล์. นี่คือพฤติกรรมที่ไม่ตั้งใจ! หากวิธีการที่บอกว่ามันส่งกลับที่คาดหวังมาตรฐานคือว่ามันจะกลับวัตถุซึ่งสนับสนุนวิธีการของอินเตอร์เฟซที่List Listนักพัฒนาไม่ควรจะต้องจดจำซึ่งของมากมายutil.Listวิธีการสร้างListของที่ไม่จริงสนับสนุนทั้งหมดListวิธีการ

หากพวกเขาตั้งชื่อเมธอดasImmutableListก็จะสมเหตุสมผล หรือถ้าพวกเขาเพิ่งมีวิธีการส่งคืนจริงList(และคัดลอกอาร์เรย์สำรอง) ก็จะสมเหตุสมผล พวกเขาตัดสินใจที่จะสนับสนุนทั้งประสิทธิภาพรันไทม์และชื่อย่อโดยมีค่าใช้จ่ายในการละเมิดทั้งหลักการของความประหลาดใจน้อยที่สุดและแนวปฏิบัติที่ดีของ OO ในการหลีกเลี่ยงUnsupportedOperationException s

(นอกจากนี้นักออกแบบอาจทำ a interface ImmutableListเพื่อหลีกเลี่ยงสิ่งที่เหลือเฟือUnsupportedOperationException)


2

โปรดทราบว่าใน Java 8 "ia" ด้านบนต้องเป็น Integer [] และไม่ใช่ int [] Arrays.asList () ของอาร์เรย์ int ส่งคืนรายการที่มีองค์ประกอบเดียว เมื่อใช้ข้อมูลโค้ดของ OP คอมไพลเลอร์จะตรวจจับปัญหา แต่วิธีการบางอย่าง (เช่น Collections.shuffle ()) จะไม่สามารถทำตามที่คุณคาดหวังได้


1
ฉันประสบปัญหาคอมไพเลอร์นี้เมื่อฉันทำ ArrayList <Integer> al = ArrayList ใหม่ <Integer> (Arrays.asList (a)); โดยที่ a เป็น int [] อัลของฉันมีองค์ประกอบเดียวที่เกินไปเมื่อพิมพ์ก็ดูเป็นขยะ องค์ประกอบนั้นคืออะไร? แล้วมันไปอยู่ที่นั่นได้อย่างไร? ฉันประสบปัญหานี้ใน Java 7
Jyotsana Nandwani

@JyotsanaNandwani กรุณาตรวจสอบคำตอบของฉัน: stackoverflow.com/a/54105519/1163607
NINCOMPOOP

1
package com.copy;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

public class CopyArray {

    public static void main(String[] args) {
        List<Integer> list1, list2 = null;
        Integer[] intarr = { 3, 4, 2, 1 };
        list1 = new ArrayList<Integer>(Arrays.asList(intarr));
        list1.add(30);
        list2 = Arrays.asList(intarr);
        // list2.add(40); Here, we can't modify the existing list,because it's a wrapper
        System.out.println("List1");
        Iterator<Integer> itr1 = list1.iterator();
        while (itr1.hasNext()) {
            System.out.println(itr1.next());
        }
        System.out.println("List2");
        Iterator<Integer> itr2 = list2.iterator();
        while (itr2.hasNext()) {
            System.out.println(itr2.next());
        }
    }
}

1

Arrays.asList()

วิธีนี้จะส่งคืนการใช้งาน List ของตัวเองใช้อาร์เรย์เป็นอาร์กิวเมนต์และสร้างเมธอดและแอตทริบิวต์ที่ด้านบนเนื่องจากไม่ได้คัดลอกข้อมูลใด ๆ จากอาร์เรย์ แต่ใช้อาร์เรย์ดั้งเดิมซึ่งทำให้เกิดการเปลี่ยนแปลงในอาร์เรย์ดั้งเดิมเมื่อคุณแก้ไข รายการที่ส่งคืนโดยArrays.asList()วิธีการ

ในทางกลับกัน.
ArrayList(Arrays.asList()); เป็นตัวสร้างของArrayListคลาสซึ่งรับรายการเป็นอาร์กิวเมนต์และส่งคืนค่าArrayListที่ไม่ขึ้นอยู่กับ list ie Arrays.asList()ในกรณีนี้ส่งผ่านเป็นอาร์กิวเมนต์ นั่นคือเหตุผลที่คุณเห็นผลลัพธ์เหล่านี้


0
1.List<Integer> list1 = new ArrayList<Integer>(Arrays.asList(ia));  //copy
2.List<Integer> list2 = Arrays.asList(ia);

ในบรรทัดที่ 2 Arrays.asList(ia)ส่งกลับListอ้างอิงของวัตถุระดับชั้นที่กำหนดไว้ภายในArraysซึ่งเรียกว่ายังแต่มีความเป็นส่วนตัวและมีเพียงขยายArrayList AbstractListซึ่งหมายความว่าสิ่งที่ส่งกลับมาArrays.asList(ia)คือคลาสออบเจ็กต์ที่แตกต่างจากสิ่งที่คุณได้รับnew ArrayList<Integer>เป็นวัตถุชั้นที่แตกต่างจากสิ่งที่คุณได้รับจาก

คุณไม่สามารถใช้การดำเนินการบางอย่างกับบรรทัดที่ 2 ได้เนื่องจากคลาสส่วนตัวภายในภายใน Arraysไม่มีวิธีการเหล่านั้น

ดูที่ลิงค์นี้และดูว่าคุณสามารถทำอะไรกับคลาสภายในส่วนตัว: http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/ Arrays.java # Arrays.ArrayList

บรรทัดที่ 1 สร้างArrayListองค์ประกอบการคัดลอกออบเจ็กต์ใหม่จากสิ่งที่คุณได้รับจากบรรทัดที่ 2 ดังนั้นคุณสามารถทำอะไรก็ได้ที่คุณต้องการเนื่องจากjava.util.ArrayListมีวิธีการเหล่านั้นทั้งหมด


0

สรุปความแตกต่าง -

เมื่อสร้างรายการโดยไม่ใช้ตัวดำเนินการใหม่ Arrays.asList () วิธีการจะส่งคืน Wrapper ซึ่งหมายความว่า

1.คุณสามารถดำเนินการเพิ่ม / อัปเดตการดำเนินการ

2.การเปลี่ยนแปลงที่ทำในอาร์เรย์ดั้งเดิมจะปรากฏในรายการเช่นกันและในทางกลับกัน


0

ในการตอบกลับความคิดเห็นที่ถามคำถามเกี่ยวกับพฤติกรรมของArrays.asList ()ตั้งแต่ Java 8:

    int[] arr1 = {1,2,3};
    /* 
       Arrays are objects in Java, internally int[] will be represented by 
       an Integer Array object which when printed on console shall output
       a pattern such as 
       [I@address for 1-dim int array,
       [[I@address for 2-dim int array, 
       [[F@address for 2-dim float array etc. 
   */
    System.out.println(Arrays.asList(arr1)); 

    /* 
       The line below results in Compile time error as Arrays.asList(int[] array)
       returns List<int[]>. The returned list contains only one element 
       and that is the int[] {1,2,3} 
    */
    // List<Integer> list1 = Arrays.asList(arr1);

    /* 
       Arrays.asList(arr1) is  Arrays$ArrayList object whose only element is int[] array
       so the line below prints [[I@...], where [I@... is the array object.
    */
    System.out.println(Arrays.asList(arr1)); 

    /* 
     This prints [I@..., the actual array object stored as single element 
     in the Arrays$ArrayList object. 
    */
    System.out.println(Arrays.asList(arr1).get(0));

    // prints the contents of array [1,2,3]
    System.out.println(Arrays.toString(Arrays.asList(arr1).get(0)));

    Integer[] arr2 = {1,2,3};
    /* 
     Arrays.asList(arr) is  Arrays$ArrayList object which is 
     a wrapper list object containing three elements 1,2,3.
     Technically, it is pointing to the original Integer[] array 
    */
    List<Integer> list2 = Arrays.asList(arr2);

    // prints the contents of list [1,2,3]
    System.out.println(list2);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.