Java学习笔记(2)

●向上转型,向下转型,

向上转型,就是子类转型成父类。如: 麻雀是鸟, 这是非强制的说法;

向下转型,就是父类转型成子类。如: 鸟是麻雀, 这是强制的说法;

Java中不被static修饰的方法,

如果去掉mul()方法的static修饰符, 第六行会提示: Cannot make a static reference to the non-static method mul(int) from the type

demo

1:没有static修饰的方法,在调用的时候需要先创造对象 类名 对象名=new 类名(); 对象名.方法名(); 2:static修饰的方法,在调用的时候直接调用 类名.方法名(); 也就是说: 没有static修饰的,它们在生成的时候,就属于对象。 有static修饰的,它们在生成的时候,就属于类。 main方法是java自带的,我们创建它的时候,就已经注定了它的必然性——静态方法。 在静态方法中,只能访问静态的变量,还有静态的其他方法。

static方法只能访问static方法,非static方法可以访问static方法,

原因:

static是属于类的,而非static的普通方法是属于对象的。

属于类的静态方法可以在对象不存在的时候就能访问到,而普通方法必须先new一个对象才能用这个对象访问。

为什么main()方法是static?

因为在程序开始执行时必须调用main()

我们知道,在C/C++当中,这个main方法并不是属于某一个类的,它是一个全局的方法,所以当我们执行的时候,c++编译器很容易的就能找到这个main方法,然而当我们执行一个java程序的时候,因为java都是以类作为程序的组织单元,当我们要执行的时候,我们并不知道这个main方法会放到哪个类当中,也不知道是否是要产生类的一个对象,为了解决程序的运行问题,我们将这个main方法定义为static,这样的话,当我们在执行一个java代码的时候,我们在命令提示符中写:java PointPoint为一个类),解释器就会在Point这个类当中,去调用这个静态的main方法,而不需要产生Point这个类的对象,当我们加载Point这个类的时候,那么main方法也被加载了,作为我们java程序的一个入口。

JavaBean的解释

JavaBean是符合某种规范的Java组件,也就是一个可以重复使用的Java, 这个类创建的一个对象称做一个Bean。这个类具有如下特点:

1. 创建JavaBean类时必须带有包名, 且必须实现序列化接口;

Java"对象序列化"能让你将一个实现了Serializable接口的对象转换成一组Byte,这样日后要用这个对象时候,你就能把这些byte数据恢复出来,并据此重新构建那个对象了。

2. 必须具有一个访问属性为public的无参构造函数;

3. 类中所有的属性都必须定义为私有的, 所有方法都必须定义为公有的;

4. 私有化的属性必须通过public类型的方法暴露给其他程序,并且方法的命名也必须遵守一定的命名规范, 具体来说:

① 如果属性名是xxx,那么为了更改或获取属性,在类中可以使用两个public方法:

getXXX():用来获取属性xxx

setXXX():用来修改属性xxx.

② 对于boolean类型的成员变量,允许使用"is"代替上面的"get""set", 如。

 

JavaBean的作用:

JavaBean可以非常好地实现控制逻辑、业务逻辑、表示层之间的分离,从而大大降低了它们之间的耦合度。

 

  • 有关内省/反射(reflection)

内省:把一类中需要进行设置和获得的属性访问权限设置为private(私有的)让外部的使用者看不见摸不着,而通过public(共有的)setget方法来对其属性的值来进行设置和获得,而内部的操作具体是怎样的?外界使用的人不用知道,这就称为内省。

JavaBean案例:

package JavaBean;

import java.io.Serializable;

public class JavaBeanDemo implements Serializable { // 实现了Serializable接口

    public JavaBeanDemo() {

    } // 无参的构造方法

    private int id; // 私有属性Id

    private String name; // 私有属性name

    public int getId() {

        return Id;

    }

    public void setId(int id) { // set()方法

        this.id = id;

    }

    public String getName() { // get()方法

        return name;

    }

    public void setName(String name) {

        this.name = name;

    }

}

JavaBean更实际的案例:

假如有人要用Java实现一个单向链表类,可能会这样写:

// 编译成 java-int-list_1.0.jar

