java关于接口,抽象类,集合,多线程

接口是什么?接口既可以理解为一组需求,也可以理解为一组规定。比如生活中的USB接口,不管什么USB插进去都能用,只要插进去的USB满足一些功能上和工艺上的要求。这些要求具体是怎么实现的这种细节接口是完全不管的,但是只要你想生产面向市场的USB,就必须implements(实现)电脑生产厂商规定的接口——否则插都插不进去。

接口怎么用?接口可以作为类型声明,但是接口不是类,不能通过实例化new一个接口对象。那声明啥?声明一个实现了该接口的实现类。比如假设一个电脑满足了USB接口,就可以写出下面的语句:

USB o=new Computer();

接口对象也可以作为参数传进方法里,总之除了不能自己实例化自己,类经常出现的地方接口也能代劳。

看起来和继承里面的多态不是一回事嘛。我假如写个object来接收也差不多啊。其实如果是为了满足object有的功能,比如equals,clone等,那确实没必要写个接口。回到USB的例子,假如我想要一个exit功能呢,object里没有啊。

最直观的思路是object没有,那写个新的超类有呗。比如写个USB超类,里面就可以有start,exit等等。c++也确实是这么做的,但这带来一个问题,比如一个手机,超类是USB,那假如又想实现另一个耳机接口,又要来个超类耳机。假如耳机也有自己的start,或者说USB和耳机两个超类有同名成员,这不乱了套。当然,c++语法里肯定有各种处理这些情况的方法, 各种作用域啊,初始化列表啊之类的语法,也许学起来不算太深奥,但忘起来也是相当快,总之挺复杂的。

java另辟蹊径,规定了一个类只能有一个超类,一下子省了那么多麻烦的语法。那怎么实现多个超类的需求呢?没错,就是用接口。

接口里可以放些什么呢?和类也差不多,放属性放方法(只能放public方法)。放的方法默认就是abstract抽象方法,不需要写关键字。那些想实现这个接口的类也必须一一重写这些方法。jdk7以前接口里的方法就空有个名字,具体实现还得看具体类,但是8以后又可以在接口里写静态默认方法了。只要接口里的方法写了default或static关键字,具体实现类就可以不用重写接口里的方法了。

这里顺便说个题外话,在看《java核心技术》提到private方法那一章的时候我懵圈了好久,一个方法不就是给别人用的吗,有必要private嘛。后来想想,应该是出于封装性的考量,比如我在内部实现一个方法的时候先给它逻辑分成几个小函数,这些小函数只是方便调试用的,没有必要暴露出去,徒增麻烦。看源码的时候很容易发现很多时候想完成一件事要一层一层封装,最后肯定是用private方法来真正实现。这里可能有一些设计模式方面的考量吧!

接下来说说集合。这里我说的集合并不特指collection。集合分为单列集合和双列集合(也就是键值对映射集合)。单列集合是一个一个元素或按数组或按链表方式排列而成的,顶层接口是Iterable,可迭代的,下一层接口是Collection,再下面就开始细分了,有List和Set,然后就是各种具体实现类,比如ArrayList,LinkedList,HashSet,TreeSet之类。

而双列集合又是完全不同的一条路。它们的顶层接口是Map,接着往下就是各种具体实现了,我们常用的HashMap,TreeMap等等。

注意ArrayList是线程不安全的。

什么是线程?一个程序写好了放在那,当开始运行的时候,就创造了一个进程。进程是个相对抽象的概念,分配了一定内存空间,真正做事的还得是线程。进程可以创造线程,线程也可以创造线程。

比如一个java程序写出来,编译运行,运行就是创造了一个进程,而这个进程先创造一个main主线程。如果在main函数里又start几个线程,就实现了线程造线程,多线程开发。

想要创造一个线程,首先要写个线程类,这个类要实现runnable接口或者干脆直接继承Thread类。

这里建议runnable,因为一方面可以摆脱单继承限制的困扰,另一方面这样写起来代码非常直观,new一个Thread就是一个线程,创建一个runnable对象就是一个对象,想run它就把它塞到线程里,还可以把一个对象塞到两个线程里,实现多线程资源共享。

当然,资源共享也可能带来潜在的问题,比如规定某个静态量<0就中断当前线程,但是假如同时有多个线程访问这个静态量并让它自减,就有可能出现负数。这里有个机制叫线程同步机制,对于敏感资源可以利用这个机制实现同一时间只能有一个线程访问资源,其他线程则处于等待状态。

线程同步机制其实就是给对象上锁,其中互斥锁的关键字是synchronized,假设有个关键操作要访问敏感资源,那么就可以给这个方法加上synchronized,接着用run方法包装这个方法。这里互斥锁写在方法上,其实是加到了对象上,非静态方法都是加到对象(this或者另一个对象,但是必须始终如一)上,而静态方法则是加到类本身上。

