JVM 简介
Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。平时最常用的 JVM 是 HotSpot VM。
JDK 和 JRE
JDK 是 Java Development Kit 缩写,它是功能齐全的 Java SDK。它拥有 JRE 所拥有的一切,还有编译器(javac)和工具(如 javadoc 和 jdb)。它能够创建和编译程序。
JRE 是 Java 运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,java 命令和其他的一些基础构件。但是,它不能用于创建新程序。
字节码
字节码是指后缀名为 .class 的文件,由 .java 文件通过 javac 编译得到
Java 数据类型
字符型
char 在 Java 中占2个字节,因此,在 Java 中,一个汉字也可以用一个字符表示
char ch = '中';
System.out.println(ch);// 中
其余数据类型与 C语言差距不大,不再作过多介绍
变量类型
局部变量
- 局部变量声明在方法、构造方法或者语句块中;
- 局部变量在方法、构造方法、或者语句块被执行的时候创建,当它们执行完成后,变量将会被销毁;
- 访问修饰符不能用于局部变量;
- 局部变量只在声明它的方法、构造方法或者语句块中可见;
- 局部变量是在栈上分配的。
- 局部变量没有默认值,所以局部变量被声明后,必须经过初始化,才可以使用。
实例变量
- 实例变量声明在一个类中,但在方法、构造方法和语句块之外;
- 当一个对象被实例化之后,每个实例变量的值就跟着确定;
- 实例变量在对象创建的时候创建,在对象被销毁的时候销毁;
- 实例变量的值应该至少被一个方法、构造方法或者语句块引用,使得外部能够通过这些方式获取实例变量信息;
- 实例变量可以声明在使用前或者使用后;
- 访问修饰符可以修饰实例变量;
- 实例变量对于类中的方法、构造方法或者语句块是可见的。一般情况下应该把实例变量设为私有。通过使用访问修饰符可以使实例变量对子类可见;
- 实例变量具有默认值。数值型变量的默认值是0,布尔型变量的默认值是false,引用类型变量的默认值是null。变量的值可以在声明时指定,也可以在构造方法中指定;
- 实例变量可以直接通过变量名访问。但在静态方法以及其他类中,就应该使用完全限定名:ObejectReference.VariableName。
类变量(静态变量)
- 类变量也称为静态变量,在类中以 static 关键字声明,但必须在方法之外。
- 无论一个类创建了多少个对象,类只拥有类变量的一份拷贝。
- 静态变量除了被声明为常量外很少使用,静态变量是指声明为 public/private,final 和 static 类型的变量。静态变量初始化后不可改变。
- 静态变量储存在静态存储区。经常被声明为常量,很少单独使用 static 声明变量。
- 静态变量在第一次被访问时创建,在程序结束时销毁。
- 与实例变量具有相似的可见性。但为了对类的使用者可见,大多数静态变量声明为 public 类型。
- 默认值和实例变量相似。数值型变量默认值是 0,布尔型默认值是 false,引用类型默认值是 null。变量的值可以在声明的时候指定,也可以在构造方法中指定。此外,静态变量还可以在静态语句块中初始化。
- 静态变量可以通过:ClassName.VariableName的方式访问。
- 类变量被声明为 public static final 类型时,类变量名称一般建议使用大写字母。如果静态变量不是 public 和 final 类型,其命名方式与实例变量以及局部变量的命名方式一致。
Arrays工具类
boolean equals(int[] a, int[] b); // 判断2个数组是否相等
String toString(int[] a); // 输出数组信息
void fill(int[] a, int val); // 将数组元素替换为val
void sort(int[] a); // 对数组进行排序
int binarySearch(int[] a, int key); // 二分查找求key在数组的下标,未找到返回负数
匿名对象
一般用于作方法参数时使用
可变个数的形参
public class Main {
public void print(String... str) {
for (int i = 0; i < str.length; i++)
System.out.println(str[i]);
}
public void ppp(String[] str) {
for (int i = 0; i < str.length; i++)
System.out.println(str[i]);
}
public void print(String strA, String strB) {
System.out.println(strA + "\n" + strB);
}
public static void main(String[] args) {
Main mmm = new Main();
mmm.print("a", "b", "c");
mmm.ppp(new String[]{"a", "b", "c"});
}
}
print 与 ppp 方法不可以发生重载(即ppp不能改方法名为print),但可以与个数确定且方法名与形参类型都相同的方法发生重载,如第一个print方法与第二个print方法发生重载,当调用print方法时,若参数为2个(与第二个print方法的形参个数相同),则优先调用个数确定的方法(第二个print方法)
可变个数的形参必须声明在形参列表的最后一个位置,因此可变个数的形参在一个方法的形参列表中最多只有一个
所在内存位置
- 局部变量 -> 栈
- 常量 -> 常量池
- 类变量(静态变量) -> 静态存储区
- new 出来的对象 -> 堆区
高内聚低耦合
高内聚:类的内部数据操作细节自己完成,不允许外部干涉
低耦合:仅对外部暴露少量的方法用于使用
this关键字
使用情况:方法形参与类的属性重名,构造器的复用,传参等
this.属性
this.方法
调用构造器:this()
特别地,只能调用一个构造器,而且调用构造器必须写在首行,不能调用自己
让当前对象传参时直接使用this
import关键字
- import 导入某包的结构 import 可以通过 “.*” 的方式导入某包下的所有结构
- java.lang和本包内的结构可以不用import
- 如果想使用不同包下2个重名的类或接口,只能导入其中一个包的类或接口,另外一个必须通过包.类(全类名)的方式来使用
- “.*” 的方式不会导入该包的子包内的结构,本包的子包的结构也必须使用import导入
- import static导入指定类或接口的静态结构(属性或方法)
继承
如果一个类没有显式地声明父类的话,那么它将继承于java.lang.Object类,所以所有类(除java.lang.Object类外)都直接或间接继承于 java.lang.Object类
重写
- 子类重写方法的权限修饰符不小于被重写的方法的权限修饰符
- 子类不能重写父类中权限为private的方法,因为该方法子类不可见,若在子类写了该方法相当于重新创建了一个方法
- 父类被重写的方法的返回值类型是基本数据类型或void型,子类重写的方法返回值类型必须与父类相同
- 父类被重写的方法的返回值类型是A类,则子类重写的方法的返回值类型只能是A类或A类的子类
- 子类重写的方法抛出的异常类型不大于父类被重写方法抛出的异常类型
- 子类和父类中的同名同参数的方法要么声明为非static的(重写),要么都声明为static(不是重写)
super关键字
使用情况:
- 子类与父类有同名的属性,想要调用父类的属性
- 子类重写了父类的方法,想要调用父类的方法
- 可以在子类的构造器中使用 “super(形参列表)” 的方式调用父类的构造器,(为什么要这么做?假如父类的属性是私有属性,不能用 “this.属性” 的方式赋值)
- “super(形参列表)” 必须放在首行
- 在构造器首行,没有显式地调用 “super(形参列表)” 或 “this(形参列表)” ,则默认调用的是父类中的空参构造器( super(); ) 问:若父类没有空参构造器怎么办?答:报错。解决方法:可以给父类一个空参构造器或子类显式地调用 “super(形参列表)” 或 “this(形参列表)”
- 在类的构造器中,至少有一个构造器使用了 “super(形参列表)” 调用父类的构造器
多态
Person person = new Man();
- 多态(对象的多态性):子类的对象赋给父类的引用
- 虚拟方法调用:当使用子父类同名同参数的方法时,使用的是子类重写父类的方法
- 不能再用子类所特有的方法
- 编译期只能调用父类的声明的方法,运行期实际执行的是子类重写父类的方法
- 多态只对方法而言,对于子父类同名的属性,调用的是父类的属性
- 虚拟方法是指父类的方法
向下转型
原因:多态对象想要使用子类所特有的方法
Person p = new Man();
Man m = (Man)p;
但是这种可能导致以下这种情况的出现,进而导致程序运行时报错
Woman = (Woman)p;
因此可以使用 instanceof 关键字判断某对象是否属于某类
Object类
- Object 类没有属性
- Object 类只有一个空参构造器
- Object类中 equals() 方法如下,比较的是2个地址
public boolean equals(Object obj) {
return (this == obj);
}
- 但是String重写了equals方法,所以比较的是2个字符串的值
单元测试
这里我使用的是IDEA,附上IDEA配置单元测试的链接
包装类
基本数据类型与包装类的相互转换
int num = 10;
Integer in = new Integer(num); // 基本数据类型 --> 包装类
int num2 = in.intValue(); // 包装类 --> 基本数据类型
特殊情况:只要传给Boolean包装类的不是true则为false
自动装箱与自动拆箱
// JDK 5.0 新增的新特性
char ch = 'A';
Character cha = ch; // 自动装箱
char ch2 = cha; // 自动拆箱
基本数据类型&包装类&字符串的相互转换
int num1 = 1;
String str1 = num1 + "";
String str2 = String.valueOf(num1); // valueOf方法将int类型转换为String类型
Integer in1 = num1; // 自动装包
String str3 = String.valueOf(in1); // 自动拆包
String str4 = "1234";
Integer in2 = Integer.parseInt(str4); // 调用包装类的parseXxx()方法
IntegerCache方法,将 [-128,127] 内的数进行缓存,若自动装箱时2数在该范围内,则2个Integer类指向的是同一个地址
Integer i1 = 127;
Integer i2 = 127;
System.out.println(i1 == i2); // true
Integer i3 = new Integer(127);
Integer i4 = new Integer(127);
System.out.println(i3 == i4); // false
注意点:三元运算符 “?” 2边的类型会保持一致
Object object = true ? new Integer(1) : new Double(2.0); // 1.0
static关键字
- static 只能修饰属性,方法,代码块,内部类
- static 方法只能调用 static 属性或方法
- static 不能使用 this、super关键字
代码块
-
作用:用来初始化类、对象
-
只能用static修饰
-
静态代码块随着类的加载而执行,而且只执行一次
-
非静态代码块随着对象的创建而执行
-
代码块的执行先于构造器(与位置无关)
static {
}
// 或
{
}
属性赋值顺序
默认初始化 -> 显示初始化 / 代码块中赋值 -> 构造器中初始化 -> 方法体中赋值或 对象.属性赋值
其中若代码块在显式初始化前,则先在代码块中赋值,再显式初始化,反之亦然
final关键字
-
final 修饰类,该类不能被继承
-
final 修饰方法,该方法不能被重写
-
final 修饰变量:变量值不能改变
-
① final 修饰属性:不能默认初始化,不能在方法中赋值
-
② final 修饰局部变量:
-
static final 修饰属性:全局常量
抽象类和抽象方法
抽象类
-
抽象类不能实例化
-
抽象类一定会有构造器,因为子类一定会调用父类的构造器
public abstract class OtherTest {
}
抽象方法
abstract void method();
-
抽象方法只存在于抽象类中
-
只有子类重写完所有父类中的抽象方法后,子类才能实例化
-
若子类没有重写完所有父类中的抽象方法,则该此类必须也为抽象类
使用abstract关键字的注意点
-
abstract 只能修饰类、方法
-
abstract 不能修饰私有方法、静态方法、final 修饰的方法、final 修饰的类
抽象类的匿名子类
需要重写父类的抽象方法
public abstract class OtherTest {
public abstract void method();
public static void main(String[] args) {
OtherTest o = new OtherTest() { // 匿名抽象类的非匿名对象
public void method() {
}
};
}
}
接口
-
JDK1.7之前,只能定义全局常量和抽象方法
-
JDK1.8及以后,还可以定义静态方法和默认方法
-
接口中,全局常量可以省略 public static final 不写
-
若子类没有覆盖完所有接口中的抽象方法,则该此类必须也为抽象类
-
常常称子类覆盖接口或父类中的的抽象方法为实现
-
接口与接口之间可以发生继承,而且一个接口可以继承多个接口
-
接口中的静态方法只能由接口调用
-
接口中的默认方法可以被实现类重写
-
如果子类(实现类)的父类和实现的接口出现了重名的方法,则默认调用父类的方法
-
接口冲突:如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现 类没有重写的情况下,报错
-
当在实现类中想要调用接口的默认方法时,可以通过 接口名.super.方法 的方式调用
interface Fly {
public static final long MAX_SPEED = 7900;
long MIN_SPEED = 1;
public abstract void fly();
void stop();
}
interface Attack {
void attack();
// 接口默认权限public,故此处是省略了public
static void method() {
System.out.println("static method");
}
// 此时default必须写出来
public default void method2() {
}
}
class Plane extends Object implements Fly, Attack {
public void fly() {
System.out.println("plane is flying");
}
public void stop() {
System.out.println("plane is stop");
}
public void attack() {
System.out.println("plane is attack");
}
public void method2() {
Attack.super.method2();
}
}
内部类
-
非静态内部类可以调用外部类的结构,省略了 外部类.this.
-
可以被 static 修饰(注意外部类不能被 static 修饰)
-
可以被四种权限修饰符修饰(外部类只能是默认权限修饰符或 public )
-
在局部内部类的方法中想要调用内部类所在方法中的变量,要求该变量声明为 final (JDK8及以后不需要显式声明,JDK7及以前需要显式声明)
-
注意以下三点
- 如何实例化成员内部类的对象
- 如何在成员内部类中区分调用外部类结构
- 开发中局部内部类的使用:一般是返回一个对象的时候使用
public class OtherTest {
public static void main(String[] args) {
// 创建静态的成员内部类
Person.Dog dog = new Person.Dog();
dog.show();
// 创建非静态的成员内部类
Person person = new Person();
Person.Bird bird = person.new Bird();
}
}
class Person {
String name;
int age;
static class Dog {
String name;
public void show() {
System.out.println("[name=" + name + "]");
}
}
class Bird {
String name;
public void show() {
System.out.println("it's host is " + Person.this.name);
// 这里的age实际上是Person.this.age
System.out.println("[name=" + name + ",age=" + age + "]");
}
}
}
异常
-
在 try 结构中声明的变量,出了 try 结构后就不能被调用了(变量的作用域)
-
try-catch() 结构中的异常类型如果有子父类的关系,则父类不能写在子类前
-
try-catch()处理了异常,后面的代码还会被执行
-
常用异常处理方式:①String getMessage() ②printStackTrace()
-
finally:一定会被执行的代码
-
throws:只要方法满足异常类型就会被抛出,异常后面的代码不再执行
-
throws:子类重写方法抛出的异常类型不大于父类被重写方法抛出的异常类型,故若父类被重写方法没有throws,则子类重写方法也不能有throws
-
throw:手动抛出异常(一般抛出Exception,RuntimeException或自定义异常)
throw new Exception("手动抛出异常");
- 自定义异常类,直接继承已有异常类型(一般是Exception或RuntimeException),然后一般会提供构造器和全局常量(serialVersionUID)