public final class JavaIntList {

static class Node {

public Node next;

public int value;

}

public Node head;

public int size;

}

上述实现为了能够快速获取链表的大小,把链表大小缓存在size变量中。用法如下:

JavaIntList myList = new JavaIntList();

System.out.println(myList.size);

JavaIntList的作者很满意,于是开源了java-int-list库的1.0版。文件名是java-int-list_1.0.jar。发布后,吸引了许多用户来使用java-int-list_1.0.jar。
有一天,作者决定要节省内存,不要缓存size变量了,把代码改成这样:

// 编译成 java-int-list_2.0.jar

public final class JavaIntList {

static final class Node {

public Node next;

public int value;

}

public Node head;

public int getSize() {

Node n = head;

int i = 0;

while (n != null) {

n = n.next;

i++;

}

return i;

}

}

然后发布了2.0版:java-int-list_2.0.jar。发布后,原有java-int-list_1.0.jar的用户纷纷升级版本到2.0。这些用户一升级,就发现自己的程序全部坏掉了,说是找不到什么size变量。于是这些用户就把作者暴打一顿,再也不敢用java-int-list库了。

这个故事告诉我们,如果不想被暴打致死,你就必须保持向后兼容性。太阳公司在设计Java语言时,也懂得这个道理。所以Java标准库中,绝对不会出现public int size这样的代码,而一定会一开始就写成:

private int size;

public int getSize() { return size; }

让用户一开始就使用getSize,以便有朝一日修改getSize实现时,不破坏向后兼容性。这种public int getSize() { return size; }的惯用手法,就是Java Bean。 

 

 

构造函数的访问属性

public 常用

private 单例

static 不可以(Java中静态的东西都是属于类的,为类服务,构造函数是为了初始化对象,为对象服务)

 

●如果子类没有重写父类的方法,那么supper.父类方法()this.父类方法()的作用是一样的。

重写父类的方法以后, super用来调用父类的方法,this调用子类重写的方法。

属性的情况是一样的。 

public class F{//定义父类

public void s(){//定义父类方法s

}

}

public class K extends F{//定义子类K,继承父类F

public void s(){//定义子类方法s,覆盖父类s

}

public void k2(){

super.s();//通过super,指明调用父类的方法s

this.s();//通过this,指明调用当前类的方法s

}

}

 

superthis的异同:

1)super(参数):调用基类中的某一个构造函数(应该为构造函数中的第一条语句)

2)this(参数):调用本类中另一种形成的构造函数(应该为构造函数中的第一条语句)

3)super: 它引用当前对象的直接父类中的成员(用来访问直接父类中被隐藏的父类中成员数据或函数,基类与派生类中有相同成员定义时如:super.变量名 super.成员函数据名(实参)

4)this:它代表当前对象名(在程序中易产生二义性之处,应使用this来指明当前对象;如果函数的形参与类中的成员数据同名,这时需用this来指明成员变量名)

5)调用super()必须写在子类构造方法的第一行,否则编译不通过。每个子类构造方法的第一条语句,都是隐含地调用super(),如果父类没有这种形式的构造函数,那么在编译的时候就会报错。

6)super()this()类似,区别是,super()从子类中调用父类的构造方法,this()在同一类内调用其它方法。

7)super()this()均需放在构造方法内第一行。

8)尽管可以用this调用一个构造器,但却不能调用两个。

9)thissuper不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有super语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。

10)this()super()都指的是对象,所以,均不可以在static环境中使用。包括:static变量,static方法,static语句块。

11)从本质上讲,this是一个指向当前类的对象的指针, 然而super是一个Java关键字。

 

●程序绑定的概念:

绑定指的是一个方法的调用与方法所在的类(方法主体)关联起来。对java来说,绑定分为静态绑定和动态绑定;或者叫做前期绑定和后期绑定.

 

静态绑定:

在程序执行前方法已经被绑定(也就是说在编译过程中就已经知道这个方法到底是哪个类中的方法),此时由编译器或其它连接程序实现。例如:C

针对java简单的可以理解为程序编译期的绑定;这里特别说明一点,java当中的方法只有finalstaticprivate和构造方法是前期绑定

 

