总结:
第一题:
a++;表示a用完以后再加一
++a;表示先给a+1在用a;
&&:短路与,当前面的条件有不满足则&&后面的代码不执行
第五题:
子类继承父类当new子类对象是会自动调用父类的构造方法,但是因为创建的是子类对象所以在没有创建父类
对象时,父类对象为null;因此创建子类对象不一定产生了父类对象
第六题:
integer类型是int类型的包装类,当范围在-128---127之间时不会new对象来存放值
当范围在这之外后会开辟内存空间来存放,会导致地址值不同.
所以,当在-128---129之间时,==比较的是具体值.
在这范围之外时==比较的是地址值
第七题:
拥有静态成员的内部类不一定是静态内部类
因为,内部类中可以有常量,常量的修饰符可以是public static fianl int A = 0;
第八题:
抽象方法不可以被static关键字修饰;
因为会编译报错,错误信息:非法的修饰符组合: abstract和static
final修饰的方法也不可以声明为抽象的;
因为会编译报错,错误信息::非法的修饰符组合: abstract和final
总结:只要是不能重写的方法,都不能声明抽象方法,因为这些方法将来没有办法重写实现。
注意,抽象方法,就是为了将来让子类中去重写实现的。抽象方法可以调用,但不能执行,调用之后将来执行的一定是这个抽象方法实现。
(这个抽象方法的实现,在这个抽象类的子类中)
(需要用到多态和重写)
第十一题:
public static void main(String[] args){
int[] arr = {3,4,6,8};
int len = arr.length*2;
int[] a = new int[len];
for(int i=0;i<arr.length;i++){
a[i] = arr[i];
}
arr=a;
}
注意:数组扩容要讲扩容后的数组赋给原数组
第十二题:
public static void main(String[] args){
List list = new ArrayList();
test1(list);
System.out.println(list.size());
test2(list);
System.out.println(list.size());
}
public static void test1(List list){
list = null;
}
public static void test2(List list){
list.add("aaaa");
}
当List list = new ArrayList时不会产生空指针异常
只有当 List list = null;才会产生空指针异常
第十三题:
启动类加载器,扩展类加载器,系统类加载器,自定义类加载器
作用:
类加载器会通过classpath中的配置路径,来查找当前需要执行的java代码所存在的class文件
第十四题:
Java中死锁最简单的情况是,一个线程T1持有锁L1并且申请获得锁L2,而另一个线程T2持有锁L2并且申请获得锁L1,因为默认的锁申请操作都是阻塞的,所以线程T1和T2永远被阻塞了。导致了死锁。
第十五题:
当调用intern方法时,如果池已经包含与该字符串相同的String对象的字符串,则返回来自池的字符串。 否则,此String对象将添加到池中,并返回对此String对象的引用。
第十六题:
== 是操作符不能被重写,可以比较引用类型和基本数据类型
比较引用类型时比较的是地址值,比较基本数据类型时比较的是具体值
equals 是方法可以被重写,如果被重写后则按照重写的方法来比较,如果没有被重写
比较的是具体值
第十七题:
八大基本数据类型:
整数型
byte 8 位 1字节
short 16位 2字节
int 32位 4字节
long 64位 8字节
浮点型
float 32位 4字节
double 64位 8字节
字符型
char 16位 2字节
布尔型
boolean 8位 1字节
String是引用类型
第十八题:
获取类类型的三种方式:
1.Class.forName("全限类名<包名.类名>");
2.类名.Class
3.对象名.getClass();
基本数据类型也可以获取Class对象
例如:
//使用Class对象来表示基本类型
public void test1(){
//这个对象c就代表java中的int类型
Class c = int.class;
//判断对象c所代表的类型是否是基本类型
System.out.println(c.isPrimitive());
//获取对象c所代表的类型的名字
System.out.println(c.getName());
}
第二十四题:
创建对象不一定会调用构造方法.
列如:在反序列化的时候,对象就没有调用构造方法
一. == 和 equals 方法的区别
== 能用在基本类型数据之间,也可以用作引用类型的对象之间
如果是俩个基本类型数字相比, == 比较是基本类型的俩个数值是否相等
如果是俩个引用类型的变量相比,== 比较是俩个引用所指向对象的内存地址值是否相等
另外,==是java中的基本的操作符,我们无法改变==号的默认的比较方式。
equals 只能用在俩个引用类型的对象之间
这个方法是Object中定义的,所以对象直接或间接继承Object类之后,都可以使用这个继承过来的equals方法。
在Object中,equals方法默认实现是这样的:
public boolean equals(Object obj) {
return (this == obj);
}
那么就是意味着,如果调用的equlas方法是从父类Object中继承过来的(没有重写),那么这比较也是借助于 == 来比较俩个引用所指向的对象的内存地址值是否相等。
如果我们不满意从Object继承过来的这个equals方法的默认实现,我们想用自己的方式来比较俩个对象是否相等,而不是比较俩个对象的内存地址值,例如我们希望使用对象中的属性来进行比较,如果俩个对象中的属性值都是一一对应相等的,那么我们就认为这个俩个对象是相等的,否则就是不相等。
这个时候,我们就需要在子类中对继承过来的equals方法进行重写。
总结:==号是操作符,不能重写,equals是Object中的方法,子类里面可以重写,重写后按照自己的要求进行对比俩个对象是否相等。如果不重写,那么父类Object的equals方法默认实现其实也是用了==号来比较俩个对象是否相等。
二.包装类型
在java中,有八种基本的数据类型,这八种基本类型只能表示一些最简单的数字,这些数字最小的在内存中占8位,最大占64位。这些都是简单的数字,不是对象,所以也不能用来调用方法或者属性。
在java的API中,对这八种基本类型,又专门提供了类类型,目的就是为了分别把这八种基本类型的数据,包装成对应的类类型,这时候就变成对象了,就可以调用方法了或者访问属性了。
这些类型,就称为基本类型所对应的包装类型。
基本类型 包装类型
boolean Boolean
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
三. abstract修饰符
abstract可以用来修饰类、方法,如果修饰类,那么这个类就是抽象类,如果修饰方法,那么这个方法就是抽象方法。
抽象方法
只有方法的声明,没有方法的实现,这样的方法就是抽象方法。
例如:
//这就是一个只有声明没有实现的方法
public void test();
//这样的方法需要使用abstract修饰符来修饰,说明它是一个抽象方法
public abstract void test();
//既有方法的声明,又有方法的实现
public void test(){
System.out.println("hello");
}
//既有方法的声明,又有方法的实现,只不过这个方法的实现是一个空实现,也就是大括号中没有代码。但是这也算是方法的实现
public void test(){
}
抽象类
只要使用abstract修饰的类,就是一个抽象类
例如:
//这是一个普通的类
public class Person{
}
//如果想把一个普通的类变为一个抽象类,只需要加一个修饰符即可 abstract
public abstract class Person{
}
注意,普通类中能写什么样的代码,在抽象类中就可以写什么样的代码,没有什么区别,但过来,抽象类中能编写一种代码,普通类中就不可以编写,那就是抽象方法。
抽象类中可以编写抽象方法,而普通类中不可以编写抽象方法。
普通类中能够编写:
非静态
属性
方法
代码块(匿名代码块)
构造器
静态
属性
方法
代码块(静态代码块)
抽象类中能够编写:
和普通类中编写的完全一致,额外还可以编写抽象方法。
但是也正是因为这个,抽象类不能被实例化,也就是不能直接使用new来创建对象。
抽象类和抽象方法之间的关系
抽象类中可以有抽象方法,也可以没有抽象方法。
如果一个类中有抽象方法,那么这个类就必须要声明为抽象类。
(抽象类中可以没有抽象方法,但是有抽象方法的类一定是抽象类)
抽象类和抽象方法的特点
抽象方法因为没有实现,所以是没有办法执行的,只有等这个方法实现以后,才能调用执行这个方法的实现代码。
思考,这个抽象方法没有实现,肯定是不能执行的,但是否可以调用?
不能执行是因为没有实现的代码,但是可以进行调用,因为一个引用调用方法,最后会调用执行哪个对象中的方法,要看运行时这个引用所指向的对象是谁,以及对象中是否重写过这个方法。
方法的调用,是编译时,编译器要确定的,确定引用所调用的方法是哪一个类中的哪一个方法,最后执行的这个方法是哪一个对象中的,这个是运行时,JVM要确定的,主要是看这个引用指向对象是谁,以及对象中是否重写过该方法。
抽象类因为代码里面【可能】会存在没有实现的抽象方法,所以我们是不能使用这个抽象类直接new对象的,假设抽象类中有抽象方法,我们new出了这个抽象类的对象,那么用这个对象调用类中的抽象方法,这个做法是没有意义,也是不符合语法要求的。所以语法层面上,根本就不允许我们创建抽象类的对象。
抽象类和它的子类
抽象类不能直接使用new创建对象(也就是不能被实例化),但是抽象类可以被子类继承,将来可以使用子类创建对象,再结合java中的多态,可以使用父类的引用,指向子类对象,这时候这个父类就是这里说的抽象类了。
子类中继承了父类,父类是一个抽象类,那么子类就必须把父类中的抽象方法给实现了,这里说的实现,语法上就是我们之前用的重写,但是从意义讲,用实现这个词来描述会更加合适一些。
如果子类没有实现父类中的抽象方法,或者没全部把父类中的抽象方法给全部实现,那么这个子类也必须要声明为抽象类。因为抽象方法是可以被继承的,所以父类中假设有5个抽象方法,子类继承后,就相当于子类中有这5个抽象方法。
思考,静态方法是否可以声明为抽象的?
//编译报错
//错误信息:非法的修饰符组合: abstract和static
public static abstract void say();
思考,final修饰的方法是否可以声明为抽象的?
//编译报错
//错误信息:非法的修饰符组合: abstract和final
public final abstract void say();
总结:只要是不能重写的方法,都不能声明抽象方法,因为这些方法将来没有办法重写实现。
注意,抽象方法,就是为了将来让子类中去重写实现的。抽象方法可以调用,但不能执行,调用之后将来执行的一定是这个抽象方法实现。
(这个抽象方法的实现,在这个抽象类的子类中)
(需要用到多态和重写)
思考,静态方法是否可以使用final修饰?
//编译通过
//final和static的含义之间没有冲突
public static final void say(){}
思考,抽象类既然不能直接new对象,那么抽象类中是否有构造器?
中的构造可以有构造器。普通类中能写什么属性、方法、构造器、代码块,那么抽象方法中就可以写什么属性、方法、构造器、代码块。
抽象类器,虽然不能直接使用它创建对象,但是可以在创建子类对象的时候,子类的构造器中会调用这个父类中的构造器。
```
思考,我们为什么要编写抽象方法?
在一个类中实现方法时候,会遇到一些问题,通过思考分析后会发现,这个方法我们在这里是没有办法很合适的进行实现的,一般的原因会是当前这个类所表示的范围比较大,它的下面很多子类的情况各自不同,所以导致我们没办法在这个范围比较大的类中,很好的对这个方法进行实现,那么我们就可以把这个方法声明为抽象方法,然后将来在子类中对象这个方法进行重写实现,因为子类一般表示的范围比较小,情况比较具体,所以可以在子类中进行很好的实现。
例如,Animal这个类,表示所有的动物,这个范围是非常大的,我们在Animal中,只能确定动物是可以跑的,有run这个方法,但是动物是用什么方法跑的,run方法如何写代码一步步的实现,在Animal类中是确定不了的。这时候就可以把run方法在Animal中声明为抽象方法,将来在一个具体的子类中再去实现这个run方法,例如可以Dog这个类中进行实现,因为Dog描述的狗这个类型,范围比较Animal小很多,所以就有可以具体描述出狗是怎么跑的,然后对run方法进行实现。
在一个表示范围比较大的类中,定义了一个方法,在结合实际情况,从当前设计和意义的角度考虑后,发现这个方法在当前表示这么大一个范围的类中无法实现,所以接下来可以有俩种选择:
1.把这个方法写出抽象方法
2.把这个方法实现了,给一个默认的实现
```
思考,我们为什么要编写抽象类?
1. 抽象类中有抽象方法(绝大多数情况属于这种)
类中有抽象方法,那么这个类一定要声明为抽象类。
2. 抽象类中没有抽象方法(比较少见)
第一种考虑的情况:
我们设计了一个类,由于某种原因,我们并不想别人直接使用这个类来new对象,我们希望别人使用子类继承这个类,然后再使用这个子类型。这时候就可以把这个类使用abstract修饰,变为一个抽象类,但是类中没有抽象方法
第二种考虑的情况:
在一个抽象类中,本来有很多个抽象方法,例如有20个,那么将来这个抽象类的子类就必须把这个20抽象方法全都个实现了,但是很多少这个子类其实只需要使用到其实一俩个方法而已,由于语法的要求,不得已实现了这个20个方法,这种情况并不是很合适,所以这个时候可以考虑,把这20抽象方法全都给一个默认的实现,例如空实现。这时候就有了一个抽象类,但是类中没有抽象方法,将来的子类继承这个抽象类后,就可以用到哪一个方法,就只重写这个方法了。
四. 接口(interface)
1.接口和抽象类的区别
抽象类也是类,除了可以编写抽象方法和不能直接new对象之外,其他地方和普通的类都是一样的。
接口已经是另外一种类型,和类是有本质的区别的,所以不能使用类的相关标准/特点去衡量接口。
例如:类中都会有构造器,但是接口中没有构造器,因为它们本来就不是一个种类的。
声明类的关键字是class,声明接口的关键字是interface
例如:
public class Person{}
public interface Action{}
类或者抽象类可以被子类继承,并且是【单继承】
例如:类A继承了类B,这时候类A的对象就属于B类型,可以使用多态:一个父类的引用,指向子类对象
B b = new A();
注意,继承使用的关键字为extends
接口可以被类进行实现,并且是【多实现】
例如:类A可以同时实现接口B、C、D、E...,这时候类A的对象就属于B、C、D、E等类型了,可以使用多态:一个接口的引用,可以指向它的任意一个实现类对象。
B b = new A();
C c = new A();
D d = new A();
E e = new A();
注意,这里使用不同类型的引用B C D E来指向A类的对象,它们区别在于,使用不同类型的引用的时候,我们所能调用到的方法是不一样的。
例如,B b = new A(); 引用b所能调用到的方法,只有B类型中定义的方法 和 Object中定义的方法。
注意,实现使用的关键字 implements
注意,一个类实现接口后,就把接口中的属性和方法都继承过来了,所以实现也是另外一种形式的继承。java中引入实现这种机制的目的,也是对继承的一种扩展。
2. 接口中的方法必须是抽象方法
接口中可以不写任何方法, 但是如果写了,那么必须是抽象方法。
例如:
public interface Action{
public abstract void run();
public void test();
//默认就是public abstract修饰
void say();
}
注意,只有在接口中,才可以省去public abstract这俩个关键字,在抽象类中,编写抽象方法的时候,这俩个关键字是不能省去的。
3.接口中的属性必须是public static final修饰的
接口中可以不写属性,但是如果写了那么就必须是公共的静态常量
public interface Action{
public static final String NAME = "tom";
//属性默认的修饰符就是public static final
int AGE = 20;
}
注意,只有在接口中,才可以省去public static final这俩个关键字,在抽象类中,编写公共的静态常量的时候,这三个关键字是不能省去的。
4.接口中可以编写哪些代码
属性必须是public static final
属性默认是public static final
方法必须是public abstract
方法默认是public abstract
例如:
public interface Action{
//public static final String NAME = "tom";
//public abstract void test();
//可以这样简写
String NAME = "tom";
void test();
}
javap Action.class 看到的结果为:
//说明代码中我们虽然简写了,但是编译之后,会自动把这些默认的修饰符给加上来
public interface com.briup.day24.Action {
public static final java.lang.String NAME;
public abstract void test();
}
注意,如果代码中使用private或者protected修饰的属性或方法的话,那么编译会报错。
注意,接口中还可以编写静态方法和默认方法,但是需要JDK1.8及以 上的支持。(目前先暂且不讨论这个,后面会专门的进行学习)
例如:
public interface Action{
static void test(){
//....
}
default void say(){
//....
}
}
注意,接口中不能编写匿名代码块,也不能编写静态代码块。
注意,接口中也没有构造器
总结:接口中只能写属性和方法,属性必须是公共的静态常量,方法必须是公共的抽象方法。(JDK1.8以上支持在接口中编写静态方法和默认方法)
5.接口的使用
一个类可以同时实现一个或者多个接口,如果有多个接口,那么需要使用逗号隔开。
注意,一个类实现了一个接口,那么就相当于把接口中的属性和方法都继承过来了,因为接口中的方法都是抽象方法,所以实现类中就需要把这些方法都给实现了,否则这个类就需要声明为抽象类.(和继承一个抽象类的要求是一样的)
注意,接口中可以有很多抽象方法,也可以没有任何抽象方法。
注意,接口+多态 是java中,非常非常常见的使用方式。
一个接口的引用,可以指向它的任何一个实现类对象。
例如:
public interface Action{
public void doSomething();
public void test();
}
public interface Fly{
public void fly();
public void test();
}
public class Student implements Action,Fly{
public void doSomething(){
//..
}
public void fly(){
//..
}
//既是对Action接口中test方法的实现
//也是对Fly接口中的test方法的实现
public void test(){
}
}
main:
Action a = new Student();
a.doSomething();
//因为变量a所属的类型Action中没有fly方法
//所以编译器报错
a.fly();
//可以做类型转换,转为Fly类型,因为Fly类型中有fly方法,所以可以调用
//注意,在当前代码情况下,这句代码,编译通过,运行也没问题,虽然Fly和Action之间没有任何关系,但是类型转换还是成功,因为我们这里对象实现类型是Student,这个类同时实现了Fly和Action接口
//所以这个对象既属于Fly类型,又属于Action类型
Fly f = (Fly)a;
f.fly();
注意,在代码中一定要是分清楚,当前引用的类型是什么,以及我们使用这个引用都可以调用到哪些方法。
6. 一个接口可以继承多个父接口
例如:
public class Student implements C{
public void testC(){}
public void testA(){}
public void testB(){}
}
class StudentTest{
public static void main(String[] args){
C c = new Student();
System.out.println(c instanceof A);
System.out.println(c instanceof B);
System.out.println(c instanceof C);
}
}
interface A{
public void testA();
}
interface B{
public void testB();
}
interface C extends A,B{
public void testC();
}
7.接口的意义
例如我们可以在完成功能之前,在接口中可以提前先定义出完成功能要使用到的相关方法,这里的方法都是抽象方法,也就是只有方法的声明, 没有方法的实现。
同时接口也可以帮我们在一定程度上解决,类和类之间单继承的束缚,因为接口可以被多现实。
在学习编程的过程中,我们会遇到很多规范、标准,在java中大多的规范、标准都是以接口的方式进行体现,因为类实现接口后,类中一定是有接口里面所声明的方法的实现,只有我们实现规范、标准中所提供的接口,那么我们的类中也一定会要这些方法,这时候我们的类其实也就是在按照这些规范、标准要的方式进行编写。