Java 语言结构
基础:包(Package)、类(Class)和对象(Object)
了解 Java 的包(Package)、类(Class)和对象(Object)这些基础术语是非常重要的,这部分内容将概要的介绍这些术语。
包(Package)
Java 使用包来组织类,通常按照业务逻辑将类分组到不同的包中。比如:应用程序的所有图形界面可能被分组到 com.vogella.webapplication.views
包中。
通常的做法是使用公司域名的倒序作为顶层包,比如:公司的域名是 "4byte.cn" 那么这个公司 Java 应用的顶层包名可能是 cn.4byte
。
包的另一个重要用途是避免类命名冲突,类命名冲突是指两个开发人员为他们编写的类使用了同样的全限定名。Java 中类的全限定名是 报名+‘.'+类名,比如:cn.4byte.HelloWorld
。
如果没有包,当两个程序猿同时给他编写的类起名为 Test
时就会产生命名冲突(而且操作系统也无法创建文件)。结合 Java 包机制,我们可以明确的告诉虚拟机我们将使用哪个 Test
类,比如:第一个程序员将 Test
类放到 report
包中,另一个程序员将他写得 Test
类放到 xmlreader
包中,那么他们就可以通过全限定名来明确区分两个类 report.Test
以及 xmlreader.Test
。
类(class)
定义:类是一个模板,用来定义对象的数据以及行为,可以理解类为对象的蓝图。
在 Java 中使用 class
关键字来定义类,类名的第一个字母必须大写。类体需要在'{..}'中定义。如:
MyClass.java:
package test; class MyClass { }
类的数据保存在属性中,类行为由方法实现。Java 源文件需要以 "类名“ + ".java" 的形式保存。
对象(Object)
定义:对象是类的一个实例。 对象是真实的元素具有数据和可执行的操作。每一个对象都是依据类的定义进行创建的。
继承
一个类可以从另一个类派生,我们称之为子类。另一个常用的说法是:一个类扩展另一个类。被派生(或继承或被扩展)的类我们称之为"父类"。
继承允许子类继承父类的方法和行为(这里还没有提到访问限定问题,会在后面介绍),下面的代码演示了如何继承一个类,Java 是单继承体系(与C++不同)一个类只能有一个父类。
MyBaseClass.java:
package com.vogella.javaintro.base; class MyBaseClass { @Override public void hello() { System.out.println("Hello from MyBaseClass"); } } class MyExtensionClass extends MyBaseClass { }
Object是所有类的父类
Java 中所有的类都隐式继承 Object 类。Object 类为每一个 Java 对象定义了下面的一些方法:
-
equals(other)
检查当前对象是否等于other对象 -
getClass()
返回对象的类(Class对象) -
hashCode()
返回对象的唯一标示符 -
toString()
返回当前对象的字符串描述
Java 接口(interface)
接口(interface)
接口是一个契约,用来描述一个实现类可以完成什么任务,接口并没有去实现契约,契约是由实现接口类来实现的。
接口的定义方法跟类很相似,接口中可以定义方法,接口中只能定义抽象方法,不能定义任何具体方法。接口中定义的方法默认都是:public abstract 方法。
接口中可以定义常量,常量默认是:public static final。
实现接口的类需要实现接口中定义的全部方法(如果不想实现部分方法,那么需要定义这个类为抽象类)。如果重写(override)接口中得方法,可以在方法上使用 @override
注解。
下面是定义接口以及实现接口的代码示例:
MyDefinition.java:
package com.vogella.javaintro.base; public interface MyDefinition { // constant definition String URL="http://www.vogella.com"; // define several method stubs void test(); void write(String s); }
MyClassImplementation.java:
package com.vogella.javaintro.base; public class MyClassImplementation implements MyDefinition { @Override public void test() { // TODO Auto-generated method stub } @Override public void write(String s) { // TODO Auto-generated method stub } }
接口进化
Java 8以前不能给接口创建新方法。Java的8引入了默认方法,类可以重载默认方法。
方法的多重继承
如果类实现两个接口且这些接口提供相同的默认方法,Java解释规则如下:
- 父类大于父接口 - 如果类继承父类和父接口的方法,类继承父类方法。
- 子类型大于父类型。
- 在其他情况下的类需要实现的默认方法。见下面列子:
public interface A { default void m() {} } public interface B { default void m() {} } public class C implements A, B { @Override public void m() {} }
实现时可以调用父类方法:
public class C implements A, B { @Override public void m() {A.super.m();} }
函数式接口(Functional interfaces)
所有只有一个方法的接口称之为函数式接口(Functional interfaces)。函数式接口的优势是可以结合lambda表达式(“闭包”或“匿名方法”)一起使用(函数式编程 )
Java 编译器可以自动识别函数式接口,然而最好在函数式接口上使用 @FunctionalInterface
注解来体现你的设计意图。
一些 Java 标准库中的函数式接口:
java.lang.Runnable
java.util.concurrent.Callable
-
java.io.FileFilter
java.util.Comparator
*-
java.beans.PropertyChangeListener
JDK 的 java.util.function
包包含了一些常用的函数式接口:
Predicate<T>:对象的布尔值属性
Consumer<T>:对象的action
Function<T , R>:转换T为R的函数
Supplier<T>:提供T的实例,类似工厂函数。
UnaryOperator<T>:转换T为T的函数
BinaryOperator<T>:转换(T, T)为T的函数
Java基础术语
重写方法和@Override 注解
如果一个类继承另一个类,它会继承父类的方法。如果它想要改变父类的一些方法,可重写这些方法。可以在子类中用相同的方法签名重写父类方法。
你可以使用 @Override
注解明确告诉后续的维护代码的程序员以及 Java 编译器,重写了父类对应方法。
下面的代码演示了如何重写父类的方法:
MyBaseClass.java:
package com.vogella.javaintro.base; class MyBaseClass { @Override public void hello() { System.out.println("Hello from MyBaseClass"); } }
MyExtensionClass2.java:
package com.vogella.javaintro.base; class MyExtensionClass2 extends MyBaseClass { public void hello() { System.out.println("Hello from MyExtensionClass2"); } }
提示: 最好始终在重写父类方法时使用
@Override
注解,这样编译机可以帮助开发人员检查是否正确的重写了父类中对应的方法。
Java 的类型系统
原始数据类型和应用
Java 中主要有两大类类型,原始类型(比如:boolean、short、int、double、float、char 以及 byte)和引用类型(比如:Object 和 String)。
原始数据类型
原始数据类型变量用来描述:数字、布尔值(true/false) 或者字符。原始类型变量不是对象,因此不能通过这些变量执行方法调用。
*,-,+,/
只能在原始类型上使用,不过 +
可以在字符串上使用,代表字符串拼接。
引用类型
引用类型变量代表到一个对象的引用(或者指针)。如果你修改引用类型变量的值,那么这个变量会指向性的对象或者 null
,null
代表空引用或者引用到一个不存在的对象。修改应用对象变量并不会修改它指向的对象。修改指向对象的内容也不会影响指向它的引用。
类型自动装箱(Autobox)和拆箱(Wrapper)
每一个原始类型都有对应的引用类型(或者说对应的类)。这些引用类型可以在一个对象中保存对应的原始类型。比如:java.lang.Integer
和 int。
将原始类型转换到一个引用类型的实例或者相反的过程称之为:装箱和拆箱。Java 会在必要的情况下自动执行这些过程。这允许你在调用参数为对象的方法时传递原始类型,这个过程称之为自动装箱。
变量和方法
变量
Java 程序在运行过程中使用变量来保存过程值。变量可以是原始类型也可以使引用类型。原始类型变量保存对应的值,而引用类型变量保存的是对象的引用(指针)。因 此,如果你比较两个引用类型变量,你实际上是在比较两个引用类型变量是否指向同一个对象。因此,比较对象时需要使用 object1.equals(object2)
。
实例变量
实例变量定义在对象一级,在对象生命周期内都可以访问。实例变量可以赋予任何访问控制并且可以被标注为 final
或者 transient
。
被标注为 final
的实例变量在被赋值后是不能被改变的(实际就是只能被赋值一次)。通常情况下,final变量有3个地方可以赋值:直接赋值,构造函数中,或是初始化块中。
由于在java的语法中,声明和初始化是联系在一起的,也就是说:如果你不显示的初始化一个变量,系统会自动用一个默认值来对其进行初始化(如 int就是0)。对于final变量,在声明时,如果你没有赋值,系统默认这是一个空白域,在构造函数进行初始化,如果同时也是是静态的 (static),则可以在初始化块赋值。
局部变量(Local variable)
局部变量不能赋予除 final
以外的访问控制修饰,final
修饰的局部变量在赋值后不可以被改变。
局部变量不会分配默认值,因此需要在使用前初始化它们。
方法
方法是具备参数表以及返回值的代码块,需要通过对象来调用方法,下面是一个 tester
方法的定义:
MyMethodExample.java:
package com.vogella.javaintro.base; public class MyMethodExample { void tester(String s) { System.out.println("Hello World"); } }
方法可以定义可变参数(var-args),定义类可变参数的方法可以接受0个或者多个值(语法:type ... name;
,一个方法只能定义一个可变参数,而且必须是方法参数表的最后一个参数定义。
重写父类方法:子类方法需要与父类方法有完全相同个数和类型的参数以及相同类型的返回值。
重载方法:重载方法是指多个方法有相同的方法名,但是有不同个数或类型参数,返回类型不同不能区分重载方法。
主方法(Main method)
具有 public static
签名的方法可以用来启动 Java 应用程序(主入口),这个方法通常是 main
方法
public static void main(String[] args) { }
构造函数(Constructor)
类包含它的构造函数,构造函数式在类构造时被调用的方法(执行 new SomeClass 时调用)。构造函数的声明方式与方法类似,唯一的要求就是构造函数名必须与类名相同且不许定义返回类型。
类可以有多个重载的构造函数,参考上一节对重载的描述。每一个类需要有至少一个构造函数。下面构造函数代码示例:
MyConstructorExample2.java:
package com.vogella.javaintro.base; public class MyConstructorExample2 { String s; public MyConstructorExample2(String s) { this.s = s; } }
如果代码中没有明确编写构造函数,编译器会在编译期间隐式添加一个,如果一个类继承与其他类,那么父类的构造函数会被隐式调用。
下面的例子中不需要定义一个无参的空构造函数,如果类中没有定义构造函数,那么编译器会在编译期间为你定义一个构造函数:
MyConstructorExample.java:
package com.vogella.javaintro.base; public class MyConstructorExample { // unnecessary: would be created by the compiler if left out public MyConstructorExample() { } }
构造函数的命名约定是: classname (Parameter p1, ...) { }
。每一个对象是基于构造函数创建的,构造函数是对象可以使用之前被调用的第一个方法。
修饰符(Modifiers)
访问控制修饰符
有三个用于访问控制的关键字: public
、protected
和 private
和 4 种访问控制级别:public
、protected
、default
以及 private
,这些级别用来定义元素对其他组件的可见性。
如果某些元素被声明为 public
。比如:类或者方法,那么它们可以由其它 Java 对象创建或访问。如果某些元素被声明为 private
,比如一个方法,那么这个方法就只能被定义它的类中的元素访问。
访问级别 protected
和 default
很相似,一个 protected
的类只能被同一个包中的类或者它的子类(同一个包或者其他包)访问,default
访问级别的类只能被同一个包中的类访问。
下表是访问级别的总结。
表1. 访问级别
修饰符 | 类 | 包 | 子类 | 全局 |
---|---|---|---|---|
public | Y | Y | Y | Y |
protected | Y | Y | Y | N |
no modifier | Y | Y | N | N |
private | Y | N | N | N |
其他修饰符
-
final
方法:不可以被子类重写 -
abstracct
方法:抽象方法,没有实现的方法 -
synchronized
方法:线程安全的方法,可以是final
方法以及赋予其他任何访问控制 -
native
方法:这种方法用来编写平台相关代码(比如:针对 Linux、Windows或Mac OS X等特定操作系统的本地代码) -
strictfp
:strictfp
关键字可应用于类、接口或方法。使用strictfp
关键字声明一个方法时,该方法中所有的float
和double
表达式都严格遵守FP-strict的限制,符合IEEE-754规范。当对一个类或接口使用strictfp
关键字时,该类中的所有代码,包括嵌套类型中的初始设定值和代码,都将严格地进行计算。严格约束意味着所有表达式的结果都必须是 IEEE 754 算法对操作数预期的结果,以单精度和双精度格式表示。
import语句
使用 import 语句
在 Java 开发中我们需要使用类的全名来访问类,比如:cn.4byte.some_package.SomeClass。
你可以在类中使用 import
语句引入一些类或包,这样你在类中可以不用使用全名称来访问引入的类。
静态导入
static import 特性可以让我们在类中使用公共静态类( public static
定义的类)中的成员(方法、属性),而不需要在使用时指定定义成员的类。
该功能提供了一种类型安全的机制,让我们在代码中使用常量,而不必引用最初定义的常量的类。
更多的java语言结构
类方法和类变量
与实例方法和实例变量不同,类方法和类变量是关联类的。需要使用 类名+'.'+方法名或变量名的方式访问类方法和类变量,如:SomeClass.someMethod
或者 SomeClass.someVariable
。
类方法和类变量需要使用 static
关键字来定义,类方法通常称为静态方法,类变量通常称为静态变量或者静态属性。
类变量的一个例子就是使用 System.out.println("Hello World")
调用 println
函数,这里的 out
就是一个静态域(静态变量),它是 PrintStream
的实例,我们通过它来调用 println
方法。
当你定义类变量后,Java 运行时环境在加载类时就会会为类变量分配固定的内存以及访问地址,因此无论类有多少个实例它的类变量始终指向相同的内存地址。我们可以将类变量理解为全局变量。下面的代码演示如何使用 static
域:
MyStaticExample.java
package com.vogella.javaintro.base; public class MyStaticExample { static String PLACEHOLDER = "TEST"; static void test() { System.out.println("Hello"); } }
Main.java:
package com.vogella.javaintro.base; public class Tester { public static void main(String[] args) { System.out.println(MyStaticExample.PLACEHOLDER); MyStaticExample.test(); } }
如果想要将变量定义为常量,可以使用 static final
关键字声明这个变量。
静态方法只能通过类来访问,不可以使用类的实例访问,它不能直接访问类中的非静态变量和方法。
抽象类和抽象方法
类以及方法可以被声明为抽象的 abstract
。如果一个类包含至少一个抽象方法(只有方法声明,没有方法实现)那么这个类就是一个抽象类,它也需要使用 abstract
关键字声明,并且这个类是不能够被直接实例化的。抽象类的子类需要实现抽象类中的抽象方法,除非子类也是抽象类。
下面是抽象类定义的代码示例:
MyAbstractClass.java:
package com.vogella.javaintro.base; public abstract class MyAbstractClass { abstract double returnDouble(); }
备忘清单
下面列表是你将要做的事情的参考。
12.1 使用类
在 Java 开发中你需要编写很多的类、方法、和实例变量,下面的代码使用 test 作为包名。
表格2
What to do | How to do it |
---|---|
创建MyNewClass类 |
package test; public class MyNewClass { } |
创建 var1 变量 |
package test; public class MyNewClass { private String var1; } |
创建构造函数,为 var1 赋值 |
package test; public class MyNewClass { private String var1; public MyNewClass(String para1) { var1 = para1; // or this.var1= para1; } } |
创建doSomeThing方法 |
package test; public class MyNewClass { private String var1; public MyNewClass(String para1) { var1 = para1; // or this.var1= para1; } public void doSomeThing() { } } |
创建doSomeThing2方法 |
package test; public class MyNewClass { private String var1; public MyNewClass(String para1) { var1 = para1; // or this.var1= para1; } public void doSomeThing() { } public void doSomeThing2(int a, Person person) { } } |
创建doSomeThing3方法。
|
package test; public class MyNewClass { private String var1; public MyNewClass(String para1) { var1 = para1; // or this.var1= para1; } public void doSomeThing() { } public void doSomeThing2(int a, Person person) { } public int doSomeThing3(String a, String b, Person person) { return 5; // any value will do for this example } } |
创建 MyOtherClass 类,以及两个属性 myvalue 和 dog |
package test; public class MyOtherClass { String myvalue; Dog dog; public String getMyvalue() { return myvalue; } public void setMyvalue(String myvalue) { this.myvalue = myvalue; } public Dog getDog() { return dog; } public void setDog(Dog dog) { this.dog = dog; } } |
使用局部变量
局部变量只能在方法中定义
表格3
What to do | How to do it |
---|---|
创建 String 类型的局部变量 | String variable1; |
创建 String 类型的局部变量,并赋值 "Test" | String variable2 = "Test"; |
创建 Person 类型的局部变量
|
Person person; |
创建 Person 类型的局部变量 ,并创建 Person 对象赋值给它 |
Person person = new Person(); |
创建字符串数组
|
String array[]; |
创建 Person 数组并指定数组长度为 5 | Person array[]= new Person[5]; |
创建局部变量 var1 赋值 5 | var1 = 5; |
将 pers1 指向 pers2 | pers1 = pers2; |
创建 ArrayList 元素类型为 Person | ArrayList<Person> persons; |
创建新的 ArrayList 并赋值给 persons | persons = new ArrayList<Person>(); |
创建 ArrayList 元素类型为 Person,并实例化 | ArrayList<Person> persons = new ArrayList<Person>(); |
集成开发环境(IDE)
前面的章节介绍了如何在Shell(命令行)中创建和编译 Java 应用程序。 Java集成开发环境(IDE)提供了大量用于创建Java程序的易用功能。有很多功能丰富的 IDE 比如:Eclipse IDE
更多的信息可以参考 教程。
术语 “创建一个 Java 项目(Create a Java project)”在这里可以理解为在 Eclipse 中创建一个 Java 项目。
练习:创建 Java 对象和方法
创建 Person 类并且实例化它
-
创建一个 Java 项目叫 exercises1 并且使用
com.vogella.javastarter.exercises1
作为包名。 -
创建
Person
类,并且为这个类增加三个实例变量:firstName
、lastName
和age
。 -
使用
Person的
构造函数设置默认值。 - 添加下面的
toString方法并完善TODO内容,用于转换对象为字符串表示。
-
@Override public String toString() { // TODO replace "" with the following: // firstName + " " + lastName return ""; }
-
创建
Main
类,并定义方法public static void main(String[] args)
,在这个方法中创建Person
类的实例。
使用构造函数
为你的 Person
类添加构造函数,构造函数定义两个参数用来传递 first name 和 last name。并将这两个参数的值赋给对应的实例变量。
定义 getter 和 setter 方法
定义用来读取和设置实例变量值的方法,这些方法被称为 getter 和 setter 方法。
Getter 方法命名方式是: get
+实例变量名(实例变量名首字符需要大写)比如: getFirstName()
。
Setter 方法命名方式是:set
+实例变量名(实例变量名手字符需要大写)比如:setFirstName(String value)
。
修改 main
方法,使用 getter 和 setter 方法修改 Person
对象的 last name字段。
创建 Address 对象
创建Address
类用来保存 Person
的地址。
在 Person
对象中增加一个新的 Address
类型实例变量,以及对应的 getter 和 setter 方法。
答案:创建 Java 对象以及方法
创建 Person 类并实例化
Person.java:
package com.vogella.javastarter.exercises1; class Person { String firstname = "Jim"; String lastname = "Knopf"; int age = 12; @Override public String toString() { return firstname + " " + lastname; } }
Main.java:
package com.vogella.javastarter.exercises1; public class Main { public static void main(String[] args){ Person person = new Person(); System.out.println(person); } }
使用构造函数
Person.java:
package com.vogella.javastarter.exercises1; class Person { String firstname; String lastname; int age; public Person(String a, String b, int value){ firstname = a; lastname = b; age = value; } @Override public String toString() { return firstname + " " + lastname; } }
Main.java:
package com.vogella.javastarter.exercises1; public class Main { public static void main(String[] args){ Person p1 = new Person("Jim", "Knopf" , 12); System.out.println(p1); Person p2 = new Person("Henry", "Ford", 104); System.out.println(p2); } }
定义 getter 和 setter 方法
Person.java:
package com.vogella.javastarter.exercises1; class Person { String firstname; String lastname; int age; public Person(String a, String b, int value){ firstname = a; lastname = b; age = value; } public String getFirstName(){ return firstname; } public void setFirstName(String firstName){ this.firstname = firstName; } public String getLastName(){ return lastname; } public void setLastName(String lastName){ this.lastname = lastName; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return firstname + " " + lastname; } }
Main.java:
package com.vogella.javastarter.exercises1; public class Main { public static void main(String[] args){ Person p2 = new Person("Jill", "Sanders", 20); p2.setLastName("Knopf"); System.out.println(p2); } }
创建 Address 对象
Address.java:
package com.vogella.javastarter.exercises1; public class Address { private String street; private String number; private String postalCode; private String city; private String country; public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } public String getNumber() { return number; } public void setNumber(String number) { this.number = number; } public String getPostalCode() { return postalCode; } public void setPostalCode(String postalCode) { this.postalCode = postalCode; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } public String toString() { return street + " " + number + " " + postalCode + " " + city + " " + country; } }
Person.java:
package com.vogella.javastarter.exercises1; class Person { String firstname; String lastname; int age; private Address address; public Person(String a, String b, int value){ firstname = a; lastname = b; age = value; } public String getFirstName(){ return firstname; } public void setFirstName(String firstName){ this.firstname = firstName; } public String getLastName(){ return lastname; } public void setLastName(String lastName){ this.lastname = lastName; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } @Override public String toString() { return firstname + " " + lastname; } }
Main.java:
package com.vogella.javastarter.exercises1; public class Main { public static void main(String[] args) { // I create a person Person pers = new Person("Jim", "Knopf", 31); // set the age of the person to 32 pers.setAge(32); // just for testing I write this to the console System.out.println(pers); /* * actually System.out.println always calls toString, if you do not * specify it so you could also have written System.out.println(pers); */ // create an address Address address = new Address(); // set the values for the address address.setCity("Heidelberg"); address.setCountry("Germany"); address.setNumber("104"); address.setPostalCode("69214"); address.setStreet("Musterstr."); // assign the address to the person pers.setAddress(address); // dispose reference to address object address = null; // person is moving to the next house in the same street pers.getAddress().setNumber("105"); } }
基础语句
if-then 和 if-then-else 语句
if-then
语句是控制语句,如果 if
部分表达式的结果为 true
将执行其定义的代码块。当 if
部分表达式结果为 false
则执行 else
代码块(若有的话)。
下面的代码演示了通过两个方法演示了 if-then
以及 if-then-else
语句:
Switch
switch
语句是一个多条件选择执行语句简称开关语句,类似于 if-else
语句。在 switch
的每一个分支里面都必须写 break
,break
表示退出整个 switch
语句,如果不使用 break
语句则当第一个 case
匹配后,会顺序执行后面的程序代码,而不管后面的 case
是否匹配,直到遇到 break
语句为止,下面是 switch
语句的一个示例:
switch (expression) { case constant1: command; break; // will prevent that the other cases or also executed case constant2: command; break; ... default: } // Example: switch (cat.getLevel()) { case 0: return true; case 1: if (cat.getLevel() == 1) { if (cat.getName().equalsIgnoreCase(req.getCategory())) { return true; } } case 2: if (cat.getName().equalsIgnoreCase(req.getSubCategory())) { return true; } }
布尔操作
使用 ==
来比较两个原始类型是否相同或者比较两个引用类型是否指向同一个对象。使用 equals()
方法比较两个不同的对象是否相等。
&&
和 ||
都是短路方法,意思是一旦表达式中某个条件判断结果已经明确时,方法会停止不再执行后面的条件判断。比如:(true || ...)
结果始终是 true
,(false && ...)
结果始终是 false
。使用示例:
1
|
<span class = "pun" >(<span class = "kwd" >var<span class = "pln" > <span class = "pun" >!=<span class = "kwd" > null <span class = "pln" > <span class = "pun" >&&<span class = "pln" > <span class = "kwd" >var<span class = "pun" >.<span class = "pln" >method1<span class = "pun" >()<span class = "pln" > <span class = "pun" >...)</span></span></span></span></span></span></span></span></span></span></span></span></span></span> |
确保在调用 var.method1()
之前变量 var
始终不为 null
。
表4
Operations | Description |
---|---|
== |
比较,对于原始类型比较两个值,对于引用比较引用的对象地址 |
&&、 |
与 |
!= |
不等于 |
a.equals(b) |
检查字符串 a 是否等于字符串 b |
a.equalsIgnoreCase(b) |
检查字符串 a 是否等于字符串 b,忽略大小写 |
If (value ? false : true) {} |
三元操作,如果 value == true 返回 true |
循环语句
for 循环
循环处理的语句。Java的for语句形式有两种:一种是和C语言中的for语句形式一样,另一种形式用于在集合和数组之中进行迭代。有时候把这种形式称为增强的 for(enhanced for)
语句,它可以使循环更加紧凑和容易阅读。它的一般形式为 for(;;)
语句; 初始化总是一个赋值语句,它用来给循环控制变量赋初值;条件表达式是一个关系表达式,它决定什么时候退出循环;增量定义循环控制变量每循环一次后按什么方式变化。这三个部分之间用";"分开。例如:
for循环定义:
for(initialization; expression; update_statement) { //block of code to run }
示例语句:
public class ForTest { public static void main(String args[]) { for(int i = 1; i < 10; i = i+1) { System.out.println("value of i : " + i); } } }
While循环
while
循环语句是一个控制结构,可以重复的特定任务次数。在执行时,如果布尔表达式的结果为真,则循环中的动作将被执行,否则就跳出循环。
语法:
while(expression) { // block of code to run }
示例:
public class WhileTest { public static void main(String args[]) { int x = 1; while (x < 10) { System.out.println("value of x : " + x); x++; } } }
do-while 循环
do-while
循环与 while
循环非常类似,区别在于 while
循环先进行条件判断再开始循环,do-while
循环则是先循环在进行条件判断。
语法:
do { // block of code to run } while(expression);
示例:
public class DoTest { public static void main(String args[]) { int x = 1; do { System.out.println("value of x : " + x); x++; } while (x < 10); } }
数组
数组是有序数据(同一类型数据)的集合,数组中的项称为元素,数组中的每个元素使用相同的数组名和下标来唯一地确定数组中的元素。数组中的第一个元素下标为 0,第二个元素下标为 1 ... 以此类推。
package com.vogella.javaintro.array; public class TestMain { public static void main(String[] args) { // declares an array of integers int[] array; // allocates memory for 10 integers array = new int[10]; // initialize values array[0] = 10; // initialize second element array[1] = 20; array[2] = 30; array[3] = 40; array[4] = 50; array[5] = 60; array[6] = 70; array[7] = 80; array[8] = 90; array[9] = 100; } }
针对数组和容器增强的 for循环
可以使用下面更简单的 for
循环语句来遍历数组以及容器类,语法为:
for(declaration : expression) { // body of code to be executed }
用法示例:
package com.vogella.javaintro.array; public class TestMain { public static void main(String[] args) { // declares an array of integers int[] array; // allocates memory for 10 integers array = new int[10]; // initialize values array[0] = 10; // initialize second element array[1] = 20; array[2] = 30; array[3] = 40; array[4] = 50; array[5] = 60; array[6] = 70; array[7] = 80; array[8] = 90; array[9] = 100; for (int i : array) { System.out.println("Element at index " + i + " :" + array[i]); } } }
19. 字符串
Java 中的字符串
Java 语言中使用 String
类来表示字符串,所有的字符串,如:"hello" 都是这个类的一个实例。字符串是不可变类型,比如:给String
对象赋新的值会创建一个新的 String
对象。
Java 中的字符串池
Java 使用 String
池来提高字符串对象的内存使用效率。因为 Java 中字符串对象是不可变类型,因此字符串池允许重复使用已存在的字符串而不是每次都创建一个新的。
如果同一个字符串在 Java 代码中被多次使用,Java 虚拟机只会创建一个改字符串实例,并将其保存在字符串池中。
当一个 String
对象创建后,如:String s = "constant"
,字符串 "connstant" 会被保存在池中。不过,new
操作符会强制创建一个新的 String
对象,并为它分配新的内存,比如:String s = new String("constant");
。
在 Java 中比较字符串
在 Java 中需要使用 equals()
来比较字符串对象,比如:s1.equals(s2)
。使用 ==
来比较字符串对象是不正确的,因为 ==
是用来比较对象引用是否相同。由于 Java 使用字符串池,因此 ==
在某些时候会给出正确的结果。
下面的例子将会得到正确的结果:
String a = "Hello"; String b = "Hello"; if (a==b) { // if statement is true // because String pool is used and // a and b point to the same constant }
下面的比较会返回 false:
String a = "Hello"; String b = new String("Hello"); if (a==b) { } else { // if statement is false // because String pool is used and // a and b point to the same constant }
警告:当进行字符串比较时,应该总是使用
equals()
方法。
使用字符串
下面的表格列出了常用的字符串方法
表格5:
Command | Description |
---|---|
"Testing".equals(text1); |
字符串对象 text1 的值等于 "Testing" 时返回 true |
"Testing".equalsIgnoreCase(text1); |
字符串对象 text1 的值等于 "Testing" 时返回 true。忽略大小写 |
StringBuffer str1 = new StringBuffer(); |
声明并实例化一个 StringBuffer 对象 |
str.charat(1); |
返回字符串中位置 1 的字符 |
str.substring(1); |
删除第一个字符 |
str.substring(1, 5); |
返回第2个至第5个字符 |
str.indexOf("Test") |
在字符串中查找 "Test" 并返回位置 |
str.lastIndexOf("ing") |
从后向前在字符串中查找 "ing" 并返回位置 |
str.endsWith("ing") |
检查字符串是否以 "ing" 结尾 |
str.startsWith("Test") |
检查字符串是否以 "Test" 开头 |
str.trim() |
删除字符串前后的空格 |
str.replace(str1, str2) |
将字符串中的 "str1" 替换为 "str2"
|
str2.concat(str1); |
将 "str1" 拼接到 "str2" 尾部 |
str.toLowerCase() /str.toUpperCase() |
转换字符串为小写或大写 |
str1 + str2 |
凭借字符串
|
|
将字符串根据 "-" 分隔成字符串数组 |
Lambda 表达式
什么是 lambda 表达式
Java 编程语言从 Java 8 开始支持 lambda 表达式。Lambda 表达式是可以作为参数使用的一段代码。lambda 表达式允许指定的代码在稍后执行。Lambda 表达式用用于任何函数式接口适用的地方。
lambda 表达式和闭包的区别
lambda 表达式是一个匿名函数,比如:它可以作为参数定义。闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。这意味着闭包可以访问不在他参数列表中的变量,并且可以将闭包赋值给一个变量。
Java 支持 lambda 表达式,但不支持闭包。
引入 lambda 表达式的目的
使用 lambda 表达式比其他 Java 语法结构更加简洁,比如,Java 8 中 Collections
新增了一个 forEach
方法,这个方法可以接受 lambda 表达式,如下例:
List<String> list = new ArrayList<>(); list.add("vogella.com"); list.add("google.com"); list.add("heise.de"); list.forEach(System.out::println);
使用方法引用
在 lambda 表达式中可以使用方法引用,方法引用定义可以通过 CalledFrom::method
来调用的方法,CallFrom 可以是:
-
instance::instanceMethod
-
SomeClass::staticMethod
-
SomeClass::instanceMethod
比如下面代码:
List<String> list = new ArrayList<>(); list.add("vogella.com"); list.add("google.com"); list.add("heise.de"); list.forEach(s-> System.out.println(s));
流(stream)
Java 8 中的流(stream)是什么?
流(stream)是支持串行和并行聚合操作的元素序列。
新增加的Stream API (java.util.stream)引入了在Java里可以工作的函数式编程。这是目前为止对java库最大的一次功能添加,希望程序员通过编写有效、整洁和简明的代码,能够大大提高生产率。
IntStream
用来创建支持串行和并行聚合操作的包含原始 int
类型的元素序列。
package com.vogella.java.streams; import java.util.ArrayList; import java.util.List; import java.util.stream.IntStream; public class IntStreamExample { public static void main(String[] args) { // printout the numbers from 1 to 100 IntStream.range(1, 101).forEach(s -> System.out.println(s)); // create a list of integers for 1 to 100 List<Integer> list = new ArrayList<>(); IntStream.range(1, 101).forEach(it -> list.add(it)); System.out.println("Size " + list.size()); } }
stream 和 lambda 的 Reduction 操作
Reduction 操作接受一个元素序列为输入,反复使用某个合并操作,把序列中的元素合并成一个汇总的结果,参考下面代码:
Task.java:
package com.vogella.java.streams; public class Task { private String summary; private int duration; public Task(String summary, int duration) { this.summary = summary; this.duration = duration; } public String getSummary() { return summary; } public void setSummary(String summary) { this.summary = summary; } public int getDuration() { return duration; } public void setDuration(int duration) { this.duration = duration; } }
StreamTester.java:
package com.vogella.java.streams; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.stream.Collectors; import java.util.stream.IntStream; public class StreamTester { public static void main(String[] args) { Random random = new Random(); // Generate a list of random task List<Task> values = new ArrayList<>(); IntStream.range(1, 20).forEach(i -> values.add(new Task("Task" + random.nextInt(10), random.nextInt(10)))); // get a list of the distinct task summary field List<String> resultList = values.stream().filter(t -> t.getDuration() > 5).map(t -> t.getSummary()).distinct().collect(Collectors.toList()); System.out.println(resultList); // get a concatenated string of Task with a duration longer than 5 hours String collect = values.stream().filter(t -> t.getDuration() > 5).map(t -> t.getSummary()).distinct().collect(Collectors.joining("-")); System.out.println(collect); } }
1
|
<span class = "kwd" > </span> |
类型转换
如果使用不同类型的变量,Java 需要进行显示的类型转换,下面章节是一些例子。
转换为字符串
参考下面的代码将其他类型对象转换为字符串:
// Convert from int to String String s1 = String.valueOf (10); // "10" // Convert from double to String String s2 = String.valueOf (Math.PI); // "3.141592653589793" // Convert from boolean to String String s3 = String.valueOf (1 < 2); // "true" // Convert from date to String String s4 = String.valueOf (new Date()); // "Tue Jun 03 14:40:38 CEST 2003"
22.2 将字符串转换为数字
// Conversion from String to int int i = Integer.parseInt(String); // Conversion from float to int float f = Float.parseFloat(String); // Conversion from double to int double d = Double.parseDouble(String);
从字符串到数字的转换独立于区域设置,比如:它总是使用数字的英语表示方法。在这种表示法中 "8.20" 是一个正确的数字,但德国藏用的 "8,20" 则是一个错误的数字。
要转换类似于德国的数字表示,你需要使用 NumberFormat
类。我们面临的挑战是,当类似于 "98.00" 这类数字表示时, NumberFormat
会将其转换成 Long
而不是 Double
。如果需要转换成 Double
请参考下面的方法,
private Double convertStringToDouble(String s) { Locale l = new Locale("de", "DE"); Locale.setDefault(l); NumberFormat nf = NumberFormat.getInstance(); Double result = 0.0; try { if (Class.forName("java.lang.Long").isInstance(nf.parse(s))) { result = Double.parseDouble(String.valueOf(nf.parse(s))); } else { result = (Double) nf.parse(new String(s)); } } catch (ClassNotFoundException e1) { e1.printStackTrace(); } catch (ParseException e1) { e1.printStackTrace(); } return result; }
22.3 Double 转换为 int
int i = (int) double;
22.4 SQL 日期类型转换
使用下面的类将 Date
类型转换为 SQL 的 Date类型
package test; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; public class ConvertDateToSQLDate { private void convertDateToSQL(){ SimpleDateFormat template = new SimpleDateFormat("yyyy-MM-dd"); java.util.Date enddate = new java.util.Date("10/31/99"); java.sql.Date sqlDate = java.sql.Date.valueOf(template.format(enddate)); } public static void main(String[] args) { ConvertDateToSQLDate date = new ConvertDateToSQLDate(); date.convertDateToSQL(); } }
计划任务
Java 支持计划任务,计划任务可以被执行一次或多次。
使用 java.util.Timer
和 java.util.TimerTask
来完成计划任务。实现 TimeTask
的对象将会由 Time
在指定的时间间隔执行。
MyTask.java:
package schedule; import java.util.TimerTask; public class MyTask extends TimerTask { private final String string; private int count = 0; public MyTask(String string) { this.string = string; } @Override public void run() { count++; System.out.println(string + " called " + count); } }
ScheduleTest.java:
package schedule; import java.util.Timer; public class ScheduleTest { public static void main(String[] args) { Timer timer = new Timer(); // wait 2 seconds (2000 milli-secs) and then start timer.schedule(new MyTask("Task1"), 2000); for (int i = 0; i < 100; i++) { // wait 1 seconds and then again every 5 seconds timer.schedule(new MyTask("Task " + i), 1000, 5000); } } }