此笔记是博主在自学java基础的时候写的笔记,记录了一些比较容易让新手困扰的地方,笔记都是博主自己一字一字编写和记录,有错误的地方欢迎大家指正。
1、static final 结合使用,相当于一个常量,在编译时就能知道所需要的内存分配大小,有助于效率的提高,故优先推荐使用。
附加:所谓常量,即像int,char,double等类型,因为这些类型的大小是固定的,所以在编译时就可确定内存分配的大小。
2、关于类的方法继承问题:
(1)覆盖父类的方法或实现接口的方法时,可以增大方法的可见性(例如protected变为public),但不可以缩小(例如public不能变为protected)。
(2)覆盖父类的方法或实现接口的方法时,异常的抛出只能减少不能增加。即只能抛出父类或接口声明过的异常或异常的子类(例如原抛出Throwable可以变为Exception)。
(3)覆盖父类的方法或实现接口的方法时,返回的参数可以是父类或接口声明的方法的返回参数的子类(例如原返回Object可以变为返回Srring)。
(4)覆盖父类的方法,除了名称要一样以外,参数类型也必须是一样的,注意参数类型是不区分是否父子类关系的,即如果父类方法fun(String param)与子类方法fun(Object param)是重载而不是覆盖关系。
3、java的动态是只有方法才拥有这个特性,成员变量是没有这个特性的。例如subClass()定义了成员变量i,那么super().i!=subClass().i,即不覆盖也不重载。
如果subClass没有覆盖父类的方法,那么方法中使用的成员变量是父类的super().i,即使子类也定义了i也不起作用。
4、注意基本类型和包装类(注意String不是包装类)的区别:基本类型的定义是把数值拷贝到一个常量池中的,
而包装类型的定义有两种,如果是用new则直接创建一个新的对象(且不会放入常量池),如果直接写数值(例如Integer i=100),则会先检查常量池是否存在该对象,如果存在则直接引用,如果不存在,则创建一个以对象,并放一份在常量池中。
对象和常量池的比较分以下几种情况:
情况一(包装类与包装类比较):new Integer(123)==new Integer(123);//结果返回false,因为此时比较的是两个对象的内存地址。
情况二(基本类型与基本类型比较):"abc"=="abc";//结果返回true,因为此时比较的是常量池中的数值。
情况三(包装类与基本类型比较):new Integer(123)==132;//结果返回true,当包装类与基本类比较时,包装类会调用valueOf()方法拿到常量池中的数值与基本类比较。
特殊情形:对于情况三,只有除String以为都成立,如果new String("abc")=="abc",结果是返回false的,因为准确来说String并不是包装类,他并没有常量池数据,因此只能拿内存地址和基本类型比较。
特殊情形2:Integer i1=Integer.valueOf("123");Integer i2=Integer.valueOf("123");此时i1==i2,结果返回ture。因为-127到127的值都是Integer都是从IntegerCache获取的,得到的都是同一个对象。
因此,Integer i=new Integer(123)的效率比Integer i=Integer.valueOf(123)低。
为保证比较的正确性,通常有包装类的情况下都建议使用equals();
5、字符串总体来说分两类,一个是对象String,另一个是常量"abc"(在常量池中存在一份引用来被其他地方重复引用,故称为常量),但不管是何种字符串对象,大部分进行字符串操作后返回的是新的对象String,特殊情况的将返回字符串常量。
例如:substring(0)、intern()方法。不管如何,凡是字符串的比较都应该使用equals()进行比较,确保程序的正确性。
6、使用注解时,如果需要一个数组参数,使用{}来表示,例如:{"ab","cd"}。
7、当对象需要保持到磁盘或进行远程传输时,都需要实现Serailizable系列化接口。并且最好显示声明serialVersionUID(作用是标识当前类是否有改动,不同的序列化对象表UID可以相同且不受影响)。
因为默认的serialVersionUID是通过方法、变量等经过复杂运算得到的,如果对象在写入磁盘后修改过对象的类,那么反序列化时将会报错,显示声明serialVersionUID可以欺骗JVM说此类没有修改,故增强了代码的健壮性。
另外,序列化对象的属性不能是transient和static,可以为public或private等。因为transient表示对象的临时值,static表示类的状态而跟对象没有直接的联系,因此,这两个修饰符修饰的变量都不会写入到磁盘中,但
在反序列化时,JVM会把类的static的值赋给反序列化对象,transient则不会赋值。序列化对象不保存方法和构造函数等,只保存对象的描述。
8、java中的内存分为堆和栈。栈的访问速度比堆快,通常堆中存放着基本类型的变量和对象的引用,如果是局部变量,在过了这个区域后就会立马释放内存供其他变量使用。
堆存放着new处理的对象和数组等,这些对象需要借组垃圾回收器来回收并释放资源。
9、一个类中初始化时的执行顺序:
父类中的静态变量内存空间开辟---->开辟子类的静态变量内存空间---->父类的static代码(含静态代码块和变量<按顺序执行>)----->
父类中的普通变量内存空间开辟---->开辟子类的普通变量内存空间---->父类中的普通代码(含非静态变量的代码块(即仅{})和变量<按顺序执行>)
---->父类中的构造器执行--------->子类中的static代码执行(跟父类的执行顺序相同)。。。。
10、全局变量的初始化认值(在开辟空间时就赋予了对应值)。char的默认值是:NUT(对应ascii码的0),byte/short/int/long的默认值是:0,float/double的默认值是:0.0,boolean的默认值是:flase,Object类型的默认值是:null
11、默认情况下,类的成员变量是包访问权限(即在同个包名下的对象都可以访问),构造函数是public权限。
12、java有个特殊的类package-info.java,此类不能有自己的实现类(即不能有public class package-info{...}),此类的功能是用于管理友好类(包访问权限的类)和友好常量,为包注解提供便利等。
注意:package-info类不能再eclipse中创建(因类名检测不通过),需要用普通文本创建后再拷贝到eclipse中。
13、自定义注解需要指定@Target(ElementType.FIELD)和@Retention(RetentionPolicy.RUNTIME),可以声明默认属性value(),这样在使用该注解是,给属性赋值不用知道名称(默认属性名就是value)。
注解中可以定义枚举,基本类型(包括String),可以指定属性默认值default。制定注解解析器时,可以通过Class对象反射得到注解。
14、String字符串创建方式有两种""或new String(),其中""方式创建时会先检查字符串常量池中是否存在(通过equals判断),如果存在则返回当前的字符串,如果不存在则重新创建一个字符串,并拷贝一份
到常量池中。而使用new String()方式创建,则不检查常量池是否存在,直接创建新的字符串,同时也不会拷贝一份到常量池中。故,优先使用""创建方式。
15、当使用可变参数时,如果默认不传入参数,那么得到的方法参数是一个长度为0的空数组,如果传入一个null,那么方法参数得到的是一个null,就会容易出现空指针异常。
16、全局变量的命名必须要规范化,且变量前面不能直接连着大写字母:
例如:String eBusiness="电商"
导致生产的get/set方法名是geteBusiness()/seteBusiness(),这种命名可能会导致很多地方调用属性方法出问题,应尽量避免。
17、字节流和字符流的区别:
字节流:在读取数据时,是按一个字节(8位)来读取的。计算机上的任何数据都是由字节进行组合而成的,因此,字节流可以读取任何类型的数据(包括图片和文字等)。
但是,在读取文字时比较特殊,因为文字都是由几个字节组合成一个字符(因编码方式而不同),因此,如果使用字节流来读取文字,那么会读取的字节需要一个一个凑合
后,再经过字符解码转换成文字,效率底下。
字符流:字符流在底层也是读取字节,但是字符流会因根据编码方式不同来判定一次性读取的字节数,从而快速的凑合成字符转码所需的字节数,提高字符转换效率。
附加:在读取文字时,应该使用字符流读取,反之,非文字时,应该使用字节流读取。
18、使用泛型时,在类和方法中声明泛型,可以使用 super 和 extends 以及 &关键字(代表"且"含义),指定名称代表改泛型 。例: public class Test<T extends Serializable & Cloneable>在变量中声明泛型,可以使用super 和 extends,但不能使用 &关键字。并且如果使用了super 或 extends关键字,泛型名称只能使用?代表。
例: List<? extends Cloneable> list=null;
19、当集合List/Set/Map正在使用for循环进行迭代读取时,如果此时集合有被修改,将抛出ConcurrentModificationException异常。
解决方案:
(1)在遍历集合时,使用迭代器iterator()进行遍历。
(2)使用CopyOnWriteArraySet、CopyOnWriteArrayList、ConcurrentHashMap、ConcurrentLinkedQueue这几种集合来替代。
这几种集合使用的思想是:创建一个底层数据的副本,在每次修改时使用副本进行操作,当操作完后,一个原子性的操作将原数组替换。
附加:CopyOnWriteArraySet、CopyOnWriteArrayList、ConcurrentHashMap、ConcurrentLinkedQueue这几个线程的操作都是使用副本,
修过过后都是通过原子性操作(例如使用Lock类的对象)立刻将原数组替换的,使得其他线程都能读取到最新的修过信息,因此这几个集合都是线程安全的。
20、使用java操作大文件(通常1G以上)时,可以采用MappedByteBuffer类(内存映射),来分段读取大文件的数据。
普通的java I/O 操作,每次获取文件数据都会执行一次硬盘的IO操作,既每读一次,就会去硬盘中读取一次,运行效率会低下。
而采用MappedByteBuffer类,是直接读取一次把全部数据保存在内存中,往后获取数据或操作数据都是直接在内存中进行,但不能一次将所有数据都读取到内存中,容易 引起内存的溢出。
21、关于类冲突的情形:
1、如果jar包下的类全限定名(包名+类名)与项目中的类全限定名相同,那么在项目中会优先使用同个项目下的类,而不是使用jar包的类。
2、当在项目中引入两个不同名称的jar包,但两个jar包下有相同类全限定名,那么会将先使用引入最先引入的那个jar包的类(目前Eclipse是按这种机制)。
22、当调用对象的方法时,如果存在某个方法被多次重载,则调用方法的优先级如下:
(1)传入的方法参数优先是使用原始类型的参数查找,优先查找子类,如果没有则查找父类。
(2)传入的方法参数被向上转型,然后查找子类,如果没有则查找父类。
例如:
public class Parent {
public void fun(Map map){
System.out.println("父类Parent--map");
}
}
public class Sub extends Parent{
public void fun(HashMap map){
System.out.println("子类Sub--HashMap");
}
public void fun(Object map){
System.out.println("子类Sub--Object");
}
}
public class MainRun {
public static void main(String[] args) {
Sub s = new Sub();
Map map = new HashMap();
/*因为子类没有对应的参数类型,而父类有,
*则调用父类的fun方法,执行结果为:父类Parent--map
*/
s.fun(map);
/*因为子类没有对应的String类型的fun方法,而父类也没有,
*则将String向上转型为Object,然后再查找,查找出子类有Object类型的fun方法,
*则执行结果为:子类Sub--Object
*/
s.fun("");
}
}
23、String字符串的repalceAll方法是支持正则表达式的,并且拥有正则表达的特点,$0~$9都是匹配到的结果。
例如:
System.out.print(str.replaceAll("<(a[^>]*)>","[$1]"));
System.out.print("汉字!和英文的!".replaceAll("\\pP",""));
System.out.print("测试数值138和196结束".replaceAll("\\pN",""))
附加:
Unicode 编码并不只是为某个字符简单定义了一个编码,而且还将其进行了归类。
\pP 其中的小写 p 是 property 的意思,表示 Unicode 属性,用于 Unicode 正表达式的前缀。
大写 P 表示 Unicode 字符集七个字符属性之一:标点字符。
L:字母;
M:标记符号(一般不会单独出现);
Z:分隔符(比如空格、换行等);
S:符号(比如数学符号、货币符号等);
N:数字(比如阿拉伯数字、罗马数字等);
C:其他字符
P: 标点字符
上面这七个是属性,七个属性下还有若干个子属性,用于更进一步地进行细分。
Java 中用于 Unicode 的正则表达式数据都是由 Unicode 组织提供的,故可以使用上面的特性来编写正则。