动态绑定:

后期绑定:在运行时根据具体对象的类型进行绑定。

若一种语言实现了后期绑定,同时必须提供一些机制,可在运行期间判断对象的类型,并分别调用适当的方法。也就是说,编译器此时依然不知道对象的类型,但方法调用机制能自己去调查,找到正确的方法主体。不同的语言对后期绑定的实现方法是有所区别的。但我们至少可以这样认为:它们都要在对象中安插某些特殊类型的信息。

动态绑定的过程:

虚拟机提取对象的实际类型的方法表;

虚拟机搜索方法签名;

调用方法。 

Java中的变量都是静态绑定的,方法的话只有staticfinal(所有private默认是final)是静态绑定的.

:类的private方法会隐式地被指定为final方法。

 

●实例变量 & 静态变量

实例变量必须创建对象后才可以通过这个对象来使用;

静态变量则可以直接使用类名来引用。 

 

● 类只加载一次

如果不是有特殊用途,每个类在虚拟机中被加载并且只加载一次。

什么时候会加载类?

有三种情况

1.创建对象:new StaticCode();

2.使用类中的静态成员:StaticCode.num=9; StaticCode.show();

3.在命令行中运行:java StaticCodeDemo

 

●静态代码块、构造代码块和构造函数的区别

静态代码块:用于给类初始化,类加载时就会被加载执行,只加载一次。

构造代码块:用于给对象初始化的。只要建立对象该部分就会被执行,且优先于构造函数。

※ 直接在类中定义且没有加static关键字的代码块称为{}构造代码块。

构造函数: 给对应对象初始化的,建立对象时,选择相应的构造函数初始化对象。 

//静态代码块案例:

package demo;

 

class Parent {

    static String name = "hello";

    {

        System.out.println("parent block");

    }

    static {

        System.out.println("parent static block");

    }

 

    public Parent() {

        System.out.println("parent constructor");

    }

}

 

class Child extends Parent {

    static String childName = "hello";

    {

        System.out.println("child block");

    }

    static {

        System.out.println("child static block");

    }

 

    public Child() {

        System.out.println("child constructor");

    }

}

 

public class StaticIniBlockOrderTest {

 

    public static void main(String[] args) {

        new Child();

    }

}

 

parent static block

child static block

parent block

parent constructor

child block

child constructor

对象的初始化顺序:首先执行父类静态的内容,

父类静态的内容执行完毕后,接着去执行子类的静态的内容,

当子类的静态内容执行完毕之后,再去看父类有没有非静态代码块,如果有就执行父类的非静态代码块,

父类的非静态代码块执行完毕,接着执行父类的构造方法;

父类的构造方法执行完毕之后,它接着去看子类有没有非静态代码块,如果有就执行子类的非静态代码块。子类的非静态代码块执行完毕再去执行子类的构造方法。

总之一句话,静态代码块内容先执行(父类静态的内容执行完毕后,接着去执行子类的静态的内容),接着执行父类非静态代码块和构造方法,然后执行子类非静态代码块和构造方法。 

再如:

class Code{
    {
      System.out.println("Code的构造块");
    }

 


										static{
        System.out.println("Code的静态代码块");
        }

 


										public Code(){
        System.out.println("Code的构造方法");
        }
    }

 

 

public class CodeBlock03{
     {
      System.out.println("CodeBlock03的构造块");    
     }

 


										static{
        System.out.println("CodeBlock03的静态代码块");
        }

 


										public CodeBlock03(){
             System.out.println("CodeBlock03的构造方法");
            }

 


										public static void main(String[] args){
            System.out.println("CodeBlock03的主方法");

										new Code();

										new Code();

										new CodeBlock03();

										new CodeBlock03();
          }
    }
/*
CodeBlock03的静态代码块
CodeBlock03的主方法
Code的静态代码块
Code的构造块
Code的构造方法
Code的构造块
Code的构造方法
CodeBlock03的构造块
CodeBlock03的构造方法
CodeBlock03的构造块
CodeBlock03的构造方法
*/

●为什么要用父类引用指向子类对象?

Shape s = new Circle(5,6,4);

