JAVA学习笔记--抽象类、接口

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

JVM的默认处理方案

如果程序出现了问题,我们没有做任何处理,最终JVM会做默认的处理

  • 把异常的名称,异常原因及异常出现的位置等信息输出在了控制台。
  • 程序停止执行。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
public class ExceptionDemo {
    public static void main(String[] args) {
        System.out.println("begin");
        method();
        System.out.println("end");
    }

    public static void method() {
        try {
            int[] arr = {1, 2, 3};
            System.out.println(arr[3]);
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println(e.getMessage());
            System.out.println(e.toString());
            e.printStackTrace();
        }

    }
}

执行结果如下:

在这里插入图片描述在这里插入图片描述
运行时异常:也就是说,运行时异常是会报错了,必须要修改之后才能正常运行,不一定需要try catch。
如:

int[] arr = {1, 2, 3};
System.out.println(arr[3]);

但也可以加上try,catch来报错

编译时异常:就是编译的时候可能会出错误,这样子的话编译器会报黄,这时候就需要加上try catch排除错误才能正常运行。
在这里插入图片描述在这里插入图片描述修改方法;加上try catch
在这里插入图片描述
运行结果:
Wed Jan 08 00:08:00 CST 2048

在这里插入图片描述也就是说,throw就是在甩锅出去,让别人解决 。

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

实际修改次数与预期修改次数的物理意义在于:

如果不进行此判断,那么在这段代码的执行的时候:

while (it.hasNext()) {
            String s = it.next();
            if (s.equals("world")) {
                list.add("javaee");
            }
        }

意思是在使用list的迭代器的时候,不能进行add操作。

修改方案

for (int i=0;i<list.size();i++){
            String s=list.get(i);
            if (s.equals("world")){
                list.add("javaee");
            }
        }

在这里插入图片描述
在这里插入图片描述

ListIterator<String> itt=list.listIterator();
while (itt.hasNext()) {
                String s = itt.next();
                if (s.equals("world")) {
                    itt.add("javaee");
                }
            }


[hello, world, javaee, java]

不同于Collection的迭代器iterator,ListIterator的功能更好,并且不会出现之前那样的并发异常。原因在于使用的是ListIterator的add方法进行添加,而不是List的方法add进行添加。在这里插入图片描述List Iterator的add方法会在执行之后将实际修改次数赋值给预期修改次数,所以就不会报错了。并且会自动对next向后移一位,这样也不会出现死循环的情况了。
在这里插入图片描述

在这里插入图片描述在这里插入图片描述

数组、List和ArrayList的区别

①数组在内存中是连续存储的,所以它的索引速度是非常的快,而且赋值与修改元素也很简单。 但是数组也存在一些不足的地方。比如在数组的两个数据间插入数据也是很麻烦的,还有我们在声明数组的时候,必须同时指明数组的长度,数组的长度过长,会造成内存浪费,数组和长度过短,会造成数据溢出的错误。这样如果在声明数组时我们并不清楚数组的长度,就变的很麻烦了。C#中最先提供了ArrayList对象来克服这些缺点

②ArrayList是.Net Framework提供的用于数据存储和检索的专用类,它是命名空间System.Collections下的一部分。它的大小是按照其中存储的数据来动态扩充与收缩的。所以,我们在声明ArrayList对象时并不需要指定它的长度。ArrayList继承了IList接口,所以它可以很方便的进行数据的添加,插入和移除。
但是,在ArrayList中,我们不仅可以插入了字符串"abc",而且还可以插入数字123。这样在ArrayList中插入不同类型的数据是允许的。因为ArrayList会把所有插入其中的数据都当作为object类型来处理。这样,在我们使用ArrayList中的数据来处理问题的时候,很可能会报类型不匹配的错误,也就是说ArrayList不是类型安全的。既使我们保证在插入数据的时候都很小心,都有插入了同一类型的数据,但在使用的时候,我们也需要将它们转化为对应的原类型来处理。这就存在了装箱与拆箱的操作,会带来很大的性能损耗。
装箱与拆箱的概念: 简单的来讲: 装箱:就是将值类型的数据打包到引用类型的实例中 比如将int类型的值123赋给object对象o

