Java基础常用易错点总结

常见的线程安全的类

Timer,StringBuffer,Stack(栈),Vector(向量),HashTable,Enumeration

Java继承问题

Java中子类不能继承父类的构造方法,只能显式或隐式调用(用super)
Java子类调用父类的构造方法不是必须用Super关键字调用可以用new关键字创建对象去调用
子类构造方法中即使不写super()调用父类的构造方法,系统也会自动的去强制调用父类的构造方法

易错: 子类在调用构造器之前,会先调用父类的构造器,当子类构造器没有使用super关键字指定调用父类的构造器时,是会默认调用父类的无参构造器,如果父类中包含有参的构造器,却没有无参构造器,则在子类构造器中一定要使用super关键字指定父类的有参构造器,否则就会编译报错

数据类型转换

由大到小需要强制转换,由小到大不需要强转
顺序:byte , short , char , int ,long,float,double

byte b=1;
int a = b;//由小到大

int c = 1;
byte d = (byte) c;//由大到小


Java类的初始化顺序

父类静态域 --> 子类静态域 --> 父类成员初始化 -->父类构造块 —>父类构造方法 -->子类成员初始化 -->子类构造块 -->子类构造方法

Integer和int

Integer是int的包装类型 虚拟机会将Integer拆箱成int,将int装箱成Integer,
装箱:Integer.valueof()
拆箱:Integer.intValue()

基本类型都有对应的包装类型,基本类型与其对应的包装类型之间的赋值使用自动装箱与拆箱完成。

Integer x = 2;     // 装箱 调用了 Integer.valueOf(2)
int y = x;         // 拆箱 调用了 X.intValue()

Integer,int 在 -127~128之前是不会创建新的对象的,即

Integer a = new Integer(12);
 int b = 12;
 System.out.println(a==b);//true

Integer和int自动装箱拆箱会通过valueOf()方法实现,当这个数在-127~128之间直接从缓存里边取,不会重新new对象

try{}和finally的问题

根据JVM规范如果try语句块里边有返回值则返回try{}里边的
如果try{}和finally{}都有return,则忽略try{}里边的使用finally{}里边的return;
finally 语句块是在 try 或者 catch 中的 return 语句之前执行的。
try后边不是必须跟catch{},可以跟finally{}

Java中的Null

Java中的null是关键字,用来标识一个不确定的对象,因此可以将null赋值给引用类型变量不可以赋值给基本类型变量
即:

int a = null; //错误 
Object b = null; //正确

null不属于Object实例,本身也不是对象
null可以被强转为任何对象
对任何不是 null 的对象 x 调用 x.equals(null) 结果都为 false

x.equals(null); // false;

关于类名调用静态方法:
用类名调用静态方法,对于Java来说,即使用对象去调用,Java自己也会将源文件编译成class文件时将其转化为用类名去调用静态方法
即:

class A{
 public  static void test(){}   
}
class B{
    //A.test()  等同于 new A().test();
}

线程的状态

NEW,RUNNABLE,RUNNING,BLOCKED,WAITING,DEAD

线程的常用方法描述

  • sleep方法属于Thread类,wait方法属于Object类
  • sleep()和wait()都会抛出InterruptedException异常,这个异常属于checkedException不可避免
  • sleep方法不会释放锁,会使线程堵塞,wait方法释放锁,让线程进入等待状态,用notify(),notifyall()方法可以唤醒,或者等待时间到了
  • wait必须在同步(synchronized)块里使用,sleep可以在任意地方使用
  • join()方法使当前线程停下来等待,直至另一个调用join方法的线程终止,值得注意的是:线程的在被激活后不一定马上就运行.而是进入到可运行线程的队列中。
  • Yield()方法是停止当前线程,让同等优先权的线程运行。如果没有同等优先权的线程,那么Yield()方法将不会起作用。

枚举(enum)

枚举类型可以理解为一个大的容器,用来保存对象

所有的枚举值都是类静态常量,在初始化的时候对所有的枚举值进行第一次初始化,枚举类有多少个实例,就会调用多少次构造方法,枚举类型的构造方法只能加private或者不加修饰符本身已经是构造好了的不允许被外边修饰

