杭州专业做网站的公司,淘宝做店招的网站,招平面设计师的招聘信息,4444k面访问升最新网站一个 Java 文件中是否可以存在多个类#xff08;修饰类除外#xff09;#xff1f;
一个 Java 文件中是可以存在多个类的#xff0c;但是一个 Java 文件中只能存在一个 public 所修饰的类#xff0c;而且这个 Java 文件的文件名还必须和 public 所修饰类的类名保持一致修饰类除外
一个 Java 文件中是可以存在多个类的但是一个 Java 文件中只能存在一个 public 所修饰的类而且这个 Java 文件的文件名还必须和 public 所修饰类的类名保持一致一个简单的实例如下。
public class Person{private String name;
}class Student{private Integer age;
}Java 有哪些特点
面向对象具有封装、继承、多态三大特性平台独立Java 通过 Java 虚拟机运行字节码所以无论在哪个平台中一旦进行编译后都可以在其他平台运行。安全可靠支持多线程解释和编译共存安全性健壮性Java 语言的强类型机制、异常处理、垃圾的自动收集等
Java 和 C 的区别
相同点两者均为 OOP面向对象 语言均支持 OOP 的三大特性封装、继承、多态。不同点 Java 不存在指针的概念所以内存更加安全。Java 类是单继承但是接口可以多继承C 的类是多继承。Java 中有自动内存管理机制但是 C 中需要开发者手动释放内存。C/C 中字符串和字符数组最后均有一个额外的 \0 标志来表示结束但 Java 中不存在这一概念。
JRE 和 JDK 有什么不同。
JREJava Runtime Environment即 Java 运行时环境是用来运行已经编译过的 Java 程序所需内容的集合JVM、Java 类库、Java 命令等不能用来开发新程序。JDKJava Development Kit即 Java 开发工具包是功能齐全的 Java SDK包含 JRE 拥有的一切还有编译器和其他工具如果我们想要创建和编译新程序就必须使用到它。 Java 程序编译过程 我们编译的源代码xxx.java经 JDK 中的 javac 命令编译后成为 JVM 能够理解的 Java 字节码xxx.class然后经由 JVM 加载通过解释器 逐行解释执行这就是为什么能经常听见说 Java 是一门编译和解释共存的语言。
其中 JVM 是解释 Java 字节码xxx.class 的虚拟机针对不同系统均有特定实现方便一次编译多次运行即 Java 语言的平台独立性
说一下 Java 中的数据类型
Java 中数据类型主要包括 8 大基本数据类型和引用数据类型两大类。
基本数据类型
数据类型bit字节封装类数据范围默认值byte81Byte − 2 7 2 7 − 1 -2^7 \text{~} 2^7-1 −27 27−10short162Short − 2 15 2 15 − 1 -2^{15} \text{~} 2^{15}-1 −215 215−10char162Character\\u0000~ \\uffff 0 65535 0 \text{~} 65535 0 65535u0000int324Integer − 2 31 2 31 − 1 -2^{31} \text{~} 2^{31}-1 −231 231−10long648Long − 2 63 2 63 − 1 -2^{63} \text{~} 2^{63}-1 −263 263−10Lfloat324Float 3.4 e − 45 1.4 e 38 3.4e^{-45} \text{~} 1.4e^{38} 3.4e−45 1.4e380.0fdouble648Double 4.9 e − 324 1.8 e 308 4.9e^{-324} \text{~} 1.8e^{308} 4.9e−324 1.8e3080.0Dboolean不确定不确定Booleantrue或 falsefalse
注意
boolean 一般用 1 bit 来存储但是具体大小并未规定JVM 在编译期将 boolean 类型转换为 int此时 1 代表 true0 代表 false。此外JVM 还指出 boolean 数组但底层是通过 byte 数组来实现;使用 long 类型时需要在后边加上 L否则将其作为整型解析可能会导致越界浮点数如果没有明确指定 float 还是 double统一按 double 处理char 是用 **单引号 ****‘’** 将内容括起来相当于一个整型值ASCII 值能够参加表达式运算而 String 是用 **双引号 **“” 将内容括起来代表的是一个地址值
引用类型
数据类型默认值数组null类null接口null
封装类
基本数据类型都有其对应的封装类两者之间的赋值通过 自动装箱 和 自动拆箱 来完成
自动装箱将基本数据类型装箱为封装类
// 实际调用 Integer.valueOf(12)
Integer x 12;自动拆箱将封装类拆箱为基本数据类型
Integer x 12;
// 实际调用 x.intValue()
int y x;基本类型与对应封装类的不同 基本类型只能按值传递封装类按引用传递基本类型 会在 栈 中创建效率较高但可能存在内存泄露问题封装类对象会在堆中创建其 引用在栈中创建
缓存池
以 new Integer(123) 和 Integer.valueOf(123) 为例
通过 new 的方式每次都会创建一个新的对象通过 valueOf() 的方式则会优先判断该值是否位于缓存池如果在的话就直接返回缓存池中的内容多次调用指向同一个对象的引用
Integer x new Integer(123);
Integer y new Integer(123);
// false通过 new 的方式每次都会创建一个新对象指向不同对象
System.out.println(x y);
Integer m Integer.valueOf(123);
Integer n Integer.valueOf(123);
// true通过 valueOf 的方式先到缓存池中查找存在时则多次调用也是指向同一对象
System.out.println(m n);数据类型默认缓存池Byte − 2 7 2 7 − 1 -2^7 \text{~} 2^7-1 −27 27−1Character\\u0000~ \\u007FShort − 2 7 2 7 − 1 -2^7 \text{~} 2^7-1 −27 27−1Integer − 2 7 2 7 − 1 -2^7 \text{~} 2^7-1 −27 27−1Booleantrue false
字符串 String
定义
public final class String implements java.io.Serializable, ComparableString, CharSequence {/** The value is used for character storage. */private final char value[];
}上述代码为 Java 8 中 String 的定义其底层实际上使用的是字符char数组而且由于被声明为 final代表着它 不能被继承。而且一旦初始化之后就不能再去引用其他数组这样就保证了 String 的不可变性也因此 String 是线程安全的。
不可变性的优点
用于缓存 **hash** 值
由于 String 的 hash 值被频繁使用它的不可变性使得 hash 值也不可变此时只需要进行一次计算
字符串常量池String Pool的需要
如果一个 String 对象已经被创建过那么就会优先从字符串常量池中获取其引用其不可变性确保了不同引用指向同一 String 对象
安全性
我们经常用 String 作为我们方法的参数其不变性能够保证参数不可变
线程安全
String 的不可变性让它天生 具备线程安全能够在多个线程中方便使用而不用考虑线程安全问题。
String vs StringBuffer vs StringBuffer
主要从三个方面对三者进行对比
可变性线程安全适用场景String不可变安全操作少量的数据StringBuffer可变安全内部使用 synchronized 进行同步多线程操作字符串缓冲区下操作大量数据StringBuilder可变不安全单线程操作字符串缓冲区下操作大量数据性能高于 StringBuffer
字符串常量池String Pool
String Pool 位于 方法区通常保存着所有 字符串字面量literal strings在编译期间就被确定。此外还可以用 String 中的 intern() 方法在运行过程中添加到 String Pool 中。当一个字符串调用 intern() 时如果 String Pool 中已经存在字面量相同的字符串则会返回 String Pool 中的引用如果不存在则向 String Pool 中添加一个新的字符串同时返回新字符串的引用。
String s1 new String(aaa);
String s2 new String(aaa);
// false 两个字符串指向不同对象
System.out.println(s1 s2); String s3 s1.intern();
String s4 s1.intern();
// true常量池中存在字面量相同的字符串直接取出
System.out.println(s3 s4);在下面的代码中内存分析如下图
String str1 村;
String str2 村;
String str3 new String(村);
String str4 new String(村);// true两个引用指向常量池中的同一对象
System.out.println(str1 str2);
// false两个引用指向堆中不同对象
System.out.println(str3 str4);new String(“xxx”)
使用 new 的方式创建字符串对象会有两种不同的情况
String Pool 中不存在 “xxx”
此时会创建两个字符串对象“xxx” 属于字符串字面量因此在编译期会在 String Pool 中创建一个字符串对象用于指向该字符串的字面量 “xxx”然后 new 会在堆中创建一个字符串对象
String Pool 中存在 “xxx”
此时只需要创建一个字符串对象由于 String Pool 中已经存在指向 “xxx” 的对象所以直接在堆中创建一个字符串对象
基础语法
注释
单行注释
// 这是单行注释
String name 村;多行注释
String name 村;文档注释
String name 村;常见关键字 标识符和关键字
标识符用于给程序、类、对象、变量、方法、接口、自定义数据类型等命名关键字特殊的标识符被 Java 赋予了特殊含义只能有特定用途标识符命名规则可以参考《阿里巴巴开发手册》 标识符由英文字符大小写a - z, A - Z、数字0 - 9、下划线_和美元符号$组成不能以数字开头不能是关键字严格区分大小写包名多个单词组成是所有单词均小写类名和接口大写驼峰命名法变量名和函数名多个单词组成时第一个单词全小写其他单词采用大写驼峰命名法常量名字母全部大写单词之间用下划线_分割
说一下 Java 中的访问控制
Java 主要提供了 3 中访问修饰符 public、protected、private但实际使用过程中一共可以形成 4 种访问权限分别是 public、protected、private、default其中 default 是不加任何修饰符时的访问权限。关于 4 种访问权限的对比如下表✅ 表示可以访问❌ 表示不可访问。
作用域当前类同一 package 的类子类其他 package 的类public✅✅✅✅protected✅✅✅❌default✅✅❌❌private✅❌❌❌
static、final、this、super
static
static 主要有如下 4 中使用场景
修饰成员变量和成员方法被 static 修饰的成员属于类属于静态成员变量存储在 Java 内存中的 方法区不属于单个对象被所有对象共享而且最好通过 类名.静态成员名/静态方法名() 调用静态代码块定义在类中方法外先于非静态代码块之前执行静态代码块 - 非静态代码块 - 构造方法 而且不管执行多少次创建新对象的操作静态代码只执行一次静态内部类static 要修饰类时只有修饰内部类这一种用法。非静态内部类在编译后会隐含保存一个引用用于指向创建它的外部类但是静态内部类不存在。即内部类的创建不用依赖外围类的创建同时内部类也只能使用任意外部类的 static 成员变量和方法静态导包用于导入静态资源import static 用于指定导入某一类中的静态资源然后我们就可以直接使用类中的静态成员变量和方法注意 abstract 方法不能同时是 static 的因为 abstract 方法需要被重写但 static 方法不可以不能从 static 方法内部发出对非静态方法的调用因为静态方法只能访问静态成员而非静态方法的调用需要先创建对象static 不能用于修饰局部变量内部类与静态内部类的区别静态内部类相对外部类是独立存在的在静态内部类中无法直接访问外部类中变量和方法。如果要进行访问则必须 new 一个外部类对象使用该对象来进行访问但对于静态变量和静态方法能够直接调用。而普通的内部类作为外部类的一个成员而存在能够直接访问外部类属性调用外部类方法。
final
修饰类时被修饰的类不能被继承而且类中所有成员方法均被隐式指定为 final 方法修饰方法时表明该方法无法被重写修饰变量时说明该变量是一个常量。若变量为基本数据类型则一旦初始化后不能再改变若变量是引用类型则初始化后不能指向其他对象。
this
用于引用类的当前实例比如我们最常用的构造方法中注意不能用在 static 方法中
public class User{int age;public User(int age){this.age age;}
}其中 this.age 说明访问的是 User 类中的成员变量而后面的 age 则代表传入的形参
super
用于从子类访问父类中的变量和方法注意不能用在 static 方法中。
public class Father{String name;public Father(String name){this.name name;}public Father(){}
}public class Son extends Father{public Son(String name){super();this.name name .jr;}
}continue、break 和 return
关键字说明continue用于循环结构指跳出当前循环进入下一次循环break用于循环结构指跳出整个循环体继续执行循环下面的语句return1. return ;进行同步直接用 return 结束方法执行用于没有返回值函数的方法2. return value; return 一个特定值用于有返回值函数的方法
while 循环与 do 循环
while 循环结构在循环开始前会判断下一个迭代是否应该继续可能一次循环体都不执行。
do……while 会在循环的结果来判断是否继续下一轮迭代至少会执行一次循环体。
final、finally、finalize
final
final 既是一个修饰符也是一个关键字修饰不同对象时表示的意义也不一样。
修饰类 表示该类无法被继承修饰变量若变量是基本数据类型则其数值一旦初始化后就不能再改变若变量是引用类型则在其初始化之后便不能再让其指向另一个对象但其指向的对象的内容是可变的。修饰方法表示方法无法被重写但是允许重载private 方法会隐式指定为 final 方法。
finally
finally 是一个关键字在异常处理时提供 finally 块来执行任何清除操作无论是否有异常被抛出或捕获finally 块均会被执行通常用于释放资源。finally 正常情况下一定会被执行但是在如下两种情况下不会执行 对应的 try 未执行则该 try 块的 finally 块并不会被执行若 try 块中 JVM 关机则 finally 块也不会执行 finally 中如果有 return 语句则会覆盖 try 或 catch 中的 return 语句导致两者无法 return所以建议 finally 中不要存在 return 关键字
finallize
finallize() 是 Object 类的 protected 方法子类能够覆盖该方法以实现资源清理工作
GC 在回收前均会调用该方法但是 finalize() 方法存在如下问题
Java 语言规范不保证 finalize() 方法会被及时执行也不保证他们一定被执行finalize() 方法会带来性能问题因为 JVM 通常在单独的低优先线程中完成 finalize 的执行finalize() 方法中可将待回收对象赋值给 GC Roots 可达的对象引用从而达到对象再生的目的finalize() 方法最多由 GC 执行一次但是可以手动调用对象的 finalize 方法
运算符
算术运算
操作符描述例子加法 - 相加运算符两侧的值A B 等于 30-减法 - 左操作数减去右操作数A – B 等于 -10*乘法 - 相乘操作符两侧的值A * B等于200/除法 - 左操作数除以右操作数B / A等于2取余 - 左操作数除以右操作数的余数B%A等于0自增: 操作数的值增加1B 或 B 等于 21--自减: 操作数的值减少1B-- 或 --B 等于 19
注意 和 -- 可以放在操作数之前也可以放在操作数之后位于操作数之前时先自增/减再赋值位于操作数之后先赋值再自增/减总结起来就是 符号在前就先加/减符号在后就后加/减。
关系运算符
运算符描述例子检查如果两个操作数的值是否相等如果相等则条件为真。A B为假。!检查如果两个操作数的值是否相等如果值不相等则条件为真。(A ! B) 为真。检查左操作数的值是否大于右操作数的值如果是那么条件为真。A B为假。检查左操作数的值是否小于右操作数的值如果是那么条件为真。A B为真。检查左操作数的值是否大于或等于右操作数的值如果是那么条件为真。A B为假。检查左操作数的值是否小于或等于右操作数的值如果是那么条件为真。A B为真。
位运算符
操作符描述例子如果相对应位都是 1则结果为 1否则为 0 ( A B ) (A\text{}B) (AB)得到 12即 0000 1100如果相对应位都是 0则结果为 0否则为 1^如果相对应位值相同则结果为 0否则为1 ( A B ) (A ^ B) (AB)得到 49即 0011 0001〜按位取反运算符翻转操作数的每一位即 0 变成 11 变成 0。 ( A ) ( \text{~} A) ( A) 得到 -61即1100 0011按位左移运算符。左操作数按位左移右操作数指定的位数。 A 2 A 2 A2 得到 240即 1111 0000按位右移运算符。左操作数按位右移右操作数指定的位数。 A 2 A 2 A2 得到 15 即 1111 1111 1111按位右移补零操作符。左操作数的值按右操作数指定的位数右移移动得到的空位以零填充。 A 2 A 2 A2 得到 15 即 00001111 0000 1111 00001111
逻辑运算符
操作符描述例子称为逻辑与运算符。当且仅当两个操作数都为真条件才为真。A B称为逻辑或操作符。如果任何两个操作数任何一个为真条件为真。!称为逻辑非运算符。用来反转操作数的逻辑状态。如果条件为true则逻辑非运算符将得到false。!(A B)为真。
赋值运算符
操作符描述例子简单的赋值运算符将右操作数的值赋给左侧操作数C A B将把A B得到的值赋给C加和赋值操作符它把左操作数和右操作数相加赋值给左操作数C A等价于C C A-减和赋值操作符它把左操作数和右操作数相减赋值给左操作数C - A等价于C C - A*乘和赋值操作符它把左操作数和右操作数相乘赋值给左操作数C _ A等价于C C _ A/除和赋值操作符它把左操作数和右操作数相除赋值给左操作数C / AC 与 A 同类型时等价于 C C / A取模和赋值操作符它把左操作数和右操作数取模后赋值给左操作数C A等价于C CA 左移位赋值运算符C 2等价于C C 2右移位赋值运算符C 2等价于C C 2按位与赋值运算符C 2等价于C C2^按位异或赋值操作符C ^ 2等价于C C ^ 2#124;按位或赋值操作符C | 2等价于C C | 2
条件运算符? :
也叫作三元运算符共有 3 个操作数且需要判断布尔表达式的值
variable x (expression) ? value if true : value if falseinstanceof
用于操作对象实例检查该对象是否是一个特定类型类类型或接口类型
( Object reference variable ) instanceof (class/interface type)equals() 和 基本数据类型用 比较的是值用于引用数据类型时判断两个对象的内存地址是否相等即两对象是否是同一个对象 本质来讲由于 Java 中只有值传递所以不管是基本数据类型还是引用数据类型比较的其实都是值只不过引用类型变量存的值是对象的地址 equals()
作用也是判断两个对象是否相等但是 不能用于基本数据类型变量的比较。存在于 Object() 类中所以所有类都具有 equals() 方法存在两种使用情况
类未覆盖 **equals()** 方法此时通过 equals() 比较该类的两个对象时等价于 比较这两个对象默认使用 Object 类中的 equals() 方法类覆盖了 **equals()** 方法一旦覆盖了该方法则用来比较两个对象的内容是否相等如我们常用的 String、BitSet、Data、File 就覆盖了 equals() 方法
方法
方法的类型
无参无返回值无参有返回值有参无返回值有参有返回值
重载和重写
重载Overload
重载就是同样方法能够根据输入的不同做出不同的处理。重载发生在 编译期而且在同一个类中方法名必须相同参数类型、参数个数、参数顺序不同返回值和访问修饰符可以不同。 总的而言重载就是同一类中多个同名方法根据不同传参来执行不同的逻辑处理。
重写Override
重写是当子类继承自父类的相同方法输入数据一样但最终响应不同于父类。重写发生在 运行期是子类对父类允许访问的方法的实现逻辑进行改写。重写方法的方法名、参数列表以及返回值必须相同抛出的异常范围不超出父类访问修饰符的范围也不能小于父类。此外若父类方法别 private/final/static 修饰则子类无法重写父类方法但 static 修饰的方法能被再次声明。构造方法是个特例不能被重写。总结起来就是重写即子类对父类方法的改造外部样子不能改变但能够改变内部逻辑。
重载 vs 重写
不同点重载重写参数列表必须不同必须相同返回类型可不同必须相同访问修饰符可不同不能比父类更严格发生范围同一类中父子类异常范围可修改可以减少或删除不能抛新异常或范围更广的异常发生阶段编译期运行期
深/浅拷贝
浅拷贝
浅拷贝是 按位拷贝对象会创建一个新对象该对象具有原始对象属性值的精确拷贝。 若属性是基本类型则拷贝的是基本类型的值若属性是引用类型内存地址则拷贝的是内存地址。因此一旦其中任一对象改变了该引用类型属性均会影响到对方 深拷贝
深拷贝会 拷贝所有属性同时拷贝属性指向的动态分配的内存。当对象和它引用的对象一起拷贝是即发生深拷贝相比于浅拷贝深拷贝速度较慢同时花销更大。 总结
浅拷贝后改变其中任一份值都会引起另一份值的改变而深拷贝后改变其中任何一份值均不会对另一份值造成影响
值传递
推荐阅读https://juejin.im/post/5bce68226fb9a05ce46a0476
形参和实参
形参方法被调用时需要传递进来的参数如 func(String name) 中的 name 就是一个形参只有在 func 被调用时 name 才被分配内存空间当方法执行完后name 将自动销毁释放空间实参方法调用时传入的实际值在方法调用前就已经被初始化且在方法调用时被传入
public static void func(String name){System.out.println(name);
}public static void main(String[] args) {//实参String name 村;func(name);
}值传递和引用传递
值传递
方法被调用时实参通过形参将其内容副本传入方法内部此时形参接收的内容实际上是实参的一个拷贝因此在方法内对形参的任何操作均只针对于实参的拷贝不会影响到实参原始值的内容。即 值传递的是实参的一个副本对副本的操作不会影响实参原始值也即无论形参如何变化都不会影响到实参的内容。
public static void valueCrossTest(int age,float weight){System.out.println(传入的ageage);System.out.println(传入的weightweight);age33;weight89.5f;System.out.println(方法内重新赋值后的ageage);System.out.println(方法内重新赋值后的weightweight);
}public static void main(String[] args) {int a25;float w77.5f;valueCrossTest(a,w);// a 25原始值不收影响System.out.println(方法执行后的agea);// w 77.5原始值不收影响System.out.println(方法执行后的weightw)
}引用传递
引用即指向真实内容的地址值在方法调用时实参的地址被传递给相应形参在方法体内形参和实参指向同一个地址内存因此此时操作形参也会影响到实参的真实内容。
但 Java 中并 不存在引用传递因为 无论是基本类型还是引用类型在实参传入形参时均为值传递即传递的都是一个副本而非实参内容本身。
总结
如果是对基本数据类型的数据进行操作由于实参原始内容和副本都是存储实际值并且处于不同栈区因此对形参的操作实参原始内容不受影响。
如果是对引用类型的数据进行操作分两种情况
一种是形参和实参保持指向同一个对象地址则形参的操作会影响实参指向的对象的内容。
public static void PersonCrossTest(Person person){System.out.println(传入的person的name person.getName());person.setName(我是张小龙);System.out.println(方法内重新赋值后的name person.getName());
}另一种是形参被改动指向新的对象地址如重新赋值引用则形参的操作不会影响实参指向的对象的内容。
public static void PersonCrossTest(Person person){System.out.println(传入的person的name person.getName());personnew Person();person.setName(我是张小龙);System.out.println(方法内重新赋值后的name person.getName());
}面向对象
面向对象 vs 面向过程
推荐阅读https://www.zhihu.com/question/27468564/answer/757537214
面向对象Object Oriented
面向过程是一种 对现实世界理解和抽象的方法更容易维护、复用、扩展。最主要的特点就是 继承、封装、多态所以 设计出的系统耦合性较低但比起面向过程性能要低。
面向过程Procedure Oriented
面向过程是一种 以过程为中心 的编程思想以正在发生为主要目标进行编程不同于面向的的是谁受影响。最主要的不同就在于 封装、继承、多态其性能比面向对象更高。
总结
面向对象的方式使得每个类都各司其职最后整合到一起来共同完成一个项目而面向过程则是让一个类中的功能越来越多就像一个全栈工程师能够一个人搞定所有事。
封装、继承、多态
封装
将客观事物封装为抽象的类同时类能把自己的数据和方法只让可信的类或对象进行操作对不可信的类进行信息隐藏。即把属于同一类事物的共性属性与方法归到一个类从而方便使用。
通过 封装实现了 专业分工将能实现特定功能的代码封装为独立实体供我们在需要时调用。此外封装还 隐藏了信息以及实现细节使得我们通过访问权限权限符就能将想要隐藏的信息隐藏起来。
继承
可以使用现有类的所有功能且无需重写现有类来进行功能扩展即个性对共性的属性与方法的接受并加入特性所特有的属性与方法。通过继承的新类叫做 子类/派生类被继承的类叫做 父类/基类/超类具有如下特点
子类拥有父类对象所有属性和方法但父类中的私有属性和方法子类是无法访问的子类可以对父类进行扩展子类可以用自己的方式来实现父类的方法
多态
多态是允许 将父对象设置为和一个或多个其子对象相等的技术赋值后父对象能够根据指向的子类对象的特性以不同方式运作即 父类引用指向子类对象实例有 重载和重写 两种实现方式。具有如下特点
对象类型不可变但引用类型可变对象类型和引用类型之间有继承类/实现接口的关系方法具有多态性但属性不具有若子类重写了父类方法则真正执行的是子类覆盖的方法若子类未覆盖父类方法则调用父类的方法。
成员变量 vs 局部变量 vs 静态变量
不同语法存储位置生命周期初始化值调用方式别名成员变量1、 属于类2、能被访问控制符、static、final等修饰堆与对象共存亡有基本数据类型为对应默认值而对象统一为 null对象调用实例变量局部变量1、属于方法方法中的变量或参数2、不能被访问控制符及 static修饰但可以被 final修饰栈与方法共存亡无必须定义赋值后使用静态变量1、属于类2、被 static修饰被所有类对象共用方法区与类共存亡同成员变量初始化值类名调用推荐、对象调用类变量
构造方法的特点
方法名与类名同名无返回值但不能用 void 关键字声明生成类对象时自动执行无需显式调用
抽象类 接口
接口
接口中所有方法默认是 public而且不能有实现Java 8 之前Java 8 开始可以有默认实现接口中所有变量均为 static、final不能有其他变量一个类可以实现多个接口通过 implements 关键字而且接口自身可以通过 extends 来扩展多个接口接口是对行为的抽象属于行为规范
抽象类
抽象类中既可以有抽象方法也可以有非抽象的方法一个类只能实现一个抽象类抽象方法可以被 public、protected、default 修饰但不能用 private否则不能被重写抽象是对类的抽象是一种模板设计
Object 类中常见方法
方法说明public final native Class? getClass()用于返回当前运行时对象的 Class 对象使用了final 关键字修饰故不允许子类重写public native int hashCode()用于返回对象的哈希码主要使用在哈希表中比如 JDK 中的 HashMappublic boolean equals(Object obj)用于比较 2 个对象的内存地址是否相等String 类对该方法进行了重写用户比较字符串的值是否相等protected native Object clone() throws CloneNotSupportedException用于创建并返回当前对象的一份浅拷贝。一般情况下对于任何对象 x表达式 x.clone() ! x 为truex.clone().getClass() x.getClass() 为 true。Object 本身没有实现 Cloneable 接口所以不重写clone方法并且进行调用的话会发生CloneNotSupportedException 异常public String toString()返回类的名字实例的哈希码的16进制的字符串。建议Object所有的子类都重写这个方法public final native void notify()不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个public final native void notifyAll()不能重写。跟notify一样唯一的区别就是会唤醒在此对象监视器上等待的所有线程而不是一个线程public final native void wait(long timeout) throws InterruptedException不能重写。暂停线程的执行注意sleep方法没有释放锁而wait方法释放了锁 。timeout是等待时间调用该方法后当前线程进入睡眠状态知道如下时间发生
其他线程调用该对象的 notify()/notifyAll() 方法时间间隔到了其他线程调用了 interrupt() 中断该线程 | | public final void wait(long timeout, int nanos) throws InterruptedException | 多了nanos参数这个参数表示额外时间以毫微秒为单位范围是 0-999999。 所以超时的时间还需要加上 nanos 毫秒 | | public final void wait() throws InterruptedException | 跟之前的 2 个 wait 方法一样只不过该方法一直等待没有超时时间这个概念 | | protected void finalize() throws Throwable { } | 实例被垃圾回收器回收的时候触发的操作 |
hashCode equals
推荐阅读https://juejin.im/post/5a4379d4f265da432003874c
equals
重写 equals() 方法的准则
准则说明自反性对任意非空引用值 xx.equals(x)应该返回 true对称性对于任何非空引用值 x和 y当 y.equals(x)返回 true时x.equals(y)也应返回 true传递性对于任何非空引用值x、y和 z如果 x.equals(y)返回 true 并且 y.equals(z)返回 true那么 x.equals(z)也应返回 true一致性对于任何非空引用值 x和 y多次调用 x.equals(y)始终返回 true或始终返回 false 前提是对象上 equals比较中所用的信息没有被修改非空性对于任何非空引用值 xx.equals(null)都应返回 false
hashCode
hashCode 用于返回对象 hash 值主要是为了加快查找的快捷性因为 hashCode() 是 Object 类中的方法所以所有 Java 类均有 hashCode()在 HashTable 和 HashMap 这类的散列结构中均是通过 hashCode() 来查找在散列表中位置通过 hashCode 能够较快的茶道小内存块。
为什么重写 equals() 必须重写 hashCode()
若两个对象相等则 hashCode() 一定也相同因为 equals() 是绝对可靠的两个对象相等则两个对象分别调用 equals() 方法也返回 true两个对象有相同的 hashCode()他们不一定相等因为 hashCode() 不是绝对可靠的如果重写了 equals()但保留 hashCode() 的实现不变则可能出现两者相等但 hashCode 却不一样因此一旦重写了 equals() 方法则必须重写 hashCode()hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode()则该 class 的两个对象无论如何都不会相等即使这两个对象指向相同的数据。
序列化与反序列化
定义
序列化指将对象转换为字节序列的过程反序列化指将字节序列转换为目标对象的过程
需要序列化的场景
当 Java 对象需要在网络上传输或者持久化存储到文件中时我们就需要对象进行序列化
如何实现序列化
要实现序列化只需要让类实现 Serializable 接口即可此时就标注该类对象能够被序列化
针对类中某些数据不想序列化时可以使用 transient 关键字来实现例如
// 通过关键字 transient 修饰表明不参与序列化
transient private String telephone;Java 进阶面试知识点
异常
异常类层次结构 从结构图可以看出所有异常均继承自 Throwable 类它有两个重要的子类Exception 和 Error 各自又包含大量子类。
Exception
程序本身可以处理的异常又可以分为 受检异常 和 非受检异常 受检异常 可以用 try...catch... 语句进行捕获处理而且能从异常中恢复。但 非受检异常 是程序运行时错误会导致程序崩溃而无法恢复。
Error
程序无法处理的错误表示程序运行过程中较严重的问题大多与 coder 所做操作无关而是代码运行时 JVM 出现的问题。此时说明故障发生于虚拟机本身、或者发生在虚拟机试图执行应用时。
Throwable 常用方法
方法说明public String getMessage()返回异常发生时的简要描述public String toString()返回异常发生时的详细信息public String getLocalizeMessage()返回异常对象的本地化信息若子类重写该方法可以生成本地化信息若未重写则返回信息同 getMessage()方法public void printStackTrace()在控制台中打印异常对象封装的异常信息
try-catch-finally 和 try-with-resources
try-catch-finally try 用于捕获异常后接零个或多个 catch没有 catch 则必须加上 finallycatch用于处理 try 捕获到的异常finally无论是否捕获/处理异常finally 块中内容均会执行就算 try 或 catch 中有 return 语句finally 中代码也将在方法返回之前执行 try-with-resources
当我们有必须要关闭的资源时建议优先使用 try-with-resources这样写出的代码更加简短清晰。
两者对比
// try-catch-finally
Scanner scanner null;
try {scanner new Scanner(new File(D:/demo.txt));while (scanner.hasNext()) {System.out.println(scanner.nextLine());}
} catch (FileNotFoundException e) {e.printStackTrace();
} finally {if (scanner ! null) {scanner.close();}
}// try-with-resources
try (Scanner scanner new Scanner(new File(D:/demo.txt))) {while (scanner.hasNext()) {System.out.println(scanner.nextLine());}
} catch (FileNotFoundException e) {e.printStackTrace();
}IO
IO 流的定义
IO 流是一种数据的流从源头流到目的地如文件拷贝操作输入和输出流都包括了。输入流从文件中读取数据存储到进程然后输出流从进程中读取数据写入到目标文件
IO 分类
Java IO 共涉及 40 多个类均从如下 4 个抽象类中派生而来
InputStream所有输入流的基类字节输入流OutputStream所有输出流的基类字节输出流Reader所有输入流的基类字符输入流Writer所有输出流的基类字符输出流 字节流 vs 字符流
推荐阅读https://www.zhihu.com/question/39262026
字节流
1 Byte 8 bit字节流处理的最基本单位为单个字节默认不使用缓冲区而是直接操作磁盘文件常用于处理音频、图片等媒体文件二进制数据
字符流
1 char 2 Byte 16 bit字符流处理的最基本的单元是 Unicode 码元更适合对于操作需要通过 IO 在内存中频繁处理字符串的情况因为字符流具有缓冲区性能更高常用于处理文本数据
有了字节流为什么还要字符流
字节流由 JVM 将字节转换而来但是该过程非常耗时而且一旦编码未知就很容易导致乱码。为了解决这个问题所以提供了一个直接操作字符的接口从而方便我们对字符进行流处理。
BIO、NIO 和 AIO
推荐阅读https://zhuanlan.zhihu.com/p/83597838
BIO (Blocking I/O)
BIO 特点就是 IO 执行的两个阶段用户进程都会阻塞住
最传统的一种 IO 模型在读写过程中会发生阻塞现象。当线程发出 IO 请求后内核查看数据是否就绪若未就绪就等待数据就绪此时用户线程处于阻塞状态用户线程交出 CPU。一旦数据准备就绪内核就将数据拷贝到用户线程并返回结果给用户线程此时用户线程才接触阻塞状态。
同步阻塞 I/O 模式数据的读取写入必须阻塞在一个线程内等待其完成。适合于连接数较小小于单机 1000且固定的框架该方式对服务器资源要求较高并发局限于应用中是 JDK 1.4 之前唯一的选择。该模式让每一个连接专注于自己的 I/O 并且编程模型简单也不用过多考虑系统的过载、限流等问题。
线程池本身就是一个天然的漏斗可以缓冲一些系统处理不了的连接或请求。但是当面对十万甚至百万级连接的时候传统的 BIO 模型是无能为力的因此需要更加高效的模式来处理更高的并发量。
假设一个烧开水的场景有一排水壶在烧开水BIO 的工作模式就是 一个线程对应一个水壶在当前水壶烧开后才去处理下一个水壶。但是实际上线程在等待水壶烧开的时间段什么都没有做。
总结而言一个典型的读操作流程大致为
用户进程调用 recvfrom 系统调用内核此时开始 IO 第一阶段准备数据将数据拷贝到内核缓冲区中等到数据拷贝到操作系统内核缓冲区后进入 IO 第二阶段将数据从内核缓冲区拷贝到用户内存然后内核返回结果用户进程才会解除 **block** 状态重新运行起来
NIO (Non-blocking/New I/O)
NIO 特点用户进程需要不断的主动询问内核数据准备好没有
用户线程不断询问内核数据是否就绪即 NIO 不会交出 CPU而是一直占用 CPU直到数据准备好。
NIO 是一种 同步非阻塞 的 I/O 模型在 Java 1.4 中引入了 NIO 框架对应 java.nio 包提供了 Channel , SelectorBuffer 等抽象适用于连接数目多且连接比较短轻操作的架构比如聊天服务器。NIO 中的 N 可以理解为 Non-blocking不单纯是 New。它支持 面向缓冲 的基于通道 的 I/O 操作方法。
NIO 提供了与传统 BIO 模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样比较简单但是性能和可靠性都不好非阻塞模式正好与之相反。对于低负载、低并发的应用程序可以使用同步阻塞 I/O 来提升开发速率和更好的维护性对于 高负载、高并发的网络应用应使用 NIO 的非阻塞模式来开发。
还拿烧开水场景来说NIO 的做法是让一个线程不断的轮询每个水壶的状态看看是否有水壶的状态发生了改变从而进行下一步的操作。
AIO (Asynchronous I/O)
AIO 中有一个线程不断去轮询多个 Socket 的状态只有当 Socket 真正有读写事件时才真正调用实际的 IO 读写操作。
AIO 也就是 NIO 2在 Java 7 中引入了 NIO 的改进版 NIO 2,它是 异步非阻塞 的 IO 模型适合连接数目多且连接比较长重操作的架构比如相册服务器。异步 IO 是基于事件和回调机制实现的也就是应用操作之后会直接返回不会堵塞在那里当后台处理完成操作系统会通知相应的线程进行后续的操作。
AIO 是异步 IO 的缩写虽然 NIO 在网络操作中提供了非阻塞的方法但是 NIO 的 IO 行为还是同步的。对于 NIO 来说我们的业务线程是在 IO 操作准备好时得到通知接着就由这个线程自行进行 IO 操作IO 操作本身是同步的。
AIO 比 NIO 效率高的原因在于在 NIO 中不断询问 Socket 状态是通过用户线程去实现的但在 AIO 中轮询 Socket 状态是内核来进行的效率比用户线程高。
对应烧开水场景中就是为每个水壶上面装了一个开关水烧开之后水壶会自动通知我水烧开了。
读取大数据量文件如何选择流
选用字节流时选择 BufferedInputStream 和 BufferedOutputStream
选用字符流时选用 BufferedReader 和 BufferedWriter
NIO
NIO 与 IO 的区别
IONIO面向流面向缓冲阻塞 IO非阻塞 IO无选择器少量连接但每次要发送大量数据大量连接但连接每次只发送少量数据
NIO 是为了弥补传统 IO 不足而诞生但也存在如下缺点因为 NIO 是面向缓冲区的操作每次的数据处理均是对缓冲区进行的所以在数据处理之前必须判断缓冲区的数据是否完整或已经读取完毕如果没有则假设数据只读取了一部分对不完整的数据处理无任何意义。
NIO 核心组件
NIO 核心组件可以分为如下三个 channelbufferselector channel
一个 channel 通道代表和某一实体的连接该实体可以是文件、网络套接字等。即 NIO 中的 channel 相当于一个中介用于程序和操作系统底层 I/O 服务进行交互
一般最常用的通道实现有
FileChannel读写文件DatagramChannelUDP 协议网络通信SocketChannel TCP 协议网络通信ServerSocketChannel监听 TCP 连接
buffer
NIO 中的缓冲区不是一个简单的 byte 数组而是封装过的 Buffer 类NIO 提供了 ByteBuffer、CharBuffer、IntBuffer 等他们之间的区别在于读写缓冲区时的单位长度不一样
buffer 有如下基本操作来进行读写数据
将数据写入 buffer调用 buffer.flip()将数据从 buffer 中读取数据调用 buffer.clean 或 buffer.compact()
selector
选择器是一个特殊组件用于采集各个通道的状态。现将通道注册到选择器并设置好关心的时间然后就可以通过调用 select() 方法等待事件发生即可
集合
Collections 工具类
java.util.Collections 工具类提供了许多常用方法而且都是静态static的可以分为如下几类
排序主要针对 List 接口
方法说明void reverse(List list)反转指定 List集合中元素的顺序void shuffle(List list)对 List中的元素进行随机排序洗牌void sort(List list)对 List里的元素根据自然升序排序void sort(List list, Comparator c)自定义比较器进行排序void swap(List list, int i, int j)将指定 List集合中 i处元素和 j处元素进行交换void rotate(List list, int distance)将所有元素向右移位指定长度如果 distance等于 size那么结果不变
查找、替换主要针对 Collection 接口
方法说明int binarySearch(List list, Object key)使用二分搜索法以获得指定对象在 List中的索引前提是集合有序int max(Collection coll)返回最大元素int max(Collection coll, Comparator c)根据自定义比较器返回最大元素int min(Collection coll)返回最小元素int min(Collection coll, Comparator c)根据自定义比较器返回最小元素void fill(List list, Object obj)使用指定对象填充 Listint frequency(Collection c, Object o)返回指定集合中指定对象出现的次数int indexOfSubList(List list, List target)统计 target在 list中第一次出现的索引找不到则返回 -1boolean replaceAll(List list, Object oldVal, Object newVal)替换
同步控制
方法说明synchronizedCollection(CollectionT c)返回指定 collection支持的同步线程安全的collectionsynchronizedList(ListT list)返回指定列表支持的同步线程安全的ListsynchronizedMap(MapK,V m)返回由指定映射支持的同步线程安全的MapsynchronizedSet(SetT s)返回指定 set支持的同步线程安全的set
设置不可变集合
方法说明emptyXxx()返回一个空的不可变的集合对象此处的集合可以是 List、Set、 MapsingletonXxx()返回一个只包含指定对象只有一个或一个元素的不可变集合对象此处的集合可以是 List、Set、 MapunmodifiableXxx()返回指定集合对象的不可变视图此处的集合既可以是List、Set、 Map
Arrays 工具类
方法说明sort()排序binarySearch二分查找equals()比较fill()填充asList()转换为列表toString()转换为字符串copyOf()复制
sort()
// 数字排序
int[] a {9, 8, 7, 2, 3, 4, 1, 0, 6, 5};
Arrays.sort(a);
for (int i 0; i a.length; i) {System.out.print(a[i] \t);
}
System.out.println();// 字符串排序
String[] a1 {a, A, b, B};
Arrays.sort(a1);
for (int i 0; i a1.length; i) {System.out.print(a1[i] \t);
}
System.out.println();// 字符串反向排序
String[] a2 {c, C, d, D};
Arrays.sort(a2, Collections.reverseOrder());
for (int i 0; i a2.length; i) {System.out.print(a2[i] \t);
}
System.out.println();// 数字反向排序此时要使用封装类
Integer[] a4 {9, 8, 7, 2, 3, 4, 1, 0, 6, 5};
Arrays.sort(a4, Collections.reverseOrder());
for (int i 0; i a.length; i) {System.out.print(a4[i] \t);
}
System.out.println();// 区间排序
int[] a5 {9, 8, 7, 2, 3, 4, 1, 0, 6, 5};
Arrays.sort(a5, 3, 7);
for (int i 0; i a5.length; i) {System.out.print(a5[i] );
}
System.out.println();bianrySearch()
int[] b new int[]{4, 43, 12, 312, 87, 21};
System.out.println(原数组为);
for (int dim1 : b) {System.out.print( dim1 );
}
// 排序
Arrays.sort(b);
System.out.println(\n排序后为);
for (int x : b) {System.out.print(x );
}
System.out.println();
int index Arrays.binarySearch(b, 312);
System.out.println(关键字2的返回值为 index);equals()
String[] str1{hello,world};
String[] str2{hello,world};
System.out.println(Arrays.equals(e, f): Arrays.equals(str1, str2));fill()
Integer[] a new Integer[10];
// 全部填充
Arrays.fill(a, 12);
System.out.println(当前数组容器Arrays.toString(a));// 区间填充
// 填充的开始位
Integer startIndex 1;
// 填充的结束位
Integer endIndex 3;
Arrays.fill(a, startIndex, endIndex, 8);
System.out.println(当前数组容器Arrays.toString(a));asList()
Integer[] array1 {5, 6, 9, 3, 2, 4};
List list Arrays.asList(array1);
for (int i 0; i list.size(); i) {System.out.print(\t list.get(i));
}toString()
String[] array02 {悟空, 八戒, 唐僧};
System.out.println(array02);
System.out.println(Arrays.toString(array02));copyOf()
// array 和 array02 互不相干int[] array {5, 6, 9, 3, 2, 4};
System.out.println(array1的数组长度: array.length);
// 根据实际情况扩容
int[] array2 Arrays.copyOf(array, array.length 1);
System.out.println(array2的数组长度: array2.length);array2[0] 100;
System.out.println(array1: Arrays.toString(array));
System.out.println(array2: Arrays.toString(array2));有关集合的更多内容可以参看 Java 容器知识点总结。
泛型
推荐阅读https://juejin.im/post/5b614848e51d45355d51f792
什么是泛型使用泛型的好处
泛型是 JDK 5 后引入的新特性提供了 编译期的类型安全检测机制确保将正确类型的对象放入集合避免了运行时报 ClassCastException。其本质是 参数化类型即所操作的数据类型被指定为一个参数。
注意虽然编译期在编译过程中移除了参数的类型信息但会保证类或方法内部参数类型的一致性
泛型的工作机制
类型擦除指 Java 编译器生成的字节码不包含泛型信息
泛型是通过 类型擦除 来实现编译期在 编译时擦除所有类型相关信息因此在运行时不存在任何类型相关的信息。我们无法在运行时访问到类型参数因为编译器已经把泛型类型转换成了原始类型。
泛型的三种使用方式
泛型类通过传入不同类型的数据可以存储相应类型的数据
// 一个泛型类声明如下在实例化时必须指定 T 的具体类型
public class GenericT{ private T key;public Generic(T key) { this.key key;}public void setKey(T key){this.key key;}public T getKey(){ return key;}
}泛型接口泛型接口未传入泛型实参时与泛型类的定义相同在声明类的时候需将泛型的声明也一起加到类中若泛型接口传入类型参数时实现该泛型接口的实现类则所有使用泛型的地方都要替换成传入的实参类型
// 一个泛型接口声明如下要实现该接口可以不指定具体类型也可以指定具体类型
public interface GeneratorT {public T method();
}// 不指定具体类型
public class GeneratorImplT implements GeneratorT{Overridepublic T method(){...}
}// 指定具体类型
public class GeneratorImplT implements GeneratorString{Overridepublic String method(){...}
}泛型方法可以存在泛型类中也可以存在普通类中如果用泛型方法能够解决的问题就尽量使用泛型方法。
// 一个具体的泛型方法声明如下使用该方法时我们可以传入不同类型的参数提高复用率
public static E void printArray( E[] inputArray )
{ for ( E element : inputArray ){ System.out.printf( %s , element );}System.out.println();
}泛型通配符
Ttype表示具体的一个 Java 类型K Vkey value代表 Java 键值对Eelement代表 Element?表示不确定的 Java 类型
限定通配符和非限定通配符
限定通配符 对类型进行限制分为两种? extends T 和 ? super T.? extends T 通过确保类型必须为 T 的子类来设定类型的上界而 ? super T 通过确保类型必须是 T 的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化否则将导致编译错误。
非限定通配符?可以用任意类型来替代。
可以把 List 传递给一个接受 List
不可以因为 ListString 中只能用来存储 String而 ListObject 中却能存储任意类型的对象String、Integer 等 。
Array 中是否可以使用泛型
不可以但是用 List 来代替 Array因为 List 可以提供编译期的类型安全保证但 Array 不能。
多线程
线程、进程、程序
推荐阅读https://juejin.im/post/5c932660f265da612524ad6d
程序
程序是指令和数据的有序集合其本身没有任何运行的含义是一个静态的概念。
线程
有时被称为轻量进程(Lightweight ProcessLWP是程序执行流的最小单元是被系统独立调度和分派的基本单位。线程是比进程更小的执行单位一个进程在执行过程中可能会产生多个线程但 不同于进程是同类的多个线程共享同一块内存空间和一组系统资源。
进程
进程是据有独立功能的程序在某个数据集合上的一次运行活动也是操作系统进行资源分配和保护的基本单位因此是动态的。
从 原理 角度上看进程是支持程序执行的一种系统机制对 CPU 上运行程序的活动规律进行抽象。
从 实现 角度看进程是一种数据结构用来准确的刻画运行程序的状态和系统动态变化状况。
系统运行一个程序即是一个进程从创建运行到消亡的过程。简单来说一个进程就是一个执行中的程序它在计算机中一个指令接着一个指令地执行着同时每个进程还占有某些系统资源如 CPU 时间内存空间文件输入输出设备的使用权等等。
换句话说当程序在执行时将会被操作系统载入内存中。 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的而各线程则不一定因为同一进程中的线程极有可能会相互影响。
从另一角度来说进程属于操作系统的范畴主要是同一段时间内可以同时执行一个以上的程序而线程则是在同一程序内几乎同时执行一个以上的程序段。
线程基本状态
状态说明NEW初始状态创建线程但还未调用 start()方法RUNNABLE运行状态“就绪” 和 “运行” 两种状态统称 “运行中”BLOCKED阻塞状态表示线程阻塞于锁WAITING等待状态线程进行等待状态进入该状态表示当前线程需要等待其他线程做出通知或中断TIME_WAITING超时等待状态不同于 WAITING经过指定时间后可以自行返回TERMINATED终止状态表示线程执行完毕 上图源自《Java 并发编程艺术》可以看出一个线程的状态变迁过程
线程创建后处于 NEW 状态然后调用 start() 方法后开始运行此时处于 READY 状态接着一旦可运行状态的线程获得了 CPU 时间片之后就处于 RUNNING 状态。如果线程执行了 wait() 方法则进入 WAITING 状态。进入 WAITING 状态的线程池需要依靠其他线程的通知后才能返回 RUNNING 状态。而 TIME_WAITING 状态相当于在 WAITING 状态的基础上增加了超时限制当超时后线程将自动返回 RUNNABLE 状态。当线程调用同步方法后如果没有获取到锁线程将进入 BLOCKED 状态。同时如果线程执行了 RUNNABLE 的 run() 方法线程将进入 TERMINATED 状态。
使用多线程的三种方式
继承 Thread 类
需要实现 run() 方法其底层也实现了 Runnable 接口。当调用 start() 方法启动一个线程时虚拟机会将该线程放入就绪队列中等待被调度当一个线程被调度时会执行该线程的 run()方法。
public class MyThread extends Thread{Overridepublic void run(){super.run();System.out.println(MyThread);}
}public class Main{public static void main(String[] args){MyThread myThread new MyThread();myThread.start();System.out.println(运行结束);}
}实现 Runnable 接口
使用 Runnable 实例再创建一个 Thread 实例然后调用 Thread 实例中的 start() 方法来启动线程。
public class MyRunnable implements Runnable {Overridepublic void run() {System.out.println(MyRunnable);}
}public class Run {public static void main(String[] args) {Runnable runnablenew MyRunnable();Thread threadnew Thread(runnable);thread.start();System.out.println(运行结束);}}实现 Callable 接口
相比于 RunnableCallable 可以有返回值结果通过 FutureTask 进行封装
public class MyCallable implements CallableInteger {public Integer call() {return 123;}
}public static void main(String[] args) throws ExecutionException, InterruptedException {MyCallable myCallable new MyCallable();FutureTaskInteger futureTask new FutureTask(myCallable);Thread thread new Thread(futureTask);thread.start();System.out.println(futureTask.get());System.out.println(运行结束);}线程优先级
每个线程都有各自优先级默认情况下均为 Thread.NORM_PRIORITY(5)。线程优先级能在程序中表明该线程的重要性若许多线程均处于就绪状态系统就会根据优先级来决定哪一个线程优先进入运行状态。线程优先级均在 Thread.MIN_PRIORITY(1) 到 Thread.MAX_PRIORITY(10) 之间数字越大优先级越高线程优先级有以下两个特点
继承性如 A 线程启动 B 线程则 B 线程的优先级和 A 线程一样随机性线程优先级高的不一定每次都先执行完只能说是优先开始执行
反射
推荐阅读https://zhuanlan.zhihu.com/p/80519709
反射的定义及作用
反射Reflection允许运行中的 Java 程序获取自身信息并且可以操作类或对象的内部属性。核心 是 JVM 在运行时才动态加载类或调用方法/访问属性无需事先知道运行对象是谁主要提供如下功能
运行时 判断任意一个对象所属类运行时 构造任意一个类的对象运行时 判断任意一个类所具有的成员变量和方法运行时 调用任意一个对象的方法生成动态代理
Class 和 java.lang.reflect 共同对反射提供了支持java.lang.reflect 类库中主要包含了如下三个类
Field用 get() 和 set() 方法来读取和修改 Field 对象相关联的成员属性Method利用 invoke() 方法调用与 Method 对象相关联的方法Constructor利用 Construcctor 中的 newInstance() 创建新的对象
反射的优缺点
静态和动态编译
静态编译在编译时确定类型绑定对象动态编译在运行时确定类型绑定对象
两者区别在于动态编译能最大化支持多态降低类的耦合性。
优点
运行时判断类动态加载类提高了代码的灵活度最大化支持多态降低类的耦合性
缺点
性能开销反射相当于一系列解释操作告知 JVM 要做的事比直接的代码要慢安全限制反射要求程序必须在一个无安全限制的环境下运行如果一个程序必须要在安全的环境中运行那么就无法应用反射了内部暴露我们虽然可以动态操作改变类的属性但同时也暴露了类的内部
反射的基本运用 如何利用反射创建对象
通过 Class.forName() 获取一个对象
# 前提是已知类的全路径名
Class class2 Class.forName(全限名);使用 .class 属性
# 前提是已知要操作的类
Class class2 User.class;使用类对象的 getClass() 方法
User user new User();
Class class3 user.getClass();反射中的一些注意事项
反射会额外消耗一定系统资源因此如果不需要动态创建一个对象那么就尽量别用反射反射调用方法时可以忽略权限检查因此可能会破坏封装性而导致安全问题
注解
推荐阅读https://www.cnblogs.com/acm-bingzi/p/javaAnnotation.html
什么是注解
Java 注解是附加在代码中的一些元信息从 Java 5 开始引入的特性用于一些工具在编译、运行时进行解析和使用起着 **说明、配置的功能。**不会也不能影响代码实际逻辑只起辅助性作用。
注解的用途
生成文档比如 param、return 等用于给方法生成文档跟踪代码依赖性实现替代配置文件功能在编译时进行格式检查如 Override 放在方法前表明该方法要覆盖父类方法
注解的原理
注解实质上是继承了 Annotation 的特殊接口具体实现类是 Java 运行时生成的动态代理类。当我们通过反射机制获取注解的时候返回的是 Java 运行时所生成的动态代理对象。通过该代理对象来调用自定义注解方法的方法最终会调用 AnnotationInvocationHandler 的 invoke() 方法。方法从 memberValues 中索引出对应值memberValues 是一个 Map来源于 Java 常量池。
元注解
java.lang.annotation 中提供了四种元注解用于注解其他注解一般用于我们自定义注解时。
元注解说明Retention定义注解声明周期
SOURCE 编译阶段CLASS 类加载时RUNTIME 始终不丢弃 | | Documented | 是否将该注解信息加入 Java 文档 | | Target | 定义注解作用域CONSTRUCTORFIELDLOCAL_VARIABLEMETHODPACKAGEPARAMETERTYPE | | Inherited | 定义注解和子类的关系 |
自定义注解的规则
注解应该定义为 interface参数成员只能用 public 或 default 两个访问控制符参数成员只能有 8 大基本数据类型和 String、Enum、Class、annotation 等数据类型及其数组获取类方法或字段的注解信息必须通过反射技术来获取 Annotation 对象注解中可以定义成员