方法的重载与重写
区别点 | 重载 | 重写(覆写) |
英文 | Overloading | Overiding |
定义 | 方法名称相同,参数的类型或个数不同(与返回值没有关系,区分时只看名字和参数列表是否一样) | 方法名称、参数类型、返回值类型全部相同 |
对权限没有要求 | 被重写的方法不能拥有更严格的权限 | |
范围 | 发生在一个类中 | 发生在继承类中 |
2.方法的重载(overload):
JVM调用名字相同的方法时 ,以参数列表的不同区分同名方法。
定义:子类中出现了和父类相同的方法(父类中用private修饰的方法除外)
如果父类中有private修饰的变量或方法,子类也可以有 但此时不是继承而是子类自己的变量和方法。
权限
子类的权限不能比父类更低
如果父类的方法是静态的子类是不能继承的
因为静态方法从程序开始运行后就已经分配了内存,也就是说已经写死了。所有引用到该方法的对象(父类的对象也好子类的对象也好)所指向的都是同一块内存中的数据,也就是该静态方法。子类中如果定义了相同名称的静态方法,并不会重写,而应该是在内存中又分配了一块给子类的静态方法,没有重写这一说
如何定义重写:在Java程序中,类的继承关系可以产生一个子类,子类继承父类,它具备了父类所有的特征,继承了父类所有的方法和变量。子类可以定义新的特征,当子类需要修改父类的一些方法进行扩展,增大功能,程序设计者常常把这样的一种操作方法称为重写,也叫称为覆写或覆盖。重写体现了Java优越性,重写是建立在继承关系上,它使语言结构更加丰富。在Java中的继承中,子类既可以隐藏和访问父类的方法,也可以覆盖继承父类的方法。在Java中覆盖继承父类的方法就是通过方法的重写来实现的。
所谓方法的重写是指子类中的方法与父类中继承的方法有完全相同的返回值类型、方法名、参数个数以及参数类型。这样,就可以实现对父类方法的覆盖。例如:下面的代码实现了方法的重写。
class Person//定义父类
fpublic void print(){//父类中的方法
System.out.println( 父类Person的print方法! );
}
}
class Student extends Person//定义子类继承Person类
{public void print(){//方法的重写
System.out.println( 子类Student的print方法! );
}
}
public class 0verrideExampleO1
{public static void main(String args[])
{Student s=new Student();
S.print();
}
}
运行结果:子类Student的print方法!
可以看出,当子类重写了父类中的print()方法后,使用S调用的是子类的print()方法,父类中的print()方法被覆盖了。也就是说,如果现在子类将父类中的方法重写了,调用的时候肯定是调用被重写过的方法,那么如果现在一定要调用父类中的方法该怎么办呢?此时,通过使用.. super关键就可以实现这个功能,super关键字可以从子类访问父类中的内容,如果要访问被重写过的方法,使用“super.方法名(参数列表)”的形式调用。
例如:
Class Person
{public void print () {
System.out.println( 父类Person的print方法! );
}
}
class Student extends Person
{public void print () {
super.print(://访问父类中被子类覆写过的方法
System.out.println(" 子类Student的print方法!" );
}
}
public class OverrideExample02
{public static void main(String args[])
{Student s=new Student();
S.print();
}
}
运行结果:父类Person的print方法!
子类Student的print方法 !
如果要使用super关键字不一定非要在方法重写之后使用,
也可以明确地表示某个方法是从父类中继承而来的。使用super
只是更加明确的说,要从父类中查找,就不在子类查找了。
二、重写规则
在重写方法时,需要遵循以下的规则:
(一) 父类方法的参数列表必须完全与被子类重写的方法的参数列表相同,否则不能称其为重写而是重载。..
(二) 父类的返回类型必须与被子类重写的方法返回类型相同,否则不能称其为重写而是重载。..
(三) Java中规定,被子类重写的方法不能拥有比父类方法更加严格的访问权限。访问权限大小关系为:
编写过Java程序的人就知道,父类中的方法并不是在任何情况下都可以重写的,当父类中方法的访问权限修饰符为private时,该方法只能被自己的类访问,不能被外部的类访问,在子类是不能被重写的。如果定义父类的方法为public,在子类定义为private,程序运行时就会报错。例如:
class Person
{public void print()(//public访问权限
System.out.println( "父类Person的print方法! ");
}
}
Class Stedent extends Person
{private void print()(//重写方法降低了访问权限,错误
System.out.println( "子类Student的print方法!" );
}
}
(四) 由于父类的访问权限修饰符的限制一定要大于被子类重写方法的访问权限修饰符,而private权限最小。所以如果某一个方法在父类中的访问权限是private,那么就不能在子类中对其进行重写。如果重新定义,也只是定义了一个新的方法,不会达到重写的效果。
(五) 在继承过程中如果父类当中的方法抛出异常,那么在子类中重写父类的该方法时,也要抛出异常,而且抛出的异常不能多于父类中抛出的异常(可以等于父类中抛出的异常)。换句话说,重写方法一定不能抛出新的检查异常,或者比被重写方法声明更加宽泛的检查型异常。例如,父类的一个方法申明了一个检查异常IOException,在重写这个方法时就不能抛出Exception,只能抛出IOException的子类异常,可以抛出非检查异常。
同样的道理,如果子类中创建了一个成员变量,而该变量和
父类中的一个变量名称相同,称作变量重写或属性覆盖。但是此
概念一般很少有人去研究它,因为意义不大。
数组
一维数组
Arrays用法整理
本文将整理 java.util.Arrays 工具类比较常用的方法:
本文介绍的方法基于JDK 1.7 之上。
1. asList方法
@SafeVarargs
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
使用该方法可以返回一个固定大小的List,如:
List<String> stringList = Arrays.asList("Welcome", "To", "Java",
"World!");
List<Integer> intList = Arrays.asList(1, 2, 3, 4);
2. binarySearch方法
binarySearch方法支持在整个数组中查找,如:
int index = Arrays.binarySearch(new int[] { 1, 2, 3, 4, 5, 6, 7 }, 6);
以及在某个区间范围内查找, 如:
public static int binarySearch(int[] a, int fromIndex, int toIndex,
int key) {
rangeCheck(a.length, fromIndex, toIndex);
return binarySearch0(a, fromIndex, toIndex, key);
}
int index = Arrays.binarySearch(new int[] { 1, 2, 3, 4, 5, 6, 7 }, 1, 6, 6);
3. copyOf及copyOfRange方法
如
String[] names2 = { "Eric", "John", "Alan", "Liz" };
//[Eric, John, Alan]
String[] copy = Arrays.copyOf(names2, 3);
//[Alan, Liz]
String[] rangeCopy = Arrays.copyOfRange(names2, 2,
names2.length);
4. sort方法
String[] names = { "Liz", "John", "Eric", "Alan" };
//只排序前两个
//[John, Liz, Eric, Alan]
Arrays.sort(names, 0, 2);
//全部排序
//[Alan, Eric, John, Liz]
Arrays.sort(names);
另外,Arrays的sort方法也可以结合比较器,完成更加复杂的排序。
public static <T> void sort(T[] a, Comparator<? super T> c) {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c);
else
TimSort.sort(a, c);
}
Arrays常用方法
Arrays的toString方法
可以方便我们打印出数组内容。
如:
String[] names = { "Liz", "John", "Eric", "Alan" };
Arrays.sort(names);
System.out.println(Arrays.toString(names));
控制台将打印出 [Alan, Eric, John, Liz]
6. deepToString方法
如果需要打印二维数组的内容:
int[][] stuGrades = { { 80, 81, 82 }, { 84, 85, 86 }, { 87, 88, 89 } };
如果直接用
System.out.println(Arrays.toString(stuGrades));
那么得到的结果类似于
[[I@35ce36, [I@757aef, [I@d9f9c3]}
这个时候得用 deepToString 方法才能得到正确的结果[[80, 81, 82], [84, 85, 86], [87, 88, 89]]
System.out.println(Arrays.deepToString(stuGrades));
7. equals方法
使用Arrays.equals来比较1维数组是否相等。
String[] names1 = { "Eric", "John", "Alan", "Liz" };
String[] names2 = { "Eric", "John", "Alan", "Liz" };
System.out.println(Arrays.equals(names1, names2));
8. deepEquals方法
Arrays.deepEquals能够去判断更加复杂的数组是否相等。
int[][] stuGrades1 = { { 80, 81, 82 }, { 84, 85, 86 }, { 87, 88, 89 } };
int[][] stuGrades2 = { { 80, 81, 82 }, { 84, 85, 86 }, { 87, 88, 89 } };
System.out.println(Arrays.deepEquals(stuGrades1, stuGrades2));
9. fill方法
int[] array1 = new int[8];
Arrays.fill(array1, 1);
//[1, 1, 1, 1, 1, 1, 1, 1]
System.out.printl
注:与C/C++不同,在java中声明时不允许指定数组元素个数,如int a[2]是错误的。
扩展:main方法中的数组。是用来接收键盘输入的数
数组的默认值和数组的数据类型默认值一致;
数组是引用类型,创建数组时,得到堆中存储地址。故直接打印数组名(对象名),得到的是一个内存地址。
引用类型的创建和基本类型的赋值是一个类似的过程,引用类型的初始值是null。
只要是new出来的东西都是不同的对象有不同的存储地址;
public class overload {
public static void main(String[] args) {
int[] a=new int[3];
int[] d=new int[3];
int[] b=a;
int[] c=new int[2];
System.out.println(a);
System.out.println(b);
System.out.println(c);
System.out.println(d);
}
}
打印结果
[I@15db9742
[I@15db9742
[I@6d06d69c
[I@7852e922
从结果可知,只要是new出来的东西都是不同的对象有不同的存储地址;而且数值是引用类型,把一个数组赋值给另一个数组,则两个数组的引用是一样的,打印的地址也一样。
空引用异常
调用空对象时,可以使用,但是一旦调用空对象的属性或方法就会报错,空引用异常
如
int[] a=null;
System.out.println(a);//不报错
int[] a=null;
System.out.println(a.length);//报错
Exception in thread "main" java.lang.NullPointerException
数组常用的方法
static Object | |
static boolean | getBoolean(Object array, int index) |
static byte | |
static char | |
static double | getDouble(Object array, int index) |
static float | getFloat(Object array, int index) |
static int | |
static int | |
static long | |
static short | getShort(Object array, int index) |
static Object | newInstance(Class<?> componentType, int... dimensions) |
static Object | newInstance(Class<?> componentType, int length) |
static void | set(Object array, int index, Object value) |
static void | setBoolean(Object array, int index, boolean z) |
static void | setByte(Object array, int index, byte b) |
static void | setChar(Object array, int index, char c) |
static void | setDouble(Object array, int index, double d) |
static void | setFloat(Object array, int index, float f) |
static void | setInt(Object array, int index, int i) |
static void | setLong(Object array, int index, long l) |
static void | setShort(Object array, int index, short s) |
二维数组
定义方式
定义三行三列的二位数组
int[][] num1=new int[3][3];
num1[0][0]=1;
num1[0][1]=2;
num1[0][2]=3;
for(int i=0;i<num1.length;i++){
for(int j=0;j<num1.length;j++){
System.out.println(num1[i][j]);
}
}
定义不规则二位数组
int[][] num2=new int[3][];
num2[0]=new int[3];
num2[1]=new int[3];
num2[2]=new int[3];
num2[0][2]=1;
num2[1][0]=2;
num2[2][1]=3;
for(int i=0;i<num2.length;i++){
for(int j=0;j<num2.length;j++){
System.out.println(num2[i][j]);
}
}
定义二位时直接赋值
int num3[][]={{1,2,3},{4,5,6},{789}};
System.out.println(num3[0][1]);
System.out.println(num3[1][2]);
System.out.println(num3[2][0]);
定义一个不规则二位数组赋值
int num4[][]=new int[][]{{11,22},{33,44,55},{66,77,88,99}};
System.out.println(num4[0][0]);
System.out.println(num4[1][2]);
System.out.println(num4[2][3]);
}
}
二维数组注意事项
int[][] a=new int[3][2];
System.out.println(a[1]);
打印:[I@15db9742
int[][] a=new int[3][];
System.out.println(a[1]);
Null
第一个输出:因为a[1]就是二维数组的第二个元素及一维数组,打印为其地址,第二次是因为二位没有声明一维数组的个数,故打印的是一个空对象为null
地址格式 [I@15db9742 @之前的为数据(后者引用类型)类型 之后的为其地址
1.静态代码块 2.构造代码块3.构造方法的执行顺序是1>2>3;明白他们是干嘛的就理解了。
1.静态代码块:是在类的加载过程的第三步初始化的时候进行的,主要目的是给类变量赋予初始值。
2.构造代码块:是独立的,必须依附载体才能运行,Java会把构造代码块放到每种构造方法的前面,用于实例化一些共有的实例变量,减少代码量。
3.构造方法:用于实例化变量。
1是类级别的,2、3是实例级别的,自然1要优先23.
在就明白一点:对子类得主动使用会导致对其父类得主动使用,所以尽管实例化的是子类,但也会导致父类的初始化和实例化,且优于子类执行
构造方法
构造方法可以重载,且没有返回值和void
创建对象时,有参数调用的是有参的构造方法,无参数调用的是无参的构造方法
前提是 这两个方法存在
如果类中,没有构造方法,系统会调用默认的构造方法,但是默认的构造方法是无参数的,而且方法体中没有语句
如果类中自定义一个或多种构造方法,那么java将不再使用默认的构造方法
注:如果不自定义构造方法,那么构造方法是无参数的 如果创建对象时,使用了带参数的,则会报错,要想带参数,需要自己在定义一个有参数的构造方法。反过来如果自己只定义了一个有参数的构造方法,那么创建对象时使用无参数创建对象也会报错
关于构造方法,以下几点要注意:
1.对象一建立,就会调用与之相应的构造函数,也就是说,不建立对象,构造函数时不会运行的。
2.构造函数的作用是用于给对象进行初始化。
3.一个对象建立,构造函数只运行一次,而一般方法可以被该对象调用多次。
构造方法的方法名必须与类名一样。
构造方法没有返回类型,也不能定义为void,在方法名前面不声明方法类型。
构造方法不能作用是完成对象的初始化工作,他能够把定义对象时的参数传递给对象的域。
构造方法不能由编程人员调用,而要系统调用。
构造方法可以重载,以参数的个数,类型,或排序顺序区分
在java中,子类构造器会默认调用super()(无论构造器中是否写有super()),用于初始化父类成员,同时当父类中存在有参构造器时,必须提供无参构造器,子类构造器中并不会自动继承有参构造器,仍然默认调用super(),使用无参构造器。因此,一个类想要被继承必须提供无参构造器。
单例模式
public class Sigleton2 {//饿汉式,就是上来就创建,然后直接返回对象
private static Sigleton2 s=new Sigleton2();
private Sigleton2(){
}
public static Sigleton2 getInstance(){
return s;
}
}
public class Sigleton {//懒汉式,上来不创建对象,判断为空时在创建对象
private static Sigleton s=null;
private Sigleton(){}
public static Sigleton getInstance(){
if(s==null){
s=new Sigleton();
}
return s;
}
}
线程安全的单例模式
出现非线程安全问题,是由于多个线程可以同时进入getInstance()方法,那么只需要对该方法进行synchronized的锁同步
public class MySingleton {
private static MySingleton instance = null;
private MySingleton(){}
public synchronized static MySingleton getInstance() {
try {
if(instance != null){//懒汉式
}else{
//创建实例之前可能会有一些准备性的耗时工作
Thread.sleep(300);
instance = new MySingleton();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return instance;
}
}
基于构造方法,1创建对象需要构造方法,把构造方法私有化
2.但是总要创建一个,故把创建对象的方法静态化,这样只有类才能调用,也就是只有自己能调用
代码块
构造代码块的作用是给对象进行初始化。
每创建一个对象就会执行一次
在构造代码块状定义一个静态的变量,初始化为0;便可以计算处该创建的对象的个数。
用于加载些文档程序资料啥的
{//构造代码块
}
关于构造代码块,以下几点要注意:
对象一建立就运行构造代码块了,而且优先于构造函数执行。这里要强调一下,有对象建立,才会运行构造代码块,类不能调用构造代码块的,而且构造代码块与构造函数的执行顺序是前者先于后者执行。
构造代码块与构造函数的区别是:构造代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化,因为构造函数是可以多个的,运行哪个构造函数就会建立什么样的对象,但无论建立哪个对象,都会先执行相同的构造代码块。也就是说,构造代码块中定义的是不同对象共性的初始化内容。
静态代码块
主要作用
7. 如果需要调用一个方法 或变量许多次,回着有限属性需要在使用类的时候就初始化 ,这时应该把这些变量和方法的调用放在静态代码块中。
静态语句块一般是用来初始化类的,如 jdbc的连接,通常不放在方法体中。不管类执行多少次,它都只在类第一次执行时执行一次
static {//静态代码块
}
关于静态代码块,要注意的是:
它是随着类的加载而执行,只执行一次,并优先于主函数。具体说,静态代码块是由类调用的。类调用时,先执行静态代码块,然后才执行主函数的。
静态代码块什么时候执行
也就是说静态代码块的执行是处在类加载的最后一个阶段“初始化”
.Class.forName()
Class.forName("com.test.MyClass1");这样调用在加载过程会执行静态代码块。
但是:不代表所有的forName()方法的调用都会执行“静态代码块”。
如下并不会执行静态代码块:
Class.forName("com.test.MyClass1",false,classLoader);
Class.forName() 在加载类后默认进行初始化,所以static被调用了。Boolean选项可以控制是否进行初始化。
ClassLoader.classLoader() 并没有初始化。Boolean选项可以控制是否链接。
静态代码块最常用的作用就是:给一个对象的属性初始化。
如果有些属性或方法在类调用或创建时必须实现,则应放到静态代码快中,如JDBC的连接属性,一些流的操作。
具体的说:
1.当调用一个类的静态变量时,这个类中的静态代码块会执行。【只有静态代码块会执行】
2.当调用一个 类的静态方法时,这个类中的静态代码块会执行。【只有静态代码块会执行】
3.当创建一个 类的一个实例时,这个类中的静态代码块、非静态代码块(也叫构造代码块)、创建实例的相应的构造方法都会执行。
静态代码块其实就是给类初始化的,而构造代码块是给对象初始化的。
静态代码块中的变量是局部变量,与普通函数中的局部变量性质没有区别。
一个类中可以有多个静态代码块
局部代码块
在方法中。随着方法的执行而执行 ,随着方法的结束而结束
public class Test{
staitc int cnt=6;
static{
cnt+=9;
}
public static void main(String[] args) {
System.out.println(cnt);
}
static{
cnt/=3;
}
}
运行结果:
5
Java类初始化顺序
## 对于一个类的情况
例子1:
public class HelloA {
public HelloA(){//构造函数
System.out.println("A的构造函数");
}
{//构造代码块
System.out.println("A的构造代码块");
}
static {//静态代码块
System.out.println("A的静态代码块");
}
public static void main(String[] args) {
}
}
运行结果:
A的静态代码块
例子2:
public class HelloA {
public HelloA(){//构造函数
System.out.println("A的构造函数");
}
{//构造代码块
System.out.println("A的构造代码块");
}
static {//静态代码块
System.out.println("A的静态代码块");
}
public static void main(String[] args) {
HelloA a=new HelloA();
}
}
运行结果:
A的静态代码块
A的构造代码块
A的构造函数
继承中的代码块和构造方法的执行顺序探索
只有静态构造代码快会夸顺序,其他按父类早于子类
本文讲述有关一个类的静态代码块,构造代码块,构造方法的执行流程问题。首先来看一个例子
/**
* Created by lili on 15/10/19.
*/
class Person{
static {
System.out.println("执行Person静态代码块");
}
{
System.out.println("执行Person构造代码块");
}
public Person(){
System.out.println("执行Person构造方法");
}
}
class Student extends Person{
static {
System.out.println("执行Student静态代码块");
}
{
System.out.println("执行Student构造代码块");
}
public Student(){
System.out.println("执行Student构造方法");
}
}
public class ExtendsStaticConstruct {
public static void main(String args[]){
Student student = new Student();
}
}
执行结果如下:
执行Person静态代码块
执行Student静态代码块
执行Person构造代码块
执行Person构造方法
执行Student构造代码块
执行Student构造方法
Process finished with exit code 0
说明程序的执行顺序是:
静态代码块 ---》 构造代码块 ----》 构造方法
执行流程解释:
new的是Student类,但是Student是继承子Person类,所以在加载Student类时先要加载Person类,而静态的内容是随着类的加载而加载的,所以先打印“执行Person静态代码块”,后执行Student的静态代码块。
加载完类后,开始走main方法,执行Student构造方法上,即初始化Student,但是Student是继承自Person,必须先初始化Person,所以先调用Person类的空参构造方法进行初始化,但是Person类的构造代码块优先于构造方法执行,所以Person类的构造代码块先执行,构造方法后执行。然后再执行Student类的构造代码块和构造方法。
类与对象
面向对象的五大基本原则
单一职责原则(SRP)
开放封闭原则(OCP)
里氏替换原则(LSP)
依赖倒置原则(DIP)
接口隔离原则(ISP)
同一个包中类名不能相同
构造方法可以重载,且没有返回值和void
创建对象时,有参数调用的是有参的构造方法,无参数调用的是无参的构造方法
前提是 这两个方法存在
如果类中,没有构造方法,系统会调用默认的构造方法,但是默认的构造方法是无参数的,而且方法体中没有语句
如果类中自定义一个或多种构造方法,那么java将不再使用默认的构造方法
注:如果不自定义构造方法,那么构造方法是无参数的 如果创建对象时,使用了带参数的,则会报错,要想带参数,需要自己在定义一个有参数的构造方法。反过来如果自己只定义了一个有参数的构造方法,那么创建对象时使用无参数创建对象也会报错
一个java文件只能有一个public类,而且和java文件名一样。
类加载的过程
a) 遇到一个新的类时,首先会到方法区去找class文件,如果没有找到就会去硬盘中找class文件,找到后会返回,将class文件加载到方法区中,在类加载的时候,静态成员变量会被分配到方法区的静态区域,非静态成员变量分配到非静态区域,然后开始给静态成员变量初始化,赋默认值,赋完默认值后,会根据静态成员变量书写的位置赋显示值,然后执行静态代码。当所有的静态代码执行完,类加载才算完成。
对象的创建
a) 遇到一个新类时,会进行类的加载,定位到class文件
b) 对所有静态成员变量初始化,静态代码块也会执行,而且只在类加载的时候执行一次
c) New 对象时,jvm会在堆中分配一个足够大的存储空间
d) 存储空间清空,为所有的变量赋默认值,所有的对象引用赋值为null
e) 根据书写的位置给字段一些初始化操作
f) 调用构造器方法(没有继承)
Java对象创建内存模型
AVA的JVM的内存可分为3个区:堆(heap)、栈(stack)和方法区(method)
堆区:
1.存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)
2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身
栈区:方法的执行在栈里面
1.每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中
2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
方法区:
1.又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。
2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。
为了更清楚地搞明白发生在运行时数据区里的黑幕,我们来准备2个小道具(2个非常简单的小程序)。
AppMain.java
public class AppMain
//运行时, jvm 把appmain的信息都放入方法区
{
public static void main(String[] args) //main 方法本身放入方法区。
{
Sample test1 = new Sample( " 测试1 " ); //test1是引用,所以放到栈区里, Sample是自定义对象应该放到堆里面
Sample test2 = new Sample( " 测试2 " );
test1.printName();
test2.printName();
}
}
Sample.java
public class Sample //运行时, jvm 把appmain的信息都放入方法区
{
/** 范例名称 */
private name; //new Sample实例后, name 引用放入栈区里, name 对象放入堆里
/** 构造方法 */
public Sample(String name)
{
this .name = name;
}
/** 输出 */
public void printName() //print方法本身放入 方法区里。
{
System.out.println(name);
}
}
OK,让我们开始行动吧,出发指令就是:“java AppMain”,包包里带好我们的行动向导图,Let’s GO!
系统收到了我们发出的指令,启动了一个Java虚拟机进程,这个进程首先从classpath中找到AppMain.class文件,读取这个文件中的二进制数据,然后把Appmain类的类信息存放到运行时数据区的方法区中。这一过程称为AppMain类的加载过程。
接着,Java虚拟机定位到方法区中AppMain类的Main()方法的字节码,开始执行它的指令。这个main()方法的第一条语句就是:
Sample test1=new Sample("测试1");
语句很简单啦,就是让java虚拟机创建一个Sample实例,并且呢,使引用变量test1引用这个实例。貌似小case一桩哦,就让我们来跟踪一下Java虚拟机,看看它究竟是怎么来执行这个任务的:
1、 Java虚拟机一看,不就是建立一个Sample实例吗,简单,于是就直奔方法区而去,先找到Sample类的类型信息再说。结果呢,嘿嘿,没找到@@, 这会儿的方法区里还没有Sample类呢。可Java虚拟机也不是一根筋的笨蛋,于是,它发扬“自己动手,丰衣足食”的作风,立马加载了Sample类, 把Sample类的类型信息存放在方法区里。
2、 好啦,资料找到了,下面就开始干活啦。Java虚拟机做的第一件事情就是在堆区中为一个新的Sample实例分配内存, 这个Sample实例持有着指向方法区的Sample类的类型信息的引用。这里所说的引用,实际上指的是Sample类的类型信息在方法区中的内存地址, 其实,就是有点类似于C语言里的指针啦~~,而这个地址呢,就存放了在Sample实例的数据区里。
3、 在JAVA虚拟机进程中,每个线程都会拥有一个方法调用栈,用来跟踪线程运行中一系列的方法调用过程,栈中的每一个元素就被称为栈帧,每当线程调用一个方 法的时候就会向方法栈压入一个新帧。这里的帧用来存储方法的参数、局部变量和运算过程中的临时数据。OK,原理讲完了,就让我们来继续我们的跟踪行动!位 于“=”前的Test1是一个在main()方法中定义的变量,可见,它是一个局部变量,因此,它被会添加到了执行main()方法的主线程的JAVA方 法调用栈中。而“=”将把这个test1变量指向堆区中的Sample实例,也就是说,它持有指向Sample实例的引用。
参数传递
形参和实参
可变参数要放在参数列表的最后
可变参数的表示方法:int…a;可变参数本质是一个数组,故使用时和数组元素的调用一样,需要遍历
形式参数可被视为local variable。形参和局部变量一样都不能离开方法。都只有在方法内才会发生作用,也只有在方法中使用,不会在方法外可见。
B: 对于形式参数只能用final修饰符,其它任何修饰符都会引起编译器错误。但是用这个修饰符也有一定的限制,就是在方法中不能对参数做任何修改。 不过一般情况下,一个方法的形参不用final修饰。只有在特殊情况下,那就是:方法内部类。 一个方法内的内部类如果使用了这个方法的参数或者局部变量的话,这个参数或局部变量应该是final。
形参的本质是一个名字,不占用内存空间。实参的本质是一个变量,已经占用内存空间
基本类型的参数不会改变变量的真实值,但是引用类型(除了string、包装类型除外)会改变,因为引用类型存储的是地址,传递会改变变量的引用,从而导致变量值的改变。
因为:string其本质是final类型的字符串数组。
Java中的字符串常量池
Java中字符串对象创建有两种形式,一种为字面量形式,如String str = "droid";,另一种就是使用new这种标准的构造对象的方法,如String str = new String("droid");,这两种方式我们在代码编写时都经常使用,尤其是字面量的方式。然而这两种实现其实存在着一些性能和内存占用的差别。这一切都是源于JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池。
工作原理
字符串常量池存放的是对象引用,不是对象。在Java中,对象都创建在堆内存中。
当代码中出现字面量形式创建字符串对象时,JVM首先会对这个字面量进行检查,如果字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回,否则新的字符串对象被创建,然后将这个引用放入字符串常量池,并返回该引用。
举例说明
字面量创建形式
1 | String str1 = "droid"; |
JVM检测这个字面量,这里我们认为没有内容为droid的对象存在。JVM通过字符串常量池查找不到内容为droid的字符串对象存在,那么会创建这个字符串对象,然后将刚创建的对象的引用放入到字符串常量池中,并且将引用返回给变量str1。
如果接下来有这样一段代码
1 | String str2 = "droid"; |
同样JVM还是要检测这个字面量,JVM通过查找字符串常量池,发现内容为”droid”字符串对象存在,于是将已经存在的字符串对象的引用返回给变量str2。注意这里不会重新创建新的字符串对象。
验证是否为str1和str2是否指向同一对象,我们可以通过这段代码
1 | System.out.println(str1 == str2); |
结果为true。
使用new创建
1 | String str3 = new String("droid"); |
当我们使用了new来构造字符串对象的时候,不管字符串常量池中有没有相同内容的对象的引用,新的字符串对象都会创建。因此我们使用下面代码测试一下,
1 2 | String str3 = new String("droid"); System.out.println(str1 == str3); |
结果如我们所想,为false,表明这两个变量指向的为不同的对象。
intern
对于上面使用new创建的字符串对象,如果想将这个对象的引用加入到字符串常量池,可以使用intern方法。
调用intern后,首先检查字符串常量池中是否有该对象的引用,如果存在,则将这个引用返回给变量,否则将引用加入并返回给变量。
1 2 | String str4 = str3.intern(); System.out.println(str4 == str1); |
输出的结果为true。
疑难问题
前提条件?
字符串常量池实现的前提条件就是Java中String对象是不可变的,这样可以安全保证多个变量共享同一个对象。如果Java中的String对象可变的话,一个引用操作改变了对象的值,那么其他的变量也会受到影响,显然这样是不合理的。
引用 or 对象
字符串常量池中存放的时引用还是对象,这个问题是最常见的。字符串常量池存放的是对象引用,不是对象。在Java中,对象都创建在堆内存中。
由于字符串常量池存在于堆内存中的永久代,适用于Java8之前。我们通过设置永久代一个很小的值来进行验证。如果字符串对象存在字符串常量池中,那么必然抛出java.lang.OutOfMemoryError permgen space错误。
1 | java -XX:PermSize=6m TestMain ~/Videos/why_to_learn.mp4 |
运行证明程序没有抛出OOM,其实这个不能很好的证明存储的是对象还是引用。
但是这个至少证明了字符串的实际内容对象char[]不存放在字符串常量池中。既然这样的话,其实字符串常量池存储字符串对象还是字符串对象的引用反而不是那么重要。但个人还是倾向于存储的为引用。
优缺点
字符串常量池的好处就是减少相同内容字符串的创建,节省内存空间。
如果硬要说弊端的话,就是牺牲了CPU计算时间来换空间。CPU计算时间主要用于在字符串常量池中查找是否有内容相同对象的引用。不过其内部实现为HashTable,所以计算成本较低。
GC回收?
因为字符串常量池中持有了共享的字符串对象的引用,这就是说是不是会导致这些对象无法回收?
首先问题中共享的对象一般情况下都比较小。据我查证了解,在早期的版本中确实存在这样的问题,但是随着弱引用的引入,目前这个问题应该没有了。
关于这个问题,可以具体了解这片文章interned Strings : Java Glossary
intern使用?
关于使用intern的前提就是你清楚自己确实需要使用。比如,我们这里有一份上百万的记录,其中记录的某个值多次为美国加利福尼亚州,我们不想创建上百万条这样的字符串对象,我们可以使用intern只在内存中保留一份即可。关于intern更深入的了解请参考深入解析String#intern。
总有例外?
你知道下面的代码,会创建几个字符串对象,在字符串常量池中保存几个引用么?
1 | String test = "a" + "b" + "c"; |
答案是只创建了一个对象,在常量池中也只保存一个引用。我们使用javap反编译看一下即可得知。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 17:02 $ javap -c TestInternedPoolGC Compiled from "TestInternedPoolGC.java" public class TestInternedPoolGC extends java.lang.Object{ public TestInternedPoolGC(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: return
public static void main(java.lang.String[]) throws java.lang.Exception; Code: 0: ldc #2; //String abc 2: astore_1 3: return |
看到了么,实际上在编译期间,已经将这三个字面量合成了一个。这样做实际上是一种优化,避免了创建多余的字符串对象,也没有发生字符串拼接问题。关于字符串拼接,可以查看Java细节:字符串的拼接。
创建一个对象都在内存中做了什么事情?
1:先将硬盘上指定位置的Person.class文件加载进内存。
2:执行main方法时,在栈内存中开辟了main方法的空间(压栈-进栈),然后在main方法的栈区分配了一个变量p。
3:在堆内存中开辟一个实体空间,分配了一个内存首地址值。new
4:在该实体空间中进行属性的空间分配,并进行了默认初始化。
5:对空间中的属性进行显示初始化。
6:进行实体的构造代码块初始化。
7:调用该实体对应的构造函数,进行构造函数初始化。()
8:将首地址赋值给p ,p变量就引用了该实体。(指向了该对象)
This代表当前对象
public class name {
String name;
public void talk(String name){
System.out.println(name);
}
public void talk(){
System.out.println(name);
}
public void talk1(String name){
System.out.println(this.name);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
name n=new name();
n.name="wangzhihua";
n.talk("hello");
n.talk1("kll");
n.talk();
}
}
输出:5
hello
wangzhihua
wangzhihua
匿名对象:只使用一次时可以创建匿名对象,当次使用时会浪费内存空间
类的 to String方法
toString是规定对象属性输出的样式
一切类都可以同toString取打印的输出,比如打印一个学生的基本信息
但是必须重写toString
如果定义一个对象,直接使用 toString会输出对象的内存地址
如果定义一个实体对象,会输出一串字符串,故toString需要重写,当然系统帮我们重写好了可以直接用,规定对象的输出形式,调用后用输出语句输出 例如、
System.out.println(o1.toString());
在Java中每个类都默认继承Object类,除非声明继承某个类。而Object类中有一个叫做toString的方法。该方法返回的是该Java对象的内存地址经过哈希算法得出的int类型的值在转换成十六进制。这个输出的结果可以等同的看作Java对象在堆中的内存地址。
例如:
public class Test1 {
public static void main(String[] args){
Object o1 = new Object();
System.out.println(o1.toString());
}
}
结果:java.lang.Object@7852e922
2.如果我们定义一个实体类,返回的结果又会是什么呢?
public class Test1 {
public static void main(String[] args){
Person p1 = new Person("king", 20);
System.out.println(p1.toString());
}
}
class Person{
String name;
int age;
Person(String name,int age){
this.name = name;
this.age = age;
}
}
结果:<span style="font-family:Arial, Helvetica, sans-serif;">com.cal.toString.Person@4e25154f很显然业务逻辑不太合适,不应该是一串看不懂的数字,而应该是符合逻辑的东西</span>
java中abstract详解
Abstract(抽象)可以修饰类、方法
如果将一个类设置为abstract,则此类必须被继承使用。此类不可生成对象,必须被继承使用。 Abstract可以将子类的共性最大限度的抽取出来,放在父类中,以提高程序的简洁性。 Abstract虽然不能生成对象,但是可以声明,作为编译时类型,但不能作为运行时类型。 Final和abstract永远不会同时出现。
当abstract用于修饰方法时,此时该方法为抽象方法,此时方法不需要实现,实现留给子类覆盖,子类覆盖该方法之后方法才能够生效。
注意比较:
private void print(){};此语句表示方法的空实现。
Abstract void print(); 此语句表示方法的抽象,无实现。
如果一个类中有一个抽象方法,那么这个类一定为一个抽象类。 反之,如果一个类为抽象类,那么其中可能有非抽象的方法。
如果让一个非抽象类继承一个含抽象方法的抽象类,则编译时会发生错误。因为当一个非抽象类继承一个抽象方法的时候,本着只有一个类中有一个抽象方法,那么这个类必须为抽象类的原则。这个类必须为抽象类,这与此类为非抽象冲突,所以报错。
所以子类的方法必须覆盖父类的抽象方法。方法才能够起作用。
只有将理论被熟练运用在实际的程序设计的过程中之后,才能说理论被完全掌握!
为了实现多态,那么父类必须有定义。而父类并不实现,留给子类去实现。此时可将父类定义成abstract类。如果没有定义抽象的父类,那么编译会出现错误。
Abstract和static不能放在一起,否则便会出现错误。(这是因为static不可被覆盖,而abstract为了生效必须被覆盖。)
访问权限
作用域 当前类 同一package 子孙类 其他package
public √ √ √ √
protected √ √ √ ×
friendly √ √ × ×
private √ × × ×
没有时默认为friendly,如构造函数等~
Private可以可以修饰内部类,但是不可以修饰外部类。局部变量不可以用范围修饰符修饰,因为局部变量的范围已经确定
protected对于子女、朋友来说,就是public的,可以自由使用,没有任何限制,而对于其他的外部class,protected就变成private。而且protected在夸包的情况下,如果是子类也能使用自己的放法和属性。
Java继承
子类覆盖父类要遵循“两同两小一大”
“两同”即方法名相同,形参列表相同
“两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等。
(注:看到有网友有这样的疑问,父类方法返回值是double,子类修改成int为什么不行呢?
这是因为返回值类型更大或者更小,是对于同一类型而言的。也就是说,返回值的类型需要有继承关系才去考虑大小这个概念。类型不同,肯定不是方法重写)
“一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。
注意:覆盖方法和被覆盖方法要么都是类方法,要么都是实例方法,不能一个是类方法一个是实例方法,否则编译出错。
注:子类不仅可以重写父类的方法也可以重载父类的方法
用父类引用创建对象的好处:比如把 鸡蛋 鸭蛋放到一个数组里面,就可以创建鸡蛋 鸭蛋是用他们的父类引用创建
常见的 List list=new ArrayList();
两个转型
多态案例及成员访问特点
成员变量,静态方法都看左边;非静态方法:编译看左边,运行看右边。
例如
class A {
public int func1(int a, int b) {
return a - b;
}
}
class B extends A {
public int func1(int a, int b) {
return a + b;
}
}
public class ChildClass {
public static void main(String[] args) {
A a = new B();
B b = new B();
System.out.println("Result=" + a.func1(100, 50));
System.out.println("Result=" + b.func1(100, 50));
}
}
输出 都是150 因为编译能通过 但是运行时看右边
向上转型:父类的引用指向子类
People p=new teacher();
此时p不能调用子类自己的属性和方法(既不是重写父类的,除了重在其他父类都不能用),只有通过向下转型才能使用
向下转型
父类强转成子类
Teacher t=(teacher)p;
此时p可以使用子类的方法和属性
在创建子类的对象时,无论是否有参数,jvm会首先执行父类的构造方法,然后再执行子类的构造方法,如果是多级继承,会先执行最顶级父类的构造方法,然后依次执行各级个子类的构造方法。
java中静态属性和静态方法可以被继承,但是没有被重写(overwrite)而是被隐藏.
原因:
1). 静态方法和属性是属于类的,调用的时候直接通过类名.方法名完成对,不需要继承机制及可以调用。如果子类里面定义了静态方法和属性,那么这时候父类的静态方法或属性称之为"隐藏"。如果你想要调用父类的静态方法和属性,直接通过父类名.方法或变量名完成,至于是否继承一说,子类是有继承静态方法和属性,但是跟实例方法和属性不太一样,存在"隐藏"的这种情况。
2). 多态之所以能够实现依赖于继承、接口和重写、重载(继承和重写最为关键)。有了继承和重写就可以实现父类的引用指向不同子类的对象。重写的功能是:"重写"后子类的优先级要高于父类的优先级,但是“隐藏”是没有这个优先级之分的。
3). 静态属性、静态方法和非静态的属性都可以被继承和隐藏而不能被重写,因此不能实现多态,不能实现父类的引用可以指向不同子类的对象。非静态方法可以被继承和重写,因此可以实现多态。
对于执行父类构造方法的问题。可以归纳为两种情况
一旦在子类的构造方法中使用super,子类的super所在的构造方法将调用父类的构造方法 如果自己里面有别的语句也继续执行。
1,如果父类有无参构造方法,子类会默认调用此方法(除非super显示调用父类有参)为隐式调用。
2,在子类中使用super显示调用父类有参了,或者this调用
自身其他构造方法,则调用父类有参和自身其他构造方法
3.说白了就是(在没有super/this显示调用情况下)子类会默认隐式调用父类无参构造方法,如果父类没有无参构造方法,并且子类没有super显示调用,那么将返回隐式调用
如果存在多继承那么上述调用规则一直执行上级到Object类无参为止!子类与父类构造方法没有必然的联系,如果想继承父类有参构造方法就必须在子类中覆盖该构造方法并且super显示调用,父类子类中都默认有无参构造方法并且方法中默认有super调用,写了有参构造方法则擦除没有显示出来的无参构造方法。
如果子类构造器没有显示地调用超类的构造器,则将自动地调用超类默认(没有参数)的构造器。如果超类没有不带参数的构造器,并且在子类的构造器中有没有显示地调用超类的其他构造器,则Java编译器将报告错误。也就是子类必须调用父类构造方法的一种。使用super调用构造器的语句必须是子类构造器的第一条语句。
静态类
它们仅包含静态成员。
它们不能被实例化。
它们是密封的。
它们不能包含实例构造函数。
因此创建静态类与创建仅包含静态成员和私有构造函数的类大致一样。私有构造函数阻止类被实例化。
优点
使用静态类的优点在于,编译器能够执行检查以确保不致偶然地添加实例成员。编译器将保证不会创建此类的实例。
静态类是密封的,因此不可被继承。静态类不能包含构造函数,但仍可声明静态构造函数以分配初始值或设置某个静态状
为什么,静态类不能实现接口?
接口是面向对象的东东
实例化就是创建对象,静态类都不能创建对象了,实现接口还有意义吗?
抽象类
抽象方法是一种特殊的方法:它只有声明,而没有具体的实现。抽象方法的声明格式为:
1 | abstract void fun(); |
注 | :不能用final和abstract同时修饰一个方法。 |
抽象方法必须用abstract关键字进行修饰。如果一个类含有抽象方法,则称这个类为抽象类,抽象类必须在类前用abstract关键字修饰。因为抽象类中含有无具体实现的方法,所以不能用抽象类创建对象。Static不能修饰类,故也不能修饰抽象方法。
抽象关键字abstract和哪些不可以共存?final , private , static
1 2 3 | [public] abstract class ClassName { abstract void fun(); } |
从这里可以看出,抽象类就是为了继承而存在的,如果你定义了一个抽象类,却不去继承它,那么等于白白创建了这个抽象类,因为你不能用它来做任何事情。对于一个父类,如果它的某个方法在父类中实现出来没有任何意义,必须根据子类的实际需求来进行不同的实现,那么就可以将这个方法声明为abstract方法,此时这个类也就成为abstract类了。
包含抽象方法的类称为抽象类,但并不意味着抽象类中只能有抽象方法,它和普通类一样,同样可以拥有成员变量和普通的成员方法。注意,抽象类也有构造方法。
抽象类的成员特点:
成员变量:既可以是变量,也可以是常量。
构造方法:有。
用于子类访问父类数据的初始化。
成员方法:既可以是抽象的,也可以是非抽象的。
抽象类和普通类的主要有三点区别:
1)抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。
2)抽象类不能用来创建对象;
3)如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。
如果子类是抽象类,那么既可以重写父类的抽象方法也可以继承抽象方法,重写抽象方法就是加上方法体,并且把abstract去掉。
4)抽象类可以有抽象方法也可以没有抽象方法。也可以投普通方法。
接口
· 扩展:
1、接口中可以有非抽象的方法,比如default方法(Java 1.8)。
2、接口中可以有带方法体的方法。(Java 1.8)
3、接口中的方法默认是public的。
发表于 2016-07-13 15:29:56 回复(0)
接口,英文称作interface,在软件工程中,接口泛指供别人调用的方法或者函数。从这里,我们可以体会到Java语言设计者的初衷,它是对行为的抽象。在Java中,定一个接口的形式如下:
1 2 3 | [public] interface InterfaceName {
} |
接口只含有常量和抽象方法,没有普通方法。但是要注意,接口中的变量会被隐式地指定为public static final变量(并且只能是public static final变量,用private修饰会报编译错误),而方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,比如private、protected、static、 final等修饰会报编译错误),并且接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。从这里可以隐约看出接口和抽象类的区别,接口是一种极度抽象的类型,它比抽象类更加“抽象”,并且一般情况下不在接口中定义变量。
要让一个类遵循某组特地的接口需要使用implements关键字,具体格式如下:
1 2 | class ClassName implements Interface1,Interface2,[....]{ } |
可以看出,允许一个类遵循多个特定的接口。如果一个非抽象类遵循了某个接口,就必须实现该接口中的所有方法。对于遵循某个接口的抽象类,可以不实现该接口中的抽象方法。
接口(Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合。接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。继承多个接口用逗号隔开。
如果一个类只由抽象方法和全局常量组成,那么这种情况下不会将其定义为一个抽象类。只会定义为一个接口,所以接口严格的来讲属于一个特殊的类,而这个类里面只有抽象方法和全局常量,就连构造方法也没有。
对于接口,里面的组成只有抽象方法和全局常量,所以很多时候为了书写简单,可以不用写public abstract 或者public static final。并且,接口中的访问权限只有一种:public,即:定义接口方法和全局常量的时候就算没有写上public,那么最终的访问权限也是public,注意不是default。以下两种写法是完全等价的:
interface A{
public static final String MSG = "hello";
public abstract void print();
}
等价于
interface A{
String MSG = "hello";
void print();
抽象类和接口的区别
abstract class 和 interface 有什么区别?
解答:声明方法的存在而不去实现它的类被叫做抽象类(abstract class),。不能创建 abstract 类的实例。然而可以创建一个变量,其类型是一个抽象类,并让它指向具体子类的一个实例。不能有抽象构造函数或抽象静态方法。Abstract 类的子类为它们父类中的所有抽象方法提供实现,否则它们也是抽象类。取而代之,在子类中实现该方法。知道其行为的其它类可以在类中实现这些方法。接口(interface)是抽象类的变体。新型多继承性可通过实现这样的接口而获得。接口中的所有方法都是抽象的,所有成员变量都是 public static final 的。一个类可以实现多个接口,当类实现接口时,必须实现接口的所有方法。抽象类在 Java 语言中表示的是一种单继承的关系,对于 interface 来说则不然,并不要求 interface 的实现者和 interface 定义在概念本质上是一致的,仅仅是实现了interface 定义的契约而已;抽象类中可以定义自己的成员变量,也可以包含非抽象的方法,而在接口中只能有静态的常量,所有方法必须是抽象的;实现抽象类时可以只实现其中的部分方法,而要是实现一个接口的话就必须实现这个接口中的所有抽象方法
1.语法层面上的区别
1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;
2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。
2.设计层面上的区别
1)抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。举个简单的例子,飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类Airplane,将鸟设计为一个类Bird,但是不能将 飞行 这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。此时可以将 飞行 设计为一个接口Fly,包含方法fly( ),然后Airplane和Bird分别根据自己的需要实现Fly这个接口。然后至于有不同种类的飞机,比如战斗机、民用飞机等直接继承Airplane即可,对于鸟也是类似的,不同种类的鸟直接继承Bird类即可。从这里可以看出,继承是一个 “是不是”的关系,而 接口 实现则是 “有没有”的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。
2)设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。什么是模板式设计?最简单例子,大家都用过ppt里面的模板,如果用模板A设计了ppt B和ppt C,ppt B和ppt C公共的部分就是模板A了,如果它们的公共部分需要改动,则只需要改动模板A就可以了,不需要重新对ppt B和ppt C进行改动。而辐射式设计,比如某个电梯都装了某种报警器,一旦要更新报警器,就必须全部更新。也就是说对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。
下面看一个网上流传最广泛的例子:门和警报的例子:门都有open( )和close( )两个动作,此时我们可以定义通过抽象类和接口来定义这个抽象概念:
1 2 3 4 | abstract class Door { public abstract void open(); public abstract void close(); } |
或者:
1 2 3 4 | interface Door { public abstract void open(); public abstract void close(); } |
但是现在如果我们需要门具有报警alarm( )的功能,那么该如何实现?下面提供两种思路:
1)将这三个功能都放在抽象类里面,但是这样一来所有继承于这个抽象类的子类都具备了报警功能,但是有的门并不一定具备报警功能;
2)将这三个功能都放在接口里面,需要用到报警功能的类就需要实现这个接口中的open( )和close( ),也许这个类根本就不具备open( )和close( )这两个功能,比如火灾报警器。
从这里可以看出, Door的open() 、close()和alarm()根本就属于两个不同范畴内的行为,open()和close()属于门本身固有的行为特性,而alarm()属于延伸的附加行为。因此最好的解决办法是单独将报警设计为一个接口,包含alarm()行为,Door设计为单独的一个抽象类,包含open和close两种行为。再设计一个报警门继承Door类和实现Alarm接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | interface Alram { void alarm(); }
abstract class Door { void open(); void close(); }
class AlarmDoor extends Door implements Alarm { void oepn() { //.... } void close() { //.... } void alarm() { //.... } } |
内部类
意义:内部类可以大大弥补Java在多重继承上的不足。另外匿名内部类可以方便的实现闭包。静态内部类可以带来更好的代码聚合,提高代码可维护性。
在类中声明的类叫内部类,包含内部类的类叫外嵌类
内部类和外嵌类有如下关系:
外嵌类的成员变量和方法在内部类中依然有效,并且内部类的方法也可以调用外嵌类的方法
在内部类不可以声明类变量和类方法 ,但是外嵌类可以用内部类声明的对象作为自己的成员。
内部类仅供外嵌类使用,别的类不可以用内部类声明对象。
如何创建成员内部类的对象?
外部类.内部类 对象名 = new 外部类().new 内部类();
outclass.innerclass b=new outclass().new innerclass(); 成员内部类中如何调用外部类中同名的对象?
外部类名.this.变量名、
This代表内部类自己的
不加this的就近
5.静态内部类中可以定义非静态变量么?成员内部类可以定义静态变量么?
静态内部类是可以的,成员内部类不能定义静态变量
6.外部类如何方问静态内部类中的成员
静态成员:外部类名.内部类名.静态变量
非静态: new 外部类名.内部类名().非静态变量
7.匿名内部类的写法:
new 接口名/类名(){
实现或重写的方法
}.方法名();
public class Demo1{
public static void main(String[] args){
OuterClass oc = new OuterClass();//创建外部类实例
OuterClass.InnerStaticClass isc = new OuterClass.InnerStaticClass();//静态,内部
OuterClass.InnerClass ic = oc.new InnerClass();//非静态,内部
oc = null;
}
}
成员内部类
成员内部类也是最普通的内部类,它是外部类的一个成员,所以他是可以无限制的访问外围类的所有成员属性和方法,尽管是private的,但是外围类要访问内部类的成员属性和方法则需要通过内部类实例来访问。
在成员内部类中要注意两点:
1. 成员内部类中不能存在任何static的变量和方法
2. 成员内部类是依附于外围类的,所以只有先创建了外围类才能够创建内部类
方法内部类
不能用修饰符修饰
方法内部类定义在外部类的方法中,局部内部类和成员内部类基本一致,只是它们的作用域不同,方法内部类只能在该方法中被使用,出了该方法就会失效。 对于这个类的使用主要是应用与解决比较复杂的问题,想创建一个类来辅助我们的解决方案,到那时又不希望这个类是公共可用的,所以就产生了局部内部类。成员内部类:可以访问外部类中的所有成员,包括静态和私有
成员内部类中不能定义静态的成员内部类调外部同名:外部类名.this.成员
静态内部类
关键字static可以修饰成员变量、方法、代码块,其实它还可以修饰内部类,使用static修饰的内部类我们称之为静态内部类。静态内部类与非静态内部类之间存在一个最大的区别,我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围内,但是静态内部类却没有。没有这个引用就意味着:
静态内部类的创建是不需要依赖于外围类,可以直接创建
静态内部类不可以使用任何外围类的非static成员变量和方法,而内部类则都可以
static:
外部类调用静态内部类中的成员:
静态成员:外部类名.内部类名.静态成员
费静态成员:new 外部类名.内部类名().非静态成员
匿名内部类
匿名内部类其实就是一个没有名字的方法内部类,所以它符合方法内部类的所有约束,初次之外,还有一些地方需要注意:
匿名内部类是没有访问修饰符的。
匿名内部类必须继承一个抽象类或者实现一个接口
匿名内部类中不能存在任何静态成员或方法
匿名内部类是没有构造方法的,因为它没有类名。
一般使用匿名内部类的场景是,要继承或实现的接口只有一个抽象方法。
本质:是一个实现了接口或继承了类的一个对象
new 接口名或者类名(){
//实现或重写方法
}.方法名();
多态
多态概述
某一个事物,在不同环境下表现出来的不同状态。
举例:
中国人可以是人的类型。中国人 p = new 中国人();
同时中国人也是人类的一种,也可以把中国人称为人类。
人类 d = new 中国人();
多态前提和体现
有继承关系
有方法重写
有父类引用指向子类对象
JAVA中创建字符串的两种方式的区别
String x = "abcd";
String y = "abcd";
System.out.println(x==y);//true
System.out.println(x.equals(y));//true
第一种:创建的字符串其实是同一个,只是x与y的引用都是 “abcd”.
第二种,使用new,创建了不同的对象
注:字符串常量相加还在常量池,一旦与字符串变量相加就存在堆中。
带包编译
没有包重新创建一个包,运行时加上包名
获取对象的全类名?
cn.edu360.day10.Person
Class clazz = p.getClass();
syso(clazz.getName());
equals和hashCode的关系。
1.equal()相等的两个对象他们的hashCode()肯定相等,也就是用equal()对比是绝对可靠的。
2.hashCode()相等的两个对象他们的equal()不一定相等,也就是hashCode()不是绝对可靠的。
所以一般重写equals方法也要重写hashCode
有了equals为什么还要有hashCode
因为重写的equal()里一般比较的比较全面比较复杂,这样效率就比较低,而利用hashCode()进行对比,
则只要生成一个hash值进行比较就可以了,效率很高,
那么hashCode()既然效率这么高为什么还要equal()呢?
equals用来比较的是两个对象的内容是否相等,由于所有的类都是继承自java.lang.Object类的,所以适用于所有对象,
如果没有对该方法进行覆盖的话,调用的仍然是Object类中的方法,而Object中的equals方法返回的却是==的判断。
如何克隆一个对象
* 1. 在类中重写clone方法,目的是可以在其他类中调用,
* 重写的方法中直接使用super调用Object的clone(),并抛出异常
*
* 2. 在需要克隆的地方,使用对象来调用clone方法,会返回一个Object类型的对象
* 这个对象实际上就是克隆出来的副本
*
* 3. 重写克隆方法的类,一定要实现一个接口:Cloneable
*
* 4. 可以把克隆出来的对象向下转型成原来的类型
*
* 5. clone方法是一个浅拷贝方法
大数操作
正常情况下一个整数最多只能放在long类型之中,但是如果现在有如下的一个数字:
1111111111111111111111111111111111111111111111111
根本就是无法保存的,所以为了解决这样的问题,在java中引入了两个大数的操作类:
操作整型:BigInteger
操作小数:BigDecimal
当然了,这些大数都会以字符串的形式传入。
BigInteger
如果在操作的时候一个 BigDecimal
使用此类可以完成大的小数操作,而且也可以使用此类进行精确的四舍五入,这一点在开发中经常使用。
对于不需要任何准确计算精度的程序可以直接使用float或double完成,但是如果需要精确计算结果,则必须使用BigDecimal类。
控制小数点后的位数;setScale
BigDecimal mData = new BigDecimal(d).setScale(n, BigDecimal.ROUND_HALF_UP);
n是小数点后的尾数
d是要控制的小数
data是用来接收保留相应小数点后的数
BigDecimal或BigInteger传入数据时尽量使用字符串,因为浮点型数存在不精确问题,可能会倒着得到一大串小数。
System类
java.lang.Object
————java.lang.System
public final class System extends Object
System 类包含一些有用的类字段和方法。*它不能被实例化。
在 System 类提供的设施中,有标准输入、标准输出和错误输出流;对外部定义的属性和环境变量的访问;
加载文件和库的方法;还有快速复制数组的一部分的实用方法。
public static final InputStream in()
“标准”输入流。此流已打开并准备提供输入数据。
通常,此流对应于键盘输入或者由主机环境或用户指定的另一个输入源。
public static final PrintStream out()
“标准”输出流。此流已打开并准备接受输出数据。
通常,此流对应于显示器输出或者由主机环境或用户指定的另一个输出目标。
对于简单独立的 Java 应用程序,编写一行输出数据的典型方式是:
System.out.println(data)
public static void gc()
运行垃圾回收器。
调用 gc 方法暗示着 Java 虚拟机做了一些努力来回收未用对象,以便能够快速地重用这些对象当前占用的内存。
当控制权从方法调用中返回时,虚拟机已经尽最大努力从所有丢弃的对象中回收了空间。
调用 System.gc() 实际上等效于调用:
Runtime.getRuntime().gc()
在Object类中
protected void finalize()
当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
子类重写 finalize 方法,以配置系统资源或执行其他清除。
public static void exit(int status)
终止当前正在运行的 Java 虚拟机。
参数用作状态码;
根据惯例,非 0 的状态码表示异常终止。
该方法调用 Runtime 类中的 exit 方法。该方法永远不会正常返回。
调用 System.exit(n) 实际上等效于调用:
Runtime.getRuntime().exit(n)
public static long currentTimeMillis()
次方法经常用于程序的性能的分析,计算程序运行时间
返回以毫秒为单位的当前时间。
注意,当返回值的时间单位是毫秒时,值的粒度取决于底层操作系统,并且粒度可能更大。
例如,许多操作系统以几十毫秒为单位测量时间。
返回:
当前时间与协调世界时 1970 年 1 月 1 日午夜之间的时间差(以毫秒为单位测量)。
public static void arraycopy(Object src,int srcPos, Object dest,int destPos,int length)
从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。
参数:
src - 源数组。
srcPos - 源数组中的起始位置。
dest - 目标数组。
destPos - 目标数据中的起始位置。
length - 要复制的数组元素的数量。
常用正则表达式
规则 | 正则表达式语法 |
一个或多个汉字 | ^[\u0391-\uFFE5]+$ |
邮政编码 | ^[1-9]\d{5}$ |
QQ号码 | ^[1-9]\d{4,10}$ |
邮箱 | ^[a-zA-Z_]{1,}[0-9]{0,}@(([a-zA-z0-9]-*){1,}\.){1,3}[a-zA-z\-]{1,}$ |
用户名(字母开头 + 数字/字母/下划线) | ^[A-Za-z][A-Za-z1-9_-]+$ |
手机号码 | ^1[3|4|5|8][0-9]\d{8}$ |
URL | ^((http|https)://)?([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$ |
18位身份证号 | ^(\d{6})(18|19|20)?(\d{2})([01]\d)([0123]\d)(\d{3})(\d|X|x)?$
|
JAVA中正则表达式匹配,替换,查找,切割的方法
一.介绍:
正则表达式在java.util.regex里面,具体的各种用法可以查找api手册
当我们面对一个具体的要求时,可以从左到右逐个分析,分析出现的次数,等等。
二. 常用格式
点.要转义\\.因为店在正则表达式里面是任何字符的意思。
\\s+ 表示一个或多个空格
其中:^表示以此正则表达式开始 $是以此正则表达式结尾
正则表达式的() [] {}有不同的意思。
() 是为了提取匹配的字符串。表达式中有几个()就有几个相应的匹配字符串。
(\s*)表示连续空格的字符串。
[]是定义匹配的字符范围。比如 [a-zA-Z0-9] 表示相应位置的字符要匹配英文字符和数字。[\s*]表示空格或者*号。
{}一般用来表示匹配的长度,比如 \s{3} 表示匹配三个空格,\s[1,3]表示匹配一到三个空格。
(0-9) 匹配 '0-9′ 本身。 [0-9]* 匹配数字(注意后面有 *,可以为空)[0-9]+ 匹配数字(注意后面有 +,不可以为空){1-9} 写法错误。
[0-9]{0,9} 表示长度为 0 到 9 的数字字符串。
正则表达式[\w]+,\w+,[\w+] 三者有何区别:
[\w]+和\w+没有区别,都是匹配数字和字母下划线的多个字符;
[\w+]表示匹配数字、字母、下划线和加号本身字符;
[]
表示数组而非排列,即不按固定次序位置排列;
在[]内的字符可以任意次序出现。
[ABC]+
可以匹配"AAABBBCCC,BBBAAACCC,BACCBACAACBAC,...",不是一定按固定A....B....C...的次序排列。
[\w./-+]+
是匹配\w [0-9a-zA-Z_] 或 . 或 / 或 - 或 + 字符;
在[./-+]内均表示字符本身;
在[]+外表示{1,}至少1次或多次;
在[.]内点,不是任意字符的意思,就是匹配点.字符本身,点.可以不需要加反斜杠\.。
在[]内特殊字符,表示匹配特殊字符本身,不需要加反斜杠,
在[]外特殊字符,表示匹配特殊字符本身,必须要加反斜杠。
1 Email地址:^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$
2 域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?
3 InternetURL:[a-zA-z]+://[^\s]* 或 ^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$
4 手机号码:^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$
5 电话号码("XXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"和"XXXXXXXX):^(\(\d{3,4}-)|\d{3.4}-)?\d{7,8}$
6 国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7}
7 身份证号:
15或18位身份证:^\d{15}|\d{18}$
15位身份证:^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$
8 中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字)
9 IP地址:\d+\.\d+\.\d+\.\d+ (提取IP地址时有用)
三、校验字符的表达式
1 汉字:^[\u4e00-\u9fa5]{0,}$
2 英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$
3 长度为3-20的所有字符:^.{3,20}$
4 由26个英文字母组成的字符串:^[A-Za-z]+$
5 由26个大写英文字母组成的字符串:^[A-Z]+$
6 由26个小写英文字母组成的字符串:^[a-z]+$
7 由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$
8 由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}$
9 中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$
10 中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$
11 可以输入含有^%&',;=?$\"等字符:[^%&',;=?$\x22]+
12 禁止输入含有~的字符:[^~\x22]+
四.特殊用法
1.替换文档里面某个字符串
String str="张三和王四是中国人";
String regex2="[(张三)(王四)]";//一个*代替一个字
String regex3="(张三)";//一个字代替一个括号里面的内容
String str2=str.replaceAll(regex2,"*");
System.out.println(str2);
String str3=str.replaceAll(regex3,"*");
2.分组替换,如替换手机号的中间四位
idCard.replaceAll("(\\d{4})\\d{10}(\\w{4})","$1*****$2");
4304*****7733
$1、$2、……表示正则表达式里面第一个、第二个、……括号
但是 对应括号内的内容不替换
2. 查找比如超找英文作文中单个字符的单词
使用 Pattern Matcher
String str4="gello hh jjjnihao good god dog mmm";
Pattern a=Pattern.compile("\\b[a-zA-Z]{3}\\b");
Matcher m=a.matcher(str4);
while (m.find()) {
System.out.println(m.group());
}
}
正则表达式的查找;主要是用到String类中的split();
String str;
str.split();方法中传入按照什么规则截取,返回一个String数组
常见的截取规则:
str.split("\\.")按照.来截取
str.split(" ")按照空格截取
String reg = " +";//按照多个空格来进行切割
str.split("cc+")按照c字符来截取,2个c或以上
str.split((1)\\.+)按照字符串中含有2个字符或以上的地方截取(1)表示分组为1
截取的例子;
按照分组截取;截取的位置在两个或两个以上的地方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | String str = "publicstaticccvoidddmain"; //对表达式进分组重用 String ragex1="(.)\\1+"; String[] ss = str.split(ragex1); for(String st:ss){ System.out.println(st); } //按照两个cc+来截取 String ragex = " "; //切割 String strs = "publicstaticccvoidddmain"; String ragexs = "cc+"; String[] s = strs.split(ragexs); for(String SSSS :s){ System.out.println(SSSS); } System.out.println("=-========="); |
正则表达式中的替换;
语法定义规则;
1 | String s =str.replaceAll(ragex, newstr); |
字符串中的替换是replace();
将4个或4个以上的连续的数字替换成*
1 2 3 4 5 6 | // 替换 String str="wei232123jin234"; String ragex = "\\d{4,}"; String newstr = "*"; String s =str.replaceAll(ragex, newstr); System.out.println(s); |
将重复的字符串换成一个*
1 2 3 4 5 | String str ="wwweiei222222jjjiiinnn1232"; String ragex ="(.)\\1+"; String newStr ="*"; String s = str.replaceAll(ragex, newStr); System.out.println(s); |
将 我...我...要..要.吃...吃...饭 换成 我要吃饭
1 2 3 4 5 6 7 8 9 10 11 12 | String str = "我...我...要..要.吃...吃...饭"; String regex = "\\.+"; String newStr = ""; str=test(str, regex, newStr); regex = "(.)\\1+"; newStr = "$1"; test(str, regex, newStr); public static String test(String str, String regex, String newStr) { String str2 = str.replaceAll(regex, newStr); System.out.println(str2); return str2; } |
正则表达式的字符串的获取
1,根据定义的正则表达式创建Pattern对象
2,使用Pattern对象类匹配
3,判断是否为true
4,加入到组
例子;
1 2 3 4 5 6 7 8 9 | String str = "public static void main(String[] args)" + " public static void main(String[] args)public static void main(String[] args)"; String ragex = "\\b[a-zA-Z]{4,5}\\b"; Pattern p =Pattern.compile(ragex); Matcher m = p.matcher(str); while(m.find()){ String s = m.group(); System.out.println(s); } ^与$的用法 |
^ :表示以什么开头,例如:^1[a-z]和1[a-z] ,1b符合两个正则表达式,但是c1b符合第二个表达式,不符合第一个表达式,^表示字符串必须用给定的表达式开头,前面不能再有任何字符。
$:表示已什么结尾,例如:1[a-z]$和1[a-z],字符1b符合两个表达式,但是1bc只符合第二个表达式,第一个表达式只匹配1+字母结尾的字符串,后头不能再有任何字符
字符类运算符的优先级如下所示,按从最高到最低的顺序排列:
1 | 字面值转义 | \x |
2 | 分组 | [...] |
3 | 范围 | a-z |
4 | 并集 | [a-e][i-u] |
5 | 交集 | [a-z&&[aeiou]] |
组和捕获
捕获组可以通过从左到右计算其开括号来编号。例如,在表达式 ((A)(B(C))) 中,存在四个这样的组:
1 | ((A)(B(C))) |
2 | \A |
3 | (B(C)) |
4 | (C) |
小括号()、中括号[]、大括号的区别
1>. 小括号():匹配小括号内的字符串,表示一个组或一个整体,如果小括号在中括号里面,表示分组,单用时表示整体,可以是一个,也可以是多个,常跟“|”(或)符号搭配使用,是多选结构的
示例1:string name = "way2014"; regex:(way|zgw) result:结果是可以匹配出way的,因为是多选结构,小括号是匹配字符串的
示例2:string text = "123456789"; regex:(0-9)result:结果是什么都匹配不到的,它只匹配字符串"0-9"而不是匹配数字, [0-9]这个字符组才是匹配0-9的数字
2>.中括号[]:匹配字符组内的字符,比如咱们常用的[0-9a-zA-Z.*?!]等,在[]内的字符都是字符,不是元字符,比如“0-9”、“a-z”这中间的“-”就是连接符号,表示范围的元字符,如果写成[-!?*(]这样的话,就是普通字符
示例1: string text = "1234567890"; regex:[0-9] result:结果是可以匹配出字符串text内的任意数字了,像上边的【或符号“|”在字符组内就是一个普通字符】
示例2:string text = "a|e|s|v"; regex:[a|e|s] result:结果就是匹配字符a、e、|三个字符,这个跟(a|e|s)有区别的,区别就是(a|e|s)匹配的是a、e、s三个字符的随意一个,三个中的任意一个,这是的|是元字符
3>.大括号{}:匹配次数,匹配在它之前表达式匹配出来的元素出现的次数,{n}出现n次、{n,}匹配最少出现n次、{n,m}匹配最少出现n次,最多出现m次
Java 读写Properties配置文件
1.Properties类与Properties配置文件
Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对的形式来保存属性集。不过Properties有特殊的地方,就是它的键和值都是字符串类型。
注:配置文件需要主要空格,可以加上空格显示器
2配置文件的读取方式
Java 开发中,需要将一些易变的配置参数放置再 XML 配置文件或者 properties 配置文件中。然而 XML 配置文件需要通过 DOM 或 SAX 方式解析,而读取 properties 配置文件就比较容易。
介绍几种读取方式:
1、基于ClassLoder读取配置文件
注意:该方式只能读取类路径下的配置文件,有局限但是如果配置文件在类路径下比较方便。
步骤:1在src下面创建一个文件,即是配置文件
2 properies p=new properties();
Class clazz=new 所在类名().getclass();
InputStream in =clazz.getClassLoader().getResourceAsStream("config/config.properties");
Properties properties = new Properties();
// 使用ClassLoader加载properties配置文件生成对应的输入流
InputStream in = PropertiesMain.class.getClassLoader().getResourceAsStream("config/config.properties");
// 使用properties对象加载输入流
properties.load(in);
//获取key对应的value值
properties.getProperty(String key);
2、基于 InputStream 读取配置文件
注意:该方式的优点在于可以读取任意路径下的配置文件
使用文件的绝对路径
Properties properties = new Properties();
// 使用InPutStream流读取properties文件
BufferedReader bufferedReader = new BufferedReader(new FileReader("E:/config.properties"));
properties.load(bufferedReader);
// 获取key对应的value值
properties.getProperty(String key);
3、通过 java.util.ResourceBundle 类来读取,这种方式比使用 Properties 要方便一些
1>通过 ResourceBundle.getBundle() 静态方法来获取(ResourceBundle是一个抽象类),这种方式来获取properties属性文件不需要加.properties后缀名,只需要文件名即可
properties.getProperty(String key);
//config为属性文件名,放在包com.test.config下,如果是放在src下,直接用config即可
ResourceBundle resource = ResourceBundle.getBundle("com/test/config/config");
String key = resource.getString("keyWord");
2>从 InputStream 中读取,获取 InputStream 的方法和上面一样,不再赘述
1 ResourceBundle resource = new PropertyResourceBundle(inStream);
注意:在使用中遇到的最大的问题可能是配置文件的路径问题,如果配置文件入在当前类所在的包下,那么需要使用包名限定,如:config.properties入在com.test.config包下,则要使用com/test/config/config.properties(通过Properties来获取)或com/test/config/config(通过ResourceBundle来获取);属性文件在src根目录下,则直接使用config.properties或config即可。
对象的序列化:
在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化
使用对象流进行对象的序列化
transient关键字
当某个字段被声明为transient后,默认序列化机制就会忽略该字段
注:自己定义的类必须实现Serializable接口,集合其他类也可以序列化。但是集合中的类必须实现Serializable接口。
二分查找:
Middle一定要放到while循环中
可以放到外面初始化 ,但是要几点在里面每次循环都要再求一次中间值
t mid = (low + high) / 2;
Java版
使用循环实现
public static int search(int[] seq, int v, int low, int high) {
while (low <= high) {
int mid = (low + high) / 2;
if (v == seq[mid]) {
return mid;
} else if (v > seq[mid]) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return Integer.MIN_VALUE;
}
使用递归实现
使用递归时 参数必须有左右角标
public static int search2(int[] seq, int v, int low, int high) {
if (low > high) {
return Integer.MIN_VALUE;
}
int mid = (low + high) / 2;
if (v == seq[mid]) {
return mid;
} else if (v > seq[mid]) {
return search2(seq, v, mid + 1, high);
} else {
return search2(seq, v, low, mid - 1);
}
}
快速排序
注意点:
第一:一定不能缺少判断角标的左右的关系 如果左角标>=右角标,则直接退出
while(left>=right){
return;
}
这个判断尽量写在int i=left;
int j=right;
int p=arr[left];
前面
第二:左右循环条件 右边找到的值大于基准点
左边找到的值小于基准点,且i<j
第三循环条件是>=和<=,等于号一定不能少。
最后:要交换大循环结束要交换中间值和开头的值,以及基准点值赋值给中间值
public static void QSort(int[] arr,int left,int right){
/**
* 上来应该判断,左交表和右角标的关系,如果满足left>=right,,说明只有一个元素 上来应该判断,左交表和右角标的关系,
* 如果满足left>=right,,说明只有一个元素 ,则直接结束方法
*/
while(left>=right){
return;
}
/**
* 如果满足有多个元素,则初始化左右指针 (区分好左右角标和左右指针的关系,)
* 左右角标的大小分别是0 和数组的长度,不会改变,但是左右指针会变
* */
int i=left;
int j=right;
int p=arr[left];
while(i<j){
/**
* 一定要先从右边比较,也就是先进行右边的while循环,没次j--,直到碰到右边元素比
* 基准点小的,跳出循环
*/
while(arr[j]>=p&&j>i){
j--;
}
/**
* 在进行左边的while循环,碰到左边元素比
* 基准点大的,跳出循环
*/
while(arr[i]<=p&&j>i){
i++;
}
/**
* 把上两步找出的元素进行交换
*/
int temp =arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
/**
* 把基准点的值和中间值进行交换。进行递归
*/
arr[left]=arr[i];
arr[i]=p;
QSort(arr, left, i-1);
QSort(arr, i+1, right);
}
Java企业级开发注意事项
1 注释
一个完美的程序一定有一个完美的注释
方法和变量注释尽量使用文本注释
2,取名一定要见名知意
3 差错
错误提示 一般都是从下向上提示
另有:程序差错,第一分析错误类型,第二分析,产生的原因,第三分析逻辑,最后分析单词有没有写错,变量的位置合不合适,一步一步排查。
4对案例分析要全面:
* 对于复杂的问题,要考虑多种情况,要考虑的全面 也就是,平时所说的,程序健壮性邀请
* 对于变量和参数,一定要判断是否为空,或值等于0
* 在思路出来和写代码之前先把种种情况分析出来
* 还有就是注意<=和<区别 不可忽视
5较长(>80字符)的语句,表达式和参数要分多行,长表达式要在低优先级操作符划分新行,操作符放在行首,新行要适当缩进,整齐,语句可读.
6对齐只用空格键,不用TAB键
说明: 以免使用不同的编辑器阅读程序时,因TAB键所设置的空格数不同而造成程序布局不整齐,JBuildr,UltraEdit等编辑环境,支持行首TAB替换成空格,应将该选项打开
7. 代码不要写在一个类中,各个逻辑能封装成方法尽量封装方法,方法和类,先使用在创建。
8. 静态代码块的使用:如果需要调用一个方法 或变量许多次,回着有限属性需要在使用类的时候就初始化 ,这时应该把这些变量和方法的调用放在静态代码块中。
静态语句块一般是用来初始化类的,通常不放在方法体中。不管类执行多少次,它都只在类第一次执行时执行一次