可以用这几个关键词来概括:多态、动态链接、向上转型

 

例如父类person有子类menwomen, 现在要写一个对象数组,其元素包括子类men类型和women类型的, 我们写成:

person[] a=new person[]{new men("小明"),new women("小红")};

这其实就是一个父类引用指向子类对象, 如果用子类是不行的.

● 抽象类和接口的异同

相同点

都不能被直接实例化,但可以定义抽象类或接口类型的引用变量,并且可以通过继承实现其抽象方法。

都是面向抽象编程的技术基础,实现了诸多的设计模式。

 

不同点

抽象类不能实现多继承;接口支持多继承。

抽象类既可以定义抽象方法,也可以定义非抽象方法;接口只能定义抽象方法。

抽象类可以有普通数据成员,接口不可以有普通数据成员(只能有全局常量)

 

形象比喻

接口是对动作的抽象,抽象类是对根源的抽象。

抽象类表示的是,这个对象是什么。接口表示的是,这个对象能做什么。比如,男人,女人,这两个类(如果是类的话……),他们的抽象类是人。说明,他们都是人。

人可以吃东西,狗也可以吃东西,你可以把"吃东西"定义成一个接口,然后让这些类去实现它.

所以,在高级语言上(不包括C++和Python),一个类只能继承一个类(抽象类)(正如人不可能同时是生物和非生物),但是可以实现多个接口(吃饭接口、走路接口)

 

接口函数就是某个模块写的主要用来给其它模块调用的函数。

简单的说接口函数就是一个类中的公有函数, 它提供程序与类的私有成员之间的接口, 一个对象可以通过它来访问类私有成员.

例如:

SendMessage(...); 是一个发消息的函数,我们无须知道它是怎么实现的,只需要知道他能实现向某个东西发送消息即可.

 

举个例子,搅拌机是一个类,把苹果,橘子,梨等水果(参数)放进去(发送消息)就出来果汁(结果,返回值).

class 搅拌机

{

private:

    零件;

public:

    饮料 搅拌(水果); // 这个就是接口

};

int main(void)

{

    搅拌机 A = new 搅拌机;

    饮料 苹果汁 = A-> 搅拌(一个黄元帅,两个红富士);// 调用接口

    delete A;

    return 0;

}

 

这就是接口,接口对应的就是实现。

 

每一个*.class的文件都对应一个类或则接口,*.class文件是JVM真正能读懂的文件格式。

 

//下面的不同点暂时不用深入

接口是一组行为规范;抽象类是一个不完全的类,着重族的概念。

接口可以用于支持回调;抽象类不能实现回调,因为继承不支持。

接口只包含方法、属性、索引器、事件的签名,但不能定义字段和包含实现的方法;抽象类可以定义字段、属性、包含有实现的方法。

接口可以作用于值类型和引用类型;抽象类只能作用于引用类型。例如,Struct就可以继承接口,而不能继承类。

//抽象类(abstract class)的定义方式如下:

public abstract class AbstractClass //抽象类里面至少有一个抽象方法

{

    public int t; //普通数据成员

    public abstract void method1(); //抽象方法,抽象类的子类在类中必须实现抽象类中的抽象方法

    public abstract void method2();

    public void method3(); //非抽象方法

    public int method4 (){

    //抽象类中可以赋予非抽象方法方法的默认行为,即方法的具体实现

    }

}

   

//接口(interface)的定义方式如下:

public interface Interface

{

    static final int i; //接口中不能有普通数据成员,只能够有静态的不能被修改的数据成员,static表示全局,final表示不可修改,可以不用static final 修饰,因为会隐式的声明为staticfinal

    public void method1(); //接口中的方法一定是抽象方法,所以不用abstract修饰

    public void method2(); //接口中不能赋予方法的默认行为,即不能有方法的具体实现

}

● 理解"一个类可以实现多个接口,但一个类不能实现多继承"

一个类可以实现多个接口: 接口成员均为全局常量和抽象方法, 即使不同接口存在同名抽象方法,并不会造成方法一个类不能实现多继承: 如果父类中存在同名但不同实现非抽象方法,多继承就造成了混乱

JavaString类的内存分配

1

