Java SE 2nd day:Object-oriented 01

(整理源于网络)

Java SE 2nd day:Object-oriented 01

1、本次课程知识点

● 面向对象的主要特点;

● 类与对象的关系;

● 对象的引用传递的初步分析;

● private封装性的实现;

● 构造方法的定义及使用;

● 匿名对象的操作;

● String类的特点及常用方法;

2、具体内容

2.1 认识面向对象(理解)

面向对象是一种现在最流行的程序设计方法,在面对对象的设计出现之前用的都是面向过程的程序设计方式,至于两者的区别,只能通过一句简单的话概括:面向对象具备很强的重用性,而面向过程的开发,只适合一次性的开发操作。

最早的面向对象的概念是由IBM Smalltalk语言所提出,而后发展出了C++和Java,而对于面向对象本身有三大主要的特点:

● 特点一:封装性,保护内部的东西对外不可见;

● 特点二:继承性,扩充类的功能;

● 特点三:多态性,在某一个范围之内,任意的改变所属的类的形式;

在面向对象之中,除了以上的三个特征之外,还有如下的三个开发过程:

OOA:面向对象的分析;

OOD:面向对象的设计;

● OOP:面向对象的程序;

面向对象的唯一好处就在于,与现实生活是完全接轨的,而且没有这么多的意外出现。

