java 学习笔记
本文最后更新于:2022年10月20日 晚上
持续更新。。。
JDK:Java Development Toolkit,JDK包括JRE(Java Runtime Environment)
Java中只有两种类型:基本类型和引用类型。引用类型引用对象(reference to object),而基本类型直接包含值(directly contain value)。所有类的=都是引用,所以会互相影响。
Java中字符串是不可变的,都是通过引用去操作对象实例
数组为定长,不能增减
Java是面向对象的语言,Java的类把方法与数据连接在一起,构成了自包含式的处理单元。为了将基本类型也能当作对象去处理,Java为每个基本类型都提供了包装类,从而当作对象处理。
equal的默认方法是使用“==”运算符去比较两个对象的引用地址,而不是去比较内容。如果想要对于自定义的类也去使用equal,那么需要重写该方法,从而比较内容。
向上转型,即子类对象赋值给引用父类的变量。背后思想在于“平行四边形也是一种四边形”,所以很自然地可以将平行四边形看作是一种四边形。这是从具体类到抽象类的转换,所以总是安全的。这也是多态机制的基本思想,也是工厂模式的基础。而在向下转型时,是将具体类赋值给一个引用抽象类的变量,会出问题,我们必须通过显式类型转换,告诉编译器,这个抽象类也是一个具体类,才不会出错(感觉就像是把父类没有的成员变量与方法声明了一下)
Java 的关键字都是小写,比如instanceof,能够检测一个对象到底是不是一个类的实例。
一切都是对象,为了得到某个对象,我们需要一个 key,通常这是reference。
增强型for循环,只能读,不能改,因为每次得到的元素都是一份copy
可预测型字符串,推荐用string builder
signature = function name + params
Java一直都是pass by value
模糊参数,用int…,本质是一个array
在多态中,继承抽象类的所有子类需要重写父类的抽象方法。但是同样会有冗余代码。比如某个子类可能不需要这个抽象方法,但是又不得不重写。如果将不需要的分出一个抽象类,又会出现多继承。于是有了接口的概念。哪个类需要这个方法,哪个类就去实现这个接口。同时注意在接口中定义的任何字段默认都是static和final的。
向上转型为抽象接口也可,不仅是抽象类
Java不允许多重继承,但是使用接口就可以实现多重继承
被声明为final的对象引用只能指向唯一 一个对象,不能指向其他的对象。但由于一个对象本身的值是可变的,因此真正不可变,可以用
static final
,通过static,在内存中开辟恒定内存,保持不变。Java中的全局常量,通常都是
public static final
定义为final的方法是不能被重写的。
内部类的实例必须要绑定在外部类的实例(注意不是外部类)上,内部类可以用外部类的东西,外部类不能用内部类的东西。
由于Java是面向对象的语言,所以在Java中异常也是以类的实例的形式出现的。
try结构发生异常后,直接执行catch的语句与之后的,try中异常语句后的不再执行。
在抛出异常的方法中处理异常,可以直接使用try-catch结构,而在方法的声明处写出throws结构,并给出要抛出的异常,是要把异常抛给方法调用者。然后在方法的调用处,因为可能会有异常,还是要有一个try-catch
throws是在方法声明处,可声明多个,可以一层一层向上抛出,但是始终要有处理的;
throw是在方法体内,执行到throw立刻停止,如果方法体内抛异常,则需要在声明里写出throws,之后再调用者处理。
所以写了throw就必须要写throws,但是写了throws可以不写throw。
一个try可以跟多个catch,对应不同的异常。
使用List集合时候通常声明为List类型,然后通过不同的实现类来实例化该集合。
Java反射可以在程序中访问已经装载到JVM中的Java对象的描述
Methods called from constructors should generally be declared final. 如果子类有重载A,并且A在父类构造函数中被调用,那么会调用子类的重写版函数,但是子类在此时尚未被实例化。所以在构造函数中调用的,需要被定义成final(推荐),是为了子类不会重写,将来调用的时候出现多态。
If a subclass defines a static method with the same signature as a static method in the superclass, then the method in the subclass hides the one in the superclass.
The distinction between hiding a static method and overriding an instance method has important implications:
- The version of the overridden instance method that gets invoked is the one in the subclass.
- The version of the hidden static method that gets invoked depends on whether it is invoked from the superclass or the subclass.
Methods called from constructors should generally be declared final.(引入如果调用一个普通函数,然后这个函数在子类中被重写了,那么子类在实例化时,由于多态,在父类中就会调用子类重写的函数,但是此时子类还没有被实例化出来,容易出问题)
抽象类和接口:
- 抽象类:
- want to share code among several closely related classes
- expect that classes extending the abstract class have many common methods or fields, or require access modifiers other than public
- want to declare non-static or non-final fields
- 接口
- unrelated classes would implement your interface
- want to specify the behavior of a particular data type, but not concerned about who implements its behavior
- multiple inheritance
- 抽象类:
C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计(C++11 开始(2011 年的时候),C++就引入了多线程库),而 Java 语言却提供了多线程支持
Java 语言诞生本身就是为简化网络编程设计的,因此 Java 语言不仅支持网络编程而且很方便
JVM 并不是只有一种!只要满足 JVM 规范,每个公司、组织或者个人都可以开发自己的专属 JVM。
Java 中,JVM 可以理解的代码就叫做字节码。不面向任何特定的处理器,只面向虚拟机。通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以, Java 程序运行时相对来说还是高效的(不过,和 C++,Rust,Go 等语言还是有一定差距的)
.java -》javac编译 - 》.class文件 - 》解释器&JIT - 》机器可以理解的代码。
.class->机器码
这一步。在这一步 JVM 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的(也就是所谓的热点代码),所以后面引进了 JIT(just-in-time compilation) 编译器,而 JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言 。Java 语言既具有编译型语言的特征,也具有解释型语言的特征。因为 Java 程序要经过先编译,后解释两个步骤,由 Java 编写的程序需要先经过编译步骤,生成字节码(
.class
文件),这种字节码必须由 Java 解释器来解释执行。代码的注释不是越详细越好。实际上好的代码本身就是注释,我们要尽量规范和美化自己的代码来减少不必要的注释。
静态方法为什么不能调用非静态方法:静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问。而非静态成员属于实例对象,只有在对象实例化之后才存在,需要通过类的实例对象去访问。
可变参数只能作为函数的最后一个参数
包装类型可用于泛型,而基本类型不可以。
几乎所有对象实例都存在于堆中。
Java 基本数据类型的包装类型的大部分都用到了缓存机制来提升性能。
Byte
,Short
,Integer
,Long
这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character
创建了数值在 [0,127] 范围的缓存数据,Boolean
直接返回True
orFalse
。如果超出对应范围仍然会去创建新的对象,缓存的范围区间的大小只是在性能和资源之间的权衡。装箱其实就是调用了 包装类的
valueOf()
方法,拆箱其实就是调用了xxxValue()
方法(比如intValue,floatValue)。性能差异是来自于这个语言的执行机制,而不是这个语言采用的编程范式。
new 创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。
接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系。
接口中的成员变量只能是
public static final
类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值。Java 有两种数据类型,一种是基本类型,比如说 int,另外一种是引用类型,比如说 String。基本类型的变量存储的都是实际的值,而引用类型的变量存储的是对象的引用——对象在内存中的地址。值和引用存储在 stack中,而对象存储在 heap中。基本数据类型的值直接存储在栈中,每当作为参数传递时,都会将原始值(实参)复制一份新的出来,给形参用。形参将会在被调用方法结束时从栈中清除。每当引用类型作为参数传递时,都会创建一个对象引用(实参)的副本(形参),该形参保存的地址和实参一样。
The terms “pass-by-value” and “pass-by-reference” are talking about variables. Pass-by-value means that the value of a variable is passed to a function/method. Pass-by-reference means that a reference to that variable is passed to the function. The latter gives the function a way to change the contents of the variable.
By those definitions, Java is always pass-by-value. Unfortunately, when we deal with variables holding objects we are really dealing with object-handles called references which are passed-by-value as well.
创建
String
类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String
对象。
Stream
Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。
Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次
用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。
而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程。
Write and Read File
When constructing a reader or writer object, the default character encoding of the operating system is used
FileReader reader = new FileReader("MyFile.txt"); FileWriter writer = new FileWriter("YourFile.txt");
1
2
3
4
5
6
- if we want to use a specific charset, use an **InputStreamReader** or **OutputStreamWriter** instead.
- ```java
InputStreamReader reader = new InputStreamReader(
new FileInputStream("MyFile.txt"), "UTF-16");In case we want to use a BufferedReader, just wrap the InputStreamReader inside
1
2
3InputStreamReader reader = new InputStreamReader(
new FileInputStream("MyFile.txt"), "UTF-16");
BufferedReader bufReader = new BufferedReader(reader);