JAVA学习笔记(第九章 集合与泛型)

一、ArrayList

1.使用数组的局限性

        比如:声明长度是10的数组,不用的数组就浪费了,超过10的个数,又放不下。

而ArrayList存放对象其容量会随着对象的增加,自动增长。

2.常用方法

(1)add 增加

//第一种是直接add对象,把对象加在最后面
heros.add(new Hero("hero " + i));
//第二种是在指定位置加对象
heros.add(3, specialHero);

(2)contains         判断一个对象是否在容器中

(3)get        获取指定位置的对象,如果输入的下标越界,会报错

(4)indexOf        用于判断一个对象在ArrayList中所处的位置

(5)remove        可以根据下标删除ArrayList的元素;也可以根据对象删除

//根据下标删除ArrayList的元素
heros.remove(2);
//根据对象删除
heros.remove(specialHero);

(6)set        用于替换指定位置的元素

heros.set(5, new Hero("hero 5"));

(7)size         用于获取ArrayList的大小

(8)toArray        可以把一个ArrayList对象转换为数组。

        需要注意的是,如果要转换为一个Hero数组,那么需要传递一个Hero数组类型的对象给toArray(),这样toArray方法才知道,你希望转换为哪种类型的数组,否则只能转换为Object数组。

Hero hs[] = (Hero[])heros.toArray(new Hero[]{});

(9)addAll         把另一个容器所有对象都加进来

(10)clear         清空一个ArrayList

3.List接口

        ArrayList实现了接口List,常见的写法会把引用声明为接口List类型。

        因为ArrayList实现了List接口,所以List接口的方法ArrayList都实现了,所以不重复说明了。

4.泛型 Generic

        不指定泛型的容器,可以存放任何类型的元素;指定了泛型的容器,只能存放指定类型的元素以及其子类。

//为了不使编译器出现警告,需要前后都使用泛型,像这样:
List<Hero> genericheros = new ArrayList<Hero>();
//不过JDK7提供了一个可以略微减少代码量的泛型简写方式
List<Hero> genericheros2 = new ArrayList<>();

5.遍历

(1)for循环

(2)增强for循环

(3)使用迭代器

        不断用hasNext()判断是否还有下一个数据。

public class TestCollection { 
    public static void main(String[] args) {
        List<Hero> heros = new ArrayList<Hero>();        
        //放5个Hero进入容器
        for (int i = 0; i < 5; i++) {
            heros.add(new Hero("hero name " +i));
        }
         
        //第二种遍历,使用迭代器
        System.out.println("--------使用while的iterator-------");
        Iterator<Hero> it= heros.iterator();
        //从最开始的位置判断"下一个"位置是否有数据
        //如果有就通过next取出来,并且把指针向下移动
        //直到"下一个"位置没有数据
        while(it.hasNext()){
            Hero h = it.next();
            System.out.println(h);
        }
        //迭代器的for写法
        System.out.println("--------使用for的iterator-------");
        for (Iterator<Hero> iterator = heros.iterator(); iterator.hasNext();) {
            Hero hero = (Hero) iterator.next();
            System.out.println(hero);
        }         
    }      
}

二、其他集合

        序列分先进先出FIFO,先进后出FILO
        FIFO在Java中又叫Queue 队列
        FILO在Java中又叫Stack 栈

1.LinkedList 与 List接口

        与ArrayList一样,LinkedList也实现了List接口,诸如add,remove,contains等等方法。

 (1)双向链表 - Deque

        除了实现了List接口外,LinkedList还实现了双向链表结构Deque,可以很方便的在头尾插入删除数据。

addFirst();    //在最前面插入新的元素
addLast();    //在最后面插入新的元素
getFirst();    //查看最前面的元素
getLast();    //查看最后面的元素
removeFirst();    //取出最前面的元素
removeLast();    //取出最后面的元素

(2)队列 - Queue

        LinkedList 除了实现了List和Deque外,还实现了Queue接口(队列)。
        Queue是先进先出队列 FIFO,常用方法:
                offer 在最后添加元素
                poll 取出第一个元素
                peek 查看第一个元素

2.二叉树

        二叉树由各种节点组成,其特点是:每个节点都可以有左子节点、右子节点,每一个节点都有一个值。

(1)二叉树排序-插入数据

        插入基本逻辑是,小、相同的放左边,大的放右边