物理的内存是线性结构,并不存在拥有不同功能的不同区域。

编译器(或者JVM)为了更高效地处理数据,会用不同的算法把内存分为各种区域,不同的区域拥有各自的特性,Java中,内存可以分为栈,堆,静态域和常量池等。(可能有不同的叫法,但逻辑是一致的)

 

2

不同内存区域的功能和特点:

栈区:存放局部变量(变量名,对象的引用等)特点:内存随着函数的调用而开辟,随着函数调用结束而释放。

堆区:存放对象(也就是new出来的东西)特点:可以跨函数使用,每个对象有自己对应的存储空间。

静态域:存放在对象中用static定义的静态成员。

常量池:存放常量。(常量池指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。)

 

3

定义String的方法:

1,String str1 = "hello";

2,String str2 = new String("hello");

第一种方法:引用str1被存放在栈区,字符串常量"hello"被存放在常量池,引用str1指向了常量池中的"hello"(str1中的存放了常量池中"hello"的地址)

第二种方法:引用str2被存放在栈区,同时在堆区开辟一块内存用于存放一个新的String类型对象。(同上,str2指向了堆区新开辟的String类型的对象)

如下图:

Java中String类的内存分配

 

4

这两种方法的区别是什么?

第一种:常量池的字符串常量,不能重复出现,也就是说,在定义多个常量时,编译器先去常量池查找该常量是否已经存在,如果不存在,则在常量池创建一个新的字符串常量;如果该常量已经存在,那么新创建的String类型引用指向常量池中已经存在的值相同的字符串常量,也就是说这是不在常量池开辟新的内存。

String str1 = "hello";

String str2 = "hello";

示意图如图1

第二种:在堆中创建新的内存空间,不考虑该String类型对象的值是否已经存在。换句话说:不管它的 只是多少,第二种方法的这个操作已经会产生的结果是:在堆区开辟一块新的内存,用来存放新定义的String类型的对象。

String str1 = new String("hello");

String str2 = new String("hello");

示意图如果2

Java中String类的内存分配

Java中String类的内存分配

 

5

下面用代码来测试:

public class Test

{

public static void main(String[] args)

{

String str1 = "hello";

String str2 = "hello";

System.out.println(str1 == str2);//true

System.out.println(str1.equals(str2));//true

 

String str3 = new String("hello");

String str4 = new String("hello");

System.out.println(str3 == str4);//false

System.out.println(str3.equals(str4));//true

System.out.println(str1 == str3);//false

System.out.println(str2.equals(str3));//true

//这里涉及到==equals方法的区别,请看我的另一篇文章:《Java中,equals ==的渊》http://jingyan.baidu.com/article/f96699bbc9d6ae894e3c1b81.html

}

}

●如果在创建类的时候没有声明要继承的类 那么java就默认 将它继承于Object

public class A{

/*Code*/

}

 

public class A extends java.lang.Object{

/*Code*/

}

以上两种的等价的public class Test // Object类继承

Object中有3个非常重要的方法

hashcode(), toString(), equals()

Sun都是建议我们写自己的类的时候,最好都重写上述3个方法

 

 

●所有类都从Object类继承。

如果自定义的类没有覆盖toString方法,则对象在调用toString方法时用的是ObjecttoString方法,返回的是:包名.类名@此对象哈希码的无符号十六进制表示。相当于:

getClass().getName() + '@' + Integer.toHexString(hashCode())

 

如果equals没有被覆盖,作用则是判断两个对象是否相同。

 

如果hashCode()没有覆盖,返回该对象的哈希码值。支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。

 

 

 

toString方法

 

hashCode( )

1)hashCode:创建的对象在堆内存中的地址值 经过一系列非常复杂的算法转化而来的int类型的数值,它是一种用于查找的索引值(一对一或多对一的关系),对开发来说无意义。

2)如果两个对象相等话,

    第①个条件:equals返回的结果必须是true

    第②个条件:必须要有相等的hashCode

3)如果两个对象不相等,则hashCode值一定不等

4)判断对象是否相等的时候,必须要重写equalshashCode

 

Java不能像C/C++一样直接查看内存地址, 但可借助OllyDbg, Cheat Engine等工具

 