int i=123; object o=(object)i;

拆箱:就是从引用数据中提取值类型 比如将object对象o的值赋给int类型的变量i

object o=123; int i=(int)o;

装箱与拆箱的过程是很损耗性能的。

正是因为ArrayList存在不安全类型与装箱拆箱的缺点,所以在C#2.0后出现了泛型的概念。而List类是ArrayList类的泛型等效类。它的大部分用法都与ArrayList相似,因为List类也继承了IList接口。最关键的区别在于,在声明List集合时,我们同时需要为其声明List集合内数据的对象类型。 比如:

List<int> list = new List<int>();
//新增数据
 list.Add(123);
//修改数据 
list[0] = 345;
//移除数据
list.RemoveAt(0);

上例中,如果我们往List集合中插入string字符"hello world",IDE就会报错,且不能通过编译。这样就避免了前面讲的类型安全问题与装箱拆箱的性能问题了。

List list = new ArrayList();这句创建了一个ArrayList的对象后把上溯到了List。此时它是一个List对象了,有些ArrayList有但是List没有的属性和方法,它就不能再用了。 而ArrayList list=new ArrayList();创建一对象则保留了ArrayList的所有属性。

ArrayList不是继承List接口,是实现了List接口。 你写成ArrayList arrayList =newArrayList();这样不会有任何问题。 和List list = new ArrayList();相比这2个写是有区别的。arrayList是一个ArrayList对象,它可以使用ArrayList的所有方法。
List是接口,它是不可以被实例化的(接口是个抽象类),所以必须以它的实现类去实例化它。list对象虽然也是被实例化为ArrayList但是它实际是List对象,list只能使用ArrayList中已经实现了的List接口中的方法,ArrayList中那些自己的、没有在List接口定义的方法是不可以被访问到的。
我们说,用接口去做是有它的好处的,如果你把类型定义成ArrayList(也就是一个具体的实现类)那么你就只能接收这一种类型的数据了,如果你要是定义为List那么你不仅可以接收ArrayList的对象还可以接收LinkedList的对象,这样你的程序就灵活了。

也就是说,像List list = new ArrayList();这种,左边是接口右边是实现类这种方式申明的list,他实质上是一个List对象,是由实现类ArrayList通过多态生成的List对象,这样做的话就只能用list存储ArrayList对象并且list中只有ArrayList中已经实现了的List接口中的方法,也就是他们共有的方法,而要是List list=new List()的话就可以存储arraylist和linkedlist两种,并且也有全部的List方法。

那么List list = new ArrayList();这种方法有什么好处?

1、在设计模式中有对依赖倒置原则。程序要尽量依赖于抽象,不依赖于具体。 从Java语法上,这种方式是使用接口引用指向具体实现。
比如,你若希望用LinkedList的实现来替代ArrayList的话,只需改动一行即可,其他的所有的都不需要改动:

List list=new LinkedList();

这也是一种很好的设计模式.一个接口有多种实现,当你想换一种实现方式时,你需要做的改动很小.
2、面向接口编程
3、提高程序宽展性,以后修改维护好些

List泛型的好处:

通过允许指定泛型类或方法操作的特定类型,泛型功能将类型安全的任务从您转移给了编译器。不需要编写代码来检测数据类型是否正确,因为会在编译时强制使用正确的数据类型。减少了类型强制转换的需要和运行时错误的可能性。泛型提供了类型安全但没有增加多个实现的开销。在这里插入图片描述在这里插入图片描述

public class SetDemo {
    public static void main(String[] args) {
        Set<String> set=new HashSet<String>();
        set.add("hello");
        set.add("world");
        set.add("hello");

        for (String s: set){
            System.out.println(s);
        }
    }

}