(2)二叉树排序-遍历

        通过上一个步骤的插入行为,实际上,数据就已经排好序了。 接下来要做的是看,把这些已经排好序的数据,遍历成我们常用的List或者数组的形式
        二叉树的遍历分左序,中序,右序
        左序即: 中间的数遍历后放在左边
        中序即: 中间的数遍历后放在中间
        右序即: 中间的数遍历后放在右边

public class Node {
    // 左子节点
    public Node leftNode;
    // 右子节点
    public Node rightNode;  
    // 值
    public Object value;  
    // 插入 数据
    public void add(Object v) {
        // 如果当前节点没有值,就把数据放在当前节点上
        if (null == value)
            value = v;
  
        // 如果当前节点有值,就进行判断,新增的值与当前值的大小关系
        else {
            // 新增的值,比当前值小或者相同
             
            if ((Integer) v -((Integer)value) <= 0) {
                if (null == leftNode)
                    leftNode = new Node();
                leftNode.add(v);
            }
            // 新增的值,比当前值大
            else {
                if (null == rightNode)
                    rightNode = new Node();
                rightNode.add(v);
            }  
        }  
    }
  
 // 中序遍历所有的节点
    public List<Object> values() {
        List<Object> values = new ArrayList<>();  
        // 左节点的遍历结果
        if (null != leftNode)
            values.addAll(leftNode.values());  
        // 当前节点
        values.add(value);  
        // 右节点的遍历结果
        if (null != rightNode)  
            values.addAll(rightNode.values());  
        return values;
    }
  
    public static void main(String[] args) {
  
        int randoms[] = new int[] { 67, 7, 30, 73, 10, 0, 78, 81, 10, 74 };  
        Node roots = new Node();
        for (int number : randoms) {
            roots.add(number);
        }
          System.out.println(roots.values());  
    }
}

3.HashMap

        HashMap储存数据的方式是—— 键值对。

public class TestCollection {
    public static void main(String[] args) {
        HashMap<String,String> dictionary = new HashMap<>();
        dictionary.put("adc", "物理英雄");
        dictionary.put("apc", "魔法英雄");
        dictionary.put("t", "坦克");         
        System.out.println(dictionary.get("t"));
    }
}

        对于HashMap而言,key是唯一的,不可以重复的。
        所以,以相同的key 把不同的value插入到 Map中会导致旧元素被覆盖,只留下最后插入的元素。不过,同一个对象可以作为值插入到map中,只要对应的key不一样。

4.HashSet

        Set中的元素,不能重复,没有顺序。

        Set不提供get()来获取指定位置的元素,所以遍历需要用到迭代器,或者增强型for循环。

public class TestCollection {
    public static void main(String[] args) {         
        HashSet<String> names = new HashSet<String>();         
        names.add("gareen");         
        System.out.println(names);         
        //第二次插入同样的数据,是插不进去的,容器中只会保留一个
        names.add("gareen");
        System.out.println(names);
    }
}

        通过观察HashSet的源代码,可以发现HashSet自身并没有独立的实现,而是在里面封装了一个Map。

        HashSet是作为Map的key而存在的,而value是一个命名为PRESENT的static的Object对象,因为是一个类属性,所以只会有一个。

5.Collection

        Collection是 Set List Queue和 Deque的接口
        Queue: 先进先出队列
        Deque: 双向链表

        注:Collection和Map之间没有关系,Collection是放一个一个对象的,Map 是放键值对的
        注:Deque 继承 Queue,间接的继承了 Collection

6.Collections

        Collections是一个类,容器的工具类,就如同Arrays是数组的工具类。

(1)reverse         使List中的数据发生翻转

(2)shuffle         混淆List中数据的顺序

(3)sort         对List中的数据进行排序

(4)swap         交换两个数据的位置

(5)rotate         把List中的数据,向右滚动指定单位的长度

(6)synchronizedList         把非线程安全的List转换为线程安全的List

三、几种集合的关系与 区别

1.ArrayList和LinkedList的区别

        ArrayList         插入,删除数据慢;
        LinkedList        插入,删除数据快;
        ArrayList        是顺序结构,所以定位很快,指哪找哪;
        LinkedList         是链表结构,就像手里的一串佛珠,要找出第99个佛珠,必须得一个一个的数过去,所以定位慢。

