java基础知识点
jdk:Java开发工具包,只要开发Java程序就需要安装该软件
jre:Java运行时环境,只要运行Java程序就需要安装该软件
javac.exe:Java语言编译器,用于将源代码文件翻译成字节码文件
java.exe:Java语言解释器,用于启动JVM解释执行所有指令
JVM:Java虚拟机,是操作系统与Java程序之间的桥梁
jdk和jre的区别
JDK(Java Development Kit)是针对开发人员的产品,是整个java的核心,包括了运行环境jre,java工具和java基础类库。
JRE(Java Runtime Environment)是运行JAVA程序所必须的环境的集合,包含JVM标准实现及java核心类库。
JVM(Java Virtual Machi’ne),是整个java实现跨平台的最核心的部分,能够运行java语言写作的软件程序。
JDK是用于java程序开发的,JDK有编译功能,而JRE只是可以运行class文件,但是并没有编译的功能。JDK是提供给Java开发人员使用的,其中包含了java的开发工具,也包括了JRE。所以安装了JDK,就不用在单独安装JRE了。 其中的开发工具包括编译工具(javac.exe)打包工具(jar.exe)等。在JDK的安装目录下有一个jre目录,里面有两个文件夹bin和lib,在这里可以认为bin里的就是jvm,lib中则是jvm工作所需要的类库,而jvm和 lib合起来就称为jre。
java语言如何实现跨平台?
首先,通过编译器将jiava源程序编译成字节码文件,字节码文件其实与平台没有关系,然后java虚拟机将字节码文件翻译成特定操作系统下机器码来运行
注意:字节码不能直接运行,只有通过jvm翻译成机器码才能够运行,不同操作系统下编译的字节码文件都是相同的,但是不同操作系统下的jvm翻译出来的机器码却是不一样的,跨平台的是java 程序,并不是jvm。
基本数据类型
java是一种强类型语言,意思就是每个变量声明一种类型。一共有8种基本类型(primitive type)。其中4个整型(byte、short、int、long),2个浮点类型(float、double),一个表示Unicode编码的字符单元字符类型(char),1个表示真值的类型布尔类型(boolean)。
整型:整型表示没有小数部分的数值,可以有负数的存在。
-
byte:字节类型,字节数:1,二进制位数:8,取值范围:-128~127
-
short:短整型,字节数:2,二进制位数:16,取值范围:-32768~32767
-
int:整型,字节数:4,二进制位数:32,取值范围:-2147483648~2147483647
-
long:长整型,字节数:8,二进制位数:64,取值范围:-9.22337E+18~9.22337E+18
注意事项:int a=1;1就是字面量,也就是直接写出来的常量,整数的字面量都默认是int类型。所以如果要是定义为long类型数据,要在数字后边加上L或者l。
浮点型:浮点型表示有小数部分的数值,可以有负数的存在。 -
float:单精度类型,字节数:4,二进制位数:32,取值范围:-3.40E+38~3.40E+38
-
double:双精度类型,字节数:8,二进制位数:64,取值范围:-1.79E+308~-1.79E+308
注意事项:如果不做声明的话,会默认小数位double类型,如果要使用float类型,必须进行强转。例如,float a=1.3;会编译报错,正确的写法为:float a=(float)1.3;或者是float a=1.3f;(或者是f/F)。
一般来说,CPU处理单精度浮点数的速度比双精度浮点数要快很多。注意:float的范围大于long,但是精度没有long高,因为其有效位数(尾数)短
-
char:字符类型,字节数:2,二进制位数:16,取值范围:0~65535 。 两字符 char 中间用“+”连接,内部先把字符转成 int 类型,再进行加法运算,char 本质就是个数!二进制的,显示的时候,经过“处理”显示为字符。
-
boolean:true/false
基本数据类型间的转换:
- 小到大 自动类型转换:byte–>short–>int–>long–>float–>double (char在自动类型转换时数据类型提升为int)
- 大到小 强制类型转换:①会损失精度,产生误差,小数点以后的数字全部舍弃。②容易超过取值范围。例如:double d =10.9 int a = (int)d;
注意:
如果,两个操作数中有一个是double,那么另一个数字就会转成double。
如果,两个操作数中有一个是float,那么另一个数字就会转成float。
如果,两个操作数中有一个是long,那么另一个数字就会转成long。
面试时可能会问到的问题:
- 问:java中3*0.1==0.3将会返回什么?true or false?
false,因为浮点数不能将完全精确的小数表示出来,在进行运算后,一般都会有精度损失。 - float=1.3;是否正确?
错误方法,编译报错。1.3是双精度类型(double)赋值给单精度类型(float)属于向下转型,会造成精度的损失,因此需要进行强制类型转换,正确的表示方法为:float a=(float)1.3;或者是float a=1.3f;(或者是f/F)。
字符类型:原本用于表示单个字符,不过现在有些Unicode字符可以用一个char值表示,另外一些Unicode字符则需要两个char值,char类型的字面量要用单引号括起来。
==和equals的区别
==号比较的是内存地址,equals()比较的是字符串内容。
1)对于==,如果是作用于基本数据类型,那么是比较存储的“值“是否相等,如果是作用于引用数据类型,那么则比较的是指向的对象的地址值。
2)对于equals()方法,特别注意的是,不可以作用于基本数据类型,equals()方法继承了Object类,比较的是是否是同一个对象;
如果没有对equals()方法重写,则等价于“ == ”,比较两个对象是否相等,实际上是比较指向两个对象的
地址值是否相等;
如果对equals()方法重写了,则比较的是两个对象的内容是否相等。(诸如String、Date、Integer等类对equals方法进行了重写的话,比较的是所指向的对象的内容)。
两个对象的hashcode()相同,那么两个对象的equals()也一定为true吗?
总结:
两个对象equals()相等,那么他们的hashcode()一定相等,但是他们的hashcode()相等,他们equals()结果不一定相等;
两个对象 == 相等,那么他们的hashcode()一定相等,但是他们的hashcode()相等,他们的equals()结果不一定相等;
两个对象hashcode()不相等,那么这两个对象一定不相等。
解决方案:(比较两个对象是否真正相等)
首先,使用hashcode()作比较,如果hashcode()结果不相等,那么两个对象一定不相等;如果hashcode()相等,那么就再使用equals()比较,如果相等,那么说明这两个对象是相等的。
6.final在java中的作用
在java中,final可以用来修饰变量,方法 和类(类,类属性,类方法 )
当用final修饰类时,表明这个类不能被继承。
当用final修饰方法,此方法不能被重写(可以重载多个final修饰的方法)
当用final修饰变量,此变量表示常量,只能被赋值一次,赋值后值不再改变,被final修饰的成员变量必须要初始化,赋值后不能再重新赋值(可以调用本对象方法修改属性值),对基本类型来说其值是不可改变的,对引用变量来说其引用也是不可变的,即不能再指向其他对象。
7、java 中的 Math.round(-1.5) 等于多少?
- Math.round(-1.5)=-1
Math.round(1.5)=2
原因:Math.round()是对传入的参数,+0.5之后,再向下取整就是返回的结果(这里的向下取整指的是比他小的第一个整数或 者是和他相等的整数)。他的返回值类型是long型。
Math.floor()方法,向下取整,返回值类型为double类型。
Math.ceil()方法,向上取整,返回值类型为double类型。
8、String 属于基础的数据类型吗?
String不属于基本数据类型,只有8种基本数据类型。(见第三个问题—java基本数据类型)
String是个对象,是引用类型。
String是final修饰的java类。
注意事项:
基本数据类型只表示简单的字符或数字,而引用类型可以表示复杂的数据类型。
java虚拟机会为基本数据类型分配实际的内存空间,而只会对引用类型指向堆中的某个实例的地址。
9、java 中操作字符串都有哪些类?它们之间有什么区别?
String、StringBuffer、StringBuilder
-
String:被final修饰,String类的方法都是返回new String的。String对象的任何改变都不会影响到原对象的值,即对字符串String的操作每次都会生成新的对象。String的缺点是每次String在使用拼接符号+时,总是重新创建一个String字符串。大量的使用字符串拼接时不推荐使用String。(String方便,但是占用了大量的内存空间)
-
StringBuffer:是可以直接改变对象的,对字符串操作的方法都添加了synchronized方法,保证了线程的安全。
-
StringBuilder:可以改变操作对象,他不能保证线程的安全,在他的方法体内,需要进行对字符串的修改操作,可以使用new StringBuilder对象调用append()、replace()、delete()等方法进行操作修改字符串。
StringBuilder的用法:
StringBuilder sb=new StringBuilder();
sb.Append(“123”);
sb.Append(“456”);
string str=sb.ToString();拓展(了解):
来说下synchronize和Lock的区别:
两者都是锁,用来控制并发冲突,区别在于Lock是个接口,提供的功能更加丰富,除了这个外,他们还有如下区别:
-
synchronize自动释放锁,而Lock必须手动释放,并且代码中出现异常会导致unlock代码不执行,所以Lock一般在Finally中释放,而synchronize释放锁是由JVM自动执行的。
-
Lock有共享锁的概念,所以可以设置读写锁提高效率,synchronize不能。(两者都可重入)
-
Lock可以让线程在获取锁的过程中响应中断,而synchronize不会,线程会一直等待下去。lock.lockInterruptibly()方法会优先响应中断,而不是像lock一样优先去获取锁。
-
Lock锁的是代码块,synchronize还能锁方法和类。
-
Lock可以知道线程有没有拿到锁,而synchronize不能(Thread.holdsLock方法可以让synchronize也能感应到是否获取到了锁)
操作效率:StingBuilder>StringBuffer>String
线程安全:StringBuilder是线程不安全的,用于单线程
StringBuffer是线程安全的,用于多线程
String是线程安全的,用于多线程 -
10、String str="i"与 String str=new String(“i”)一样吗?
String str = “i”; 在创建之后,会将“i”这个值放在常量池中,这个只是一个引用,内存中如果有“i"的话(java虚拟机会在常量池中自动检索),str就指向它;如果没有,才创建它;
如果出现了 String str1 = “i”; String str2 = “i”; String str3 = “i”; 那么str、str1、str2、str3都会指向这个“i”
而String str = new String(“i”);对象创建了以后放在堆内存中,这是根据"i"这个String对象再次构造一个String对象,将新构造出来的String对象的引用赋给str。
注意:new出来的对象放在堆内存中,而String str = “i”; 是将“i” 放在常量池中,如果常量池中有“i”,则将str引用指向“i”.没有则创建。
拓展:
- String str="i"与 String str1=new String(“i”)一样吗?
(a)str == str1 结果是false(两个地址值是不同的)
(b)str.equals(str1) 结果为true(两个对象的值是相当的,所以内存中只能往HashSet中放一个)
- String str = new String(“Hello”);创建了几个对象?
这个主要分2种情况:
1)如果String的字符串常量池中有Hello这个对象,那么就只创建了一个对象。
原因:因为字符串常量池中有Hello,字符串会用已有的Hello对象,所有不会再创建Hello这个对象了,只会创建一个new String对象。
2)如果String的字符串常量池中没有Hello这个对象,那么久创建了两个对象。
原因:只要是new了就一定会创建一个新对象,而字符串常量池中没有Hello,所以会在字符串常量池中创建一个Hello对象。 - Sting str = “a”;
String str1 = “a”+“b”;
创建了几个对象?
一共创建了2个对象,一个是str对象a,一个str1对象ab。 - Sting str = “a”;
String str1 = str+“b”;
创建了几个对象?
一共创建了3个对象,一个str对象a,一个b对象,一个str1对象ab。
11、如何将字符串反转
一、提到字符串的反转,最先想到的应该是StringBuiler / StringBuffer的reverse()的方法,方便快捷。
public static String reverseStringBuilder(String s) {
StringBuilder sb = new StringBuilder(s);
String afterreverse = sb.reverse().toString();
return afterreverse;
}
二、通过String类的charAt()的方法来获取字符串中的每一个字符,然后将其拼接为一个新的字符串。
/**
* 该方法是通过charAt()方法获得每一个char的字符,i=0时获得第一个字符a然后赋值给reverse
* 此时reverse="a";i=1时获得第二个字符b然后加上reverse再赋值给reverse,此时reverse="ba";
* 以次类推
*/
public static String CharAtreverse(String s) {
int length = s.length();
String reverse = "";
for (int i=0; i<length; i++)
reverse = s.charAt(i) + reverse;
return reverse;
}
三、 通过String的toCharArray()方法可以获得字符串中的每一个字符串并转换为字符数组,然后用一个空的字符串从后向前一个个的拼接成新的字符串。
public static String reverseCharArray(String s) {
char[] array = s.toCharArray();
String reverse = "";
for (int i = array.length - 1; i >= 0; i--) {
reverse += array[i];
}
return reverse;
}
四、 通过递归的方式
public static String reverseRecursive(String s) {
int length = s.length();
if (length <= 1)
return s;
String left = s.substring(0, length / 2);
String right = s.substring(length / 2, length);
String afterReverse = reverse1(right) + reverse1(left);
return afterReverse;
}
12、String 类的常用方法都有哪些
- str.length()
获取字符串长度 - str.indexOf(String str)
从头开始查找str在字符串中第一次出现的位置 - str.indexOf(String str,int fromIndex)
从下标fromIndex处开始开始查找str在字符串中第一次出现的位置 - str.lastIndexOf(String str)
从尾部开始查找str在字符串中最后一次出现的位置 - str.lastIndexOf(String str,int fromIndex)
从下标fromIndex处开始开始查找str在字符串中最后一次出现的位置 - str.charAt(int index)
查找字符串中下标为index位置出现的字符串 - str.substring(fromIndex,endIndex)
从fromIndex处开始截取到endIndex处结束,不包含下标为endIndex的字符 - str.substring(fromIndex)
从fromIndex处开始截取到最后 - 开始位置字符:str.startWith(String str1),返回Boolean值;
结束位置字符:str.endWith(String str1),返回Boolean值; - int num = str.compareTo(String str1)
对字符串内容按字典顺序进行大小比较,通过返回的整数值指明当前字符串与参数字符串的大小关系。若当前对象比参数大则返回正整数,反之返回负整数,相等返回0。 - int num = str.compareToIgnore(String str1)
和compareTo()方法类似,忽略大小写。 - boolean b = str.equals(Object obj);
比较当前字符串和参数字符串,在两个字符串相等的时候返回true,否则返回false。 - boolean b = str.equalsIgnoreCase(Object obj)
和equals方法类似,忽略大小写。 - str2 = str.concat(String str1)
用concat连接两个字符城成为一个新的字符串。 - 小写转大写:str.toUpperCase()
大写转小写:str.toLowerCase() - str.replace(oldStr,newStr)
用新的字符代替就得字符 - str.trim()
清除字符串str两侧的空字符串 - str.split(String str1)
用str1将字符串str分割成数组
数组:
一维数组:
-
数组的概念:
数组是相同数据类型元素的集合。 数组本身是引用数据类型,但它可以存储基本数据类型和引用数据类型。
-
数组的声明:
语法一:数据类型[] 数组名;
语法二:数据类型 数组名[];
-
数组的创建:
语法一:数据类型[] 数组名 = new 数据类型[长度];(动态初始化,先定义数组,然后初始化数据)
语法二:数据类型[] 数组名 = new 数据类型[]{初始化数据};(静态初始化,定义数组的同时,初始化数据)
语法三:数据类型[] 数组名 = {初始化数据};(静态初始化)
-
数组的特点:
1)数组一旦定义,长度不变。
2)数组存储的数据类型相同。
3)数组的长度length是属性。
4)数组在内存中处于连续的内存空间。
-
数组的访问:
数组的访问需要使用索引,数组的索引从0开始,最大值为数组的长度减一。
语法:数组名[索引]
-
数组的遍历
//for循环遍历数组 @Test public void test_05(){ int[] arr = {1,2,3,4,5,6}; for(int i= 0; i < arr.length; i++){ System.out.println(arr[i]); } }
//while循环遍历数组 @Test public void test_06(){ int[] arr = {1,2,3,4,5,6}; int i = 0; while(i < arr.length){ System.out.println(arr[i]); i++; } }
-
数组中常见的异常:
1)ArrayIndexOutOfBoundsException:数组索引越界异常。
2)NullPointerException:空指针异常。
-
获取数组中的最大值
//获取数组中的最大值 @Test public void test_07(){ int[] arr = {89,66,8,5,99,3,5,66,78,100}; int max = arr[0]; for (int i = 1; i < arr.length; i++){ if(arr[i] > max){ max = arr[i]; } } System.out.println("数组中的最大值为:"+max); }
-
数组的逆序
原理:首尾元素来能量交换。
逆序不等于反向遍历
//数组的逆序 @Test public void test_13(){ //定义数组 int[] arr = {1,2,3,4,5}; int temp; for(int i = 0;i <=(arr.length/2); i++){ temp = arr[i]; //数组的长度比索引大1,所以要减去1 arr[i] = arr[arr.length-1-i]; arr[arr.length-1-i] = temp; } //遍历输出逆序后的数组 for (int i = 0; i < arr.length;i++){ System.out.println(arr[i]); } }
二、二维数组
-
二维数组的声明
语法:数据类型[] [] [][]数组名;
-
二维数组的创建
语法一:数据类型[] [] 数组名 = new int[二维数组的长度][一维数组的长度];
语法二:数据类型[] [] 数组名 = new int[二维数组的长度][];(此种方式定义的一维数组需要另外分配长度,其长度可以不相同)
语法三:数据类型[] [] 数组名 = new int[][]{{初始化数据},{初始化数据}};(每一对花括号是一个一维数组,使用逗号分割。此种方式定义的一维数组的长度可以不相同)
-
选择排序
原理:数组中的每个元素和其他的元素比较大小,根据排序要求交换位置。
思路:使用for的嵌套循环。外层循环控制比较次数,内循环控制每次比较的元素。
//选择排序 @Test public void test_14(){ //定义一维数组 int[] arr = {56,98,36,33,88,77,88,4,6,99}; //最后剩余的一个元素不用比较,所以要减1 for (int i = 0; i < arr.length - 1; i++){ for (int j = 1+i; j < arr.length; j++){ if(arr[i] > arr[j]){ int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } } //遍历输出数组元素 for (int i = 0; i < arr.length;i++){ System.out.print(arr[i]+" "); } }
-
冒泡排序
原理:每轮冒泡,数组中相邻元素两两比较,根据排序要求交换位置。外层循环控制冒泡的次数,内层循环控制每次比较的元素。
-
//冒泡排序 @Test public void test_15(){ //定义一维数组 int[] arr = {88,66,23,44,5,6,77,99,52,-9}; for(int i=0;i<arr.length-1;i++){ for(int j = 0;j<arr.length-i-1;j++){ if(arr[j] > arr[j+1]){ int temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } } //遍历输出数组元素 for (int i = 0; i < arr.length;i++){ System.out.print(arr[i]+" "); } }
数组的相关注意事项:
- 数组不是集合,它只能保存同种类型的多个原始类型或者对象的引用。数组保存的仅仅是对象的引用,而不是对象本身。
- 在数组声明中包含数组长度永远是不合法的!如:int[5] arr; 。因为,声明的时候并没有实例化任何对象,只有在实例化数组对象时,JVM才分配空间,这时才与长度有关。
- 数组声明的两种形式:一、int[] arr; 二、int arr[]; 推荐使用前者,这符合Sun的命名规范,而且容易了解到关键点,这是一个int数组对象,而不是一个int原始类型。
- 在数组构造的时候必须指定长度,因为JVM要知道需要在堆上分配多少空间。反例:int[] arr = new int[];
- 原始类型数组元素的默认值。对于原始类型数组,在用new构造完成而没有初始化时,JVM自动对其进行初始化。默认值:byte、short、 int、long–0 float–0.0f double–0.0 boolean–false char–’"u0000’。(无论该数组是成员变量还是局部变量)
- 对象类型数组中的引用被默认初始化为null。如:Car[] myCar = new Car[10]; 相当于从myCar[0]到myCar[9]都这样被自动初始化为myCar[i] = null;
- 对象类型的数组虽然被默认初始化了,但是并没有调用其构造函数。也就是说:Car[] myCar = new Car[10];只创建了一个myCar数组对象!并没有创建Car对象的任何实例!
- 多维数组的构造。float[][] ratings = new float[9][]; 第一维的长度必须给出,其余的可以不写,因为JVM只需要知道赋给变量ratings的对象的长度。
- Java有数组下标检查,当访问超出索引范围时,将产生ArrayIndexOutOfBoundsException运行时异常。注意,这种下标检查不是在编译时刻进行的,而是在运行时!也就是说int[] arr = new int[10]; arr[100] = 100; 这么明显的错误可以通过编译,但在运行时抛出!
-
面向对象编程
-
抽象类必须要有抽象方法吗?
抽象类可以没有抽象方法,但是如果你的一个类已经声明成了抽象类,即使这个类中没有抽象方法,它也不能再实例化,即不能直接构造一个该类的对象。
如果一个类中有了一个抽象方法,那么这个类必须声明为抽象类,否则编译通不过。- 抽象类中必须有关键字abstract来修饰
- 抽象类中可以不包含抽象方法
- 如果一个类包含抽象方法,那么这个类一定是抽象类
-
抽象类和普通类的区别
-
普通类可以去实例化调用;抽象类不能被实例化,因为它是存在于一种概念而不非具体。
-
抽象方法只需要声明,而不需要实现,抽象类中可以允许普通方法有主体。
-
含有抽象方法的类必须声明为抽象类。
-
抽象的子类必须实现抽象类中所有的抽象方法,否则这个子类也是抽象类。
-
普通类和抽象类都可以被继承,但是抽象类被继承后子类必须重写继承的方法,除非子类也是抽象类。
-
抽象类可以有构造函数,被继承时子类必须继承父类一个构造方法,抽象方法不能被声明为静态
-
抽象方法不能够被static修饰为静态-----static修饰的方法在类实例化之前,就已经分配了内存,而抽象方法不能够被实例化,前后矛盾,故抽象方法不能够和static同时存在。
-
抽象方法不能够被private修饰-----private修饰后只能在本类中使用,而抽象方法一定在要子类中重写,故产生矛盾。
-
抽象方法不能被final修饰------final修饰后子类中不能够重写
-
抽象类是否可以有构造函数?答案是可以有。抽象类的构造函数用来初始化抽象类的一些字段,而这一切都在抽象类的派生类实例化之前发生。不仅如此,抽象类的构造函数还有一种巧妙应用:就是在其内部实现子类必须执行的代码
-
-
抽象类和接口的区别
-
抽象类 和 接口 都是用来抽象具体对象的. 但是接口的抽象级别最高
-
抽象类可以有具体的方法 和属性, 接口只能有抽象方法和不可变常量
-
抽象类主要用来抽象类别,接口主要用来抽象功能.
-
抽象类中,且不包含任何实现,派生类必须覆盖它们。接口中所有方法都必须是未实 现的。
-
抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
-
抽象类要被子类继承,接口要被类实现。
-
接口方法默认的是public,不可以使用其他的修饰符;抽象方法可以使用public,protected,default等
-
接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现
-
接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
-
抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法, 那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。
-
抽象方法只能申明,不能实现,接口是设计的结果 ,抽象类是重构的结果
-
抽象类里可以没有抽象方法
-
如果一个类里有抽象方法,那么这个类只能是抽象类
-
抽象方法要被实现,所以不能是静态的,也不能是私有的。
-
接口可继承接口,并可多继承接口,但类只能单继承。
-
-
final关键字在java中的作用
- final关键字修饰类,表明这个类不能被继承,final类中所有的方法均会被隐式的指定为final方法,也就是说final类没有子类。
- final修饰方法,则表明该方法所在类的子类不能够重写该方法,(但是可以重载)如果父类中final修饰的方法同时访问权限控制为private,那么将会导致子类不能直接继承父类中的此方法。
- final修饰变量,final修饰基本数据类型时,这个值在初始化之后将永远不会改变;final修饰引用数据类型时(即修饰一个对象时),在初始化之后,将永远指向一个内存地址,不可修改,但是该内存地址中保存的是对象信息,可以修改。
-
抽象类能用final修饰吗?
Java抽象类不可以被 final修饰抽象类需要被继承才能使用,而被final修饰的类无法被继承,所以abstract和final是不能共存的。
-
static关键字在java中的作用
static可以修饰变量,方法和静态代码块
-
被static修饰的成员变量叫做静态变量,也叫做类变量,说明这个变量是属于这个类的,而不是属于是对象,没有被static修饰的成员变量叫做实例变量,说明这个变量是属于某个具体的对象的。 jvm在加载的时候,会把静态变量直接加载到方法区,而不是堆中,无论哪一个方法修改了此变量,此变量都会发生变化,可以让对象共享。
注意:在类的生命周期内,静态变量只会被初始化一次。类会在首次被“主动使用”时执行初始化,为类(静态)变量赋予正确的初始值。在Java代码中,一个正确的初始值是通过类变量初始化语句或者静态初始化块给出的。而我们这里所说的主动使用 包括:
- 创建类的实例
- 调用类的静态方法
- 使用类的非常量静态字段
- 调用Java API中的某些反射方法
- 初始化某个类的子类
- 含有main()方法的类启动时
-
static修饰的方法,可以使用“类名.方法名”来直接访问成员变量,避免了new来创建对象,然后再通过对象来访问成员变量的步骤,避免了资源的消耗。
-
静态代码块指 Java 类中的 static{} 代码块,主要用于初始化类,为类的静态变量赋初始值。静态代码块的特点如下:
- 静态代码块类似于一个方法,但它不可以存在于任何方法体中。
- Java 虚拟机在加载类时会执行静态代码块,如果类中包含多个静态代码块,则 Java 虚拟机将按它们在类中出现的顺序依次执行它们,每个静态代码块只会被执行一次。
- 静态代码块与静态方法一样,不能直接访问类的实例变量和实例方法,而需要通过类的实例对象来访问。
注意:
类初始化的执行顺序:(静态代码块和静态变量执行顺序由书写顺序决定)
父类静态变量
父类静态代码块
子类静态变量
子类静态代码块
父类普通变量
父类普通代码块
父类构造函数
子类普通变量
子类普通代码块
子类构造函数
-
-
普通方法和构造方法的区别?
- 构造方法名必须和类名相同,没有返回值,甚至连viod都没有。
- 类中必定有构造方法,若不写,系统自动添加无参构造方法。接口不允许被实例化,所以接口中没有构造方法。
- 不能被static、final、synchronized、abstract和native修饰。
- 构造方法在初始化对象时自动执行,一般不能显式地直接调用.当同一个类存在多个构造方法时,java编译系统会自动按照初始化时最后面括号的参数个数以及参数类型来自动一一对应。完成构造函数的调用。
- 但是如果我们定义了构造方法,系统就不会再为我们提供默认的无参构造方法了。这个时候想使用无参构造方法必须自己定义。因为实例化对象时,不一定会输入参数,那么没有无参的构造方法,又不输入参数,就会出错。
- 构造方法需要用public来修饰,若使用private,在其他类中无法将其实例化。
- 构造方法是可以重载的。
- 构造方法创建对象时,需要调用new来创建。
8.重载和重写的区别
方法重载:
1. 在同一个类中。 2. 方法名相同,参数列表不同(参数顺序,个数,类型) 3. 方法返回值,访问修饰符随意。 4. 与方法的参数名无关。
方法重写:
- 在有继承关系的子类中。
- 方法名相同,参数列表相同(参数顺序,个数,类型),方法返回值相同。
- 访问修饰符,访问范围需要大于等于父类的访问权限。
- 与方法的参数名无关
- 不能抛出比父类声明更加宽泛的检查型异常。
重载是静态多态性的表现,在编译时起作用。
重写是动态多态性的表现,在运行时起作用。
-
权限修饰符
Java中有四种权限修饰符:
public > protected > (default) > private
同一个类(我自己) YES YES YES YES
同一个包(我邻居) YES YES YES
不同包子类(我儿子) YES YES
不同包非子类(陌生人) YES注意:
public 和 default可以修饰类,成员变量,成员方法,构造方法,但是private和protect不可以修饰类,其余的成员变量,成员变量,构造方法都可以修饰。
-
内部类(理解)
成员内部类:成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。不过要注意的是,当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,应该用
外部类.
this
.成员变量
外部类.this
.成员方法 来访问虽然成员内部类可以无条件地访问外部类的成员,而外部类想访问成员内部类的成员却不是这么随心所欲了。在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问:
class` `Circle { ``private` `double` `radius = ``0``;
public Circle(double`radius) { this.radius = radius; getDrawInstance().drawSahpe(); //必须先创建成员内部类的对象,再进行访问 } private` `Draw getDrawInstance() { return` `new` `Draw(); } class` `Draw { ``//内部类 public` `void` `drawSahpe() { System.out.println(radius); ``//外部类的private成员 } } }
成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如下:
//第一种方式:
Outter outter = newOutter();
Outter.Inner inner = outter.new`Inner(); //必须通过Outter对象来创建
//第二种方式:
Outter.Inner inner1 = outter.getInnerInstance();
内部类可以拥有private访问权限、protected访问权限、public访问权限及包访问权限。
局部外部类:局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。注意,局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。
匿名内部类:匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。
静态内部类:静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。
注意:成员内部类的引用方式必须为 Outter.Inner.
-
java语言的特性并描述
封装, 继承,多态
封装:
-
java语言可以将事务抽象成一个类,然后进行封装,将事务拥有的属性和动作进行隐藏,只保留所需要用到的方法。
-
封装的单一性原则,一个类把自己的一些方法和属性封装起来,其他的类并不会访问到这些方法,当这个类的内部逻辑发生变化以后,外部调用不会因为这个类内部的变化做一些对应的措施,他们只是调用开放的接口,并不需要担心类内部的实现。
继承:
java的类可以分为三种:类,抽象类,接口
1)类:使用class定义,类中不能有抽象方法;
类可以extends(继承)类,抽象类(子类必须实现抽象类中的所有抽象方法)并且类只能继承一个类或者抽象类。但是可以implement多个接口(类必须实现接口中的所有方法)
2)抽象类:使用abstract class定义,类中的抽象方法可有可无
抽象类可以extends类,抽象类(抽象类继承抽象类时,可以全部,部分或者完全不实现父类抽象方法)可以implement多个接口(可以全部,部分或者完全不实现接口方法)
3)接口:使用interface定义,接口中只有抽象方法
接口可以extends多个接口
4)子类在继承以后可以获得(1)父类非private的属性和方法(2)子类也可以在本类中添加自己的属性和方法,实现对父类的扩展(3)可以重写父类的方法
注意事项:构造函数不能够被继承,子类可以通过super()来显示的调用父类的构造函数;一旦确定了父子类的继承关系,那么编译器会自动的为子类调用父类的无参构造;如果父类没有定义无参构造,那么子类必须在构造函数的第一行代码上使用super()显示调用。(所有的类都默认拥有无参构造,如果说定义了其他有参构造,那么无参构造则自动失效)多态:“一个接口,多种形态”主要是有编译时多态(方法的重载)和运行时多态(继承中的方法的重写,向上造型)
向上造型:父类声明,子类创建.-
this表示对当前对象的引用,调用本类中的成员变量和成员方法;
-
this也可以用于构造函数之间的相互调用。
-
super表示子类对父类的调用,可以调用父类的成员变量和成员方法。
-
注意事项:this()和super()都是必须写在第一行,所以this()和super()是不可能同时出现的。
-
简述包装类和基本数据类型的区别?
- 基本数据类型的值都是存储在栈中,包装类型存储在堆中。
- 包装类是对象,包装类创建的对象,是拥有字段和方法的,可以使用api提供的一些有用的方法。更为强大。
- 基本数据类型是值传递,而包装类是引用传递。
- 向ArrayList,LinkedList中放数据的时候,只能放Object类型的,基本数据类型是放不进去的。
- 包装类的默认值都是null。
-
java中的IO流分为几种?
字符流 字节流
输入流 Writer OutPutStream
输出流 Reader InPutStream
数据传输的形式:字节流和字符流
异常在IO流中的处理办法:流对象的初始值为null,真正的初始化过程是在try块中
在关流之前要保证流对象不为null,为null的文件不能够close
无论关流是否成功,都要将流对象置为null,变成无用对象,等待系统收回,提高内存资源的利用效率
关流可能失败,如果失败的话,自带的缓冲区操作没有完成,则需要手动冲刷,否则会造成文件数据的滞留-
字节流和字符流的区别?
- 操作的单位不一致:一个是字节,一个是字符
- 字符流操作的是中文(文档);字节流操作的是文本,视频,音频,图片等
- 字符流中有直接可以写字符串的方法
- 字节流如果不关闭流也会写入文件;字符流必须经过刷新或者关闭才能写入文件
-
java中File类的常用方法及应用
creatNewFile()
在制定位置创建一个空文件,成功返回true,如果已经存在则返回false
mkdir()
在制定位置创建一个单级文件夹
mkdirs()
在制定位置创建一个多级文件夹
renameTo(File dest)
如果目标文件与源文件是在同一路径下,那么renameTo的作用是重命名,如果目标文件与源文件不在同一个路径下,那么renameTo的作用是剪切,而且不能操作文件夹
delete()
删除文件或者一个空文件夹,不能删除非空文件夹,返回值是布尔类型
deleteOnExit()
jvm退出时,删除文件或者文件夹,用于删除临时文件,无返回值
exists()
文件或者文件夹是否存在
isFile()
是否是一个文件,若不存在则返回false
isDierctory()
是否是一个目录,若不存在,始终为false
isHidden()
是否是隐藏的文件或者目录
isAbsolute()
测试此抽象路径是否是为绝对路径名
getName()
获取文件或者文件夹的名称
length()
获取文件的大小(字节数)
list()
返回目录下的文件或者目录名,包含隐藏文件
-