在枚举中成员变量默认都是public static final

枚举类的原理是将枚举类继承java.lang.Enum,并且声明为final,其内部维护多个实例,而且是在静态代码块中进行实例化多个实例。其实普通的类构造方法声明为private,静态代码块中初始化对应的变量即可实现enum的原理代码。所以枚举类不能在继承其他的类,可以实现接口

枚举类型实现单例模式可以预防反射攻击原因:反射底层会判断该类是不是枚举类型,如果是枚举类型会抛出异常

构造函数必须是私有的
1.根据单例老汉式推导出来 直接写枚举类型
public static final OldSingleton oldSingleton = new OleSinleton();
2.枚举项必须在枚举类的第一行 成员属性
在枚举类创建成员变量时,私有化有参构造,生成get,set方法
3.可以声明abstract方法,但必须在枚举类实现(匿名内部类)
关于被动引用不会触发子类初始化问题:
1.只调用父类的成员变量时不会触发子类初始化
2.该变量被final修饰,被final修饰的常量在编译时就已经在常量池
3.定义该类型的数组时,不会初始化该类

异常

方法内抛异常如果是运行是异常(包括他的子类)可以声明也可不声明,如果是编译时异常(检查时异常)必须在方法上声明

网络编程

UDP:速度快,面向无连接,不区分客户端和服务器端,只有发送端和接收端,不安全(容易丢包) 常用作视频通话
TCP:面向右链接,速度慢,区分客户端和服务器端,安全

短路运算符

&&与|| : 是按照短路方式求值的

&&: 只有当两边的表达式或者变量都为true时,才会满足条件,当第一个表达式为false时不执行后边的代码也就是短路

||: 当||两边的表达式有一个为true就可以 所以如果第一个表达式为true,后边的代码就不执行了,也就短路了

例如:

String s = null;
 if( (s!=null) && (s.length()>0) )// null != null 为false 所以不执行 &&后边的代码
 if( (s==null) || (s.length()==0) )//null == null 为true 不执行后边的代码  

类加载器

类的加载是由类加载器完成的,类加载器包括根类加载器(BootStrap) ,扩展类加载器(Extension) ,系统类加载器(System)和用户自定义的类加载器(java.lang.ClassLoader的子类)从Java1.2开始,类加载过程采用双亲委派机制(PDM).PDM更好的保证了JAVA平台的安全性,在该机制中,JVM自带的BootStrap 是跟类加载器,其他的类加载器仅有一个父类加载器,类的加载器首先请求父类加载器加载,父类加载器无能为力时子类加载器自行加载.JVM不会向程序提供BootStrap的引用

  1. 启动类加载器
    负责将存放在 < JAVA_HOME > \lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib中也不会被加载)类库加载到虚拟机内存中。
  2. 扩展类加载器
    扩展类加载器(Extension ClassLoader):这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载< JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
  3. 应用程序类加载器
    应用程序类加载器(Application ClassLoader):这个类加载器由sun.misc.Launcher$App-ClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(classPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认类加载器。

易错:参数传递问题

引用数据类型在传递过程中传递的是地址,不会创建对象,而基本类型传递的是值本身,会创建新的对象

HashCode和Equals

equals:作用是判断两个对象是否相等,但一般有两种情况
类没有覆盖equals方法,则相当于通过 "=="来比较 俩个对象的地址
类覆盖equals方法,一般我们通过equals()来比较两个对象的内容是否相等,相等则返回true
地址比较是通过对象的哈希值来比较的,hashCode属于Object的本地方法,对象相等(地址相等),hashCode相等
两个对象,如果equals()相等,hashcode一定相等
两个对象,如果hashcode相等,equals不一定相等

在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象散列值也相等。
下面的代码中,新建了两个等价的对象,并将它们添加到 HashSet 中。我们希望将这两个对象当成一样的,只在集合中添加一个对象,但是因为 EqualExample 没有实现 hashCode() 方法,因此这两个对象的散列值是不同的,最终导致集合添加了两个等价的对象。

EqualExample e1 = new EqualExample(1, 1, 1);
EqualExample e2 = new EqualExample(1, 1, 1);
System.out.println(e1.equals(e2)); // true
HashSet<EqualExample> set = new HashSet<>();
set.add(e1);
set.add(e2);
System.out.println(set.size());   // 2

理想的散列函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的散列值上。这就要求了散列函数要把所有域的值都考虑进来。可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位。
一个数与 31 相乘可以转换成移位和减法:

31*x == (x<<5)-x

编译器会自动进行这个优化。

//重写hashCode
@Override
public int hashCode() {
    int result = 17;
    result = 31 * result + x;
    result = 31 * result + y;
    result = 31 * result + z;
    return result;
}
//重写equals
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    EqualExample that = (EqualExample) o;

    if (x != that.x) return false;
    if (y != that.y) return false;
    return z == that.z;
}