2.2 类与对象(重点

2.2.1 类与对象的基本定义

 类和对象是面向对象之中最基本的组成单元,也是以后的所以程序的开发基础,下面首先分析这两者的关系:

● 类:表示某一个群体的共同特征,是一个抽象的概念;

类中主要有两个部分组成:

1、成员(属性):用于描述不同的对象信息;

2、方法:用于完成某些功能的实现;

● 对象: 表示的是一个个体的特征,受到类的控制,类中可以规定出对象的操作行为;

通过以上分析,可以得出以下的结论:

● 在操作的时候应该先有类再有对象;

● 类中规定出来了对象的所有操作的方式(属性、方法),而对象是类的具体使用形式;

● 如果把类比喻成汽车设计图纸的话,那么对象就是那一辆辆的汽车。

类是对象的操作模版,而对象是类的使用实例,对象是其可以看得见摸得着的东西,而类只是一个虚拟的概念。

2.2.2 类和对象的基本操作

类的具体实现,首先在开发之中应该先建立类,而所有的类都使用class关键字定义,类中要包含属性(变量)和方法;

范例:定义一个Person类

class Person{     //定义一个Person

    String name //属性:表示姓名

    int age;      //属性:表示年龄

    public void tell(){      //表示方法

       System.out.println("姓名:"+name+",年龄:"+age);

    }

}

本类定义了两个属性(name、age)以及一个操作方法 (tell) ,而类定义完成之后并不能立刻使用,必须要有对象,而对象的定义格式如下:

格式一:声明并实例化对象

类名称 对象名称 =  new 类名称();

格式二:

声明对象:

类名称 对象名称 =  null;

实例化对象:

对象 =  new 类名称();

此时在对象的定义格式上出现了关键字“new”,此处肯定表示要开辟堆内存空间。

当声明并实例化一个类的对象之后,下面就可以通过如下的语法进行类的操作:

● 操作属性:对象.属性

● 操作方法:对象.方法();

范例:定义对象并使用

class Person{     //定义一个Person

    String name //属性:表示姓名

    int age;      //属性:表示年龄

    public void tell(){      //表示方法

       System.out.println("姓名:"+name+",年龄:"+age);

    }

}

public class Demo {

    public static  void main(String args[]){

       Person per = new Person();  //声明并实例化对象

       per.name="张三";      //调用属性

       per.age=30;          //调用属性

       per.tell();          //调用方法

    }

}

姓名:张三,年龄:30

记住了,区分方法与属性:只要是有“()”的都是属于方法!

如果说现在的Demo类之中,没有设置name和age的属性,那么结果是null和0。

class Person{     //定义一个Person

    String name //属性:表示姓名

    int age;      //属性:表示年龄

    public void tell(){      //表示方法

       System.out.println("姓名:"+name+",年龄:"+age);

    }

}

public class Demo {

    public static  void main(String args[]){

       Person per = new Person();  //声明并实例化对象

       per.tell();          //调用方法

    }

}

姓名:null,年龄:0

此时,由于没有堆name和age属性设置内容,所以都是默认值,String是一个类默认值就是null,而age是一个int型的数据,默认是0,而且既然是引用数据类型,肯定要有对象的内存关系,实际上这个的内存分析和数组是完成类似;

● 堆内存:保存对象的具体信息,实际上就是属性的信息

栈内存:保存了堆内存的地址,简单理解保存的就是对象名称


以下程序是使用“声明并实例化”的操作来定义对象,实际上对象的操作也可以分为两步来完成:

       Person per = null;       //声明对象

       per = new Person();      //实例化对象

此时的内存分析关系图如下:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


所以来说,现在规定出对于以后的对象名称,有两种叫法:

      ● 声明对象:指的是已经开辟了栈内存,但是没有堆内存空间;

      ● 实例化对象:已经明确的开辟了堆内存空间,则表示对象可以使用。

范例:错误的程序

class Person{     //定义一个Person

    String name //属性:表示姓名

    int age;      //属性:表示年龄

    public void tell(){      //表示方法

       System.out.println("姓名:"+name+",年龄:"+age);

    }

}

public class Demo {

    public static  void main(String args[]){

       Person per=null;     //声明对象

       per.age=30;

       per.name="Alex";

       per.tell();          //调用方法

    }

}

本程序只是声明了对象,但是并没有进行对象的实例化操作,所以本程序在运行的时候将出现如下的错误信息:

Exception in thread "main" java.lang.NullPointerException at Demo.main(Demo.java:13)

此时出现了“NullpoiterException”空指向异常,这种异常只发生在引用数据类型之中,表示直接使用了没有开辟堆内存的对象,而且这个问题会一直伴随着程序开发。

2.2.3 深入分析对象的引用传递

以上只是对程序做了一个简单的实现,但是时间的工作之中,对象的引用传递是一个难点,尤其对于初学者而言。

范例:如果说现在产生两个对象,那么这两个对象之间是否会互相影响呢?

class Person{     //定义一个Person

    String name //属性:表示姓名

    int age;      //属性:表示年龄

    public void tell(){      //操作方法

       System.out.println("姓名:"+name+",年龄:"+age);

    }

}

public class Demo {

    public static  void main(String args[]){

       Person per1=new Person();       //声明对象并实例化对象

       Person per2=new Person();       //声明对象并实例化对象

       per1.name="张三";            //设置name属性

       per1.age=30;                //设置age属性

       per2.name="李四";            //设置name属性

        per2.age=40;                //设置age属性

       per1.tell();                //调用方法

       per2.tell();                //调用方法

    }

}

姓名:张三,年龄:30

姓名:李四,年龄:40

此时内存关系图如下:

因为使用的是关键字new,所以在任何情况下都表示会开辟新的内存空间。

类本身是属于引用数据类型,那么既然是引用数据类型,则肯定一块堆内存能同时被多个栈内存所指向,所以下面通过一个引用传递的操作来分析:

class Person { // 定义一个Person

    String name; // 属性:表示姓名

    int age; // 属性:表示年龄

 

    public void tell() { // 操作方法

       System.out.println("姓名:" + name + ",年龄:" + age);

    }

}

 

public class Demo {

    public static void main(String args[]) {

       Person per1 = new Person(); // 声明对象并实例化对象

       Person per2 = null; // 声明第二个对象

       per1.name = "张三"; // 设置name属性

       per1.age = 30; // 设置age属性

       per2 = per1; // 将per1的堆地址给了per2

       per2.name = "李四"; // 修改name属性

       per1.tell(); // 调用方法

    }

}

姓名:李四,年龄:30

不管是数组也好,还是类也好,只要是引用传递,其基本的操作流程都是一样的,就是不同的栈指向同一个堆,而且记住一点:一个栈内存由于保存的是堆内存的地址,所以只能保存一个地址,即:一个栈只能指向一个堆,但是一个堆内存却可以同时被多个栈内存所指向。

范例:进一步分析如下的题目

class Person{     //定义一个Person

    String name //属性:表示姓名

    int age;      //属性:表示年龄

    public void tell(){      //操作方法

       System.out.println("姓名:"+name+",年龄:"+age);

    }

}

public class Demo {

    public static  void main(String args[]){

       Person per1=new Person();       //声明对象并实例化对象

       Person per2=new Person();       //声明第二个对象

       per1.name="张三";        //设置name属性

       per1.age=30;         //设置age属性

       per2.name="李四";

       per2.age=20;

       per2=per1;           //将per1的堆地址给了per2

       per2.name="王五";        //修改name属性

       per1.tell();

       per2.tell();         //调用方法

    }

}

姓名:王五,年龄:30

姓名:王五,年龄:30

通过程序的分析可以发现,在java之中,不使用的堆内存空间(没有栈内存指向)就将成为垃圾,所有的垃圾在默认情况下将等待GC(garbagecollection垃圾收集器)进行不定期的回收,而在实际的开发之中,为了代码的性能,所以垃圾的产生应该越少越好。

2.3 封装性(重点

所谓封装性指的就是类内部的操作对外部不可见,例如,现在有如下一道程序:

class Person { // 定义一个Person

    String name; // 属性:表示姓名

    int age; // 属性:表示年龄

 

    public void tell() { // 操作方法

       System.out.println("姓名:" + name + ",年龄:" + age);

    }

}

 

public class Demo {

    public static void main(String args[]) {

       Person per1 = new Person();

       per1.name = "张三"; // 设置name属性

       per1.age = -30; // 设置age属性

       per1.tell();

    }

}

姓名:张三,年龄:-30

此时将Person对象的age属性设置为了“-30”岁,但并没有造成程序错误,因为int型的数据本身就包含了负数,但是没有任何人的年龄是负数,所以说现在是业务上出现了问题。

而造成此问题的关键就在于Person类中的age属性可以直接被外部所访问,所以这种情况下要想解决此类问题,就必须想办法将age属性变为外部不可见,而此种操作可以通过封装性实现,但是由于java之中封装性的概念比较多,所以今天只是讲解一种最简单的封装使用,直接用private关键字定义。

    private String name; // 属性:表示姓名

    private int age; // 属性:表示年龄

现在在name和age属性上使用了private关键字。则在编译时出现如下错误提示:

一旦使用了private声明之后,这两个属性就表示只能在Person类的内部进行访问,但是如果现在在外部想访问这些属性,则按照java的开发标准来讲,就必须定义getter、setter

● setter:用于设置属性的内容,可以增加一些检查的措施;

● getter:用于取得属性的内容,只是简单的返回;

范例:编写setter、getter

class Person { // 定义一个Person

    private String name; // 属性:表示姓名

    private int age; // 属性:表示年龄

 

    public void setName(String n) {  // 注意setName的书写,N要大写。

       name = n;

    }

 

    public void setAge(int a) {    

       if (a >= 0 && a <= 250) {

           age = a;

       }

    }

 

    public String getName() {

       return name;

    }

 

    public int getAge() {    

       return age;

    }

 

    public void tell() { // 操作方法

       System.out.println("姓名:" + name + ",年龄:" + age);

    }

}

 

public class Demo {

    public static void main(String args[]) {

       Person per1 = new Person();

       per1.setName("张三");

       per1.setAge(-30);

       per1.tell();

    }

}

姓名:张三,年龄:0

注意:此次并没有用到getName和getAge这两个方法

在以后的开发之中,类中的所以属性都必须使用private关键字封装,所以被封装的属性必须使用settergetter方法设置和取得,而且settergetter的编写必须符合规范。

本处只是对封装性做了一个最简单的实现,而实际上封装的完整概念将再最后为大家进行总结,封装实际上就是对于访问权限的控制操作。

2.4 构造方法(重点)

在之前给出过一个对象的实例化格式:

类名称 对象名称 = new 类名称()

可以发现在实例化对象的时候需要执行一个“类名称()”的代码,在之前提示过,只有在方法的调用上才会出现“()”,所以此处所调用的方法实际上就是一个构造方法。

对于java程序而言,如果用户没有在类中明确定义一个构造方法的话,会自动生成一个无参的,什么都不做的构造方法,所以以上的对象实例化的时候就是由java自动为用户生成的构造方法,而如果用户想要明确的定义一个自己的构造方法,则必须满足如下几个要求:

构造方法的方法名称与类名称相同;(用于区别普通方法)

● 构造方法定义的时候没有返回值声明;

范例:定义构造方法,定义无参构造(默认生成的也是此类构造)

class Person { // 定义一个Person

    private String name; // 属性:表示姓名

    private int age; // 属性:表示年龄

   

     public Person(){    //无参构造方法          System.out.println("***********");

    }

}

 

public class Demo {

    public static void main(String args[]) {

        Person per =null;   //声明对象

    }

}

class Person { // 定义一个Person

    private String name; // 属性:表示姓名

    private int age; // 属性:表示年龄

   

     public Person(){//无参构造方法  

       System.out.println("***********");  }

}

 

public class Demo {

    public static void main(String args[]) {

        Person per =null;   //声明对象

        per=new Person();   //实例化对象

    }

}

(无输出)

***********

通过代码的演示可以发现,构造方法是在一个类的对象实例化的时候才进行调用的,所以构造方法在开发之中的主要目的是为类中的属性初始化,这个初始化指的是可以由用户定义的默认值,也可以由外部输入。

范例:通过构造方法传递属性内容

class Person { // 定义一个Person

    private String name; // 属性:表示姓名

    private int age; // 属性:表示年龄

 

    public Person(String name, int age) {

       setName(name);

       setAge(age);

    }

 

    public void setName(String n) {

       name = n;

    }

 

    public void setAge(int a) {

       if (a >= 0 && a <= 250) {

           age = a;

       }

    }

 

    public String getName() {

       return name;

    }

 

    public int getAge() {

       return age;

    }

 

    public void tell() { // 操作方法

       System.out.println("姓名:" + name + ",年龄:" + age);

    }

}

 

public class Demo {

    public static void main(String args[]) {

       Person per = null; // 声明对象

       per = new Person("张三", 30); // 实例化对象

       per.tell();

    }

}

姓名:张三,年龄:30

所以现在有以下两个注意点:

● 第一点:一个类中至少有一个构造方法;

● 第二点:如果一个类中定义属性的时候给出了默认值的话,则这个默认值,必须在构造方法执行完毕之后才可以被赋予;

    private String name="张三"; // 属性:表示姓名,此处定义了默认值

    private int age; // 属性:表示年龄

 

    public Person() { // 无参构造方法

    }

构造方法本身既然是方法,那么就可以按照之前方法重载的概念在一个类中定义多个构造方法。

class Person { // 定义一个Person

    private String name; // 属性:表示姓名

    private int age; // 属性:表示年龄

 

    public Person() {

       System.out.println("无参构造方法。");

    }

 

    public Person(int age) {

       setAge(age); //

    }

 

    public Person(String name) {

       this.setName(name); // 如果此时需要明确的表示出这个方法。则加this,此处不用加也行

    }

 

    public Person(String name, int age) {

       setName(name);

       setAge(age);

    }

 

    public void setName(String n) {

       name = n;

    }

 

    public void setAge(int a) {

       if (a >= 0 && a <= 250) {

           age = a;

       }

    }

 

    public void tell() { // 操作方法

       System.out.println("姓名:" + name + ",年龄:" + age);

    }

}

 

public class Demo {

    public static void main(String args[]) {

       Person per = null; // 声明对象

       per = new Person(); // 实例化对象

       per.tell();

    }

}

无参构造方法。

姓名:null,年龄:0

如果以后一个类之中存在多个构造方法的话,建议的编写顺序:参数少的写在前面,参数多的写在后面,或者是与之相反,总之要有编写的顺序。

问题:为什么构造方法的定义上不写返回值类型,如果不返回写一个void不也可吗?

如果构造方法上写上了void的话,则表示就是一个普通方法了,普通方法是在实例化之后调用的,而构造方法是在对象实例化的时候同时调用的。

2.5 匿名对象(重点

匿名 = 没有名字,按照之前的分析,所有的对象信息都保存在栈和堆,其中栈保持的是地址(名字),而堆保存的是对象的具体信息(是真正有用的东西),所以所谓的匿名对象就是指只开辟了堆内存的对象

public class Demo {

    public static void main(String args[]) {

       new Person("张三",50).tell();

    }

}

姓名:张三,年龄:50

由于匿名对象没有任何的名字,所以在使用之后将成为垃圾,等待GC进行回收,所以以后只使用一次的实例化对象就使用匿名对象表示。

2.6 思考题(重点

现在要求定义一个表示雇员的操作类(emp),里面有如下的属性:雇员编号、姓名、职位、基本工资、奖金,要求可以取得一个雇员的完整信息,也可以计算出雇员的月薪和年薪收入。

本程序的最终效果与之前所写的类是一样的,以后面对的类都有一个统一的名称——简单java类,即:只包含属性、setter、getter方法的操作类;

对于所有的简单java类开发要求如下:

● 根据已有的要求定义类的名称——Emp.java;

● 根据给出的提示编写相应的属性,但是所有的属性都必须使用private关键字进行封装;

● 封装之后的属性如果需要被外部所操作,则要编写对应的setter、getter方法;

● 类中的所有方法都不允许直接使用System.out.println()输出,所有的输出内容返回给被调用处输出;

● 可以编写构造方法进行属性内容的传递,但是类中一定要保留一个无参构造方法。

范例:Emp定义:

class Emp{    //定义Emp

    private int empno;

    private String ename;

    private String job;

    private double sal;

    private double comm;

   

    public Emp(){}

    public Emp(int empno,String ename,String job,double sal,double comm){

       this.setCom(comm);

       this.setEmpno(empno);

       this.setEname(ename);

       this.setJob(job);

       this.setSal(sal);

    }

    public String getEmpInfo(){

       return "雇员信息: "+"\n"+

              "\t|- 编号: "+this.getEmpno()+"\n"+

              "\t|- 姓名: "+this.getEname()+"\n"+

              "\t|- 职位: "+this.getJob()+"\n"+

              "\t|- 工资: "+this.getSal()+"\n"+

              "\t|- 奖金: "+this.getComm()+"\n"+

              "\t|- 月薪: "+this.salary()+"\n"+

              "\t|- 年薪: "+this.income();

    }

   

    public double salary(){

       return sal+comm;

    }

    public double income(){

       return this.salary()*12;

    }

   

    public void setEname(String n){

       ename=n;

    }

    public void setEmpno(int n){

       empno=n;

    }

    public void setJob(String j){

       job=j;

    }

    public void setSal(double s){

       sal=s;

    }

    public void setCom(double c){

       comm=c;

    }

   

    public int getEmpno(){

       return empno;

    }

    public String getEname(){

       return ename;

    }

    public String getJob(){

       return job;

    }

    public double getSal(){

       return sal;

    }

    public double getComm(){

       return comm;

    }

   

}

范例:编写测试程序,测试Emp类

public class Demo {

    public static void main(String args[]) {

       Emp emp=new Emp(7369,"SMISH","CHERK",800,10);

       System.out.println(emp.getEmpInfo());

    }

}

雇员信息:

    |- 编号: 7369

    |- 姓名: SMISH

    |- 职位: CHERK

    |- 工资: 800.0

    |- 奖金: 10.0

    |- 月薪: 810.0

    |- 年薪: 9720.0

2.7 String类(核心重点

在java中,String是一个较为特殊的类,下面通过若干个分析进行一个初期的全面介绍。

2.7.1 String类对象的实例化方式

String肯定是一个类,但是这个类的对象却有两种实例化形式,分别如下:

● 直接将一个使用“"”定义的字符串赋给String对象;

● 使用String类中的构造方法:public String(String  str)

范例:直接赋值

    public static void main(String args[]){

       String str="Hello World.";

       System.out.println(str);

    }

Hello World.

范例:通过构造方法完成

    public static void main(String args[]){

       String str=new String("Hello World.");

       System.out.println(str);

    }

Hello World.

此时只需暂时知道有两种方式可以实例化String类的对象,而这种方式的区别之后再解释。

2.7.2 String类对象的比较

在java之中会直接提供一个“= =”来的操作符来进行两个变量的比较。

范例:使用“= =”进行比较

    public static void main(String args[]){

       int x=10;

       int y=10;

       System.out.println(x==y);

    }

true

以上是连个数字相比较所以结果是true,下面使用字符串比较,

范例:进行两个字符串的比较,使用“= =”完成

    public static void main(String args[]){

       String str1="Hello";

       String str2=new String("Hello");

       String str3=str2;

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

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

       System.out.println(str2==str3);

    }

}

false

false

true

字符串的内容是一样的,但比较的结果却不都是一样。

通过内存关系可以发现使用能够“= =”实际上就是进行了数值比较,而用在对象上完成的是两个对象的内存地址的数值比较,并没有比较字符串的内容。

如果要想进行字符串内容的比较,则需要使用String类的一个比较方法完成;

内容比较:public.boolean equals(String other);

    public static void main(String args[]){

       String str1="Hello";

       String str2=new String("Hello");

       String str3=str2;

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

       System.out.println(str1.equals(str3));

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

    }

true

true

true

此时返回的结果全部都是true,证明现在比较的的确是字符串的内容,即:严格来说是比较了堆内存中的内容。

面试题:请解释String的两种比较方式?

● 在String之中可有使用“= =”和equals()两种操作来进行字符串的比较;

● “= =”比较的是两个字符串的内存地址数值,属于数值比较;

● equals()是String类中提供的一个方法,可以用于字符串内容的比较;

最简单的做法就是比较字符串的时候永远使用equals()就可以了。

2.7.3 一个字符串常量就是String的匿名对象

很明显,只要使用了“"”的内容就是字符串,但是在java之中,字符串并不属于一个基本数据类型,所以java会自动的把一个字符串常量当作一个String的匿名对象来处理。

范例:验证字符串是匿名对象

    public static void main(String args[]){

       String str="Hello";

       System.out.println("Hello".equals(str));

    }

true

       "Hello".equals(str)中的“Hello”实际就是一个匿名对象,因为它调用了equals()对象方法。

小技巧:在开发之中采用以上方式可以避免NullPointerException

如果现在要判断一个字符串对象是否与一个字符串常量相等,可以有以下两种写法:

写法一:

正常情况:

存在隐患:

public static void main(String args[]){

     String str="Hello";

      System.out.println(str.equals("Hello"));

}

public static void main(String args[]){

     String str=null;

     System.out.println(str.equals("Hello"));

}

True

NullPointerException

使用写法一虽然在正常情况下可以实现比较操作,但是这种做法有可能会造成空间指向异常。

写法二:

    public static void main(String args[]) {

        String str = null;

        System.out.println("Hello".equals(str));

    }

false

通过字符串常量equals()方法,那么这个常量永远不会是null,所以可以避免指向空间异常,那么在以后的开发之中,建议按照此种方式进行比较。

2.7.4 两种实例化方式的区别

对于String的对象而言,现在给出了两种实例化方式,那么在开发之中使用能够哪种方式更好呢?为了解决这个问题,下面通过一些代码及内存的关系图来加以说明。

1、使用直接赋值

 

    public static void main(String args[]){

       String str="Hello";

       System.out.println("Hello".equals(str));

    }

true

本程序只开辟了一块堆内存和一块栈内存,除了以上的特点之外,下面再来观察如下程序:

    public static void main(String args[]){

       String str1="Hello";

       String str2="Hello";

       String str3="Hello";

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

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

       System.out.println(str2==str3);

    }

true

true

true

在java中存在一种称为共享设计模式的概念,所谓的共享设计指的是在JVM,之中为用户提供一个对象池,当用户创建了一个新的且池中没有新的对象时,除了这个对象分配内心之外,还会在对象池之中进行保留,以后如果有其他对象声明了与之一样的内容时,不会再重复声明,而是从对象池中取出内容继续使用,而String就正好利用了此机制。

当用户采用直接赋值实例化String对象的时候,如果是第一次定义,则会自动的将对象的内容(字符串内容)保存在字符串对象池之中,以后如果其他的字符串对象依然采用直接赋值的话,一块直接通过对象池取出已经保存的内容继续使用,而不会再重新开辟新的空间,所以此时的内存关系图如下:

2、通过构造方法完成

在String类提供了一个构造方法用于String

String str=new String("Hello");

 

本操作会开辟两块堆内存空间,其中有一块空间将称为垃圾,而且使用这种方法定义的字符串对象内容,本身不能自动入池;

    public static void main(String args[]){

       String str1=new String("Hello");

       String str2="Hello";

       String str3="Hello";

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

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

       System.out.println(str2==str3);

    }

false

false

true

但是用户此时却可以通过String类中提供的一个intern()方法手工入池:public.String intern();注:英,intern有实习生的意思。

    public static void main(String args[]){

       String str1=new String("Hello").intern();

       String str2="Hello";

       String str3="Hello";

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

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

       System.out.println(str2==str3);

    }

true

true

true

虽然可以手工入池,但是很明显,这种操作并不便于用户的开发。

面试题:请解释String对象的两种实例化方法的区别?

● String对象的实例化方式有两种:一种是直接赋值,另外一种是通过构造方法完成;

● 直接赋值:只开辟了一个堆内存空间,而且采用了共享设计模式,可以自动的入池,以备下次对象继续使用;

● 构造方法:会开辟两块内存空间,其中有一块空间将成为垃圾,而且不会自动入池,但是可以使用intern()方法进行手工入池;

● 从开发角度而言,很明显使用直接赋值的方式会更好一些。

2.7.5 字符串的内容一旦声明则不可改变

对于字符串的内容在java之中规定是不能改变的,即:如果定义了字符串,则肯定无法修改,但有如下问题:

    public static void main(String args[]){

       String str=new String("Hello");

       str+=" World";

       str=str+"!!!!";

       System.out.println(str);

    }

Hello World!!!!

明显可以修改!此时,程序的输出结果是“Hello World!!!!”,明明是改变了,为什么非说不能改变呢?

下面同内存关系图分析一下;

    笔记:以上也就说明了在Java中只要是遇到“ "…"”都会开辟一个堆内存。

通过以上的分析可以发现,实际上对于String中的字符串内容并没有任何变化,而最后的改变实际上改变的是String对象的内存地址的指向,所以字符串内容依然没有变化,但这样绝对含有垃圾产生,所以在开发之中对于以下的代码必须回避:

    public static void main(String args[]){

       String str="";

       for(int i=0;i<1000;i++){

           str+=i;

       }

       System.out.println(str);

    }

此时的代码实际上相当于“断开--链接”1000次,而且会产生大量的垃圾空间,所以这种代码的性能是很差的,但是在开发之中对于此类代码也并非不能实现,只是不能通过String实现,而通过StringBuffer实现。

2.8 字符串的操作方法(核心重点,背

在开发之中,String类使用的几率是最高的,基本上只要是程序都有String,在String类中提供了许多的操作方法,这些方法下面进行一些归类的讲解,对于以下的所讲解的全部方法,要求记住方法的作用,方法的名称,参数的类型及个数,返回值的类型。

所有的操作方法可以直接通过查找JDK Doc文档取得。

2.8.1 字符与字符串

在许多的编程语言之中,都会把字符当作字符串来处理,这一点在java中也是有所体现的,这些方法可以直接通过文档查询:

No

方法名称

类型

描述

1

public String(char[] value)

构造

将字符数组变为String类

2

public String(char[] value,int offset,int count)

构造

将部分字符数组变为String

3

public char charAt(int index)

普通

返回指定位置上的字符

4

public char[] toCharArray()

普通

将字符串变为字符数组

范例:通过程序来完成字符串和字符数组的互相转换

    public static void main(String args[]){

       String str="hello world";

       char c[]=str.toCharArray();  //将字符串变为字符数组

       for(int i=0;i<str.length();i++){

           System.out.print(c[i]);

           c[i]-=32;   //转大写

       }

       System.out.println("\n"+new String(c)); //全部字符数组变为字符串

       System.out.println(new String(c,1,4));  //部分字符数组变为字符串

    }

hello world

HELLO WORLD

ELLO

现在也可以使用charAt()方法取得一个字符串的指定位置的字符;

思考题:判断一个字符串是否由数字组成

● 首先将字符串变为字符数组;

● 之后字符数组中的每一个元素进行判断,判断其是否是数字,数组范围:‘0’~‘9’。

    public static void main(String args[]) {

       String str = "0123456789";

       System.out.println(isNumber(str) ? "纯数字组成" : "非纯数字组成");

    }

 

    public static boolean isNumber(String data) {

       char c[] = data.toCharArray(); // 变为字符数组

       for (int i = 0; i < c.length; i++) {

           if (c[i] > '9' || c[i] < '0') { // 不是数字

              return false;

           }

       }

       return true;

    }

纯数字组成

对于方法的命名,除了之前给的setter、getter之外,如果方法的返回值是boolean型数据的话,方法名称最好以“is”开头,例如:isNumber();

2.8.2 字节数组与字符串

字节数组也可以和字符串进行转换,

No

方法名称

类型

描述

1

public String(byte[] bytes)

构造

将全部字节数组变为字符串

2

public String(byte[] bytes,int offset,int length)

构造

将部分字节数组变为字符串

3

public byte[] getBytes()

普通

将字符串变为字节数组

4

public byte[] getBytes(String charsetName) throws UnsupportedEncodingException

普通

进行转码操作

范例:验证字符串和字节数组

    public static void main(String args[]) {

       String str = "helloworld";

       byte data[] = str.getBytes(); // 字符串变为字符数组

       for (int i = 0; i < data.length; i++) {

           data[i] -= 32;

           System.out.print(data[i] + "\t");

       }

       str = new String(data); // 将字节数组data转为字符串并存储在str

       System.out.println("\n" + str);

        System.out.println(new String(data, 0, 5)); // 将data中的从下标为0的5字节数据转变为字符串,但没有存储在变量中,用完将成为垃圾

    }

72  69  76  76  79  87  79  82  76  68 

HELLOWORLD

HELLO

通过以上的代码可以发现这种操作和之前的字符数组很相似,但是字节的操作主要用于二进制数据的传输上,在java IO操作中会继续使用

2.8.3 字符串的比较

equals()方法可以用于字符串内容的比较,对于比较的操作有如下几个方法:

No

方法名称

类型

描述

1

public boolean equals(Object anObject)

普通

进行字符串内容的比较,区分大小写

2

public boolean equalsIgnoreCase(String anotherString)

普通

进行字符串内容的比较,不区分大小写

3

public int compareTo(String anotherString)

普通

判断字符串的大于、小于、等于

4

public int compareToIgnoreCase(String anotherString)

普通

判断时,忽略大小写

范例:验证区分大小写的比较

    public static void main(String args[]){

       String str1="hello";

       String str2="Hello";

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

       System.out.println(str1.equalsIgnoreCase(str2)?"相等":"不相等");

    }

false

相等

对于String类中的compareTo()方法使用需要注意,此方法返回的是int型数据,这个数据有三种形式:

● 大于:返回大于0的数字;

● 小于:返回小于0的数字;

● 等于:返回等于0的数字;

范例:比较字符串的大小

    public static void main(String args[]){

       String str1="hello";

       String str2="Hello";

       System.out.println(str1.compareTo(str2));

       System.out.println("Hello".compareTo("hello"));

       System.out.println(str1.compareToIgnoreCase(str2));

 

    }

32

-32

0

但是对于compareTo()方法而言,除了返回以上的几种数据之外,课也可返回另外三种:大于(1),小于(-1),等于(0)。

2.8.4 字符串检索

可以在String中判断是否存在某些字符串,而可以实现此操作的方法有如下几个:

No

方法名称

类型

描述

1

public boolean contains(CharSequence s)

普通

判断指定的字符串是否存在

2

public int indexOf(int ch)

普通

从头查找指定的字符串是否存在,存在则返回字符串的索引,不存在则返回-1

3

public int indexOf(int ch,int fromIndex)

普通

从指定位置开始检索,如果没找到返回-1

4

public int lastIndexOf(int ch)

普通

从后向前查找指定字符串的位置

5

public int lastIndexOf(int ch, int fromIndex)

普通

从指定位置开始由后向前查找

6

public boolean startsWith(String prefix)

普通

判断是否以指定的字符串开头

7

public boolean endsWith(String suffix)

普通

判断是否以指定的字符串结尾

范例:验证以上方法

public class StringDemo {

    public static void main(String args[]){

       String str="##Hello World**";

       System.out.println(str.contains("hello"));

       System.out.println(str.indexOf("World"));

       System.out.println(str.indexOf("World",9));

       System.out.println(str.startsWith("##"));

       System.out.println(str.endsWith("**"));

       int ind=0;

       //先将str.indexOf()方法的返回值给ind变量,随后再判断ind的内容是否是-1

       if((ind=str.indexOf("World"))!=-1){

           System.out.println("the result of index:"+ind);

       }

    }

false

8

-1

true

true

the result of index:8

说明:关于字符串查找操作

通过上面的演示可以发现使用contains()和indexOf()方法都可以完成字符串的查找操作,但是从实际开发来讲,都是使用contains()方法较多,所以建议使用contains()。

2.8.5 字符串替换

在oracle中有一个函数可以完成字符串替换,是replace()函数,同样的功能在String类也有,方法如下:

No

方法名称

类型

描述

1

public String replaceAll(String regex, String replacement)

普通

将满足条件的内容全部替换

2

public String replaceFirst(String regex, String replacement)

普通

替换第一个满足条件的内容

范例:演示替换操作

    public static void main(String args[]){

       String str="Hello World";

       System.out.println(str.replaceAll("l","?"));

       System.out.println(str.replaceFirst("l","?"));

    }

He??o Wor?d

He?lo World

对于替换操作,以上只是列出了字符串的直接替换,而在String类中也有许多替换操作,但是这些操作一般使用较少,所以不一一列出。

2.8.6 字符串截取

在oracle之中使用substr()函数完成字符串的截取操作,而同样的功能在String类中也有。

No

方法名称

类型

描述

1

public String substring(int beginIndex)

普通

从头截取到结尾

2

public String substring(int beginIndex, int endIndex)

普通

截取中间的部分内容

范例:字符串截取

    public static void main(String args[]){

       String str="Hello World";

       System.out.println(str.substring(6));

       System.out.println(str.substring(2,4));

    }

World

ll

在String类之中的字符串截取操作和oracle()中是不一样的,String类中下标只能从0开始。

2.8.7 字符串拆分

在String类中提供了一个可以将字符串按照指定内容拆分的操作

No

方法名称

类型

描述

1

public String[] split(String regex)

普通

全拆分

2

public String[] split(String regex, int limit)

普通

拆分成指定的个数

范例:验证拆分功能实现

    public static void main(String args[]){

       String str="Hello World JAVA";

       String temp[]=str.split(" ");        //全拆分

       for(int i=0;i<temp.length;i++){

           System.out.println(temp[i]);

       }

       System.out.println();

       String data[]=str.split(" ",2); //部分拆分

       for(int i=0;i<data.length;i++){

           System.out.println(data[i]);

        }

    }

Hello

World

JAVA

 

Hello

World JAVA

范例:更细节的拆分

public class Hello {

    public static void main(String args[]) {

        String str = "a1223 a34 a56 a78 a90  "; //结尾有3个空格

        String temp[] = str.split(" "); // 全拆分,忽略结尾空格

        System.out.println("-----全拆分,长度:" + temp.length + "-----");

        for (int i = 0; i < temp.length; i++) {

            System.out.println(temp[i]);

        }

        System.out.println("-----结束-----" + "\n");

        String data[] = str.split("2", 3); // 正数,拆分2

        System.out.println("-----拆分限度为3,长度:" + data.length + "-----");

        for (int i = 0; i < data.length; i++) {

            System.out.println(data[i]);

        }

        System.out.println("-----结束-----" + "\n");

        data = str.split(" ", 7); // 将结尾的空格也进行拆分

        System.out.println("-----拆分限度为7,长度:" + data.length + "-----");

        for (int i = 0; i < data.length; i++) {

            System.out.println(data[i]);

        }

        System.out.println("-----结束-----" + "\n");

        data = str.split(" ", 0); //limit0时,忽略结尾的空格

        System.out.println("-----拆分限度为0,长度:" + data.length + "-----");

        for (int i = 0; i < data.length; i++) {

            System.out.println(data[i]);

        }

        System.out.println("-----结束-----" + "\n");

        data = str.split(" ", -2); //limit为负数时,拆分包括结尾空格,负数的数值对结果没有影响

        System.out.println("-----拆分限度为-2,长度:" + data.length + "-----");

        for (int i = 0; i < data.length; i++) {

            System.out.println(data[i]);

        }

        System.out.println("-----结束-----" + "\n");

    }

}

-----全拆分,长度:5-----

a1223

a34

a56

a78

a90

-----结束-----

 

-----拆分限度为3,长度:3-----

a1

 

3 a34 a56 a78 a90  

-----结束-----

 

-----拆分限度为7,长度:7-----

a1223

a34

a56

a78

a90

 

 

-----结束-----

 

-----拆分限度为0,长度:5-----

a1223

a34

a56

a78

a90

-----结束-----

 

-----拆分限度为-2,长度:8-----

a1223

a34

a56

a78

a90

 

 

 

-----结束-----

 

 

       由上面的范例可以看出,在split(str,n)中的n可以为正数、负数、0。

面试题:现在给定一个IP地址,要求将其拆分

● 很明显现在的IP地址应该按照“.”进行拆分;

    public static void main(String args[]){

       String str="192.168.0.100";

       String temp[]=str.split("\\."); //此处如果不加“\\”,则无结果输出,因为java中“.”表示任意字符

       for(int i=0;i<temp.length;i++){

           System.out.println(temp[i]);

       }

    }

192

168

0

100

在开发之中,split()方法和replaceAll()方法比较特殊,因为都需要“regex”支持,此为正则表达式,如果以后拆不开的问题,就使用“\\”(表示一个“\”)进行转义。

对于需要转义的情况,只观察方法定义的参数是否存在“regex”单词。

 

2.8.8 其他方法

除了以上的可以归纳的方法之外,在String中也有一些小的功能的方法

No

方法名称

类型

描述

1

public boolean isEmpty()

普通

判断是否是空字符,不是null

2

public int length()

普通

取得字符串内容的长度

3

public String toLowerCase()

普通

所有内容变为小写

4

public String toUpperCase()

普通

所有内容变为大写

5

public String trim()

普通

去掉左右空格,中间的无法去掉

范例:验证以上方法

    public static void main(String args[]){

       String str=("      Hello    World    ");

       System.out.println(str.length()+"\t"+str.trim().length());

       System.out.println(str.trim()); //trim()方法只是去掉左右空格,中间的不处理

       System.out.println("".isEmpty()+" "+str.isEmpty());

       System.out.println("to upper case:"+str.toUpperCase());

       System.out.println("to lower case:"+str.toLowerCase());

    }

24  14

Hello    World

true false

to upper case:      HELLO    WORLD   

to lower case:      hello    world   

但是需要强调的是length()方法,因为在数组中有一个length属性可以求出数组的长度,但是这个属性操作的时候后面是没有“( )”的,可是String类中的length()是一个方法,所以要有“( )”。

在oracle中有一个initcap()函数,这个函数可以让开头首字母大写,但是在String类中并没有提供此方法,所以用户可以自己实现一个:

    public static void main(String args[]){

       String str=("hello");

       System.out.println(initcap(str));

    }

    public static String initcap(String str){

       return str.substring(0, 1).toUpperCase()+str.substring(1);

    }

Hello

虽然这样的方法在JDK中没有直接提供,但是一些其他组建包中是有所提供的,例如:commons组建包。

3、总结

1、面向对象的三个特征:封装、继承、多态

2、类和对象的关系、定义、引用传递

3、构造方法的使用

4、private封装的实现

5、简单java类的开发原则:有时候可以成为POJO,也可以成为VO(Value Object)。

● 简单java类的定义原则:只有属性及setter、getter方法所组成的类;

● 根据要求定义出类的名称,并编写属性及对应的setter、getter方法,而且所有的属性必须使用private封装;

● 类中可以提供构造方法为属性进行初始化,但是一定要保留有一个无参构造;

● 类中的所有内容都不允许直接输出,必须返回给被调用初输出;

6、String类的特点及相关的方法

● String类的特点:

|- 有两种实例化方式;

1.        直接字符串赋值:在堆内存之中只开辟一块空间,可以自动入池,以备下次继续使用;

2.        通过构造方法完成:会产生两块堆内存空间,不会自动入池,可以使用intern()方法手工入池;

|- 字符串比较:

1.    “==”:比较的是两个字符串所在的堆内存地址的比较,属于数值比较;

2.   “equals”:是String类中提供的一个方法,进行内容的比较;

|- 一个字符串常量就是String的匿名对象,是会占堆内存的;

|- 字符串的内容一旦声明之后则不可改变,改变的只是内存地址的指向;

● String的常用方法:

|- 字符数组和字符串转换:

Ø  【构造】public String(char c[ ]);

Ø  【构造】public String(char c[ ],intoffset,int len);

Ø  【普通】public char[] toCharArray();

Ø  【普通】public char charAt();

|- 字节数组和字符串转换:

Ø  【构造】public String(byte b[ ]);

Ø  【构造】public String(byte b,intoffset,int len);

Ø  【普通】public byte[ ] getBytes();

Ø  【构造】public byte[ ] getBytes(Stringcharset);

|- 字符串拆分:

Ø  【普通】public String[ ] split(Stringregex);

Ø  【普通】public String[ ] split(Strngregex,int size);

|- 字符串比较:

Ø  【普通】public boolean equals(Stringother);

Ø  【普通】public booleaneuqalsIgnoreCase(String other);

Ø  【普通】public int compareTo(Stingstr);   → >0、<0、=0

|- 字符串检索:

Ø  【普通】public boolean contains(Stringstr);

Ø  【普通】public int indexOf(String str);

Ø  【普通】public int indexOf(Stringstr,int offset);

Ø  【普通】public int lastIndexOf(Stringstr,);

Ø  【普通】public int lastIndexOf(Stringstr,int offset);

Ø  【普通】public boolean startsWith(Stringstr);

Ø  【普通】public boolean endsWith(Stringstr);

|- 字符串截取:

Ø  【普通】public String substring(intbegin);

Ø  【普通】public String substring(int begin,intend);

|- 字符串替换:

Ø  【普通】public Sting replaceAll(StingoldStr,Sting newStr);

Ø  【普通】public StringreplaceFirst(String oleStr,String newStr)

|- 其他方法

Ø  【普通】public int length();

Ø  【普通】public String trim();

Ø  【普通】public boolean isEmpty();

Ø  【普通】public String toLowerCase(Stringstr);

Ø  【普通】public String toUpperCase(Stringstr);

4、作业

1、现在给出如下一个字符串格式:“姓名:成绩 | 姓名:成绩”,例如:给定的字符串是:“Tom:90 | Jerry:80 | Tony:89”,要求可以对数据进行处理,将数据按照如下的形式显示:

● 例如:显示格式 姓名:Tom,成绩:90;

DIY:

import java.util.Scanner;

 

public class DisplayInfo{

    public static void main(String args[]) {

       Scanner scan = new Scanner(System.in);

       String str;

       str = scan.nextLine();

       String strTemp[] = str.split("\\|");

       for (int i = 0; i < strTemp.length; i++) {

           int indexNum = strTemp[i].indexOf(":");

           System.out.println("Name:" + strTemp[i].substring(0, indexNum)

                  + ", Grade" + strTemp[i].substring(indexNum));

       }

    }

}

Tom:90|Jerry:87|Marry:82|Jessica:79

Name:Tom, Grade:90

Name:Jerry, Grade:87

Name:Marry, Grade:82

Name:Jessica, Grade:79

Answers:

    public static void main(String args[]) {

       String str="Tom:90|Jerry:87|Marry:82|Jessica:79";

       String temp[]=str.split("\\|");

       for(int i=0;i<temp.length;i++){

           String result[]=temp[i].split(":");

           System.out.println("Name: "+result[0]+", Grade:"+result[1]);

       }

    }

Name: Tom, Grade:90

Name: Jerry, Grade:87

Name: Marry, Grade:82

Name: Jessica, Grade:79

此题主的目的是训练字符串的拆分操作,记住,拆不开的问题肯定会有,所以只要拆不开的内容就转义“\\”。

2、给定一个email地址,要求验证其是否这确,提示:可以简单的验证一下,重点验证“@”和“ . ”。

下面是几个验证标准:

● 最短email长度是5:a@a.a

● @和. 不能作为开头和结尾;

● @和. 顺序要有定义,假设只有一个. ;

DIY:

import java.util.Scanner;

 

public class CheckEmail{

    public static void main(String args[]) {

        Scanner scan = new Scanner(System.in);

        String str = scan.nextLine();

        if (str.length() < 5 || str.startsWith("@") || str.startsWith(".")

                || str.endsWith("@") || str.endsWith(".") || DotNum(str) > 1) {

            System.out.println("The format of email is wrong.");

        } else

            System.out.println("The format of email is correct.");

    }

 

    public static int DotNum(String str) {

        int num = 0;

        for (int i = 0; i < str.length(); i++) {

            if (str.charAt(i) == '.') {

                num++;

            }

        }

        return num;

    }

}

wolex_li@hotmail.com

The format of email is correct.

Answers:

    public static void main(String args[]) {

       String str = "teatemail@gmail.com";

       System.out.println(isEmail(str));

    }

 

    public static boolean isEmail(String email) {

       if (email == null || email.length() < 5) { // 长度肯定不够

           return false; // 没有必要判断了

       }

       if (!(email.contains("@")) && !(email.contains("."))) {

           return false;

       }

       if (email.startsWith("@") || email.startsWith(".")

              || email.endsWith("@") || email.endsWith(".")) {

           return false;

       }

       if (email.indexOf("@") > email.indexOf(".")) {

           return false;

       }

       return true; // 如果都正确则返回true

    }

true

       注意比较自己写的代码和答案的,答案的明显可读性高,因为把if语句拆分来写,为了提高原来自己写的代码的可读性,可修改为:

package firstCourse;

 

import java.util.Scanner;

 

public class Hello {

    public static void main(String args[]) {

        Scanner scan = new Scanner(System.in);

        String str = scan.nextLine();

        boolean flag = true; //增加一个信号灯

        if (str.isEmpty() && str.length() < 5) {

            flag = false;

        }

        if (str.startsWith("@") || str.startsWith(".")) {

            flag = false;

        }

        if (str.endsWith("@") || str.endsWith(".")) {

            flag = false;

        }

        if (DotNum(str) > 1) {

            flag = false;

        }

        if (flag) { //最后再判断,这样代码的可读性更高

            System.out.println("The format of email is correct.");

        } else

            System.out.println("The format of email is wrong.");

    }

 

    public static int DotNum(String str) {

        int num = 0;

        for (int i = 0; i < str.length(); i++) {

            if (str.charAt(i) == '.') {

                num++;

            }

        }

        return num;

    }

}

       因此,在开发中,要把复杂的或多个的if条件尽量简单化,if中的判断语句可以使用表达式(flag)来代替,让程序的每个结构看起来都显得简洁。

5、测试题

1、【Java】写出java中的数据类型划分及默认值

● 基本数据类型:

|- 数值型:

|- 整  形:byte、short、int、long; → 0

|- 浮点型:float、double;               →0

|- 字符型:char;                                    →空字符:'\u0000'

|- 布尔型:boolean;                               →false

● 引用数据类型:数组、类、接口                 →null

2、【Java】写出&、&&和 |、|| 的区别

● &(普通与)和 |(普通或)指的是所有条件都进行判断;

● &&(短路与)如果前面的条件不满足,则后面不再进行判断,|| (短路或)如果前面的条件满足则后面不再判断;

● 在开发中为了性能的提高,主要用短路与和短路或操作;

● &和|除了用于逻辑运算之外,也可以进行位运算的操作;

3、【Java】完成一个数组的排序操作;

public class BubbleSort{

    public static void main(String args[]){

       int data[]={89,62,82,41,72,93,13,8};

       for(int i=0;i<data.length-1;i++){

           for(int j=0;j<data.length-i-1;j++){

              if(data[j]>data[j+1]){

                  int temp=data[j];

                  data[j]=data[j+1];

                  data[j+1]=temp;

              }

           }

       }

       for(int i=0;i<data.length;i++){

           System.out.print(data[i]+"  ");

       }

    }

}

8  13  41  62  72  82  89  93 

记得补充:也可以使用java.util.Arrays.Sort(数组名称)进行排序。

 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值