既然这样,给方法上加synchronized关键字就挺没必要的,完全可以用另一种语法来代替:synchronized(object){//想要加锁的代码块}。

这儿可能有点晕,注意多线程资源共享的核心是一个runnable对象塞到多个线程里,run的是同一个run方法,所以给这个对象加锁才可以保证多个线程只有一个能执行被加锁的代码块(其他线程里的这一块也同样被加了锁)。

这里有个编程陷阱:死锁。什么是死锁?一个对象,多个线程,假如这个对象被上了锁,那么一个线程想要访问这个对象,必须要获得这把锁。考虑一种情况:这个对象有两个地方被上了锁(所以这是种风险挺高的写法),A线程得到了第一把锁,B线程得到了第二把锁。然后呢,然后就寄了。没有B手上的锁A执行不下去,B也同理。电脑可不会心理博弈,威慑平衡,两个线程杠上了就没完了,谁也走不下去。

说到共对象,顺便介绍一下一个常用的技巧,通知方法。其实就是假如现在我创建了一个线程,希望另一个线程能监督或操控这个线程,就可以在另一个线程类里包含被操控线程类的对象,接着通过某些指令来实现对这个对象的操控。

回头说线程,那么想要创造一个线程,我们具体要实现什么呢?其实就是要实现一个run方法,这个方法里就是我们要做的事情。而线程的灵魂其实是start方法,通过调用线程类实例的start的方法,这个对象才开始走它自己的线程。 

start方法其实是对start0的包装,start0是一个native方法,由JVM机控制,对不同系统用c/c++写了不同的算法,我们不能直接调用。start0方法其实是告诉cpu这个线程进入了可运行状态,具体什么时候运行还得看cpu的安排调度——当然一般都很快。

回到ArrayList的话题。ArrayList区别于数组的最经典方法:add前面就没有synchronized关键字:也就是说这个方法没有加互斥锁,用安全性换了更高的效率。所以在多线程开发的时候可以考虑用Vector。

ArrayList底层其实就是用一个object数组来实现的,每次扩容为原来容量的1.5倍。扩容的实质是调用Arrays.copyOf方法,源码如下:

 可以发现其实就是新建一个空数组然后再拷贝,返回的对象和传进来的对象没有半毛钱关系。

下面介绍线程安全的,c++的老熟人——Vector。vector的扩容机制是两倍扩容,其实我觉得再大点也没关系,反正扩容一次的时间复杂度和扩容大小关系不大,但是扩容次数还是挺重要的,当今容量远比时间廉价得多。

下面说一下链表——LinkedList。注意这也是个线程不安全的数据结构。先详细说一下remove()这个方法。

注意,要测试这个方法就不能用List接口来当作编译类型,因为List没有remove()方法, 只有remove(Object o)的有参移除方法。

当执行remove()的时候,会调用public的removeFirst()方法(其实直接用removeFirst更好一点,更容易传达语义)。

然后才真正调用一个private 的方法unlinkfirst(),并返回被移除的元素。移除方法是先用一个final 的Node类型常量来接收待移除(也就是第一个)元素,然后如图:

这段代码很有意思。首先解释一下注释help GC,就是帮助垃圾回收器的意思。java中一切等号都是按值传递,但是对象的引用按值传递其实是另一个引用,这点《java核心技术》里面阐述的挺好的。

首先,我们用next引用指向f.next,并将其作为final类型。注意final在修饰对象的时候有点像c++里的指针常量,指针固定而对象可改。

接下来是让人很容易困惑的两句代码:f.item=null,f.next=null。 我先用c++的地址思想阐述一下:想象f有一个地址,上面放着f这个Node对象,f这个对象里放着两个指针指向item和next。然后,这个item和next两个指针也有两个地址,这两个地址上放着的就是自己这个Node对象和下一个Node对象。

当我让f.next=null,其实就是让next地址上的值为null,嗯?那next地址上原来的对象呢?它没有被清空,而是被挪出了f对象的next成员的地址。

这里很容易被误解,误解为那两个对象变成null了。其实确实,假如没有前两行的final代码,那两个对象确实和null也没区别了:就算趁垃圾回收机制还没发现苟活一会儿,已经没有任何引用能访问到他们了,没有名字的对象和不存在也没区别。

这样一通操作,f的所有成员都是null,原本指向f的next.prev也是null了,整个程序nothing与f相关。垃圾回收线程一看,好,十分垃圾,可以回收了。next地址上原来的那个对象被next标识符用final接收地好好的,first离开了f指向了next,清理完成。返回值呢?不是被清空了吗?

还是那个道理,清空的只是f.item地址,上面的对象被拿走了,但没有被消灭(嗯,在这个方法走完之前垃圾回收机制是不会动手的,只有等element这个局部量被返回之后f才是彻底的垃圾,被毫不留情地回收掉)。

这篇文章也写了不短了,set接口和map接口就下次再写吧。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值