面向过程的性能比面向对象高。因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考虑因素的时候,比如单片机,嵌入式一般采用面向过程开发。
面向对象易维护、易复用、易扩展。因为面向对象有封装、继承、多态特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。但是,面向对象性能比面向过程低。
1.简单易学
2.面向对象
3.平台无关性(一次编译,到处运行)
4.可靠性
5.安全性
6.支持多线程
7.编译与解释并存
JVM是运行JAVA字节码的虚拟机。JVM有针对不同系统的特点实现,目的是使用相同的字节码,他们会给出相等的结果。
1.3.1.1 什么是字节码?采用字节码的好处是什么?
在Java中,JVM可以理解的代码叫做字节码(拓展名为.class),他不面向任何处理器,只面向虚拟机。Java语言通过字节码的形式,在一定程度上解决了传统语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不针对一种特定的机器,因此,Java程序不用重复编译便可在多种不同的操作系统的计算机上运行。
Java程序从源代码到运行一般有下面3步:
需要注意的是.class到可执行的二进制机器码的过程中,JVM有两种方式,一种是解释器,一种是JIT编译器。如果是热点程序会走JIT编译器,否则走解释器。
总结:
Java虚拟机是运行Java字节码的虚拟机。JVM有针对不同系统的特定实现,目的是用相同的字节码,能出相同的结果。不同系统的JVM实现了Java语言的“一次编译多次运行”的关键所在。
JDK是Java Development Kit,它是功能齐全的Java SDK(软件开发工具包)。它拥有JRE所拥有的一切,还有编译器(javac)和工具(如javadoc和jdb)。他能够创建和编译程序。
JRE是Java运行时环境。他是运行已编译Java程序所需的所有内容的集合,包括Java虚拟机(JVM),Java类库,java命令和其他的一些基础构建。但是,他不用用于创建新程序。
如果你只是为了运行下Java程序的话,那么你只需要安装JRE就可以了。如果你需要进行一些Java编程方向的工作,那么你就需要安装JDK。但是,这也不是绝对的。有时,即使你不打算在计算机进行任何Java开发,但仍需要安装JDK。例如,如果要使用JSP部署Web应用程序,因为应用程序服务器会将JSP转换为Java servlet,并且需要使用JDK来编译servlet。
同:都是面向对象的语言,支持封装、继承、多态
异:1.Java不提供指针操作内存,程序内存更加安全
2.Java的类是单继承的,C++可以多继承;虽然类不能多继承,但是接口可以多实现
3.Java有自动内存管理机制,不需要程序员手动释放无用内存+
1.形式上:字符常量是单引号引起的一个字符;字符串常量是双引号的若干字符
2.含义上:字符常量相当于一个整型值(ASCII值),可以参加表达式运算;字符串常量代表一个地址值(该字符串在内存中存放位置)
3.占内存大小:字符常量只占两个字节(Java中);字符串常量占若干个字节
1字节:B
2字节:CS
4字节:IF
8字节:DL
构造器不能被Override(重写),但是可以被Overload(重载),所以你可以看到一个类中有多个构造函数的情况。
重载是同一个方法能够根据输入数据的不同,做出不同的处理
重写是当子类继承父类的相同方法,输入数据一样,但要做出有别于父类的相应时,你就要覆盖父类的方法。
发生在同一个类中,方法名必须相同,参数类型不同,个数不同,顺序不同,方法返回值和访问修饰符可以不同。
重写发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写。
1.返回值类型、方法名、参数列表必须相同,
2.如果父类的方法修饰符为private/static/final,子类不能重写该方法。
3.构造方法无法被重写
综上说:重写就是对父类的方法进行重新改造,外部样子不能改变,但是内部逻辑可以改。
将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问。
好处:1.只能通过规定的方法访问数据。
2.隐藏该类的实例细节,方便修改和实现。
A.访问修饰符
B.Java中的内部类:
内部类就是定义在另外一个类里面的类。与之对应,包含内部类的类被称为外部类。
那么问题来了:那为什么要将一个类定义在另一个类里面呢?清清爽爽的独立的一个类多好啊!
内部类的作用:
1.内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类。
2.内部类的方法可以直接访问外部类的所有数据,包括私有的数据。
3.内部类所实现的功能使用外部类同样可以实现,只是有时使用内部类更方便。
内部类分类:
继承是类与类的一种关系,是一种”is a”的关系。比如“狗”继承“动物”,这里动物类是狗类的父类或者基类,狗类是动物类的子类或者派生类。
好处:
子类拥有父类的所有属性和方法(除了Private修饰的属性不能拥有)从而实现了代码的复用;
A.方法的重写
子类如果对继承的父类的方法不满意(不合适),可以自己编写继承的方法,这种方式就成为方法的重写,当调用方法时会优先调用子类的方法。
重写要注意,以下三个条件都是保持一样:
比如动物会叫,那么狗叫,a.都返回叫;b.都是叫的方法;c.都是需要相同类型的发生器官
方法重载:在同一个类中处理不同数据的多个相同方法名的多态手段。
B.继承的初始化顺序
父类对象属性初始化—》父类对象构造方法—》子类对象属性初始化—》子类对象构造方法
C、Final关键字
使用final关键字做标识有“最终的”含义:
多态是对象的多种形态。
Java中里的多态主要表现在两个方面:
引用多态:
父类的引用指向本类的对象;
父类的引用可以指向子类的对象;
方法多态:
当我们父类的引用指向不同的子对象时,他们调用的方法也是多态的。
创建本类对象时,调用的方法为本类方法;
创建子类对象时,调用的方法为子类重写的方法或者继承的方法;
但是多态使用的时候应该注意:如果子类编写了一个父类不存在的方法,那么就不能通过父类的引用来调用这个方法。
注意:继承是多态的基础
A、引用类型转换
1.向上类型转换(隐式/自动类型转换),是小类型转换到大类型。
2向下类型转换(强制类型转换),是大类型转换到小类型(有风险,可能出现数据溢出)。
但是,如果父类没有引用该子类,那么是不能向下类型转换,虽然编译器不报错,但是运行会出错:
还有一种情况也无法强制,父类引用是指向一个子类,之后又去强转为其他类中是不行的。
是因为程序animal开辟的是Dog类型的内存空间,这与Cat类型内存空间不匹配,所以无法正常转换。
instanceof运算符
instanceof是JAVA的一个二元操作符,和==,>,<是同一类。他的作用是测试他左边的对象是否是它右边的类的实例,返回boolean类型的数据。
B、抽象类
定义:抽象类前使用abstract关键字修饰,则该类为抽象类。
使用抽象类要注意以下几点:
C、接口
1.概念
接口可以理解为一种特殊的类,由全局变量和公共的抽象方法所组成。
如果说类是一种具体实现体,那么接口定义某一批类所需要遵守的规范,接口不关心这些类的内部数据,也不关心这些类里面方法的实现细节,它只规定这些类里必须提供的某些方法。(与抽象类相似)
2.基本语法
3.为什么要有接口
Java中一个类只能继承一个父类,是不够灵活,通过实现多个接口可以补充。
注意:如果要继承父类,继承父类必须在实现接口之前,即extends关键字必须在Implements关键字之前。
D、接口和匿名内部类配合使用
接口在使用过程中经常和匿名内部类配合使用。匿名内部类就是没有名字的内部类,多用于关注实现而不关注实现类的名称。
String类中使用final关键字修饰字符数组来保存字符串,所以String对象是不可变的。
他们都是继承自AbstactStringBuilder类。其中char[]没有被final关键字修饰,所以这两种对象都是可变的。
String中的对象是不可变的,可以理解为常量,线程安全。StringBuffer对方法加了同步锁,线程安全;StringBuilder没有对方法进行加同步锁,所以是非线程安全的。
String修改的时候都会需要新生成一个String对象,然后将指针指向新的String对象。
StringBuffer和StringBuilder都会对自己操作,不会更改指针指向。StringBuilder的性能比StringBuffer好,但是冒着多线程不安全的风险。
对于三者的总结:
1.操作少量的数据:使用String
2.单线程操作字符串缓冲区下操作大量数据:使用StringBuilder
3.多线程操作字符串缓冲区下操作大量数据:使用StringBuffer
装箱:将基本类型用他们对应的引用类包装起来;
拆箱:将引用类型转换为基本数据类型;
因为静态方法可以不通过对象调用,所以静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。
Java程序在执行子类的构造方法的时候,会去调用父类特定的构造方法,如果没有,会去调用父类的空参构造方法。所以为了编译不报错,父类必须要加上无参构造方法。
1.一个类只能继承一个抽象类,而一个类却可以实现多个接口。
2.抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;所以不能实现类中不能重新定义,也不能改变其值;而抽象类成员变量是frendly,其值可以在子类中修改。
3.抽象方法中可以有非抽象方法,接口中则只能有default,static办法(JDK1.8)。
4.接口中可以省略abstract,抽象类是不能省略的。
5.接口中不能含有静态代码块,而抽象类可以有静态代码块;
1)抽象类是对类的一种抽象,而接口是对行为的抽象。比如飞机和鸟,他们都会飞,我们会把飞机设计为一个类,鸟会设计一个类,但是不会把飞行设计为一个类,只会把飞行设计为一个接口,然后类去实现自己的飞行方式。其他不能飞的东西就不能实现这个接口。
1.从语法上看:成员变量是属于类的,局部变量是属于方法的。成员变量可以被pubilc、private、static修饰,局部变量不能被访问修饰符修饰及static修饰;但是,成员变量和局部变量可以被final修饰。
2.从变量在内存中的存储方式来看:因为成员变量是属于类的,所以是存在堆中的。局部变量如果是基本类型,是存在栈内存的,如果是引用类型,存的内容是指向堆的地址或者常量池中的地址。
3.从变量生存周期来看:成员变量随着对象消亡而消亡;局部变量随着方法消亡而向往。
4.是否能有初值
对象实体是存在堆内存中,对象引用是存在栈内存。一个对象可以被任意个对象引用所引用。
1.名字与类名相同
2.没有返回值,但不能用void声明构造函数。
3.生成类的对象时自动执行,无需调用。
1.在外部调用静态方法时,可以使用“类名.方法名”的方式,也可以使用“对象名.方法名”的方式。而实例方法只能有后面这种方式。也就是说,调用静态方法可以无需创建对象。
2.静态方法在访问本类的成员的时候,值允许访问静态成员,而不允许访问实例成员变量和实例方法;实例方法则无此限制。
帮助子类做初始化工作
它的作用是判断两个对象的地址是否相等。判断是不是同一个对象(基本数据类型的==是比较值,引用数据类型==比较的是内存地址)
它的作用是判断两个对象是否相等,一般都有两种情况:
情况1:类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”来比较对象。
情况2:类覆盖了equals()方法。一般,我们都会覆盖equals()方法来比较两个对象的内容是否相等。若他们相等会返回true。
说明:
1.String中得到equals方法是被重写的,因为Object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。
2.当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String.
a,b自身的地址是不一样的,但是指向内容是一样的。
面试官可能会问你:“你重写过hashcode和equals吗,为什么重写equals时必须重写hashCode方法”
1)hashCode:
hashCode()的作用是获取哈希码,也称为散列码;它实际上是返回一个Int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。HashCode()是定义在Object类中的,所以涉及到对象我们就会用到这个方法,而且这个方法时本地方法,是用C或C++实现的,该方法通常用来把对象的物理地址转换为整数之后返回。
用散列表的原因是:查找对象的时候可以根据索引快速找到,时间复杂度是O(1)。
2)为什么重写equals时必须重写hashCode方法?
如果两个对象相等,则hashcode一定也是相同的。两个对象相等,对两个对象分别调用equals方法都返回true。但是,两个对象有相同的hashcode值,他们也不一定是相等的。因此equals方法被覆盖过,则hashCode方法也必须被覆盖。这样才能保证相等。
就是说虽然是一个类,但是实例不一样,所以在堆上面产生的也不一样,所以hashcode也不一样。如果要一样的话必须重写hashcode方法。
3)为什么两个对象有相同的hashcode值,他们也不一定相等?
因为hashCode()所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指不同的对象得到相同的hashCode).
比如HashSet,它如何避免添加重复值呢?通过hashcode与equal一起作用,hashcode只是用来缩小查找成本。
程序是含有指令和数据的文件,被储存在磁盘或其他的数据存储设备中,也就是程序是静态的代码。
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序是一个进程从创建、运行到消亡的过程。一个进程就是一个执行中的程序,他在计算机中一个指令接着一个指令地执行着,同时,每个进程还占着某些系统资源如CPU时间,内存空间,文件,输入输出设备的使用权等等。换句话说,当程序在执行的时候,将会被操作系统挂入内存中。
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间切换工作时,负担要比进程小得多,所以线程也被称为轻量级进程。
线程比进程多了一个超时等待。
这边对应进程的五个状态:
Final关键字主要用在三个地方:变量、方法、类。
1.对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
2.当用final修饰一个类时,表明这个类不能被继承。Final类中的所有成员方法都会被隐式地指定为final方法。
在Java中,所有的异常都有一个共同的祖先是Throwable类。其中有两个子类Exception(异常)和Error(错误)。Exception能被程序本身处理(try..catch),Error是无法处理的(只能尽量避免)。
程序本身可以处理的异常,可以通过catch来进行捕获。Exception又可以分为受检查异常(必须处理)和不受检查异常(可以不处理)。
Error属于程序无法处理的错误,我们没有办法通过catch来进行捕获。例如,Java虚拟机运行错误、虚拟机内存不够(OOM)、类定义错误。这些异常发生时,Java虚拟机一般会选择线程终止。
不受检查异常:只有RuntimeException及其子类
受检查异常:除开RuntimeException及其子类意外的异常类都需要
Throwable类常用方法
public string getMessage():返回异常发生时的简要描述
Public string toString():返回异常发生时的详细信息
Public string getLocalizedMeassage():返回异常对象的本地化信息。
Public void printStackTrace():在控制台上打印Throwable对象封装的异常信息
Try块:用于捕获异常。
Catch块:用于处理try捕获到的异常
Finally块:无论是否捕获或处理异常,finally块里的语句都会被执行。
1.在try或finally用了system.exit(int)退出程序。
2.所有线程死亡
3.关闭CPU。
将Java对象转换成二进制流写入磁盘,这个过程叫做序列化
Java中对象的序列化指的是将对象转换成字节序列的形式来表示,这些字节序列包含了对象的数据和信息,一个序列化的对象写到数据库或者磁盘中,也可用于网络传输,其中我们的实体类实现Serializable接口,目的就是为了让其可序列化。
当然,序列化的目标就是为了反序列化,这样才可以把传输过来的二进制转化为本地java对象。
如果不想被序列化那么在变量名前加transient关键字,其中的作用有两点:
1.为了数据的安全,避免序列化和反序列化。在实际开发中,如果一个用户有一些敏感化的词(比如密码和银行卡号等),为了安全起见,不希望在网络操作中被传输,这些信息就需要加上transient关键字,这个字段只会在调用者的内存中,而不会写到磁盘里持久化。
2.节省存储空间。类中的字段可以从其他字段导出,比如计算一个矩形的面积,我们只需要长宽就可以了,那么面积就不用被序列化。
按照流向方向区分:输入和输出流
按照操作单元区分:字节流和字符流
按照担任角色划分:节点流和处理流
输入和输出流
写文件输入流,读文件输出流
字节流和字符流
字节流是数据单位为8位的字节,字符流操作的是数据单位为16位的字符
为什么要字符流:Java中字符采用的是Unicode标准,在Unicode标准下,一个英文为一个字节,一个中文是两个字节。如果采用字节流一个一个读会出现乱码。有字符流才会在缓冲区根据编码规则进行编码成对应的字符。
节点流和处理流
节点流:直接操作数据读写的流类。如FileInputStream
处理流:对一个已存在的流的链接和封装,通过对数据进行处理为程序提供功能强大、灵活的读写功能,例如BufferedInputStream(缓冲字节流)
处理流和节点流应用了Java的装饰者设计模式。
在诸多的处理流中,缓冲流非常重要。为了减少程序与磁盘的交互(IO操作真的很浪费时间)
就跟我们搬砖一个道理,我们一块一块搬到车前是比我们拿个小推车,先把砖装上小推车,然后小推车在推到车面前效率低很多。
但是如果其中长度超过了输出缓冲区的大小,刷新输出缓冲区,然后直接写入数据。所以就跟车子有容量,你装了太多车子会坏掉的一样道理。
BIO(Blocking I/O):同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况,这种模型是比较不错的,可以让每一个连接专注于自己的I/O并且编程模型简单,也不用过多考虑系统的过载、限流等问题。
NIO(Non-Blocking/New I/O):同步非阻塞I/O模型,NIO提供了与传统的BIO模型中的Socket和ServerSocket 相对应的SocketChannel和ServerSocketChannel两种不同的套接字(用程序通过网络协议进行通信的接口)通道实现。这两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的应用,应使用NIO的非阻塞模式来开发。
AIO(Asynchronous I/O):AIO也就是NIO 2。在Java 7中引入了NIO的改进版NIO2,它是异步非阻塞的IO模型。异步IO是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会阻塞在哪里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
浅拷贝:对基本数据类型进行值传递,对引用数据类型进行地址拷贝。
深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容。
1.新建文件.java
2.javac 将.java文件编译成字节码.class文件
3.java Test 执行字节码文件,命令台输出test
反射是一个比较生涩难懂的概念,为了形象,我先举一个例子,我们有时候项目中会用mysql,有时会用到sqlserver等数据库。我们实际情况是需要哪个驱动类才会去加载,不会把这些数据库加载驱动都加载到JVM里面,如果都加载,岂不是JVM可用的内存就少了,这对服务器来说就很难受了,内存中存在一些经常不用的驱动类。因此我们需要在动态去加载我们需要的驱动。如果不是动态加载,如果静态加载的话,那么就是这样的情况,要切换数据库驱动的时候,我们就把服务停下来,new一个需要的驱动,这对用户来说体验极差,肯定不行啊。因此我们就会采用动态加载。
反射机制就是采用这种动态加载,我们通过三种方法获得Class:
然后获得了Class后,我们就可以调用Class对象的newInstance()方法来创建Class对象对应类的实例了;或者通过获得构造器(类一定要有空参构造),给构造器传入参数,也可创建实例。
数组:在内存中,数组是一块连续的区域,随机查找快。插入数据和删除数据效率低,插入数据时,这个位置后面的数据在内存中都要往后移动。删除数据时,这个数据后面的数据都要往前移动。
链表:在内存中可以存在任何地方,不要求连续。增加数据和删除数据很容易,直接将尾指针指向新加的节点,删除数据的话,把尾指针指向下一个节点即可。但是查询的时候,需要从头结点一个一个查询下去!
从以下几个方面介绍:
在静态方法中,我们可以直接类名.方法名()即可调用,要注意的是,静态方法中是不可访问非静态变量和方法的。
静态变量是被所有的对象共享,在内存中只有一个副本(存在方法区),只能在类初次加载的时候才会被初始化。
只需要执行一次的初始化操作都放在static代码块中进行,为了优化程序性能。比如判断年龄区间,有一个开始年龄和结束年龄,我不会每次都用Date类去生成,固定好了区间,直接初始化一次即可。
1.如果内部类不会引用到外部类东西的话,强烈建议使用静态内部类,因为这样更节省资源,减少内部类其中的一个指向外部类的引用。
2.这个静态内部类,不需要在外部引用,只为当前类提供服务。 封装的完整性。
3.还有静态内部类只能访问外部类的静态属性和方法,而且静态内部类还可以有静态数据,静态方法或者又一个静态内部类,这些是非静态内部类所没有的。
其目是为了减少字符输入量,提高代码的可阅读性,以便更好地理解程序
Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤、映射数据等操作。
特点:
1.不是数据结构,不会保存数据
2.不会修改原来的数据源,他会将操作后的数据保存到另外一个对象中(peek方法可以修改)
3.惰性求职,流再中间处理过程,只是对操作进行哦记录,并不会立即执行,需要等到执行终止操作的时候才会进行实际的计算。
常用方法:
最后转成数据结构collect()