2.HashMap和Hashtable的区别

        HashMap和Hashtable都实现了Map接口,都是键值对保存数据的方式
        区别1:
                HashMap        可以存放 null
                Hashtable        不能存放null
        区别2:
                HashMap        不是线程安全的类
                Hashtable        是线程安全的类

3.HashSet、LinkedHashSet、TreeSet

        HashSet: 无序
        LinkedHashSet: 按照插入顺序
        TreeSet: 从小到大排序

四、比较器与聚合操作

1.Comparator

        简单说,就是让集合中的对象能按照你的意愿进行比较。

        假设Hero有三个属性 name,hp,damage
        一个集合中放存放10个Hero,通过Collections.sort对这10个进行排序
        那么到底是hp小的放前面?还是damage小的放前面?Collections.sort也无法确定,所以要指定到底按照哪种属性进行排序。这里就需要提供一个Comparator给定如何进行两个对象之间的大小比较。

Comparator<Hero> c = new Comparator<Hero>() {
    @Override
    public int compare(Hero h1, Hero h2) {
        //按照hp进行排序
        if(h1.hp>=h2.hp)
            return 1;  //正数表示h1比h2要大
        else
            return -1;
    }
};

2.Comparable

        在类里面提供比较算法,无需额外提供比较器Comparator。

public class Hero implements Comparable<Hero>{
    public String name;
    public float hp;       
    public int damage;       
    public Hero(){          
    }      
    public Hero(String name) {
        this.name =name;  
    }      
    //初始化name,hp,damage的构造方法
    public Hero(String name,float hp, int damage) {
        this.name =name;
        this.hp = hp;
        this.damage = damage;
    }
  
    @Override
    public int compareTo(Hero anotherHero) {
        if(damage<anotherHero.damage)
            return 1; 
        else
            return -1;
    }  
    @Override
    public String toString() {
        return "Hero [name=" + name + ", hp=" + hp + ", damage=" + damage + "]\r\n";
    }      
}

3.聚合操作

        JDK8之后,引入了对集合的聚合操作,可以非常容易的遍历,筛选,比较集合中的元素。

public class TestAggregate {  
    public static void main(String[] args) {
        Random r = new Random();
        List<Hero> heros = new ArrayList<Hero>();
        for (int i = 0; i < 10; i++) {
            heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));
        } 
        System.out.println("初始化集合后的数据 (最后一个数据重复):");
        System.out.println(heros);
         
        //传统方式
        Collections.sort(heros,new Comparator<Hero>() {
            @Override
            public int compare(Hero o1, Hero o2) {
                return (int) (o2.hp-o1.hp);
            }
        });
         
        Hero hero = heros.get(2);
        System.out.println("通过传统方式找出来的hp第三高的英雄名称是:" + hero.name);
         
        //聚合方式
        String name =heros
            .stream()
            .sorted((h1,h2)->h1.hp>h2.hp?-1:1)
            .skip(2)
            .map(h->h.getName())
            .findFirst()
            .get();
 
        System.out.println("通过聚合操作找出来的hp第三高的英雄名称是:" + name);         
    }
}

五、泛型

1.集合中的泛型

2.类中的泛型

        支持泛型的Stack。在类的声明上,加上一个<T>,表示该类支持泛型。

public class MyStack<T> {   
    LinkedList<T> values = new LinkedList<T>();       
    public void push(T t) {
        values.addLast(t);
    }   
    public T pull() {
        return values.removeLast();
    }   
    public T peek() {
        return values.getLast();
    }
       
    public static void main(String[] args) {
        //在声明这个Stack的时候,使用泛型<Hero>就表示该Stack只能放Hero
        MyStack<Hero> heroStack = new MyStack<>();
        heroStack.push(new Hero());
        //不能放Item
        heroStack.push(new Item());
         
        //在声明这个Stack的时候,使用泛型<Item>就表示该Stack只能放Item
        MyStack<Item> itemStack = new MyStack<>();
        itemStack.push(new Item());
        //不能放Hero
        itemStack.push(new Hero());
    }   
}

3.通配符

(1)? extends

        ArrayList heroList<? extends Hero> 表示这是一个Hero泛型或者其子类泛型。
                heroList 的泛型可能是Hero
                heroList 的泛型可能是APHero
                heroList 的泛型可能是ADHero
        所以 可以确凿的是,从heroList取出来的对象,一定是可以转型成Hero的。

        但是,不能往里面放东西。

