本文主要说明JAVA中的一些特性。
可变参数
JAVA在进行方法调用的时候,必须要按照方法定义的变量进行参数传递,而若实际要传递的参数个数是不确定的,便可以将实际要传送的参数封装为数组的形式,但是这种形式看起来比较笨拙。
因此从JDK 1.5开始,为了解决任意多个参数的问题,JAVA提供了可变参数的概念,语法为:
[public|protected|private][static][final][abstract] returntype func(vartype ... var) {
[return returnvalue];
}
从参数的形式来看,用户可以使用vartype ... var的形式传递多个参数,而实际这多个参数传递到方法内部将以指定类型的数组进行保存:
public class Demo {
public static void main(String[] args) throws Exception {
System.out.println(func(new String[]{"Hello","world","java"}));
System.out.println(func("Hello","world","java"));
System.out.println(func());
}
public static String func(String ... var) {
String tmp = null;
for(String iter:var) {
tmp += iter;
}
return tmp;
}
}
执行结果为:
nullHelloworldjava
nullHelloworldjava
null
上面的结果可以接收任意数目的参数,也就实现了可变参数,从形式上看,也要直观些。
foreach
这里的foreach也是一种循环的形式,在上一段代码中就已经示范过。
和一般的for循环写法不同,foreach的写法为:
for(vartype var:array) {
//statement;
}
public class Demo {
public static void main(String[] args) throws Exception {
String arr[] = new String[]{"Hello","world","java"};
for(String iter:arr) {
System.out.println(iter);
}
}
}
执行结果为:
Hello
world
java
从这里来看,其执行结果和一般的for循环是一致的。
静态导入
在某些类中提供了很多静态方法,这意味着可以直接使用类名进行方法的调用,而如果该类处于其它包中,首先需要先将该包中的相关类导入。
比如下面的包:
package com.wood.myclass;
public class MyClass {
public static void func(){
String arr[] = new String[]{"Hello","world","java"};
for(String iter:arr) {
System.out.println(iter);
}
}
}
而可以实现如下调用:
import com.wood.myclass.MyClass;
public class Demo {
public static void main(String[] args) throws Exception {
MyClass.func();
}
}
而如果不希望出现类名的话,就可以使用静态导入,静态导入的形式为:
import static package.class.*;
而可以将上面的代码修改为:
import static com.wood.myclass.MyClass.*;
public class Demo {
public static void main(String[] args) throws Exception {
func();
}
}
其打印结果是一样的。
静态导入从形式上看,是将类MyClass中的所有静态方法全部导入了,因此便可以省略类型直接使用。而借用这个思想,若只是想调用某个静态方法,则可以使用下面的形式:
import static com.wood.myclass.MyClass.func;
public class Demo {
public static void main(String[] args) throws Exception {
func();
}
}
这里明确只导入MyClass中的func方法,其打印结果是一样的。
泛型
C/C++中存在模板的定义,以便实现不同类型的参数适配和功能扩展。而JAVA中也存在泛型的概念,其实不管是C/C++中的模板还是JAVA中的泛型,其实都是泛型编程的体现。
该思想的核心在于:类属性或方法的参数在定义数据类型时,可以直接使用标记进行展位,在具体使用时才设置其对应的数据类型。
class A<T> {
private T tmp;
A(T in) {
tmp = in;
}
void setTmp(T in) {
this.tmp = in;
}
T getTmp() {
return tmp;
}
void print() {
System.out.println(tmp);
}
}
public class Demo {
public static void main(String[] args) throws Exception {
A<String> a = new A<String>("Hello");
System.out.println(a.getTmp());
a.setTmp("world");
a.print();
A<Integer> aa = new A<Integer>(10);
System.out.println(aa.getTmp());
aa.setTmp(20);
aa.print();
A aaa = new A("Hello");
System.out.println(aaa.getTmp().getClass());
System.out.println(aaa.getTmp());
aaa.setTmp("world");
aaa.print();
A aaaa = new A(10);
System.out.println(aaaa.getTmp().getClass());
System.out.println(aaaa.getTmp());
aaaa.setTmp(20);
aaaa.print();
func("Hello");
func(10);
}
public static<T> void func(T tmp) {
System.out.println(tmp);
}
}
执行结果为:
Hello
world
10
20
class java.lang.String
Hello
world
class java.lang.Integer
10
20
Hello
10
上面的代码构建了泛型类A和泛型方法func,从而可以接收不同的参数,从而实现了不同类型间的参数适配,但实际上,开发中单个的泛型中可能存在多个泛型类型,不过原理都是一样的。
同时如果设置了泛型,而实际传递的泛型类型为int、double等类型时,又是不可以的,需要使用对应的包装类,如上面的Integer。
而如果不设置泛型类型,在参数传递时,JAVA会自动分辨出对应的数据类型,并使用对应数据类型去接纳这些参数。不过实际开发中,最好还是不要这么使用,否则会带来较大的歧义。
通配符
如果将上面的泛型方法设置为普通方法,并希望接收一个泛型参数的话,由于泛型并没有设置具体的泛型类型,而不同的泛型类型之间不能构成重载,因此此时就可以使用通配符来进行统一适配:
class A<T> {
private T tmp;
A(T in) {
tmp = in;
}
void setTmp(T in) {
this.tmp = in;
}
T getTmp() {
return tmp;
}
void print() {
System.out.println(tmp);
}
}
public class Demo {
public static void main(String[] args) throws Exception {
A<String> a = new A<String>("Hello");
A<Integer> aa = new A<Integer>(10);
func(a);
func(aa);
}
public static void func(A<?> tmp) {
tmp.print();
}
}
执行结果为:
Hello
10
这里将func方法中的泛型参数的泛型类型设置为通配符?,以适配不同的泛型类型,完成统一动作。
但是很显然上面的func方法中,只是获取了类属性进行打印,并没有进行修改操作,这样是OK的。但如果要在func中使用setter方法进行属性修改,就会出现问题,因为对于泛型来说,不同的泛型类型不能够互相转换。
而如果func方法只接收该类,并没有使用通配符的话,便会出现下面的结果:
class A<T> {
private T tmp;
A(T in) {
tmp = in;
}
void setTmp(T in) {
this.tmp = in;
}
T getTmp() {
return tmp;
}
void print() {
System.out.println(tmp);
}
}
public class Demo {
public static void main(String[] args) throws Exception {
A<String> a = new A<String>("Hello");
A<Integer> aa = new A<Integer>(10);
func(a);
func(aa);
}
public static void func(A tmp) {
System.out.println(tmp.getTmp().getClass());
tmp.print();
tmp.setTmp(10.12);
System.out.println(tmp.getTmp().getClass());
tmp.print();
}
}
执行结果为:
class java.lang.String
Hello
class java.lang.Double
10.12
class java.lang.Integer
10
class java.lang.Double
10.12
上面的代码中,执行结果很奇怪。在未使用通配符的情况下,可以修改类属性,并实现不同类型的转换。而这种使用方法很显然是不科学的,因为会引起很大的歧义。因此实际开发中并不建议这样使用,要使用通配符加以限制。
除了通配符?外,还存在两个子通配符:
? extends class: 设置泛型上限,可以在声明和方法参数上使用
? super class: 设置泛型下限,方法参数上使用
泛型上限意味着可以这设置对应class及其子类,泛型下限意味着只能设置class及其父类。
class A<T> {
private T tmp;
A(T in) {
tmp = in;
}
void setTmp(T in) {
this.tmp = in;
}
T getTmp() {
return tmp;
}
void print() {
System.out.println(tmp);
}
}
public class Demo {
public static void main(String[] args) throws Exception {
A<String> a = new A<String>("Hello");
A<Integer> aa = new A<Integer>(10);
func(aa);
foo(a);
}
public static void func(A<? extends Number> tmp) {
tmp.print();
}
public static void foo(A<? super String> tmp) {
tmp.print();
}
}
若设置了泛型的界限,那么上面的代码便只能分开调用了,因为不符合泛型界限的泛型类型会被禁止使用。
泛型接口
除了类,还可以定义泛型接口。
interface IDemo<T> {
public void print(T tmp);
}
class DemoImpl<P> implements IDemo<P> {
public void print(P tmp) {
System.out.println(tmp);
}
}
public class Demo {
public static void main(String[] args) throws Exception {
IDemo<String> d = new DemoImpl<String>();
d.print("Hello world.");
}
}
执行结果为:
Hello world.
上面的代码中在接口中设置了泛型类型,而在子类中也设置了泛型类型,在实例化子类对象所设置的泛型类型,也是接口中的泛型类型。
但同时子类也可以不设置泛型,而直接为父接口明确定义一个泛型类型。
interface IDemo<T> {
public void print(T tmp);
}
class DemoImpl implements IDemo<String> {
public void print(String tmp) {
System.out.println(tmp);
}
}
public class Demo {
public static void main(String[] args) throws Exception {
IDemo<String> d = new DemoImpl();
d.print("Hello world.");
}
}
执行结果是一样的,不过便不能使用其它泛型类型作为参数类型了。
泛型方法
前面的代码中还提到了泛型方法,这里再看一下:
public class Demo {
public static void main(String[] args) throws Exception {
func("Hello");
func(10);
}
public static<T> void func(T tmp) {
System.out.println(tmp);
}
}
这里泛型方法并不在泛型类中定义,但仍可以使用泛型。在方法的返回值前定义了泛型标记,这样就可以在方法的返回值或参数类型上使用泛型标记进行声明。
枚举
枚举定义
enum Color {
RED,
GREEN,
BLUE;
}
public class Demo {
public static void main(String[] args) throws Exception {
Color c = Color.RED;
System.out.println(c);
}
}
执行结果为:
RED
上面就是枚举的简单使用,但其实枚举只是类结构的加强,JAVA中使用enum定义的枚举类就相当于默认继承java.lang.Enum类,定义为:
public abstract class Enum<E extends Enum<E>>
extends Object
implements Constable, Comparable<E>, Serializable
其中的常用方法为:
protected Enum(String name, int ordinal) // Sole constructor.
final int ordinal() // Returns the ordinal of this enumeration constant (its position in its enum declaration, where the initial constant is assigned an ordinal of zero).
final String name() // Returns the name of this enum constant, exactly as declared in its enum declaration.
这里看一下这两个方法:
enum Color {
RED,
GREEN,
BLUE;
}
public class Demo {
public static void main(String[] args) throws Exception {
Color c = Color.RED;
System.out.println(c.ordinal());
System.out.println(c.name());
for(Color tmp:c.values()) {
System.out.println(tmp.ordinal() + " " + tmp.name());
}
}
}
执行结果为:
0
RED
0 RED
1 GREEN
2 BLUE
上面的ordinal方法会获取枚举对象的序号,name方法会获取枚举对象的名字,values可以通过类调用或方法调用,以对象数组形式返回枚举类中的所有对象。
定义其它结构
既然enum是类结构的加强,那么便也可以使用enum来重新定义枚举。
enum Color {
RED("red"),GREEN("green"),BLUE("blue");
private String name;
private Color(String name) {
this.name = name;
}
public String toString() {
return name;
}
}
public class Demo {
public static void main(String[] args) throws Exception {
Color c = Color.RED;
System.out.println(c.ordinal());
System.out.println(c.name());
for(Color tmp:Color.values()) {
System.out.println(tmp);
}
}
}
执行结果为:
0
RED
red
green
blue
上面使用enum定义了枚举结构,但是有两点需要注意:
- 构造方法不能使用public声明,如果没有无参构造,要手动调用构造传递参数
- 枚举对象必须要放在首行,随后才能够定义属性、构造、方法
既然如此,enum也可以实现接口等操作,但一般枚举结构实际开发中也不会用的那么复杂。
Annotation
在某些代码的方法前可能会使用@来说明,这就是注解,利用注解技术可以回避面向对象中覆写方法名称固定的问题,并且可读性也很强。
@Override
当进行方法覆写时,为了保证子类所覆写的方法是父类中定义过的方法,可以加上@Override注解,这样若用户覆写时出现了错误,就可以在编译时检查出来。
class A {
@Override
public String toString() {
return "Hello world.";
}
}
public class Demo {
public static void main(String[] args) throws Exception {
A a = new A();
System.out.println(a);
}
}
虽然@Override在上面的代码中写与不写区别不大,但是不写在正确覆写时没有问题,但是覆写错误无法验证。
@Deprecated
该注解表示该方法不建议使用,如果开发中继续使用该方法,就会出现警告信息。
@SuppressWarnings
该注解可以在有可能出现警告信息的代码上使用,以压制所有出现的警告信息。
class A<T> {
private T tmp;
A(T in) {
tmp = in;
}
void setTmp(T in) {
this.tmp = in;
}
T getTmp() {
return tmp;
}
void print() {
System.out.println(tmp);
}
}
public class Demo {
@SuppressWarnings({"rawtypes","unchecked"})
public static void main(String[] args) throws Exception {
A<String> a = new A<String>("Hello");
System.out.println(a.getTmp());
a.setTmp("world");
a.print();
}
}
上面的代码中在编译时由于setTmp方法没有对变量做检查,因此会出现警告,而使用该注解可以去除这些警告信息。
@FunctionalInterface
表示该接口为函数式接口,里面只允许定义一个抽象方法。可与Lambda表达式一起使用。
Lambda
C/C++中后来也扩展了Lambda表达式,JAVA中也存在这一特性。
Lambda表达式指的是应用在单一抽象方法(Single Abstract Method, SAM)接口环境下的一种简化定义形式,可以用于解决匿名内部类的定义复杂问题。
先看下匿名内部类的使用:
interface IDemo {
public void print();
}
public class Demo {
public static void main(String[] args) throws Exception {
func(new IDemo() {
@Override
public void print() {
System.out.println("Hello world");
}
});
}
public static void func(IDemo tmp) {
tmp.print();
}
}
如果使用Lambda表达式:
interface IDemo {
public void print();
}
public class Demo {
public static void main(String[] args) throws Exception {
func(()->System.out.println("Hello world"));
}
public static void func(IDemo tmp) {
tmp.print();
}
}
两者的执行结果一致,但是Lambda的形式就要比匿名内部类简洁很多。Lambda表达式形式为:
(var)->statements
其中var表示参数,statements表示函数体。
interface IDemo {
public void print();
}
interface IDemo2 {
public void print(String str);
}
public class Demo {
public static void main(String[] args) throws Exception {
func(()->System.out.println("Hello world"));
foo((str)-> {
System.out.println(str);
});
}
public static void func(IDemo tmp) {
tmp.print();
}
public static void foo(IDemo2 tmp) {
tmp.print("Hello world");
}
}
执行结果为:
Hello world
Hello world
接口IDemo中print方法为无参方法,而IDemo2中的print方法存在一个String参数,因此在实际上main中foo的参数为函数体,而参数传入在foo的定义中,这样看起来和普通的方法不同,普通方法调用是方法中为函数体,调用处传参,这里倒成了方法中传参,调用处为函数体。
利用上面理解,看下面的代码:
@FunctionalInterface
interface IDemo {
public int add(int ... args);
static int sum(int ... args) {
int res = 0;
for(int tmp:args) {
res += tmp;
}
return res;
}
}
public class Demo {
public static void main(String[] args) throws Exception {
func((int ... parm)->IDemo.sum(parm));
}
public static void func(IDemo tmp) {
System.out.println(tmp.add(1,2,3));
}
}
上面的代码首先定义了sum的静态方法,然后使用Lambda表达式传递了可变参数,调用sum进行计算。
方法引用
JAVA也支持引用操作,JAVA定义了4种操作形式:
- 引用静态方法:类名称::static方法名
- 引用某个对象的方法:实例化对象::普通方法
- 引用特定类型的方法:特定类::普通方法
- 引用构造方法:类名::new
引用静态方法
@FunctionalInterface
interface IDemo<T,P> {
public T trans(P tmp);
}
public class Demo {
public static void main(String[] args) throws Exception {
IDemo<String, Integer> func = String::valueOf;
System.out.println(func.trans(1000));
}
}
方法引用其实就是构造一个函数式接口去接收要应用的方法,然后实现调用。这里使用IDemo的trans接收String的valueOf方法,实现调用。
普通方法引用
@FunctionalInterface
interface IDemo<T> {
public T trans();
}
public class Demo {
public static void main(String[] args) throws Exception {
String str = "Hello world";
IDemo<String> func = str::toUpperCase;
System.out.println(func.trans());
}
}
和上边的逻辑式一样的。
引用特定类的方法
@FunctionalInterface
interface IDemo<T> {
public int trans(T var1,T var2);
}
public class Demo {
public static void main(String[] args) throws Exception {
IDemo<String> func = String::compareTo;
System.out.println(func.trans("Hello","world"));
}
}
这里“Hello”.compareTo("world")在引用时就变成了func.trans("Hello","world"),逻辑仍然是相同的。
引用构造方法
@FunctionalInterface
interface IDemo<T> {
public T trans(char[] str);
}
public class Demo {
public static void main(String[] args) throws Exception {
IDemo<String> func = String::new;
System.out.println(func.trans(new char[]{'h','e','l','l','o'}));
}
}
这里构造的是String(char[] str)形式,逻辑仍然是相同的。
内建函数式接口
对于函数式接口来说,最多有四类:
- 有参数有返回值
- 有参数无返回值
- 无参数有返回值
- 判断真假
在java.util.function种存在4个核心的函数式接口:
Function:功能型接口
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
}
Consumer:消费型接口
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
}
Supplier:供给型接口
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
Predicate:断言型接口
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
}
代码示例为:
import java.util.function.*;
class A {
static private String name = "Hello world";
static String valueOf(int tmp) {
return "" + tmp;
}
static void print(String str) {
System.out.println(str);
}
static String getName() {
return name;
}
static boolean equal(String tmp) {
return name.equalsIgnoreCase(tmp);
}
}
public class Demo {
public static void main(String[] args) throws Exception {
Function<Integer,String> func1 = A::valueOf;
System.out.println(func1.apply(1000));
Consumer<String> func2 = A::print;
func2.accept("Hello world");
Supplier<String> func3 = A::getName;
System.out.println(func3.get());
Predicate<String> func4 = A::equal;
System.out.println(func4.test("abcd"));
}
}
执行结果为:
1000
Hello world
Hello world
false
这里的内建函数式接口和之前的方法引用是一样的逻辑,无非是JAVA提供的接口而已。