面向对象的优点是 易维护、易复用、易扩展。
为什么呢?
由于面向对象有封装、继承、多态三大特性,我们可以根据面向对象的思想设计出低耦合的系统,使系统更加灵活,更加易于维护。
面向对象的缺点是性能比面向过程低。
面向过程的优点是性能比面向对象高.
因为类调用时需要实例化,开销比较大,比较消耗资源.比如单片机、嵌入式开发、linux等一般采用面向过程开发而不是面向对象开发就是这个原因。
面向过程的缺点是没有面向对象易维护、易复用、易扩展。
java虚拟机(JVM)是运行java字节码的虚拟机,JVM有针对不同系统的特定实现(windows、linux、macos),目的是使用相同的字节码,它们都会给出相同的结果。
在java中,JVM可以理解的代码就叫做字节码(扩展名为.class),它不面向任何特定的处理器,只面向虚拟机。java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,java程序无须重新编译便可在多种不同的计算机上运行。
HotSpot 采用了惰性评估(Lazy Evaluation)的做法,根据二八定律,消耗大部分系统资源的只有那一小部分的代码(热点代码),而这也就是 JIT 所需要编译
的部分。JVM 会根据代码每次被执行的情况收集信息并相应地做出一些优化,
因此执行的次数越多,它的速度就越快。
JDK 9 引入了一种新的编译模式AOT(Ahead of Time Compilation),它是直接将字节码编译成机器码,这样就避免了 JIT 预热等各方面的开销。JDK 支持分层编译和 AOT 协作使用。但是 ,AOT 编译器的编译质量是肯定比不上 JIT 编译器的。
总结:Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同系
统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们
都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言能够做到“一次编译,随处可以运行”的关键所在。
JDK 是 Java Development Kit,它是功能齐全的 Java SDK。它拥有 JRE 所拥有的一切,还有编译器(javac)和工具(如 javadoc 和 jdb)。它能够创建和编译程序。
JRE 是 Java 运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,java 命令和其他的一些基础构件。但是,它不能用于创建新程序。
OpenJDK 存储库中的源代码与用于构建 Oracle JDK 的代码非常接近,因为oracle
JDK 版本构建是基于open JDK 构建的,在Open JDK的基础上增加部署代码,其中包括 Oracle 的 Java 插件和 Java WebStart的实现,以及一些封闭的源代码派对组件,如图形光栅化器,一些开源的第三方组件,如 Rhino,以及一些零碎的东西,如附加文档或第三方字体。
总结:
一个程序中可以有多个类,但只能有一个类是主类。在 Java 应用程序中,这
个主类是指包含 main()方法的类。在 Java 小程序中,这个主类是一个继承自系统类 JApplet 或 Applet 的子类。主类是 Java 程序执行的入口点。
应用程序的主类不一定要求是 public类,
但小程序的主类要求必须是 public 类。
简单说应用程序是从主线程启动(也就是 main() 方法)。applet 小程序没有
main 方法,主要是嵌在浏览器页面上运行(调用 init()线程或者 run()来启动),嵌
入浏览器这点跟 flash 的小游戏类似。
基本类型 | 大小 |
---|---|
boolean | 4字节 |
char | 2字节 |
byte | 1字节 |
short | 2字节 |
int | 4字节 |
long | 8字节 |
float | 4字节 |
double | 8字节 |
在讲继承的时候我们就知道父类的私有属性和构造方法并不能被继承,所以
Constructor 也就不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。
重载(overload):发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
重写(override):发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类
方法访问修饰符为 private 则子类就不能重写该方法。
继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
int count;
AbstractStringBuilder() {
}
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
总结:
装箱:将基本类型用它们对应的引用类型包装起来;
拆箱:将包装类型转换为基本数据类型
由于静态方法可以不通过对象进行调用(意味着如果静态方法中有非静态成员的话,这个非静态成员极有可能没有初始化,那这个静态方法就会报错),因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。
Java 程序在执行子类的构造方法之前,如果没有用 super() 来调用父类特定
的构造方法,则会调用父类中“没有参数的构造方法”。
因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 super() 来调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。
刚开始的时候 JavaAPI 所必需的包是 java 开头的包
javax 当时只是扩展API 包(对图形界面的扩展)来说使用。
然而随着时间的推移,javax 逐渐的扩展成为 Java API 的组成部分。
但是,将扩展从 javax 包移动到 java 包将是太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准 API 的一部分。
接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始
接口方法可以有默认实现),
抽象类可以有非抽象的方法
接口中的实例变量默认是 final 类型的,而抽象类中则不一定
一个类可以实现多个接口,但最多只能继承一个抽象类
一个类实现接口的话要实现接口的所有方法,而抽象类不一定
接口不能用 new 实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是
行为的抽象,是一种行为的规范。
从语法形式上看,成员变量是属于类的,而局部变量是在方法中定义的
变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所
修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员
变量和局部变量都能被 final 所修饰;
从变量在内存中的存储方式来看,成员变量是对象的一部分,而对象存
在于堆内存(因此成员变量也在堆中),局部变量存在于栈内存(对象的引用也在栈中)
从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对
象的创建而存在,而局部变量随着方法的调用而自动消失。
成员变量如果没有被赋初值,则会自动以类型的默认值而赋值(一种情
况例外被 final 修饰的成员变量也必须显示地赋值);而局部变量则不
会自动赋值。
new 运算符,new 创建对象实例(对象实例在堆内存中),对象引用指向对象
实例(对象引用存放在栈内存中)。
一个对象可以有 n 个引用指向它。
方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!
返回值的作用:接收出结果,使得它可以用于其他的操作
构造方法的主要作用是对类对象的初始化工作。若一个类没有生命构造方法,将会自动生成一个无参的构造方法,因此是可以执行的。
在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对
象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用
静态方法可以无需创建对象。
静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量
和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无
此限制.
对象的相等,比的是内存中存放的内容是否相等。
而引用相等,比较的是他们指向的内存地址是否相等。
帮助子类做初始化工作。
== :它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同
一个对象。(基本数据类型 == 比较的是值,引用数据类型==比较的是内存地址)
equals: 它的作用也是判断两个对象是否相等。但它一般有两种使用情况
情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个
对象时,等价于通过“==”比较这两个对象.
情况2:类覆盖了 equals() 方法。这时候equals到底是做什么的只有看了代码才知道了。
注意点:
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。
hashCode() 定义在 JDK 的 Object.java 中,这就意味着 Java 中的任何类都包含有 hashCode() 函数。
散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应
的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)。
我们以“HashSet 如何检查重复” 为例子来说明为什么要有 hashCode :
当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断
对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如
果没有相符的 hashcode,HashSet 会假设对象没有重复出现。但是如果发现有
相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相
等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。
如果不同的话,就会重新散列到其他位置。通过hashcode找到地址将equals的执行次数从o(n)降至o(1)甚至不执行equals。大大提高了执行速度。
public class Student {
private String num;
private String name;
public Student(String num, String name) {
this.num = num;
this.name = name;
}
@Override
public int hashCode() {
StringBuilder sb = new StringBuilder();
sb.append(num);
sb.append(name);
char[] charArr = sb.toString().toCharArray();
int hash = 0;
for(char c : charArr) {
hash = hash * 131 + c;
}
return hash;
}
}
按值调用(call by value)表示方法接收的是调用者提供的值
按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。
Java程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。
对象传递是引用传递,原始类型数据传递是值传递
举个反射机制改值的例子:
private static void swap(Integer x, Integer y) {
try {
Field field = Integer.class.getDeclaredField("value");
field.setAccessible(true); //private final int value; Integer中value的声明是private
//为了防止下面x的值的变化而引起temp值的变化,必须为temp开启一个新的内存空间存放值
Integer temp = new Integer(x);
field.set(x, y.intValue()); //x的值发生了改变
field.set(y, temp);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的
过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空
间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工
作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就
是说程序是静态的代码。
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态
的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,
一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行
着,同时,每个进程还占有某些系统资源如 CPU 时间,内存空间,文件,文
件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系
统载入内存中。 线程是进程划分成的更小的运行单位。线程和进程最大的不同
在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有
可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执
行一个以上的程序段。
线程一定处于下述6种状态中的一种,
状态名称 | 说明 |
---|---|
new | 初始状态,线程被构建,但是还没有调用start()方法 |
runnable | 运行状态,Java线程将操作系统种的就绪和运行两种状态笼统的称作”运行中“ |
blocked | 阻塞状态,表示线程阻塞于锁 |
waiting | 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或者中断) |
time_waiting | 超时等待状态,该状态不同于waiting,它是可以在指定的时间自行返回的 |
terminated | 终止状态,表示当前线程已经执行完毕 |
由图4-1中可以看到,线程创建之后,调用start()方法开始运行。当线程执行wait()方法之后,线程进入等待状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而超时等待状态相当于在等待状态的基础上增加了超时限制,也就是超时时间到达时将会返回到运行状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到阻塞状态。线程在执行Runnable的run()方法之后将会进入到终止状态。
Java将操作系统中的运行和就绪两个状态合并称为运行状态。阻塞状态是线程
阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态,但是阻塞在java.concurrent包中Lock接口的线程状态却是等待状态,因为java.concurrent包中Lock接口对于阻塞的实现均使用了LockSupport类中的相关方法。
final 关键字主要用在三个地方:变量、方法、类
在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable
类 。
Throwable: 有两个重要的子类:Exception (异常) 和 Error (错
误) ,二者都是 Java 异常处理的重要子类,各自都包含大量子类。
Error (错误): 是程序无法处理的错误,表示运行应用程序中较严重问题。大
多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟
机)出现的问题。例如,Java 虚拟机运行错误(Virtual MachineError),当
JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这
些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java 虚拟机运行错误(VirtualMachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java 中,错误通过 Error 的子类描述。
Exception (异常): 是程序本身可以处理的异常。Exception 类有一个重要的
子类 RuntimeException。RuntimeException 异常由 Java 虚拟机抛出。
NullPointerException(要访问的变量没有引用任何对象时,抛出该异常)、
ArithmeticException(算术运算异常,一个整数除以 0 时,抛出该异常)和
ArrayIndexOutOfBoundsException (下标越界异常)。
异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。
public string getMessage():返回异常发生时的详细信息
public string toString():返回异常发生时的简要描述
public string getLocalizedMessage():返回异常对象的本地化信息。使
用 Throwable 的子类覆盖这个方法,可以声称本地化信息。如果子类没
有覆盖该方法,则该方法返回的信息与 getMessage()返回的结果相同
public void printStackTrace():在控制台上打印 Throwable 对象封装的
异常信息
异常处理总结
try 块:用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch
块,则必须跟一个 finally 块。
catch 块:用于处理 try 捕获到的异常。
finally 块:无论是否捕获或处理异常,finally 块里的语句都会被执行。
当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回
之前被执行。
对于不想进行序列化的变量,使用 transient 关键字修饰。
transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;
当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。
transient 只能修饰变量,不能修饰类和方法。
Scanner input = new Scanner(System.in);
String s = input.nextLine();
input.close();
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String s = input.readLine();