(2)? super

        ArrayList heroList<? super Hero> 表示这是一个Hero泛型或者其父类泛型
                heroList的泛型可能是Hero
                heroList的泛型可能是Object
        可以往里面插入Hero以及Hero的子类,但是取出来有风险,因为不确定取出来是Hero还是Object。

(3)泛型通配符?

        泛型通配符? 代表任意泛型。既然?代表任意泛型,也就是说,这个容器什么泛型都有可能,所以只能以Object的形式取出来。
        因此不能往里面放对象,因为不知道到底是一个什么泛型的容器

 (4)总结

        如果希望只取出,不插入,就使用? extends Hero
        如果希望只插入,不取出,就使用? super Hero
        如果希望,又能插入,又能取出,就不要用通配符?

六、lambda表达式

1.Hello lambda

        假设一个情景: 找出满足条件(hp>100 && damage<50)的Hero

(1)普通方法

for (Hero hero : heros) {
    if(hero.hp>100 && hero.damage<50)
    System.out.print(hero);
}

(2)匿名类方法

interface HeroChecker {
    public boolean test(Hero h);
}
public class TestLambda {
    public static void main(String[] args) {
        Random r = new Random();
        List<Hero> heros = new ArrayList<Hero>();
        for (int i = 0; i < 5; i++) {
            heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));
        }
        System.out.println("初始化后的集合:");
        System.out.println(heros);
        System.out.println("使用匿名类的方式,筛选出 hp>100 && damange<50的英雄");
        
        HeroChecker checker = new HeroChecker() {
            @Override
            public boolean test(Hero h) {
                return (h.hp>100 && h.damage<50);
            }
        };
           
        filter(heros,checker);
    }
   
    private static void filter(List<Hero> heros,HeroChecker checker) {
        for (Hero hero : heros) {
            if(checker.test(hero))
                System.out.print(hero);
        }
    }   
}

(3)Lambda方法

public class TestLamdba {
    public static void main(String[] args) {
        Random r = new Random();
        List<Hero> heros = new ArrayList<Hero>();
        for (int i = 0; i < 5; i++) {
            heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));
        }
        System.out.println("初始化后的集合:");
        System.out.println(heros);
        System.out.println("使用Lamdba的方式,筛选出 hp>100 && damange<50的英雄");
        
        filter(heros,h->h.hp>100 && h.damage<50);
    }
 
    private static void filter(List<Hero> heros,HeroChecker checker) {
        for (Hero hero : heros) {
            if(checker.test(hero))
                System.out.print(hero);
        }
    } 
}

(4)从匿名类到Lambda表达式

        ①匿名类的正常写法

HeroChecker c1 = new HeroChecker() {
    public boolean test(Hero h) {
        return (h.hp>100 && h.damage<50);
    }
};

        ②把外面的壳子去掉,只保留方法参数和方法体,参数和方法体之间加上符号 ->

HeroChecker c2 = (Hero h) ->{
    return h.hp>100 && h.damage<50;
};

        ③把return和{}去掉

HeroChecker c3 = (Hero h) ->h.hp>100 && h.damage<50;

        ④把参数类型和圆括号去掉(只有一个参数的时候,才可以去掉圆括号)

HeroChecker c4 = h ->h.hp>100 && h.damage<50;

        ⑤ 把c4作为参数传递进去        

filter(heros,c4);

        ⑥直接把表达式传递进去

filter(heros, h -> h.hp > 100 && h.damage < 50);

        与匿名类概念相比较,Lambda 其实就是匿名方法,这是一种把方法作为参数进行传递的编程思想。虽然Lambda表达式这样写,但是Java会在背后,悄悄的,把这些都还原成匿名类方式。

(5)Lambda表达式的弊端

        Lambda表达式虽然带来了代码的简洁,但是也有其局限性。
                ①可读性差,与啰嗦的但是清晰的匿名类代码结构比较起来,Lambda表达式一旦变得比较长,就难以理解
                ② 不便于调试,很难在Lambda表达式中增加调试信息,比如日志
                ③版本支持,Lambda表达式在JDK8版本中才开始支持,如果系统使用的是以前的版本,考虑系统的稳定性等原因,而不愿意升级,那么就无法使用。

        Lambda比较适合用在简短的业务代码中,并不适合用在复杂的系统中,会加大维护成本。