可见性原子性,有序性

1.可见性:
可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉。通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制。

可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果。另一个线程马上就能看到。比如:用volatile修饰的变量,就会具有可见性。volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存。所以对其他线程是可见的。但是这里需要注意一个问题,volatile只能让被他修饰内容具有可见性,但不能保证它具有原子性。比如 volatile int a = 0;之后有一个操作 a++;这个变量a具有可见性,但是a++ 依然是一个非原子操作,也就是这个操作同样存在线程安全问题。

在 Java 中 volatile、synchronized 和 final 实现可见性。

2.原子性:
原子是世界上的最小单位,具有不可分割性。比如 a=0;(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作时原子操作。再比如:a++; 这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。非原子操作都会存在线程安全问题,需要我们使用同步技(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。java的concurrent包下提供了一些原子类,我们可以通过阅读API来了解这些原子类的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。

在 Java 中 synchronized 和在 lock、unlock 中操作保证原子性。

3.有序性:
Java 语言提供了 volatile 和 synchronized 两个关键字来保证线程之间操作的有序性,volatile 是因为其本身包含“禁止指令重排序”的语义,synchronized 是由“一个变量在同一个时刻只允许一条线程对其进行 lock 操作”这条规则获得的,此规则决定了持有同一个对象锁的两个同步块只能串行执行。

结点流和处理流

按照流是否直接与特定的地方(如磁盘、内存、设备等)相连,分为节点流和处理流两类.

节点流:可以从或向一个特定的地方(节点)读写数据。如FileReader.

处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader.处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。

JAVA常用的节点流:

  • 文 件 FileInputStream FileOutputStrean FileReader FileWriter 文件进行处理的节点流。
  • 字符串 StringReader StringWriter 对字符串进行处理的节点流。
  • 数 组 ByteArrayInputStream ByteArrayOutputStreamCharArrayReader CharArrayWriter 对数组进行处理的节点流(对应的不再是文件,而是内存中的一个数组).
  • 管 道 PipedInputStream PipedOutputStream PipedReaderPipedWriter对管道进行处理的节点流。

常用处理流(关闭处理流使用关闭里面的节点流):

  • 缓冲流:BufferedInputStrean BufferedOutputStream BufferedReader BufferedWriter 增加缓冲功能,避免频繁读写硬盘。
  • 转换流:InputStreamReader OutputStreamReader 实现字节流和字符流之间的转换。
  • 数据流 DataInputStream DataOutputStream 等-提供将基础数据类型写入到文件中,或者读取出来.

流的关闭顺序:
一般情况下是:先打开的后关闭,后打开的先关闭。另一种情况:看依赖关系,如果流a依赖流b,应该先关闭流a,再关闭流b。例如,处理流a依赖节点流b,应该先关闭处理流a,再关闭节点流b

可以只关闭处理流,不用关闭节点流。处理流关闭的时候,会调用其处理的节点流的关闭方法。

常见创建对象的几种方式

使用 new 关键字(最常用):

ObjectName obj = new ObjectName()

使用反射的Class类的newInstance()方法:

 ObjectName obj = ObjectName.class.newInstance()

使用反射的Constructor类的newInstance()方法:

 ObjectName obj = ObjectName.class.getConstructor.newInstance()

使用对象克隆clone()方法:

 ObjectName obj = obj.clone();

使用反序列化(ObjectInputStream)的readObject()方法:

 try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_NAME))) { ObjectName obj = ois.readObject(); }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值