下面是一个最简单的Java应用程序,它只发送一条消息到控制台中。
public class example {
public static void main(String[] args) {
System.out.println("Hello Word");
}
}
要点
三种方式
//这是单行注释
/*
* 这是多行注释
*/
/**
* 这是多行注释,可用来自动生成文档
*/
整型用于表示没有小数部分的数值,允许是负数。Java提供了4种类型:
类型 | 存储空间 | 取值范围 |
---|---|---|
int | 4Byte | − 2 31 到 2 31 − 1 -2^{31} 到2^{31} - 1 −231到231−1 |
short | 2Byte | − 2 16 到 2 16 − 1 -2^{16} 到 2^{16} - 1 −216到216−1 |
long | 8Byte | − 2 64 到 2 64 − 1 -2^{64} 到 2^{64} - 1 −264到264−1 |
byte | 1Byte | − 2 8 到 2 8 − 1 -2^{8} 到 2^{8} - 1 −28到28−1 |
浮点类型用于表示有小数部分的数值。Java提供了2种类型:
类型 | 存储空间 | 取值范围 |
---|---|---|
float | 4Byte | 大约正负3.402 823 47E+38F(有效位数为6~7位) |
double | 8Byte | 大约正负1.797 693 134 862 315 70E+308(有效位数为15位) |
注释:
常量Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY,Double.NaN(以及对应的Float类型常量)分别表示这三个特殊值,但在实际中很少遇到。
特别说明:
不能用
if (x == Double.NaN) //is never true
所有NaN都是不相同的。
可以使用
if (Double.isNaN(x)) //check x is NaN
警告:
浮点数值不适用与无法接受舍入误差的金融计算。如 2.0-1.1 将为0.8999999999 而不是希望的0.9.原因是浮点数值用二进制系统表示,在二进制中无法精确表示分数1/10,应使用BigDecimal类。
char类型原本用于表示单个字符,如今有些Unicode字符可以用一个char值描述,另外一些Unicode字符则需要两个char字符。char类型字面量要用单引号括起来。
转义序列 | 名称 |
---|---|
\b | 退格 |
\t | 制表 |
\n | 换行 |
\r | 回车 |
\" | 双引号 |
\’ | 单引号 |
\\ | 转义 |
**码点:**指与一个编码表中的某个字符对应的代码值。
两个值:true和false。整形与布尔值之间不可相互转换。
先指定变量类型,然后是变量名。
如下:
int a;
变量名必须是一个以字母开头并有字母和数字构成的序列。
注意
Java中的字母和数字范围更大。字母包括“A-Z”、“a-z”、“_”、“$”或在某种语言中表示字母的任何Unicode字符。
**提示:**如果想要知道哪些Unicode字符属于Java中的“字母”,可以使用Character类的isJavaIdentifierStart和isJavaIdentifierPart方法来检查。
**提示:**尽管$是一个合法的Java字符,但不要在你的代码中使用这个字符,它只用在Java编译器或其他工具生成的名字中。
**注释:**从Jav10开始,对于局部变量,如果可以从初始值推断出他的类型,就无须声明变量类型,只需要用关键字var。
在Java中用关键字final指示常量。final表示这个变量只能被赋值一次,不能被修改。
定义:
enum Size{SMALL, MEDIUM, LARGE};
Size s = Size.SMALL;
Size类型的变量只能为枚举中的值。
+ - * / 表示加减乘除。%表示求余数。
Math类的常用函数:
//算术计算
Math.sqrt() : //计算平方根
Math.cbrt() : //计算立方根
Math.pow(a, b) : //计算a的b次方
Math.max( a,b ) : //计算最大值
Math.min( a,b ) : //计算最小值
Math.abs() : //取绝对值
//进位运算
Math.ceil(): //天花板的意思,就是逢余进一
Math.floor() : //地板的意思,就是逢余舍一
Math.rint(): //四舍五入,返回double值。注意.5的时候会取偶数
Math.round(): //四舍五入,float时返回int值,double时返回long值
//随机数
Math.random(): //取得一个[0, 1)范围内的随机数
图中的实线箭头表示无信息丢失的转换。虚线箭头表示可能有精度损失的转换。
当用一个二元运算符连接两个值时(例如a + b ,a为整形,b为浮点型),先要将两个操作数转换为同一种类型然后计算。转换顺序如下:
语法格式:
double x = 9.997;
int a = (int) x;
**警告:**在强制转换时,如果超出了目标类型的表示范围,结果就会截断成一个完全不同的值。如:(byte)300 的实际值是44.
x += 4;
//等价于
x = x + 4;
一般来说,要把运算符放在=左边,如*=,+=,-=等。
注释:
int x=0;
x += 3.5;
会发生强制类型转换,把x设置为(int)(x+3.5)
++,–。
要注意自增或自减出现的位置
int m = 7;
int n = 7;
int a = 2 * ++m; //now, a is 16,m is 8
int b = 2 * n++; //now b is 14,n is 8
建议不要在表达式中使用++,这样的代码容易让人困惑,产生bug。
关系:==、!=、<、>、<=、>=
逻辑:&&-与、||-或。
注释:&&和||是按照“短路”的方法来求值的,如果第一个操作数已经能够确定表达式的值,第二个操作数就不必计算。
Java支持三元运算符 ?:
a>0 ? a : 0;//如果a大于0,则a=a;否则,a=0;
& 按位与
| 按位或
^ 异或
~ 非
注释:&和|应用在布尔值上,结果也是布尔值。不过&和|不采用短路运算。
<< : 左移运算符,num << 1,相当于num乘以2
>> : 右移运算符,num >> 1,相当于num除以2
>>> : 无符号右移,忽略符号位,空位都以0补齐
从概念上讲,Java字符串就是Unicode字符序列。
String s = "Hello";
s.subString(0,2); //He
subString方法的第二个参数是不想复制的第一个位置。s.subString(a,b)子串长度为b-a。
String s1 = "Hel";
String s2 = "lo";
s1 + s2; //"Hell0"
s1.concat(s2); //"hello"
//静态方法join,把对个字符串放在一起,用一个定界符分隔。
String all = String.join("/","s","m","L") //all = "s/m/l";
//在Java11中,提供了repeat方法。
String repeated = "Java".repeat(3); //"JavaJavaJava"
String类对象不可变。
原因:底层是用final修饰的char数组保存。
使用equals方法。
注意一定不要使用==检测字符串是否相等。这只能检测两个字符串是否放在同一位置上。
空串“”是长度为0的字符串。空串是一个Java对象。有自己的串长度和内容。
检测空串:
if(s.equals("")){}
if(s.length() == 0){}
Null串检测:
if(s == null){}
检测不是空串也不是null串:
if(s.length() != 0 && s != null){}
p49~p51
StringBuilder builder = new StringBuilder();
builder.append(s1);
builder.append(s2);
builder.toString();
StringBuilder API:p54~p55
Scanner in = new Scanner(System.in);
Scanner API:
p56~p57。
System.out.printf("hello,%s",name);
每一个以%字符开始的格式说明符都用相应的参数替换。f表示浮点数,s表示字符串,d表示十进制数。
//读文件
Scanner in = new Scanner(Path.of("myfile.txt"),StandardCharsets.UTF_8);
//写文件
PrintWriter out = new PrintWriter("myfile.txt",StandardCharsets.UTF_8);
与其他程序设计语言一样,Java使用条件语句和循环语句确定控制流程。
块是指由若干Java语句组成的语句,并用一对大括号括起来。块确定了变量的作用域。一个块可以嵌套另一个快。
注意:不能在嵌套的两个块中声明同名的变量。
if(){}else{}
while(){}
do{}while()
for(){}
switch(){
case:
}
break;//跳出当前循环
continue;//中断当前循环,跳入下一步骤。
带标签的break语句:
read_data:
while(){
for(){
if(){
break read_data;
}
}
}
执行break,直接跳出while循环。
实现任意精度的整数运算。
实现任意精度的浮点数运算。
https://blog.csdn.net/qq_41202539/article/details/106150338
int[] a;
int[] a = new int[100];
int[] a = {1,2,3,4};
int[] a = {1,2,3,4};
a[0];
创建一个数字数组时,所有元素初始化为0.boolean数组,初始化为false。对象数组初始化为null。
int b[] = {1,0,3}
for(int a : b){
System.out.print(a);
}
还可以利用Arrays.toString()打印数组:
int a = {0,1,2};
System.out.print(Arrays.toString(a));//[0,1,2]
int[] a = {0,1,2,3,4};
int[] b = Arrays.copyOf(a,a.length);
第二个参数是新数组的长度,通常用这个函数实现数组扩容。
int[] a = {0,1,2,3,4};
Arrays.sort(a);
这个方法使用了优先快速排序的方法。
Arrays API p85
类是构造对象的蓝图或模板。由类构造对象的过程称为创建类的实例。
封装就是将数据和行为组合在一个包中,并对对象的使用者隐藏具体的实现方式。对象中的数据称为实例字段,操作数据的过程称为方法。作为一个类的实例,特定对象都有一组特定的实例对象值,这些值得集合就是当前对象的状态。
实现封装的关键在于,决不能让类中的方法直接访问其他类的实例字段。程序只能通过对象的方法与对象数据进行交互。
在Java中所有类都拓展自Object类。在拓展一个已有类是,拓展后的新类会拥有被拓展的类的所有属性和方法。
对象的三个主要特性:
在Java中要使用构造器构造新实例。构造器是一种特殊的方法,用来构造并初始化对象。
对象变量并没有实际包含一个对象,他只是引用一个对象。在Java中,任何对象变量的值都是存储在另一个地方的某个对象的引用。new操作符的返回值也是一个引用。
API p103
更改器方法:更改对象的状态(Setter是一个更改器方法)
访问器方法:只访问对象而不修改对象的方法。(Getter是一个访问器方法)
注意不要编写返回可变对象引用的访问器方法。
如果需要返回一个可变对象的引用,首先应该对他进行克隆。
构造器:
var关键字:
使用null引用:
对于构造器中传入的参数如果为null,在Java9中提供了两种解决办法:
宽容型:
public Employee(String n){
//将null引用替换为unkown
this.name = Object.requireNonNullElse(n,"unkown");
}
严格型:
public Employee(String n){
//抛出空指针异常
this.name = Object.requireNonNull(n,"the name can not be null");
}
**注释:**当构造器不希望接受可有可无的值时,用严格型更加合适。
隐式参数和显示参数
Employee employee = new Employee();
employee.setName(name);
在setName方法中有两个参数,第一个为在方法之前的employee对象为隐式参数,括号中的name为显示参数。
在每一个方法中,关键字this指示隐式参数。
final实例字段
定义为final的字段必须初始化,相当于一个常量。
一个字段定义为static,那么他就是静态字段。他属于类而不属于实例。调用方式为 类名.字段名。
public class Math{
*************
public static final double PI = 3.1419926535897932386;
*************
}
前面提过,由于每个对象都可以修改公共字段,所以最好不要有公共字段。然后,公共常量是没有问题的。因为被声明为final,所以不可被修改。
可以认为静态方法时没有this参数的方法。静态方法只可以访问静态字段,不能访问实例字段。
建议使用类名而不是实例名来调用静态方法。
以下两种情况可以使用静态方法:
在启动程序时还没有任何对象。静态的main方法将执行并构造所需要的对象。
Object API:p120
方法的参数有两种:
用例:
传入基本数据类型
int a = 10;
int b = 20;
public void swap(int x, int y){
int temp;
temp = x;
x = y;
y = temp;
Sys.out.print("x="+x+"y="+y);
}
//调用
swap(a,b);//结果是x=20,y=10,但是a=10,b=20;
public void changeName(Employee e){
e.setName("MyName");
}
Employee employee = new Employee("Luo");
changeName(employee);//此时employee.getName = "MyName"
可以看出Java采用的是按值传递的例子:
Employee e1= new Employee("Luo");
Employee e2= new Employee("Wang");
public void swap(Employee x,Employee y){
Employee temp;
temp = x;
x = y;
y = temp;
}
swap(e1.e2);//此时e1.getName="Luo",e2.getName="Wang"
图中虚线为调用swap方法之前x,y的指向,实现为调用后的指向。
这个例子可以很好的说明Java是按值传递的。
**重载:**多个方法有着相同的名字,不同的参数。Overload
Java允许重载任何方法。因此要完整的描述一个方法,必须制定方法的方法名和参数。这叫方法的签名。
如果在构造器中没有显示的为字段设置初值,就会设置默认值:数值为0,布尔为false,对象引用为null。
**注释:**局部变量必须初始化,这是字段和局部变量的一个重要区别。
使用无参构造器时,对象的状态会设置为合适的默认值。
如果一个类没有编写构造器,系统会为你提供一个无参构造器,并把所有实例字段设置成默认值。
如果类中提供了至少一个构造器,而没有提供无参构造器,那么调用无参构造器就是非法的。
public Employee(String name, double salary){
this.name = name;
this.salary = salary;
}
public Employee(String name, double salary, int id){
//调用了Employee(String name, double salary)构造器
this(name,salary);
this.id = id;
}
使用包的主要原因是为了确定类名的唯一性。
包名一般为因特网域名的倒叙:com.baidu;
使用import导入包。
使用关键字extends定义子类
public class Manger extends Employee{
}
子类拥有超类的所有属性和方法,所以定义子类时,只需要指出子类和超类的不同之处。
假如子类要覆盖父类中的方法,子类中可用super关键字调用父类的同名方法。
public class Manger extends Employee{
private double bonus;
public double getSalary(){
duoble baseSalary = super.getSalary;
return bonus + baseSalary;
}
}
**注释:**子类中可以添加字段、添加方法、覆盖方法,但是决不允许删除任何字段或方法。
public Manger(String name, double Salary, int year){
super(name,Salary, yaer);
bonus = 0;
}
用super关键字调用父类中的构造器,接下来在进行子类属性的初始化。
**注意:**如果子类中没有显示的调用父类的构造器,将自动调用父类的无参构造器。如果父类中没有无参构造器,并且子类中又没有显示的调用父类的其他构造器,Java编译器就会报错。
一个对象变量可以指向多种实际类型的现象称为多态。在运行时能够自动选择适当的方法,称为动态绑定。
Employee e;
e = new Employee();
e = new Manager();
e既可以指向Employee,也可以指向Manager。
Manager boss = new Manager();
Employee[] staff = new Employee[3];
staff[0] = boss;
staff[0]和boss指向同一个对象,但是编译器只把staff[0]看出是Employee对象。这就意味着:
boss.setBonus(5000); //ok
staff[0].setBonus(5000); //error
因为staff[0]是Emloyee对象,employee对象没有setBonus方法。
但是,不能讲超类赋值给子类。
虚拟机会预先为每个类计算一个方法表,其中列出了所有方法的签名和要调用的实际方法。将在方法表中进行搜索。
用final修饰的类不可继承,类中的所有方法也被隐式的修饰词final,而不包括字段。
用final修饰的方法不可重写。
将子类引用赋值给父类是允许的不需要强制类型转换。
Manager boss = new Manager();
Employee e = boss;//ok
将父类引用赋值给子类是允许的需要强制类型转换。
Employee e = new Employee();
if(e instanceof Manager){
Manager boss = (Manager) e;
}
综上:
使用abstarct修饰的类,为抽象类。
抽象类除了包含抽象方法之外,还可以包含字段和具体方法。
public abstract class Person {
private String name;
public Person(String name) {
this.name = name;
}
public abstract String getDescription();
public String getName() {
return name;
}
}
protected声明的字段和方法只允许子类和本包中的类访问。
在Java中只有基本数据类型不是对象。
Object类的默认实现实判断两个对象的引用是否相等。
该方法具有的特性:
编写一个完美的equals方法:
if(this == otherObject) return true;
if(otherObject == null) return false;
if(this.getClass() != otherObject.getClass()) return false;
if(!(otherObject instanceof ClassName)) return false;
ClassName other = (ClassName) otherObject ;
散列码(hash code)是对象导出的一个整型值。
如果像个对象相等,那么他们的hashCode一定相等。反之,不然。
如果重新定义了equals方法,就必须为用户可能插入散列表的对象重新定义hashCode方法。
理由:equals与hashCode的定义必须相容,如果x.equals(y)返回true,那么x.hashCode()和y.hashCode()必须相等。
只要对象与另一个字符串通过 “+” 连接起来,Java编译器就会自动调用toString方法。
强烈建议,给每个对象添加toString方法
ArrayList稍后详细介绍。
详见另一篇博客
public PrintStream printf(String format, Object ... args) {
return format(format, args);
}
这里的 … 省略号是Java代码的一部分,表明可以接受任意数量的对象。
实际上,printf方法接受两个参数,一个格式化字符串,一个Objec[]数组,其中保存所有其他参数(如果调用者提供的是基本数据类型,会进行自动装箱)。
public enum Size {
SMALL("s"),
MEDIUM("m"),
LARGE("l");
private Size(String size) {
this.size = size;
}
public String getSize() {
return size;
}
private String size;
}
如上,枚举定义的是一个类,他只有三个实例,不能构造新对象。
枚举的构造器总是private的,否则会报错。
所有枚举类型都是Enum的子类。
能够分析类能力的程序成为反射。
反射可以用来:
在程序运行期间。Java运行时系统始终为所有对象维护一个运行时类型标识。这个信息会跟踪每个对象所属的类,虚拟机利用运行时类型信息选择要执行的正确方法。保存这些信息的类就是Class类。
虚拟机为每个类型管理一个唯一的Class对象,因此可以用 == 实现两个对象的比较。
API:
获取一个类对应的Class类的方法
返回Class类对应的实体类的相关的Class类的方法
public Class<? super T> getSuperclass()
public Class<?>[] getClasses()
public Class<?>[] getDeclaredClasses()
java.lang.reflect.Class.getDeclaringClass() ;//返回一个成员内部类所在的类的Class
java.lang.reflect.Field.getDeclaringClass() ;//返回一个字段所在的类的Class
java.lang.reflect.Method.getDeclaringClass() ;//返回一个方法所在的类的Class
java.lang.reflect.Constructor.getDeclaringClass() ;//返回一个构造器所在的类的Class
public int getModifiers();//返回此类或接口以整数编码的 Java 语言修饰符。
/**
修饰符由 Java 虚拟机的 public、protected、private、final、static、abstract 和 interface 对应的常量组成;
它们应当使用 Modifier 类的方法来解码。
*/
Constructor<?>[] getDeclaredConstructors();//返回所有的构造方法的Constructor对象的数组的Constructor对象的数组
Constructor<?>[] getConstructors(); //返回所有共有的构造方法的Constructor对象的数组
(2)获取成员变量
Field[] getFields()
返回此 Class 对象所表示的类或接口的所有公有字段数组(Field 对象数组)
Field[] getDeclaredFields()
返回此 Class 对象所表示的类或接口中所有声明的字段数组(Field对象数组)。
(3)获取成员方法
Method[] getMethods()
返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口
(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法
Method[] getDeclaredMethods()
返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,
包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
String getCanonicalName()
返回 Java Language Specification 中所定义的底层类的规范化名称。
String getName()
以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称(全限定名:包名.类名)。
String getSimpleName()
返回源代码中给出的底层类的简称。
String toString()
将对象转换为字符串。
boolean isLocalClass() ;//判断是不是局部类,也就是方法里面的类
boolean isMemberClass() ;//判断是不是成员内部类,也就是一个类里面定义的类
boolean isAnonymousClass() ;//判断当前类是不是匿名类,匿名类一般用于实例化接口
在java.lang.reflect包中有三个类Field、Method、Constructor分别用于描述类的字段、方法、构造器。
这三个类都有getName方法返回项目的名称
Field类的getType方法可返回描述域所属类型的Class对象
Method和Constructor类都有报告参数的方法,Method类还有报告返回类型的方法
这三个类还有getModifiers方法返回整数数值,用不同的位开关描述public和static这样的修饰符的使用状况。还可以使用java.lang.reflect.Modifier类的静态方法分析getModifiers返回的整数数值,如isPublic、isFinal、isPrivate
Class类中的getFields、getMethods和getConstructors方法返回类提供的public域、方法和构造器数组,其中包括超类的公有成员
Class类中的getDeclareFields、getDeclareMethods和getDeclaredConstructors方法将返回类中声明的所有域、方法和构造器,不包括超类的成员
实例:
import java.lang.reflect.*;
import java.util.Scanner;
public class Mian2 {
public static void main(String[] args) throws ClassNotFoundException {
String name;
if (args.length > 0) name = args[0];
else {
Scanner in = new Scanner(System.in);
name = in.next();
}
Class<?> aClass = Class.forName(name);
Class<?> superclass = aClass.getSuperclass();
String s = Modifier.toString(aClass.getModifiers());
if (s.length() > 0) {
System.out.print(s + " ");
}
System.out.print("class " + name);
if (superclass != null && superclass != Object.class) System.out.print(" extends" + superclass.getName());
System.out.print(" {\n");
printConstructor(aClass);
System.out.println();
printMethods(aClass);
System.out.println();
printFields(aClass);
System.out.print("}");
}
public static void printConstructor(Class cl) {
Constructor[] declaredConstructors = cl.getDeclaredConstructors();
for (Constructor c : declaredConstructors) {
String name = c.getName();
System.out.print(" ");
String modifiers = Modifier.toString(c.getModifiers());
if (modifiers.length() > 0) System.out.print(modifiers + " ");
System.out.print(name + "(");
Class[] parameterTypes = c.getParameterTypes();
for (int j = 0; j < parameterTypes.length; j++) {
if (j > 0) System.out.print(", ");
System.out.print(parameterTypes[j].getName());
}
System.out.println(");");
}
}
public static void printMethods(Class cl) {
Method[] declaredMethods = cl.getDeclaredMethods();
for (Method m : declaredMethods) {
String name = m.getName();
System.out.print(" ");
String modifier = Modifier.toString(cl.getModifiers());
if (modifier.length() > 0) System.out.print(modifier + " ");
Class<?> returnType = m.getReturnType();
System.out.print(returnType.getName() + "(");
Class<?>[] parameterTypes = m.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
if (i > 0) System.out.print(", ");
System.out.print(parameterTypes[i].getName());
}
System.out.println(");");
}
}
private static void printFields(Class cl) {
Field[] declaredFields = cl.getDeclaredFields();
for (Field f : declaredFields) {
Class<?> type = f.getType();
String name = f.getName();
System.out.print(" ");
String modifiers = Modifier.toString(cl.getModifiers());
if (modifiers.length() > 0) System.out.print(modifiers + " ");
System.out.println(type.getName() + " " + name);
}
}
}
反射机制的默认行为受限于Java的访问控制。然而,如果一个Java程序没有受到安全管理器的控制,就可以覆盖访问控制。为了达到这个目的,需要调用 Field、Method 或constructor 对象的 setAccessible 方法。例如
f.setAtcessible(true); // now OK to call f.get(harry);
public class ObjectAnalyzer {
private ArrayList<Object> visited = new ArrayList<>();
public String toString(Object obj) {
if (obj == null) return "null";
if (visited.contains(obj)) return "...";
visited.add(obj);
Class cl = obj.getClass();
if (cl == String.class) return (String) obj;
if (cl.isArray()) {
String r = cl.getComponentType() + "[]{";
for (int i = 0; i < Array.getLength(obj); i++) {
if (i > 0) r += ",";
Object val = Array.get(obj, i);
if (cl.getComponentType().isPrimitive()) r += val;
else r += toString(val);
}
return r + "}";
}
String r = cl.getName();
do {
r += "[";
Field[] fields = cl.getDeclaredFields();
AccessibleObject.setAccessible(fields, true);
for (Field f : fields) {
if (!Modifier.isStatic(f.getModifiers())) {
if (!r.endsWith("[")) r += ",";
r += f.getName() + "=";
try {
Class t = f.getType();
Object val = f.get(obj);
if (t.isPrimitive()) r += val;
else r += toString(val);
} catch (Exception e) {
e.printStackTrace();
}
}
}
r += "]";
cl = cl.getSuperclass();
}
while (cl != null);
return r;
}
}
接口不是类,而是对希望符合这个接口的类的一组要求。
接口中的所有方法都是public,因此声明方法时不用加public修饰符。
接口绝不会有实例字段。在Java8之前接口中不会有任何实现方法。但是现在可以提供一些简单的默认实现。
类实现接口:
在实现接口时,必须把所有方法声明为public。
接口不能用new来构造。
可以用instanceof来检查一个对象是否实现了摸个接口。
接口中虽然不能有示例字段,但是可以包含常量。
与接口中的方法都为public一样,接口中的字段总是为public static final。
在Java8中,允许接口增加静态方法。
在Java9中,接口的方法可以是private。private可以是静态方法或实例方法。
用default修饰的方法为接口的默认实现。
public interface Comparabel<T>{
default int compareTo(T other){ return 0; }
}
默认方法的一个重要用法就是“接口演化”。
假设有一个用了很久的接口,要在接口增加一个方法。如果这个方法不是默认方法,那么之前实现接口的类就会编译失败,统统要去实现这个新方法,就会很麻烦。
如果现在一个接口中定义了一个默认方法,然后又在父类或另一个接口中定义了同样的方法,就会产生冲突。Java的解决方法是:
**深拷贝:**对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
**浅拷贝:**对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
Lambda 表达式,也可称为闭包。
语法:
(parameters) -> expression
//或
(parameters) ->{ statements; }
以下是lambda表达式的重要特征:
实例
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
变量作用域
lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。
lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)
在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
内部类就是定义在另一个类中的类。
两个原因:
内部类的对象总有一个隐式引用,指向创建他的外部类对象。
这个引用在内部类中的定义是不可见的。
可见这个博主的文章:
内部类
异常类对象都是派生与Throwable类的一个类实例。
Java将派生与Error类和RuntimeException类的所有异常成为非检查型异常。
其他异常均称为检查型异常。
在Java中只能抛出检查型异常。
public printFileName(String name) throws FileNotFoundException{
}
下面4种情况会抛出异常:
警告:
如果子类覆盖了父类的一个方法,则子类方法中声明的检查类异常不能比超类中声明的异常更通用。如果超类方法中没有抛出检查类型异常,则子类也不能抛出任何检查型异常。
习惯做法是,自定义异常类应该包含两个构造器,一个是默认无参构造器,一个是包含详细信息的构造器。
class FileFormatException extends IOException{
public FileFormatException(){}
public FileFormatException(String gripe){
super(gripe);
}
}
try{
...
}catch(..){
...
}catch(...){
..
}
finally{
...
}
如果try中的代码抛出了catch中的任一个异常,那么程序将跳过剩余的try子句,执行catch中的子句。如果没有抛出任何异常就跳过catch子句。如果抛出了一个catch中没有声明的异常,这个方法就会立即退出。最后不管如何,finally子句都会执行。
注释:
可以在catch中抛出一个异常,通常用于改变异常的类型。
try{
}
catch(SQLException e){
throw new ServeletException("Error"+d.getMessage());
}
更好的方法是把原始异常设置为新异常的原因:
try{
}
catch(SQlexception original){
var e = new ServeletException("error");
e.initCause(original);
throw e;
}
注意:finally子句要用于清理资源,不要把改变控制流的语句(return、throw、break、continue)放在finally子句中。
try-with-resources语句
最简形式:
try(Resource res = ...){
work with res...
}
当try语句退出时,会自动调用res.close。
典型例子:
try(var in = new Scanner(
new FileInputStream("/usr/txt.txt"),StandardCharsets.UTF_8)){
while(in.hasNext){
System.out.println(in.next());
}
}
这个快正常退出时,或者存在一个异常时,都会调用in.close()方法,都能关闭。
它还可以指定多个资源:
try(var in = new Scanner(
new FileInputStream("/usr/txt.txt"),StandardCharsets.UTF_8);
var out = new Printer("out.txt"),StandardCharsets.UTF_8){
while(in.hasNext){
out.println(in.next());
}
}
总结:只要需要关闭资源,就尽量用try-with-Resources语句,相对于try-finally可以减少代码量.
“5和6”可以总结为“早抛出、晚捕获”
在Java中,集合类库也将接口与实现分离。
集合框架中的接口
集合架构中的类
fail-fast 机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。
例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。
程序抛出Java.util.ConcurrentModificationException异常。
当多个线程对同一个集合进行操作时,某线程访问该集合时,集合内容被其他线程修改导致改变了modCount的值,从而使modCount != exceptedModCount ,所以抛出异常。
package java.util;
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
...
// AbstractList中唯一的属性
// 用来记录List修改的次数:每修改一次(添加/删除等操作),将modCount+1
protected transient int modCount = 0;
// 返回List对应迭代器。实际上,是返回Itr对象。
public Iterator<E> iterator() {
return new Itr();
}
// Itr是Iterator(迭代器)的实现类
private class Itr implements Iterator<E> {
int cursor = 0;
int lastRet = -1;
// 修改数的记录值。
// 每次新建Itr()对象时,都会保存新建该对象时对应的modCount;
// 以后每次遍历List中的元素的时候,都会比较expectedModCount和modCount是否相等;
// 若不相等,则抛出ConcurrentModificationException异常,产生fail-fast事件。
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size();
}
public E next() {
// 获取下一个元素之前,都会判断“新建Itr对象时保存的modCount”和“当前的modCount”是否相等;
// 若不相等,则抛出ConcurrentModificationException异常,产生fail-fast事件。
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
...
}
将集合类转化为java.util.concurrent包下的对应类型即可。
ArrayList源码解析–基于JDK1.8
Vector源码解析–基于JDK1.8
目前Java不推荐使用Vector,原因如下:
LinkedList源码解析–基于JDK1.8
Stack源码解析 – 基于JDK1.8
LinkedList和ArrayList性能差异
Vector和ArrayList比较
HashMap源码解析
Hashtable源码解析
TreeMap源码解析
WeakHashMap源码解析
Map概述
HashMap 是基于“拉链法”实现的散列表。一般用于单线程程序中。
Hashtable 也是基于“拉链法”实现的散列表。它一般用于多线程程序中。
WeakHashMap 也是基于“拉链法”实现的散列表,它一般也用于单线程程序中。相比HashMap,WeakHashMap中的键是“弱键”,当“弱键”被GC回收时,它对应的键值对也会被从WeakHashMap中删除;而HashMap中的键是强键。
TreeMap 是有序的散列表,它是通过红黑树实现的。它一般用于单线程中存储有序的映射。
HashSet源码解析
TreeSet源码解析
Java核心技术