2.方法引用

(1)引用静态方法

        首先为TestLambda添加一个静态方法:

public static boolean testHero(Hero h) {
    return h.hp>100 && h.damage<50;
}

        Lambda表达式:

filter(heros, h->h.hp>100 && h.damage<50);

        在Lambda表达式中调用这个静态方法:

filter(heros, h -> TestLambda.testHero(h) );

        调用静态方法还可以改写为:

filter(heros, TestLambda::testHero);

(2)引用对象方法

        与引用静态方法很类似,只是传递方法的时候,需要一个对象的存在。

TestLambda testLambda = new TestLambda();
filter(heros, testLambda::testHero);

(3)引用容器中的对象的方法

        首先为Hero添加一个方法

public boolean matched(){
    return this.hp>100 && this.damage<50;
}

        使用Lambda表达式

filter(heros,h-> h.hp>100 && h.damage<50 );

        在Lambda表达式中调用容器中的对象Hero的方法matched

filter(heros,h-> h.matched() );

        matched恰好就是容器中的对象Hero的方法,那就可以进一步改写为

filter(heros, Hero::matched);

(4)引用构造器

        有的接口中的方法会返回一个对象,比如java.util.function.Supplier提供了一个get方法,返回一个对象。

public interface Supplier<T> {
    T get();
}

        设计一个方法,参数是这个接口

public static List getList(Supplier<List> s){
    return s.get();
}

        为了调用这个方法,有3种方式
第一种匿名类:

Supplier<List> s = new Supplier<List>() {
    public List get() {
        return new ArrayList();
    }
};

List list1 = getList(s);

第二种:Lambda表达式

List list2 = getList(()->new ArrayList());

第三种:引用构造器

List list3 = getList(ArrayList::new);

3.聚合操作

(1)传统方式与聚合操作方式遍历数据

        遍历数据的传统方式就是使用for循环,然后条件判断,最后打印出满足条件的数据

for (Hero h : heros) {
    if (h.hp > 100 && h.damage < 50)
    System.out.println(h.name);
}

        使用聚合操作方式,画风就发生了变化:

heros
.stream()
.filter(h -> h.hp > 100 && h.damage < 50)
.forEach(h -> System.out.println(h.name));

(2)Stream和管道的概念

        要了解聚合操作,首先要建立Stream和管道的概念
        Stream 和Collection结构化的数据不一样,Stream是一系列的元素,就像是生产线上的罐头一样,一串串的出来。
        管道指的是一系列的聚合操作。管道又分3个部分:
                ①管道源:在这个例子里,源是一个List
                ②中间操作: 每个中间操作,又会返回一个Stream,比如.filter()又返回一个Stream, 中间操作是“懒”操作,并不会真正进行遍历。
                ③结束操作:当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。 结束操作不会返回Stream,但是会返回int、float、String、 Collection或者像forEach,什么都不返回, 结束操作才进行真正的遍历行为,在遍历的时候,才会去进行中间操作的相关判断

注: 这个Stream和I/O章节的InputStream,OutputStream是不一样的概念。

(3)管道源

        把Collection切换成管道源很简单,调用stream()就行了。

heros.stream()

        但是数组却没有stream()方法,需要使用

Arrays.stream(hs)
//或者
Stream.of(hs)

(4)中间操作

        每个中间操作,又会返回一个Stream,比如.filter()又返回一个Stream, 中间操作是“懒”操作,并不会真正进行遍历。
        中间操作比较多,主要分两类:对元素进行筛选 和 转换为其他形式的流。
        ①对元素进行筛选:
                filter 匹配
                distinct 去除重复(根据equals判断)
                sorted 自然排序
                sorted(Comparator<T>) 指定排序
                limit 保留
                skip 忽略
        ②转换为其他形式的流:
                mapToDouble 转换为double的流
                map 转换为任意类型的流

(5)结束操作

        当进行结束操作后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。 结束操作不会返回Stream,但是会返回int、float、String、 Collection或者像forEach,什么都不返回,。
        结束操作才真正进行遍历行为,前面的中间操作也在这个时候,才真正的执行。
        常见结束操作如下:
                forEach() 遍历每个元素
                toArray() 转换为数组
                min(Comparator<T>) 取最小的元素
                max(Comparator<T>) 取最大的元素
                count() 总数
                findFirst() 第一个元素

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值