c17164是12677476的十六进制

 

equals方法

(1)s1 == s2为true,因为s1s2都是字符串字面值"nihao"的引用,指向同一块地址,所以相等。

(2)s1 == s3为false,是因为通过new产生的对象在堆中,s3是堆中对象的引用,而是s1是指向字符串字面值"nihao"的引用,地址不同所以不相等。

 

arraycopy(),

System提供了一个静态方法arraycopy(),我们可以使用它来实现数组之间的复制。其函数原型是: public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

src:源数组;

srcPos:源数组要复制的起始位置;

dest:目的数组;

destPos:目的数组放置的起始位置;

length:复制的长度。 

 

Javastringconcat方法和+的区别concat()和+都是String类的方法

都可以将2个字符串拼接到一块,这一点2这功能相同。

 

但是 + 还可以将 字符串与非字符串(比如数字),拼接在一起,成为字符串。

 

  • concatenate [kən'kætɪneɪt] vt.把 (一系列事件、事情等)联系起来;
  • "1"+"2"结果是"12"; '1'+'2'结果是49+50=99

 

●字符串池

Java虚拟机有一个字符串池,保存着几乎所有的字符串对象。

字符串表达式总是指向字符串池 中的一个对象。

使用new操作创建的字符串对象不指向字符串池中的对象, 但是可以使用intern方法使其指向字符串池中的对象.

(注:如果池中已经有相同的 字符串--使用equals方法确定,则直接返回池中的字符串,否则先将字符串添加到池中,再返回)。池中两个相等的字符串如果使用"=="来比较将返回 真。

 

==和equals

==是判断两个变量或实例是不是指向同一个内存空间

equals是判断两个变量或实例所指向的内存空间的值是不是相同

 

●区别下面两种说法

while(in.readLine()!=null){

String aa=in.readLine();

}

//第一个在读到while(in.readLine()!=null)的时候,如果有数据,那么执行String aa=in,readLine()的时候程序会继续往下读,也就是第一个while里面的读入被抛弃了,如果程序读入的只有是一行的话,那么这行就不会读入!!

 

while(true){

String aa=in.readLine();

}

因此while(true) 是一个无限循环,因为表达式的值一直为真。

 

为了跳出循环,循环体内部要用break语句来跳出。

例如,可以在循环体内部用if来判断,if(x==5)break;

 

Java的正则表达式的运用

Java使用正则表达式需要涉及到PatternMatcher

Pattern和Matcher之间的关系就好比Pattern是做模具的师傅,Pattern将模具(正则表达)做好之后,指派一个小工(matcher)去匹配,matcher要做的就是原材料(即要被匹配的源字符串)和模具(即Pattern中的正则表达式)配对、比较。

 

※ 回想学KMP算法时候学到的Pattern(模式串)

 

matcher.find()matcher.matches()的区别?

find是部分匹配,matches是全部匹配

 

Pattern的静态方法matcher()的作用?

Matcher matcher = pattern.mathcer(scanner.nextLine())

Pattern对象将会使用matcher()方法来生成一个Matcher实例,接着便可以使用这个 Matcher实例(以编译的正则表达式为基础)对目标字符串进行匹配工作,多个Matcher是可以共用一个Pattern对象的。

 

StringBuilder

public StringBuilder delete(int start, int end)

参数

start -- This is the beginning index, inclusive.

end -- This is the ending index, exclusive.

 

ArrayList的元素可以是……

//下面ArrayList的元素可以是

    ArrayList list=new ArrayList();

    list.add("");

    list.add("");

    list.add(0, "");

    list.add(1, "");

    System.out.println(list);

    System.out.println(list.indexOf(""));

    System.out.println(list.get(2));

    

    ArrayList list2=new ArrayList();

    list2.add(new String(""));

    list2.add(new String(""));

    list2.add(0, new String(""));

    list2.add(1, new String(""));

    System.out.println(list);

    System.out.println(list.indexOf(""));

    System.out.println(list.get(2));

[, , , ]

3

[, , , ]

3

 

原文地址:https://www.cnblogs.com/ArrozZhu/p/8382326.html