执行结果:
world
hello

在这里插入图片描述

public class HashDemo {
    public static void main(String[] args) {
        Student s1=new Student("林青霞",10);
        //同一个对象多次调用hashCode( )方法返回的哈希值是相同的
        System.out.println(s1.hashCode());
        System.out.println(s1.hashCode());
        System.out.println("--------");

        //默认情况下,不同对象的哈希值是不相同的
        //通过方法重写,可以实现不同对象的哈希值是相同的
        Student s2=new Student("林青霞",10);
        System.out.println(s2.hashCode());
        System.out.println(s2.hashCode());
        System.out.println("--------");

        System.out.println("hello".hashCode());
        System.out.println("hello".hashCode());
        System.out.println("--------");

        System.out.println("重地".hashCode());
        System.out.println("通话".hashCode());

    }
}

输出结果:
985922955
985922955
--------
1435804085
1435804085
--------
99162322
99162322
--------
1179395
1179395

关于为什么相等的Java字符串采用相同的地址?

String str1 = "whatever";
String str2 = str1;
String str3 = "whatever";
System.out.println(str1==str2); //prints true...that's normal, they point to the same object
System.out.println(str1==str3); //gives true..how's that possible ?

最后一行如何显示为真?这意味着str1和str3在内存中具有相同的地址.

这是一个编译器优化,它足以检测两个字符串文字是否相同(“无论如何”),从而将str1和str3分配给同一对象?还是我在字符串的基本机制中缺少某些东西?

原因是:与所有对象分配一样,字符串分配在时间和内存上都非常昂贵.
JVM在实例化字符串文字时执行一些技巧,以提高性能并减少内存开销.为了减少在JVM中创建的String对象的数量,String类保留了一个字符串池.每次代码创建字符串文字时,JVM都会首先检查字符串文字池.如果该字符串已存在于池中,则返回对该池实例的引用.如果该字符串在池中不存在,则将实例化一个新的String对象,然后将其放置在池中.

在这里插入图片描述

public class HashSetDemo {
    public static void main(String[] args) {
        HashSet<String> hs=new HashSet<String>();
        hs.add("hello");
        hs.add("world");
        hs.add("java");

        hs.add("world");

        for (String s :hs){
            System.out.println(s);
        }
    }
}

执行结果:
world
java
hello

在这里插入图片描述

public class HashSetTest {
    public static void main(String[] args) {
        HashSet<Student> hs = new HashSet<Student>();
        Student s1 = new Student("小明", 10);
        Student s2 = new Student("小王", 12);
        Student s3 = new Student("小强", 13);

        Student s4 = new Student("小强", 13);

        hs.add(s1);
        hs.add(s2);
        hs.add(s3);

        hs.add(s4);

        for (Student s:hs){
            System.out.println(s.getName()+s.getAge());
        }

    }
}

执行结果:
小王12
小强13
小明10
小强13

这里对于对象内容相同的对象并没有进行不添加的操作,而是依然添加进Hash Set中了,因为如果要保证元素的唯一性必须要重写hashCode()和equals()方法。

在Student类中,重写hashCode()和equals()方法很简单,只需要alt+insert 选择equals() and hashCode()
在这里插入图片描述
然后一路下一步就欧克了。在这里插入图片描述再次执行结果为:
小王12
小强13
小明10

—————————————————————————————————

在这里插入图片描述

public class LinkedHashSetDemo {
    public static void main(String[] args) {
        LinkedHashSet<String> lhs=new LinkedHashSet<String>();
        lhs.add("hello");
        lhs.add("world");
        lhs.add("world");
        lhs.add("java");

        for (String s:lhs){
            System.out.println(s);
        }
    }

}
执行结果:
hello
world
java
posted @ 2022-11-04 15:12  一统天下。  阅读(9)  评论(0编辑  收藏  举报