Java 3分钟入门
面向对象
接口
多线程
流
容器.数据结构
泛型
本章节我们主要从Java入门到流畅使用Java,结合我们的C++知识,提纲掣领从「面向对象」这个基本概念出发,引出一系列Java的概念和使用方式。
面向对象-封装、继承、多态
面向对象的封装以「类」为实现手段,Java中的字段(Filed)和方法(Function)以及关键字class
构建了基本的封装形态-类
//Node.java
public class Node{
protected int value = 0;//filed;
protected float f_value = 0.0f;
private final String type = "Node-type";//final关键字类似于C++中的const,不过final的使用范围更广,可用于修饰类(被final修饰的类无法派生)
public void setValue(int value){//function
this.value = value;
}
public void setValue(float value){//方法重载
this.f_value = value;
}
Node(){//构造函数
super();//需要位于函数首行
}
//Java并没有析构函数的概念
Node(int value){//构造函数的重载
this.value = value;
}
//Node类的入口函数,main.
public static void main(String[] args){
//jvm执行node.class文件时,首先调用的函数就是main函数。
}
}
以上就是Java的基本的类封装。
//Node.java
public class Node{
private int value = 0;
Node(int value){
this.value = value;
}
Node(){}//如果没有提供带参的构造函数,那么编译器会自行生成默认构造函数,但是,当自定义了构造函数时,则编译器并不再提供默认构造函数。
public void printValue(){
System.out.println(this.value);
}
}
//TNode.java
public class TNode extends Node{//java使用extends关键字作为继承
TNode(int value){
super(value);
//
if(value){
///
}else{
///other logic
}
}
}
//Main.java
public class Main{
public static void main(String[] args){
TNode tnode = new TNode(11);
tnode.printValue();//输出11
Node tnode = new TNode(12);
tnode.printValue();//输出12;//父类引用指向子类,多态特性
}
}
面向对象中一个重要的特性是「多态」,「父类引用(指针)指向子类,该引用(指针)有用子类特性,这被称为多态性」。
多态性之所以在面向对象中占核心重要地位,缘于其本身就是「封装」的代表。
多态性使得「金玉其外精华其中」得以实现,使用一个固定的「对象」供第三方(外界)使用,而不暴露这个固定对象后面的具体实现,这样的好处在于当需要进行重构时,供外界使用的对象并不需要进行改动,而功能(或优化)在内部「偷偷进行」,而不大动干戈。这个供外界使用的固定对象通常被称之为「接口」。
而在Java中甚至专门为此设立了一个关键字interface
(接口)
与interface
相提并论的通常还有abstract
抽象类
abstract
//Node.java
public abstract class Node{//抽象类
//抽象类无法被实例化(也就是并不能new一个Node对象)
//抽象类提供抽象方法与普通方法
private int value = 0;
public abstract void printValue();//抽象方法并不需要(也不能)提供实现
public void showValue(){//普通方法提供实现
//The implementation
}
Node(){}//抽象类可以拥有自己的构造函数
Node(int value){this.value = value;}//抽象类可以拥有自定义构造函数
}
//抽象类的通常目的,就是用做多态使用,抽象类无法被实例化,这就注定只能充当实例类的引用(指向某实例对象);
//需要说明的是,抽象类无法被实例化,但是却能够拥有自己的构造函数
//TNode.java
public class TNode extends Node{
//TNode必须实现抽象类Node中的抽象方法,否则,TNode本身就成了一个抽象类(通常,这并没什么意义)
@Override
public void printValue(){
//The implementation
}
}
以上,抽象类的主要作用是体现在「多态性」的实现。
通过abstract
关键字创建一个抽象类,这样的类含有抽象方法。
1、该类无法被实例化
2、抽象方法必须被子类实现(否则子类无法完成实例化)
3、抽象类可以拥有非抽象方法(这就使得其子类不必重写所有方法)
这就从语法层面对「多态」这一特性进行了进一步加强。
抽象类本质还是一个类,而Java是不支持多重继承的,因此一个子类是无法继承多个抽象类的,因此Java提供了另一个「更抽象」的概念:interface
(接口)
接口
interface
接口是一种更「纯粹」的抽象类。
接口中只有常量而无变量,只有方法的定义而无实现(jdk8后支持default
关键字,使得接口中也可以实现方法,不过需要用default
修饰。目的在于使得实现接口的类不必自行实现方法,而获得默认方法的能力)
//DInterface.java
public interface DInterface {
static final int t = 11;//接口中定义的filed必须被初始化,因为interface无法被实例化,因此变量t并不能在创建对象时初始化(因为并不能创建对象)
public void fun1();//接口实现类必须实现该方法,否则接口实现类必须为抽象类
public default void fun2(){//jdk8之前是不支持在接口中实现方法的,因此在jdk8之前,接口的所有实现类都必须将接口中的方法实现,否则只能成为抽象类。而jdk8之后的default关键字,提供了一种其他语言中的option功能,也就是「可选的方法实现」,接口实现类不一定要实现这个方法,这就提供了一种更大的自由度
//The implementation
}
public abstract void fun3();//接口实现类必须实现该方法,否则接口实现类必须为抽象类
}
public interface Fly{
public void fly();
public default void birdFly(){}
public default void duckFly(){}
public default void chickenFly(){}
}
public interface Walk{
public void walk();
public default void birdWalk(){}
public default void duckWalk(){}
public default void chickenWalk(){}
}
//SpecialAinmal.java
public class SpecialAnimal implements Fly,Walk{
@Override
void duckFly(){
//The implementation
}
@Override
void duckWalk(){
//The implementation
}
}
//SpecialAnimal这个类实现了🦆走路和🦆飞的方法,但是它还是叫SpecialAnimal而非鸭子,可能后续有进化,又拥有了其他方法,但依旧还是SpecialAnimal。Java虽然不支持多重继承,但是支持同时实现多个接口,这就是「更纯粹的抽象类」- 「接口」的魅力。
本质上,interface
接口,是一种「协议protocol
」,事先设计好一种协议(interface
),相关方都实现该协议,这样就能自由的调用与被调用。
多线程
Java的多线程被封装成线程类,使用及其简单,线程加锁也通过synchronized
关键字简单高效的得以实施,此外,Java多线程也提供诸如wait
notify
等等函数来进行更细节灵活的操作。本章节只讲解最基本的Java线程操作。
//Work.java
public interface Work {//创建一个接口
public abstract void job();
}
//TThread.java
public class TThread extends Thread{
private Work work = null;
private static String flag = "once";
TThread(Work job){//接受外界传入的一个工作者对象
work = job;
}
TThread(){}
@Override
public void run() {//当线程被启动(start)时,该方法被执行,在这个方法内调用work对象的job方法开始执行任务
super.run();
synchronized (flag){//synchronized传入一个对象,当该对象被1个线程获取时,另一个线程则无法获取该对象,在Java中,每一个对象都可以被当做同步对象,这就是java的线程锁机制,synchronized代码块只能被1个线程执行完,该线程结束,另一个线程才能回去到该同步对象,并执行代码块
if(work != null){
work.job();
}
}
}
}
//Worker.java
public class Worker implements Work{
@Override
void job(){
//do the job logic
}
public static void main(String[] args){
Worker wk = new Worker();
//开启2个线程执行工作
TThread t = new TThread(wk);
t.start();
TThread t2 = new TThread(wk);
t2.start();
}
}
以上就是一个最简单的多线程使用场景。在番外篇中我们会更为详细的讨论Java的多线程相关内容。
IO与流
Java将IO相关接口封装得非常简单好用,整体以「流(Stream
)」为世界观构建。为内存中的数据提供各种编解码方式。
「流」是一个非常抽象的概念,但好在如果以「二进制bit序列」的角度去尝试理解则会具象很多。一切数据,无论存在于文件,还是内存,亦或是其他输入输出设备甚至远程主机(网络),本质都能被抽象成「一串」二进制序列,也就是我们所谓的「Stram
流」。
「水往低处流」,流,具有方向性。通常以「程序」为起点,「设备」为终点,确定为流的方向。
内存中数据(程序数据)流向设备(如写文件),称之为「输出流」,向屏幕上输出内容(内存数据流向设备),同样被称之为「输出流」。
而设备数据流向程序(内存),则被称之为「输入流」,如读取文件,如从远处设备(网络)读取数据等等。
除了「输入输出流」「字节流字符流」这种见名知意的所谓「流类型」外,还需要从逻辑上理解一种流类型:「节点流」「过滤流」。
当然,在java中并不存在真正的概念或者关键字用来表示所谓节点流或者过滤流,这只是从逻辑上而言的
- 节点流: 直接的操作IO设备,这种流就是节点流。
- 过滤流: 对节点流的包装,这种流类型提供更强大的「附加」功能
//File.java
public class File {
public static void main(String[] args) {
try {
//创建一个节点流,非常简单
FileOutputStream outstream = new FileOutputStream("file.txt");
byte[] bytes = new byte[111];
for (int i = 0;i<bytes.length;i++){
bytes[i] = Byte.parseByte(String.format("%d",i));
}
//创建节点流后,直接往磁盘文件中写入内容
outstream.write(bytes);
}catch (IOException e){
}
}
}
以上就是一个节点流的基本用法,但是这样似乎效率不高,我们可以更近一步,使用过滤流,给这个节点流添加一个缓存buf,提高IO效率
//File.java
public class File {
public static void main(String[] args) {
try {
//创建一个单节点,这个file节点既不能直接写入字符串,也没有buf功能
FileOutputStream outstream = new FileOutputStream("file.txt");
//创建一个buf过滤节点,用来包装这个单节点,使得这个单节点拥有buf功能,IO效率更高
BufferedOutputStream bufOutStream = new BufferedOutputStream(outstream);
//buf这个过滤节点包装后,这个流依然不具备直接写入字符串的能力,需要再用一个输出写入流进行包装
OutputStreamWriter outwrite = new OutputStreamWriter(bufOutStream);
//最后,这个流就具备了,文件读取,buf缓存以及直接写入字符串的功能。通过这种「搭积木」的构建方式,能够自由灵活的构建所需要的完整功能,这也是unix哲学中的「功能最小化」世界观。
outwrite.flush();
outwrite.write("hello world");
outwrite.close();
/*
当然,在实际中,相较于OutputStreamWriter,我们更多使用的是DataOutputStream,根据其名可知,这是一个专注数据处理(输入输出)的过滤流,实际上,这个类提供了各种方便快捷的数据写入方式
DataOutputStream dataout = new DataOutputStream(bufOutStream);
dataout.writeBytes("opps");//直接写入字节流
dataout.flush();
dataout.close();
*/
}catch (IOException e){
e.printStackTrace();
}
}
}
Java中的容器(数据结构)
「容器」是每个面向对象编程语言的编程利器,C++中有STL(尽管饱受诟病),Java中有一系列自带的类库,均以面向对象的方式进行封装,简单好用。
Collection
- List
- ArrayList
- LinkedList
- Vector
- Stack
- Set
- HashSet (还有其他,这个最常用)
- List
Map
- HashMap
- HashMap(还有其他,这个最常用)
- TreeMap
- Hashtable(还有其他,这个最常用)
- HashMap
这就是Java中的两类数据容器。
最常见的数据结构是List
Set
Map
这3类。每一类中都有具体的容器类,在同一个系列下,不同的容器类源于其底层实现的数据结构的不同。
这些容器之间几乎使用相同的面向对象接口,决定这些容器的不同使用方式通常指取决于2点
1、业务需求是什么(是单纯的元素存储还是键值对方式)
2、结合业务需求选用最高效(结构合适)的那个容器
本章节只简单的讨论我们常用的集中容器类型
- 「动态数组类型」
- 「字典类型」
- 「集合类型」
这三种也是实际场景中最为常见和常用的容器类型
-
「动态数组」
ArrayList
,这个动态数组底层是基于连续内存分配的方式进行实现的,因此拥有极高的随机访问和遍历特性,但插入和删除效率较低。//DataContainer.java public class DataContainer { public static void main(String[] args) { //内存连续的动态数组,Java的数据容器只支持「对象」,而不支持「原始(primitive)」类型,不过好在Java提供primitive的「装箱」操作,也就是提供一个对象类型,可以无缝的用来包装primitive类型数据。比如Integer v = 11;这里的v是一个Java的对象,而Integer是一个Java类,专门用来包装int这个原始类型的,我们知道每一个Java对象的创建都是通过new来进行的,而Java为了原始类型与对象类型能够「看起来」无缝装箱,因此从语法设计上支持Integer value = 1024这样的操作,但并不意味着value是一个primitive ArrayList<Integer> nums = new ArrayList<>(); for (int i=0;i<100;i++){ nums.add(i); } nums.set(0, 1024); int v = nums.get(0); System.out.println(v);//output:1024 System.out.println(nums);//output:[1024,1,2,3...] nums.removeAll(nums); System.out.println("after removed:"+nums);//output:[] nums.add(101); int lastone = nums.get(nums.size()-1); System.out.println(lastone);//output:101 } //ArrayList是内存连续的结构,因此对于数据的访问和遍历是非常高效的,但是增加和删除效率都比较底下 }
LinkedList
这个动态数组底层是基于双向链表的方式进行实现的,因此拥有极高的插入删除特性,但随机访问和遍历效率低下。//DataContainer.java public class DataContainer { public static void main(String[] args) { //基于双向链表实现的动态数组,这个类也实现了List接口,因此从操作方法上与ArrayList无异,不过因为其基于链表的实现原理,该类也实现了Deque接口,因此也支持push、pop、addLast、addFirst等链表特性的方法,因此,若无需进行频繁的随机访问,而又需要频繁的插入删除操作,则可选择LinkedList容器 LinkedList<Integer> list = new LinkedList<>(); list.add(11); list.add(22); System.out.println(list); list.remove();//移除首个元素 System.out.println(list); list.push(33);//提供栈的push方法 list.push(44); System.out.println(list); list.pop(); System.out.println(list); } }
此外Java也提供
vector
这样的动态数组,与其他不同的是这是 一个线程安全的动态数组,但效率很低,因此现在实际中使用并不太多了。 -
「字典类型」
Map
就是Java中的所谓字典类型,不过这是一个接口,其实现最为经典古老的类是HashTable
,这是一个线程安全类,不过官方逐渐放弃对其维护,因此现在最为常用的是「HashMap
」//DataContainerMap.java public class DataContainerMap { public static void main(String[] args) { //数据放在内存中,其实无非不是只有2种情形,1、一段连续的内存,2、一段非连续的内存。一段连续的内存只需要知道这段内存的起始地址以及结束(长度或者某个标志)就能进行管理,而另外一种类型是离散的内存,对离散内存的管理无疑是比连续内存管理复杂度更高更难的,但它的意义很大,在于既能够充分利用碎片化的内存,也能够发挥高效数据插入删除的优势,「管理离散内存」的方案大体上是2个方向,1、表,2、树。链表本质是一种特殊的树,而Map容器就是「表」的管理方式。 //所谓表,记录的是一种「A数据与B数据之间的关系」,这些关系通通被记录在表中,在表中A数据通常被称为字段(key),而B数据则通常被称为值(value),通常一个表中会记录N对这样的关系对,本质上这些关系对没有顺序前后关系,但可以从逻辑上进行排序(比如根据字段的某些特性如ASCII码等)。 //在Java中,一对这样的关系被称为一个实体(Entry) HashMap<String,Object> map = new HashMap<>(); map.put("name","zs");//创建一个实体(entry) map.put("age",11); map.put("height",123.0f); for (String key:map.keySet()){//遍历这个表中所有实体的字段值(key) System.out.println(key); } //遍历这个表中所有的entry,一个entry拥有key,和value for (Map.Entry<String,Object> enter:map.entrySet()){ System.out.println(enter.getValue()); } //一个表中所有的字段(key)和值(value)分别构成一个「集合set」,set也是一种数据容器,一种离散的内存数据结构,Java中的HashSet本质就是HashMap,set的底层数据结构就是一个HashMap,调用set结构存放object时,本质就是在往HashMap中存放数据,不过放入set中的对象在底层被用作HashMap的key,如此而已。 //set的用处在于极高(O(1))的访问速度,以及不允许重复元素存在的特性,这在很多场合中带来便利,去重,就是一项基本的功能。 } }
以上,就是本章节的基本内容,此外,Java还有很多其他的概念和功能,这部分内容在番外篇中讲解。
posted on 2021-12-28 00:10 shadow_fan 阅读(25) 评论(0) 编辑 收藏 举报